Compare commits
31 Commits
8632bb8c3c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ad064725f | |||
| 886dccb6b8 | |||
| 57e8a06383 | |||
| ab14f58079 | |||
| 4106215180 | |||
| cdea376f94 | |||
| d157dfc9b6 | |||
| 773625b842 | |||
| 22ca5c98f3 | |||
| 08deedea8f | |||
| 86b769d7ed | |||
| e139eae06d | |||
| 34e8c1f812 | |||
| cc3128d65a | |||
| d04544030a | |||
| e13c435afa | |||
| 4429bbecd1 | |||
| 2099eeb97e | |||
| c22ed0dc83 | |||
| 494aeeb401 | |||
| 298b1368e8 | |||
| b20a1a5964 | |||
| af8e04473a | |||
| 351f606d29 | |||
| da376647af | |||
| 2ec8d9cfdb | |||
| 2dd75edc03 | |||
| e08580925d | |||
| 63659a4934 | |||
| af4e06411d | |||
| 4426925ebb |
@@ -3,14 +3,17 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/disgoorg/disgo"
|
||||
@@ -26,24 +29,23 @@ import (
|
||||
|
||||
func main() {
|
||||
var (
|
||||
// data options
|
||||
skillsFile string
|
||||
skillGroupsFile string
|
||||
// discord bot options
|
||||
tokenFile string
|
||||
// http api options
|
||||
// public site
|
||||
addr string
|
||||
route string
|
||||
dataDir string
|
||||
public string
|
||||
// discord
|
||||
tokenFile string
|
||||
apiRoute string
|
||||
pubkey string
|
||||
// logging options
|
||||
// logging
|
||||
level slog.Level
|
||||
textfmt string
|
||||
)
|
||||
flag.StringVar(&skillsFile, "skills", "", "json `file` containing skill data")
|
||||
flag.StringVar(&skillGroupsFile, "skill-groups", "", "json `file` containing skill group data")
|
||||
flag.StringVar(&addr, "http", ":80", "`address` to bind HTTP server")
|
||||
flag.StringVar(&dataDir, "data", "", "`dir`ectory containing exported json data")
|
||||
flag.StringVar(&public, "public", "", "`dir`ectory containing the website to serve")
|
||||
flag.StringVar(&tokenFile, "token", "", "`file` containing the Discord bot token")
|
||||
flag.StringVar(&addr, "http", "", "`address` to bind HTTP API server")
|
||||
flag.StringVar(&route, "route", "/interactions/callback", "`path` to serve HTTP API calls")
|
||||
flag.StringVar(&apiRoute, "route", "/interactions/callback", "`path` to serve Discord HTTP API calls")
|
||||
flag.StringVar(&pubkey, "key", "", "Discord public key")
|
||||
flag.TextVar(&level, "log", slog.LevelInfo, "slog logging `level`")
|
||||
flag.StringVar(&textfmt, "log-format", "text", "slog logging `format`, text or json")
|
||||
@@ -61,13 +63,26 @@ func main() {
|
||||
}
|
||||
slog.SetDefault(slog.New(lh))
|
||||
|
||||
byID, byName, err := loadSkills(skillsFile)
|
||||
groups, err2 := loadSkillGroups(skillGroupsFile)
|
||||
stat, err := os.Stat(public)
|
||||
if err != nil {
|
||||
slog.Error("public", slog.Any("err", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
if !stat.IsDir() {
|
||||
slog.Error("public", slog.String("err", "not a directory"))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
skills, err := loadSkills(filepath.Join(dataDir, "skill.json"))
|
||||
slog.Info("loaded skills", slog.Int("count", len(skills)))
|
||||
groups, err2 := loadSkillGroups(filepath.Join(dataDir, "skill-group.json"))
|
||||
slog.Info("loaded skill groups", slog.Int("count", len(groups)))
|
||||
if err = errors.Join(err, err2); err != nil {
|
||||
slog.Error("loading data", slog.Any("err", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
skillsByID, skillsByName, skillGroupMap = byID, byName, groups
|
||||
skillSrv := newSkillServer(skills, groups)
|
||||
slog.Info("skill server ready")
|
||||
|
||||
token, err := os.ReadFile(tokenFile)
|
||||
if err != nil {
|
||||
@@ -83,42 +98,55 @@ func main() {
|
||||
r.Use(middleware.Go)
|
||||
r.Use(logMiddleware)
|
||||
r.Route("/skill", func(r handler.Router) {
|
||||
r.SlashCommand("/", skillHandler)
|
||||
r.Autocomplete("/", skillAutocomplete)
|
||||
r.ButtonComponent("/{id}", skillButton)
|
||||
r.SlashCommand("/", skillSrv.slash)
|
||||
r.Autocomplete("/", skillSrv.autocomplete)
|
||||
r.SelectMenuComponent("/swap", skillSrv.menu)
|
||||
r.ButtonComponent("/swap/{id}", skillSrv.button)
|
||||
})
|
||||
|
||||
opts := []bot.ConfigOpt{bot.WithDefaultGateway(), bot.WithEventListeners(r)}
|
||||
if addr != "" {
|
||||
if pubkey == "" {
|
||||
slog.Error("Discord public key must be provided when using HTTP API")
|
||||
os.Exit(1)
|
||||
}
|
||||
opts = append(opts, bot.WithHTTPServerConfigOpts(pubkey,
|
||||
httpserver.WithAddress(addr),
|
||||
httpserver.WithURL(route),
|
||||
))
|
||||
}
|
||||
|
||||
slog.Info("connect", slog.String("disgo", disgo.Version))
|
||||
client, err := disgo.New(string(token), opts...)
|
||||
client, err := disgo.New(string(token), bot.WithDefaultGateway(), bot.WithEventListeners(r))
|
||||
if err != nil {
|
||||
slog.Error("building bot", slog.Any("err", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("GET /", http.FileServerFS(os.DirFS(public)))
|
||||
if pubkey != "" {
|
||||
pk, err := hex.DecodeString(pubkey)
|
||||
if err != nil {
|
||||
slog.Error("pubkey", slog.Any("err", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
mux.Handle("POST "+apiRoute, httpserver.HandleInteraction(httpserver.DefaultVerifier{}, pk, slog.Default(), client.EventManager.HandleHTTPEvent))
|
||||
slog.Info("Discord HTTP API enabled", slog.String("pubkey", pubkey))
|
||||
}
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
slog.Error("listen", slog.String("addr", addr), slog.Any("err", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
srv := http.Server{
|
||||
Addr: addr,
|
||||
Handler: mux,
|
||||
ReadTimeout: 15 * time.Second,
|
||||
WriteTimeout: 15 * time.Second,
|
||||
BaseContext: func(l net.Listener) context.Context { return ctx },
|
||||
}
|
||||
go func() {
|
||||
slog.Info("HTTP", slog.Any("addr", l.Addr()))
|
||||
err := srv.Serve(l)
|
||||
if err == http.ErrServerClosed {
|
||||
return
|
||||
}
|
||||
slog.Error("HTTP server closed", slog.Any("err", err))
|
||||
}()
|
||||
|
||||
if err := handler.SyncCommands(client, commands, nil, rest.WithCtx(ctx)); err != nil {
|
||||
slog.Error("syncing commands", slog.Any("err", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if addr != "" {
|
||||
slog.Info("start HTTP server", slog.String("address", addr), slog.String("route", route))
|
||||
if err := client.OpenHTTPServer(); err != nil {
|
||||
slog.Error("starting HTTP server", slog.Any("err", err))
|
||||
stop()
|
||||
}
|
||||
}
|
||||
slog.Info("start gateway")
|
||||
if err := client.OpenGateway(ctx); err != nil {
|
||||
slog.Error("starting gateway", slog.Any("err", err))
|
||||
@@ -131,6 +159,10 @@ func main() {
|
||||
ctx, stop = context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer stop()
|
||||
client.Close(ctx)
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
slog.Error("HTTP API shutdown", slog.Any("err", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
var commands = []discord.ApplicationCommandCreate{
|
||||
@@ -144,37 +176,27 @@ var commands = []discord.ApplicationCommandCreate{
|
||||
Required: true,
|
||||
Autocomplete: true,
|
||||
},
|
||||
discord.ApplicationCommandOptionBool{
|
||||
Name: "share",
|
||||
Description: "Share the skill info",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// TODO(zeph): these globals could go away, but there's a bit of ceremony to doing that
|
||||
var (
|
||||
skillsByID map[horse.SkillID]horse.Skill
|
||||
skillsByName map[string]horse.SkillID
|
||||
skillGroupMap map[horse.SkillGroupID]horse.SkillGroup
|
||||
)
|
||||
|
||||
func loadSkills(file string) (map[horse.SkillID]horse.Skill, map[string]horse.SkillID, error) {
|
||||
func loadSkills(file string) ([]horse.Skill, error) {
|
||||
b, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
var skills []horse.Skill
|
||||
if err := json.Unmarshal(b, &skills); err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
byID := make(map[horse.SkillID]horse.Skill, len(skills))
|
||||
byName := make(map[string]horse.SkillID, len(skills))
|
||||
for _, s := range skills {
|
||||
byID[s.ID] = s
|
||||
byName[s.Name] = s.ID
|
||||
}
|
||||
slog.Info("loaded skills", slog.Int("count", len(skills)))
|
||||
return byID, byName, nil
|
||||
return skills, nil
|
||||
}
|
||||
|
||||
func loadSkillGroups(file string) (map[horse.SkillGroupID]horse.SkillGroup, error) {
|
||||
func loadSkillGroups(file string) ([]horse.SkillGroup, error) {
|
||||
b, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -183,59 +205,5 @@ func loadSkillGroups(file string) (map[horse.SkillGroupID]horse.SkillGroup, erro
|
||||
if err := json.Unmarshal(b, &groups); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := make(map[horse.SkillGroupID]horse.SkillGroup, len(groups))
|
||||
for _, s := range groups {
|
||||
m[s.ID] = s
|
||||
}
|
||||
slog.Info("loaded skill groups", slog.Int("count", len(groups)))
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func skillHandler(data discord.SlashCommandInteractionData, e *handler.CommandEvent) error {
|
||||
q := data.String("query")
|
||||
id, err := strconv.ParseInt(q, 10, 32)
|
||||
if err == nil {
|
||||
// note inverted condition; this is when we have an id
|
||||
id = int64(skillsByID[horse.SkillID(id)].ID)
|
||||
}
|
||||
if id == 0 {
|
||||
// Either we weren't given a number or the number doesn't match any skill ID.
|
||||
v := skillsByName[q]
|
||||
if v == 0 {
|
||||
// No such skill.
|
||||
m := discord.MessageCreate{
|
||||
Content: "No such skill.",
|
||||
Flags: discord.MessageFlagEphemeral,
|
||||
}
|
||||
return e.CreateMessage(m)
|
||||
}
|
||||
id = int64(v)
|
||||
}
|
||||
// TODO(zeph): search conditions and effects, give a list
|
||||
m := discord.MessageCreate{
|
||||
Components: []discord.LayoutComponent{RenderSkill(horse.SkillID(id), skillsByID, skillGroupMap)},
|
||||
Flags: discord.MessageFlagIsComponentsV2,
|
||||
}
|
||||
return e.CreateMessage(m)
|
||||
}
|
||||
|
||||
func skillAutocomplete(e *handler.AutocompleteEvent) error {
|
||||
q := e.Data.String("query")
|
||||
opts := skillGlobalAuto().Find(nil, q)
|
||||
return e.AutocompleteResult(opts[:min(len(opts), 25)])
|
||||
}
|
||||
|
||||
func skillButton(data discord.ButtonInteractionData, e *handler.ComponentEvent) error {
|
||||
id, err := strconv.ParseInt(e.Vars["id"], 10, 32)
|
||||
if err != nil {
|
||||
m := discord.MessageCreate{
|
||||
Content: "That button produced an invalid skill ID. That's not supposed to happen.",
|
||||
Flags: discord.MessageFlagEphemeral,
|
||||
}
|
||||
return e.CreateMessage(m)
|
||||
}
|
||||
m := discord.MessageUpdate{
|
||||
Components: &[]discord.LayoutComponent{RenderSkill(horse.SkillID(id), skillsByID, skillGroupMap)},
|
||||
}
|
||||
return e.UpdateMessage(m)
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
@@ -2,66 +2,167 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/disgoorg/disgo/discord"
|
||||
"github.com/disgoorg/disgo/handler"
|
||||
|
||||
"git.sunturtle.xyz/zephyr/horse/cmd/horsebot/autocomplete"
|
||||
"git.sunturtle.xyz/zephyr/horse/horse"
|
||||
)
|
||||
|
||||
func RenderSkill(id horse.SkillID, all map[horse.SkillID]horse.Skill, groups map[horse.SkillGroupID]horse.SkillGroup) discord.ContainerComponent {
|
||||
s, ok := all[id]
|
||||
if !ok {
|
||||
return discord.NewContainer(discord.NewTextDisplayf("invalid skill ID %v made it to RenderSkill", id))
|
||||
type skillServer struct {
|
||||
skills map[horse.SkillID]horse.Skill
|
||||
byName map[string]horse.SkillID
|
||||
groups map[horse.SkillGroupID]horse.SkillGroup
|
||||
autocom autocomplete.Set[discord.AutocompleteChoice]
|
||||
}
|
||||
|
||||
thumburl := fmt.Sprintf("https://gametora.com/images/umamusume/skill_icons/utx_ico_skill_%d.png", s.IconID)
|
||||
top := "## " + s.Name
|
||||
if s.UniqueOwner != "" {
|
||||
top += "\n-# " + s.UniqueOwner
|
||||
func newSkillServer(skills []horse.Skill, groups []horse.SkillGroup) *skillServer {
|
||||
s := skillServer{
|
||||
skills: make(map[horse.SkillID]horse.Skill, len(skills)),
|
||||
byName: make(map[string]horse.SkillID, len(skills)),
|
||||
groups: make(map[horse.SkillGroupID]horse.SkillGroup, len(groups)),
|
||||
}
|
||||
for _, skill := range skills {
|
||||
s.skills[skill.ID] = skill
|
||||
s.byName[skill.Name] = skill.ID
|
||||
switch {
|
||||
case skill.UniqueOwner == "":
|
||||
s.autocom.Add(skill.Name, discord.AutocompleteChoiceString{Name: skill.Name, Value: strconv.Itoa(int(skill.ID))})
|
||||
case skill.Rarity >= 3:
|
||||
s.autocom.Add(skill.Name, discord.AutocompleteChoiceString{Name: skill.Name, Value: skill.Name})
|
||||
s.autocom.Add(skill.UniqueOwner, discord.AutocompleteChoiceString{Name: "Unique: " + skill.UniqueOwner, Value: strconv.Itoa(int(skill.ID))})
|
||||
default:
|
||||
s.autocom.Add(skill.Name, discord.AutocompleteChoiceString{Name: skill.Name + " (Inherited)", Value: strconv.Itoa(int(skill.ID))})
|
||||
s.autocom.Add(skill.UniqueOwner, discord.AutocompleteChoiceString{Name: "Inherited unique: " + skill.UniqueOwner, Value: skill.Name})
|
||||
}
|
||||
}
|
||||
for _, g := range groups {
|
||||
s.groups[g.ID] = g
|
||||
}
|
||||
return &s
|
||||
}
|
||||
|
||||
func (s *skillServer) slash(data discord.SlashCommandInteractionData, e *handler.CommandEvent) error {
|
||||
q := data.String("query")
|
||||
id, err := strconv.ParseInt(q, 10, 32)
|
||||
if err == nil {
|
||||
// note inverted condition; this is when we have an id
|
||||
id = int64(s.skills[horse.SkillID(id)].ID)
|
||||
}
|
||||
if id == 0 {
|
||||
// Either we weren't given a number or the number doesn't match any skill ID.
|
||||
v := s.byName[q]
|
||||
if v == 0 {
|
||||
// No such skill.
|
||||
m := discord.MessageCreate{
|
||||
Content: "No such skill.",
|
||||
Flags: discord.MessageFlagEphemeral,
|
||||
}
|
||||
return e.CreateMessage(m)
|
||||
}
|
||||
id = int64(v)
|
||||
}
|
||||
m := discord.MessageCreate{
|
||||
Components: []discord.LayoutComponent{s.render(horse.SkillID(id))},
|
||||
Flags: discord.MessageFlagIsComponentsV2,
|
||||
}
|
||||
if !data.Bool("share") {
|
||||
m.Flags |= discord.MessageFlagEphemeral
|
||||
}
|
||||
return e.CreateMessage(m)
|
||||
}
|
||||
|
||||
func (s *skillServer) autocomplete(e *handler.AutocompleteEvent) error {
|
||||
q := e.Data.String("query")
|
||||
opts := s.autocom.Find(nil, q)
|
||||
return e.AutocompleteResult(opts[:min(len(opts), 25)])
|
||||
}
|
||||
|
||||
func (s *skillServer) button(data discord.ButtonInteractionData, e *handler.ComponentEvent) error {
|
||||
id, err := strconv.ParseInt(e.Vars["id"], 10, 32)
|
||||
if err != nil {
|
||||
m := discord.MessageCreate{
|
||||
Content: "That button produced an invalid skill ID. That's not supposed to happen.",
|
||||
Flags: discord.MessageFlagEphemeral,
|
||||
}
|
||||
return e.CreateMessage(m)
|
||||
}
|
||||
m := discord.MessageUpdate{
|
||||
Components: &[]discord.LayoutComponent{s.render(horse.SkillID(id))},
|
||||
}
|
||||
return e.UpdateMessage(m)
|
||||
}
|
||||
|
||||
func (s *skillServer) menu(data discord.SelectMenuInteractionData, e *handler.ComponentEvent) error {
|
||||
d, ok := data.(discord.StringSelectMenuInteractionData)
|
||||
if !ok {
|
||||
return fmt.Errorf("wrong select menu type %T", data)
|
||||
}
|
||||
if len(d.Values) != 1 {
|
||||
return fmt.Errorf("wrong number of values: %q", d.Values)
|
||||
}
|
||||
id, err := strconv.ParseInt(d.Values[0], 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m := discord.MessageUpdate{
|
||||
Components: &[]discord.LayoutComponent{s.render(horse.SkillID(id))},
|
||||
}
|
||||
return e.UpdateMessage(m)
|
||||
}
|
||||
|
||||
func (s *skillServer) render(id horse.SkillID) discord.ContainerComponent {
|
||||
skill, ok := s.skills[id]
|
||||
if !ok {
|
||||
slog.Error("invalid skill id", slog.Int("id", int(id)))
|
||||
return discord.NewContainer(discord.NewTextDisplayf("invalid skill ID %v made it to render", id))
|
||||
}
|
||||
|
||||
top := "### " + skill.Name
|
||||
if skill.UniqueOwner != "" {
|
||||
top += "\n-# " + skill.UniqueOwner
|
||||
}
|
||||
r := discord.NewContainer(
|
||||
discord.NewSection(
|
||||
discord.NewTextDisplay(top),
|
||||
discord.NewTextDisplay(s.Description),
|
||||
).WithAccessory(discord.NewThumbnail(thumburl)),
|
||||
discord.NewSmallSeparator(),
|
||||
)
|
||||
var skilltype string
|
||||
switch {
|
||||
case s.Rarity == 3, s.Rarity == 4, s.Rarity == 5:
|
||||
case skill.Rarity == 3, skill.Rarity == 4, skill.Rarity == 5:
|
||||
// unique of various star levels
|
||||
r.AccentColor = 0xaca4d4
|
||||
skilltype = "Unique Skill"
|
||||
case s.UniqueOwner != "":
|
||||
case skill.UniqueOwner != "":
|
||||
r.AccentColor = 0xcccccc
|
||||
skilltype = "Inherited Unique"
|
||||
case s.Rarity == 2:
|
||||
case skill.Rarity == 2:
|
||||
// rare (gold)
|
||||
r.AccentColor = 0xd7c25b
|
||||
skilltype = "Rare Skill"
|
||||
case s.GroupRate == -1:
|
||||
case skill.GroupRate == -1:
|
||||
// negative (purple) skill
|
||||
r.AccentColor = 0x9151d4
|
||||
skilltype = "Negative Skill"
|
||||
case !s.WitCheck:
|
||||
case !skill.WitCheck:
|
||||
// should be passive (green)
|
||||
r.AccentColor = 0x66ae1c
|
||||
skilltype = "Passive Skill"
|
||||
case isDebuff(s):
|
||||
case isDebuff(skill):
|
||||
// debuff (red)
|
||||
r.AccentColor = 0xe34747
|
||||
skilltype = "Debuff Skill"
|
||||
case s.Rarity == 1:
|
||||
case skill.Rarity == 1:
|
||||
// common (white)
|
||||
r.AccentColor = 0xcccccc
|
||||
skilltype = "Common Skill"
|
||||
}
|
||||
r.Components = append(r.Components, discord.NewSmallSeparator())
|
||||
text := make([]string, 0, 3)
|
||||
abils := make([]string, 0, 3)
|
||||
for _, act := range s.Activations {
|
||||
for _, act := range skill.Activations {
|
||||
text, abils = text[:0], abils[:0]
|
||||
if act.Precondition != "" {
|
||||
text = append(text, "Precondition: "+formatCondition(act.Precondition))
|
||||
@@ -91,25 +192,40 @@ func RenderSkill(id horse.SkillID, all map[horse.SkillID]horse.Skill, groups map
|
||||
r.Components = append(r.Components, discord.NewTextDisplay(strings.Join(text, "\n")))
|
||||
}
|
||||
|
||||
l := discord.NewTextDisplayf("%s ・ SP cost %d ・ Grade value %d ・ [Conditions on GameTora](https://gametora.com/umamusume/skill-condition-viewer?skill=%d)", skilltype, s.SPCost, s.GradeValue, s.ID)
|
||||
l := discord.NewTextDisplayf("%s ・ SP cost %d ・ Grade value %d ・ [Conditions on GameTora](https://gametora.com/umamusume/skill-condition-viewer?skill=%d)", skilltype, skill.SPCost, skill.GradeValue, skill.ID)
|
||||
r.Components = append(r.Components, discord.NewSmallSeparator(), l)
|
||||
rel := make([]horse.Skill, 0, 4)
|
||||
group := groups[s.Group]
|
||||
group := s.groups[skill.Group]
|
||||
for _, id := range [...]horse.SkillID{group.Skill1, group.Skill2, group.Skill3, group.SkillBad} {
|
||||
if id != 0 {
|
||||
rel = append(rel, all[id])
|
||||
rel = append(rel, s.skills[id])
|
||||
}
|
||||
}
|
||||
if len(rel) > 1 {
|
||||
buttons := make([]discord.InteractiveComponent, 0, 4)
|
||||
opts := make([]discord.StringSelectMenuOption, 0, 4)
|
||||
for _, rs := range rel {
|
||||
b := discord.NewSecondaryButton(rs.Name, fmt.Sprintf("/skill/%d", rs.ID))
|
||||
if rs.ID == id {
|
||||
b = b.AsDisabled()
|
||||
name := rs.Name
|
||||
emoji := "⚪"
|
||||
switch rs.Rarity {
|
||||
case 1:
|
||||
if rs.UniqueOwner != "" {
|
||||
name += " (Inherited)"
|
||||
}
|
||||
buttons = append(buttons, b)
|
||||
case 2:
|
||||
emoji = "🟡"
|
||||
case 3, 4, 5:
|
||||
emoji = "🟣"
|
||||
default:
|
||||
emoji = "⁉️"
|
||||
}
|
||||
r.Components = append(r.Components, discord.NewActionRow(buttons...))
|
||||
b := discord.NewStringSelectMenuOption(name, strconv.Itoa(int(rs.ID))).WithEmoji(discord.NewComponentEmoji(emoji))
|
||||
if rs.ID == skill.ID {
|
||||
b = b.WithDefault(true)
|
||||
}
|
||||
opts = append(opts, b)
|
||||
}
|
||||
row := discord.NewActionRow(discord.NewStringSelectMenu("/skill/swap", "Related skills", opts...))
|
||||
r.Components = append(r.Components, row)
|
||||
}
|
||||
return r
|
||||
}
|
||||
@@ -132,19 +248,3 @@ func isDebuff(s horse.Skill) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var skillGlobalAuto = sync.OnceValue(func() *autocomplete.Set[discord.AutocompleteChoice] {
|
||||
var set autocomplete.Set[discord.AutocompleteChoice]
|
||||
// NOTE(zeph): we're using global variables here
|
||||
for _, s := range skillsByID {
|
||||
set.Add(s.Name, discord.AutocompleteChoiceString{Name: s.Name, Value: s.Name})
|
||||
if s.UniqueOwner != "" {
|
||||
if s.Rarity >= 3 {
|
||||
set.Add(s.UniqueOwner, discord.AutocompleteChoiceString{Name: "Unique: " + s.UniqueOwner, Value: s.Name})
|
||||
} else {
|
||||
set.Add(s.UniqueOwner, discord.AutocompleteChoiceString{Name: "Inherited unique: " + s.UniqueOwner, Value: s.Name})
|
||||
}
|
||||
}
|
||||
}
|
||||
return &set
|
||||
})
|
||||
|
||||
@@ -30,7 +30,7 @@ func main() {
|
||||
region string
|
||||
)
|
||||
flag.StringVar(&mdb, "mdb", os.ExpandEnv(`$USERPROFILE\AppData\LocalLow\Cygames\Umamusume\master\master.mdb`), "`path` to Umamusume master.mdb")
|
||||
flag.StringVar(&out, "o", `horse`, "`dir`ectory for output files")
|
||||
flag.StringVar(&out, "o", `.`, "`dir`ectory for output files")
|
||||
flag.StringVar(®ion, "region", "global", "region the database is for (global, jp)")
|
||||
flag.Parse()
|
||||
|
||||
@@ -223,6 +223,18 @@ func main() {
|
||||
Value2: s.ColumnInt32(4),
|
||||
}
|
||||
})
|
||||
convos := load(ctx1, loadgroup, db, "lobby conversations", conversationSQL, func(s *sqlite.Stmt) horse.Conversation {
|
||||
return horse.Conversation{
|
||||
CharacterID: horse.CharacterID(s.ColumnInt(0)),
|
||||
Number: s.ColumnInt(1),
|
||||
Location: horse.LobbyConversationLocationID(s.ColumnInt(2)),
|
||||
LocationName: horse.LobbyConversationLocationID(s.ColumnInt(2)).String(),
|
||||
Chara1: horse.CharacterID(s.ColumnInt(3)),
|
||||
Chara2: horse.CharacterID(s.ColumnInt(4)),
|
||||
Chara3: horse.CharacterID(s.ColumnInt(5)),
|
||||
ConditionType: s.ColumnInt(6),
|
||||
}
|
||||
})
|
||||
|
||||
if err := os.MkdirAll(filepath.Join(out, region), 0775); err != nil {
|
||||
slog.Error("create output dir", slog.Any("err", err))
|
||||
@@ -239,6 +251,7 @@ func main() {
|
||||
writegroup.Go(func() error { return write(ctx2, out, region, "saddle.json", saddles) })
|
||||
writegroup.Go(func() error { return write(ctx2, out, region, "scenario.json", scenarios) })
|
||||
writegroup.Go(func() error { return write(ctx2, out, region, "spark.json", mergesparks(sparks, sparkeffs)) })
|
||||
writegroup.Go(func() error { return write(ctx2, out, region, "conversation.json", convos) })
|
||||
if err := writegroup.Wait(); err != nil {
|
||||
slog.ErrorContext(ctx, "write", slog.Any("err", err))
|
||||
os.Exit(1)
|
||||
@@ -268,6 +281,8 @@ var (
|
||||
sparkSQL string
|
||||
//go:embed sql/spark-effect.sql
|
||||
sparkEffectSQL string
|
||||
//go:embed sql/conversation.sql
|
||||
conversationSQL string
|
||||
)
|
||||
|
||||
func load[T any](ctx context.Context, group *errgroup.Group, db *sqlitex.Pool, kind, sql string, row func(*sqlite.Stmt) T) func() ([]T, error) {
|
||||
|
||||
10
cmd/horsegen/sql/conversation.sql
Normal file
10
cmd/horsegen/sql/conversation.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
SELECT
|
||||
gallery_chara_id,
|
||||
disp_order,
|
||||
pos_id,
|
||||
chara_id_1,
|
||||
chara_id_2,
|
||||
chara_id_3,
|
||||
condition_type
|
||||
FROM home_story_trigger
|
||||
ORDER BY gallery_chara_id, disp_order
|
||||
@@ -3,8 +3,8 @@ WITH skill_groups AS (
|
||||
)
|
||||
SELECT
|
||||
g.group_id,
|
||||
IFNULL(s1.id, 0) AS skill1,
|
||||
IFNULL(s2.id, 0) AS skill2,
|
||||
COALESCE(inh_from.id, s1.id, 0) AS skill1,
|
||||
COALESCE(inh_to.id, s2.id, 0) AS skill2,
|
||||
IFNULL(s3.id, 0) AS skill3,
|
||||
IFNULL(m1.id, 0) AS skill_bad
|
||||
FROM skill_groups g
|
||||
@@ -12,4 +12,6 @@ FROM skill_groups g
|
||||
LEFT JOIN skill_data s2 ON g.group_id = s2.group_id AND s2.group_rate = 2
|
||||
LEFT JOIN skill_data s3 ON g.group_id = s3.group_id AND s3.group_rate = 3
|
||||
LEFT JOIN skill_data m1 ON g.group_id = m1.group_id AND m1.group_rate = -1
|
||||
LEFT JOIN skill_data inh_to ON s1.id = inh_to.unique_skill_id_1
|
||||
LEFT JOIN skill_data inh_from ON s2.unique_skill_id_1 = inh_from.id
|
||||
ORDER BY g.group_id
|
||||
|
||||
137122
doc/2026-03-12-global.sql
Normal file
137122
doc/2026-03-12-global.sql
Normal file
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,7 @@ This file is my notes from exploring the database.
|
||||
- 33 is race names by race id, 28 is race names by race instance id, 31 is race courses, 111 is saddle names
|
||||
- 65 is player titles, 66 is title descriptions - ties with honor_data?
|
||||
- 119 is scenario full titles (e.g. The Beginning: URA Finale), 120 is scenario descriptions, 237 is scenario names (e.g. URA Finale)
|
||||
- 130 is epithet names, 131 is epithet descriptions
|
||||
|
||||
# succession factor (sparks)
|
||||
|
||||
@@ -192,12 +193,41 @@ target types:
|
||||
- 22 specific character, target value is character id
|
||||
- 23 other who triggered the skill
|
||||
|
||||
TODO target types only in jp: 2, 7, 11, 22, 23
|
||||
|
||||
ability_value_usage can be 1 for plain or 2-6 for aoharu stat skill stat scaling
|
||||
|
||||
seems to be activate_lot = 1 means wit check, 0 means guaranteed
|
||||
|
||||
tag_id is a slash-separated list of numeric IDs that roughly describe all effects relevant to the skill.
|
||||
- 101..104 style front, pace, late, end
|
||||
- 201..204 distance sprint, mile, medium, long
|
||||
- 301..303 timing early, mid, late race
|
||||
- 401 affects speed stat or target speed
|
||||
- 402 affects stamina stat or hp
|
||||
- 403 affects power stat or acceleration
|
||||
- 404 affects guts stat
|
||||
- 405 affects wit stat or vision
|
||||
- 406 is a debuff of any type, or is a main story self-debuff (creeping anxiety, blatant fear), or is Behold Thine Emperor's Divine Might (both own unique and inherited versions are tagged only 406??)
|
||||
- 407 modifies gate delay
|
||||
- 501..502 turf/dirt
|
||||
- 801..812 scenario skill
|
||||
|
||||
600 series applies to skills available in global but are only used in jp.
|
||||
- 601 ground condition passive
|
||||
- 602 weather passive
|
||||
- 603 season passive
|
||||
- 604 race track passive
|
||||
- 605 time of day passive
|
||||
- 606 exchange race passive (kawasaki, funabashi, morioka, ooi)
|
||||
- 607 distance passive (non/standard)
|
||||
- 608 track direction or corner count passive
|
||||
- 609 gate position passive
|
||||
- 610 ground type passive
|
||||
- 611 popularity passive
|
||||
- 612 running style passive
|
||||
- 613 passive depending on other horses' styles or skills
|
||||
- 614 base stat threshold passive
|
||||
- 615 mood passive
|
||||
|
||||
# races
|
||||
|
||||
- group 1, grade: g1 100, g2 200, g3 300, op 400, pre-op 700
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -199,6 +199,14 @@
|
||||
"chara_id": 1061,
|
||||
"name": "King Halo"
|
||||
},
|
||||
{
|
||||
"chara_id": 1062,
|
||||
"name": "Matikanetannhauser"
|
||||
},
|
||||
{
|
||||
"chara_id": 1067,
|
||||
"name": "Satono Diamond"
|
||||
},
|
||||
{
|
||||
"chara_id": 1068,
|
||||
"name": "Kitasan Black"
|
||||
@@ -210,5 +218,9 @@
|
||||
{
|
||||
"chara_id": 1071,
|
||||
"name": "Mejiro Ardan"
|
||||
},
|
||||
{
|
||||
"chara_id": 1074,
|
||||
"name": "Mejiro Bright"
|
||||
}
|
||||
]
|
||||
2378
global/conversation.json
Normal file
2378
global/conversation.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -8,5 +8,10 @@
|
||||
"scenario_id": 2,
|
||||
"name": "Unity Cup",
|
||||
"title": "Unity Cup: Shine On, Team Spirit!"
|
||||
},
|
||||
{
|
||||
"scenario_id": 4,
|
||||
"name": "TS Climax",
|
||||
"title": "Trackblazer: Start of the Climax"
|
||||
}
|
||||
]
|
||||
@@ -63,282 +63,375 @@
|
||||
"skill_group": 1061,
|
||||
"skill1": 10611
|
||||
},
|
||||
{
|
||||
"skill_group": 1062,
|
||||
"skill1": 10621
|
||||
},
|
||||
{
|
||||
"skill_group": 10001,
|
||||
"skill1": 100011
|
||||
"skill1": 100011,
|
||||
"skill2": 900011
|
||||
},
|
||||
{
|
||||
"skill_group": 10002,
|
||||
"skill1": 100021
|
||||
"skill1": 100021,
|
||||
"skill2": 900021
|
||||
},
|
||||
{
|
||||
"skill_group": 10003,
|
||||
"skill1": 100031
|
||||
"skill1": 100031,
|
||||
"skill2": 900031
|
||||
},
|
||||
{
|
||||
"skill_group": 10004,
|
||||
"skill1": 100041
|
||||
"skill1": 100041,
|
||||
"skill2": 900041
|
||||
},
|
||||
{
|
||||
"skill_group": 10005,
|
||||
"skill1": 100051
|
||||
"skill1": 100051,
|
||||
"skill2": 900051
|
||||
},
|
||||
{
|
||||
"skill_group": 10006,
|
||||
"skill1": 100061
|
||||
"skill1": 100061,
|
||||
"skill2": 900061
|
||||
},
|
||||
{
|
||||
"skill_group": 10007,
|
||||
"skill1": 100071
|
||||
"skill1": 100071,
|
||||
"skill2": 900071
|
||||
},
|
||||
{
|
||||
"skill_group": 10008,
|
||||
"skill1": 100081
|
||||
"skill1": 100081,
|
||||
"skill2": 900081
|
||||
},
|
||||
{
|
||||
"skill_group": 10009,
|
||||
"skill1": 100091
|
||||
"skill1": 100091,
|
||||
"skill2": 900091
|
||||
},
|
||||
{
|
||||
"skill_group": 10010,
|
||||
"skill1": 100101
|
||||
"skill1": 100101,
|
||||
"skill2": 900101
|
||||
},
|
||||
{
|
||||
"skill_group": 10011,
|
||||
"skill1": 100111
|
||||
"skill1": 100111,
|
||||
"skill2": 900111
|
||||
},
|
||||
{
|
||||
"skill_group": 10012,
|
||||
"skill1": 100121
|
||||
"skill1": 100121,
|
||||
"skill2": 900121
|
||||
},
|
||||
{
|
||||
"skill_group": 10013,
|
||||
"skill1": 100131
|
||||
"skill1": 100131,
|
||||
"skill2": 900131
|
||||
},
|
||||
{
|
||||
"skill_group": 10014,
|
||||
"skill1": 100141
|
||||
"skill1": 100141,
|
||||
"skill2": 900141
|
||||
},
|
||||
{
|
||||
"skill_group": 10015,
|
||||
"skill1": 100151
|
||||
"skill1": 100151,
|
||||
"skill2": 900151
|
||||
},
|
||||
{
|
||||
"skill_group": 10016,
|
||||
"skill1": 100161
|
||||
"skill1": 100161,
|
||||
"skill2": 900161
|
||||
},
|
||||
{
|
||||
"skill_group": 10017,
|
||||
"skill1": 100171
|
||||
"skill1": 100171,
|
||||
"skill2": 900171
|
||||
},
|
||||
{
|
||||
"skill_group": 10018,
|
||||
"skill1": 100181
|
||||
"skill1": 100181,
|
||||
"skill2": 900181
|
||||
},
|
||||
{
|
||||
"skill_group": 10019,
|
||||
"skill1": 100191
|
||||
"skill1": 100191,
|
||||
"skill2": 900191
|
||||
},
|
||||
{
|
||||
"skill_group": 10020,
|
||||
"skill1": 100201
|
||||
"skill1": 100201,
|
||||
"skill2": 900201
|
||||
},
|
||||
{
|
||||
"skill_group": 10021,
|
||||
"skill1": 100211
|
||||
"skill1": 100211,
|
||||
"skill2": 900211
|
||||
},
|
||||
{
|
||||
"skill_group": 10022,
|
||||
"skill1": 100221
|
||||
"skill1": 100221,
|
||||
"skill2": 900221
|
||||
},
|
||||
{
|
||||
"skill_group": 10023,
|
||||
"skill1": 100231
|
||||
"skill1": 100231,
|
||||
"skill2": 900231
|
||||
},
|
||||
{
|
||||
"skill_group": 10024,
|
||||
"skill1": 100241
|
||||
"skill1": 100241,
|
||||
"skill2": 900241
|
||||
},
|
||||
{
|
||||
"skill_group": 10025,
|
||||
"skill1": 100251
|
||||
"skill1": 100251,
|
||||
"skill2": 900251
|
||||
},
|
||||
{
|
||||
"skill_group": 10026,
|
||||
"skill1": 100261
|
||||
"skill1": 100261,
|
||||
"skill2": 900261
|
||||
},
|
||||
{
|
||||
"skill_group": 10027,
|
||||
"skill1": 100271
|
||||
"skill1": 100271,
|
||||
"skill2": 900271
|
||||
},
|
||||
{
|
||||
"skill_group": 10028,
|
||||
"skill1": 100281
|
||||
"skill1": 100281,
|
||||
"skill2": 900281
|
||||
},
|
||||
{
|
||||
"skill_group": 10030,
|
||||
"skill1": 100301
|
||||
"skill1": 100301,
|
||||
"skill2": 900301
|
||||
},
|
||||
{
|
||||
"skill_group": 10032,
|
||||
"skill1": 100321
|
||||
"skill1": 100321,
|
||||
"skill2": 900321
|
||||
},
|
||||
{
|
||||
"skill_group": 10033,
|
||||
"skill1": 100331
|
||||
"skill1": 100331,
|
||||
"skill2": 900331
|
||||
},
|
||||
{
|
||||
"skill_group": 10035,
|
||||
"skill1": 100351
|
||||
"skill1": 100351,
|
||||
"skill2": 900351
|
||||
},
|
||||
{
|
||||
"skill_group": 10037,
|
||||
"skill1": 100371
|
||||
"skill1": 100371,
|
||||
"skill2": 900371
|
||||
},
|
||||
{
|
||||
"skill_group": 10038,
|
||||
"skill1": 100381
|
||||
"skill1": 100381,
|
||||
"skill2": 900381
|
||||
},
|
||||
{
|
||||
"skill_group": 10039,
|
||||
"skill1": 100391
|
||||
"skill1": 100391,
|
||||
"skill2": 900391
|
||||
},
|
||||
{
|
||||
"skill_group": 10040,
|
||||
"skill1": 100401
|
||||
"skill1": 100401,
|
||||
"skill2": 900401
|
||||
},
|
||||
{
|
||||
"skill_group": 10041,
|
||||
"skill1": 100411
|
||||
"skill1": 100411,
|
||||
"skill2": 900411
|
||||
},
|
||||
{
|
||||
"skill_group": 10045,
|
||||
"skill1": 100451
|
||||
"skill1": 100451,
|
||||
"skill2": 900451
|
||||
},
|
||||
{
|
||||
"skill_group": 10046,
|
||||
"skill1": 100461
|
||||
"skill1": 100461,
|
||||
"skill2": 900461
|
||||
},
|
||||
{
|
||||
"skill_group": 10048,
|
||||
"skill1": 100481
|
||||
"skill1": 100481,
|
||||
"skill2": 900481
|
||||
},
|
||||
{
|
||||
"skill_group": 10050,
|
||||
"skill1": 100501
|
||||
"skill1": 100501,
|
||||
"skill2": 900501
|
||||
},
|
||||
{
|
||||
"skill_group": 10052,
|
||||
"skill1": 100521
|
||||
"skill1": 100521,
|
||||
"skill2": 900521
|
||||
},
|
||||
{
|
||||
"skill_group": 10056,
|
||||
"skill1": 100561
|
||||
"skill1": 100561,
|
||||
"skill2": 900561
|
||||
},
|
||||
{
|
||||
"skill_group": 10058,
|
||||
"skill1": 100581
|
||||
"skill1": 100581,
|
||||
"skill2": 900581
|
||||
},
|
||||
{
|
||||
"skill_group": 10059,
|
||||
"skill1": 100591
|
||||
"skill1": 100591,
|
||||
"skill2": 900591
|
||||
},
|
||||
{
|
||||
"skill_group": 10060,
|
||||
"skill1": 100601
|
||||
"skill1": 100601,
|
||||
"skill2": 900601
|
||||
},
|
||||
{
|
||||
"skill_group": 10061,
|
||||
"skill1": 100611
|
||||
"skill1": 100611,
|
||||
"skill2": 900611
|
||||
},
|
||||
{
|
||||
"skill_group": 10062,
|
||||
"skill1": 100621,
|
||||
"skill2": 900621
|
||||
},
|
||||
{
|
||||
"skill_group": 10067,
|
||||
"skill1": 100671,
|
||||
"skill2": 900671
|
||||
},
|
||||
{
|
||||
"skill_group": 10068,
|
||||
"skill1": 100681,
|
||||
"skill2": 900681
|
||||
},
|
||||
{
|
||||
"skill_group": 10069,
|
||||
"skill1": 100691
|
||||
"skill1": 100691,
|
||||
"skill2": 900691
|
||||
},
|
||||
{
|
||||
"skill_group": 10071,
|
||||
"skill1": 100711
|
||||
"skill1": 100711,
|
||||
"skill2": 900711
|
||||
},
|
||||
{
|
||||
"skill_group": 10074,
|
||||
"skill1": 100741,
|
||||
"skill2": 900741
|
||||
},
|
||||
{
|
||||
"skill_group": 11001,
|
||||
"skill1": 110011
|
||||
"skill1": 110011,
|
||||
"skill2": 910011
|
||||
},
|
||||
{
|
||||
"skill_group": 11003,
|
||||
"skill1": 110031
|
||||
"skill1": 110031,
|
||||
"skill2": 910031
|
||||
},
|
||||
{
|
||||
"skill_group": 11004,
|
||||
"skill1": 110041
|
||||
"skill1": 110041,
|
||||
"skill2": 910041
|
||||
},
|
||||
{
|
||||
"skill_group": 11006,
|
||||
"skill1": 110061
|
||||
"skill1": 110061,
|
||||
"skill2": 910061
|
||||
},
|
||||
{
|
||||
"skill_group": 11011,
|
||||
"skill1": 110111
|
||||
"skill1": 110111,
|
||||
"skill2": 910111
|
||||
},
|
||||
{
|
||||
"skill_group": 11013,
|
||||
"skill1": 110131
|
||||
"skill1": 110131,
|
||||
"skill2": 910131
|
||||
},
|
||||
{
|
||||
"skill_group": 11014,
|
||||
"skill1": 110141
|
||||
"skill1": 110141,
|
||||
"skill2": 910141
|
||||
},
|
||||
{
|
||||
"skill_group": 11015,
|
||||
"skill1": 110151
|
||||
"skill1": 110151,
|
||||
"skill2": 910151
|
||||
},
|
||||
{
|
||||
"skill_group": 11017,
|
||||
"skill1": 110171
|
||||
"skill1": 110171,
|
||||
"skill2": 910171
|
||||
},
|
||||
{
|
||||
"skill_group": 11018,
|
||||
"skill1": 110181
|
||||
"skill1": 110181,
|
||||
"skill2": 910181
|
||||
},
|
||||
{
|
||||
"skill_group": 11023,
|
||||
"skill1": 110231
|
||||
"skill1": 110231,
|
||||
"skill2": 910231
|
||||
},
|
||||
{
|
||||
"skill_group": 11024,
|
||||
"skill1": 110241
|
||||
"skill1": 110241,
|
||||
"skill2": 910241
|
||||
},
|
||||
{
|
||||
"skill_group": 11026,
|
||||
"skill1": 110261
|
||||
"skill1": 110261,
|
||||
"skill2": 910261
|
||||
},
|
||||
{
|
||||
"skill_group": 11030,
|
||||
"skill1": 110301
|
||||
"skill1": 110301,
|
||||
"skill2": 910301
|
||||
},
|
||||
{
|
||||
"skill_group": 11037,
|
||||
"skill1": 110371
|
||||
"skill1": 110371,
|
||||
"skill2": 910371
|
||||
},
|
||||
{
|
||||
"skill_group": 11040,
|
||||
"skill1": 110401
|
||||
"skill1": 110401,
|
||||
"skill2": 910401
|
||||
},
|
||||
{
|
||||
"skill_group": 11045,
|
||||
"skill1": 110451
|
||||
"skill1": 110451,
|
||||
"skill2": 910451
|
||||
},
|
||||
{
|
||||
"skill_group": 11052,
|
||||
"skill1": 110521
|
||||
"skill1": 110521,
|
||||
"skill2": 910521
|
||||
},
|
||||
{
|
||||
"skill_group": 11056,
|
||||
"skill1": 110561
|
||||
"skill1": 110561,
|
||||
"skill2": 910561
|
||||
},
|
||||
{
|
||||
"skill_group": 20001,
|
||||
"skill1": 200012,
|
||||
"skill2": 200011,
|
||||
"skill3": 200014,
|
||||
"skill_bad": 200013
|
||||
},
|
||||
{
|
||||
@@ -424,6 +517,7 @@
|
||||
"skill_group": 20015,
|
||||
"skill1": 200152,
|
||||
"skill2": 200151,
|
||||
"skill3": 200154,
|
||||
"skill_bad": 200153
|
||||
},
|
||||
{
|
||||
@@ -926,7 +1020,8 @@
|
||||
{
|
||||
"skill_group": 20117,
|
||||
"skill1": 201172,
|
||||
"skill2": 201171
|
||||
"skill2": 201171,
|
||||
"skill3": 201173
|
||||
},
|
||||
{
|
||||
"skill_group": 20118,
|
||||
@@ -1219,6 +1314,15 @@
|
||||
"skill_group": 20205,
|
||||
"skill2": 202051
|
||||
},
|
||||
{
|
||||
"skill_group": 20206,
|
||||
"skill1": 202061
|
||||
},
|
||||
{
|
||||
"skill_group": 20207,
|
||||
"skill1": 202072,
|
||||
"skill2": 202071
|
||||
},
|
||||
{
|
||||
"skill_group": 21001,
|
||||
"skill1": 210012,
|
||||
@@ -1244,6 +1348,11 @@
|
||||
"skill1": 210052,
|
||||
"skill2": 210051
|
||||
},
|
||||
{
|
||||
"skill_group": 21006,
|
||||
"skill1": 210062,
|
||||
"skill2": 210061
|
||||
},
|
||||
{
|
||||
"skill_group": 30001,
|
||||
"skill1": 300011
|
||||
@@ -1284,276 +1393,372 @@
|
||||
"skill_group": 30010,
|
||||
"skill1": 300101
|
||||
},
|
||||
{
|
||||
"skill_group": 30011,
|
||||
"skill1": 300111
|
||||
},
|
||||
{
|
||||
"skill_group": 30012,
|
||||
"skill1": 300121
|
||||
},
|
||||
{
|
||||
"skill_group": 90001,
|
||||
"skill1": 100011,
|
||||
"skill2": 900011
|
||||
},
|
||||
{
|
||||
"skill_group": 90002,
|
||||
"skill1": 100021,
|
||||
"skill2": 900021
|
||||
},
|
||||
{
|
||||
"skill_group": 90003,
|
||||
"skill1": 100031,
|
||||
"skill2": 900031
|
||||
},
|
||||
{
|
||||
"skill_group": 90004,
|
||||
"skill1": 100041,
|
||||
"skill2": 900041
|
||||
},
|
||||
{
|
||||
"skill_group": 90005,
|
||||
"skill1": 100051,
|
||||
"skill2": 900051
|
||||
},
|
||||
{
|
||||
"skill_group": 90006,
|
||||
"skill1": 100061,
|
||||
"skill2": 900061
|
||||
},
|
||||
{
|
||||
"skill_group": 90007,
|
||||
"skill1": 100071,
|
||||
"skill2": 900071
|
||||
},
|
||||
{
|
||||
"skill_group": 90008,
|
||||
"skill1": 100081,
|
||||
"skill2": 900081
|
||||
},
|
||||
{
|
||||
"skill_group": 90009,
|
||||
"skill1": 100091,
|
||||
"skill2": 900091
|
||||
},
|
||||
{
|
||||
"skill_group": 90010,
|
||||
"skill1": 100101,
|
||||
"skill2": 900101
|
||||
},
|
||||
{
|
||||
"skill_group": 90011,
|
||||
"skill1": 100111,
|
||||
"skill2": 900111
|
||||
},
|
||||
{
|
||||
"skill_group": 90012,
|
||||
"skill1": 100121,
|
||||
"skill2": 900121
|
||||
},
|
||||
{
|
||||
"skill_group": 90013,
|
||||
"skill1": 100131,
|
||||
"skill2": 900131
|
||||
},
|
||||
{
|
||||
"skill_group": 90014,
|
||||
"skill1": 100141,
|
||||
"skill2": 900141
|
||||
},
|
||||
{
|
||||
"skill_group": 90015,
|
||||
"skill1": 100151,
|
||||
"skill2": 900151
|
||||
},
|
||||
{
|
||||
"skill_group": 90016,
|
||||
"skill1": 100161,
|
||||
"skill2": 900161
|
||||
},
|
||||
{
|
||||
"skill_group": 90017,
|
||||
"skill1": 100171,
|
||||
"skill2": 900171
|
||||
},
|
||||
{
|
||||
"skill_group": 90018,
|
||||
"skill1": 100181,
|
||||
"skill2": 900181
|
||||
},
|
||||
{
|
||||
"skill_group": 90019,
|
||||
"skill1": 100191,
|
||||
"skill2": 900191
|
||||
},
|
||||
{
|
||||
"skill_group": 90020,
|
||||
"skill1": 100201,
|
||||
"skill2": 900201
|
||||
},
|
||||
{
|
||||
"skill_group": 90021,
|
||||
"skill1": 100211,
|
||||
"skill2": 900211
|
||||
},
|
||||
{
|
||||
"skill_group": 90022,
|
||||
"skill1": 100221,
|
||||
"skill2": 900221
|
||||
},
|
||||
{
|
||||
"skill_group": 90023,
|
||||
"skill1": 100231,
|
||||
"skill2": 900231
|
||||
},
|
||||
{
|
||||
"skill_group": 90024,
|
||||
"skill1": 100241,
|
||||
"skill2": 900241
|
||||
},
|
||||
{
|
||||
"skill_group": 90025,
|
||||
"skill1": 100251,
|
||||
"skill2": 900251
|
||||
},
|
||||
{
|
||||
"skill_group": 90026,
|
||||
"skill1": 100261,
|
||||
"skill2": 900261
|
||||
},
|
||||
{
|
||||
"skill_group": 90027,
|
||||
"skill1": 100271,
|
||||
"skill2": 900271
|
||||
},
|
||||
{
|
||||
"skill_group": 90028,
|
||||
"skill1": 100281,
|
||||
"skill2": 900281
|
||||
},
|
||||
{
|
||||
"skill_group": 90030,
|
||||
"skill1": 100301,
|
||||
"skill2": 900301
|
||||
},
|
||||
{
|
||||
"skill_group": 90032,
|
||||
"skill1": 100321,
|
||||
"skill2": 900321
|
||||
},
|
||||
{
|
||||
"skill_group": 90033,
|
||||
"skill1": 100331,
|
||||
"skill2": 900331
|
||||
},
|
||||
{
|
||||
"skill_group": 90035,
|
||||
"skill1": 100351,
|
||||
"skill2": 900351
|
||||
},
|
||||
{
|
||||
"skill_group": 90037,
|
||||
"skill1": 100371,
|
||||
"skill2": 900371
|
||||
},
|
||||
{
|
||||
"skill_group": 90038,
|
||||
"skill1": 100381,
|
||||
"skill2": 900381
|
||||
},
|
||||
{
|
||||
"skill_group": 90039,
|
||||
"skill1": 100391,
|
||||
"skill2": 900391
|
||||
},
|
||||
{
|
||||
"skill_group": 90040,
|
||||
"skill1": 100401,
|
||||
"skill2": 900401
|
||||
},
|
||||
{
|
||||
"skill_group": 90041,
|
||||
"skill1": 100411,
|
||||
"skill2": 900411
|
||||
},
|
||||
{
|
||||
"skill_group": 90045,
|
||||
"skill1": 100451,
|
||||
"skill2": 900451
|
||||
},
|
||||
{
|
||||
"skill_group": 90046,
|
||||
"skill1": 100461,
|
||||
"skill2": 900461
|
||||
},
|
||||
{
|
||||
"skill_group": 90048,
|
||||
"skill1": 100481,
|
||||
"skill2": 900481
|
||||
},
|
||||
{
|
||||
"skill_group": 90050,
|
||||
"skill1": 100501,
|
||||
"skill2": 900501
|
||||
},
|
||||
{
|
||||
"skill_group": 90052,
|
||||
"skill1": 100521,
|
||||
"skill2": 900521
|
||||
},
|
||||
{
|
||||
"skill_group": 90056,
|
||||
"skill1": 100561,
|
||||
"skill2": 900561
|
||||
},
|
||||
{
|
||||
"skill_group": 90058,
|
||||
"skill1": 100581,
|
||||
"skill2": 900581
|
||||
},
|
||||
{
|
||||
"skill_group": 90059,
|
||||
"skill1": 100591,
|
||||
"skill2": 900591
|
||||
},
|
||||
{
|
||||
"skill_group": 90060,
|
||||
"skill1": 100601,
|
||||
"skill2": 900601
|
||||
},
|
||||
{
|
||||
"skill_group": 90061,
|
||||
"skill1": 100611,
|
||||
"skill2": 900611
|
||||
},
|
||||
{
|
||||
"skill_group": 90062,
|
||||
"skill1": 100621,
|
||||
"skill2": 900621
|
||||
},
|
||||
{
|
||||
"skill_group": 90067,
|
||||
"skill1": 100671,
|
||||
"skill2": 900671
|
||||
},
|
||||
{
|
||||
"skill_group": 90068,
|
||||
"skill1": 100681,
|
||||
"skill2": 900681
|
||||
},
|
||||
{
|
||||
"skill_group": 90069,
|
||||
"skill1": 100691,
|
||||
"skill2": 900691
|
||||
},
|
||||
{
|
||||
"skill_group": 90071,
|
||||
"skill1": 100711,
|
||||
"skill2": 900711
|
||||
},
|
||||
{
|
||||
"skill_group": 90074,
|
||||
"skill1": 100741,
|
||||
"skill2": 900741
|
||||
},
|
||||
{
|
||||
"skill_group": 91001,
|
||||
"skill1": 110011,
|
||||
"skill2": 910011
|
||||
},
|
||||
{
|
||||
"skill_group": 91003,
|
||||
"skill1": 110031,
|
||||
"skill2": 910031
|
||||
},
|
||||
{
|
||||
"skill_group": 91004,
|
||||
"skill1": 110041,
|
||||
"skill2": 910041
|
||||
},
|
||||
{
|
||||
"skill_group": 91006,
|
||||
"skill1": 110061,
|
||||
"skill2": 910061
|
||||
},
|
||||
{
|
||||
"skill_group": 91011,
|
||||
"skill1": 110111,
|
||||
"skill2": 910111
|
||||
},
|
||||
{
|
||||
"skill_group": 91013,
|
||||
"skill1": 110131,
|
||||
"skill2": 910131
|
||||
},
|
||||
{
|
||||
"skill_group": 91014,
|
||||
"skill1": 110141,
|
||||
"skill2": 910141
|
||||
},
|
||||
{
|
||||
"skill_group": 91015,
|
||||
"skill1": 110151,
|
||||
"skill2": 910151
|
||||
},
|
||||
{
|
||||
"skill_group": 91017,
|
||||
"skill1": 110171,
|
||||
"skill2": 910171
|
||||
},
|
||||
{
|
||||
"skill_group": 91018,
|
||||
"skill1": 110181,
|
||||
"skill2": 910181
|
||||
},
|
||||
{
|
||||
"skill_group": 91023,
|
||||
"skill1": 110231,
|
||||
"skill2": 910231
|
||||
},
|
||||
{
|
||||
"skill_group": 91024,
|
||||
"skill1": 110241,
|
||||
"skill2": 910241
|
||||
},
|
||||
{
|
||||
"skill_group": 91026,
|
||||
"skill1": 110261,
|
||||
"skill2": 910261
|
||||
},
|
||||
{
|
||||
"skill_group": 91030,
|
||||
"skill1": 110301,
|
||||
"skill2": 910301
|
||||
},
|
||||
{
|
||||
"skill_group": 91037,
|
||||
"skill1": 110371,
|
||||
"skill2": 910371
|
||||
},
|
||||
{
|
||||
"skill_group": 91040,
|
||||
"skill1": 110401,
|
||||
"skill2": 910401
|
||||
},
|
||||
{
|
||||
"skill_group": 91045,
|
||||
"skill1": 110451,
|
||||
"skill2": 910451
|
||||
},
|
||||
{
|
||||
"skill_group": 91052,
|
||||
"skill1": 110521,
|
||||
"skill2": 910521
|
||||
},
|
||||
{
|
||||
"skill_group": 91056,
|
||||
"skill1": 110561,
|
||||
"skill2": 910561
|
||||
},
|
||||
{
|
||||
File diff suppressed because it is too large
Load Diff
@@ -28322,6 +28322,141 @@
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 2020701,
|
||||
"name": "Free-Spirited",
|
||||
"description": "A Spark that gives a skill hint for \"Free-Spirited\".",
|
||||
"spark_group": 20207,
|
||||
"rarity": 1,
|
||||
"type": 4,
|
||||
"effects": [
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 202072,
|
||||
"value2": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 202072,
|
||||
"value2": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 202072,
|
||||
"value2": 3
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 202072,
|
||||
"value2": 4
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 202072,
|
||||
"value2": 5
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 2020702,
|
||||
"name": "Free-Spirited",
|
||||
"description": "A Spark that gives a skill hint for \"Free-Spirited\".",
|
||||
"spark_group": 20207,
|
||||
"rarity": 2,
|
||||
"type": 4,
|
||||
"effects": [
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 202072,
|
||||
"value2": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 202072,
|
||||
"value2": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 202072,
|
||||
"value2": 3
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 202072,
|
||||
"value2": 4
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 202072,
|
||||
"value2": 5
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 2020703,
|
||||
"name": "Free-Spirited",
|
||||
"description": "A Spark that gives a skill hint for \"Free-Spirited\".",
|
||||
"spark_group": 20207,
|
||||
"rarity": 3,
|
||||
"type": 4,
|
||||
"effects": [
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 202072,
|
||||
"value2": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 202072,
|
||||
"value2": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 202072,
|
||||
"value2": 3
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 202072,
|
||||
"value2": 4
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 202072,
|
||||
"value2": 5
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 2100101,
|
||||
"name": "Ignited Spirit SPD",
|
||||
@@ -28997,6 +29132,141 @@
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 2100601,
|
||||
"name": "Glittering Star",
|
||||
"description": "A Spark that gives a skill hint for \"Glittering Star\".",
|
||||
"spark_group": 21006,
|
||||
"rarity": 1,
|
||||
"type": 4,
|
||||
"effects": [
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 210062,
|
||||
"value2": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 210062,
|
||||
"value2": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 210062,
|
||||
"value2": 3
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 210062,
|
||||
"value2": 4
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 210062,
|
||||
"value2": 5
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 2100602,
|
||||
"name": "Glittering Star",
|
||||
"description": "A Spark that gives a skill hint for \"Glittering Star\".",
|
||||
"spark_group": 21006,
|
||||
"rarity": 2,
|
||||
"type": 4,
|
||||
"effects": [
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 210062,
|
||||
"value2": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 210062,
|
||||
"value2": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 210062,
|
||||
"value2": 3
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 210062,
|
||||
"value2": 4
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 210062,
|
||||
"value2": 5
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 2100603,
|
||||
"name": "Glittering Star",
|
||||
"description": "A Spark that gives a skill hint for \"Glittering Star\".",
|
||||
"spark_group": 21006,
|
||||
"rarity": 3,
|
||||
"type": 4,
|
||||
"effects": [
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 210062,
|
||||
"value2": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 210062,
|
||||
"value2": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 210062,
|
||||
"value2": 3
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 210062,
|
||||
"value2": 4
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 210062,
|
||||
"value2": 5
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 3000101,
|
||||
"name": "URA Finale",
|
||||
@@ -29237,6 +29507,126 @@
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 3000301,
|
||||
"name": "TS Climax Scenario",
|
||||
"description": "A Spark that increases Stamina and Guts.",
|
||||
"spark_group": 30003,
|
||||
"rarity": 1,
|
||||
"type": 6,
|
||||
"effects": [
|
||||
[
|
||||
{
|
||||
"target": 2,
|
||||
"value1": 10
|
||||
},
|
||||
{
|
||||
"target": 4,
|
||||
"value1": 10
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 2,
|
||||
"value1": 20
|
||||
},
|
||||
{
|
||||
"target": 4,
|
||||
"value1": 20
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 2,
|
||||
"value1": 30
|
||||
},
|
||||
{
|
||||
"target": 4,
|
||||
"value1": 30
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 3000302,
|
||||
"name": "TS Climax Scenario",
|
||||
"description": "A Spark that increases Stamina and Guts.",
|
||||
"spark_group": 30003,
|
||||
"rarity": 2,
|
||||
"type": 6,
|
||||
"effects": [
|
||||
[
|
||||
{
|
||||
"target": 2,
|
||||
"value1": 10
|
||||
},
|
||||
{
|
||||
"target": 4,
|
||||
"value1": 10
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 2,
|
||||
"value1": 20
|
||||
},
|
||||
{
|
||||
"target": 4,
|
||||
"value1": 20
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 2,
|
||||
"value1": 30
|
||||
},
|
||||
{
|
||||
"target": 4,
|
||||
"value1": 30
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 3000303,
|
||||
"name": "TS Climax Scenario",
|
||||
"description": "A Spark that increases Stamina and Guts.",
|
||||
"spark_group": 30003,
|
||||
"rarity": 3,
|
||||
"type": 6,
|
||||
"effects": [
|
||||
[
|
||||
{
|
||||
"target": 2,
|
||||
"value1": 10
|
||||
},
|
||||
{
|
||||
"target": 4,
|
||||
"value1": 10
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 2,
|
||||
"value1": 20
|
||||
},
|
||||
{
|
||||
"target": 4,
|
||||
"value1": 20
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 2,
|
||||
"value1": 30
|
||||
},
|
||||
{
|
||||
"target": 4,
|
||||
"value1": 30
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 10010101,
|
||||
"name": "Shooting Star",
|
||||
@@ -35375,6 +35765,285 @@
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 10620101,
|
||||
"name": "Go, Go, Mun!",
|
||||
"description": "A Spark that gives a skill hint for \"Go, Go, Mun!\".",
|
||||
"spark_group": 106201,
|
||||
"rarity": 1,
|
||||
"type": 3,
|
||||
"effects": [
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900621,
|
||||
"value2": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900621,
|
||||
"value2": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900621,
|
||||
"value2": 3
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 10620102,
|
||||
"name": "Go, Go, Mun!",
|
||||
"description": "A Spark that gives a skill hint for \"Go, Go, Mun!\".",
|
||||
"spark_group": 106201,
|
||||
"rarity": 2,
|
||||
"type": 3,
|
||||
"effects": [
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900621,
|
||||
"value2": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900621,
|
||||
"value2": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900621,
|
||||
"value2": 3
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 10620103,
|
||||
"name": "Go, Go, Mun!",
|
||||
"description": "A Spark that gives a skill hint for \"Go, Go, Mun!\".",
|
||||
"spark_group": 106201,
|
||||
"rarity": 3,
|
||||
"type": 3,
|
||||
"effects": [
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900621,
|
||||
"value2": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900621,
|
||||
"value2": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900621,
|
||||
"value2": 3
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 10670101,
|
||||
"name": "Eternal Encompassing Shine",
|
||||
"description": "A Spark that gives a skill hint for \"Eternal Encompassing Shine\".",
|
||||
"spark_group": 106701,
|
||||
"rarity": 1,
|
||||
"type": 3,
|
||||
"effects": [
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900671,
|
||||
"value2": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900671,
|
||||
"value2": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900671,
|
||||
"value2": 3
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 10670102,
|
||||
"name": "Eternal Encompassing Shine",
|
||||
"description": "A Spark that gives a skill hint for \"Eternal Encompassing Shine\".",
|
||||
"spark_group": 106701,
|
||||
"rarity": 2,
|
||||
"type": 3,
|
||||
"effects": [
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900671,
|
||||
"value2": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900671,
|
||||
"value2": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900671,
|
||||
"value2": 3
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 10670103,
|
||||
"name": "Eternal Encompassing Shine",
|
||||
"description": "A Spark that gives a skill hint for \"Eternal Encompassing Shine\".",
|
||||
"spark_group": 106701,
|
||||
"rarity": 3,
|
||||
"type": 3,
|
||||
"effects": [
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900671,
|
||||
"value2": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900671,
|
||||
"value2": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900671,
|
||||
"value2": 3
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 10680101,
|
||||
"name": "Victory Cheer!",
|
||||
"description": "A Spark that gives a skill hint for \"Victory Cheer!\".",
|
||||
"spark_group": 106801,
|
||||
"rarity": 1,
|
||||
"type": 3,
|
||||
"effects": [
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900681,
|
||||
"value2": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900681,
|
||||
"value2": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900681,
|
||||
"value2": 3
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 10680102,
|
||||
"name": "Victory Cheer!",
|
||||
"description": "A Spark that gives a skill hint for \"Victory Cheer!\".",
|
||||
"spark_group": 106801,
|
||||
"rarity": 2,
|
||||
"type": 3,
|
||||
"effects": [
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900681,
|
||||
"value2": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900681,
|
||||
"value2": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900681,
|
||||
"value2": 3
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 10680103,
|
||||
"name": "Victory Cheer!",
|
||||
"description": "A Spark that gives a skill hint for \"Victory Cheer!\".",
|
||||
"spark_group": 106801,
|
||||
"rarity": 3,
|
||||
"type": 3,
|
||||
"effects": [
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900681,
|
||||
"value2": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900681,
|
||||
"value2": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900681,
|
||||
"value2": 3
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 10690101,
|
||||
"name": "Ambition to Surpass the Sakura",
|
||||
@@ -35560,5 +36229,98 @@
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 10740101,
|
||||
"name": "Lovely Spring Breeze",
|
||||
"description": "A Spark that gives a skill hint for \"Lovely Spring Breeze\".",
|
||||
"spark_group": 107401,
|
||||
"rarity": 1,
|
||||
"type": 3,
|
||||
"effects": [
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900741,
|
||||
"value2": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900741,
|
||||
"value2": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900741,
|
||||
"value2": 3
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 10740102,
|
||||
"name": "Lovely Spring Breeze",
|
||||
"description": "A Spark that gives a skill hint for \"Lovely Spring Breeze\".",
|
||||
"spark_group": 107401,
|
||||
"rarity": 2,
|
||||
"type": 3,
|
||||
"effects": [
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900741,
|
||||
"value2": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900741,
|
||||
"value2": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900741,
|
||||
"value2": 3
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"spark_id": 10740103,
|
||||
"name": "Lovely Spring Breeze",
|
||||
"description": "A Spark that gives a skill hint for \"Lovely Spring Breeze\".",
|
||||
"spark_group": 107401,
|
||||
"rarity": 3,
|
||||
"type": 3,
|
||||
"effects": [
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900741,
|
||||
"value2": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900741,
|
||||
"value2": 2
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"target": 41,
|
||||
"value1": 900741,
|
||||
"value2": 3
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -1583,6 +1583,78 @@
|
||||
"skill_pl4": 201072,
|
||||
"skill_pl5": 201431
|
||||
},
|
||||
{
|
||||
"chara_card_id": 106201,
|
||||
"chara_id": 1062,
|
||||
"name": "[Clippety-Tippety-Clop] Matikanetannhauser",
|
||||
"variant": "[Clippety-Tippety-Clop]",
|
||||
"sprint": 0,
|
||||
"mile": 4,
|
||||
"medium": 7,
|
||||
"long": 7,
|
||||
"front": 2,
|
||||
"pace": 7,
|
||||
"late": 7,
|
||||
"end": 3,
|
||||
"turf": 7,
|
||||
"dirt": 1,
|
||||
"unique": 100621,
|
||||
"skill1": 200482,
|
||||
"skill2": 201212,
|
||||
"skill3": 200602,
|
||||
"skill_pl2": 201621,
|
||||
"skill_pl3": 200481,
|
||||
"skill_pl4": 200512,
|
||||
"skill_pl5": 201211
|
||||
},
|
||||
{
|
||||
"chara_card_id": 106701,
|
||||
"chara_id": 1067,
|
||||
"name": "[Natural Brilliance] Satono Diamond",
|
||||
"variant": "[Natural Brilliance]",
|
||||
"sprint": 0,
|
||||
"mile": 5,
|
||||
"medium": 7,
|
||||
"long": 7,
|
||||
"front": 1,
|
||||
"pace": 6,
|
||||
"late": 7,
|
||||
"end": 4,
|
||||
"turf": 7,
|
||||
"dirt": 1,
|
||||
"unique": 100671,
|
||||
"skill1": 200012,
|
||||
"skill2": 201692,
|
||||
"skill3": 201102,
|
||||
"skill_pl2": 202012,
|
||||
"skill_pl3": 201691,
|
||||
"skill_pl4": 200152,
|
||||
"skill_pl5": 200014
|
||||
},
|
||||
{
|
||||
"chara_card_id": 106801,
|
||||
"chara_id": 1068,
|
||||
"name": "[Gilded Shrine to Glory] Kitasan Black",
|
||||
"variant": "[Gilded Shrine to Glory]",
|
||||
"sprint": 0,
|
||||
"mile": 5,
|
||||
"medium": 7,
|
||||
"long": 7,
|
||||
"front": 7,
|
||||
"pace": 6,
|
||||
"late": 5,
|
||||
"end": 1,
|
||||
"turf": 7,
|
||||
"dirt": 1,
|
||||
"unique": 100681,
|
||||
"skill1": 200432,
|
||||
"skill2": 201172,
|
||||
"skill3": 200532,
|
||||
"skill_pl2": 201102,
|
||||
"skill_pl3": 200531,
|
||||
"skill_pl4": 201272,
|
||||
"skill_pl5": 201173
|
||||
},
|
||||
{
|
||||
"chara_card_id": 106901,
|
||||
"chara_id": 1069,
|
||||
@@ -1630,5 +1702,29 @@
|
||||
"skill_pl3": 200571,
|
||||
"skill_pl4": 201142,
|
||||
"skill_pl5": 201701
|
||||
},
|
||||
{
|
||||
"chara_card_id": 107401,
|
||||
"chara_id": 1074,
|
||||
"name": "[Brunissage Line] Mejiro Bright",
|
||||
"variant": "[Brunissage Line]",
|
||||
"sprint": 0,
|
||||
"mile": 5,
|
||||
"medium": 7,
|
||||
"long": 7,
|
||||
"front": 1,
|
||||
"pace": 4,
|
||||
"late": 7,
|
||||
"end": 7,
|
||||
"turf": 7,
|
||||
"dirt": 1,
|
||||
"unique": 100741,
|
||||
"skill1": 200472,
|
||||
"skill2": 201462,
|
||||
"skill3": 202072,
|
||||
"skill_pl2": 201212,
|
||||
"skill_pl3": 200471,
|
||||
"skill_pl4": 201222,
|
||||
"skill_pl5": 202071
|
||||
}
|
||||
]
|
||||
@@ -17,3 +17,43 @@ type AffinityRelation struct {
|
||||
IDC int `json:"chara_c,omitzero"`
|
||||
Affinity int `json:"affinity"`
|
||||
}
|
||||
|
||||
// Conversation describes a lobby conversation.
|
||||
type Conversation struct {
|
||||
// CharacterID is the ID of the character who has the conversation as
|
||||
// a gallery entry.
|
||||
CharacterID CharacterID `json:"chara_id"`
|
||||
// Number is the conversation number within the character's gallery.
|
||||
Number int `json:"number"`
|
||||
// Location is the ID of the location the conversation occurs in the lobby.
|
||||
Location LobbyConversationLocationID `json:"location"`
|
||||
// LocationName is the name of the lobby location, for convenience.
|
||||
LocationName string `json:"location_name"`
|
||||
// Chara1 is the first conversation participant ID.
|
||||
// It does not necessarily match CharacterID.
|
||||
Chara1 CharacterID `json:"chara_1"`
|
||||
// Chara2 is the second conversation participant ID.
|
||||
Chara2 CharacterID `json:"chara_2,omitzero"`
|
||||
// Chara3 is the third conversation participant ID.
|
||||
Chara3 CharacterID `json:"chara_3,omitzero"`
|
||||
// ConditionType is a complete mystery to me.
|
||||
ConditionType int `json:"condition_type"`
|
||||
}
|
||||
|
||||
type LobbyConversationLocationID int
|
||||
|
||||
//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type LobbyConversationLocationID -trimprefix Lobby -linecomment
|
||||
const (
|
||||
LobbyRightFront1 LobbyConversationLocationID = 110 // right side front
|
||||
LobbyRightFront2 LobbyConversationLocationID = 120 // right side front
|
||||
LobbyRightFront3 LobbyConversationLocationID = 130 // right side front
|
||||
LobbyLeftTable1 LobbyConversationLocationID = 210 // left side table
|
||||
LobbyLeftTable2 LobbyConversationLocationID = 220 // left side table
|
||||
LobbyBackSeat LobbyConversationLocationID = 310 // center back seat
|
||||
LobbyPosters1 LobbyConversationLocationID = 410 // center posters
|
||||
LobbyPosters2 LobbyConversationLocationID = 420 // center posters
|
||||
LobbyPosters3 LobbyConversationLocationID = 430 // center posters
|
||||
LobbySchoolMap1 LobbyConversationLocationID = 510 // left side school map
|
||||
LobbySchoolMap2 LobbyConversationLocationID = 520 // left side school map
|
||||
LobbySchoolMap3 LobbyConversationLocationID = 530 // left side school map
|
||||
)
|
||||
|
||||
47
horse/lobbyconversationlocationid_string.go
Normal file
47
horse/lobbyconversationlocationid_string.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Code generated by "stringer -type LobbyConversationLocationID -trimprefix Lobby -linecomment"; DO NOT EDIT.
|
||||
|
||||
package horse
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[LobbyRightFront1-110]
|
||||
_ = x[LobbyRightFront2-120]
|
||||
_ = x[LobbyRightFront3-130]
|
||||
_ = x[LobbyLeftTable1-210]
|
||||
_ = x[LobbyLeftTable2-220]
|
||||
_ = x[LobbyBackSeat-310]
|
||||
_ = x[LobbyPosters1-410]
|
||||
_ = x[LobbyPosters2-420]
|
||||
_ = x[LobbyPosters3-430]
|
||||
_ = x[LobbySchoolMap1-510]
|
||||
_ = x[LobbySchoolMap2-520]
|
||||
_ = x[LobbySchoolMap3-530]
|
||||
}
|
||||
|
||||
const _LobbyConversationLocationID_name = "right side frontright side frontright side frontleft side tableleft side tablecenter back seatcenter posterscenter posterscenter postersleft side school mapleft side school mapleft side school map"
|
||||
|
||||
var _LobbyConversationLocationID_map = map[LobbyConversationLocationID]string{
|
||||
110: _LobbyConversationLocationID_name[0:16],
|
||||
120: _LobbyConversationLocationID_name[16:32],
|
||||
130: _LobbyConversationLocationID_name[32:48],
|
||||
210: _LobbyConversationLocationID_name[48:63],
|
||||
220: _LobbyConversationLocationID_name[63:78],
|
||||
310: _LobbyConversationLocationID_name[78:94],
|
||||
410: _LobbyConversationLocationID_name[94:108],
|
||||
420: _LobbyConversationLocationID_name[108:122],
|
||||
430: _LobbyConversationLocationID_name[122:136],
|
||||
510: _LobbyConversationLocationID_name[136:156],
|
||||
520: _LobbyConversationLocationID_name[156:176],
|
||||
530: _LobbyConversationLocationID_name[176:196],
|
||||
}
|
||||
|
||||
func (i LobbyConversationLocationID) String() string {
|
||||
if str, ok := _LobbyConversationLocationID_map[i]; ok {
|
||||
return str
|
||||
}
|
||||
return "LobbyConversationLocationID(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
@@ -46,7 +46,7 @@ type Activation struct {
|
||||
Precondition string `json:"precondition,omitzero"`
|
||||
Condition string `json:"condition"`
|
||||
Duration TenThousandths `json:"duration,omitzero"`
|
||||
DurScale DurScale `json:"dur_scale,omitzero"`
|
||||
DurScale DurScale `json:"dur_scale"`
|
||||
Cooldown TenThousandths `json:"cooldown,omitzero"`
|
||||
Abilities []Ability `json:"abilities"`
|
||||
}
|
||||
@@ -57,7 +57,7 @@ type Ability struct {
|
||||
ValueUsage AbilityValueUsage `json:"value_usage"`
|
||||
Value TenThousandths `json:"value"`
|
||||
Target AbilityTarget `json:"target"`
|
||||
TargetValue int32 `json:"target_value"`
|
||||
TargetValue int32 `json:"target_value,omitzero"`
|
||||
}
|
||||
|
||||
func (a Ability) String() string {
|
||||
@@ -211,6 +211,9 @@ type SkillGroupID int32
|
||||
// skill with the corresponding group rate.
|
||||
// Some skill groups contain only Skill2 or SkillBad, while others may have all
|
||||
// four skills.
|
||||
//
|
||||
// As a special case, horsegen lists both unique skills and their inherited
|
||||
// versions in the skill groups for both.
|
||||
type SkillGroup struct {
|
||||
ID SkillGroupID `json:"skill_group"`
|
||||
// Skill1 is the base version of the skill, either a common (white) skill
|
||||
|
||||
420
schema/schema.ts
Normal file
420
schema/schema.ts
Normal file
@@ -0,0 +1,420 @@
|
||||
/**
|
||||
* TypeScript schema for JSON files generated by horsegen.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Character definitions.
|
||||
*/
|
||||
export interface Character {
|
||||
/**
|
||||
* Character ID.
|
||||
*/
|
||||
chara_id: number;
|
||||
/**
|
||||
* Regional name of the character.
|
||||
* E.g., Special Week for Global, or スペシャルウィーク for JP.
|
||||
*/
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Precomputed character pair and trio affinity.
|
||||
*/
|
||||
export interface Affinity {
|
||||
/**
|
||||
* First character in the relation.
|
||||
*/
|
||||
chara_a: number;
|
||||
/**
|
||||
* Second character in the relation.
|
||||
* chara_a < chara_b is an invariant.
|
||||
*/
|
||||
chara_b: number;
|
||||
/**
|
||||
* Third character in the relation, if it is a trio relation.
|
||||
* If defined, chara_b < chara_c is an invariant.
|
||||
*/
|
||||
chara_c?: number;
|
||||
/**
|
||||
* Total base compatibility between characters in the relation.
|
||||
*/
|
||||
affinity: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uma or character card definitions.
|
||||
*/
|
||||
export interface Uma {
|
||||
/**
|
||||
* Uma ID.
|
||||
*/
|
||||
chara_card_id: number;
|
||||
/**
|
||||
* Character ID that the Uma is a variant of.
|
||||
*/
|
||||
chara_id: number;
|
||||
/**
|
||||
* Regional name of the Uma, comprised of the variant name and the character name.
|
||||
* E.g. "[Special Dreamer] Special Week".
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Regional variant name.
|
||||
* E.g. "[Special Dreamer]".
|
||||
*/
|
||||
variant: string;
|
||||
|
||||
sprint: AptitudeLevel;
|
||||
mile: AptitudeLevel;
|
||||
medium: AptitudeLevel;
|
||||
long: AptitudeLevel;
|
||||
front: AptitudeLevel;
|
||||
pace: AptitudeLevel;
|
||||
late: AptitudeLevel;
|
||||
end: AptitudeLevel;
|
||||
turf: AptitudeLevel;
|
||||
dirt: AptitudeLevel;
|
||||
|
||||
/**
|
||||
* ID of the Uma's unique skill.
|
||||
*/
|
||||
unique: number;
|
||||
/**
|
||||
* ID of the Uma's first built-in skill.
|
||||
*/
|
||||
skill1: number;
|
||||
/**
|
||||
* ID of the Uma's second built-in skill.
|
||||
*/
|
||||
skill2: number;
|
||||
/**
|
||||
* ID of the Uma's third built-in skill.
|
||||
*/
|
||||
skill3: number;
|
||||
/**
|
||||
* ID of the skill unlocked at potential level 2.
|
||||
*/
|
||||
skill_pl2: number;
|
||||
/**
|
||||
* ID of the skill unlocked at potential level 3.
|
||||
*/
|
||||
skill_pl3: number;
|
||||
/**
|
||||
* ID of the skill unlocked at potential level 4.
|
||||
*/
|
||||
skill_pl4: number;
|
||||
/**
|
||||
* ID of the skill unlocked at potential level 5.
|
||||
*/
|
||||
skill_pl5: number;
|
||||
}
|
||||
|
||||
export type AptitudeLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
||||
|
||||
/**
|
||||
* Race data.
|
||||
*/
|
||||
export interface Race {
|
||||
/**
|
||||
* Race ID.
|
||||
*/
|
||||
race_id: number;
|
||||
/**
|
||||
* Regional name of the race.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Thumbnail asset ID number.
|
||||
*/
|
||||
thumbnail: number;
|
||||
/**
|
||||
* Primary race ID.
|
||||
* For most races, this is the same as race_id. Some races are alternate
|
||||
* versions for certain careers; this holds the ID of the normal version of
|
||||
* the race.
|
||||
*/
|
||||
primary: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Race saddle data.
|
||||
*/
|
||||
export interface Saddle {
|
||||
/**
|
||||
* Saddle ID.
|
||||
*/
|
||||
saddle_id: number;
|
||||
/**
|
||||
* Regional name of the saddle.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* IDs of race wins required to earn the saddle.
|
||||
*/
|
||||
races: number[];
|
||||
/**
|
||||
* Saddle type: 0 for multi-race honors, 3 for G1, 2 for G2, 1 for G3.
|
||||
*/
|
||||
type: 0 | 1 | 2 | 3;
|
||||
/**
|
||||
* Primary saddle ID.
|
||||
* Respective for races.
|
||||
*/
|
||||
primary: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scenario data.
|
||||
*/
|
||||
export interface Scenario {
|
||||
/**
|
||||
* Scenario ID.
|
||||
*/
|
||||
scenario_id: number;
|
||||
/**
|
||||
* Regional scenario name, e.g. "TS Climax".
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Regional full title, e.g. "Trackblazer: Start of the Climax".
|
||||
*/
|
||||
title: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skill data.
|
||||
*/
|
||||
export interface Skill {
|
||||
/**
|
||||
* Skill ID.
|
||||
*/
|
||||
skill_id: number;
|
||||
/**
|
||||
* Regional skill name.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Regional skil description.
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* Skill group ID.
|
||||
*/
|
||||
group: number;
|
||||
/**
|
||||
* Skill rarity. 3-5 are uniques for various star levels.
|
||||
*/
|
||||
rarity: 1 | 2 | 3 | 4 | 5;
|
||||
/**
|
||||
* Upgrade position within the skill's group.
|
||||
* -1 is for negative (purple) skills.
|
||||
*/
|
||||
group_rate: 1 | 2 | 3 | -1;
|
||||
/**
|
||||
* Grade value, or the amount of rating gained for having the skill with
|
||||
* appropriate aptitude.
|
||||
*/
|
||||
grade_value?: number;
|
||||
/**
|
||||
* Whether the skill requires a wit check.
|
||||
*/
|
||||
wit_check: boolean;
|
||||
/**
|
||||
* Conditions and results of skill activation.
|
||||
*/
|
||||
activations: [Activation] | [Activation, Activation];
|
||||
/**
|
||||
* Name of the Uma which owns this skill as a unique, if applicable.
|
||||
*/
|
||||
unique_owner?: string;
|
||||
/**
|
||||
* SP cost to purchase the skill, if applicable.
|
||||
*/
|
||||
sp_cost?: number;
|
||||
/**
|
||||
* Skill icon ID.
|
||||
*/
|
||||
icon_id: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conditions and results of skill activation.
|
||||
*/
|
||||
export interface Activation {
|
||||
/**
|
||||
* Precondition which must be satisfied before the condition is checked.
|
||||
*/
|
||||
precondition?: string;
|
||||
/**
|
||||
* Activation conditions.
|
||||
*/
|
||||
condition: string;
|
||||
/**
|
||||
* Skill duration in ten thousandths of a second.
|
||||
* Generally undefined for activations which only affect HP.
|
||||
*/
|
||||
duration?: number;
|
||||
/**
|
||||
* Special skill duration scaling mode.
|
||||
*/
|
||||
dur_scale: 1 | 2 | 3 | 4 | 5 | 7;
|
||||
/**
|
||||
* Skill cooldown in ten thousandths of a second.
|
||||
* A value of 5000000 indicates that the cooldown is forever.
|
||||
* Generally undefined for passive skills.
|
||||
*/
|
||||
cooldown?: number;
|
||||
/**
|
||||
* Results applied when the skill's conditions are met.
|
||||
*/
|
||||
abilities: [Ability] | [Ability, Ability] | [Ability, Ability, Ability];
|
||||
}
|
||||
|
||||
/**
|
||||
* Effects applied when a skill activates.
|
||||
*/
|
||||
export interface Ability {
|
||||
/**
|
||||
* Race mechanic affected by the ability.
|
||||
*/
|
||||
type: 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 13 | 21 | 27 | 28 | 31 | 35;
|
||||
/**
|
||||
* Special scaling type of the skill value.
|
||||
*/
|
||||
value_usage: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 19 | 20 | 22 | 23 | 24 | 25;
|
||||
/**
|
||||
* Amount that the skill modifies the race mechanic in ten thousandths of
|
||||
* whatever is the appropriate unit.
|
||||
*/
|
||||
value: number;
|
||||
/**
|
||||
* Selector for horses targeted by the ability.
|
||||
*/
|
||||
target: 1 | 2 | 4 | 7 | 9 | 10 | 11 | 18 | 19 | 20 | 21 | 22 | 23;
|
||||
/**
|
||||
* Argument value for the ability target, when appropriate.
|
||||
*/
|
||||
target_value?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skill groups.
|
||||
* Skills in a skill group replace each other when purchased.
|
||||
*
|
||||
* As a special case, horsegen lists both unique skills and their inherited
|
||||
* versions in the skill groups for both.
|
||||
*/
|
||||
export interface SkillGroup {
|
||||
/**
|
||||
* Skill group ID.
|
||||
*/
|
||||
skill_group: number;
|
||||
/**
|
||||
* Base skill in the skill group, if any.
|
||||
* Either a common (white) skill or an Uma's own unique.
|
||||
*
|
||||
* Some skill groups, e.g. for G1 Averseness, have no base skill.
|
||||
*/
|
||||
skill1?: number;
|
||||
/**
|
||||
* First upgraded version of a skill, if any.
|
||||
* A rare (gold) skill, double circle skill, or an inherited unique skill.
|
||||
*/
|
||||
skill2?: number;
|
||||
/**
|
||||
* Highest upgraded version of a skill, if any.
|
||||
* Gold version of a skill with a double circle version.
|
||||
*/
|
||||
skill3?: number;
|
||||
/**
|
||||
* Negative (purple) version of a skill, if any.
|
||||
*/
|
||||
skill_bad?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sparks, or succession factors.
|
||||
*/
|
||||
export interface Spark {
|
||||
/**
|
||||
* Spark ID.
|
||||
*/
|
||||
spark_id: number;
|
||||
/**
|
||||
* Regional spark name.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Regional spark description.
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* Spark group.
|
||||
* Different star levels of a given spark are different spark IDs but
|
||||
* share a spark group.
|
||||
*/
|
||||
spark_group: number;
|
||||
/**
|
||||
* Spark rarity, or star level.
|
||||
*/
|
||||
rarity: 1 | 2 | 3;
|
||||
/**
|
||||
* Spark type.
|
||||
* Roughly the spark color, with extra subdivisions for white sparks.
|
||||
*/
|
||||
type: 1 | 2 | 5 | 4 | 6 | 7 | 10 | 8 | 11 | 9 | 3;
|
||||
/**
|
||||
* Possible effects applied by the spark during inspiration.
|
||||
* A random element is selected from this list according to unknown
|
||||
* distributions, then all effects in that selection are applied.
|
||||
*/
|
||||
effects: SparkEffect[][];
|
||||
}
|
||||
|
||||
/**
|
||||
* Effects that a spark can apply.
|
||||
*/
|
||||
export interface SparkEffect {
|
||||
target: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 21 | 22 | 23 | 24 | 31 | 32 | 33 | 34 | 41 | 51 | 61 | 62 | 63 | 64 | 65;
|
||||
value1?: number;
|
||||
value2: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lobby conversation data.
|
||||
*/
|
||||
export interface Conversation {
|
||||
/**
|
||||
* Character who owns the conversation as a gallery entry.
|
||||
*/
|
||||
chara_id: number;
|
||||
/**
|
||||
* Number of the conversation within the character's conversation gallery.
|
||||
*/
|
||||
number: number;
|
||||
/**
|
||||
* Location ID of the conversation.
|
||||
*/
|
||||
location: 110 | 120 | 130 | 210 | 220 | 310 | 410 | 420 | 430 | 510 | 520 | 530;
|
||||
/**
|
||||
* English name of the location, for convenience.
|
||||
*/
|
||||
location_name: string;
|
||||
/**
|
||||
* First character in the conversation.
|
||||
* Not necessarily equal to chara_id.
|
||||
*/
|
||||
chara_1: number;
|
||||
/**
|
||||
* Second character, if present.
|
||||
*/
|
||||
chara_2?: number;
|
||||
/**
|
||||
* Third character, if present.
|
||||
*/
|
||||
chara_3?: number;
|
||||
/**
|
||||
* Some unknown number in the game's local database.
|
||||
*/
|
||||
condition_type: 0 | 1 | 2 | 3 | 4;
|
||||
}
|
||||
23
zenno/.gitignore
vendored
Normal file
23
zenno/.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
node_modules
|
||||
|
||||
# Output
|
||||
.output
|
||||
.vercel
|
||||
.netlify
|
||||
.wrangler
|
||||
/.svelte-kit
|
||||
/build
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Env
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.test
|
||||
|
||||
# Vite
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
1
zenno/.npmrc
Normal file
1
zenno/.npmrc
Normal file
@@ -0,0 +1 @@
|
||||
engine-strict=true
|
||||
9
zenno/.prettierignore
Normal file
9
zenno/.prettierignore
Normal file
@@ -0,0 +1,9 @@
|
||||
# Package Managers
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
yarn.lock
|
||||
bun.lock
|
||||
bun.lockb
|
||||
|
||||
# Miscellaneous
|
||||
/static/
|
||||
16
zenno/.prettierrc
Normal file
16
zenno/.prettierrc
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 130,
|
||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.svelte",
|
||||
"options": {
|
||||
"parser": "svelte"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tailwindStylesheet": "./src/routes/layout.css"
|
||||
}
|
||||
42
zenno/README.md
Normal file
42
zenno/README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# sv
|
||||
|
||||
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```sh
|
||||
# create a new project
|
||||
npx sv create my-app
|
||||
```
|
||||
|
||||
To recreate this project with the same configuration:
|
||||
|
||||
```sh
|
||||
# recreate this project
|
||||
npx sv@0.13.0 create --template minimal --types ts --add prettier eslint vitest="usages:unit,component" tailwindcss="plugins:none" --install npm zenno
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
||||
39
zenno/eslint.config.js
Normal file
39
zenno/eslint.config.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import prettier from 'eslint-config-prettier';
|
||||
import path from 'node:path';
|
||||
import { includeIgnoreFile } from '@eslint/compat';
|
||||
import js from '@eslint/js';
|
||||
import svelte from 'eslint-plugin-svelte';
|
||||
import { defineConfig } from 'eslint/config';
|
||||
import globals from 'globals';
|
||||
import ts from 'typescript-eslint';
|
||||
import svelteConfig from './svelte.config.js';
|
||||
|
||||
const gitignorePath = path.resolve(import.meta.dirname, '.gitignore');
|
||||
|
||||
export default defineConfig(
|
||||
includeIgnoreFile(gitignorePath),
|
||||
js.configs.recommended,
|
||||
ts.configs.recommended,
|
||||
svelte.configs.recommended,
|
||||
prettier,
|
||||
svelte.configs.prettier,
|
||||
{
|
||||
languageOptions: { globals: { ...globals.browser, ...globals.node } },
|
||||
rules: {
|
||||
// typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
|
||||
// see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
|
||||
'no-undef': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
extraFileExtensions: ['.svelte'],
|
||||
parser: ts.parser,
|
||||
svelteConfig,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
4347
zenno/package-lock.json
generated
Normal file
4347
zenno/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
45
zenno/package.json
Normal file
45
zenno/package.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "zenno",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"format": "prettier --write .",
|
||||
"test:unit": "vitest",
|
||||
"test": "npm run test:unit -- --run"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^2.0.3",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@sveltejs/adapter-auto": "^7.0.0",
|
||||
"@sveltejs/adapter-static": "^3.0.10",
|
||||
"@sveltejs/kit": "^2.50.2",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@types/node": "^22",
|
||||
"@vitest/browser-playwright": "^4.1.0",
|
||||
"eslint": "^10.0.3",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-svelte": "^3.15.2",
|
||||
"globals": "^17.4.0",
|
||||
"playwright": "^1.58.2",
|
||||
"prettier": "^3.8.1",
|
||||
"prettier-plugin-svelte": "^3.4.1",
|
||||
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||
"svelte": "^5.54.0",
|
||||
"svelte-check": "^4.4.2",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.57.0",
|
||||
"vite": "^7.3.1",
|
||||
"vitest": "^4.1.0",
|
||||
"vitest-browser-svelte": "^2.0.2"
|
||||
}
|
||||
}
|
||||
13
zenno/src/app.d.ts
vendored
Normal file
13
zenno/src/app.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
11
zenno/src/app.html
Normal file
11
zenno/src/app.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
38
zenno/src/lib/CharaPick.svelte
Normal file
38
zenno/src/lib/CharaPick.svelte
Normal file
@@ -0,0 +1,38 @@
|
||||
<script lang="ts">
|
||||
import { character } from '$lib/data/character';
|
||||
import type { ClassValue } from 'svelte/elements';
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
value: number;
|
||||
label?: string;
|
||||
class?: ClassValue | null;
|
||||
optionClass?: ClassValue | null;
|
||||
labelClass?: ClassValue | null;
|
||||
region?: keyof typeof character;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
id,
|
||||
value = $bindable(),
|
||||
label,
|
||||
class: className,
|
||||
optionClass,
|
||||
labelClass,
|
||||
region = 'global',
|
||||
required = false,
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if label}
|
||||
<label for={id} class={labelClass}>{label}</label>
|
||||
{/if}
|
||||
<select {id} class={className} bind:value {required}>
|
||||
{#if !required}
|
||||
<option value="0" class={optionClass}></option>
|
||||
{/if}
|
||||
{#each character[region] as c}
|
||||
<option value={c.chara_id} class={optionClass}>{c.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
BIN
zenno/src/lib/assets/favicon.png
Executable file
BIN
zenno/src/lib/assets/favicon.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
26
zenno/src/lib/data/character.ts
Normal file
26
zenno/src/lib/data/character.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { RegionalName } from '$lib/regional-name';
|
||||
import globalJSON from '../../../../global/character.json';
|
||||
|
||||
/**
|
||||
* Character definitions.
|
||||
*/
|
||||
export interface Character {
|
||||
/**
|
||||
* Character ID.
|
||||
*/
|
||||
chara_id: number;
|
||||
/**
|
||||
* Regional name of the character.
|
||||
* E.g., Special Week for Global, or スペシャルウィーク for JP.
|
||||
*/
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const character = {
|
||||
global: globalJSON as Character[],
|
||||
};
|
||||
|
||||
export const charaNames = globalJSON.reduce(
|
||||
(m, c) => m.set(c.chara_id, { en: c.name }),
|
||||
new Map<Character['chara_id'], RegionalName>(),
|
||||
);
|
||||
85
zenno/src/lib/data/convo.ts
Normal file
85
zenno/src/lib/data/convo.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import type { RegionalName } from '$lib/regional-name';
|
||||
import globalJSON from '../../../../global/conversation.json';
|
||||
|
||||
/**
|
||||
* Lobby conversation data.
|
||||
*/
|
||||
export interface Conversation {
|
||||
/**
|
||||
* Character who owns the conversation as a gallery entry.
|
||||
*/
|
||||
chara_id: number;
|
||||
/**
|
||||
* Number of the conversation within the character's conversation gallery.
|
||||
*/
|
||||
number: number;
|
||||
/**
|
||||
* Location ID of the conversation.
|
||||
*/
|
||||
location: 110 | 120 | 130 | 210 | 220 | 310 | 410 | 420 | 430 | 510 | 520 | 530;
|
||||
/**
|
||||
* English name of the location, for convenience.
|
||||
*/
|
||||
location_name: string;
|
||||
/**
|
||||
* First character in the conversation.
|
||||
* Not necessarily equal to chara_id.
|
||||
*/
|
||||
chara_1: number;
|
||||
/**
|
||||
* Second character, if present.
|
||||
*/
|
||||
chara_2?: number;
|
||||
/**
|
||||
* Third character, if present.
|
||||
*/
|
||||
chara_3?: number;
|
||||
/**
|
||||
* Some unknown number in the game's local database.
|
||||
*/
|
||||
condition_type: 0 | 1 | 2 | 3 | 4;
|
||||
}
|
||||
|
||||
export const conversation = {
|
||||
global: globalJSON as Conversation[],
|
||||
};
|
||||
|
||||
export const byChara = {
|
||||
global: globalJSON.reduce(
|
||||
(m, c) => m.set(c.chara_id, (m.get(c.chara_id) ?? []).concat(c as Conversation)),
|
||||
new Map<Conversation['chara_id'], Conversation[]>(),
|
||||
),
|
||||
};
|
||||
|
||||
export const locations: Record<Conversation['location'], { name: RegionalName; group: 1 | 2 | 3 | 4 | 5 }> = {
|
||||
110: { name: { en: 'right side front' }, group: 1 },
|
||||
120: { name: { en: 'right side front' }, group: 1 },
|
||||
130: { name: { en: 'right side front' }, group: 1 },
|
||||
210: { name: { en: 'left side table' }, group: 2 },
|
||||
220: { name: { en: 'left side table' }, group: 2 },
|
||||
310: { name: { en: 'center back seat' }, group: 3 },
|
||||
410: { name: { en: 'center posters' }, group: 4 },
|
||||
420: { name: { en: 'center posters' }, group: 4 },
|
||||
430: { name: { en: 'center posters' }, group: 4 },
|
||||
510: { name: { en: 'left side school map' }, group: 5 },
|
||||
520: { name: { en: 'left side school map' }, group: 5 },
|
||||
530: { name: { en: 'left side school map' }, group: 5 },
|
||||
};
|
||||
|
||||
function locCharas(convos: Conversation[], locGroup: 1 | 2 | 3 | 4 | 5) {
|
||||
const m = convos
|
||||
.filter((c) => locations[c.location].group === locGroup)
|
||||
.flatMap((c) => [c.chara_1, c.chara_2, c.chara_3].filter((x) => x != null))
|
||||
.reduce((m, id) => m.set(id, 1 + (m.get(id) ?? 0)), new Map<number, number>());
|
||||
return [...m].toSorted((a, b) => b[1] - a[1]); // descending
|
||||
}
|
||||
|
||||
export const groupPopulars = {
|
||||
global: {
|
||||
1: locCharas(conversation.global, 1),
|
||||
2: locCharas(conversation.global, 2),
|
||||
3: locCharas(conversation.global, 3),
|
||||
4: locCharas(conversation.global, 4),
|
||||
5: locCharas(conversation.global, 5),
|
||||
},
|
||||
};
|
||||
1
zenno/src/lib/index.ts
Normal file
1
zenno/src/lib/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
||||
7
zenno/src/lib/regional-name.ts
Normal file
7
zenno/src/lib/regional-name.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Names accounting for regions.
|
||||
* Currently English is the only supported language.
|
||||
*/
|
||||
export interface RegionalName {
|
||||
en: string;
|
||||
}
|
||||
8
zenno/src/lib/vitest-examples/Welcome.svelte
Normal file
8
zenno/src/lib/vitest-examples/Welcome.svelte
Normal file
@@ -0,0 +1,8 @@
|
||||
<script>
|
||||
import { greet } from './greet';
|
||||
|
||||
let { host = 'SvelteKit', guest = 'Vitest' } = $props();
|
||||
</script>
|
||||
|
||||
<h1>{greet(host)}</h1>
|
||||
<p>{greet(guest)}</p>
|
||||
13
zenno/src/lib/vitest-examples/Welcome.svelte.spec.ts
Normal file
13
zenno/src/lib/vitest-examples/Welcome.svelte.spec.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { page } from 'vitest/browser';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { render } from 'vitest-browser-svelte';
|
||||
import Welcome from './Welcome.svelte';
|
||||
|
||||
describe('Welcome.svelte', () => {
|
||||
it('renders greetings for host and guest', async () => {
|
||||
render(Welcome, { host: 'SvelteKit', guest: 'Vitest' });
|
||||
|
||||
await expect.element(page.getByRole('heading', { level: 1 })).toHaveTextContent('Hello, SvelteKit!');
|
||||
await expect.element(page.getByText('Hello, Vitest!')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
8
zenno/src/lib/vitest-examples/greet.spec.ts
Normal file
8
zenno/src/lib/vitest-examples/greet.spec.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { greet } from './greet';
|
||||
|
||||
describe('greet', () => {
|
||||
it('returns a greeting', () => {
|
||||
expect(greet('Svelte')).toBe('Hello, Svelte!');
|
||||
});
|
||||
});
|
||||
3
zenno/src/lib/vitest-examples/greet.ts
Normal file
3
zenno/src/lib/vitest-examples/greet.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function greet(name: string): string {
|
||||
return 'Hello, ' + name + '!';
|
||||
}
|
||||
38
zenno/src/routes/+layout.svelte
Normal file
38
zenno/src/routes/+layout.svelte
Normal file
@@ -0,0 +1,38 @@
|
||||
<script lang="ts">
|
||||
import './layout.css';
|
||||
import favicon from '$lib/assets/favicon.png';
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Zenno Rob Roy</title>
|
||||
<link rel="icon" href={favicon} />
|
||||
</svelte:head>
|
||||
|
||||
<div class="flex h-screen flex-col">
|
||||
<nav class="mb-4 flex min-w-full bg-mist-300 p-4 shadow-md dark:bg-mist-900">
|
||||
<span class="hidden flex-1 md:inline">
|
||||
<a href="/" class="text-4xl">Zenno Rob Roy</a>
|
||||
</span>
|
||||
<span class="flex-1 text-center">
|
||||
<a href="/" class="mx-8 my-1 block font-semibold md:hidden">Zenno Rob Roy</a>
|
||||
<a href="/inherit" class="mx-8 my-1 inline-block">Inheritance Chance</a>
|
||||
<a href="/spark" class="mx-8 my-1 inline-block">Spark Chance</a>
|
||||
<a href="/vet" class="mx-8 my-1 inline-block">My Veterans</a>
|
||||
<a href="/convo" class="mx-8 my-1 inline-block">Lobby Conversations</a>
|
||||
</span>
|
||||
</nav>
|
||||
<div class="mx-4 grow lg:m-auto lg:max-w-7xl lg:min-w-7xl">
|
||||
{@render children()}
|
||||
</div>
|
||||
<footer class="inset-x-0 bottom-0 mt-8 border-t bg-mist-300 p-4 text-center text-sm md:mt-20 dark:border-none dark:bg-mist-900">
|
||||
Umamusume: Pretty Derby tools by <a href="https://zephyrtronium.date/" target="_blank" rel="noopener noreferrer"
|
||||
>zephyrtronium</a
|
||||
>.<br />
|
||||
All game data is auto-generated from the
|
||||
<a href="https://git.sunturtle.xyz/zephyr/horse/src/branch/main/doc/README.md" target="_blank" rel="noopener noreferrer"
|
||||
>game's local database</a
|
||||
>.
|
||||
</footer>
|
||||
</div>
|
||||
2
zenno/src/routes/+layout.ts
Normal file
2
zenno/src/routes/+layout.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const prerender = true;
|
||||
export const trailingSlash = 'always';
|
||||
40
zenno/src/routes/+page.svelte
Normal file
40
zenno/src/routes/+page.svelte
Normal file
@@ -0,0 +1,40 @@
|
||||
<h1 class="m-8 text-center text-7xl">Zenno Rob Roy</h1>
|
||||
<p>She's read all about Umamusume, and she's always happy to share her knowledge and give recommendations!</p>
|
||||
<h2 class="mt-8 mb-4 text-4xl">Tools</h2>
|
||||
<ul class="list-disc pl-4">
|
||||
<li>
|
||||
<a href="/inherit">Inheritance Chance</a> — <i>Not yet implemented</i> — Given a legacy, calculate the probability distribution
|
||||
of activation counts for each spark.
|
||||
</li>
|
||||
<li>
|
||||
<a href="/spark">Spark Chance</a> — <i>Not yet implemented</i> — Given a legacy, calculate the chance of generating each spark if
|
||||
you fulfill the conditions to do so, and the distribution of total spark counts.
|
||||
</li>
|
||||
<li>
|
||||
<a href="/vet">My Veterans</a> — <i>Not yet implemented</i> — Set up and track your veterans for Zenno Rob Roy's inspiration and
|
||||
spark calculators.
|
||||
</li>
|
||||
<li>
|
||||
<a href="/convo">Lobby Conversations</a> — Check participants in lobby conversations and get recommendations on unlocking them quickly.
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://discord.com/oauth2/authorize?client_id=1461931240264568994" target="_blank" rel="noopener noreferrer"
|
||||
>Discord Bot</a
|
||||
>
|
||||
— Skill search by name or unique owner within Discord. Install to a server or user.
|
||||
</li>
|
||||
</ul>
|
||||
<h2 class="mt-8 mb-4 text-4xl">About</h2>
|
||||
<p>Tools to fill some gaps I've felt in Umamusume optimization.</p>
|
||||
<p>This site is very under construction. To demonstrate just how under construction it is, here is lorem ipsum:</p>
|
||||
<p>
|
||||
Lorem ipsum (/ ˌ l ɔː. r ə m ˈ ɪ p. s ə m/ LOR-əm IP-səm) is a dummy or placeholder text commonly used in graphic design,
|
||||
publishing, and web development. It is typically a corrupted version of De finibus bonorum et malorum, a 1st-century BC text by
|
||||
the Roman statesman and philosopher Cicero, with words altered, added, and removed to make it nonsensical and improper Latin.
|
||||
The first two words are the truncation of dolorem ipsum ("pain itself"). Lorem ipsum's purpose is to permit a page layout to be
|
||||
designed, independently of the copy that will subsequently populate it, or to demonstrate various fonts of a typeface without
|
||||
meaningful text that could be distracting. Versions of the Lorem ipsum text have been used in typesetting since the 1960s, when
|
||||
advertisements for Letraset transfer sheets popularized it. Lorem ipsum was introduced to the digital world in the mid-1980s,
|
||||
when Aldus employed it in graphic and word-processing templates for its desktop publishing program PageMaker. Other popular word
|
||||
processors, including Pages and Microsoft Word, have since adopted Lorem ipsum, as have many LaTeX packages, web content
|
||||
</p>
|
||||
69
zenno/src/routes/convo/+page.svelte
Normal file
69
zenno/src/routes/convo/+page.svelte
Normal file
@@ -0,0 +1,69 @@
|
||||
<script lang="ts">
|
||||
import { charaNames } from '$lib/data/character';
|
||||
import { byChara, locations, groupPopulars } from '$lib/data/convo';
|
||||
import CharaPick from '$lib/CharaPick.svelte';
|
||||
|
||||
const minSuggest = 8;
|
||||
|
||||
let charaID = $state(1001);
|
||||
let convo = $state(1);
|
||||
|
||||
let options = $derived(byChara.global.get(charaID) ?? []);
|
||||
let cur = $derived(options.find((c) => c.number === convo));
|
||||
let suggested = $derived.by(() => {
|
||||
if (cur == null) {
|
||||
return [];
|
||||
}
|
||||
const u = groupPopulars.global[locations[cur.location].group].filter(
|
||||
(s) => charaNames.get(s[0]) != null && s[0] !== cur.chara_1 && s[0] !== cur.chara_2 && s[0] !== cur.chara_3,
|
||||
);
|
||||
const r = u.length <= minSuggest ? u : u.filter((s) => s[1] >= u[minSuggest][1]);
|
||||
return r.map(([chara_id, count]) => ({ chara_id, count }));
|
||||
});
|
||||
</script>
|
||||
|
||||
<h1 class="text-4xl">Lobby Conversations</h1>
|
||||
<div class="mx-auto mt-8 flex flex-col rounded-md text-center shadow-md ring md:max-w-xl md:flex-row">
|
||||
<div class="m-4 flex-1 md:mt-3">
|
||||
<CharaPick id="chara" class="w-full" label="Character" labelClass="hidden md:inline" bind:value={charaID} required />
|
||||
</div>
|
||||
<div class="m-4 flex-1 md:mt-3">
|
||||
<label for="convo" class="hidden md:inline">Conversation</label>
|
||||
<select id="convo" bind:value={convo} class="w-full">
|
||||
{#each options as opt}
|
||||
<option value={opt.number}>Slice of Life {opt.number}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{#if cur}
|
||||
<div class="shadow-sm transition-shadow hover:shadow-md">
|
||||
<div class="mt-8 flex text-center text-lg">
|
||||
<span class="flex-1"
|
||||
>{charaNames.get(cur.chara_1)?.en ?? 'someone not a trainee'}{(cur.chara_2 ?? cur.chara_3) == null ? ' alone' : ''}</span
|
||||
>
|
||||
{#if cur.chara_2}
|
||||
<span class="flex-1">{charaNames.get(cur.chara_2)?.en ?? 'someone not a trainee'}</span>
|
||||
{/if}
|
||||
{#if cur.chara_3}
|
||||
<span class="flex-1">{charaNames.get(cur.chara_3)?.en ?? 'someone not a trainee'}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex w-full text-center text-lg">
|
||||
<span class="flex-1">at {locations[cur.location].name.en}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 block text-center">
|
||||
<span>Other characters who appear here most often:</span>
|
||||
</div>
|
||||
<div class="mt-4 grid text-center shadow-sm transition-shadow ease-in hover:shadow-md hover:ease-out md:grid-cols-4">
|
||||
{#each suggested as s}
|
||||
<span>{charaNames.get(s.chara_id)?.en}: {s.count}×</span>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="mt-4 block text-center">
|
||||
<span>
|
||||
Set these characters to fixed positions (main, upgrades, story, races) to maximize the chance of getting this conversation.
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
3
zenno/src/routes/inherit/+page.svelte
Normal file
3
zenno/src/routes/inherit/+page.svelte
Normal file
@@ -0,0 +1,3 @@
|
||||
<h1>Inheritance Chance</h1>
|
||||
<p>Given a legacy, calculate the probability distribution of activation counts for each spark.</p>
|
||||
<p>TODO</p>
|
||||
42
zenno/src/routes/layout.css
Normal file
42
zenno/src/routes/layout.css
Normal file
@@ -0,0 +1,42 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: light-dark(var(--color-mist-200), var(--color-mist-800));
|
||||
color: light-dark(var(--color-amber-950), var(--color-amber-50));
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: calc(var(--spacing) * 4);
|
||||
}
|
||||
|
||||
a {
|
||||
color: light-dark(var(--color-sky-900), var(--color-sky-100));
|
||||
}
|
||||
|
||||
nav > span > a {
|
||||
color: light-dark(var(--color-amber-950), var(--color-amber-50));
|
||||
}
|
||||
|
||||
a:hover {
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
select {
|
||||
background-color: light-dark(var(--color-mist-300), var(--color-mist-900));
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
6
zenno/src/routes/spark/+page.svelte
Normal file
6
zenno/src/routes/spark/+page.svelte
Normal file
@@ -0,0 +1,6 @@
|
||||
<h1>Spark Generation Chance</h1>
|
||||
<p>
|
||||
Given a legacy, calculate the chance of generating each spark if you fulfill the conditions to do so, and the distribution of
|
||||
total spark counts.
|
||||
</p>
|
||||
<p>TODO</p>
|
||||
4
zenno/src/routes/vet/+page.svelte
Normal file
4
zenno/src/routes/vet/+page.svelte
Normal file
@@ -0,0 +1,4 @@
|
||||
<h1>My Veterans</h1>
|
||||
<p>Set up and track your veterans for Zenno Rob Roy's inspiration and spark calculators.</p>
|
||||
<p>All data is saved on your own machine, not transmitted or shared unless you explicitly choose to do so.</p>
|
||||
<p>TODO</p>
|
||||
3
zenno/static/robots.txt
Normal file
3
zenno/static/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# allow crawling everything by default
|
||||
User-agent: *
|
||||
Disallow:
|
||||
21
zenno/svelte.config.js
Normal file
21
zenno/svelte.config.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import adapter from '@sveltejs/adapter-static';
|
||||
import { relative, sep } from 'node:path';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
compilerOptions: {
|
||||
// defaults to rune mode for the project, execept for `node_modules`. Can be removed in svelte 6.
|
||||
runes: ({ filename }) => {
|
||||
const relativePath = relative(import.meta.dirname, filename);
|
||||
const pathSegments = relativePath.toLowerCase().split(sep);
|
||||
const isExternalLibrary = pathSegments.includes('node_modules');
|
||||
|
||||
return isExternalLibrary ? undefined : true;
|
||||
},
|
||||
},
|
||||
kit: {
|
||||
adapter: adapter(),
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
20
zenno/tsconfig.json
Normal file
20
zenno/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rewriteRelativeImportExtensions": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
||||
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
||||
//
|
||||
// To make changes to top-level options such as include and exclude, we recommend extending
|
||||
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
|
||||
}
|
||||
36
zenno/vite.config.ts
Normal file
36
zenno/vite.config.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import { playwright } from '@vitest/browser-playwright';
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), sveltekit()],
|
||||
test: {
|
||||
expect: { requireAssertions: true },
|
||||
projects: [
|
||||
{
|
||||
extends: './vite.config.ts',
|
||||
test: {
|
||||
name: 'client',
|
||||
browser: {
|
||||
enabled: true,
|
||||
provider: playwright(),
|
||||
instances: [{ browser: 'chromium', headless: true }],
|
||||
},
|
||||
include: ['src/**/*.svelte.{test,spec}.{js,ts}'],
|
||||
exclude: ['src/lib/server/**'],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
extends: './vite.config.ts',
|
||||
test: {
|
||||
name: 'server',
|
||||
environment: 'node',
|
||||
include: ['src/**/*.{test,spec}.{js,ts}'],
|
||||
exclude: ['src/**/*.svelte.{test,spec}.{js,ts}'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user