Compare commits
33 Commits
5540bb2c4e
...
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 | |||
| 8632bb8c3c | |||
| 7ff271ff2d |
@@ -3,12 +3,17 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strconv"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/disgoorg/disgo"
|
"github.com/disgoorg/disgo"
|
||||||
@@ -20,23 +25,27 @@ import (
|
|||||||
"github.com/disgoorg/disgo/rest"
|
"github.com/disgoorg/disgo/rest"
|
||||||
|
|
||||||
"git.sunturtle.xyz/zephyr/horse/horse"
|
"git.sunturtle.xyz/zephyr/horse/horse"
|
||||||
"git.sunturtle.xyz/zephyr/horse/horse/global"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var (
|
var (
|
||||||
tokenFile string
|
// public site
|
||||||
// http api options
|
|
||||||
addr string
|
addr string
|
||||||
route string
|
dataDir string
|
||||||
|
public string
|
||||||
|
// discord
|
||||||
|
tokenFile string
|
||||||
|
apiRoute string
|
||||||
pubkey string
|
pubkey string
|
||||||
// logging options
|
// logging
|
||||||
level slog.Level
|
level slog.Level
|
||||||
textfmt string
|
textfmt string
|
||||||
)
|
)
|
||||||
|
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(&tokenFile, "token", "", "`file` containing the Discord bot token")
|
||||||
flag.StringVar(&addr, "http", "", "`address` to bind HTTP API server")
|
flag.StringVar(&apiRoute, "route", "/interactions/callback", "`path` to serve Discord HTTP API calls")
|
||||||
flag.StringVar(&route, "route", "/interactions/callback", "`path` to serve HTTP API calls")
|
|
||||||
flag.StringVar(&pubkey, "key", "", "Discord public key")
|
flag.StringVar(&pubkey, "key", "", "Discord public key")
|
||||||
flag.TextVar(&level, "log", slog.LevelInfo, "slog logging `level`")
|
flag.TextVar(&level, "log", slog.LevelInfo, "slog logging `level`")
|
||||||
flag.StringVar(&textfmt, "log-format", "text", "slog logging `format`, text or json")
|
flag.StringVar(&textfmt, "log-format", "text", "slog logging `format`, text or json")
|
||||||
@@ -54,6 +63,27 @@ func main() {
|
|||||||
}
|
}
|
||||||
slog.SetDefault(slog.New(lh))
|
slog.SetDefault(slog.New(lh))
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
skillSrv := newSkillServer(skills, groups)
|
||||||
|
slog.Info("skill server ready")
|
||||||
|
|
||||||
token, err := os.ReadFile(tokenFile)
|
token, err := os.ReadFile(tokenFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("reading token", slog.Any("err", err))
|
slog.Error("reading token", slog.Any("err", err))
|
||||||
@@ -68,42 +98,55 @@ func main() {
|
|||||||
r.Use(middleware.Go)
|
r.Use(middleware.Go)
|
||||||
r.Use(logMiddleware)
|
r.Use(logMiddleware)
|
||||||
r.Route("/skill", func(r handler.Router) {
|
r.Route("/skill", func(r handler.Router) {
|
||||||
r.SlashCommand("/", skillHandler)
|
r.SlashCommand("/", skillSrv.slash)
|
||||||
r.Autocomplete("/", skillAutocomplete)
|
r.Autocomplete("/", skillSrv.autocomplete)
|
||||||
r.ButtonComponent("/{id}", skillButton)
|
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))
|
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 {
|
if err != nil {
|
||||||
slog.Error("building bot", slog.Any("err", err))
|
slog.Error("building bot", slog.Any("err", err))
|
||||||
os.Exit(1)
|
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 {
|
if err := handler.SyncCommands(client, commands, nil, rest.WithCtx(ctx)); err != nil {
|
||||||
slog.Error("syncing commands", slog.Any("err", err))
|
slog.Error("syncing commands", slog.Any("err", err))
|
||||||
os.Exit(1)
|
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")
|
slog.Info("start gateway")
|
||||||
if err := client.OpenGateway(ctx); err != nil {
|
if err := client.OpenGateway(ctx); err != nil {
|
||||||
slog.Error("starting gateway", slog.Any("err", err))
|
slog.Error("starting gateway", slog.Any("err", err))
|
||||||
@@ -116,6 +159,10 @@ func main() {
|
|||||||
ctx, stop = context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, stop = context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer stop()
|
defer stop()
|
||||||
client.Close(ctx)
|
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{
|
var commands = []discord.ApplicationCommandCreate{
|
||||||
@@ -129,55 +176,34 @@ var commands = []discord.ApplicationCommandCreate{
|
|||||||
Required: true,
|
Required: true,
|
||||||
Autocomplete: true,
|
Autocomplete: true,
|
||||||
},
|
},
|
||||||
|
discord.ApplicationCommandOptionBool{
|
||||||
|
Name: "share",
|
||||||
|
Description: "Share the skill info",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func skillHandler(data discord.SlashCommandInteractionData, e *handler.CommandEvent) error {
|
func loadSkills(file string) ([]horse.Skill, error) {
|
||||||
q := data.String("query")
|
b, err := os.ReadFile(file)
|
||||||
id, err := strconv.ParseInt(q, 10, 32)
|
|
||||||
if err == nil {
|
|
||||||
// note inverted condition; this is when we have an id
|
|
||||||
id = int64(global.AllSkills[horse.SkillID(id)].ID)
|
|
||||||
}
|
|
||||||
if id == 0 {
|
|
||||||
// Either we weren't given a number or the number doesn't match any skill ID.
|
|
||||||
v := global.SkillNameToID[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), global.AllSkills, global.SkillGroups)},
|
|
||||||
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 {
|
if err != nil {
|
||||||
m := discord.MessageCreate{
|
return nil, err
|
||||||
Content: "That button produced an invalid skill ID. That's not supposed to happen.",
|
|
||||||
Flags: discord.MessageFlagEphemeral,
|
|
||||||
}
|
}
|
||||||
return e.CreateMessage(m)
|
var skills []horse.Skill
|
||||||
|
if err := json.Unmarshal(b, &skills); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
m := discord.MessageUpdate{
|
return skills, nil
|
||||||
Components: &[]discord.LayoutComponent{RenderSkill(horse.SkillID(id), global.AllSkills, global.SkillGroups)},
|
}
|
||||||
}
|
|
||||||
return e.UpdateMessage(m)
|
func loadSkillGroups(file string) ([]horse.SkillGroup, error) {
|
||||||
|
b, err := os.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var groups []horse.SkillGroup
|
||||||
|
if err := json.Unmarshal(b, &groups); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return groups, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,67 +2,167 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/disgoorg/disgo/discord"
|
"github.com/disgoorg/disgo/discord"
|
||||||
|
"github.com/disgoorg/disgo/handler"
|
||||||
|
|
||||||
"git.sunturtle.xyz/zephyr/horse/cmd/horsebot/autocomplete"
|
"git.sunturtle.xyz/zephyr/horse/cmd/horsebot/autocomplete"
|
||||||
"git.sunturtle.xyz/zephyr/horse/horse"
|
"git.sunturtle.xyz/zephyr/horse/horse"
|
||||||
"git.sunturtle.xyz/zephyr/horse/horse/global"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func RenderSkill(id horse.SkillID, all map[horse.SkillID]horse.Skill, groups map[int32][4]horse.SkillID) discord.ContainerComponent {
|
type skillServer struct {
|
||||||
s, ok := all[id]
|
skills map[horse.SkillID]horse.Skill
|
||||||
|
byName map[string]horse.SkillID
|
||||||
|
groups map[horse.SkillGroupID]horse.SkillGroup
|
||||||
|
autocom autocomplete.Set[discord.AutocompleteChoice]
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
if !ok {
|
||||||
return discord.NewContainer(discord.NewTextDisplayf("invalid skill ID %v made it to RenderSkill", id))
|
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))
|
||||||
}
|
}
|
||||||
|
|
||||||
thumburl := fmt.Sprintf("https://gametora.com/images/umamusume/skill_icons/utx_ico_skill_%d.png", s.IconID)
|
top := "### " + skill.Name
|
||||||
top := "## " + s.Name
|
if skill.UniqueOwner != "" {
|
||||||
if s.UniqueOwner != "" {
|
top += "\n-# " + skill.UniqueOwner
|
||||||
top += "\n-# " + s.UniqueOwner
|
|
||||||
}
|
}
|
||||||
r := discord.NewContainer(
|
r := discord.NewContainer(
|
||||||
discord.NewSection(
|
|
||||||
discord.NewTextDisplay(top),
|
discord.NewTextDisplay(top),
|
||||||
discord.NewTextDisplay(s.Description),
|
discord.NewSmallSeparator(),
|
||||||
).WithAccessory(discord.NewThumbnail(thumburl)),
|
|
||||||
)
|
)
|
||||||
var skilltype string
|
var skilltype string
|
||||||
switch {
|
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
|
// unique of various star levels
|
||||||
r.AccentColor = 0xaca4d4
|
r.AccentColor = 0xaca4d4
|
||||||
skilltype = "Unique Skill"
|
skilltype = "Unique Skill"
|
||||||
case s.UniqueOwner != "":
|
case skill.UniqueOwner != "":
|
||||||
r.AccentColor = 0xcccccc
|
r.AccentColor = 0xcccccc
|
||||||
skilltype = "Inherited Unique"
|
skilltype = "Inherited Unique"
|
||||||
case s.Rarity == 2:
|
case skill.Rarity == 2:
|
||||||
// rare (gold)
|
// rare (gold)
|
||||||
r.AccentColor = 0xd7c25b
|
r.AccentColor = 0xd7c25b
|
||||||
skilltype = "Rare Skill"
|
skilltype = "Rare Skill"
|
||||||
case s.GroupRate == -1:
|
case skill.GroupRate == -1:
|
||||||
// negative (purple) skill
|
// negative (purple) skill
|
||||||
r.AccentColor = 0x9151d4
|
r.AccentColor = 0x9151d4
|
||||||
skilltype = "Negative Skill"
|
skilltype = "Negative Skill"
|
||||||
case !s.WitCheck:
|
case !skill.WitCheck:
|
||||||
// should be passive (green)
|
// should be passive (green)
|
||||||
r.AccentColor = 0x66ae1c
|
r.AccentColor = 0x66ae1c
|
||||||
skilltype = "Passive Skill"
|
skilltype = "Passive Skill"
|
||||||
case isDebuff(s):
|
case isDebuff(skill):
|
||||||
// debuff (red)
|
// debuff (red)
|
||||||
r.AccentColor = 0xe34747
|
r.AccentColor = 0xe34747
|
||||||
skilltype = "Debuff Skill"
|
skilltype = "Debuff Skill"
|
||||||
case s.Rarity == 1:
|
case skill.Rarity == 1:
|
||||||
// common (white)
|
// common (white)
|
||||||
r.AccentColor = 0xcccccc
|
r.AccentColor = 0xcccccc
|
||||||
skilltype = "Common Skill"
|
skilltype = "Common Skill"
|
||||||
}
|
}
|
||||||
r.Components = append(r.Components, discord.NewSmallSeparator())
|
|
||||||
text := make([]string, 0, 3)
|
text := make([]string, 0, 3)
|
||||||
abils := 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]
|
text, abils = text[:0], abils[:0]
|
||||||
if act.Precondition != "" {
|
if act.Precondition != "" {
|
||||||
text = append(text, "Precondition: "+formatCondition(act.Precondition))
|
text = append(text, "Precondition: "+formatCondition(act.Precondition))
|
||||||
@@ -92,24 +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")))
|
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)
|
r.Components = append(r.Components, discord.NewSmallSeparator(), l)
|
||||||
rel := make([]horse.Skill, 0, 4)
|
rel := make([]horse.Skill, 0, 4)
|
||||||
for _, id := range groups[s.Group] {
|
group := s.groups[skill.Group]
|
||||||
|
for _, id := range [...]horse.SkillID{group.Skill1, group.Skill2, group.Skill3, group.SkillBad} {
|
||||||
if id != 0 {
|
if id != 0 {
|
||||||
rel = append(rel, all[id])
|
rel = append(rel, s.skills[id])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(rel) > 1 {
|
if len(rel) > 1 {
|
||||||
buttons := make([]discord.InteractiveComponent, 0, 4)
|
opts := make([]discord.StringSelectMenuOption, 0, 4)
|
||||||
for _, rs := range rel {
|
for _, rs := range rel {
|
||||||
b := discord.NewSecondaryButton(rs.Name, fmt.Sprintf("/skill/%d", rs.ID))
|
name := rs.Name
|
||||||
if rs.ID == id {
|
emoji := "⚪"
|
||||||
b = b.AsDisabled()
|
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
|
return r
|
||||||
}
|
}
|
||||||
@@ -132,19 +248,3 @@ func isDebuff(s horse.Skill) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var skillGlobalAuto = sync.OnceValue(func() *autocomplete.Set[discord.AutocompleteChoice] {
|
|
||||||
var set autocomplete.Set[discord.AutocompleteChoice]
|
|
||||||
for _, id := range global.OrderedSkills {
|
|
||||||
s := global.AllSkills[id]
|
|
||||||
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
|
|
||||||
})
|
|
||||||
|
|||||||
418
cmd/horsegen/generate.go
Normal file
418
cmd/horsegen/generate.go
Normal file
@@ -0,0 +1,418 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"cmp"
|
||||||
|
"context"
|
||||||
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"maps"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
"zombiezen.com/go/sqlite"
|
||||||
|
"zombiezen.com/go/sqlite/sqlitex"
|
||||||
|
|
||||||
|
"git.sunturtle.xyz/zephyr/horse/horse"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var (
|
||||||
|
mdb string
|
||||||
|
out string
|
||||||
|
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", `.`, "`dir`ectory for output files")
|
||||||
|
flag.StringVar(®ion, "region", "global", "region the database is for (global, jp)")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
slog.Info("open", slog.String("mdb", mdb))
|
||||||
|
db, err := sqlitex.NewPool(mdb, sqlitex.PoolOptions{Flags: sqlite.OpenReadOnly})
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("opening mdb", slog.String("mdb", mdb), slog.Any("err", err))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
stop()
|
||||||
|
}()
|
||||||
|
|
||||||
|
loadgroup, ctx1 := errgroup.WithContext(ctx)
|
||||||
|
charas := load(ctx1, loadgroup, db, "characters", characterSQL, func(s *sqlite.Stmt) horse.Character {
|
||||||
|
return horse.Character{
|
||||||
|
ID: horse.CharacterID(s.ColumnInt(0)),
|
||||||
|
Name: s.ColumnText(1),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
aff := load(ctx1, loadgroup, db, "pair affinity", affinitySQL, func(s *sqlite.Stmt) horse.AffinityRelation {
|
||||||
|
return horse.AffinityRelation{
|
||||||
|
IDA: s.ColumnInt(0),
|
||||||
|
IDB: s.ColumnInt(1),
|
||||||
|
IDC: s.ColumnInt(2),
|
||||||
|
Affinity: s.ColumnInt(3),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
umas := load(ctx1, loadgroup, db, "umas", umaSQL, func(s *sqlite.Stmt) horse.Uma {
|
||||||
|
return horse.Uma{
|
||||||
|
ID: horse.UmaID(s.ColumnInt(0)),
|
||||||
|
CharacterID: horse.CharacterID(s.ColumnInt(1)),
|
||||||
|
Name: s.ColumnText(2),
|
||||||
|
Variant: s.ColumnText(3),
|
||||||
|
Sprint: horse.AptitudeLevel(s.ColumnInt(4)),
|
||||||
|
Mile: horse.AptitudeLevel(s.ColumnInt(6)),
|
||||||
|
Medium: horse.AptitudeLevel(s.ColumnInt(7)),
|
||||||
|
Long: horse.AptitudeLevel(s.ColumnInt(8)),
|
||||||
|
Front: horse.AptitudeLevel(s.ColumnInt(9)),
|
||||||
|
Pace: horse.AptitudeLevel(s.ColumnInt(10)),
|
||||||
|
Late: horse.AptitudeLevel(s.ColumnInt(11)),
|
||||||
|
End: horse.AptitudeLevel(s.ColumnInt(12)),
|
||||||
|
Turf: horse.AptitudeLevel(s.ColumnInt(13)),
|
||||||
|
Dirt: horse.AptitudeLevel(s.ColumnInt(14)),
|
||||||
|
Unique: horse.SkillID(s.ColumnInt(15)),
|
||||||
|
Skill1: horse.SkillID(s.ColumnInt(16)),
|
||||||
|
Skill2: horse.SkillID(s.ColumnInt(17)),
|
||||||
|
Skill3: horse.SkillID(s.ColumnInt(18)),
|
||||||
|
SkillPL2: horse.SkillID(s.ColumnInt(19)),
|
||||||
|
SkillPL3: horse.SkillID(s.ColumnInt(20)),
|
||||||
|
SkillPL4: horse.SkillID(s.ColumnInt(21)),
|
||||||
|
SkillPL5: horse.SkillID(s.ColumnInt(22)),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
sg := load(ctx1, loadgroup, db, "skill groups", skillGroupSQL, func(s *sqlite.Stmt) horse.SkillGroup {
|
||||||
|
return horse.SkillGroup{
|
||||||
|
ID: horse.SkillGroupID(s.ColumnInt(0)),
|
||||||
|
Skill1: horse.SkillID(s.ColumnInt(1)),
|
||||||
|
Skill2: horse.SkillID(s.ColumnInt(2)),
|
||||||
|
Skill3: horse.SkillID(s.ColumnInt(3)),
|
||||||
|
SkillBad: horse.SkillID(s.ColumnInt(4)),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
skills := load(ctx1, loadgroup, db, "skills", skillSQL, func(s *sqlite.Stmt) horse.Skill {
|
||||||
|
return horse.Skill{
|
||||||
|
ID: horse.SkillID(s.ColumnInt(0)),
|
||||||
|
Name: s.ColumnText(1),
|
||||||
|
Description: s.ColumnText(2),
|
||||||
|
Group: horse.SkillGroupID(s.ColumnInt32(3)),
|
||||||
|
Rarity: int8(s.ColumnInt(5)),
|
||||||
|
GroupRate: int8(s.ColumnInt(6)),
|
||||||
|
GradeValue: s.ColumnInt32(7),
|
||||||
|
WitCheck: s.ColumnBool(8),
|
||||||
|
Activations: trimActivations([]horse.Activation{
|
||||||
|
{
|
||||||
|
Precondition: s.ColumnText(9),
|
||||||
|
Condition: s.ColumnText(10),
|
||||||
|
Duration: horse.TenThousandths(s.ColumnInt(11)),
|
||||||
|
DurScale: horse.DurScale(s.ColumnInt(12)),
|
||||||
|
Cooldown: horse.TenThousandths(s.ColumnInt(13)),
|
||||||
|
Abilities: trimAbilities([]horse.Ability{
|
||||||
|
{
|
||||||
|
Type: horse.AbilityType(s.ColumnInt(14)),
|
||||||
|
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(15)),
|
||||||
|
Value: horse.TenThousandths(s.ColumnInt(16)),
|
||||||
|
Target: horse.AbilityTarget(s.ColumnInt(17)),
|
||||||
|
TargetValue: s.ColumnInt32(18),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: horse.AbilityType(s.ColumnInt(19)),
|
||||||
|
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(20)),
|
||||||
|
Value: horse.TenThousandths(s.ColumnInt(21)),
|
||||||
|
Target: horse.AbilityTarget(s.ColumnInt(22)),
|
||||||
|
TargetValue: s.ColumnInt32(23),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: horse.AbilityType(s.ColumnInt(24)),
|
||||||
|
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(25)),
|
||||||
|
Value: horse.TenThousandths(s.ColumnInt(26)),
|
||||||
|
Target: horse.AbilityTarget(s.ColumnInt(27)),
|
||||||
|
TargetValue: s.ColumnInt32(28),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Precondition: s.ColumnText(29),
|
||||||
|
Condition: s.ColumnText(30),
|
||||||
|
Duration: horse.TenThousandths(s.ColumnInt(31)),
|
||||||
|
DurScale: horse.DurScale(s.ColumnInt(32)),
|
||||||
|
Cooldown: horse.TenThousandths(s.ColumnInt(33)),
|
||||||
|
Abilities: trimAbilities([]horse.Ability{
|
||||||
|
{
|
||||||
|
Type: horse.AbilityType(s.ColumnInt(34)),
|
||||||
|
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(35)),
|
||||||
|
Value: horse.TenThousandths(s.ColumnInt(36)),
|
||||||
|
Target: horse.AbilityTarget(s.ColumnInt(37)),
|
||||||
|
TargetValue: s.ColumnInt32(38),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: horse.AbilityType(s.ColumnInt(39)),
|
||||||
|
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(40)),
|
||||||
|
Value: horse.TenThousandths(s.ColumnInt(41)),
|
||||||
|
Target: horse.AbilityTarget(s.ColumnInt(42)),
|
||||||
|
TargetValue: s.ColumnInt32(43),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: horse.AbilityType(s.ColumnInt(44)),
|
||||||
|
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(45)),
|
||||||
|
Value: horse.TenThousandths(s.ColumnInt(46)),
|
||||||
|
Target: horse.AbilityTarget(s.ColumnInt(47)),
|
||||||
|
TargetValue: s.ColumnInt32(48),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
UniqueOwner: s.ColumnText(52), // TODO(zeph): should be id, not name
|
||||||
|
SPCost: s.ColumnInt(49),
|
||||||
|
IconID: s.ColumnInt(53),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
races := load(ctx1, loadgroup, db, "races", raceSQL, func(s *sqlite.Stmt) horse.Race {
|
||||||
|
return horse.Race{
|
||||||
|
ID: horse.RaceID(s.ColumnInt(0)),
|
||||||
|
Name: s.ColumnText(1),
|
||||||
|
// TODO(zeph): grade
|
||||||
|
Thumbnail: s.ColumnInt(3),
|
||||||
|
Primary: horse.RaceID(s.ColumnInt(4)),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
saddles := load(ctx1, loadgroup, db, "saddles", saddleSQL, func(s *sqlite.Stmt) horse.Saddle {
|
||||||
|
return horse.Saddle{
|
||||||
|
ID: horse.SaddleID(s.ColumnInt(0)),
|
||||||
|
Name: s.ColumnText(1),
|
||||||
|
Races: trimZeros(
|
||||||
|
horse.RaceID(s.ColumnInt(2)),
|
||||||
|
horse.RaceID(s.ColumnInt(3)),
|
||||||
|
horse.RaceID(s.ColumnInt(4)),
|
||||||
|
),
|
||||||
|
Type: horse.SaddleType(s.ColumnInt(5)),
|
||||||
|
Primary: horse.SaddleID(s.ColumnInt(6)),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
scenarios := load(ctx1, loadgroup, db, "scenarios", scenarioSQL, func(s *sqlite.Stmt) horse.Scenario {
|
||||||
|
return horse.Scenario{
|
||||||
|
ID: horse.ScenarioID(s.ColumnInt(0)),
|
||||||
|
Name: s.ColumnText(1),
|
||||||
|
Title: s.ColumnText(2),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
sparks := load(ctx1, loadgroup, db, "sparks", sparkSQL, func(s *sqlite.Stmt) horse.Spark {
|
||||||
|
return horse.Spark{
|
||||||
|
ID: horse.SparkID(s.ColumnInt(0)),
|
||||||
|
Name: s.ColumnText(1),
|
||||||
|
Description: s.ColumnText(2),
|
||||||
|
Group: horse.SparkGroupID(s.ColumnInt(3)),
|
||||||
|
Rarity: horse.SparkRarity(s.ColumnInt(4)),
|
||||||
|
Type: horse.SparkType(s.ColumnInt(5)),
|
||||||
|
// Effects filled in later.
|
||||||
|
}
|
||||||
|
})
|
||||||
|
sparkeffs := load(ctx1, loadgroup, db, "spark effects", sparkEffectSQL, func(s *sqlite.Stmt) SparkEffImm {
|
||||||
|
return SparkEffImm{
|
||||||
|
Group: horse.SparkGroupID(s.ColumnInt(0)),
|
||||||
|
Effect: s.ColumnInt(1),
|
||||||
|
Target: horse.SparkTarget(s.ColumnInt(2)),
|
||||||
|
Value1: s.ColumnInt32(3),
|
||||||
|
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))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
writegroup, ctx2 := errgroup.WithContext(ctx)
|
||||||
|
writegroup.Go(func() error { return write(ctx2, out, region, "character.json", charas) })
|
||||||
|
writegroup.Go(func() error { return write(ctx2, out, region, "affinity.json", aff) })
|
||||||
|
writegroup.Go(func() error { return write(ctx2, out, region, "uma.json", umas) })
|
||||||
|
writegroup.Go(func() error { return write(ctx2, out, region, "skill-group.json", sg) })
|
||||||
|
writegroup.Go(func() error { return write(ctx2, out, region, "skill.json", skills) })
|
||||||
|
writegroup.Go(func() error { return write(ctx2, out, region, "race.json", races) })
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.InfoContext(ctx, "done")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
//go:embed sql/character.sql
|
||||||
|
characterSQL string
|
||||||
|
//go:embed sql/affinity.sql
|
||||||
|
affinitySQL string
|
||||||
|
//go:embed sql/uma.sql
|
||||||
|
umaSQL string
|
||||||
|
//go:embed sql/skill-group.sql
|
||||||
|
skillGroupSQL string
|
||||||
|
//go:embed sql/skill.sql
|
||||||
|
skillSQL string
|
||||||
|
//go:embed sql/race.sql
|
||||||
|
raceSQL string
|
||||||
|
//go:embed sql/saddle.sql
|
||||||
|
saddleSQL string
|
||||||
|
//go:embed sql/scenario.sql
|
||||||
|
scenarioSQL string
|
||||||
|
//go:embed sql/spark.sql
|
||||||
|
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) {
|
||||||
|
slog.InfoContext(ctx, "load", slog.String("kind", kind))
|
||||||
|
var r []T
|
||||||
|
group.Go(func() error {
|
||||||
|
conn, err := db.Take(ctx)
|
||||||
|
defer db.Put(conn)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't get connection for %s: %w", kind, err)
|
||||||
|
}
|
||||||
|
stmt, _, err := conn.PrepareTransient(sql)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't prepare statement for %s: %w", kind, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
ok, err := stmt.Step()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error stepping %s: %w", kind, err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
r = append(r, row(stmt))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return func() ([]T, error) {
|
||||||
|
err := group.Wait()
|
||||||
|
if err == context.Canceled {
|
||||||
|
// After the first wait, all future ones return context.Canceled.
|
||||||
|
// We want to be able to wait any number of times, so hide it.
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func write[T any](ctx context.Context, out, region, name string, v func() (T, error)) error {
|
||||||
|
p := filepath.Join(out, region, name)
|
||||||
|
r, err := v()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slog.InfoContext(ctx, "write", slog.String("path", p))
|
||||||
|
f, err := os.Create(p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
w := bufio.NewWriter(f)
|
||||||
|
enc := json.NewEncoder(w)
|
||||||
|
enc.SetEscapeHTML(false)
|
||||||
|
enc.SetIndent("", "\t")
|
||||||
|
err = enc.Encode(r)
|
||||||
|
err = errors.Join(err, w.Flush())
|
||||||
|
slog.InfoContext(ctx, "marshaled", slog.String("path", p))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergesparks(sparks func() ([]horse.Spark, error), effs func() ([]SparkEffImm, error)) func() ([]horse.Spark, error) {
|
||||||
|
return func() ([]horse.Spark, error) {
|
||||||
|
sp, err := sparks()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ef, err := effs()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Spark effects are sorted by group ID, but groups apply to multiple
|
||||||
|
// sparks, and we don't rely on sparks and groups being in the same order.
|
||||||
|
// It is possible to merge in linear time, but not worth the effort:
|
||||||
|
// n log n is fine since this is an AOT step.
|
||||||
|
for i := range sp {
|
||||||
|
k, ok := slices.BinarySearchFunc(ef, sp[i].Group, func(e SparkEffImm, v horse.SparkGroupID) int { return cmp.Compare(e.Group, v) })
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Errorf("mergesparks: no spark group for %+v", &sp[i]))
|
||||||
|
}
|
||||||
|
// Back up to the first effect in the group.
|
||||||
|
for k > 0 && ef[k-1].Group == sp[i].Group {
|
||||||
|
k--
|
||||||
|
}
|
||||||
|
// Map effect IDs to the lists of their effects.
|
||||||
|
m := make(map[int][]horse.SparkEffect)
|
||||||
|
for _, e := range ef[k:] {
|
||||||
|
if e.Group != sp[i].Group {
|
||||||
|
// Done with this group.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
m[e.Effect] = append(m[e.Effect], horse.SparkEffect{Target: e.Target, Value1: e.Value1, Value2: e.Value2})
|
||||||
|
}
|
||||||
|
// Now get effects in order.
|
||||||
|
keys := slices.Sorted(maps.Keys(m))
|
||||||
|
sp[i].Effects = make([][]horse.SparkEffect, 0, len(keys))
|
||||||
|
for _, key := range keys {
|
||||||
|
sp[i].Effects = append(sp[i].Effects, m[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sp, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SparkEffImm struct {
|
||||||
|
Group horse.SparkGroupID
|
||||||
|
Effect int
|
||||||
|
Target horse.SparkTarget
|
||||||
|
Value1 int32
|
||||||
|
Value2 int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimAbilities(s []horse.Ability) []horse.Ability {
|
||||||
|
for len(s) > 0 && s[len(s)-1].Type == 0 {
|
||||||
|
s = s[:len(s)-1]
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimActivations(s []horse.Activation) []horse.Activation {
|
||||||
|
for len(s) > 0 && s[len(s)-1].Condition == "" {
|
||||||
|
s = s[:len(s)-1]
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimZeros[T comparable](s ...T) []T {
|
||||||
|
var zero T
|
||||||
|
for len(s) > 0 && s[len(s)-1] == zero {
|
||||||
|
s = s[:len(s)-1]
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
60
cmd/horsegen/sql/affinity.sql
Normal file
60
cmd/horsegen/sql/affinity.sql
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
WITH pairs AS (
|
||||||
|
SELECT
|
||||||
|
a.id AS id_a,
|
||||||
|
b.id AS id_b
|
||||||
|
FROM chara_data a
|
||||||
|
JOIN chara_data b ON a.id < b.id
|
||||||
|
-- Exclude characters who have no succession relations defined.
|
||||||
|
WHERE a.id IN (SELECT chara_id FROM succession_relation_member)
|
||||||
|
AND b.id IN (SELECT chara_id FROM succession_relation_member)
|
||||||
|
), trios AS (
|
||||||
|
SELECT
|
||||||
|
a.id AS id_a,
|
||||||
|
b.id AS id_b,
|
||||||
|
c.id AS id_c
|
||||||
|
FROM chara_data a
|
||||||
|
JOIN chara_data b ON a.id < b.id
|
||||||
|
JOIN chara_data c ON a.id < c.id AND b.id < c.id
|
||||||
|
-- Exclude characters who have no succession relations defined.
|
||||||
|
WHERE a.id IN (SELECT chara_id FROM succession_relation_member)
|
||||||
|
AND b.id IN (SELECT chara_id FROM succession_relation_member)
|
||||||
|
AND c.id IN (SELECT chara_id FROM succession_relation_member)
|
||||||
|
), pair_relations AS (
|
||||||
|
SELECT
|
||||||
|
ra.relation_type,
|
||||||
|
ra.chara_id AS id_a,
|
||||||
|
rb.chara_id AS id_b
|
||||||
|
FROM succession_relation_member ra
|
||||||
|
JOIN succession_relation_member rb ON ra.relation_type = rb.relation_type
|
||||||
|
), trio_relations AS (
|
||||||
|
SELECT
|
||||||
|
ra.relation_type,
|
||||||
|
ra.chara_id AS id_a,
|
||||||
|
rb.chara_id AS id_b,
|
||||||
|
rc.chara_id AS id_c
|
||||||
|
FROM succession_relation_member ra
|
||||||
|
JOIN succession_relation_member rb ON ra.relation_type = rb.relation_type
|
||||||
|
JOIN succession_relation_member rc ON ra.relation_type = rc.relation_type
|
||||||
|
), affinity AS (
|
||||||
|
SELECT
|
||||||
|
pairs.*,
|
||||||
|
0 AS id_c,
|
||||||
|
SUM(IFNULL(relation_point, 0)) AS base_affinity
|
||||||
|
FROM pairs
|
||||||
|
LEFT JOIN pair_relations rp ON pairs.id_a = rp.id_a AND pairs.id_b = rp.id_b
|
||||||
|
LEFT JOIN succession_relation sr ON rp.relation_type = sr.relation_type
|
||||||
|
GROUP BY pairs.id_a, pairs.id_b
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
trios.*,
|
||||||
|
SUM(IFNULL(relation_point, 0)) AS base_affinity
|
||||||
|
FROM trios
|
||||||
|
LEFT JOIN trio_relations rt ON trios.id_a = rt.id_a AND trios.id_b = rt.id_b AND trios.id_c = rt.id_c
|
||||||
|
LEFT JOIN succession_relation sr ON rt.relation_type = sr.relation_type
|
||||||
|
GROUP BY trios.id_a, trios.id_b, trios.id_c
|
||||||
|
)
|
||||||
|
SELECT * FROM affinity
|
||||||
|
WHERE base_affinity != 0
|
||||||
|
ORDER BY id_a, id_b, id_c
|
||||||
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
|
||||||
17
cmd/horsegen/sql/skill-group.sql
Normal file
17
cmd/horsegen/sql/skill-group.sql
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
WITH skill_groups AS (
|
||||||
|
SELECT DISTINCT group_id FROM skill_data
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
g.group_id,
|
||||||
|
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
|
||||||
|
LEFT JOIN skill_data s1 ON g.group_id = s1.group_id AND s1.group_rate = 1
|
||||||
|
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
|
||||||
@@ -6,4 +6,4 @@ SELECT
|
|||||||
value_2
|
value_2
|
||||||
FROM succession_factor_effect
|
FROM succession_factor_effect
|
||||||
WHERE factor_group_id NOT IN (40001) -- exclude Carnival Bonus
|
WHERE factor_group_id NOT IN (40001) -- exclude Carnival Bonus
|
||||||
ORDER BY factor_group_id, effect_id, target_type
|
ORDER BY factor_group_id, effect_id, 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
|
- 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?
|
- 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)
|
- 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)
|
# succession factor (sparks)
|
||||||
|
|
||||||
@@ -192,12 +193,41 @@ target types:
|
|||||||
- 22 specific character, target value is character id
|
- 22 specific character, target value is character id
|
||||||
- 23 other who triggered the skill
|
- 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
|
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
|
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
|
# races
|
||||||
|
|
||||||
- group 1, grade: g1 100, g2 200, g3 300, op 400, pre-op 700
|
- group 1, grade: g1 100, g2 200, g3 300, op 400, pre-op 700
|
||||||
|
|||||||
145007
global/affinity.json
Normal file
145007
global/affinity.json
Normal file
File diff suppressed because it is too large
Load Diff
226
global/character.json
Normal file
226
global/character.json
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"chara_id": 1001,
|
||||||
|
"name": "Special Week"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1002,
|
||||||
|
"name": "Silence Suzuka"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1003,
|
||||||
|
"name": "Tokai Teio"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1004,
|
||||||
|
"name": "Maruzensky"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1005,
|
||||||
|
"name": "Fuji Kiseki"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1006,
|
||||||
|
"name": "Oguri Cap"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1007,
|
||||||
|
"name": "Gold Ship"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1008,
|
||||||
|
"name": "Vodka"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1009,
|
||||||
|
"name": "Daiwa Scarlet"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1010,
|
||||||
|
"name": "Taiki Shuttle"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1011,
|
||||||
|
"name": "Grass Wonder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1012,
|
||||||
|
"name": "Hishi Amazon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1013,
|
||||||
|
"name": "Mejiro McQueen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1014,
|
||||||
|
"name": "El Condor Pasa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1015,
|
||||||
|
"name": "T.M. Opera O"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1016,
|
||||||
|
"name": "Narita Brian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1017,
|
||||||
|
"name": "Symboli Rudolf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1018,
|
||||||
|
"name": "Air Groove"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1019,
|
||||||
|
"name": "Agnes Digital"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1020,
|
||||||
|
"name": "Seiun Sky"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1021,
|
||||||
|
"name": "Tamamo Cross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1022,
|
||||||
|
"name": "Fine Motion"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1023,
|
||||||
|
"name": "Biwa Hayahide"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1024,
|
||||||
|
"name": "Mayano Top Gun"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1025,
|
||||||
|
"name": "Manhattan Cafe"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1026,
|
||||||
|
"name": "Mihono Bourbon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1027,
|
||||||
|
"name": "Mejiro Ryan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1028,
|
||||||
|
"name": "Hishi Akebono"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1030,
|
||||||
|
"name": "Rice Shower"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1032,
|
||||||
|
"name": "Agnes Tachyon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1033,
|
||||||
|
"name": "Admire Vega"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1034,
|
||||||
|
"name": "Inari One"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1035,
|
||||||
|
"name": "Winning Ticket"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1037,
|
||||||
|
"name": "Eishin Flash"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1038,
|
||||||
|
"name": "Curren Chan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1039,
|
||||||
|
"name": "Kawakami Princess"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1040,
|
||||||
|
"name": "Gold City"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1041,
|
||||||
|
"name": "Sakura Bakushin O"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1044,
|
||||||
|
"name": "Sweep Tosho"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1045,
|
||||||
|
"name": "Super Creek"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1046,
|
||||||
|
"name": "Smart Falcon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1048,
|
||||||
|
"name": "Tosen Jordan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1050,
|
||||||
|
"name": "Narita Taishin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1051,
|
||||||
|
"name": "Nishino Flower"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1052,
|
||||||
|
"name": "Haru Urara"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1056,
|
||||||
|
"name": "Matikanefukukitaru"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1058,
|
||||||
|
"name": "Meisho Doto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1059,
|
||||||
|
"name": "Mejiro Dober"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1060,
|
||||||
|
"name": "Nice Nature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1061,
|
||||||
|
"name": "King Halo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1062,
|
||||||
|
"name": "Matikanetannhauser"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1067,
|
||||||
|
"name": "Satono Diamond"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1068,
|
||||||
|
"name": "Kitasan Black"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chara_id": 1069,
|
||||||
|
"name": "Sakura Chiyono O"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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
1712
global/race.json
Normal file
1712
global/race.json
Normal file
File diff suppressed because it is too large
Load Diff
1420
global/saddle.json
Normal file
1420
global/saddle.json
Normal file
File diff suppressed because it is too large
Load Diff
17
global/scenario.json
Normal file
17
global/scenario.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"scenario_id": 1,
|
||||||
|
"name": "URA Finale",
|
||||||
|
"title": "The Beginning: URA Finale"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
1768
global/skill-group.json
Normal file
1768
global/skill-group.json
Normal file
File diff suppressed because it is too large
Load Diff
15853
global/skill.json
Normal file
15853
global/skill.json
Normal file
File diff suppressed because it is too large
Load Diff
36326
global/spark.json
Normal file
36326
global/spark.json
Normal file
File diff suppressed because it is too large
Load Diff
1730
global/uma.json
Normal file
1730
global/uma.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,13 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "os"
|
|
||||||
|
|
||||||
//go:generate go run ./horsegen
|
|
||||||
//go:generate go generate ./horse/...
|
|
||||||
//go:generate go fmt ./...
|
|
||||||
//go:generate go test ./...
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
os.Stderr.WriteString("go generate, not go run\n")
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
2
go.mod
2
go.mod
@@ -5,7 +5,7 @@ go 1.25.5
|
|||||||
require (
|
require (
|
||||||
github.com/disgoorg/disgo v0.19.0-rc.15
|
github.com/disgoorg/disgo v0.19.0-rc.15
|
||||||
github.com/junegunn/fzf v0.67.0
|
github.com/junegunn/fzf v0.67.0
|
||||||
golang.org/x/sync v0.14.0
|
golang.org/x/sync v0.20.0
|
||||||
zombiezen.com/go/sqlite v1.4.2
|
zombiezen.com/go/sqlite v1.4.2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -40,8 +40,8 @@ golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5Z
|
|||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
|||||||
@@ -3,10 +3,57 @@ package horse
|
|||||||
type CharacterID int16
|
type CharacterID int16
|
||||||
|
|
||||||
type Character struct {
|
type Character struct {
|
||||||
ID CharacterID
|
ID CharacterID `json:"chara_id"`
|
||||||
Name string
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Character) String() string {
|
func (c Character) String() string {
|
||||||
return c.Name
|
return c.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AffinityRelation struct {
|
||||||
|
IDA int `json:"chara_a"`
|
||||||
|
IDB int `json:"chara_b"`
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2014
horse/global/race.go
2014
horse/global/race.go
File diff suppressed because it is too large
Load Diff
@@ -1,913 +0,0 @@
|
|||||||
module horse/global/race
|
|
||||||
|
|
||||||
// Automatically generated with horsegen; DO NOT EDIT
|
|
||||||
|
|
||||||
import std/core/delayed
|
|
||||||
import std/core/vector
|
|
||||||
import std/core-extras
|
|
||||||
import std/data/rb-map
|
|
||||||
import horse/game-id
|
|
||||||
pub import horse/race
|
|
||||||
|
|
||||||
extern create-id-table(): vector<int>
|
|
||||||
c inline "int32_t arr[] = {1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,1014,1015,1016,1017,1018,1019,1020,1021,1022,1023,1024,1025,1026,1027,1028,1101,1102,1103,1104,1105,1106,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024,2025,2026,2027,2028,2029,2030,2031,2032,2033,2034,2035,3001,3002,3003,3004,3005,3006,3007,3008,3009,3010,3011,3012,3013,3014,3015,3016,3017,3018,3019,3020,3021,3022,3023,3024,3025,3026,3027,3028,3029,3030,3031,3032,3033,3034,3035,3036,3037,3038,3039,3040,3041,3042,3043,3044,3045,3046,3047,3048,3049,3050,3051,3052,3053,3054,3055,3056,3057,3058,3059,3060,3061,3062,3063,3064,3065,3066,3067,3068,3069,3070,4001,4002,4003,4004,4005,4006,4007,4008,4009,4010,4011,4012,4013,4014,4015,4016,4017,4018,4019,4020,4021,4022,4023,4024,4025,4026,4027,4028,4030,4031,4032,4033,4035,4036,4037,4038,4039,4040,4041,4042,4043,4044,4045,4046,4047,4048,4049,4050,4051,4052,4053,4054,4055,4056,4057,4058,4059,4060,4061,4062,4063,4064,4065,4066,4068,4069,4070,4071,4072,4073,4074,4075,4076,4077,4078,4079,4080,4081,4082,4083,4084,4085,4086,4087,4088,4089,4090,4091,4092,4093,4094,4095,4096,4097,4098,4099,4100,4101,4102,4103,4104,4105,4106,4107,4108,4109,4110,4111,4112,4113,4114,4115,4116,4118,4119,4120,4121,4122,4123,4124,4501,4502,4503,4504,4505,4506,4507,4508,4509,4510,4511,4512,4513,4514,4515,4516,4517,4518,4519,4520,4521,4522,4523,4524,4525,4526,};\nkk_vector_from_cint32array(arr, (kk_ssize_t)285, kk_context())"
|
|
||||||
js inline "[1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,1014,1015,1016,1017,1018,1019,1020,1021,1022,1023,1024,1025,1026,1027,1028,1101,1102,1103,1104,1105,1106,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024,2025,2026,2027,2028,2029,2030,2031,2032,2033,2034,2035,3001,3002,3003,3004,3005,3006,3007,3008,3009,3010,3011,3012,3013,3014,3015,3016,3017,3018,3019,3020,3021,3022,3023,3024,3025,3026,3027,3028,3029,3030,3031,3032,3033,3034,3035,3036,3037,3038,3039,3040,3041,3042,3043,3044,3045,3046,3047,3048,3049,3050,3051,3052,3053,3054,3055,3056,3057,3058,3059,3060,3061,3062,3063,3064,3065,3066,3067,3068,3069,3070,4001,4002,4003,4004,4005,4006,4007,4008,4009,4010,4011,4012,4013,4014,4015,4016,4017,4018,4019,4020,4021,4022,4023,4024,4025,4026,4027,4028,4030,4031,4032,4033,4035,4036,4037,4038,4039,4040,4041,4042,4043,4044,4045,4046,4047,4048,4049,4050,4051,4052,4053,4054,4055,4056,4057,4058,4059,4060,4061,4062,4063,4064,4065,4066,4068,4069,4070,4071,4072,4073,4074,4075,4076,4077,4078,4079,4080,4081,4082,4083,4084,4085,4086,4087,4088,4089,4090,4091,4092,4093,4094,4095,4096,4097,4098,4099,4100,4101,4102,4103,4104,4105,4106,4107,4108,4109,4110,4111,4112,4113,4114,4115,4116,4118,4119,4120,4121,4122,4123,4124,4501,4502,4503,4504,4505,4506,4507,4508,4509,4510,4511,4512,4513,4514,4515,4516,4517,4518,4519,4520,4521,4522,4523,4524,4525,4526,]"
|
|
||||||
// Vector of all race IDs in order for easy iterating.
|
|
||||||
pub val all = once(create-id-table)
|
|
||||||
|
|
||||||
val name2id = once()
|
|
||||||
var m: rbmap<string, int> := empty()
|
|
||||||
all().foreach() fn(id) m := m.set(Race-id(id).show, id)
|
|
||||||
m
|
|
||||||
|
|
||||||
// Get the race ID that has the given exact name.
|
|
||||||
// Alternate versions of races have an indication of their ID in their names.
|
|
||||||
// If no race matches the name, the result is an invalid ID.
|
|
||||||
pub fun from-name(name: string): race-id
|
|
||||||
Race-id(name2id().rb-map/lookup(name).default(0))
|
|
||||||
|
|
||||||
// Get the name for a race.
|
|
||||||
// Alternate versions of races have an indication of their ID in their names.
|
|
||||||
// If no race matches the ID, the result is the numeric ID.
|
|
||||||
pub fun show(r: race-id): string
|
|
||||||
match r.game-id
|
|
||||||
1001 -> "February Stakes"
|
|
||||||
1002 -> "Takamatsunomiya Kinen"
|
|
||||||
1003 -> "Osaka Hai"
|
|
||||||
1004 -> "Oka Sho"
|
|
||||||
1005 -> "Satsuki Sho"
|
|
||||||
1006 -> "Tenno Sho (Spring)"
|
|
||||||
1007 -> "NHK Mile Cup"
|
|
||||||
1008 -> "Victoria Mile"
|
|
||||||
1009 -> "Japanese Oaks"
|
|
||||||
1010 -> "Tokyo Yushun (Japanese Derby)"
|
|
||||||
1011 -> "Yasuda Kinen"
|
|
||||||
1012 -> "Takarazuka Kinen"
|
|
||||||
1013 -> "Sprinters Stakes"
|
|
||||||
1014 -> "Shuka Sho"
|
|
||||||
1015 -> "Kikuka Sho"
|
|
||||||
1016 -> "Tenno Sho (Autumn)"
|
|
||||||
1017 -> "Queen Elizabeth II Cup"
|
|
||||||
1018 -> "Mile Championship"
|
|
||||||
1019 -> "Japan Cup"
|
|
||||||
1020 -> "Champions Cup"
|
|
||||||
1021 -> "Hanshin Juvenile Fillies"
|
|
||||||
1022 -> "Asahi Hai Futurity Stakes"
|
|
||||||
1023 -> "Arima Kinen"
|
|
||||||
1024 -> "Hopeful Stakes"
|
|
||||||
1025 -> "Takarazuka Kinen" ++ " (Alternate 1025)"
|
|
||||||
1026 -> "Kikuka Sho" ++ " (Alternate 1026)"
|
|
||||||
1027 -> "Tenno Sho (Spring)" ++ " (Alternate 1027)"
|
|
||||||
1028 -> "Satsuki Sho" ++ " (Alternate 1028)"
|
|
||||||
1101 -> "Teio Sho"
|
|
||||||
1102 -> "Japan Dirt Derby"
|
|
||||||
1103 -> "JBC Ladies’ Classic"
|
|
||||||
1104 -> "JBC Sprint"
|
|
||||||
1105 -> "JBC Classic"
|
|
||||||
1106 -> "Tokyo Daishoten"
|
|
||||||
2001 -> "Nikkei Shinshun Hai"
|
|
||||||
2002 -> "Tokai Stakes"
|
|
||||||
2003 -> "American JCC"
|
|
||||||
2004 -> "Kyoto Kinen"
|
|
||||||
2005 -> "Nakayama Kinen"
|
|
||||||
2006 -> "Yayoi Sho"
|
|
||||||
2007 -> "Kinko Sho"
|
|
||||||
2008 -> "Fillies' Revue"
|
|
||||||
2009 -> "Hanshin Daishoten"
|
|
||||||
2010 -> "Spring Stakes"
|
|
||||||
2011 -> "Nikkei Sho"
|
|
||||||
2012 -> "Hanshin Umamusume Stakes"
|
|
||||||
2013 -> "New Zealand Trophy"
|
|
||||||
2014 -> "Milers Cup"
|
|
||||||
2015 -> "Flora Stakes"
|
|
||||||
2016 -> "Aoba Sho"
|
|
||||||
2017 -> "Kyoto Shimbun Hai"
|
|
||||||
2018 -> "Keio Hai Spring Cup"
|
|
||||||
2019 -> "Meguro Kinen"
|
|
||||||
2020 -> "Sapporo Kinen"
|
|
||||||
2021 -> "Centaur Stakes"
|
|
||||||
2022 -> "Rose Stakes"
|
|
||||||
2023 -> "St. Lite Kinen"
|
|
||||||
2024 -> "Kobe Shimbun Hai"
|
|
||||||
2025 -> "All Comers"
|
|
||||||
2026 -> "Mainichi Okan"
|
|
||||||
2027 -> "Kyoto Daishoten"
|
|
||||||
2028 -> "Fuchu Umamusume Stakes"
|
|
||||||
2029 -> "Swan Stakes"
|
|
||||||
2030 -> "Keio Hai Junior Stakes"
|
|
||||||
2031 -> "Copa Republica Argentina"
|
|
||||||
2032 -> "Daily Hai Junior Stakes"
|
|
||||||
2033 -> "Stayers Stakes"
|
|
||||||
2034 -> "Hanshin Cup"
|
|
||||||
2035 -> "Spring Stakes" ++ " (Alternate 2035)"
|
|
||||||
3001 -> "Kyoto Kimpai"
|
|
||||||
3002 -> "Nakayama Kimpai"
|
|
||||||
3003 -> "Shinzan Kinen"
|
|
||||||
3004 -> "Fairy Stakes"
|
|
||||||
3005 -> "Aichi Hai"
|
|
||||||
3006 -> "Keisei Hai"
|
|
||||||
3007 -> "Silk Road Stakes"
|
|
||||||
3008 -> "Negishi Stakes"
|
|
||||||
3009 -> "Kisaragi Sho"
|
|
||||||
3010 -> "Tokyo Shimbun Hai"
|
|
||||||
3011 -> "Queen Cup"
|
|
||||||
3012 -> "Kyodo News Hai"
|
|
||||||
3013 -> "Kyoto Umamusume Stakes"
|
|
||||||
3014 -> "Diamond Stakes"
|
|
||||||
3015 -> "Kokura Daishoten"
|
|
||||||
3016 -> "Arlington Cup"
|
|
||||||
3017 -> "Hankyu Hai"
|
|
||||||
3018 -> "Tulip Sho"
|
|
||||||
3019 -> "Ocean Stakes"
|
|
||||||
3020 -> "Nakayama Umamusume Stakes"
|
|
||||||
3021 -> "Falcon Stakes"
|
|
||||||
3022 -> "Flower Cup"
|
|
||||||
3023 -> "Mainichi Hai"
|
|
||||||
3024 -> "March Stakes"
|
|
||||||
3025 -> "Lord Derby Challenge Trophy"
|
|
||||||
3026 -> "Antares Stakes"
|
|
||||||
3027 -> "Fukushima Umamusume Stakes"
|
|
||||||
3028 -> "Niigata Daishoten"
|
|
||||||
3029 -> "Heian Stakes"
|
|
||||||
3030 -> "Naruo Kinen"
|
|
||||||
3031 -> "Mermaid Stakes"
|
|
||||||
3032 -> "Epsom Cup"
|
|
||||||
3033 -> "Unicorn Stakes"
|
|
||||||
3034 -> "Hakodate Sprint Stakes"
|
|
||||||
3035 -> "CBC Sho"
|
|
||||||
3036 -> "Radio Nikkei Sho"
|
|
||||||
3037 -> "Procyon Stakes"
|
|
||||||
3038 -> "Tanabata Sho"
|
|
||||||
3039 -> "Hakodate Kinen"
|
|
||||||
3040 -> "Chukyo Kinen"
|
|
||||||
3041 -> "Hakodate Junior Stakes"
|
|
||||||
3042 -> "Ibis Summer Dash"
|
|
||||||
3043 -> "Queen Stakes"
|
|
||||||
3044 -> "Kokura Kinen"
|
|
||||||
3045 -> "Leopard Stakes"
|
|
||||||
3046 -> "Sekiya Kinen"
|
|
||||||
3047 -> "Elm Stakes"
|
|
||||||
3048 -> "Kitakyushu Kinen"
|
|
||||||
3049 -> "Niigata Junior Stakes"
|
|
||||||
3050 -> "Keeneland Cup"
|
|
||||||
3051 -> "Sapporo Junior Stakes"
|
|
||||||
3052 -> "Kokura Junior Stakes"
|
|
||||||
3053 -> "Niigata Kinen"
|
|
||||||
3054 -> "Shion Stakes"
|
|
||||||
3055 -> "Keisei Hai Autumn Handicap"
|
|
||||||
3056 -> "Sirius Stakes"
|
|
||||||
3057 -> "Saudi Arabia Royal Cup"
|
|
||||||
3058 -> "Fuji Stakes"
|
|
||||||
3059 -> "Artemis Stakes"
|
|
||||||
3060 -> "Fantasy Stakes"
|
|
||||||
3061 -> "Miyako Stakes"
|
|
||||||
3062 -> "Musashino Stakes"
|
|
||||||
3063 -> "Fukushima Kinen"
|
|
||||||
3064 -> "Tokyo Sports Hai Junior Stakes"
|
|
||||||
3065 -> "Kyoto Junior Stakes"
|
|
||||||
3066 -> "Keihan Hai"
|
|
||||||
3067 -> "Challenge Cup"
|
|
||||||
3068 -> "Chunichi Shimbun Hai"
|
|
||||||
3069 -> "Capella Stakes"
|
|
||||||
3070 -> "Turquoise Stakes"
|
|
||||||
4001 -> "Manyo Stakes"
|
|
||||||
4002 -> "Junior Cup"
|
|
||||||
4003 -> "Yodo Tankyori Stakes"
|
|
||||||
4004 -> "Pollux Stakes"
|
|
||||||
4005 -> "January Stakes"
|
|
||||||
4006 -> "New Year Stakes"
|
|
||||||
4007 -> "Kobai Stakes"
|
|
||||||
4008 -> "Subaru Stakes"
|
|
||||||
4009 -> "Wakagoma Stakes"
|
|
||||||
4010 -> "Carbuncle Stakes"
|
|
||||||
4011 -> "Shirafuji Stakes"
|
|
||||||
4012 -> "Crocus Stakes"
|
|
||||||
4013 -> "Yamato Stakes"
|
|
||||||
4014 -> "Elfin Stakes"
|
|
||||||
4015 -> "Rakuyo Stakes"
|
|
||||||
4016 -> "Aldebaran Stakes"
|
|
||||||
4017 -> "Valentine Stakes"
|
|
||||||
4018 -> "Hyacinth Stakes"
|
|
||||||
4019 -> "Sobu Stakes"
|
|
||||||
4020 -> "Sumire Stakes"
|
|
||||||
4021 -> "Osakajo Stakes"
|
|
||||||
4022 -> "Polaris Stakes"
|
|
||||||
4023 -> "Nigawa Stakes"
|
|
||||||
4024 -> "Anemone Stakes"
|
|
||||||
4025 -> "Shoryu Stakes"
|
|
||||||
4026 -> "Kochi Stakes"
|
|
||||||
4027 -> "Wakaba Stakes"
|
|
||||||
4028 -> "Chiba Stakes"
|
|
||||||
4030 -> "Rokko Stakes"
|
|
||||||
4031 -> "Coral Stakes"
|
|
||||||
4032 -> "Marguerite Stakes"
|
|
||||||
4033 -> "Fukuryu Stakes"
|
|
||||||
4035 -> "Wasurenagusa Sho"
|
|
||||||
4036 -> "Keiyo Stakes"
|
|
||||||
4037 -> "Shunrai Stakes"
|
|
||||||
4038 -> "Fukushima Mimpo Hai"
|
|
||||||
4039 -> "Tachibana Stakes"
|
|
||||||
4040 -> "Oasis Stakes"
|
|
||||||
4041 -> "Tennozan Stakes"
|
|
||||||
4042 -> "Tango Stakes"
|
|
||||||
4043 -> "Sweetpea Stakes"
|
|
||||||
4044 -> "Tanigawadake Stakes"
|
|
||||||
4045 -> "Principal Stakes"
|
|
||||||
4046 -> "Metropolitan Stakes"
|
|
||||||
4047 -> "Kurama Stakes"
|
|
||||||
4048 -> "Brilliant Stakes"
|
|
||||||
4049 -> "Miyakooji Stakes"
|
|
||||||
4050 -> "Aoi Stakes"
|
|
||||||
4051 -> "Ritto Stakes"
|
|
||||||
4052 -> "Seiryu Stakes"
|
|
||||||
4053 -> "May Stakes"
|
|
||||||
4054 -> "Hosu Stakes"
|
|
||||||
4055 -> "Idaten Stakes"
|
|
||||||
4056 -> "Shirayuri Stakes"
|
|
||||||
4057 -> "Keyaki Stakes"
|
|
||||||
4058 -> "Azuchijo Stakes"
|
|
||||||
4059 -> "Akhalteke Stakes"
|
|
||||||
4060 -> "Tempozan Stakes"
|
|
||||||
4061 -> "Yonago Stakes"
|
|
||||||
4062 -> "Onuma Stakes"
|
|
||||||
4063 -> "Paradise Stakes"
|
|
||||||
4064 -> "Tomoe Sho"
|
|
||||||
4065 -> "Marine Stakes"
|
|
||||||
4066 -> "Meitetsu Hai"
|
|
||||||
4068 -> "Chukyo Junior Stakes"
|
|
||||||
4069 -> "Fukushima TV Open"
|
|
||||||
4070 -> "Dahlia Sho"
|
|
||||||
4071 -> "Sapporo Nikkei Open"
|
|
||||||
4072 -> "UHB Sho"
|
|
||||||
4073 -> "Aso Stakes"
|
|
||||||
4074 -> "Phoenix Sho"
|
|
||||||
4075 -> "Cosmos Sho"
|
|
||||||
4076 -> "NST Sho"
|
|
||||||
4077 -> "Clover Sho"
|
|
||||||
4078 -> "Himawari Sho"
|
|
||||||
4079 -> "BSN Sho"
|
|
||||||
4080 -> "Kokura Nikkei Open"
|
|
||||||
4081 -> "Toki Stakes"
|
|
||||||
4082 -> "Tancho Stakes"
|
|
||||||
4083 -> "Suzuran Sho"
|
|
||||||
4084 -> "Enif Stakes"
|
|
||||||
4085 -> "Nojigiku Stakes"
|
|
||||||
4086 -> "Radio Nippon Sho"
|
|
||||||
4087 -> "Kikyo Stakes"
|
|
||||||
4088 -> "Fuyo Stakes"
|
|
||||||
4089 -> "Canna Stakes"
|
|
||||||
4090 -> "Port Island Stakes"
|
|
||||||
4091 -> "Opal Stakes"
|
|
||||||
4092 -> "Green Channel Cup"
|
|
||||||
4093 -> "Momiji Stakes"
|
|
||||||
4094 -> "October Stakes"
|
|
||||||
4095 -> "Shinetsu Stakes"
|
|
||||||
4096 -> "Ivy Stakes"
|
|
||||||
4097 -> "Muromachi Stakes"
|
|
||||||
4098 -> "Brazil Cup"
|
|
||||||
4099 -> "Hagi Stakes"
|
|
||||||
4100 -> "Cassiopeia Stakes"
|
|
||||||
4101 -> "Lumiere Autumn Dash"
|
|
||||||
4102 -> "Oro Cup"
|
|
||||||
4103 -> "Fukushima Junior Stakes"
|
|
||||||
4104 -> "Andromeda Stakes"
|
|
||||||
4105 -> "Shimotsuki Stakes"
|
|
||||||
4106 -> "Fukushima Minyu Cup"
|
|
||||||
4107 -> "Capital Stakes"
|
|
||||||
4108 -> "Autumn Leaf Stakes"
|
|
||||||
4109 -> "Lapis Lazuli Stakes"
|
|
||||||
4110 -> "Shiwasu Stakes"
|
|
||||||
4111 -> "Rigel Stakes"
|
|
||||||
4112 -> "Tanzanite Stakes"
|
|
||||||
4113 -> "December Stakes"
|
|
||||||
4114 -> "Christmas Rose Stakes"
|
|
||||||
4115 -> "Galaxy Stakes"
|
|
||||||
4116 -> "Betelgeuse Stakes"
|
|
||||||
4118 -> "Kitakyushu Tankyori Stakes"
|
|
||||||
4119 -> "Azumakofuji Stakes"
|
|
||||||
4120 -> "Sleipnir Stakes"
|
|
||||||
4121 -> "Sannomiya Stakes"
|
|
||||||
4122 -> "Kanetsu Stakes"
|
|
||||||
4123 -> "Nagatsuki Stakes"
|
|
||||||
4124 -> "Uzumasa Stakes"
|
|
||||||
4501 -> "Aster Sho"
|
|
||||||
4502 -> "Saffron Sho"
|
|
||||||
4503 -> "Rindo Sho"
|
|
||||||
4504 -> "Shigiku Sho"
|
|
||||||
4505 -> "Platanus Sho"
|
|
||||||
4506 -> "Nadeshiko Sho"
|
|
||||||
4507 -> "Hyakunichiso Tokubetsu"
|
|
||||||
4508 -> "Kimmokusei Tokubetsu"
|
|
||||||
4509 -> "Oxalis Sho"
|
|
||||||
4510 -> "Kigiku Sho"
|
|
||||||
4511 -> "Mochinoki Sho"
|
|
||||||
4512 -> "Akamatsu Sho"
|
|
||||||
4513 -> "Shumeigiku Sho"
|
|
||||||
4514 -> "Cattleya Sho"
|
|
||||||
4515 -> "Begonia Sho"
|
|
||||||
4516 -> "Shiragiku Sho"
|
|
||||||
4517 -> "Habotan Sho"
|
|
||||||
4518 -> "Koyamaki Sho"
|
|
||||||
4519 -> "Manryo Sho"
|
|
||||||
4520 -> "Kuromatsu Sho"
|
|
||||||
4521 -> "Erica Sho"
|
|
||||||
4522 -> "Tsuwabuki Sho"
|
|
||||||
4523 -> "Hiiragi Sho"
|
|
||||||
4524 -> "Sazanka Sho"
|
|
||||||
4525 -> "Kantsubaki Sho"
|
|
||||||
4526 -> "Senryo Sho"
|
|
||||||
x -> "race " ++ x.show
|
|
||||||
|
|
||||||
// Get the grade for a race.
|
|
||||||
// If no race matches the ID, the result is Pre-OP.
|
|
||||||
pub fun grade(r: race-id): grade
|
|
||||||
match r.game-id
|
|
||||||
1001 -> G1
|
|
||||||
1002 -> G1
|
|
||||||
1003 -> G1
|
|
||||||
1004 -> G1
|
|
||||||
1005 -> G1
|
|
||||||
1006 -> G1
|
|
||||||
1007 -> G1
|
|
||||||
1008 -> G1
|
|
||||||
1009 -> G1
|
|
||||||
1010 -> G1
|
|
||||||
1011 -> G1
|
|
||||||
1012 -> G1
|
|
||||||
1013 -> G1
|
|
||||||
1014 -> G1
|
|
||||||
1015 -> G1
|
|
||||||
1016 -> G1
|
|
||||||
1017 -> G1
|
|
||||||
1018 -> G1
|
|
||||||
1019 -> G1
|
|
||||||
1020 -> G1
|
|
||||||
1021 -> G1
|
|
||||||
1022 -> G1
|
|
||||||
1023 -> G1
|
|
||||||
1024 -> G1
|
|
||||||
1025 -> G1
|
|
||||||
1026 -> G1
|
|
||||||
1027 -> G1
|
|
||||||
1028 -> G1
|
|
||||||
1101 -> G1
|
|
||||||
1102 -> G1
|
|
||||||
1103 -> G1
|
|
||||||
1104 -> G1
|
|
||||||
1105 -> G1
|
|
||||||
1106 -> G1
|
|
||||||
2001 -> G2
|
|
||||||
2002 -> G2
|
|
||||||
2003 -> G2
|
|
||||||
2004 -> G2
|
|
||||||
2005 -> G2
|
|
||||||
2006 -> G2
|
|
||||||
2007 -> G2
|
|
||||||
2008 -> G2
|
|
||||||
2009 -> G2
|
|
||||||
2010 -> G2
|
|
||||||
2011 -> G2
|
|
||||||
2012 -> G2
|
|
||||||
2013 -> G2
|
|
||||||
2014 -> G2
|
|
||||||
2015 -> G2
|
|
||||||
2016 -> G2
|
|
||||||
2017 -> G2
|
|
||||||
2018 -> G2
|
|
||||||
2019 -> G2
|
|
||||||
2020 -> G2
|
|
||||||
2021 -> G2
|
|
||||||
2022 -> G2
|
|
||||||
2023 -> G2
|
|
||||||
2024 -> G2
|
|
||||||
2025 -> G2
|
|
||||||
2026 -> G2
|
|
||||||
2027 -> G2
|
|
||||||
2028 -> G2
|
|
||||||
2029 -> G2
|
|
||||||
2030 -> G2
|
|
||||||
2031 -> G2
|
|
||||||
2032 -> G2
|
|
||||||
2033 -> G2
|
|
||||||
2034 -> G2
|
|
||||||
2035 -> G2
|
|
||||||
3001 -> G3
|
|
||||||
3002 -> G3
|
|
||||||
3003 -> G3
|
|
||||||
3004 -> G3
|
|
||||||
3005 -> G3
|
|
||||||
3006 -> G3
|
|
||||||
3007 -> G3
|
|
||||||
3008 -> G3
|
|
||||||
3009 -> G3
|
|
||||||
3010 -> G3
|
|
||||||
3011 -> G3
|
|
||||||
3012 -> G3
|
|
||||||
3013 -> G3
|
|
||||||
3014 -> G3
|
|
||||||
3015 -> G3
|
|
||||||
3016 -> G3
|
|
||||||
3017 -> G3
|
|
||||||
3018 -> G2
|
|
||||||
3019 -> G3
|
|
||||||
3020 -> G3
|
|
||||||
3021 -> G3
|
|
||||||
3022 -> G3
|
|
||||||
3023 -> G3
|
|
||||||
3024 -> G3
|
|
||||||
3025 -> G3
|
|
||||||
3026 -> G3
|
|
||||||
3027 -> G3
|
|
||||||
3028 -> G3
|
|
||||||
3029 -> G3
|
|
||||||
3030 -> G3
|
|
||||||
3031 -> G3
|
|
||||||
3032 -> G3
|
|
||||||
3033 -> G3
|
|
||||||
3034 -> G3
|
|
||||||
3035 -> G3
|
|
||||||
3036 -> G3
|
|
||||||
3037 -> G3
|
|
||||||
3038 -> G3
|
|
||||||
3039 -> G3
|
|
||||||
3040 -> G3
|
|
||||||
3041 -> G3
|
|
||||||
3042 -> G3
|
|
||||||
3043 -> G3
|
|
||||||
3044 -> G3
|
|
||||||
3045 -> G3
|
|
||||||
3046 -> G3
|
|
||||||
3047 -> G3
|
|
||||||
3048 -> G3
|
|
||||||
3049 -> G3
|
|
||||||
3050 -> G3
|
|
||||||
3051 -> G3
|
|
||||||
3052 -> G3
|
|
||||||
3053 -> G3
|
|
||||||
3054 -> G3
|
|
||||||
3055 -> G3
|
|
||||||
3056 -> G3
|
|
||||||
3057 -> G3
|
|
||||||
3058 -> G2
|
|
||||||
3059 -> G3
|
|
||||||
3060 -> G3
|
|
||||||
3061 -> G3
|
|
||||||
3062 -> G3
|
|
||||||
3063 -> G3
|
|
||||||
3064 -> G3
|
|
||||||
3065 -> G3
|
|
||||||
3066 -> G3
|
|
||||||
3067 -> G3
|
|
||||||
3068 -> G3
|
|
||||||
3069 -> G3
|
|
||||||
3070 -> G3
|
|
||||||
4001 -> OP
|
|
||||||
4002 -> OP
|
|
||||||
4003 -> OP
|
|
||||||
4004 -> OP
|
|
||||||
4005 -> OP
|
|
||||||
4006 -> OP
|
|
||||||
4007 -> OP
|
|
||||||
4008 -> OP
|
|
||||||
4009 -> OP
|
|
||||||
4010 -> OP
|
|
||||||
4011 -> OP
|
|
||||||
4012 -> OP
|
|
||||||
4013 -> OP
|
|
||||||
4014 -> OP
|
|
||||||
4015 -> OP
|
|
||||||
4016 -> OP
|
|
||||||
4017 -> OP
|
|
||||||
4018 -> OP
|
|
||||||
4019 -> OP
|
|
||||||
4020 -> OP
|
|
||||||
4021 -> OP
|
|
||||||
4022 -> OP
|
|
||||||
4023 -> OP
|
|
||||||
4024 -> OP
|
|
||||||
4025 -> OP
|
|
||||||
4026 -> OP
|
|
||||||
4027 -> OP
|
|
||||||
4028 -> OP
|
|
||||||
4030 -> OP
|
|
||||||
4031 -> OP
|
|
||||||
4032 -> OP
|
|
||||||
4033 -> OP
|
|
||||||
4035 -> OP
|
|
||||||
4036 -> OP
|
|
||||||
4037 -> OP
|
|
||||||
4038 -> OP
|
|
||||||
4039 -> OP
|
|
||||||
4040 -> OP
|
|
||||||
4041 -> OP
|
|
||||||
4042 -> OP
|
|
||||||
4043 -> OP
|
|
||||||
4044 -> OP
|
|
||||||
4045 -> OP
|
|
||||||
4046 -> OP
|
|
||||||
4047 -> OP
|
|
||||||
4048 -> OP
|
|
||||||
4049 -> OP
|
|
||||||
4050 -> G3
|
|
||||||
4051 -> OP
|
|
||||||
4052 -> OP
|
|
||||||
4053 -> OP
|
|
||||||
4054 -> OP
|
|
||||||
4055 -> OP
|
|
||||||
4056 -> OP
|
|
||||||
4057 -> OP
|
|
||||||
4058 -> OP
|
|
||||||
4059 -> OP
|
|
||||||
4060 -> OP
|
|
||||||
4061 -> OP
|
|
||||||
4062 -> OP
|
|
||||||
4063 -> OP
|
|
||||||
4064 -> OP
|
|
||||||
4065 -> OP
|
|
||||||
4066 -> OP
|
|
||||||
4068 -> OP
|
|
||||||
4069 -> OP
|
|
||||||
4070 -> OP
|
|
||||||
4071 -> OP
|
|
||||||
4072 -> OP
|
|
||||||
4073 -> OP
|
|
||||||
4074 -> OP
|
|
||||||
4075 -> OP
|
|
||||||
4076 -> OP
|
|
||||||
4077 -> OP
|
|
||||||
4078 -> OP
|
|
||||||
4079 -> OP
|
|
||||||
4080 -> OP
|
|
||||||
4081 -> OP
|
|
||||||
4082 -> OP
|
|
||||||
4083 -> OP
|
|
||||||
4084 -> OP
|
|
||||||
4085 -> OP
|
|
||||||
4086 -> OP
|
|
||||||
4087 -> OP
|
|
||||||
4088 -> OP
|
|
||||||
4089 -> OP
|
|
||||||
4090 -> OP
|
|
||||||
4091 -> OP
|
|
||||||
4092 -> OP
|
|
||||||
4093 -> OP
|
|
||||||
4094 -> OP
|
|
||||||
4095 -> OP
|
|
||||||
4096 -> OP
|
|
||||||
4097 -> OP
|
|
||||||
4098 -> OP
|
|
||||||
4099 -> OP
|
|
||||||
4100 -> OP
|
|
||||||
4101 -> OP
|
|
||||||
4102 -> OP
|
|
||||||
4103 -> OP
|
|
||||||
4104 -> OP
|
|
||||||
4105 -> OP
|
|
||||||
4106 -> OP
|
|
||||||
4107 -> OP
|
|
||||||
4108 -> OP
|
|
||||||
4109 -> OP
|
|
||||||
4110 -> OP
|
|
||||||
4111 -> OP
|
|
||||||
4112 -> OP
|
|
||||||
4113 -> OP
|
|
||||||
4114 -> OP
|
|
||||||
4115 -> OP
|
|
||||||
4116 -> OP
|
|
||||||
4118 -> OP
|
|
||||||
4119 -> OP
|
|
||||||
4120 -> OP
|
|
||||||
4121 -> OP
|
|
||||||
4122 -> OP
|
|
||||||
4123 -> OP
|
|
||||||
4124 -> OP
|
|
||||||
4501 -> Pre-OP
|
|
||||||
4502 -> Pre-OP
|
|
||||||
4503 -> Pre-OP
|
|
||||||
4504 -> Pre-OP
|
|
||||||
4505 -> Pre-OP
|
|
||||||
4506 -> Pre-OP
|
|
||||||
4507 -> Pre-OP
|
|
||||||
4508 -> Pre-OP
|
|
||||||
4509 -> Pre-OP
|
|
||||||
4510 -> Pre-OP
|
|
||||||
4511 -> Pre-OP
|
|
||||||
4512 -> Pre-OP
|
|
||||||
4513 -> Pre-OP
|
|
||||||
4514 -> Pre-OP
|
|
||||||
4515 -> Pre-OP
|
|
||||||
4516 -> Pre-OP
|
|
||||||
4517 -> Pre-OP
|
|
||||||
4518 -> Pre-OP
|
|
||||||
4519 -> Pre-OP
|
|
||||||
4520 -> Pre-OP
|
|
||||||
4521 -> Pre-OP
|
|
||||||
4522 -> Pre-OP
|
|
||||||
4523 -> Pre-OP
|
|
||||||
4524 -> Pre-OP
|
|
||||||
4525 -> Pre-OP
|
|
||||||
4526 -> Pre-OP
|
|
||||||
_ -> Pre-OP
|
|
||||||
|
|
||||||
// Get the thumbnail ID for a race.
|
|
||||||
// If no race matches the ID, the result is an invalid ID.
|
|
||||||
pub fun thumbnail(r: race-id): race-thumbnail-id
|
|
||||||
match r.game-id
|
|
||||||
1001 -> Race-thumbnail-id(1001)
|
|
||||||
1002 -> Race-thumbnail-id(1002)
|
|
||||||
1003 -> Race-thumbnail-id(1003)
|
|
||||||
1004 -> Race-thumbnail-id(1004)
|
|
||||||
1005 -> Race-thumbnail-id(1005)
|
|
||||||
1006 -> Race-thumbnail-id(1006)
|
|
||||||
1007 -> Race-thumbnail-id(1007)
|
|
||||||
1008 -> Race-thumbnail-id(1008)
|
|
||||||
1009 -> Race-thumbnail-id(1009)
|
|
||||||
1010 -> Race-thumbnail-id(1010)
|
|
||||||
1011 -> Race-thumbnail-id(1011)
|
|
||||||
1012 -> Race-thumbnail-id(1012)
|
|
||||||
1013 -> Race-thumbnail-id(1013)
|
|
||||||
1014 -> Race-thumbnail-id(1014)
|
|
||||||
1015 -> Race-thumbnail-id(1015)
|
|
||||||
1016 -> Race-thumbnail-id(1016)
|
|
||||||
1017 -> Race-thumbnail-id(1017)
|
|
||||||
1018 -> Race-thumbnail-id(1018)
|
|
||||||
1019 -> Race-thumbnail-id(1019)
|
|
||||||
1020 -> Race-thumbnail-id(1020)
|
|
||||||
1021 -> Race-thumbnail-id(1021)
|
|
||||||
1022 -> Race-thumbnail-id(1022)
|
|
||||||
1023 -> Race-thumbnail-id(1023)
|
|
||||||
1024 -> Race-thumbnail-id(1024)
|
|
||||||
1025 -> Race-thumbnail-id(1012)
|
|
||||||
1026 -> Race-thumbnail-id(1015)
|
|
||||||
1027 -> Race-thumbnail-id(1027)
|
|
||||||
1028 -> Race-thumbnail-id(1028)
|
|
||||||
1101 -> Race-thumbnail-id(1101)
|
|
||||||
1102 -> Race-thumbnail-id(1102)
|
|
||||||
1103 -> Race-thumbnail-id(1103)
|
|
||||||
1104 -> Race-thumbnail-id(1104)
|
|
||||||
1105 -> Race-thumbnail-id(1105)
|
|
||||||
1106 -> Race-thumbnail-id(1106)
|
|
||||||
2001 -> Race-thumbnail-id(2001)
|
|
||||||
2002 -> Race-thumbnail-id(2002)
|
|
||||||
2003 -> Race-thumbnail-id(2003)
|
|
||||||
2004 -> Race-thumbnail-id(2004)
|
|
||||||
2005 -> Race-thumbnail-id(2005)
|
|
||||||
2006 -> Race-thumbnail-id(2006)
|
|
||||||
2007 -> Race-thumbnail-id(2007)
|
|
||||||
2008 -> Race-thumbnail-id(2008)
|
|
||||||
2009 -> Race-thumbnail-id(2009)
|
|
||||||
2010 -> Race-thumbnail-id(2010)
|
|
||||||
2011 -> Race-thumbnail-id(2011)
|
|
||||||
2012 -> Race-thumbnail-id(2012)
|
|
||||||
2013 -> Race-thumbnail-id(2013)
|
|
||||||
2014 -> Race-thumbnail-id(2014)
|
|
||||||
2015 -> Race-thumbnail-id(2015)
|
|
||||||
2016 -> Race-thumbnail-id(2016)
|
|
||||||
2017 -> Race-thumbnail-id(2017)
|
|
||||||
2018 -> Race-thumbnail-id(2018)
|
|
||||||
2019 -> Race-thumbnail-id(2019)
|
|
||||||
2020 -> Race-thumbnail-id(2020)
|
|
||||||
2021 -> Race-thumbnail-id(2021)
|
|
||||||
2022 -> Race-thumbnail-id(2022)
|
|
||||||
2023 -> Race-thumbnail-id(2023)
|
|
||||||
2024 -> Race-thumbnail-id(2024)
|
|
||||||
2025 -> Race-thumbnail-id(2025)
|
|
||||||
2026 -> Race-thumbnail-id(2026)
|
|
||||||
2027 -> Race-thumbnail-id(2027)
|
|
||||||
2028 -> Race-thumbnail-id(2028)
|
|
||||||
2029 -> Race-thumbnail-id(2029)
|
|
||||||
2030 -> Race-thumbnail-id(2030)
|
|
||||||
2031 -> Race-thumbnail-id(2031)
|
|
||||||
2032 -> Race-thumbnail-id(2032)
|
|
||||||
2033 -> Race-thumbnail-id(2033)
|
|
||||||
2034 -> Race-thumbnail-id(2034)
|
|
||||||
2035 -> Race-thumbnail-id(2010)
|
|
||||||
3001 -> Race-thumbnail-id(3001)
|
|
||||||
3002 -> Race-thumbnail-id(3002)
|
|
||||||
3003 -> Race-thumbnail-id(3003)
|
|
||||||
3004 -> Race-thumbnail-id(3004)
|
|
||||||
3005 -> Race-thumbnail-id(3005)
|
|
||||||
3006 -> Race-thumbnail-id(3006)
|
|
||||||
3007 -> Race-thumbnail-id(3007)
|
|
||||||
3008 -> Race-thumbnail-id(3008)
|
|
||||||
3009 -> Race-thumbnail-id(3009)
|
|
||||||
3010 -> Race-thumbnail-id(3010)
|
|
||||||
3011 -> Race-thumbnail-id(3011)
|
|
||||||
3012 -> Race-thumbnail-id(3012)
|
|
||||||
3013 -> Race-thumbnail-id(3013)
|
|
||||||
3014 -> Race-thumbnail-id(3014)
|
|
||||||
3015 -> Race-thumbnail-id(3015)
|
|
||||||
3016 -> Race-thumbnail-id(3016)
|
|
||||||
3017 -> Race-thumbnail-id(3017)
|
|
||||||
3018 -> Race-thumbnail-id(3018)
|
|
||||||
3019 -> Race-thumbnail-id(3019)
|
|
||||||
3020 -> Race-thumbnail-id(3020)
|
|
||||||
3021 -> Race-thumbnail-id(3021)
|
|
||||||
3022 -> Race-thumbnail-id(3022)
|
|
||||||
3023 -> Race-thumbnail-id(3023)
|
|
||||||
3024 -> Race-thumbnail-id(3024)
|
|
||||||
3025 -> Race-thumbnail-id(3025)
|
|
||||||
3026 -> Race-thumbnail-id(3026)
|
|
||||||
3027 -> Race-thumbnail-id(3027)
|
|
||||||
3028 -> Race-thumbnail-id(3028)
|
|
||||||
3029 -> Race-thumbnail-id(3029)
|
|
||||||
3030 -> Race-thumbnail-id(3030)
|
|
||||||
3031 -> Race-thumbnail-id(3031)
|
|
||||||
3032 -> Race-thumbnail-id(3032)
|
|
||||||
3033 -> Race-thumbnail-id(3033)
|
|
||||||
3034 -> Race-thumbnail-id(3034)
|
|
||||||
3035 -> Race-thumbnail-id(3035)
|
|
||||||
3036 -> Race-thumbnail-id(3036)
|
|
||||||
3037 -> Race-thumbnail-id(3037)
|
|
||||||
3038 -> Race-thumbnail-id(3038)
|
|
||||||
3039 -> Race-thumbnail-id(3039)
|
|
||||||
3040 -> Race-thumbnail-id(3040)
|
|
||||||
3041 -> Race-thumbnail-id(3041)
|
|
||||||
3042 -> Race-thumbnail-id(3042)
|
|
||||||
3043 -> Race-thumbnail-id(3043)
|
|
||||||
3044 -> Race-thumbnail-id(3044)
|
|
||||||
3045 -> Race-thumbnail-id(3045)
|
|
||||||
3046 -> Race-thumbnail-id(3046)
|
|
||||||
3047 -> Race-thumbnail-id(3047)
|
|
||||||
3048 -> Race-thumbnail-id(3048)
|
|
||||||
3049 -> Race-thumbnail-id(3049)
|
|
||||||
3050 -> Race-thumbnail-id(3050)
|
|
||||||
3051 -> Race-thumbnail-id(3051)
|
|
||||||
3052 -> Race-thumbnail-id(3052)
|
|
||||||
3053 -> Race-thumbnail-id(3053)
|
|
||||||
3054 -> Race-thumbnail-id(3054)
|
|
||||||
3055 -> Race-thumbnail-id(3055)
|
|
||||||
3056 -> Race-thumbnail-id(3056)
|
|
||||||
3057 -> Race-thumbnail-id(3057)
|
|
||||||
3058 -> Race-thumbnail-id(3058)
|
|
||||||
3059 -> Race-thumbnail-id(3059)
|
|
||||||
3060 -> Race-thumbnail-id(3060)
|
|
||||||
3061 -> Race-thumbnail-id(3061)
|
|
||||||
3062 -> Race-thumbnail-id(3062)
|
|
||||||
3063 -> Race-thumbnail-id(3063)
|
|
||||||
3064 -> Race-thumbnail-id(3064)
|
|
||||||
3065 -> Race-thumbnail-id(3065)
|
|
||||||
3066 -> Race-thumbnail-id(3066)
|
|
||||||
3067 -> Race-thumbnail-id(3067)
|
|
||||||
3068 -> Race-thumbnail-id(3068)
|
|
||||||
3069 -> Race-thumbnail-id(3069)
|
|
||||||
3070 -> Race-thumbnail-id(3070)
|
|
||||||
4001 -> Race-thumbnail-id(4001)
|
|
||||||
4002 -> Race-thumbnail-id(4002)
|
|
||||||
4003 -> Race-thumbnail-id(4003)
|
|
||||||
4004 -> Race-thumbnail-id(4004)
|
|
||||||
4005 -> Race-thumbnail-id(4005)
|
|
||||||
4006 -> Race-thumbnail-id(4006)
|
|
||||||
4007 -> Race-thumbnail-id(4007)
|
|
||||||
4008 -> Race-thumbnail-id(4008)
|
|
||||||
4009 -> Race-thumbnail-id(4009)
|
|
||||||
4010 -> Race-thumbnail-id(4010)
|
|
||||||
4011 -> Race-thumbnail-id(4011)
|
|
||||||
4012 -> Race-thumbnail-id(4012)
|
|
||||||
4013 -> Race-thumbnail-id(4013)
|
|
||||||
4014 -> Race-thumbnail-id(4014)
|
|
||||||
4015 -> Race-thumbnail-id(4015)
|
|
||||||
4016 -> Race-thumbnail-id(4016)
|
|
||||||
4017 -> Race-thumbnail-id(4017)
|
|
||||||
4018 -> Race-thumbnail-id(4018)
|
|
||||||
4019 -> Race-thumbnail-id(4019)
|
|
||||||
4020 -> Race-thumbnail-id(4020)
|
|
||||||
4021 -> Race-thumbnail-id(4021)
|
|
||||||
4022 -> Race-thumbnail-id(4022)
|
|
||||||
4023 -> Race-thumbnail-id(4023)
|
|
||||||
4024 -> Race-thumbnail-id(4024)
|
|
||||||
4025 -> Race-thumbnail-id(4025)
|
|
||||||
4026 -> Race-thumbnail-id(4026)
|
|
||||||
4027 -> Race-thumbnail-id(4027)
|
|
||||||
4028 -> Race-thumbnail-id(4028)
|
|
||||||
4030 -> Race-thumbnail-id(4030)
|
|
||||||
4031 -> Race-thumbnail-id(4031)
|
|
||||||
4032 -> Race-thumbnail-id(4032)
|
|
||||||
4033 -> Race-thumbnail-id(4033)
|
|
||||||
4035 -> Race-thumbnail-id(4035)
|
|
||||||
4036 -> Race-thumbnail-id(4036)
|
|
||||||
4037 -> Race-thumbnail-id(4037)
|
|
||||||
4038 -> Race-thumbnail-id(4038)
|
|
||||||
4039 -> Race-thumbnail-id(4039)
|
|
||||||
4040 -> Race-thumbnail-id(4040)
|
|
||||||
4041 -> Race-thumbnail-id(4041)
|
|
||||||
4042 -> Race-thumbnail-id(4042)
|
|
||||||
4043 -> Race-thumbnail-id(4043)
|
|
||||||
4044 -> Race-thumbnail-id(4044)
|
|
||||||
4045 -> Race-thumbnail-id(4045)
|
|
||||||
4046 -> Race-thumbnail-id(4046)
|
|
||||||
4047 -> Race-thumbnail-id(4047)
|
|
||||||
4048 -> Race-thumbnail-id(4048)
|
|
||||||
4049 -> Race-thumbnail-id(4049)
|
|
||||||
4050 -> Race-thumbnail-id(4050)
|
|
||||||
4051 -> Race-thumbnail-id(4051)
|
|
||||||
4052 -> Race-thumbnail-id(4052)
|
|
||||||
4053 -> Race-thumbnail-id(4053)
|
|
||||||
4054 -> Race-thumbnail-id(4054)
|
|
||||||
4055 -> Race-thumbnail-id(4055)
|
|
||||||
4056 -> Race-thumbnail-id(4056)
|
|
||||||
4057 -> Race-thumbnail-id(4057)
|
|
||||||
4058 -> Race-thumbnail-id(4058)
|
|
||||||
4059 -> Race-thumbnail-id(4059)
|
|
||||||
4060 -> Race-thumbnail-id(4060)
|
|
||||||
4061 -> Race-thumbnail-id(4061)
|
|
||||||
4062 -> Race-thumbnail-id(4062)
|
|
||||||
4063 -> Race-thumbnail-id(4063)
|
|
||||||
4064 -> Race-thumbnail-id(4064)
|
|
||||||
4065 -> Race-thumbnail-id(4065)
|
|
||||||
4066 -> Race-thumbnail-id(4066)
|
|
||||||
4068 -> Race-thumbnail-id(4068)
|
|
||||||
4069 -> Race-thumbnail-id(4069)
|
|
||||||
4070 -> Race-thumbnail-id(4070)
|
|
||||||
4071 -> Race-thumbnail-id(4071)
|
|
||||||
4072 -> Race-thumbnail-id(4072)
|
|
||||||
4073 -> Race-thumbnail-id(4073)
|
|
||||||
4074 -> Race-thumbnail-id(4074)
|
|
||||||
4075 -> Race-thumbnail-id(4075)
|
|
||||||
4076 -> Race-thumbnail-id(4076)
|
|
||||||
4077 -> Race-thumbnail-id(4077)
|
|
||||||
4078 -> Race-thumbnail-id(4078)
|
|
||||||
4079 -> Race-thumbnail-id(4079)
|
|
||||||
4080 -> Race-thumbnail-id(4080)
|
|
||||||
4081 -> Race-thumbnail-id(4081)
|
|
||||||
4082 -> Race-thumbnail-id(4082)
|
|
||||||
4083 -> Race-thumbnail-id(4083)
|
|
||||||
4084 -> Race-thumbnail-id(4084)
|
|
||||||
4085 -> Race-thumbnail-id(4085)
|
|
||||||
4086 -> Race-thumbnail-id(4086)
|
|
||||||
4087 -> Race-thumbnail-id(4087)
|
|
||||||
4088 -> Race-thumbnail-id(4088)
|
|
||||||
4089 -> Race-thumbnail-id(4089)
|
|
||||||
4090 -> Race-thumbnail-id(4090)
|
|
||||||
4091 -> Race-thumbnail-id(4091)
|
|
||||||
4092 -> Race-thumbnail-id(4092)
|
|
||||||
4093 -> Race-thumbnail-id(4093)
|
|
||||||
4094 -> Race-thumbnail-id(4094)
|
|
||||||
4095 -> Race-thumbnail-id(4095)
|
|
||||||
4096 -> Race-thumbnail-id(4096)
|
|
||||||
4097 -> Race-thumbnail-id(4097)
|
|
||||||
4098 -> Race-thumbnail-id(4098)
|
|
||||||
4099 -> Race-thumbnail-id(4099)
|
|
||||||
4100 -> Race-thumbnail-id(4100)
|
|
||||||
4101 -> Race-thumbnail-id(4101)
|
|
||||||
4102 -> Race-thumbnail-id(4102)
|
|
||||||
4103 -> Race-thumbnail-id(4103)
|
|
||||||
4104 -> Race-thumbnail-id(4104)
|
|
||||||
4105 -> Race-thumbnail-id(4105)
|
|
||||||
4106 -> Race-thumbnail-id(4106)
|
|
||||||
4107 -> Race-thumbnail-id(4107)
|
|
||||||
4108 -> Race-thumbnail-id(4108)
|
|
||||||
4109 -> Race-thumbnail-id(4109)
|
|
||||||
4110 -> Race-thumbnail-id(4110)
|
|
||||||
4111 -> Race-thumbnail-id(4111)
|
|
||||||
4112 -> Race-thumbnail-id(4112)
|
|
||||||
4113 -> Race-thumbnail-id(4113)
|
|
||||||
4114 -> Race-thumbnail-id(4114)
|
|
||||||
4115 -> Race-thumbnail-id(4115)
|
|
||||||
4116 -> Race-thumbnail-id(4116)
|
|
||||||
4118 -> Race-thumbnail-id(4117)
|
|
||||||
4119 -> Race-thumbnail-id(4118)
|
|
||||||
4120 -> Race-thumbnail-id(4119)
|
|
||||||
4121 -> Race-thumbnail-id(4120)
|
|
||||||
4122 -> Race-thumbnail-id(4121)
|
|
||||||
4123 -> Race-thumbnail-id(4122)
|
|
||||||
4124 -> Race-thumbnail-id(4123)
|
|
||||||
4501 -> Race-thumbnail-id(4501)
|
|
||||||
4502 -> Race-thumbnail-id(4502)
|
|
||||||
4503 -> Race-thumbnail-id(4503)
|
|
||||||
4504 -> Race-thumbnail-id(4504)
|
|
||||||
4505 -> Race-thumbnail-id(4505)
|
|
||||||
4506 -> Race-thumbnail-id(4506)
|
|
||||||
4507 -> Race-thumbnail-id(4507)
|
|
||||||
4508 -> Race-thumbnail-id(4508)
|
|
||||||
4509 -> Race-thumbnail-id(4509)
|
|
||||||
4510 -> Race-thumbnail-id(4510)
|
|
||||||
4511 -> Race-thumbnail-id(4511)
|
|
||||||
4512 -> Race-thumbnail-id(4512)
|
|
||||||
4513 -> Race-thumbnail-id(4513)
|
|
||||||
4514 -> Race-thumbnail-id(4514)
|
|
||||||
4515 -> Race-thumbnail-id(4515)
|
|
||||||
4516 -> Race-thumbnail-id(4516)
|
|
||||||
4517 -> Race-thumbnail-id(4517)
|
|
||||||
4518 -> Race-thumbnail-id(4518)
|
|
||||||
4519 -> Race-thumbnail-id(4519)
|
|
||||||
4520 -> Race-thumbnail-id(4520)
|
|
||||||
4521 -> Race-thumbnail-id(4521)
|
|
||||||
4522 -> Race-thumbnail-id(4522)
|
|
||||||
4523 -> Race-thumbnail-id(4523)
|
|
||||||
4524 -> Race-thumbnail-id(4524)
|
|
||||||
4525 -> Race-thumbnail-id(4525)
|
|
||||||
4526 -> Race-thumbnail-id(4526)
|
|
||||||
_ -> Race-thumbnail-id(0)
|
|
||||||
|
|
||||||
// Get the primary ID for a race.
|
|
||||||
// For races which are the primary version, or if no race matches the given ID,
|
|
||||||
// the result is the input.
|
|
||||||
pub fun primary(r: race-id): race-id
|
|
||||||
match r.game-id
|
|
||||||
1025 -> Race-id(1012)
|
|
||||||
1026 -> Race-id(1015)
|
|
||||||
1027 -> Race-id(1006)
|
|
||||||
1028 -> Race-id(1005)
|
|
||||||
2035 -> Race-id(2010)
|
|
||||||
_ -> r
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,518 +0,0 @@
|
|||||||
module horse/global/saddle
|
|
||||||
|
|
||||||
// Automatically generated with horsegen; DO NOT EDIT
|
|
||||||
|
|
||||||
import std/core/delayed
|
|
||||||
import std/core/vector
|
|
||||||
import std/core-extras
|
|
||||||
import horse/game-id
|
|
||||||
pub import horse/race
|
|
||||||
pub import horse/global/race
|
|
||||||
|
|
||||||
extern create-id-table(): vector<int>
|
|
||||||
c inline "int32_t arr[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,};\nkk_vector_from_cint32array(arr, (kk_ssize_t)155, kk_context())"
|
|
||||||
js inline "[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,]"
|
|
||||||
// Vector of all saddle IDs in order for easy iterating.
|
|
||||||
pub val all = once(create-id-table)
|
|
||||||
|
|
||||||
// Get the name for a saddle.
|
|
||||||
// Alternate versions of saddles have an indication of their ID in their names.
|
|
||||||
// If no saddle matches the ID, the result contains the numeric ID.
|
|
||||||
pub fun show(s: saddle-id): string
|
|
||||||
match s.game-id
|
|
||||||
1 -> "Classic Triple Crown"
|
|
||||||
2 -> "Senior Autumn Triple Crown"
|
|
||||||
3 -> "Triple Tiara"
|
|
||||||
4 -> "Senior Spring Triple Crown"
|
|
||||||
5 -> "Tenno Sweep"
|
|
||||||
6 -> "Dual Grand Prix"
|
|
||||||
7 -> "Dual Miles"
|
|
||||||
8 -> "Dual Sprints"
|
|
||||||
9 -> "Dual Dirts"
|
|
||||||
10 -> "Arima Kinen"
|
|
||||||
11 -> "Japan C."
|
|
||||||
12 -> "Japanese Derby"
|
|
||||||
13 -> "Tenno Sho (Spring)"
|
|
||||||
14 -> "Takarazuka Kinen"
|
|
||||||
15 -> "Tenno Sho (Autumn)"
|
|
||||||
16 -> "Kikuka Sho"
|
|
||||||
17 -> "Osaka Hai"
|
|
||||||
18 -> "Satsuki Sho"
|
|
||||||
19 -> "Japanese Oaks"
|
|
||||||
20 -> "Takamatsunomiya Kinen"
|
|
||||||
21 -> "Yasuda Kinen"
|
|
||||||
22 -> "Sprinters S."
|
|
||||||
23 -> "Mile Ch."
|
|
||||||
24 -> "Oka Sho"
|
|
||||||
25 -> "Victoria Mile"
|
|
||||||
26 -> "Queen Elizabeth II Cup"
|
|
||||||
27 -> "NHK Mile C."
|
|
||||||
28 -> "Shuka Sho"
|
|
||||||
29 -> "Champions C."
|
|
||||||
30 -> "February S."
|
|
||||||
31 -> "JBC Classic"
|
|
||||||
32 -> "Tokyo Daishoten"
|
|
||||||
33 -> "Asahi Hai F.S."
|
|
||||||
34 -> "Hopeful S."
|
|
||||||
35 -> "Hanshin J.F."
|
|
||||||
36 -> "Teio Sho"
|
|
||||||
37 -> "JBC Sprint"
|
|
||||||
38 -> "J.D. Derby"
|
|
||||||
39 -> "JBC L. Classic"
|
|
||||||
40 -> "Nikkei Shinshun Hai"
|
|
||||||
41 -> "Tokai S."
|
|
||||||
42 -> "American JCC"
|
|
||||||
43 -> "Kyoto Kinen"
|
|
||||||
44 -> "Nakayama Kinen"
|
|
||||||
45 -> "Yayoi Sho"
|
|
||||||
46 -> "Kinko Sho"
|
|
||||||
47 -> "Fillies' Revue"
|
|
||||||
48 -> "Hanshin Daishoten"
|
|
||||||
49 -> "Spring S."
|
|
||||||
50 -> "Nikkei Sho"
|
|
||||||
51 -> "Hanshin Umamusume S."
|
|
||||||
52 -> "New Zealand T."
|
|
||||||
53 -> "Yomiuri Milers C."
|
|
||||||
54 -> "Flora S."
|
|
||||||
55 -> "Aoba Sho"
|
|
||||||
56 -> "Kyoto Shimbun Hai"
|
|
||||||
57 -> "Keio Hai Spring C."
|
|
||||||
58 -> "Meguro Kinen"
|
|
||||||
59 -> "Sapporo Kinen"
|
|
||||||
60 -> "Centaur S."
|
|
||||||
61 -> "Rose S."
|
|
||||||
62 -> "St. Lite Kinen"
|
|
||||||
63 -> "Kobe Shimbun Hai"
|
|
||||||
64 -> "All Comers"
|
|
||||||
65 -> "Mainichi Okan"
|
|
||||||
66 -> "Kyoto Daishoten"
|
|
||||||
67 -> "Fuchu Umamusume S."
|
|
||||||
68 -> "Swan S."
|
|
||||||
69 -> "Keio Hai Junior S."
|
|
||||||
70 -> "Copa Republica Argentina"
|
|
||||||
71 -> "Daily Hai Junior S."
|
|
||||||
72 -> "Stayers S."
|
|
||||||
73 -> "Hanshin C."
|
|
||||||
74 -> "Kyoto Kimpai"
|
|
||||||
75 -> "Nakayama Kimpai"
|
|
||||||
76 -> "Shinzan Kinen"
|
|
||||||
77 -> "Fairy S."
|
|
||||||
78 -> "Aichi Hai"
|
|
||||||
79 -> "Keisei Hai"
|
|
||||||
80 -> "Silk Road S."
|
|
||||||
81 -> "Negishi S."
|
|
||||||
82 -> "Kisaragi Sho"
|
|
||||||
83 -> "Tokyo Shimbun Hai"
|
|
||||||
84 -> "Queen C."
|
|
||||||
85 -> "Kyodo News Hai"
|
|
||||||
86 -> "Kyoto Umamusume S."
|
|
||||||
87 -> "Diamond S."
|
|
||||||
88 -> "Kokura Daishoten"
|
|
||||||
89 -> "Arlington C."
|
|
||||||
90 -> "Hankyu Hai"
|
|
||||||
91 -> "Tulip Sho"
|
|
||||||
92 -> "Ocean S."
|
|
||||||
93 -> "Nakayama Umamusume S."
|
|
||||||
94 -> "Falcon S."
|
|
||||||
95 -> "Flower C."
|
|
||||||
96 -> "Mainichi Hai"
|
|
||||||
97 -> "March S."
|
|
||||||
98 -> "Lord Derby C.T."
|
|
||||||
99 -> "Antares S."
|
|
||||||
100 -> "Fukushima Umamusume S."
|
|
||||||
101 -> "Niigata Daishoten"
|
|
||||||
102 -> "Heian S."
|
|
||||||
103 -> "Naruo Kinen"
|
|
||||||
104 -> "Mermaid S."
|
|
||||||
105 -> "Epsom C."
|
|
||||||
106 -> "Unicorn S."
|
|
||||||
107 -> "Hakodate Sprint S."
|
|
||||||
108 -> "CBC Sho"
|
|
||||||
109 -> "Radio Nikkei Sho"
|
|
||||||
110 -> "Procyon S."
|
|
||||||
111 -> "Tanabata Sho"
|
|
||||||
112 -> "Hakodate Kinen"
|
|
||||||
113 -> "Chukyo Kinen"
|
|
||||||
114 -> "Hakodate Junior S."
|
|
||||||
115 -> "Ibis Summer D."
|
|
||||||
116 -> "Queen S."
|
|
||||||
117 -> "Kokura Kinen"
|
|
||||||
118 -> "Leopard S."
|
|
||||||
119 -> "Sekiya Kinen"
|
|
||||||
120 -> "Elm S."
|
|
||||||
121 -> "Kitakyushu Kinen"
|
|
||||||
122 -> "Niigata Junior S."
|
|
||||||
123 -> "Keeneland C."
|
|
||||||
124 -> "Sapporo Junior S."
|
|
||||||
125 -> "Kokura Junior S."
|
|
||||||
126 -> "Niigata Kinen"
|
|
||||||
127 -> "Shion S."
|
|
||||||
128 -> "Keisei Hai A.H."
|
|
||||||
129 -> "Sirius S."
|
|
||||||
130 -> "Saudi Arabia R.C."
|
|
||||||
131 -> "Fuji S."
|
|
||||||
132 -> "Artemis S."
|
|
||||||
133 -> "Fantasy S."
|
|
||||||
134 -> "Miyako S."
|
|
||||||
135 -> "Musashino S."
|
|
||||||
136 -> "Fukushima Kinen"
|
|
||||||
137 -> "Tokyo Sports Hai Junior S."
|
|
||||||
138 -> "Kyoto Junior S."
|
|
||||||
139 -> "Keihan Hai"
|
|
||||||
140 -> "Challenge C."
|
|
||||||
141 -> "Chunichi Shimbun Hai"
|
|
||||||
142 -> "Capella S."
|
|
||||||
143 -> "Turquoise S."
|
|
||||||
144 -> "Classic Triple Crown" ++ " (Alternate 144)"
|
|
||||||
145 -> "Senior Spring Triple Crown" ++ " (Alternate 145)"
|
|
||||||
146 -> "Dual Grand Prix" ++ " (Alternate 146)"
|
|
||||||
147 -> "Takarazuka Kinen" ++ " (Alternate 147)"
|
|
||||||
148 -> "Kikuka Sho" ++ " (Alternate 148)"
|
|
||||||
149 -> "Spring S." ++ " (Alternate 149)"
|
|
||||||
150 -> "Aoi S."
|
|
||||||
151 -> "Senior Spring Triple Crown" ++ " (Alternate 151)"
|
|
||||||
152 -> "Tenno Sweep" ++ " (Alternate 152)"
|
|
||||||
153 -> "Tenno Sho (Spring)" ++ " (Alternate 153)"
|
|
||||||
154 -> "Classic Triple Crown" ++ " (Alternate 154)"
|
|
||||||
155 -> "Satsuki Sho" ++ " (Alternate 155)"
|
|
||||||
x -> "saddle " ++ x.show
|
|
||||||
|
|
||||||
// Get the list of races that entitle a horse to a saddle.
|
|
||||||
// If no saddle matches the ID, the result is the empty list.
|
|
||||||
pub fun races(s: saddle-id): list<race-id>
|
|
||||||
match s.game-id
|
|
||||||
1 -> [Race-id(100501), Race-id(101001), Race-id(101501), ]
|
|
||||||
2 -> [Race-id(101601), Race-id(101901), Race-id(102301), ]
|
|
||||||
3 -> [Race-id(100401), Race-id(100901), Race-id(101401), ]
|
|
||||||
4 -> [Race-id(100301), Race-id(100601), Race-id(101201), ]
|
|
||||||
5 -> [Race-id(100601), Race-id(101601), ]
|
|
||||||
6 -> [Race-id(101201), Race-id(102301), ]
|
|
||||||
7 -> [Race-id(101101), Race-id(101801), ]
|
|
||||||
8 -> [Race-id(101301), Race-id(100201), ]
|
|
||||||
9 -> [Race-id(100101), Race-id(102001), ]
|
|
||||||
10 -> [Race-id(102301), ]
|
|
||||||
11 -> [Race-id(101901), ]
|
|
||||||
12 -> [Race-id(101001), ]
|
|
||||||
13 -> [Race-id(100601), ]
|
|
||||||
14 -> [Race-id(101201), ]
|
|
||||||
15 -> [Race-id(101601), ]
|
|
||||||
16 -> [Race-id(101501), ]
|
|
||||||
17 -> [Race-id(100301), ]
|
|
||||||
18 -> [Race-id(100501), ]
|
|
||||||
19 -> [Race-id(100901), ]
|
|
||||||
20 -> [Race-id(100201), ]
|
|
||||||
21 -> [Race-id(101101), ]
|
|
||||||
22 -> [Race-id(101301), ]
|
|
||||||
23 -> [Race-id(101801), ]
|
|
||||||
24 -> [Race-id(100401), ]
|
|
||||||
25 -> [Race-id(100801), ]
|
|
||||||
26 -> [Race-id(101701), ]
|
|
||||||
27 -> [Race-id(100701), ]
|
|
||||||
28 -> [Race-id(101401), ]
|
|
||||||
29 -> [Race-id(102001), ]
|
|
||||||
30 -> [Race-id(100101), ]
|
|
||||||
31 -> [Race-id(110501), ]
|
|
||||||
32 -> [Race-id(110601), ]
|
|
||||||
33 -> [Race-id(102201), ]
|
|
||||||
34 -> [Race-id(102401), ]
|
|
||||||
35 -> [Race-id(102101), ]
|
|
||||||
36 -> [Race-id(110101), ]
|
|
||||||
37 -> [Race-id(110401), ]
|
|
||||||
38 -> [Race-id(110201), ]
|
|
||||||
39 -> [Race-id(110301), ]
|
|
||||||
40 -> [Race-id(200101), ]
|
|
||||||
41 -> [Race-id(200201), ]
|
|
||||||
42 -> [Race-id(200301), ]
|
|
||||||
43 -> [Race-id(200401), ]
|
|
||||||
44 -> [Race-id(200501), ]
|
|
||||||
45 -> [Race-id(200601), ]
|
|
||||||
46 -> [Race-id(200701), ]
|
|
||||||
47 -> [Race-id(200801), ]
|
|
||||||
48 -> [Race-id(200901), ]
|
|
||||||
49 -> [Race-id(201001), ]
|
|
||||||
50 -> [Race-id(201101), ]
|
|
||||||
51 -> [Race-id(201201), ]
|
|
||||||
52 -> [Race-id(201301), ]
|
|
||||||
53 -> [Race-id(201401), ]
|
|
||||||
54 -> [Race-id(201501), ]
|
|
||||||
55 -> [Race-id(201601), ]
|
|
||||||
56 -> [Race-id(201701), ]
|
|
||||||
57 -> [Race-id(201801), ]
|
|
||||||
58 -> [Race-id(201901), ]
|
|
||||||
59 -> [Race-id(202001), ]
|
|
||||||
60 -> [Race-id(202101), ]
|
|
||||||
61 -> [Race-id(202201), ]
|
|
||||||
62 -> [Race-id(202301), ]
|
|
||||||
63 -> [Race-id(202401), ]
|
|
||||||
64 -> [Race-id(202501), ]
|
|
||||||
65 -> [Race-id(202601), ]
|
|
||||||
66 -> [Race-id(202701), ]
|
|
||||||
67 -> [Race-id(202801), ]
|
|
||||||
68 -> [Race-id(202901), ]
|
|
||||||
69 -> [Race-id(203001), ]
|
|
||||||
70 -> [Race-id(203101), ]
|
|
||||||
71 -> [Race-id(203201), ]
|
|
||||||
72 -> [Race-id(203301), ]
|
|
||||||
73 -> [Race-id(203401), ]
|
|
||||||
74 -> [Race-id(300101), ]
|
|
||||||
75 -> [Race-id(300201), ]
|
|
||||||
76 -> [Race-id(300301), ]
|
|
||||||
77 -> [Race-id(300401), ]
|
|
||||||
78 -> [Race-id(300501), ]
|
|
||||||
79 -> [Race-id(300601), ]
|
|
||||||
80 -> [Race-id(300701), ]
|
|
||||||
81 -> [Race-id(300801), ]
|
|
||||||
82 -> [Race-id(300901), ]
|
|
||||||
83 -> [Race-id(301001), ]
|
|
||||||
84 -> [Race-id(301101), ]
|
|
||||||
85 -> [Race-id(301201), ]
|
|
||||||
86 -> [Race-id(301301), ]
|
|
||||||
87 -> [Race-id(301401), ]
|
|
||||||
88 -> [Race-id(301501), ]
|
|
||||||
89 -> [Race-id(301601), ]
|
|
||||||
90 -> [Race-id(301701), ]
|
|
||||||
91 -> [Race-id(301801), ]
|
|
||||||
92 -> [Race-id(301901), ]
|
|
||||||
93 -> [Race-id(302001), ]
|
|
||||||
94 -> [Race-id(302101), ]
|
|
||||||
95 -> [Race-id(302201), ]
|
|
||||||
96 -> [Race-id(302301), ]
|
|
||||||
97 -> [Race-id(302401), ]
|
|
||||||
98 -> [Race-id(302501), ]
|
|
||||||
99 -> [Race-id(302601), ]
|
|
||||||
100 -> [Race-id(302701), ]
|
|
||||||
101 -> [Race-id(302801), ]
|
|
||||||
102 -> [Race-id(302901), ]
|
|
||||||
103 -> [Race-id(303001), ]
|
|
||||||
104 -> [Race-id(303101), ]
|
|
||||||
105 -> [Race-id(303201), ]
|
|
||||||
106 -> [Race-id(303301), ]
|
|
||||||
107 -> [Race-id(303401), ]
|
|
||||||
108 -> [Race-id(303501), ]
|
|
||||||
109 -> [Race-id(303601), ]
|
|
||||||
110 -> [Race-id(303701), ]
|
|
||||||
111 -> [Race-id(303801), ]
|
|
||||||
112 -> [Race-id(303901), ]
|
|
||||||
113 -> [Race-id(304001), ]
|
|
||||||
114 -> [Race-id(304101), ]
|
|
||||||
115 -> [Race-id(304201), ]
|
|
||||||
116 -> [Race-id(304301), ]
|
|
||||||
117 -> [Race-id(304401), ]
|
|
||||||
118 -> [Race-id(304501), ]
|
|
||||||
119 -> [Race-id(304601), ]
|
|
||||||
120 -> [Race-id(304701), ]
|
|
||||||
121 -> [Race-id(304801), ]
|
|
||||||
122 -> [Race-id(304901), ]
|
|
||||||
123 -> [Race-id(305001), ]
|
|
||||||
124 -> [Race-id(305101), ]
|
|
||||||
125 -> [Race-id(305201), ]
|
|
||||||
126 -> [Race-id(305301), ]
|
|
||||||
127 -> [Race-id(305401), ]
|
|
||||||
128 -> [Race-id(305501), ]
|
|
||||||
129 -> [Race-id(305601), ]
|
|
||||||
130 -> [Race-id(305701), ]
|
|
||||||
131 -> [Race-id(305801), ]
|
|
||||||
132 -> [Race-id(305901), ]
|
|
||||||
133 -> [Race-id(306001), ]
|
|
||||||
134 -> [Race-id(306101), ]
|
|
||||||
135 -> [Race-id(306201), ]
|
|
||||||
136 -> [Race-id(306301), ]
|
|
||||||
137 -> [Race-id(306401), ]
|
|
||||||
138 -> [Race-id(306501), ]
|
|
||||||
139 -> [Race-id(306601), ]
|
|
||||||
140 -> [Race-id(306701), ]
|
|
||||||
141 -> [Race-id(306801), ]
|
|
||||||
142 -> [Race-id(306901), ]
|
|
||||||
143 -> [Race-id(307001), ]
|
|
||||||
144 -> [Race-id(100501), Race-id(101001), Race-id(102601), ]
|
|
||||||
145 -> [Race-id(100301), Race-id(100601), Race-id(102501), ]
|
|
||||||
146 -> [Race-id(102501), Race-id(102301), ]
|
|
||||||
147 -> [Race-id(102501), ]
|
|
||||||
148 -> [Race-id(102601), ]
|
|
||||||
149 -> [Race-id(203501), ]
|
|
||||||
150 -> [Race-id(405001), ]
|
|
||||||
151 -> [Race-id(100301), Race-id(102701), Race-id(101201), ]
|
|
||||||
152 -> [Race-id(102701), Race-id(101601), ]
|
|
||||||
153 -> [Race-id(102701), ]
|
|
||||||
154 -> [Race-id(102801), Race-id(101001), Race-id(101501), ]
|
|
||||||
155 -> [Race-id(102801), ]
|
|
||||||
_ -> []
|
|
||||||
|
|
||||||
// Get a saddle's type.
|
|
||||||
// If no saddle matches the ID, the result is Honor.
|
|
||||||
pub fun saddle-type(s: saddle-id): saddle-type
|
|
||||||
match s.game-id
|
|
||||||
1 -> Honor
|
|
||||||
2 -> Honor
|
|
||||||
3 -> Honor
|
|
||||||
4 -> Honor
|
|
||||||
5 -> Honor
|
|
||||||
6 -> Honor
|
|
||||||
7 -> Honor
|
|
||||||
8 -> Honor
|
|
||||||
9 -> Honor
|
|
||||||
10 -> G1-Win
|
|
||||||
11 -> G1-Win
|
|
||||||
12 -> G1-Win
|
|
||||||
13 -> G1-Win
|
|
||||||
14 -> G1-Win
|
|
||||||
15 -> G1-Win
|
|
||||||
16 -> G1-Win
|
|
||||||
17 -> G1-Win
|
|
||||||
18 -> G1-Win
|
|
||||||
19 -> G1-Win
|
|
||||||
20 -> G1-Win
|
|
||||||
21 -> G1-Win
|
|
||||||
22 -> G1-Win
|
|
||||||
23 -> G1-Win
|
|
||||||
24 -> G1-Win
|
|
||||||
25 -> G1-Win
|
|
||||||
26 -> G1-Win
|
|
||||||
27 -> G1-Win
|
|
||||||
28 -> G1-Win
|
|
||||||
29 -> G1-Win
|
|
||||||
30 -> G1-Win
|
|
||||||
31 -> G1-Win
|
|
||||||
32 -> G1-Win
|
|
||||||
33 -> G1-Win
|
|
||||||
34 -> G1-Win
|
|
||||||
35 -> G1-Win
|
|
||||||
36 -> G1-Win
|
|
||||||
37 -> G1-Win
|
|
||||||
38 -> G1-Win
|
|
||||||
39 -> G1-Win
|
|
||||||
40 -> G2-Win
|
|
||||||
41 -> G2-Win
|
|
||||||
42 -> G2-Win
|
|
||||||
43 -> G2-Win
|
|
||||||
44 -> G2-Win
|
|
||||||
45 -> G2-Win
|
|
||||||
46 -> G2-Win
|
|
||||||
47 -> G2-Win
|
|
||||||
48 -> G2-Win
|
|
||||||
49 -> G2-Win
|
|
||||||
50 -> G2-Win
|
|
||||||
51 -> G2-Win
|
|
||||||
52 -> G2-Win
|
|
||||||
53 -> G2-Win
|
|
||||||
54 -> G2-Win
|
|
||||||
55 -> G2-Win
|
|
||||||
56 -> G2-Win
|
|
||||||
57 -> G2-Win
|
|
||||||
58 -> G2-Win
|
|
||||||
59 -> G2-Win
|
|
||||||
60 -> G2-Win
|
|
||||||
61 -> G2-Win
|
|
||||||
62 -> G2-Win
|
|
||||||
63 -> G2-Win
|
|
||||||
64 -> G2-Win
|
|
||||||
65 -> G2-Win
|
|
||||||
66 -> G2-Win
|
|
||||||
67 -> G2-Win
|
|
||||||
68 -> G2-Win
|
|
||||||
69 -> G2-Win
|
|
||||||
70 -> G2-Win
|
|
||||||
71 -> G2-Win
|
|
||||||
72 -> G2-Win
|
|
||||||
73 -> G2-Win
|
|
||||||
74 -> G3-Win
|
|
||||||
75 -> G3-Win
|
|
||||||
76 -> G3-Win
|
|
||||||
77 -> G3-Win
|
|
||||||
78 -> G3-Win
|
|
||||||
79 -> G3-Win
|
|
||||||
80 -> G3-Win
|
|
||||||
81 -> G3-Win
|
|
||||||
82 -> G3-Win
|
|
||||||
83 -> G3-Win
|
|
||||||
84 -> G3-Win
|
|
||||||
85 -> G3-Win
|
|
||||||
86 -> G3-Win
|
|
||||||
87 -> G3-Win
|
|
||||||
88 -> G3-Win
|
|
||||||
89 -> G3-Win
|
|
||||||
90 -> G3-Win
|
|
||||||
91 -> G2-Win
|
|
||||||
92 -> G3-Win
|
|
||||||
93 -> G3-Win
|
|
||||||
94 -> G3-Win
|
|
||||||
95 -> G3-Win
|
|
||||||
96 -> G3-Win
|
|
||||||
97 -> G3-Win
|
|
||||||
98 -> G3-Win
|
|
||||||
99 -> G3-Win
|
|
||||||
100 -> G3-Win
|
|
||||||
101 -> G3-Win
|
|
||||||
102 -> G3-Win
|
|
||||||
103 -> G3-Win
|
|
||||||
104 -> G3-Win
|
|
||||||
105 -> G3-Win
|
|
||||||
106 -> G3-Win
|
|
||||||
107 -> G3-Win
|
|
||||||
108 -> G3-Win
|
|
||||||
109 -> G3-Win
|
|
||||||
110 -> G3-Win
|
|
||||||
111 -> G3-Win
|
|
||||||
112 -> G3-Win
|
|
||||||
113 -> G3-Win
|
|
||||||
114 -> G3-Win
|
|
||||||
115 -> G3-Win
|
|
||||||
116 -> G3-Win
|
|
||||||
117 -> G3-Win
|
|
||||||
118 -> G3-Win
|
|
||||||
119 -> G3-Win
|
|
||||||
120 -> G3-Win
|
|
||||||
121 -> G3-Win
|
|
||||||
122 -> G3-Win
|
|
||||||
123 -> G3-Win
|
|
||||||
124 -> G3-Win
|
|
||||||
125 -> G3-Win
|
|
||||||
126 -> G3-Win
|
|
||||||
127 -> G3-Win
|
|
||||||
128 -> G3-Win
|
|
||||||
129 -> G3-Win
|
|
||||||
130 -> G3-Win
|
|
||||||
131 -> G2-Win
|
|
||||||
132 -> G3-Win
|
|
||||||
133 -> G3-Win
|
|
||||||
134 -> G3-Win
|
|
||||||
135 -> G3-Win
|
|
||||||
136 -> G3-Win
|
|
||||||
137 -> G3-Win
|
|
||||||
138 -> G3-Win
|
|
||||||
139 -> G3-Win
|
|
||||||
140 -> G3-Win
|
|
||||||
141 -> G3-Win
|
|
||||||
142 -> G3-Win
|
|
||||||
143 -> G3-Win
|
|
||||||
144 -> Honor
|
|
||||||
145 -> Honor
|
|
||||||
146 -> Honor
|
|
||||||
147 -> G1-Win
|
|
||||||
148 -> G1-Win
|
|
||||||
149 -> G2-Win
|
|
||||||
150 -> G3-Win
|
|
||||||
151 -> Honor
|
|
||||||
152 -> Honor
|
|
||||||
153 -> G1-Win
|
|
||||||
154 -> Honor
|
|
||||||
155 -> G1-Win
|
|
||||||
_ -> Honor
|
|
||||||
|
|
||||||
// Get the primary ID for a saddle.
|
|
||||||
// For saddles which are the primary version, or if no saddle matches the given ID,
|
|
||||||
// the result is the input.
|
|
||||||
pub fun primary(s: saddle-id): saddle-id
|
|
||||||
match s.game-id
|
|
||||||
144 -> Saddle-id(1)
|
|
||||||
145 -> Saddle-id(4)
|
|
||||||
146 -> Saddle-id(6)
|
|
||||||
147 -> Saddle-id(14)
|
|
||||||
148 -> Saddle-id(16)
|
|
||||||
149 -> Saddle-id(49)
|
|
||||||
151 -> Saddle-id(4)
|
|
||||||
152 -> Saddle-id(5)
|
|
||||||
153 -> Saddle-id(13)
|
|
||||||
154 -> Saddle-id(1)
|
|
||||||
155 -> Saddle-id(18)
|
|
||||||
_ -> s
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package global
|
|
||||||
|
|
||||||
// Automatically generated with horsegen; DO NOT EDIT
|
|
||||||
|
|
||||||
import . "git.sunturtle.xyz/zephyr/horse/horse"
|
|
||||||
|
|
||||||
const (
|
|
||||||
ScenarioURAFinale ScenarioID = 1 // URA Finale
|
|
||||||
ScenarioUnityCup ScenarioID = 2 // Unity Cup
|
|
||||||
)
|
|
||||||
|
|
||||||
var AllScenarios = map[ScenarioID]Scenario{
|
|
||||||
ScenarioURAFinale: {
|
|
||||||
ID: 1,
|
|
||||||
Name: "URA Finale",
|
|
||||||
Title: "The Beginning: URA Finale",
|
|
||||||
},
|
|
||||||
ScenarioUnityCup: {
|
|
||||||
ID: 2,
|
|
||||||
Name: "Unity Cup",
|
|
||||||
Title: "Unity Cup: Shine On, Team Spirit!",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
module horse/global/scenario
|
|
||||||
|
|
||||||
// Automatically generated with horsegen; DO NOT EDIT
|
|
||||||
|
|
||||||
import std/core/delayed
|
|
||||||
import std/core/vector
|
|
||||||
import std/core-extras
|
|
||||||
import horse/game-id
|
|
||||||
|
|
||||||
extern create-id-table(): vector<int>
|
|
||||||
c inline "int32_t arr[] = {1,2,};\nkk_vector_from_cint32array(arr, (kk_ssize_t)2, kk_context())"
|
|
||||||
js inline "[1,2,]"
|
|
||||||
// Vector of all scenario IDs in order for easy iterating.
|
|
||||||
pub val all = once(create-id-table)
|
|
||||||
|
|
||||||
// Get the name for a scenario.
|
|
||||||
// If no scenario matches the ID, the result contains the numeric ID.
|
|
||||||
pub fun show(s: scenario-id): string
|
|
||||||
match s.game-id
|
|
||||||
1 -> "URA Finale"
|
|
||||||
2 -> "Unity Cup"
|
|
||||||
x -> "scenario " ++ x.show
|
|
||||||
|
|
||||||
// Get the full title for a scenario, e.g. "The Beginning: URA Finale".
|
|
||||||
// If no scenario matches the ID, the result contains the numeric ID.
|
|
||||||
pub fun title(s: scenario-id): string
|
|
||||||
match s.game-id
|
|
||||||
1 -> "The Beginning: URA Finale"
|
|
||||||
2 -> "Unity Cup: Shine On, Team Spirit!"
|
|
||||||
x -> "scenario " ++ x.show
|
|
||||||
13794
horse/global/skill.go
13794
horse/global/skill.go
File diff suppressed because it is too large
Load Diff
14417
horse/global/skill.kk
14417
horse/global/skill.kk
File diff suppressed because it is too large
Load Diff
13268
horse/global/spark.go
13268
horse/global/spark.go
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
1711
horse/global/uma.go
1711
horse/global/uma.go
File diff suppressed because it is too large
Load Diff
1571
horse/global/uma.kk
1571
horse/global/uma.kk
File diff suppressed because it is too large
Load Diff
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) + ")"
|
||||||
|
}
|
||||||
@@ -4,26 +4,26 @@ type RaceID int32
|
|||||||
|
|
||||||
// Race is the internal data about a race.
|
// Race is the internal data about a race.
|
||||||
type Race struct {
|
type Race struct {
|
||||||
ID RaceID
|
ID RaceID `json:"race_id"`
|
||||||
Name string
|
Name string `json:"name"`
|
||||||
Thumbnail int
|
Thumbnail int `json:"thumbnail"`
|
||||||
// Some careers contain unusual versions of races, e.g. Tenno Sho (Spring)
|
// Some careers contain unusual versions of races, e.g. Tenno Sho (Spring)
|
||||||
// in Hanshin instead of Kyoto for Narita Taishin and Biwa Hayahide.
|
// in Hanshin instead of Kyoto for Narita Taishin and Biwa Hayahide.
|
||||||
// For such races, this field holds the normal race ID.
|
// For such races, this field holds the normal race ID.
|
||||||
Primary RaceID
|
Primary RaceID `json:"primary"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SaddleID int32
|
type SaddleID int32
|
||||||
|
|
||||||
// Saddle is the internal data about a race win saddle.
|
// Saddle is the internal data about a race win saddle.
|
||||||
type Saddle struct {
|
type Saddle struct {
|
||||||
ID SaddleID
|
ID SaddleID `json:"saddle_id"`
|
||||||
Name string
|
Name string `json:"name"`
|
||||||
Races []RaceID
|
Races []RaceID `json:"races"`
|
||||||
Type SaddleType
|
Type SaddleType `json:"type"`
|
||||||
// Saddles that involve alternate races are themselves alternate.
|
// Saddles that involve alternate races are themselves alternate.
|
||||||
// For such saddles, this field holds the normal saddle ID.
|
// For such saddles, this field holds the normal saddle ID.
|
||||||
Primary SaddleID
|
Primary SaddleID `json:"primary"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SaddleType int8
|
type SaddleType int8
|
||||||
@@ -40,7 +40,7 @@ type ScenarioID int8
|
|||||||
|
|
||||||
// Scenario is metadata about a career scenario.
|
// Scenario is metadata about a career scenario.
|
||||||
type Scenario struct {
|
type Scenario struct {
|
||||||
ID ScenarioID
|
ID ScenarioID `json:"scenario_id"`
|
||||||
Name string
|
Name string `json:"name"`
|
||||||
Title string
|
Title string `json:"title"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,37 +27,37 @@ func (x TenThousandths) String() string {
|
|||||||
|
|
||||||
// Skill is the internal data about a skill.
|
// Skill is the internal data about a skill.
|
||||||
type Skill struct {
|
type Skill struct {
|
||||||
ID SkillID
|
ID SkillID `json:"skill_id"`
|
||||||
Name string
|
Name string `json:"name"`
|
||||||
Description string
|
Description string `json:"description"`
|
||||||
Group int32
|
Group SkillGroupID `json:"group"`
|
||||||
Rarity int8
|
Rarity int8 `json:"rarity"`
|
||||||
GroupRate int8
|
GroupRate int8 `json:"group_rate"`
|
||||||
GradeValue int32
|
GradeValue int32 `json:"grade_value,omitzero"`
|
||||||
WitCheck bool
|
WitCheck bool `json:"wit_check"`
|
||||||
Activations []Activation
|
Activations []Activation `json:"activations"`
|
||||||
UniqueOwner string
|
UniqueOwner string `json:"unique_owner,omitzero"`
|
||||||
SPCost int
|
SPCost int `json:"sp_cost,omitzero"`
|
||||||
IconID int
|
IconID int `json:"icon_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activation is the parameters controlling when a skill activates.
|
// Activation is the parameters controlling when a skill activates.
|
||||||
type Activation struct {
|
type Activation struct {
|
||||||
Precondition string
|
Precondition string `json:"precondition,omitzero"`
|
||||||
Condition string
|
Condition string `json:"condition"`
|
||||||
Duration TenThousandths
|
Duration TenThousandths `json:"duration,omitzero"`
|
||||||
DurScale DurScale
|
DurScale DurScale `json:"dur_scale"`
|
||||||
Cooldown TenThousandths
|
Cooldown TenThousandths `json:"cooldown,omitzero"`
|
||||||
Abilities []Ability
|
Abilities []Ability `json:"abilities"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ability is an individual effect applied by a skill.
|
// Ability is an individual effect applied by a skill.
|
||||||
type Ability struct {
|
type Ability struct {
|
||||||
Type AbilityType
|
Type AbilityType `json:"type"`
|
||||||
ValueUsage AbilityValueUsage
|
ValueUsage AbilityValueUsage `json:"value_usage"`
|
||||||
Value TenThousandths
|
Value TenThousandths `json:"value"`
|
||||||
Target AbilityTarget
|
Target AbilityTarget `json:"target"`
|
||||||
TargetValue int32
|
TargetValue int32 `json:"target_value,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Ability) String() string {
|
func (a Ability) String() string {
|
||||||
@@ -202,3 +202,29 @@ const (
|
|||||||
TargetCharacter AbilityTarget = 22 // specific character
|
TargetCharacter AbilityTarget = 22 // specific character
|
||||||
TargetTriggering AbilityTarget = 23 // whosoever triggered this skill
|
TargetTriggering AbilityTarget = 23 // whosoever triggered this skill
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SkillGroupID int32
|
||||||
|
|
||||||
|
// SkillGroup is a group of skills which are alternate versions of each other.
|
||||||
|
//
|
||||||
|
// Any of the skill IDs in a group may be zero, indicating that there is no
|
||||||
|
// 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
|
||||||
|
// or an Uma's own unique.
|
||||||
|
Skill1 SkillID `json:"skill1,omitzero"`
|
||||||
|
// Skill2 is the first upgraded version of the skill: a rare (gold)
|
||||||
|
// skill, a double circle skill, or an inherited unique skill.
|
||||||
|
Skill2 SkillID `json:"skill2,omitzero"`
|
||||||
|
// Skill3 is the highest upgraded version, a gold version of a skill with
|
||||||
|
// a double circle version.
|
||||||
|
Skill3 SkillID `json:"skill3,omitzero"`
|
||||||
|
// SkillBad is a negative (purple) skill.
|
||||||
|
SkillBad SkillID `json:"skill_bad,omitzero"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,38 +1,11 @@
|
|||||||
package horse_test
|
package horse_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cmp"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.sunturtle.xyz/zephyr/horse/horse"
|
"git.sunturtle.xyz/zephyr/horse/horse"
|
||||||
"git.sunturtle.xyz/zephyr/horse/horse/global"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var SortedSkills = sync.OnceValue(func() []horse.Skill {
|
|
||||||
skills := make([]horse.Skill, 0, len(global.AllSkills))
|
|
||||||
for _, v := range global.AllSkills {
|
|
||||||
skills = append(skills, v)
|
|
||||||
}
|
|
||||||
slices.SortFunc(skills, func(a, b horse.Skill) int { return cmp.Compare(a.ID, b.ID) })
|
|
||||||
return skills
|
|
||||||
})
|
|
||||||
|
|
||||||
func TestSkillStrings(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
for _, s := range SortedSkills() {
|
|
||||||
for _, a := range s.Activations {
|
|
||||||
for _, abil := range a.Abilities {
|
|
||||||
if n := abil.Type.String(); strings.HasPrefix(n, "AbilityType(") {
|
|
||||||
t.Errorf("%v %s: %s", s.ID, s.Name, n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTenThousandthsString(t *testing.T) {
|
func TestTenThousandthsString(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Spark struct {
|
type Spark struct {
|
||||||
ID SparkID
|
ID SparkID `json:"spark_id"`
|
||||||
Name string
|
Name string `json:"name"`
|
||||||
Description string
|
Description string `json:"description"`
|
||||||
Group SparkGroupID
|
Group SparkGroupID `json:"spark_group"`
|
||||||
Rarity SparkRarity
|
Rarity SparkRarity `json:"rarity"`
|
||||||
Type SparkType
|
Type SparkType `json:"type"`
|
||||||
Effects [][]SparkEffect
|
Effects [][]SparkEffect `json:"effects"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SparkType int8
|
type SparkType int8
|
||||||
@@ -46,9 +46,9 @@ func (r SparkRarity) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SparkEffect struct {
|
type SparkEffect struct {
|
||||||
Target SparkTarget
|
Target SparkTarget `json:"target"`
|
||||||
Value1 int32
|
Value1 int32 `json:"value1,omitzero"`
|
||||||
Value2 int32
|
Value2 int32 `json:"value2,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SparkTarget int8
|
type SparkTarget int8
|
||||||
|
|||||||
32
horse/uma.go
32
horse/uma.go
@@ -3,18 +3,30 @@ package horse
|
|||||||
type UmaID int32
|
type UmaID int32
|
||||||
|
|
||||||
type Uma struct {
|
type Uma struct {
|
||||||
ID UmaID
|
ID UmaID `json:"chara_card_id"`
|
||||||
CharacterID CharacterID
|
CharacterID CharacterID `json:"chara_id"`
|
||||||
Name string
|
Name string `json:"name"`
|
||||||
Variant string
|
Variant string `json:"variant"`
|
||||||
|
|
||||||
Sprint, Mile, Medium, Long AptitudeLevel
|
Sprint AptitudeLevel `json:"sprint"`
|
||||||
Front, Pace, Late, End AptitudeLevel
|
Mile AptitudeLevel `json:"mile"`
|
||||||
Turf, Dirt AptitudeLevel
|
Medium AptitudeLevel `json:"medium"`
|
||||||
|
Long AptitudeLevel `json:"long"`
|
||||||
|
Front AptitudeLevel `json:"front"`
|
||||||
|
Pace AptitudeLevel `json:"pace"`
|
||||||
|
Late AptitudeLevel `json:"late"`
|
||||||
|
End AptitudeLevel `json:"end"`
|
||||||
|
Turf AptitudeLevel `json:"turf"`
|
||||||
|
Dirt AptitudeLevel `json:"dirt"`
|
||||||
|
|
||||||
Unique SkillID
|
Unique SkillID `json:"unique"`
|
||||||
Skill1, Skill2, Skill3 SkillID
|
Skill1 SkillID `json:"skill1"`
|
||||||
SkillPL2, SkillPL3, SkillPL4, SkillPL5 SkillID
|
Skill2 SkillID `json:"skill2"`
|
||||||
|
Skill3 SkillID `json:"skill3"`
|
||||||
|
SkillPL2 SkillID `json:"skill_pl2"`
|
||||||
|
SkillPL3 SkillID `json:"skill_pl3"`
|
||||||
|
SkillPL4 SkillID `json:"skill_pl4"`
|
||||||
|
SkillPL5 SkillID `json:"skill_pl5"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AptitudeLevel int8
|
type AptitudeLevel int8
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
# gen
|
|
||||||
|
|
||||||
Go tool to generate the Koka source code from the game's SQLite database.
|
|
||||||
|
|
||||||
Code is generated using Go templates.
|
|
||||||
Templates use a `kkenum` function which converts a name `Mr. C.B.` to a Koka enumerant name `Mr-CB`.
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
WITH uma_names AS (
|
|
||||||
SELECT
|
|
||||||
"index" AS "id",
|
|
||||||
"text" AS "name"
|
|
||||||
FROM text_data
|
|
||||||
WHERE category = 6 AND "index" BETWEEN 1000 AND 1999
|
|
||||||
-- Exclude characters who have no succession relations defined.
|
|
||||||
AND "index" IN (SELECT chara_id FROM succession_relation_member)
|
|
||||||
), pairs AS (
|
|
||||||
SELECT
|
|
||||||
a.id AS id_a,
|
|
||||||
a.name AS name_a,
|
|
||||||
b.id AS id_b,
|
|
||||||
b.name AS name_b
|
|
||||||
FROM uma_names a
|
|
||||||
JOIN uma_names b ON a.id != b.id -- exclude reflexive cases
|
|
||||||
), relation_pairs AS (
|
|
||||||
SELECT
|
|
||||||
ra.relation_type,
|
|
||||||
ra.chara_id AS id_a,
|
|
||||||
rb.chara_id AS id_b
|
|
||||||
FROM succession_relation_member ra
|
|
||||||
JOIN succession_relation_member rb ON ra.relation_type = rb.relation_type
|
|
||||||
), affinity AS (
|
|
||||||
SELECT
|
|
||||||
pairs.*,
|
|
||||||
SUM(IFNULL(relation_point, 0)) AS base_affinity
|
|
||||||
FROM pairs
|
|
||||||
LEFT JOIN relation_pairs rp ON pairs.id_a = rp.id_a AND pairs.id_b = rp.id_b
|
|
||||||
LEFT JOIN succession_relation sr ON rp.relation_type = sr.relation_type
|
|
||||||
GROUP BY pairs.id_a, pairs.id_b
|
|
||||||
|
|
||||||
UNION ALL
|
|
||||||
-- Reflexive cases.
|
|
||||||
SELECT
|
|
||||||
uma_names.id AS id_a,
|
|
||||||
uma_names.name AS name_a,
|
|
||||||
uma_names.id AS id_b,
|
|
||||||
uma_names.name AS name_b,
|
|
||||||
0 AS base_affinity
|
|
||||||
FROM uma_names
|
|
||||||
)
|
|
||||||
SELECT * FROM affinity
|
|
||||||
ORDER BY id_a, id_b
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
WITH uma_names AS (
|
|
||||||
SELECT
|
|
||||||
"index" AS "id",
|
|
||||||
"text" AS "name"
|
|
||||||
FROM text_data
|
|
||||||
WHERE category = 6 AND "index" BETWEEN 1000 AND 1999
|
|
||||||
-- Exclude characters who have no succession relations defined.
|
|
||||||
AND "index" IN (SELECT chara_id FROM succession_relation_member)
|
|
||||||
), trios AS (
|
|
||||||
SELECT
|
|
||||||
a.id AS id_a,
|
|
||||||
a.name AS name_a,
|
|
||||||
b.id AS id_b,
|
|
||||||
b.name AS name_b,
|
|
||||||
c.id AS id_c,
|
|
||||||
c.name AS name_c
|
|
||||||
FROM uma_names a
|
|
||||||
JOIN uma_names b ON a.id != b.id -- exclude pairwise reflexive cases
|
|
||||||
JOIN uma_names c ON a.id != c.id AND b.id != c.id
|
|
||||||
), relation_trios AS (
|
|
||||||
SELECT
|
|
||||||
ra.relation_type,
|
|
||||||
ra.chara_id AS id_a,
|
|
||||||
rb.chara_id AS id_b,
|
|
||||||
rc.chara_id AS id_c
|
|
||||||
FROM succession_relation_member ra
|
|
||||||
JOIN succession_relation_member rb ON ra.relation_type = rb.relation_type
|
|
||||||
JOIN succession_relation_member rc ON ra.relation_type = rc.relation_type
|
|
||||||
), affinity AS (
|
|
||||||
SELECT
|
|
||||||
trios.*,
|
|
||||||
SUM(IFNULL(relation_point, 0)) AS base_affinity
|
|
||||||
FROM trios
|
|
||||||
LEFT JOIN relation_trios rt ON trios.id_a = rt.id_a AND trios.id_b = rt.id_b AND trios.id_c = rt.id_c
|
|
||||||
LEFT JOIN succession_relation sr ON rt.relation_type = sr.relation_type
|
|
||||||
GROUP BY trios.id_a, trios.id_b, trios.id_c
|
|
||||||
|
|
||||||
UNION ALL
|
|
||||||
-- A = B = C
|
|
||||||
SELECT
|
|
||||||
n.id AS id_a,
|
|
||||||
n.name AS name_a,
|
|
||||||
n.id AS id_b,
|
|
||||||
n.name AS name_b,
|
|
||||||
n.id AS id_c,
|
|
||||||
n.name AS name_c,
|
|
||||||
0 AS base_affinity
|
|
||||||
FROM uma_names n
|
|
||||||
|
|
||||||
UNION ALL
|
|
||||||
-- A = B
|
|
||||||
SELECT
|
|
||||||
n.id AS id_a,
|
|
||||||
n.name AS name_a,
|
|
||||||
n.id AS id_a,
|
|
||||||
n.name AS id_b,
|
|
||||||
m.id AS id_c,
|
|
||||||
m.name AS name_c,
|
|
||||||
0 AS base_affinity
|
|
||||||
FROM uma_names n JOIN uma_names m ON n.id != m.id
|
|
||||||
|
|
||||||
UNION ALL
|
|
||||||
-- A = C
|
|
||||||
SELECT
|
|
||||||
n.id AS id_a,
|
|
||||||
n.name AS name_a,
|
|
||||||
m.id AS id_a,
|
|
||||||
m.name AS id_b,
|
|
||||||
n.id AS id_c,
|
|
||||||
n.name AS name_c,
|
|
||||||
0 AS base_affinity
|
|
||||||
FROM uma_names n JOIN uma_names m ON n.id != m.id
|
|
||||||
|
|
||||||
UNION ALL
|
|
||||||
-- B = C
|
|
||||||
SELECT
|
|
||||||
m.id AS id_a,
|
|
||||||
m.name AS name_a,
|
|
||||||
n.id AS id_a,
|
|
||||||
n.name AS id_b,
|
|
||||||
n.id AS id_c,
|
|
||||||
n.name AS name_c,
|
|
||||||
0 AS base_affinity
|
|
||||||
FROM uma_names n JOIN uma_names m ON n.id != m.id
|
|
||||||
)
|
|
||||||
SELECT * FROM affinity
|
|
||||||
ORDER BY id_a, id_b, id_c
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
{{ define "go-character" -}}
|
|
||||||
package {{ $.Region }}
|
|
||||||
|
|
||||||
// Automatically generated with horsegen; DO NOT EDIT
|
|
||||||
|
|
||||||
import . "git.sunturtle.xyz/zephyr/horse/horse"
|
|
||||||
|
|
||||||
const (
|
|
||||||
{{- range $c := $.Characters }}
|
|
||||||
Character{{ goenum $c.Name }} CharacterID = {{ $c.ID }} // {{ $c.Name }}
|
|
||||||
{{- end }}
|
|
||||||
)
|
|
||||||
|
|
||||||
var OrderedCharacters = [...]CharacterID{
|
|
||||||
{{- range $c := $.Characters }}
|
|
||||||
Character{{ goenum $c.Name }},
|
|
||||||
{{- end }}
|
|
||||||
}
|
|
||||||
|
|
||||||
var Characters = map[CharacterID]Character{
|
|
||||||
{{- range $c := $.Characters }}
|
|
||||||
Character{{ goenum $c.Name }}: {ID: {{ $c.ID }}, Name: {{ printf "%q" $c.Name -}} },
|
|
||||||
{{- end }}
|
|
||||||
}
|
|
||||||
|
|
||||||
var CharacterNameToID = map[string]CharacterID{
|
|
||||||
{{- range $c := $.Characters }}
|
|
||||||
{{ printf "%q" $c.Name }}: {{ $c.ID }},
|
|
||||||
{{- end }}
|
|
||||||
}
|
|
||||||
|
|
||||||
var pairAffinity = []int8{
|
|
||||||
{{- range $a := $.Characters -}}
|
|
||||||
{{- range $b := $.Characters -}}
|
|
||||||
{{- index $.PairMaps $a.ID $b.ID -}},
|
|
||||||
{{- end -}}
|
|
||||||
{{- end -}}
|
|
||||||
}
|
|
||||||
|
|
||||||
var trioAffinity = []int8{
|
|
||||||
{{- range $a := $.Characters -}}
|
|
||||||
{{- range $b := $.Characters -}}
|
|
||||||
{{- range $c := $.Characters -}}
|
|
||||||
{{- index $.TrioMaps $a.ID $b.ID $c.ID -}},
|
|
||||||
{{- end -}}
|
|
||||||
{{- end -}}
|
|
||||||
{{- end -}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func PairAffinity(a, b CharacterID) int {
|
|
||||||
if _, ok := Characters[a]; !ok {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
if _, ok := Characters[b]; !ok {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return int(pairAffinity[a*{{ $.CharaCount }} + b])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TrioAffinity(a, b, c CharacterID) int {
|
|
||||||
if _, ok := Characters[a]; !ok {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
if _, ok := Characters[b]; !ok {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
if _, ok := Characters[c]; !ok {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return int(trioAffinity[a*{{ $.CharaCount }}*{{ $.CharaCount }} + b*{{ $.CharaCount }} + c])
|
|
||||||
}
|
|
||||||
{{ end }}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
{{ define "koka-character" -}}
|
|
||||||
module horse/{{ $.Region }}/character
|
|
||||||
|
|
||||||
// Automatically generated with horsegen; DO NOT EDIT
|
|
||||||
|
|
||||||
import std/core/delayed
|
|
||||||
import std/core/vector
|
|
||||||
import std/core-extras
|
|
||||||
import std/data/rb-map
|
|
||||||
import horse/game-id
|
|
||||||
pub import horse/character
|
|
||||||
|
|
||||||
extern create-id-table(): vector<int>
|
|
||||||
c inline "int32_t arr[] = { {{- range $chara := $.Characters }}{{ $chara.ID }},{{ end -}} };\nkk_vector_from_cint32array(arr, (kk_ssize_t){{ $.CharaCount }}, kk_context())"
|
|
||||||
js inline "[{{ range $chara := $.Characters }}{{ $chara.ID }},{{ end }}]"
|
|
||||||
// Vector of all character ID values in order for easy iterating.
|
|
||||||
pub val all = once(create-id-table)
|
|
||||||
|
|
||||||
val name2id = once()
|
|
||||||
var m: rbmap<string, int> := empty()
|
|
||||||
all().foreach() fn(id) m := m.set(Character-id(id).show, id)
|
|
||||||
m
|
|
||||||
|
|
||||||
// Get the character ID that has the given exact name.
|
|
||||||
// If no character matches the name, the result is an invalid ID.
|
|
||||||
pub fun from-name(name: string): character-id
|
|
||||||
Character-id(name2id().rb-map/lookup(name).default(0))
|
|
||||||
|
|
||||||
// Get the name for a character.
|
|
||||||
// If no character matches the ID, the result is the numeric ID.
|
|
||||||
pub fun show(c: character-id): string
|
|
||||||
match c.game-id
|
|
||||||
{{- range $chara := $.Characters }}
|
|
||||||
{{ $chara.ID }} -> {{ printf "%q" $chara.Name }}
|
|
||||||
{{- end }}
|
|
||||||
x -> "character " ++ x.show
|
|
||||||
|
|
||||||
fun character/index(c: character-id): int
|
|
||||||
match c.game-id
|
|
||||||
{{- range $chara := $.Characters }}
|
|
||||||
{{ $chara.ID }} -> {{ $chara.Index }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> -99999999
|
|
||||||
|
|
||||||
// Create the table of all pair affinities.
|
|
||||||
// The affinity is the value at a.index*count + b.index.
|
|
||||||
extern global/create-pair-table(): vector<int>
|
|
||||||
c inline "int32_t arr[] = {
|
|
||||||
{{- range $a := $.Characters }}
|
|
||||||
{{- range $b := $.Characters }}
|
|
||||||
{{- index $.PairMaps $a.ID $b.ID }},
|
|
||||||
{{- end }}
|
|
||||||
{{- end -}}
|
|
||||||
};\nkk_vector_from_cint32array(arr, (kk_ssize_t){{ $.CharaCount }} * (kk_ssize_t){{ $.CharaCount }}, kk_context())"
|
|
||||||
js inline "[
|
|
||||||
{{- range $a := $.Characters }}
|
|
||||||
{{- range $b := $.Characters }}
|
|
||||||
{{- index $.PairMaps $a.ID $b.ID }},
|
|
||||||
{{- end }}
|
|
||||||
{{- end -}}
|
|
||||||
]"
|
|
||||||
val global/pair-table = global/create-pair-table()
|
|
||||||
|
|
||||||
// Base affinity between a pair using the global ruleset.
|
|
||||||
pub fun global/pair-affinity(a: character-id, b: character-id): int
|
|
||||||
global/pair-table.at(a.index * {{ $.CharaCount }} + b.index).default(0)
|
|
||||||
|
|
||||||
// Create the table of all trio affinities.
|
|
||||||
// The affinity is the value at a.index*count*count + b.index*count + c.index.
|
|
||||||
extern global/create-trio-table(): vector<int>
|
|
||||||
c inline "int32_t arr[] = {
|
|
||||||
{{- range $a := $.Characters }}
|
|
||||||
{{- range $b := $.Characters }}
|
|
||||||
{{- range $c := $.Characters }}
|
|
||||||
{{- index $.TrioMaps $a.ID $b.ID $c.ID }},
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end -}}
|
|
||||||
};\nkk_vector_from_cint32array(arr, (kk_ssize_t){{ $.CharaCount }} * (kk_ssize_t){{ $.CharaCount }} * (kk_ssize_t){{ $.CharaCount }}, kk_context())"
|
|
||||||
js inline "[
|
|
||||||
{{- range $a := $.Characters }}
|
|
||||||
{{- range $b := $.Characters }}
|
|
||||||
{{- range $c := $.Characters }}
|
|
||||||
{{- index $.TrioMaps $a.ID $b.ID $c.ID }},
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end -}}
|
|
||||||
]"
|
|
||||||
val global/trio-table = global/create-trio-table()
|
|
||||||
|
|
||||||
// Base affinity for a trio using the global ruleset.
|
|
||||||
pub fun global/trio-affinity(a: character-id, b: character-id, c: character-id): int
|
|
||||||
global/trio-table.at(a.index * {{ $.CharaCount }} * {{ $.CharaCount }} + b.index * {{ $.CharaCount }} + c.index).default(0)
|
|
||||||
{{ end }}
|
|
||||||
263
horsegen/gen.go
263
horsegen/gen.go
@@ -1,263 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed *.template
|
|
||||||
var templates embed.FS
|
|
||||||
|
|
||||||
// LoadTemplates sets up templates to render game data to source code.
|
|
||||||
func LoadTemplates() (*template.Template, error) {
|
|
||||||
t := template.New("root")
|
|
||||||
t.Funcs(template.FuncMap{
|
|
||||||
"kkenum": kkenum,
|
|
||||||
"goenum": goenum,
|
|
||||||
})
|
|
||||||
return t.ParseFS(templates, "*")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExecCharacter renders the Koka character module to kk and the Go character file to g.
|
|
||||||
// If either is nil, it is skipped.
|
|
||||||
func ExecCharacter(t *template.Template, region string, kk, g io.Writer, c []NamedID[Character], pairs, trios []AffinityRelation) error {
|
|
||||||
if len(pairs) != len(c)*len(c) {
|
|
||||||
return fmt.Errorf("there are %d pairs but there must be %d for %d characters", len(pairs), len(c)*len(c), len(c))
|
|
||||||
}
|
|
||||||
if len(trios) != len(c)*len(c)*len(c) {
|
|
||||||
return fmt.Errorf("there are %d trios but there must be %d for %d characters", len(trios), len(c)*len(c)*len(c), len(c))
|
|
||||||
}
|
|
||||||
|
|
||||||
maxid := 0
|
|
||||||
pm := make(map[int]map[int]int, len(c))
|
|
||||||
tm := make(map[int]map[int]map[int]int, len(c))
|
|
||||||
for _, u := range c {
|
|
||||||
maxid = max(maxid, u.ID)
|
|
||||||
pm[u.ID] = make(map[int]int, len(c))
|
|
||||||
tm[u.ID] = make(map[int]map[int]int, len(c))
|
|
||||||
for _, v := range c {
|
|
||||||
tm[u.ID][v.ID] = make(map[int]int, len(c))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, p := range pairs {
|
|
||||||
pm[p.IDA][p.IDB] = p.Affinity
|
|
||||||
}
|
|
||||||
for _, t := range trios {
|
|
||||||
tm[t.IDA][t.IDB][t.IDC] = t.Affinity
|
|
||||||
}
|
|
||||||
|
|
||||||
data := struct {
|
|
||||||
Region string
|
|
||||||
Characters []NamedID[Character]
|
|
||||||
Pairs []AffinityRelation
|
|
||||||
Trios []AffinityRelation
|
|
||||||
PairMaps map[int]map[int]int
|
|
||||||
TrioMaps map[int]map[int]map[int]int
|
|
||||||
CharaCount int
|
|
||||||
MaxID int
|
|
||||||
}{region, c, pairs, trios, pm, tm, len(c), maxid}
|
|
||||||
var err error
|
|
||||||
if kk != nil {
|
|
||||||
err = errors.Join(err, t.ExecuteTemplate(kk, "koka-character", &data))
|
|
||||||
}
|
|
||||||
if g != nil {
|
|
||||||
err = errors.Join(err, t.ExecuteTemplate(g, "go-character", &data))
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExecSkill(t *template.Template, region string, kk, g io.Writer, groups []NamedID[SkillGroup], skills []Skill) error {
|
|
||||||
m := make(map[int][]Skill, len(groups))
|
|
||||||
for _, t := range skills {
|
|
||||||
m[t.GroupID] = append(m[t.GroupID], t)
|
|
||||||
}
|
|
||||||
data := struct {
|
|
||||||
Region string
|
|
||||||
Groups []NamedID[SkillGroup]
|
|
||||||
Skills []Skill
|
|
||||||
Related map[int][]Skill
|
|
||||||
SkillCount int
|
|
||||||
}{region, groups, skills, m, len(skills)}
|
|
||||||
var err error
|
|
||||||
if kk != nil {
|
|
||||||
err = errors.Join(err, t.ExecuteTemplate(kk, "koka-skill", &data))
|
|
||||||
}
|
|
||||||
if g != nil {
|
|
||||||
err = errors.Join(err, t.ExecuteTemplate(g, "go-skill-data", &data))
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExecRace(t *template.Template, region string, kk, g io.Writer, races []Race) error {
|
|
||||||
data := struct {
|
|
||||||
Region string
|
|
||||||
Races []Race
|
|
||||||
RaceCount int
|
|
||||||
}{region, races, len(races)}
|
|
||||||
var err error
|
|
||||||
if kk != nil {
|
|
||||||
err = errors.Join(err, t.ExecuteTemplate(kk, "koka-race", &data))
|
|
||||||
}
|
|
||||||
if g != nil {
|
|
||||||
err = errors.Join(err, t.ExecuteTemplate(g, "go-race", &data))
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExecSaddle(t *template.Template, region string, kk, g io.Writer, saddles []Saddle) error {
|
|
||||||
data := struct {
|
|
||||||
Region string
|
|
||||||
Saddles []Saddle
|
|
||||||
SaddleCount int
|
|
||||||
}{region, saddles, len(saddles)}
|
|
||||||
var err error
|
|
||||||
if kk != nil {
|
|
||||||
err = errors.Join(err, t.ExecuteTemplate(kk, "koka-saddle", &data))
|
|
||||||
}
|
|
||||||
if g != nil {
|
|
||||||
err = errors.Join(err, t.ExecuteTemplate(g, "go-saddle", &data))
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExecScenario(t *template.Template, region string, kk, g io.Writer, scen []Scenario) error {
|
|
||||||
data := struct {
|
|
||||||
Region string
|
|
||||||
Scenarios []Scenario
|
|
||||||
ScenarioCount int
|
|
||||||
}{region, scen, len(scen)}
|
|
||||||
var err error
|
|
||||||
if kk != nil {
|
|
||||||
err = errors.Join(err, t.ExecuteTemplate(kk, "koka-scenario", &data))
|
|
||||||
}
|
|
||||||
if g != nil {
|
|
||||||
err = errors.Join(err, t.ExecuteTemplate(g, "go-scenario", &data))
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExecSparks(t *template.Template, region string, kk, g io.Writer, sparks []Spark, effects map[int]map[int][]SparkEffect) error {
|
|
||||||
data := struct {
|
|
||||||
Region string
|
|
||||||
Sparks []Spark
|
|
||||||
SparkEffects map[int]map[int][]SparkEffect
|
|
||||||
SparkCount int
|
|
||||||
}{region, sparks, effects, len(sparks)}
|
|
||||||
var err error
|
|
||||||
if kk != nil {
|
|
||||||
err = errors.Join(err, t.ExecuteTemplate(kk, "koka-spark", &data))
|
|
||||||
}
|
|
||||||
if g != nil {
|
|
||||||
err = errors.Join(err, t.ExecuteTemplate(g, "go-spark", &data))
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExecUmas(t *template.Template, region string, kk, g io.Writer, umas []Uma) error {
|
|
||||||
data := struct {
|
|
||||||
Region string
|
|
||||||
Umas []Uma
|
|
||||||
UmaCount int
|
|
||||||
}{region, umas, len(umas)}
|
|
||||||
var err error
|
|
||||||
if kk != nil {
|
|
||||||
err = errors.Join(err, t.ExecuteTemplate(kk, "koka-uma", &data))
|
|
||||||
}
|
|
||||||
if g != nil {
|
|
||||||
err = errors.Join(err, t.ExecuteTemplate(g, "go-uma", &data))
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
const wordSeps = " ,!?/-+();#○☆♡'=♪∀゚∴[]:"
|
|
||||||
|
|
||||||
var (
|
|
||||||
kkReplace = func() *strings.Replacer {
|
|
||||||
r := []string{
|
|
||||||
"Triple 7s", "Triple-Sevens", // hard to replace with the right thing automatically
|
|
||||||
"1,500,000 CC", "One-Million-CC",
|
|
||||||
"15,000,000 CC", "Fifteen-Million-CC",
|
|
||||||
"1st", "First",
|
|
||||||
"114th", "Hundred-Fourteenth",
|
|
||||||
"♡ 3D Nail Art", "Nail-Art",
|
|
||||||
".", "",
|
|
||||||
"\u2019", "",
|
|
||||||
"&", "-and-",
|
|
||||||
"'s", "s",
|
|
||||||
"ó", "o",
|
|
||||||
"∞", "Infinity",
|
|
||||||
"\u00d7", "x",
|
|
||||||
"◎", "Lv2",
|
|
||||||
}
|
|
||||||
for _, c := range wordSeps {
|
|
||||||
r = append(r, string(c), "-")
|
|
||||||
}
|
|
||||||
return strings.NewReplacer(r...)
|
|
||||||
}()
|
|
||||||
kkMultidash = regexp.MustCompile(`-+`)
|
|
||||||
kkDashNonletter = regexp.MustCompile(`-[^A-Za-z]`)
|
|
||||||
|
|
||||||
goReplace = func() *strings.Replacer {
|
|
||||||
r := []string{
|
|
||||||
"Triple 7s", "TripleSevens",
|
|
||||||
"1,500,000 CC", "OneMillionCC",
|
|
||||||
"15,000,000 CC", "FifteenMillionCC",
|
|
||||||
"1st", "First",
|
|
||||||
"♡ 3D Nail Art", "NailArt",
|
|
||||||
".", "",
|
|
||||||
"\u2019", "",
|
|
||||||
"&", "And",
|
|
||||||
"'s", "s",
|
|
||||||
"∞", "Infinity",
|
|
||||||
"\u00d7", "X",
|
|
||||||
"◎", "Lv2",
|
|
||||||
}
|
|
||||||
for _, c := range wordSeps {
|
|
||||||
r = append(r, string(c), "")
|
|
||||||
}
|
|
||||||
return strings.NewReplacer(r...)
|
|
||||||
}()
|
|
||||||
)
|
|
||||||
|
|
||||||
func kkenum(name string) string {
|
|
||||||
orig := name
|
|
||||||
name = kkReplace.Replace(name)
|
|
||||||
name = kkMultidash.ReplaceAllLiteralString(name, "-")
|
|
||||||
name = strings.Trim(name, "-")
|
|
||||||
if len(name) == 0 {
|
|
||||||
panic(fmt.Errorf("%q became empty as Koka enum variant", orig))
|
|
||||||
}
|
|
||||||
name = strings.ToUpper(name[:1]) + name[1:]
|
|
||||||
if !unicode.IsLetter(rune(name[0])) {
|
|
||||||
//lint:ignore ST1005 proper name
|
|
||||||
panic(fmt.Errorf("Koka enum variant %q (from %q) starts with a non-letter", name, orig))
|
|
||||||
}
|
|
||||||
for _, c := range name {
|
|
||||||
if c > 127 {
|
|
||||||
// Koka does not allow non-ASCII characters in source code.
|
|
||||||
// Don't proceed if we've missed one.
|
|
||||||
panic(fmt.Errorf("non-ASCII character %q (%[1]U) in Koka enum variant %q (from %q)", c, name, orig))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if kkDashNonletter.MatchString(name) {
|
|
||||||
panic(fmt.Errorf("non-letter character after a dash in Koka enum variant %q (from %q)", name, orig))
|
|
||||||
}
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
func goenum(name string) string {
|
|
||||||
// go names are a bit more lax, so we need fewer checks
|
|
||||||
orig := name
|
|
||||||
name = goReplace.Replace(name)
|
|
||||||
if len(name) == 0 {
|
|
||||||
panic(fmt.Errorf("%q became empty as Go enum variant", orig))
|
|
||||||
}
|
|
||||||
name = strings.ToUpper(name[:1]) + name[1:]
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
613
horsegen/load.go
613
horsegen/load.go
@@ -1,613 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
_ "embed"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"zombiezen.com/go/sqlite/sqlitex"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed character.sql
|
|
||||||
var characterSQL string
|
|
||||||
|
|
||||||
//go:embed character.affinity2.sql
|
|
||||||
var characterAffinity2SQL string
|
|
||||||
|
|
||||||
//go:embed character.affinity3.sql
|
|
||||||
var characterAffinity3SQL string
|
|
||||||
|
|
||||||
//go:embed uma.sql
|
|
||||||
var umaSQL string
|
|
||||||
|
|
||||||
//go:embed skill-group.sql
|
|
||||||
var skillGroupSQL string
|
|
||||||
|
|
||||||
//go:embed skill.sql
|
|
||||||
var skillSQL string
|
|
||||||
|
|
||||||
//go:embed race.sql
|
|
||||||
var raceSQL string
|
|
||||||
|
|
||||||
//go:embed saddle.sql
|
|
||||||
var saddleSQL string
|
|
||||||
|
|
||||||
//go:embed scenario.sql
|
|
||||||
var scenarioSQL string
|
|
||||||
|
|
||||||
//go:embed spark.sql
|
|
||||||
var sparkSQL string
|
|
||||||
|
|
||||||
//go:embed spark-effect.sql
|
|
||||||
var sparkEffectSQL string
|
|
||||||
|
|
||||||
type (
|
|
||||||
Character struct{}
|
|
||||||
SkillGroup struct{}
|
|
||||||
)
|
|
||||||
|
|
||||||
type NamedID[T any] struct {
|
|
||||||
// Disallow conversions between NamedID types.
|
|
||||||
_ [0]*T
|
|
||||||
|
|
||||||
ID int
|
|
||||||
Name string
|
|
||||||
// For internal use, the index of the identity, when it's needed.
|
|
||||||
// We don't show this in public API, but it lets us use vectors for lookups.
|
|
||||||
Index int
|
|
||||||
}
|
|
||||||
|
|
||||||
func Characters(ctx context.Context, db *sqlitex.Pool) ([]NamedID[Character], error) {
|
|
||||||
conn, err := db.Take(ctx)
|
|
||||||
defer db.Put(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't get connection for characters: %w", err)
|
|
||||||
}
|
|
||||||
stmt, _, err := conn.PrepareTransient(characterSQL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't prepare statement for characters: %w", err)
|
|
||||||
}
|
|
||||||
defer stmt.Finalize()
|
|
||||||
|
|
||||||
var r []NamedID[Character]
|
|
||||||
for {
|
|
||||||
ok, err := stmt.Step()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error stepping characters: %w", err)
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
c := NamedID[Character]{
|
|
||||||
ID: stmt.ColumnInt(0),
|
|
||||||
Name: stmt.ColumnText(1),
|
|
||||||
Index: stmt.ColumnInt(2),
|
|
||||||
}
|
|
||||||
r = append(r, c)
|
|
||||||
}
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type AffinityRelation struct {
|
|
||||||
IDA int
|
|
||||||
NameA string
|
|
||||||
IDB int
|
|
||||||
NameB string
|
|
||||||
IDC int
|
|
||||||
NameC string
|
|
||||||
Affinity int
|
|
||||||
}
|
|
||||||
|
|
||||||
func CharacterPairs(ctx context.Context, db *sqlitex.Pool) ([]AffinityRelation, error) {
|
|
||||||
conn, err := db.Take(ctx)
|
|
||||||
defer db.Put(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't get connection for character pairs: %w", err)
|
|
||||||
}
|
|
||||||
stmt, _, err := conn.PrepareTransient(characterAffinity2SQL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't prepare statement for character pairs: %w", err)
|
|
||||||
}
|
|
||||||
defer stmt.Finalize()
|
|
||||||
|
|
||||||
var r []AffinityRelation
|
|
||||||
for {
|
|
||||||
ok, err := stmt.Step()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error stepping character pairs: %w", err)
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
p := AffinityRelation{
|
|
||||||
IDA: stmt.ColumnInt(0),
|
|
||||||
NameA: stmt.ColumnText(1),
|
|
||||||
IDB: stmt.ColumnInt(2),
|
|
||||||
NameB: stmt.ColumnText(3),
|
|
||||||
Affinity: stmt.ColumnInt(4),
|
|
||||||
}
|
|
||||||
r = append(r, p)
|
|
||||||
}
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CharacterTrios(ctx context.Context, db *sqlitex.Pool) ([]AffinityRelation, error) {
|
|
||||||
conn, err := db.Take(ctx)
|
|
||||||
defer db.Put(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't get connection for character trios: %w", err)
|
|
||||||
}
|
|
||||||
stmt, _, err := conn.PrepareTransient(characterAffinity3SQL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't prepare statement for character trios: %w", err)
|
|
||||||
}
|
|
||||||
defer stmt.Finalize()
|
|
||||||
|
|
||||||
var r []AffinityRelation
|
|
||||||
for {
|
|
||||||
ok, err := stmt.Step()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error stepping character trios: %w", err)
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
p := AffinityRelation{
|
|
||||||
IDA: stmt.ColumnInt(0),
|
|
||||||
NameA: stmt.ColumnText(1),
|
|
||||||
IDB: stmt.ColumnInt(2),
|
|
||||||
NameB: stmt.ColumnText(3),
|
|
||||||
IDC: stmt.ColumnInt(4),
|
|
||||||
NameC: stmt.ColumnText(5),
|
|
||||||
Affinity: stmt.ColumnInt(6),
|
|
||||||
}
|
|
||||||
r = append(r, p)
|
|
||||||
}
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SkillGroups(ctx context.Context, db *sqlitex.Pool) ([]NamedID[SkillGroup], error) {
|
|
||||||
conn, err := db.Take(ctx)
|
|
||||||
defer db.Put(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't get connection for skill groups: %w", err)
|
|
||||||
}
|
|
||||||
stmt, _, err := conn.PrepareTransient(skillGroupSQL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't prepare statement for skill groups: %w", err)
|
|
||||||
}
|
|
||||||
defer stmt.Finalize()
|
|
||||||
|
|
||||||
var r []NamedID[SkillGroup]
|
|
||||||
for {
|
|
||||||
ok, err := stmt.Step()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error stepping skill groups: %w", err)
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
g := NamedID[SkillGroup]{
|
|
||||||
ID: stmt.ColumnInt(0),
|
|
||||||
Name: stmt.ColumnText(1),
|
|
||||||
}
|
|
||||||
r = append(r, g)
|
|
||||||
}
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Skill struct {
|
|
||||||
ID int
|
|
||||||
Name string
|
|
||||||
Description string
|
|
||||||
GroupID int
|
|
||||||
GroupName string
|
|
||||||
Rarity int
|
|
||||||
GroupRate int
|
|
||||||
GradeValue int
|
|
||||||
WitCheck bool
|
|
||||||
Activations [2]SkillActivation
|
|
||||||
SPCost int
|
|
||||||
InheritID int
|
|
||||||
UniqueOwnerID int
|
|
||||||
UniqueOwner string
|
|
||||||
IconID int
|
|
||||||
Index int
|
|
||||||
}
|
|
||||||
|
|
||||||
type SkillActivation struct {
|
|
||||||
Precondition string
|
|
||||||
Condition string
|
|
||||||
Duration int
|
|
||||||
DurScale int
|
|
||||||
Cooldown int
|
|
||||||
Abilities [3]SkillAbility
|
|
||||||
}
|
|
||||||
|
|
||||||
type SkillAbility struct {
|
|
||||||
Type int
|
|
||||||
ValueUsage int
|
|
||||||
Value int
|
|
||||||
Target int
|
|
||||||
TargetValue int
|
|
||||||
}
|
|
||||||
|
|
||||||
func Skills(ctx context.Context, db *sqlitex.Pool) ([]Skill, error) {
|
|
||||||
conn, err := db.Take(ctx)
|
|
||||||
defer db.Put(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't get connection for skills: %w", err)
|
|
||||||
}
|
|
||||||
stmt, _, err := conn.PrepareTransient(skillSQL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't prepare statement for skills: %w", err)
|
|
||||||
}
|
|
||||||
defer stmt.Finalize()
|
|
||||||
|
|
||||||
var r []Skill
|
|
||||||
for {
|
|
||||||
ok, err := stmt.Step()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error stepping skills: %w", err)
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
s := Skill{
|
|
||||||
ID: stmt.ColumnInt(0),
|
|
||||||
Name: stmt.ColumnText(1),
|
|
||||||
Description: stmt.ColumnText(2),
|
|
||||||
GroupID: stmt.ColumnInt(3),
|
|
||||||
GroupName: stmt.ColumnText(4),
|
|
||||||
Rarity: stmt.ColumnInt(5),
|
|
||||||
GroupRate: stmt.ColumnInt(6),
|
|
||||||
GradeValue: stmt.ColumnInt(7),
|
|
||||||
WitCheck: stmt.ColumnInt(8) != 0,
|
|
||||||
Activations: [2]SkillActivation{
|
|
||||||
{
|
|
||||||
Precondition: stmt.ColumnText(9),
|
|
||||||
Condition: stmt.ColumnText(10),
|
|
||||||
Duration: stmt.ColumnInt(11),
|
|
||||||
DurScale: stmt.ColumnInt(12),
|
|
||||||
Cooldown: stmt.ColumnInt(13),
|
|
||||||
Abilities: [3]SkillAbility{
|
|
||||||
{
|
|
||||||
Type: stmt.ColumnInt(14),
|
|
||||||
ValueUsage: stmt.ColumnInt(15),
|
|
||||||
Value: stmt.ColumnInt(16),
|
|
||||||
Target: stmt.ColumnInt(17),
|
|
||||||
TargetValue: stmt.ColumnInt(18),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: stmt.ColumnInt(19),
|
|
||||||
ValueUsage: stmt.ColumnInt(20),
|
|
||||||
Value: stmt.ColumnInt(21),
|
|
||||||
Target: stmt.ColumnInt(22),
|
|
||||||
TargetValue: stmt.ColumnInt(23),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: stmt.ColumnInt(24),
|
|
||||||
ValueUsage: stmt.ColumnInt(25),
|
|
||||||
Value: stmt.ColumnInt(26),
|
|
||||||
Target: stmt.ColumnInt(27),
|
|
||||||
TargetValue: stmt.ColumnInt(28),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Precondition: stmt.ColumnText(29),
|
|
||||||
Condition: stmt.ColumnText(30),
|
|
||||||
Duration: stmt.ColumnInt(31),
|
|
||||||
DurScale: stmt.ColumnInt(32),
|
|
||||||
Cooldown: stmt.ColumnInt(33),
|
|
||||||
Abilities: [3]SkillAbility{
|
|
||||||
{
|
|
||||||
Type: stmt.ColumnInt(34),
|
|
||||||
ValueUsage: stmt.ColumnInt(35),
|
|
||||||
Value: stmt.ColumnInt(36),
|
|
||||||
Target: stmt.ColumnInt(37),
|
|
||||||
TargetValue: stmt.ColumnInt(38),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: stmt.ColumnInt(39),
|
|
||||||
ValueUsage: stmt.ColumnInt(40),
|
|
||||||
Value: stmt.ColumnInt(41),
|
|
||||||
Target: stmt.ColumnInt(42),
|
|
||||||
TargetValue: stmt.ColumnInt(43),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: stmt.ColumnInt(44),
|
|
||||||
ValueUsage: stmt.ColumnInt(45),
|
|
||||||
Value: stmt.ColumnInt(46),
|
|
||||||
Target: stmt.ColumnInt(47),
|
|
||||||
TargetValue: stmt.ColumnInt(48),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
SPCost: stmt.ColumnInt(49),
|
|
||||||
InheritID: stmt.ColumnInt(50),
|
|
||||||
UniqueOwnerID: stmt.ColumnInt(51),
|
|
||||||
UniqueOwner: stmt.ColumnText(52),
|
|
||||||
IconID: stmt.ColumnInt(53),
|
|
||||||
Index: stmt.ColumnInt(54),
|
|
||||||
}
|
|
||||||
r = append(r, s)
|
|
||||||
}
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Race struct {
|
|
||||||
ID int
|
|
||||||
Name string
|
|
||||||
Grade int
|
|
||||||
ThumbnailID int
|
|
||||||
Primary int
|
|
||||||
Alternate int
|
|
||||||
}
|
|
||||||
|
|
||||||
func Races(ctx context.Context, db *sqlitex.Pool) ([]Race, error) {
|
|
||||||
conn, err := db.Take(ctx)
|
|
||||||
defer db.Put(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't get connection for races: %w", err)
|
|
||||||
}
|
|
||||||
stmt, _, err := conn.PrepareTransient(raceSQL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't prepare statement for races: %w", err)
|
|
||||||
}
|
|
||||||
defer stmt.Finalize()
|
|
||||||
|
|
||||||
var r []Race
|
|
||||||
for {
|
|
||||||
ok, err := stmt.Step()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error stepping races: %w", err)
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
race := Race{
|
|
||||||
ID: stmt.ColumnInt(0),
|
|
||||||
Name: stmt.ColumnText(1),
|
|
||||||
Grade: stmt.ColumnInt(2),
|
|
||||||
ThumbnailID: stmt.ColumnInt(3),
|
|
||||||
Primary: stmt.ColumnInt(4),
|
|
||||||
Alternate: stmt.ColumnInt(5),
|
|
||||||
}
|
|
||||||
r = append(r, race)
|
|
||||||
}
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Saddle struct {
|
|
||||||
ID int
|
|
||||||
Name string
|
|
||||||
Races [3]int
|
|
||||||
Type int
|
|
||||||
Primary int
|
|
||||||
Alternate int
|
|
||||||
}
|
|
||||||
|
|
||||||
func Saddles(ctx context.Context, db *sqlitex.Pool) ([]Saddle, error) {
|
|
||||||
conn, err := db.Take(ctx)
|
|
||||||
defer db.Put(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't get connection for saddles: %w", err)
|
|
||||||
}
|
|
||||||
stmt, _, err := conn.PrepareTransient(saddleSQL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't prepare statement for saddles: %w", err)
|
|
||||||
}
|
|
||||||
defer stmt.Finalize()
|
|
||||||
|
|
||||||
var r []Saddle
|
|
||||||
for {
|
|
||||||
ok, err := stmt.Step()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error stepping saddles: %w", err)
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
s := Saddle{
|
|
||||||
ID: stmt.ColumnInt(0),
|
|
||||||
Name: stmt.ColumnText(1),
|
|
||||||
Races: [3]int{stmt.ColumnInt(2), stmt.ColumnInt(3), stmt.ColumnInt(4)},
|
|
||||||
Type: stmt.ColumnInt(5),
|
|
||||||
Primary: stmt.ColumnInt(6),
|
|
||||||
Alternate: stmt.ColumnInt(7),
|
|
||||||
}
|
|
||||||
r = append(r, s)
|
|
||||||
}
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Scenario struct {
|
|
||||||
ID int
|
|
||||||
Name string
|
|
||||||
Title string
|
|
||||||
}
|
|
||||||
|
|
||||||
func Scenarios(ctx context.Context, db *sqlitex.Pool) ([]Scenario, error) {
|
|
||||||
conn, err := db.Take(ctx)
|
|
||||||
defer db.Put(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't get connection for scenario: %w", err)
|
|
||||||
}
|
|
||||||
stmt, _, err := conn.PrepareTransient(scenarioSQL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't prepare statement for scenario: %w", err)
|
|
||||||
}
|
|
||||||
defer stmt.Finalize()
|
|
||||||
|
|
||||||
var r []Scenario
|
|
||||||
for {
|
|
||||||
ok, err := stmt.Step()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error stepping scenarios: %w", err)
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
s := Scenario{
|
|
||||||
ID: stmt.ColumnInt(0),
|
|
||||||
Name: stmt.ColumnText(1),
|
|
||||||
Title: stmt.ColumnText(2),
|
|
||||||
}
|
|
||||||
r = append(r, s)
|
|
||||||
}
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Spark struct {
|
|
||||||
ID int
|
|
||||||
Name string
|
|
||||||
Description string
|
|
||||||
Group int
|
|
||||||
Rarity int
|
|
||||||
Type int
|
|
||||||
}
|
|
||||||
|
|
||||||
type SparkEffect struct {
|
|
||||||
Target int
|
|
||||||
Value1 int
|
|
||||||
Value2 int
|
|
||||||
}
|
|
||||||
|
|
||||||
func Sparks(ctx context.Context, db *sqlitex.Pool) ([]Spark, error) {
|
|
||||||
conn, err := db.Take(ctx)
|
|
||||||
defer db.Put(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't get connection for sparks: %w", err)
|
|
||||||
}
|
|
||||||
stmt, _, err := conn.PrepareTransient(sparkSQL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't prepare statement for sparks: %w", err)
|
|
||||||
}
|
|
||||||
defer stmt.Finalize()
|
|
||||||
|
|
||||||
var r []Spark
|
|
||||||
for {
|
|
||||||
ok, err := stmt.Step()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error stepping sparks: %w", err)
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
s := Spark{
|
|
||||||
ID: stmt.ColumnInt(0),
|
|
||||||
Name: stmt.ColumnText(1),
|
|
||||||
Description: stmt.ColumnText(2),
|
|
||||||
Group: stmt.ColumnInt(3),
|
|
||||||
Rarity: stmt.ColumnInt(4),
|
|
||||||
Type: stmt.ColumnInt(5),
|
|
||||||
}
|
|
||||||
r = append(r, s)
|
|
||||||
}
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SparkEffects(ctx context.Context, db *sqlitex.Pool) (map[int]map[int][]SparkEffect, error) {
|
|
||||||
conn, err := db.Take(ctx)
|
|
||||||
defer db.Put(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't get connection for spark effects: %w", err)
|
|
||||||
}
|
|
||||||
stmt, _, err := conn.PrepareTransient(sparkEffectSQL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't prepare statement for spark effects: %w", err)
|
|
||||||
}
|
|
||||||
defer stmt.Finalize()
|
|
||||||
|
|
||||||
r := make(map[int]map[int][]SparkEffect)
|
|
||||||
for {
|
|
||||||
ok, err := stmt.Step()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error stepping spark effects: %w", err)
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
group := stmt.ColumnInt(0)
|
|
||||||
eff := stmt.ColumnInt(1)
|
|
||||||
s := SparkEffect{
|
|
||||||
Target: stmt.ColumnInt(2),
|
|
||||||
Value1: stmt.ColumnInt(3),
|
|
||||||
Value2: stmt.ColumnInt(4),
|
|
||||||
}
|
|
||||||
if r[group] == nil {
|
|
||||||
r[group] = make(map[int][]SparkEffect)
|
|
||||||
}
|
|
||||||
r[group][eff] = append(r[group][eff], s)
|
|
||||||
}
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Uma struct {
|
|
||||||
ID int
|
|
||||||
CharacterID int
|
|
||||||
Name string
|
|
||||||
Variant string
|
|
||||||
CharacterName string
|
|
||||||
|
|
||||||
Sprint, Mile, Medium, Long int
|
|
||||||
Front, Pace, Late, End int
|
|
||||||
Turf, Dirt int
|
|
||||||
|
|
||||||
UniqueID int
|
|
||||||
Skill1, Skill2, Skill3 int
|
|
||||||
SkillPL2, SkillPL3, SkillPL4, SkillPL5 int
|
|
||||||
}
|
|
||||||
|
|
||||||
func Umas(ctx context.Context, db *sqlitex.Pool) ([]Uma, error) {
|
|
||||||
conn, err := db.Take(ctx)
|
|
||||||
defer db.Put(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't get connection for umas: %w", err)
|
|
||||||
}
|
|
||||||
stmt, _, err := conn.PrepareTransient(umaSQL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't prepare statement for umas: %w", err)
|
|
||||||
}
|
|
||||||
defer stmt.Finalize()
|
|
||||||
|
|
||||||
var r []Uma
|
|
||||||
for {
|
|
||||||
ok, err := stmt.Step()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error stepping umas: %w", err)
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
uma := Uma{
|
|
||||||
ID: stmt.ColumnInt(0),
|
|
||||||
CharacterID: stmt.ColumnInt(1),
|
|
||||||
Name: stmt.ColumnText(2),
|
|
||||||
Variant: stmt.ColumnText(3),
|
|
||||||
CharacterName: stmt.ColumnText(4),
|
|
||||||
Sprint: stmt.ColumnInt(5),
|
|
||||||
Mile: stmt.ColumnInt(6),
|
|
||||||
Medium: stmt.ColumnInt(7),
|
|
||||||
Long: stmt.ColumnInt(8),
|
|
||||||
Front: stmt.ColumnInt(9),
|
|
||||||
Pace: stmt.ColumnInt(10),
|
|
||||||
Late: stmt.ColumnInt(11),
|
|
||||||
End: stmt.ColumnInt(12),
|
|
||||||
Turf: stmt.ColumnInt(13),
|
|
||||||
Dirt: stmt.ColumnInt(14),
|
|
||||||
UniqueID: stmt.ColumnInt(15),
|
|
||||||
Skill1: stmt.ColumnInt(16),
|
|
||||||
Skill2: stmt.ColumnInt(17),
|
|
||||||
Skill3: stmt.ColumnInt(18),
|
|
||||||
SkillPL2: stmt.ColumnInt(19),
|
|
||||||
SkillPL3: stmt.ColumnInt(20),
|
|
||||||
SkillPL4: stmt.ColumnInt(21),
|
|
||||||
SkillPL5: stmt.ColumnInt(22),
|
|
||||||
}
|
|
||||||
r = append(r, uma)
|
|
||||||
}
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
227
horsegen/main.go
227
horsegen/main.go
@@ -1,227 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"flag"
|
|
||||||
"log/slog"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
"zombiezen.com/go/sqlite"
|
|
||||||
"zombiezen.com/go/sqlite/sqlitex"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var (
|
|
||||||
mdb string
|
|
||||||
out string
|
|
||||||
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(®ion, "region", "global", "region the database is for (global, jp)")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
pctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
|
|
||||||
go func() {
|
|
||||||
<-pctx.Done()
|
|
||||||
stop()
|
|
||||||
}()
|
|
||||||
|
|
||||||
t, err := LoadTemplates()
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("loading templates", slog.Any("err", err))
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("open", slog.String("mdb", mdb))
|
|
||||||
db, err := sqlitex.NewPool(mdb, sqlitex.PoolOptions{Flags: sqlite.OpenReadOnly})
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("opening mdb", slog.String("mdb", mdb), slog.Any("err", err))
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
eg, ctx := errgroup.WithContext(pctx)
|
|
||||||
var (
|
|
||||||
charas []NamedID[Character]
|
|
||||||
pairs []AffinityRelation
|
|
||||||
trios []AffinityRelation
|
|
||||||
sg []NamedID[SkillGroup]
|
|
||||||
skills []Skill
|
|
||||||
races []Race
|
|
||||||
saddles []Saddle
|
|
||||||
scens []Scenario
|
|
||||||
sparks []Spark
|
|
||||||
sparkeff map[int]map[int][]SparkEffect
|
|
||||||
umas []Uma
|
|
||||||
)
|
|
||||||
eg.Go(func() error {
|
|
||||||
slog.Info("get characters")
|
|
||||||
r, err := Characters(ctx, db)
|
|
||||||
charas = r
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
eg.Go(func() error {
|
|
||||||
slog.Info("get pairs")
|
|
||||||
r, err := CharacterPairs(ctx, db)
|
|
||||||
pairs = r
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
eg.Go(func() error {
|
|
||||||
slog.Info("get trios")
|
|
||||||
r, err := CharacterTrios(ctx, db)
|
|
||||||
trios = r
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
eg.Go(func() error {
|
|
||||||
slog.Info("get skill groups")
|
|
||||||
r, err := SkillGroups(ctx, db)
|
|
||||||
sg = r
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
eg.Go(func() error {
|
|
||||||
slog.Info("get skills")
|
|
||||||
r, err := Skills(ctx, db)
|
|
||||||
skills = r
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
eg.Go(func() error {
|
|
||||||
slog.Info("get races")
|
|
||||||
r, err := Races(ctx, db)
|
|
||||||
races = r
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
eg.Go(func() error {
|
|
||||||
slog.Info("get saddles")
|
|
||||||
r, err := Saddles(ctx, db)
|
|
||||||
saddles = r
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
eg.Go(func() error {
|
|
||||||
slog.Info("get scenarios")
|
|
||||||
r, err := Scenarios(ctx, db)
|
|
||||||
scens = r
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
eg.Go(func() error {
|
|
||||||
slog.Info("get sparks")
|
|
||||||
r, err := Sparks(ctx, db)
|
|
||||||
sparks = r
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
eg.Go(func() error {
|
|
||||||
slog.Info("get spark effects")
|
|
||||||
r, err := SparkEffects(ctx, db)
|
|
||||||
sparkeff = r
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
eg.Go(func() error {
|
|
||||||
slog.Info("get umas")
|
|
||||||
r, err := Umas(ctx, db)
|
|
||||||
umas = r
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err := eg.Wait(); err != nil {
|
|
||||||
slog.Error("load", slog.Any("err", err))
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Join(out, region), 0775); err != nil {
|
|
||||||
slog.Error("create output dir", slog.Any("err", err))
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
eg, ctx = errgroup.WithContext(pctx)
|
|
||||||
eg.Go(func() error {
|
|
||||||
cf, err := os.Create(filepath.Join(out, region, "character.kk"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
gf, err := os.Create(filepath.Join(out, region, "character.go"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
slog.Info("write characters")
|
|
||||||
return ExecCharacter(t, region, cf, gf, charas, pairs, trios)
|
|
||||||
})
|
|
||||||
eg.Go(func() error {
|
|
||||||
sf, err := os.Create(filepath.Join(out, region, "skill.kk"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
gf, err := os.Create(filepath.Join(out, region, "skill.go"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
slog.Info("write skills")
|
|
||||||
return ExecSkill(t, region, sf, gf, sg, skills)
|
|
||||||
})
|
|
||||||
eg.Go(func() error {
|
|
||||||
kf, err := os.Create(filepath.Join(out, region, "race.kk"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
gf, err := os.Create(filepath.Join(out, region, "race.go"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
slog.Info("write races")
|
|
||||||
return ExecRace(t, region, kf, gf, races)
|
|
||||||
})
|
|
||||||
eg.Go(func() error {
|
|
||||||
kf, err := os.Create(filepath.Join(out, region, "saddle.kk"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
gf, err := os.Create(filepath.Join(out, region, "saddle.go"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
slog.Info("write saddles")
|
|
||||||
return ExecSaddle(t, region, kf, gf, saddles)
|
|
||||||
})
|
|
||||||
eg.Go(func() error {
|
|
||||||
kf, err := os.Create(filepath.Join(out, region, "scenario.kk"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
gf, err := os.Create(filepath.Join(out, region, "scenario.go"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
slog.Info("write scenarios")
|
|
||||||
return ExecScenario(t, region, kf, gf, scens)
|
|
||||||
})
|
|
||||||
eg.Go(func() error {
|
|
||||||
kf, err := os.Create(filepath.Join(out, region, "spark.kk"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
gf, err := os.Create(filepath.Join(out, region, "spark.go"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
slog.Info("write sparks")
|
|
||||||
return ExecSparks(t, region, kf, gf, sparks, sparkeff)
|
|
||||||
})
|
|
||||||
eg.Go(func() error {
|
|
||||||
kf, err := os.Create(filepath.Join(out, region, "uma.kk"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
gf, err := os.Create(filepath.Join(out, region, "uma.go"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
slog.Info("write umas")
|
|
||||||
return ExecUmas(t, region, kf, gf, umas)
|
|
||||||
})
|
|
||||||
if err := eg.Wait(); err != nil {
|
|
||||||
slog.Error("generate", slog.Any("err", err))
|
|
||||||
os.Exit(1)
|
|
||||||
} else {
|
|
||||||
slog.Info("done")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
{{- define "go-race" -}}
|
|
||||||
package {{ $.Region }}
|
|
||||||
|
|
||||||
// Automatically generated with horsegen; DO NOT EDIT
|
|
||||||
|
|
||||||
import . "git.sunturtle.xyz/zephyr/horse/horse"
|
|
||||||
|
|
||||||
const (
|
|
||||||
{{- range $r := $.Races }}
|
|
||||||
Race{{ goenum $r.Name }}{{ if $r.Alternate }}Alt{{ $r.ID }}{{ end }} RaceID = {{ $r.ID }} // {{ $r.Name }}
|
|
||||||
{{- end }}
|
|
||||||
)
|
|
||||||
|
|
||||||
var AllRaces = map[RaceID]Race{
|
|
||||||
{{- range $r := $.Races }}
|
|
||||||
Race{{ goenum $r.Name }}{{ if $r.Alternate }}Alt{{ $r.ID }}{{ end }}: {
|
|
||||||
ID: {{ $r.ID }},
|
|
||||||
Name: {{ printf "%q" $r.Name }}{{ if $r.Alternate }} + " (Alternate {{ $r.ID }})"{{ end }},
|
|
||||||
Thumbnail: {{ $r.ThumbnailID }},
|
|
||||||
{{- if ne $r.Primary $r.ID }}
|
|
||||||
Primary: {{ $r.Primary }},
|
|
||||||
{{- end }}
|
|
||||||
},
|
|
||||||
{{- end }}
|
|
||||||
}
|
|
||||||
|
|
||||||
var RaceNameToID = map[string]RaceID{
|
|
||||||
{{- range $r := $.Races }}
|
|
||||||
{{ printf "%q" $r.Name }}{{ if $r.Alternate }} + " (Alternate {{ $r.ID }})"{{ end }}: {{ $r.ID }},
|
|
||||||
{{- end }}
|
|
||||||
}
|
|
||||||
{{ end }}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
{{- define "koka-race" -}}
|
|
||||||
module horse/{{ $.Region }}/race
|
|
||||||
|
|
||||||
// Automatically generated with horsegen; DO NOT EDIT
|
|
||||||
|
|
||||||
import std/core/delayed
|
|
||||||
import std/core/vector
|
|
||||||
import std/core-extras
|
|
||||||
import std/data/rb-map
|
|
||||||
import horse/game-id
|
|
||||||
pub import horse/race
|
|
||||||
|
|
||||||
extern create-id-table(): vector<int>
|
|
||||||
c inline "int32_t arr[] = { {{- range $r := $.Races }}{{ $r.ID }},{{ end -}} };\nkk_vector_from_cint32array(arr, (kk_ssize_t){{ $.RaceCount }}, kk_context())"
|
|
||||||
js inline "[{{ range $r := $.Races }}{{ $r.ID }},{{ end }}]"
|
|
||||||
// Vector of all race IDs in order for easy iterating.
|
|
||||||
pub val all = once(create-id-table)
|
|
||||||
|
|
||||||
val name2id = once()
|
|
||||||
var m: rbmap<string, int> := empty()
|
|
||||||
all().foreach() fn(id) m := m.set(Race-id(id).show, id)
|
|
||||||
m
|
|
||||||
|
|
||||||
// Get the race ID that has the given exact name.
|
|
||||||
// Alternate versions of races have an indication of their ID in their names.
|
|
||||||
// If no race matches the name, the result is an invalid ID.
|
|
||||||
pub fun from-name(name: string): race-id
|
|
||||||
Race-id(name2id().rb-map/lookup(name).default(0))
|
|
||||||
|
|
||||||
// Get the name for a race.
|
|
||||||
// Alternate versions of races have an indication of their ID in their names.
|
|
||||||
// If no race matches the ID, the result is the numeric ID.
|
|
||||||
pub fun show(r: race-id): string
|
|
||||||
match r.game-id
|
|
||||||
{{- range $r := $.Races }}
|
|
||||||
{{ $r.ID }} -> {{ printf "%q" $r.Name }}{{ if $r.Alternate }} ++ " (Alternate {{ $r.ID }})"{{ end }}
|
|
||||||
{{- end }}
|
|
||||||
x -> "race " ++ x.show
|
|
||||||
|
|
||||||
// Get the grade for a race.
|
|
||||||
// If no race matches the ID, the result is Pre-OP.
|
|
||||||
pub fun grade(r: race-id): grade
|
|
||||||
match r.game-id
|
|
||||||
{{- range $r := $.Races }}
|
|
||||||
{{ $r.ID }} -> {{ if eq $r.Grade 100 }}G1{{ else if eq $r.Grade 200 }}G2{{ else if eq $r.Grade 300 }}G3{{ else if eq $r.Grade 400 }}OP{{ else if eq $r.Grade 700 }}Pre-OP{{ else }}??? $r.Grade={{ $r.Grade }}{{ end }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> Pre-OP
|
|
||||||
|
|
||||||
// Get the thumbnail ID for a race.
|
|
||||||
// If no race matches the ID, the result is an invalid ID.
|
|
||||||
pub fun thumbnail(r: race-id): race-thumbnail-id
|
|
||||||
match r.game-id
|
|
||||||
{{- range $r := $.Races }}
|
|
||||||
{{ $r.ID }} -> Race-thumbnail-id({{ $r.ThumbnailID }})
|
|
||||||
{{- end }}
|
|
||||||
_ -> Race-thumbnail-id(0)
|
|
||||||
|
|
||||||
// Get the primary ID for a race.
|
|
||||||
// For races which are the primary version, or if no race matches the given ID,
|
|
||||||
// the result is the input.
|
|
||||||
pub fun primary(r: race-id): race-id
|
|
||||||
match r.game-id
|
|
||||||
{{- range $r := $.Races }}
|
|
||||||
{{- if $r.Alternate }}
|
|
||||||
{{ $r.ID }} -> Race-id({{ $r.Primary }})
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> r
|
|
||||||
{{ end }}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
{{- define "go-saddle" -}}
|
|
||||||
package {{ $.Region }}
|
|
||||||
|
|
||||||
// Automatically generated with horsegen; DO NOT EDIT
|
|
||||||
|
|
||||||
import . "git.sunturtle.xyz/zephyr/horse/horse"
|
|
||||||
|
|
||||||
const (
|
|
||||||
{{- range $s := $.Saddles }}
|
|
||||||
Saddle{{ goenum $s.Name }}{{ if $s.Alternate }}Alt{{ $s.Alternate }}{{ end }} SaddleID = {{ $s.ID }} // {{ $s.Name }}
|
|
||||||
{{- end }}
|
|
||||||
)
|
|
||||||
|
|
||||||
var AllSaddles = map[SaddleID]Saddle{
|
|
||||||
{{- range $s := $.Saddles }}
|
|
||||||
Saddle{{ goenum $s.Name }}{{ if $s.Alternate }}Alt{{ $s.Alternate }}{{ end }}: {
|
|
||||||
ID: {{ $s.ID }},
|
|
||||||
Name: {{ printf "%q" $s.Name }}{{ if $s.Alternate }} + " (Alternate {{ $s.Alternate }})"{{ end }},
|
|
||||||
Races: []RaceID{ {{- range $id := $s.Races }}{{ if $id }}{{ $id }}, {{ end }}{{ end -}} },
|
|
||||||
Type: SaddleType{{ if eq $s.Type 0 }}Honor{{ else if eq $s.Type 1 }}G3{{ else if eq $s.Type 2 }}G2{{ else if eq $s.Type 3 }}G1{{ else }}??? $s.Type={{ $s.Type }}{{ end }},
|
|
||||||
{{- if $s.Alternate }}
|
|
||||||
Primary: {{ $s.Primary }},
|
|
||||||
{{- end }}
|
|
||||||
},
|
|
||||||
{{- end }}
|
|
||||||
}
|
|
||||||
{{ end }}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
{{- define "koka-saddle" -}}
|
|
||||||
module horse/{{ $.Region }}/saddle
|
|
||||||
|
|
||||||
// Automatically generated with horsegen; DO NOT EDIT
|
|
||||||
|
|
||||||
import std/core/delayed
|
|
||||||
import std/core/vector
|
|
||||||
import std/core-extras
|
|
||||||
import horse/game-id
|
|
||||||
pub import horse/race
|
|
||||||
pub import horse/{{ $.Region }}/race
|
|
||||||
|
|
||||||
extern create-id-table(): vector<int>
|
|
||||||
c inline "int32_t arr[] = { {{- range $s := $.Saddles }}{{ $s.ID }},{{ end -}} };\nkk_vector_from_cint32array(arr, (kk_ssize_t){{ $.SaddleCount }}, kk_context())"
|
|
||||||
js inline "[{{ range $s := $.Saddles }}{{ $s.ID }},{{ end }}]"
|
|
||||||
// Vector of all saddle IDs in order for easy iterating.
|
|
||||||
pub val all = once(create-id-table)
|
|
||||||
|
|
||||||
// Get the name for a saddle.
|
|
||||||
// Alternate versions of saddles have an indication of their ID in their names.
|
|
||||||
// If no saddle matches the ID, the result contains the numeric ID.
|
|
||||||
pub fun show(s: saddle-id): string
|
|
||||||
match s.game-id
|
|
||||||
{{- range $s := $.Saddles }}
|
|
||||||
{{ $s.ID }} -> {{ printf "%q" $s.Name }}{{ if $s.Alternate }} ++ " (Alternate {{ $s.ID }})"{{ end }}
|
|
||||||
{{- end }}
|
|
||||||
x -> "saddle " ++ x.show
|
|
||||||
|
|
||||||
// Get the list of races that entitle a horse to a saddle.
|
|
||||||
// If no saddle matches the ID, the result is the empty list.
|
|
||||||
pub fun races(s: saddle-id): list<race-id>
|
|
||||||
match s.game-id
|
|
||||||
{{- range $s := $.Saddles }}
|
|
||||||
{{ $s.ID }} -> [{{ range $id := $s.Races }}{{ if $id }}Race-id({{ $id }}), {{ end }}{{ end }}]
|
|
||||||
{{- end }}
|
|
||||||
_ -> []
|
|
||||||
|
|
||||||
// Get a saddle's type.
|
|
||||||
// If no saddle matches the ID, the result is Honor.
|
|
||||||
pub fun saddle-type(s: saddle-id): saddle-type
|
|
||||||
match s.game-id
|
|
||||||
{{- range $s := $.Saddles }}
|
|
||||||
{{ $s.ID }} -> {{ if eq $s.Type 0 }}Honor{{ else if eq $s.Type 1 }}G3-Win{{ else if eq $s.Type 2 }}G2-Win{{ else if eq $s.Type 3 }}G1-Win{{ else }}??? $s.Type={{ $s.Type }}{{ end }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> Honor
|
|
||||||
|
|
||||||
// Get the primary ID for a saddle.
|
|
||||||
// For saddles which are the primary version, or if no saddle matches the given ID,
|
|
||||||
// the result is the input.
|
|
||||||
pub fun primary(s: saddle-id): saddle-id
|
|
||||||
match s.game-id
|
|
||||||
{{- range $s := $.Saddles }}
|
|
||||||
{{- if $s.Alternate }}
|
|
||||||
{{ $s.ID }} -> Saddle-id({{ $s.Primary }})
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> s
|
|
||||||
{{ end }}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
{{- define "go-scenario" -}}
|
|
||||||
package {{ $.Region }}
|
|
||||||
|
|
||||||
// Automatically generated with horsegen; DO NOT EDIT
|
|
||||||
|
|
||||||
import . "git.sunturtle.xyz/zephyr/horse/horse"
|
|
||||||
|
|
||||||
const (
|
|
||||||
{{- range $s := $.Scenarios }}
|
|
||||||
Scenario{{ goenum $s.Name }} ScenarioID = {{ $s.ID }} // {{ $s.Name }}
|
|
||||||
{{- end }}
|
|
||||||
)
|
|
||||||
|
|
||||||
var AllScenarios = map[ScenarioID]Scenario{
|
|
||||||
{{- range $s := $.Scenarios }}
|
|
||||||
Scenario{{ goenum $s.Name }}: {
|
|
||||||
ID: {{ $s.ID }},
|
|
||||||
Name: {{ printf "%q" $s.Name }},
|
|
||||||
Title: {{ printf "%q" $s.Title }},
|
|
||||||
},
|
|
||||||
{{- end }}
|
|
||||||
}
|
|
||||||
{{ end }}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
{{- define "koka-scenario" -}}
|
|
||||||
module horse/{{ $.Region }}/scenario
|
|
||||||
|
|
||||||
// Automatically generated with horsegen; DO NOT EDIT
|
|
||||||
|
|
||||||
import std/core/delayed
|
|
||||||
import std/core/vector
|
|
||||||
import std/core-extras
|
|
||||||
import horse/game-id
|
|
||||||
|
|
||||||
extern create-id-table(): vector<int>
|
|
||||||
c inline "int32_t arr[] = { {{- range $s := $.Scenarios }}{{ $s.ID }},{{ end -}} };\nkk_vector_from_cint32array(arr, (kk_ssize_t){{ $.ScenarioCount }}, kk_context())"
|
|
||||||
js inline "[{{ range $s := $.Scenarios }}{{ $s.ID }},{{ end }}]"
|
|
||||||
// Vector of all scenario IDs in order for easy iterating.
|
|
||||||
pub val all = once(create-id-table)
|
|
||||||
|
|
||||||
// Get the name for a scenario.
|
|
||||||
// If no scenario matches the ID, the result contains the numeric ID.
|
|
||||||
pub fun show(s: scenario-id): string
|
|
||||||
match s.game-id
|
|
||||||
{{- range $s := $.Scenarios }}
|
|
||||||
{{ $s.ID }} -> {{ printf "%q" $s.Name }}
|
|
||||||
{{- end }}
|
|
||||||
x -> "scenario " ++ x.show
|
|
||||||
|
|
||||||
// Get the full title for a scenario, e.g. "The Beginning: URA Finale".
|
|
||||||
// If no scenario matches the ID, the result contains the numeric ID.
|
|
||||||
pub fun title(s: scenario-id): string
|
|
||||||
match s.game-id
|
|
||||||
{{- range $s := $.Scenarios }}
|
|
||||||
{{ $s.ID }} -> {{ printf "%q" $s.Title }}
|
|
||||||
{{- end }}
|
|
||||||
x -> "scenario " ++ x.show
|
|
||||||
{{ end }}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
WITH skill_names AS (
|
|
||||||
SELECT
|
|
||||||
"index" AS "id",
|
|
||||||
"text" AS "name"
|
|
||||||
FROM text_data
|
|
||||||
WHERE category = 47
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
group_id,
|
|
||||||
name
|
|
||||||
FROM skill_data d
|
|
||||||
JOIN skill_names n ON d.id = n.id
|
|
||||||
WHERE d.group_rate = 1
|
|
||||||
ORDER BY group_id
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
{{- define "go-skill-data" -}}
|
|
||||||
package {{ $.Region }}
|
|
||||||
|
|
||||||
// Automatically generated with horsegen; DO NOT EDIT
|
|
||||||
|
|
||||||
import . "git.sunturtle.xyz/zephyr/horse/horse"
|
|
||||||
|
|
||||||
const (
|
|
||||||
{{- range $s := $.Skills }}
|
|
||||||
Skill{{ goenum $s.Name }}{{ if ne $s.InheritID 0 }}Inherit{{ end }} SkillID = {{ $s.ID }} // {{ $s.Name }}
|
|
||||||
{{- end }}
|
|
||||||
)
|
|
||||||
|
|
||||||
var OrderedSkills = [...]SkillID{
|
|
||||||
{{- range $s := $.Skills }}
|
|
||||||
Skill{{ goenum $s.Name }}{{ if ne $s.InheritID 0 }}Inherit{{ end }},
|
|
||||||
{{- end }}
|
|
||||||
}
|
|
||||||
|
|
||||||
var AllSkills = map[SkillID]Skill{
|
|
||||||
{{- range $s := $.Skills }}
|
|
||||||
Skill{{ goenum $s.Name }}{{ if ne $s.InheritID 0 }}Inherit{{ end }}: {
|
|
||||||
ID: {{ $s.ID }},
|
|
||||||
Name: {{ printf "%q" $s.Name }}{{ if ne $s.InheritID 0 }} + " (Inherited)"{{ end }},
|
|
||||||
Description: {{ printf "%q" $s.Description }},
|
|
||||||
Group: {{ $s.GroupID }},
|
|
||||||
Rarity: {{ $s.Rarity }},
|
|
||||||
GroupRate: {{ $s.GroupRate }},
|
|
||||||
GradeValue: {{ $s.GradeValue }},
|
|
||||||
{{- if $s.WitCheck }}
|
|
||||||
WitCheck: {{ $s.WitCheck }},
|
|
||||||
{{- end }}
|
|
||||||
Activations: []Activation{
|
|
||||||
{{- range $a := $s.Activations }}
|
|
||||||
{{- if ne $a.Condition "" }}
|
|
||||||
{
|
|
||||||
{{- if $a.Precondition }}
|
|
||||||
Precondition: {{ printf "%q" $a.Precondition }},
|
|
||||||
{{- end }}
|
|
||||||
Condition: {{ printf "%q" $a.Condition }},
|
|
||||||
Duration: {{ $a.Duration }},
|
|
||||||
DurScale: {{ $a.DurScale }},
|
|
||||||
{{- if $a.Cooldown }}
|
|
||||||
Cooldown: {{ $a.Cooldown }},
|
|
||||||
{{- end }}
|
|
||||||
Abilities: []Ability{
|
|
||||||
{{- range $abil := $a.Abilities }}
|
|
||||||
{{- if ne $abil.Type 0 }}
|
|
||||||
{Type: {{ $abil.Type }}, ValueUsage: {{ $abil.ValueUsage }}, Value: {{ $abil.Value }}, Target: {{ $abil.Target }}, TargetValue: {{ $abil.TargetValue -}} },
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
},
|
|
||||||
{{- if $s.UniqueOwner }}
|
|
||||||
UniqueOwner: {{ printf "%q" $s.UniqueOwner }},
|
|
||||||
{{- end }}
|
|
||||||
{{- if $s.SPCost }}
|
|
||||||
SPCost: {{ $s.SPCost }},
|
|
||||||
{{- end }}
|
|
||||||
IconID: {{ $s.IconID }},
|
|
||||||
},
|
|
||||||
{{- end }}
|
|
||||||
}
|
|
||||||
|
|
||||||
var SkillNameToID = map[string]SkillID{
|
|
||||||
{{- range $s := $.Skills }}
|
|
||||||
{{ printf "%q" $s.Name }}{{ if ne $s.InheritID 0 }} + " (Inherited)"{{ end }}: {{ $s.ID }},
|
|
||||||
{{- end }}
|
|
||||||
}
|
|
||||||
|
|
||||||
var SkillGroups = map[int32][4]SkillID{
|
|
||||||
{{- range $g := $.Groups }}
|
|
||||||
{{ $g.ID }}: { {{- range $s := index $.Related $g.ID }}Skill{{ goenum $s.Name }}{{ if ne $s.InheritID 0 }}Inherit{{ end }}, {{ end -}} },
|
|
||||||
{{- end }}
|
|
||||||
}
|
|
||||||
{{ end }}
|
|
||||||
@@ -1,230 +0,0 @@
|
|||||||
{{- define "koka-skill" -}}
|
|
||||||
module horse/{{ $.Region }}/skill
|
|
||||||
|
|
||||||
// Automatically generated with horsegen; DO NOT EDIT
|
|
||||||
|
|
||||||
import std/core/delayed
|
|
||||||
import std/core/vector
|
|
||||||
import std/core-extras
|
|
||||||
import std/data/rb-map
|
|
||||||
import std/num/decimal
|
|
||||||
import horse/game-id
|
|
||||||
import horse/movement
|
|
||||||
pub import horse/skill
|
|
||||||
|
|
||||||
extern create-id-table(): vector<int>
|
|
||||||
c inline "int32_t arr[] = { {{- range $s := $.Skills }}{{ $s.ID }},{{ end -}} };\nkk_vector_from_cint32array(arr, (kk_ssize_t){{ $.SkillCount }}, kk_context())"
|
|
||||||
js inline "[{{ range $s := $.Skills }}{{ $s.ID }},{{ end }}]"
|
|
||||||
// Vector of all skill ID values in order for easy iterating.
|
|
||||||
pub val all = once(create-id-table)
|
|
||||||
|
|
||||||
val name2id = once()
|
|
||||||
var m: rbmap<string, int> := empty()
|
|
||||||
all().foreach() fn(id) m := m.set(Skill-id(id).show, id)
|
|
||||||
m
|
|
||||||
|
|
||||||
// Get the skill ID that has the given exact name.
|
|
||||||
// Inherited skills have `" (Inherited)"` appended to their names.
|
|
||||||
// If no skill matches the name, the result is an invalid ID.
|
|
||||||
pub fun from-name(name: string): skill-id
|
|
||||||
Skill-id(name2id().rb-map/lookup(name).default(0))
|
|
||||||
|
|
||||||
// Get the name for a skill.
|
|
||||||
// Inherited skills have `" (Inherited)"` appended to their names.
|
|
||||||
// If no skill matches the ID, the result is the numeric ID.
|
|
||||||
pub fun show(s: skill-id): string
|
|
||||||
match s.game-id
|
|
||||||
{{- range $s := $.Skills }}
|
|
||||||
{{ $s.ID }} -> {{ printf "%q" $s.Name }}{{ if $s.InheritID }} ++ " (Inherited)"{{ end }}
|
|
||||||
{{- end }}
|
|
||||||
x -> "skill " ++ x.show
|
|
||||||
|
|
||||||
// Get the description for a skill.
|
|
||||||
// If no skill matches the ID, the result is the empty string.
|
|
||||||
pub fun description(s: skill-id): string
|
|
||||||
match s.game-id
|
|
||||||
{{- range $s := $.Skills }}
|
|
||||||
{{ $s.ID }} -> {{ printf "%q" $s.Description }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> ""
|
|
||||||
|
|
||||||
// Get the skill group ID for a skill.
|
|
||||||
// If no skill matches the ID, the result is an invalid ID.
|
|
||||||
pub fun group(s: skill-id): skill-group-id
|
|
||||||
match s.game-id
|
|
||||||
{{- range $s := $.Skills }}
|
|
||||||
{{ $s.ID }} -> Skill-group-id( {{- $s.GroupID -}} )
|
|
||||||
{{- end }}
|
|
||||||
_ -> Skill-group-id(0)
|
|
||||||
|
|
||||||
// Get the rarity of a skill.
|
|
||||||
// If no skill matches the ID, the result is Common.
|
|
||||||
pub fun rarity(s: skill-id): rarity
|
|
||||||
match s.game-id
|
|
||||||
{{- range $s := $.Skills }}
|
|
||||||
{{ $s.ID }} -> {{ if eq $s.Rarity 1 }}Common{{ else if eq $s.Rarity 2 }}Rare{{ else if eq $s.Rarity 3 }}Unique-Low{{ else if eq $s.Rarity 4 }}Unique-Upgraded{{ else if eq $s.Rarity 5 }}Unique{{ else }}??? $s.Rarity={{ $s.Rarity }}{{ end }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> Common
|
|
||||||
|
|
||||||
// Get the group rate of a skill.
|
|
||||||
// If no skill matches the ID, the result is 0.
|
|
||||||
pub fun group-rate(s: skill-id): int
|
|
||||||
match s.game-id
|
|
||||||
{{- range $s := $.Skills }}
|
|
||||||
{{ $s.ID }} -> {{ $s.GroupRate }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> 0
|
|
||||||
|
|
||||||
// Get the grade value of a skill.
|
|
||||||
// If no skill matches the ID, the result is 0.
|
|
||||||
pub fun grade-value(s: skill-id): int
|
|
||||||
match s.game-id
|
|
||||||
{{- range $s := $.Skills }}
|
|
||||||
{{ $s.ID }} -> {{ $s.GradeValue }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> 0
|
|
||||||
|
|
||||||
// Get whether a skill is a wit check.
|
|
||||||
// If no skill matches the ID, the result is False.
|
|
||||||
pub fun wit-check(s: skill-id): bool
|
|
||||||
match s.game-id
|
|
||||||
{{- range $s := $.Skills }}
|
|
||||||
{{ $s.ID }} -> {{ if $s.WitCheck }}True{{ else }}False{{ end }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> False
|
|
||||||
|
|
||||||
// Get the activations of a skill.
|
|
||||||
// If no skill matches the ID, the result is an empty list.
|
|
||||||
pub fun activations(s: skill-id): list<activation>
|
|
||||||
match s.game-id
|
|
||||||
{{- range $s := $.Skills }}
|
|
||||||
{{ $s.ID }} -> [
|
|
||||||
{{- range $a := $s.Activations }}
|
|
||||||
{{- if $a.Condition }}
|
|
||||||
Activation(
|
|
||||||
precondition = {{ printf "%q" $a.Precondition }},
|
|
||||||
condition = {{ printf "%q" $a.Condition }},
|
|
||||||
duration = {{ $a.Duration }}.decimal{{ if gt $a.Duration 0 }}(-4){{ end }},
|
|
||||||
dur-scale = {{ if eq $a.DurScale 1 }}Direct-Dur
|
|
||||||
{{- else if eq $a.DurScale 2 }}Front-Distance-Dur
|
|
||||||
{{- else if eq $a.DurScale 3 }}Multiply-Remaining-HP
|
|
||||||
{{- else if eq $a.DurScale 4 }}Increment-Pass
|
|
||||||
{{- else if eq $a.DurScale 5 }}Midrace-Side-Block-Time-Dur
|
|
||||||
{{- else if eq $a.DurScale 7 }}Multiply-Remaining-HP2
|
|
||||||
{{- else }}??? $a.DurScale={{ $a.DurScale }}
|
|
||||||
{{- end }},
|
|
||||||
cooldown = {{ $a.Cooldown }}.decimal{{ if gt $a.Cooldown 0 }}(-4){{ end }},
|
|
||||||
abilities = [
|
|
||||||
{{- range $abil := $a.Abilities }}
|
|
||||||
{{- if $abil.Type }}
|
|
||||||
Ability(
|
|
||||||
ability-type = {{ if eq $abil.Type 1 }}Passive-Speed({{ $abil.Value }}.decimal(-4))
|
|
||||||
{{- else if eq $abil.Type 2 }}Passive-Stamina({{ $abil.Value }}.decimal(-4))
|
|
||||||
{{- else if eq $abil.Type 3 }}Passive-Power({{ $abil.Value }}.decimal(-4))
|
|
||||||
{{- else if eq $abil.Type 4 }}Passive-Guts({{ $abil.Value }}.decimal(-4))
|
|
||||||
{{- else if eq $abil.Type 5 }}Passive-Wit({{ $abil.Value }}.decimal(-4))
|
|
||||||
{{- else if eq $abil.Type 6 }}Great-Escape
|
|
||||||
{{- else if eq $abil.Type 8 }}Vision({{ $abil.Value }}.decimal(-4))
|
|
||||||
{{- else if eq $abil.Type 9 }}HP({{ $abil.Value }}.decimal(-4))
|
|
||||||
{{- else if eq $abil.Type 10 }}Gate-Delay({{ $abil.Value }}.decimal(-4))
|
|
||||||
{{- else if eq $abil.Type 13 }}Frenzy({{ $abil.Value }}.decimal(-4))
|
|
||||||
{{- else if eq $abil.Type 21 }}Current-Speed({{ $abil.Value }}.decimal(-4))
|
|
||||||
{{- else if eq $abil.Type 27 }}Target-Speed({{ $abil.Value }}.decimal(-4))
|
|
||||||
{{- else if eq $abil.Type 28 }}Lane-Speed({{ $abil.Value }}.decimal(-4))
|
|
||||||
{{- else if eq $abil.Type 31 }}Accel({{ $abil.Value }}.decimal(-4))
|
|
||||||
{{- else if eq $abil.Type 35 }}Lane-Change({{ $abil.Value }}.decimal(-4))
|
|
||||||
{{- else }}??? $abil.Type={{$abil.Type}}
|
|
||||||
{{- end }},
|
|
||||||
value-usage = {{ if eq $abil.ValueUsage 1 }}Direct
|
|
||||||
{{- else if eq $abil.ValueUsage 3 }}Team-Speed
|
|
||||||
{{- else if eq $abil.ValueUsage 4 }}Team-Stamina
|
|
||||||
{{- else if eq $abil.ValueUsage 5 }}Team-Power
|
|
||||||
{{- else if eq $abil.ValueUsage 6 }}Team-Guts
|
|
||||||
{{- else if eq $abil.ValueUsage 7 }}Team-Wit
|
|
||||||
{{- else if eq $abil.ValueUsage 8 }}Multiply-Random
|
|
||||||
{{- else if eq $abil.ValueUsage 9 }}Multiply-Random2
|
|
||||||
{{- else if eq $abil.ValueUsage 10 }}Climax
|
|
||||||
{{- else if eq $abil.ValueUsage 13 }}Max-Stat
|
|
||||||
{{- else if eq $abil.ValueUsage 14 }}Passive-Count
|
|
||||||
{{- else if eq $abil.ValueUsage 19 }}Front-Distance-Add
|
|
||||||
{{- else if eq $abil.ValueUsage 20 }}Midrace-Side-Block-Time
|
|
||||||
{{- else if eq $abil.ValueUsage 22 }}Speed-Scaling
|
|
||||||
{{- else if eq $abil.ValueUsage 23 }}Speed-Scaling2
|
|
||||||
{{- else if eq $abil.ValueUsage 24 }}Arc-Global-Potential
|
|
||||||
{{- else if eq $abil.ValueUsage 25 }}Max-Lead-Distance
|
|
||||||
{{- else }}??? $abil.ValueUsage={{ $abil.ValueUsage }}
|
|
||||||
{{- end }},
|
|
||||||
target = {{ if eq $abil.Target 1}}Self
|
|
||||||
{{- else if eq $abil.Target 4 }}Sympathizers
|
|
||||||
{{- else if eq $abil.Target 4 }}In-View
|
|
||||||
{{- else if eq $abil.Target 4 }}Frontmost
|
|
||||||
{{- else if eq $abil.Target 9 }}Ahead({{ $abil.TargetValue }})
|
|
||||||
{{- else if eq $abil.Target 10 }}Behind({{ $abil.TargetValue }})
|
|
||||||
{{- else if eq $abil.Target 4 }}All-Teammates
|
|
||||||
{{- else if eq $abil.Target 18 }}Style({{ if eq $abil.TargetValue 1 }}Front-Runner{{ else if eq $abil.TargetValue 2 }}Pace-Chaser{{ else if eq $abil.TargetValue 3 }}Late-Surger{{ else if eq $abil.TargetValue 4 }}End-Closer{{ else }}??? $abil.TargetValue={{ $abil.TargetValue }}{{ end }})
|
|
||||||
{{- else if eq $abil.Target 19 }}Rushing-Ahead({{ $abil.TargetValue }})
|
|
||||||
{{- else if eq $abil.Target 20 }}Rushing-Behind({{ $abil.TargetValue }})
|
|
||||||
{{- else if eq $abil.Target 21 }}Rushing-Style({{ if eq $abil.TargetValue 1 }}Front-Runner{{ else if eq $abil.TargetValue 2 }}Pace-Chaser{{ else if eq $abil.TargetValue 3 }}Late-Surger{{ else if eq $abil.TargetValue 4 }}End-Closer{{ else }}??? $abil.TargetValue={{ $abil.TargetValue }}{{ end }})
|
|
||||||
{{- else if eq $abil.Target 22 }}Specific-Character(Character-id({{ $abil.TargetValue }}))
|
|
||||||
{{- else if eq $abil.Target 23 }}Triggering
|
|
||||||
{{- end }}
|
|
||||||
),
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
]
|
|
||||||
),
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
]
|
|
||||||
{{- end }}
|
|
||||||
_ -> Nil
|
|
||||||
|
|
||||||
// Get the owner of a unique skill.
|
|
||||||
// If the skill is not unique, or if there is no skill with the given ID,
|
|
||||||
// the result is Nothing.
|
|
||||||
pub fun unique-owner(s: skill-id): maybe<uma-id>
|
|
||||||
match s.game-id
|
|
||||||
{{- range $s := $.Skills }}
|
|
||||||
{{- if $s.UniqueOwnerID }}
|
|
||||||
{{ $s.ID }} -> Just(Uma-id({{ $s.UniqueOwnerID }}))
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> Nothing
|
|
||||||
|
|
||||||
// Get the SP cost of a skill.
|
|
||||||
// If there is no skill with the given ID, the result is 0.
|
|
||||||
pub fun sp-cost(s: skill-id): int
|
|
||||||
match s.game-id
|
|
||||||
{{- range $s := $.Skills }}
|
|
||||||
{{ $s.ID }} -> {{ $s.SPCost }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> 0
|
|
||||||
|
|
||||||
// Get the icon ID of a skill.
|
|
||||||
// If there is no skill with the given ID, the result is an invalid ID.
|
|
||||||
pub fun icon-id(s: skill-id): skill-icon-id
|
|
||||||
match s.game-id
|
|
||||||
{{- range $s := $.Skills }}
|
|
||||||
{{ $s.ID }} -> Skill-icon-id({{ $s.IconID }})
|
|
||||||
{{- end }}
|
|
||||||
_ -> Skill-icon-id(0)
|
|
||||||
|
|
||||||
// Get the name for a skill group.
|
|
||||||
// Skill group names are the name of the base skill in the group.
|
|
||||||
// If there is no skill group with the given ID, the result is the numeric ID.
|
|
||||||
pub fun skill-group/show(sg: skill-group-id): string
|
|
||||||
match sg.game-id
|
|
||||||
{{- range $g := $.Groups }}
|
|
||||||
{{ $g.ID }} -> {{- printf "%q" $g.Name -}}
|
|
||||||
{{- end }}
|
|
||||||
x -> "skill group " ++ x.show
|
|
||||||
|
|
||||||
// Get the list of skills in a skill group.
|
|
||||||
pub fun skill-group/skills(sg: skill-group-id): list<skill-id>
|
|
||||||
match sg.game-id
|
|
||||||
{{- range $g := $.Groups }}
|
|
||||||
{{ $g.ID }} -> [ {{- range $s := index $.Related $g.ID }}Skill-id({{ $s.ID }}), {{ end -}} ]
|
|
||||||
{{- end }}
|
|
||||||
_ -> Nil
|
|
||||||
|
|
||||||
{{- end }}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
{{- define "go-spark" -}}
|
|
||||||
package {{ $.Region }}
|
|
||||||
|
|
||||||
// Automatically generated with horsegen; DO NOT EDIT
|
|
||||||
|
|
||||||
import . "git.sunturtle.xyz/zephyr/horse/horse"
|
|
||||||
|
|
||||||
const (
|
|
||||||
{{- range $s := $.Sparks }}
|
|
||||||
Spark{{ goenum $s.Name }}Lv{{ $s.Rarity }} SparkID = {{ $s.ID }} // {{ $s.Name }}
|
|
||||||
{{- end }}
|
|
||||||
)
|
|
||||||
|
|
||||||
var AllSparks = map[SparkID]Spark{
|
|
||||||
{{- range $s := $.Sparks }}
|
|
||||||
Spark{{ goenum $s.Name }}Lv{{ $s.Rarity }}: {
|
|
||||||
ID: {{ $s.ID }},
|
|
||||||
Name: {{ printf "%q" $s.Name }},
|
|
||||||
Description: {{ printf "%q" $s.Description }},
|
|
||||||
Group: {{ $s.Group }},
|
|
||||||
Rarity: {{ $s.Rarity }},
|
|
||||||
Type: {{ $s.Type }},
|
|
||||||
Effects: [][]SparkEffect{
|
|
||||||
{{- range $r := index $.SparkEffects $s.Group }}
|
|
||||||
{
|
|
||||||
{{- range $e := $r -}}
|
|
||||||
{ {{- $e.Target }}, {{ $e.Value1 }}, {{ $e.Value2 -}} },
|
|
||||||
{{- end -}}
|
|
||||||
},
|
|
||||||
{{- end }}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{{- end }}
|
|
||||||
}
|
|
||||||
{{ end }}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
{{- define "koka-spark" -}}
|
|
||||||
module horse/{{ $.Region }}/spark
|
|
||||||
|
|
||||||
// Automatically generated with horsegen; DO NOT EDIT
|
|
||||||
|
|
||||||
import std/core/delayed
|
|
||||||
import std/core/vector
|
|
||||||
import std/core-extras
|
|
||||||
import horse/game-id
|
|
||||||
pub import horse/spark
|
|
||||||
|
|
||||||
extern create-id-table(): vector<int>
|
|
||||||
c inline "int32_t arr[] = { {{- range $s := $.Sparks }}{{ $s.ID }},{{ end -}} };\nkk_vector_from_cint32array(arr, (kk_ssize_t){{ $.SparkCount }}, kk_context())"
|
|
||||||
js inline "[{{ range $s := $.Sparks }}{{ $s.ID }},{{ end }}]"
|
|
||||||
// Vector of all spark IDs in order for easy iterating.
|
|
||||||
pub val all = once(create-id-table)
|
|
||||||
|
|
||||||
// Get the name for a spark.
|
|
||||||
// The name does not indicate the spark level.
|
|
||||||
// If no spark matches the ID, the result contains the numeric ID.
|
|
||||||
pub fun show(s: spark-id): string
|
|
||||||
match s.game-id
|
|
||||||
{{- range $s := $.Sparks }}
|
|
||||||
{{ $s.ID }} -> {{ printf "%q" $s.Name }}
|
|
||||||
{{- end }}
|
|
||||||
x -> "spark " ++ x.show
|
|
||||||
|
|
||||||
// Get the description for a spark.
|
|
||||||
// The description does not indicate the spark level.
|
|
||||||
// If no spark matches the ID, the result contains the numeric ID.
|
|
||||||
pub fun description(s: spark-id): string
|
|
||||||
match s.game-id
|
|
||||||
{{- range $s := $.Sparks }}
|
|
||||||
{{ $s.ID }} -> {{ printf "%q" $s.Description }}
|
|
||||||
{{- end }}
|
|
||||||
x -> "spark " ++ x.show
|
|
||||||
|
|
||||||
// Get the spark group ID of a spark.
|
|
||||||
// If no spark matches the ID, the result is an invalid ID.
|
|
||||||
pub fun spark-group(s: spark-id): spark-group-id
|
|
||||||
match s.game-id
|
|
||||||
{{- range $s := $.Sparks }}
|
|
||||||
{{ $s.ID }} -> Spark-group-id({{ $s.Group }})
|
|
||||||
{{- end }}
|
|
||||||
_ -> Spark-group-id(0)
|
|
||||||
|
|
||||||
// Get the rarity (level or star count) of a spark.
|
|
||||||
// If no spark matches the ID, the result is One.
|
|
||||||
pub fun rarity(s: spark-id): rarity
|
|
||||||
match s.game-id
|
|
||||||
{{- range $s := $.Sparks }}
|
|
||||||
{{ $s.ID }} -> {{ if eq $s.Rarity 1 }}One{{ else if eq $s.Rarity 2 }}Two{{ else if eq $s.Rarity 3 }}Three{{ else }}??? $s.Rarity={{ $s.Rarity }}{{ end }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> One
|
|
||||||
|
|
||||||
// Get the type of a spark.
|
|
||||||
// If no spark matches the ID, the result is Stat.
|
|
||||||
pub fun spark-type(s: spark-id): spark-type
|
|
||||||
match s.game-id
|
|
||||||
{{- range $s := $.Sparks }}
|
|
||||||
{{ $s.ID }} -> {{ if eq $s.Type 1 }}Stat
|
|
||||||
{{- else if eq $s.Type 2 }}Aptitude
|
|
||||||
{{- else if eq $s.Type 5 }}Race
|
|
||||||
{{- else if eq $s.Type 4 }}Skill
|
|
||||||
{{- else if eq $s.Type 6 }}Scenario
|
|
||||||
{{- else if eq $s.Type 7 }}Carnival-Bonus
|
|
||||||
{{- else if eq $s.Type 10 }}Surface
|
|
||||||
{{- else if eq $s.Type 8 }}Distance
|
|
||||||
{{- else if eq $s.Type 11 }}Style
|
|
||||||
{{- else if eq $s.Type 9 }}Hidden
|
|
||||||
{{- else if eq $s.Type 3 }}Unique
|
|
||||||
{{- else }}??? $s.Type={{ $s.Type }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> Stat
|
|
||||||
|
|
||||||
// Get the list of all effects a spark can apply during inheritance.
|
|
||||||
// When a spark procs, a random element is chosen from the list yielded by this
|
|
||||||
// function according to a hidden distribution, then all effects in that are applied.
|
|
||||||
// If no spark matches the ID, the result is the empty list.
|
|
||||||
pub fun effects(s: spark-id): list<list<spark-effect>>
|
|
||||||
match s.game-id
|
|
||||||
{{- range $s := $.Sparks }}
|
|
||||||
{{ $s.ID }} -> [
|
|
||||||
{{- range $r := index $.SparkEffects $s.Group }}
|
|
||||||
[
|
|
||||||
{{- range $e := $r -}}
|
|
||||||
{{- if eq $e.Target 1 -}}Stat-Up(Speed, {{ $e.Value1 }}),
|
|
||||||
{{- else if eq $e.Target 2 -}}Stat-Up(Stamina, {{ $e.Value1 }}),
|
|
||||||
{{- else if eq $e.Target 3 -}}Stat-Up(Power, {{ $e.Value1 }}),
|
|
||||||
{{- else if eq $e.Target 4 -}}Stat-Up(Guts, {{ $e.Value1 }}),
|
|
||||||
{{- else if eq $e.Target 5 -}}Stat-Up(Wit, {{ $e.Value1 }}),
|
|
||||||
{{- else if eq $e.Target 6 -}}SP-Up({{ $e.Value1 }}),
|
|
||||||
{{- else if eq $e.Target 7 -}}Random-Stat-Up({{ $e.Value1 }}),
|
|
||||||
{{- else if eq $e.Target 11 -}}Aptitude-Up(Turf, {{ $e.Value1 }}),
|
|
||||||
{{- else if eq $e.Target 12 -}}Aptitude-Up(Dirt, {{ $e.Value1 }}),
|
|
||||||
{{- else if eq $e.Target 21 -}}Aptitude-Up(Front-Runner, {{ $e.Value1 }}),
|
|
||||||
{{- else if eq $e.Target 22 -}}Aptitude-Up(Pace-Chaser, {{ $e.Value1 }}),
|
|
||||||
{{- else if eq $e.Target 23 -}}Aptitude-Up(Late-Surger, {{ $e.Value1 }}),
|
|
||||||
{{- else if eq $e.Target 24 -}}Aptitude-Up(End-Closer, {{ $e.Value1 }}),
|
|
||||||
{{- else if eq $e.Target 31 -}}Aptitude-Up(Sprint, {{ $e.Value1 }}),
|
|
||||||
{{- else if eq $e.Target 32 -}}Aptitude-Up(Mile, {{ $e.Value1 }}),
|
|
||||||
{{- else if eq $e.Target 33 -}}Aptitude-Up(Medium, {{ $e.Value1 }}),
|
|
||||||
{{- else if eq $e.Target 34 -}}Aptitude-Up(Long, {{ $e.Value1 }}),
|
|
||||||
{{- else if eq $e.Target 41 -}}Skill-Hint(Skill-id({{ $e.Value1 }}), {{ $e.Value2 }}),
|
|
||||||
{{- else if eq $e.Target 51 -}}Carnival-Bonus {{/*- skipped, but doesn't hurt to put it here -*/}}
|
|
||||||
{{- else if eq $e.Target 61 -}}Stat-Cap-Up(Speed, {{ $e.Value1 }}),
|
|
||||||
{{- else if eq $e.Target 62 -}}Stat-Cap-Up(Stamina, {{ $e.Value1 }}),
|
|
||||||
{{- else if eq $e.Target 63 -}}Stat-Cap-Up(Power, {{ $e.Value1 }}),
|
|
||||||
{{- else if eq $e.Target 64 -}}Stat-Cap-Up(Guts, {{ $e.Value1 }}),
|
|
||||||
{{- else if eq $e.Target 65 -}}Stat-Cap-Up(Wit, {{ $e.Value1 }}),
|
|
||||||
{{- else -}}
|
|
||||||
??? $e.Target={{- $e.Target -}}
|
|
||||||
{{- end -}}
|
|
||||||
{{- end -}}
|
|
||||||
],
|
|
||||||
{{- end }}
|
|
||||||
]
|
|
||||||
{{- end }}
|
|
||||||
_ -> []
|
|
||||||
{{ end }}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
{{- define "go-uma" -}}
|
|
||||||
package {{ $.Region }}
|
|
||||||
|
|
||||||
// Automatically generated with horsegen; DO NOT EDIT
|
|
||||||
|
|
||||||
import . "git.sunturtle.xyz/zephyr/horse/horse"
|
|
||||||
|
|
||||||
const (
|
|
||||||
{{- range $uma := $.Umas }}
|
|
||||||
{{ goenum $uma.CharacterName }}{{ goenum $uma.Variant }} UmaID = {{ $uma.ID }} // {{ $uma.Name }}
|
|
||||||
{{- end }}
|
|
||||||
)
|
|
||||||
|
|
||||||
var AllUmas = map[UmaID]Uma{
|
|
||||||
{{- range $uma := $.Umas }}
|
|
||||||
{{ goenum $uma.CharacterName }}{{ goenum $uma.Variant }}: {
|
|
||||||
ID: {{ $uma.ID }},
|
|
||||||
CharacterID: {{ $uma.CharacterID }},
|
|
||||||
Name: {{ printf "%q" $uma.Name }},
|
|
||||||
Variant: {{ printf "%q" $uma.Variant }},
|
|
||||||
Sprint: {{ $uma.Sprint }},
|
|
||||||
Mile: {{ $uma.Mile }},
|
|
||||||
Medium: {{ $uma.Medium }},
|
|
||||||
Long: {{ $uma.Long }},
|
|
||||||
Front: {{ $uma.Front }},
|
|
||||||
Pace: {{ $uma.Pace }},
|
|
||||||
Late: {{ $uma.Late }},
|
|
||||||
End: {{ $uma.End }},
|
|
||||||
Turf: {{ $uma.Turf }},
|
|
||||||
Dirt: {{ $uma.Dirt }},
|
|
||||||
Unique: {{ $uma.UniqueID }},
|
|
||||||
Skill1: {{ $uma.Skill1 }},
|
|
||||||
Skill2: {{ $uma.Skill2 }},
|
|
||||||
Skill3: {{ $uma.Skill3 }},
|
|
||||||
SkillPL2: {{ $uma.SkillPL2 }},
|
|
||||||
SkillPL3: {{ $uma.SkillPL3 }},
|
|
||||||
SkillPL4: {{ $uma.SkillPL4 }},
|
|
||||||
SkillPL5: {{ $uma.SkillPL5 }},
|
|
||||||
},
|
|
||||||
{{- end }}
|
|
||||||
}
|
|
||||||
{{ end }}
|
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
{{- define "koka-uma" -}}
|
|
||||||
module horse/{{ $.Region }}/uma
|
|
||||||
|
|
||||||
// Automatically generated with horsegen; DO NOT EDIT
|
|
||||||
|
|
||||||
import std/core/delayed
|
|
||||||
import std/core/vector
|
|
||||||
import std/core-extras
|
|
||||||
import horse/game-id
|
|
||||||
import horse/movement
|
|
||||||
pub import horse/uma
|
|
||||||
|
|
||||||
extern create-id-table(): vector<int>
|
|
||||||
c inline "int32_t arr[] = { {{- range $uma := $.Umas }}{{ $uma.ID }},{{ end -}} };\nkk_vector_from_cint32array(arr, (kk_ssize_t){{ $.UmaCount }}, kk_context())"
|
|
||||||
js inline "[{{ range $uma := $.Umas }}{{ $uma.ID }},{{ end }}]"
|
|
||||||
// Vector of all Uma IDs in order for easy iterating.
|
|
||||||
pub val all = once(create-id-table)
|
|
||||||
|
|
||||||
// Get the name for an Uma.
|
|
||||||
// The name includes the costume variant, e.g. `[Special Dreamer] Special Week`.
|
|
||||||
// If no Uma matches the ID, the result contains the numeric ID.
|
|
||||||
pub fun show(uma: uma-id): string
|
|
||||||
match uma.game-id
|
|
||||||
{{- range $uma := $.Umas }}
|
|
||||||
{{ $uma.ID }} -> {{ printf "%q" $uma.Name }}
|
|
||||||
{{- end }}
|
|
||||||
x -> "uma " ++ x.show
|
|
||||||
|
|
||||||
// Get the costume variant for an Uma, e.g. `[Special Dreamer]`.
|
|
||||||
// If no Uma matches the ID, the result contains the numeric ID.
|
|
||||||
pub fun variant(uma: uma-id): string
|
|
||||||
match uma.game-id
|
|
||||||
{{- range $uma := $.Umas }}
|
|
||||||
{{ $uma.ID }} -> {{ printf "%q" $uma.Variant }}
|
|
||||||
{{- end }}
|
|
||||||
x -> "uma " ++ x.show
|
|
||||||
|
|
||||||
// Get the character ID for an Uma.
|
|
||||||
// If no Uma matches the ID, the result is an invalid ID.
|
|
||||||
pub fun character-id(uma: uma-id): character-id
|
|
||||||
match uma.game-id
|
|
||||||
{{- range $uma := $.Umas }}
|
|
||||||
{{ $uma.ID }} -> Character-id({{ $uma.CharacterID }})
|
|
||||||
{{- end }}
|
|
||||||
_ -> Character-id(0)
|
|
||||||
|
|
||||||
// Get the sprint aptitude for an Uma.
|
|
||||||
// If no Uma matches the ID, the result is G.
|
|
||||||
pub fun sprint(uma: uma-id): aptitude-level
|
|
||||||
match uma.game-id
|
|
||||||
{{- range $uma := $.Umas }}
|
|
||||||
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Sprint }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> G
|
|
||||||
|
|
||||||
// Get the mile aptitude for an Uma.
|
|
||||||
// If no Uma matches the ID, the result is G.
|
|
||||||
pub fun mile(uma: uma-id): aptitude-level
|
|
||||||
match uma.game-id
|
|
||||||
{{- range $uma := $.Umas }}
|
|
||||||
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Mile }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> G
|
|
||||||
|
|
||||||
// Get the medium aptitude for an Uma.
|
|
||||||
// If no Uma matches the ID, the result is G.
|
|
||||||
pub fun medium(uma: uma-id): aptitude-level
|
|
||||||
match uma.game-id
|
|
||||||
{{- range $uma := $.Umas }}
|
|
||||||
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Medium }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> G
|
|
||||||
|
|
||||||
// Get the long aptitude for an Uma.
|
|
||||||
// If no Uma matches the ID, the result is G.
|
|
||||||
pub fun long(uma: uma-id): aptitude-level
|
|
||||||
match uma.game-id
|
|
||||||
{{- range $uma := $.Umas }}
|
|
||||||
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Long }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> G
|
|
||||||
|
|
||||||
// Get the front runner aptitude for an Uma.
|
|
||||||
// If no Uma matches the ID, the result is G.
|
|
||||||
pub fun front-runner(uma: uma-id): aptitude-level
|
|
||||||
match uma.game-id
|
|
||||||
{{- range $uma := $.Umas }}
|
|
||||||
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Front }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> G
|
|
||||||
|
|
||||||
// Get the pace chaser aptitude for an Uma.
|
|
||||||
// If no Uma matches the ID, the result is G.
|
|
||||||
pub fun pace-chaser(uma: uma-id): aptitude-level
|
|
||||||
match uma.game-id
|
|
||||||
{{- range $uma := $.Umas }}
|
|
||||||
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Pace }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> G
|
|
||||||
|
|
||||||
// Get the late surger aptitude for an Uma.
|
|
||||||
// If no Uma matches the ID, the result is G.
|
|
||||||
pub fun late-surger(uma: uma-id): aptitude-level
|
|
||||||
match uma.game-id
|
|
||||||
{{- range $uma := $.Umas }}
|
|
||||||
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Late }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> G
|
|
||||||
|
|
||||||
// Get the end closer aptitude for an Uma.
|
|
||||||
// If no Uma matches the ID, the result is G.
|
|
||||||
pub fun end-closer(uma: uma-id): aptitude-level
|
|
||||||
match uma.game-id
|
|
||||||
{{- range $uma := $.Umas }}
|
|
||||||
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.End }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> G
|
|
||||||
|
|
||||||
// Get the turf aptitude for an Uma.
|
|
||||||
// If no Uma matches the ID, the result is G.
|
|
||||||
pub fun turf(uma: uma-id): aptitude-level
|
|
||||||
match uma.game-id
|
|
||||||
{{- range $uma := $.Umas }}
|
|
||||||
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Turf }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> G
|
|
||||||
|
|
||||||
// Get the dirt aptitude for an Uma.
|
|
||||||
// If no Uma matches the ID, the result is G.
|
|
||||||
pub fun dirt(uma: uma-id): aptitude-level
|
|
||||||
match uma.game-id
|
|
||||||
{{- range $uma := $.Umas }}
|
|
||||||
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Dirt }}
|
|
||||||
{{- end }}
|
|
||||||
_ -> G
|
|
||||||
|
|
||||||
// Get the unique skill for an Uma.
|
|
||||||
// If no Uma matches the ID, the result is an invalid ID.
|
|
||||||
pub fun unique(uma: uma-id): skill-id
|
|
||||||
match uma.game-id
|
|
||||||
{{- range $uma := $.Umas }}
|
|
||||||
{{ $uma.ID }} -> Skill-id({{ $uma.UniqueID }})
|
|
||||||
{{- end }}
|
|
||||||
_ -> Skill-id(0)
|
|
||||||
|
|
||||||
// Get the first built-in skill for an Uma.
|
|
||||||
// If no Uma matches the ID, the result is an invalid ID.
|
|
||||||
pub fun skill1(uma: uma-id): skill-id
|
|
||||||
match uma.game-id
|
|
||||||
{{- range $uma := $.Umas }}
|
|
||||||
{{ $uma.ID }} -> Skill-id({{ $uma.Skill1 }})
|
|
||||||
{{- end }}
|
|
||||||
_ -> Skill-id(0)
|
|
||||||
|
|
||||||
// Get the second built-in skill for an Uma.
|
|
||||||
// If no Uma matches the ID, the result is an invalid ID.
|
|
||||||
pub fun skill2(uma: uma-id): skill-id
|
|
||||||
match uma.game-id
|
|
||||||
{{- range $uma := $.Umas }}
|
|
||||||
{{ $uma.ID }} -> Skill-id({{ $uma.Skill2 }})
|
|
||||||
{{- end }}
|
|
||||||
_ -> Skill-id(0)
|
|
||||||
|
|
||||||
// Get the third built-in skill for an Uma.
|
|
||||||
// If no Uma matches the ID, the result is an invalid ID.
|
|
||||||
pub fun skill3(uma: uma-id): skill-id
|
|
||||||
match uma.game-id
|
|
||||||
{{- range $uma := $.Umas }}
|
|
||||||
{{ $uma.ID }} -> Skill-id({{ $uma.Skill3 }})
|
|
||||||
{{- end }}
|
|
||||||
_ -> Skill-id(0)
|
|
||||||
|
|
||||||
// Get the potential level 2 skill for an Uma.
|
|
||||||
// If no Uma matches the ID, the result is an invalid ID.
|
|
||||||
pub fun skill-pl2(uma: uma-id): skill-id
|
|
||||||
match uma.game-id
|
|
||||||
{{- range $uma := $.Umas }}
|
|
||||||
{{ $uma.ID }} -> Skill-id({{ $uma.SkillPL2 }})
|
|
||||||
{{- end }}
|
|
||||||
_ -> Skill-id(0)
|
|
||||||
|
|
||||||
// Get the potential level 3 skill for an Uma.
|
|
||||||
// If no Uma matches the ID, the result is an invalid ID.
|
|
||||||
pub fun skill-pl3(uma: uma-id): skill-id
|
|
||||||
match uma.game-id
|
|
||||||
{{- range $uma := $.Umas }}
|
|
||||||
{{ $uma.ID }} -> Skill-id({{ $uma.SkillPL3 }})
|
|
||||||
{{- end }}
|
|
||||||
_ -> Skill-id(0)
|
|
||||||
|
|
||||||
// Get the potential level 4 skill for an Uma.
|
|
||||||
// If no Uma matches the ID, the result is an invalid ID.
|
|
||||||
pub fun skill-pl4(uma: uma-id): skill-id
|
|
||||||
match uma.game-id
|
|
||||||
{{- range $uma := $.Umas }}
|
|
||||||
{{ $uma.ID }} -> Skill-id({{ $uma.SkillPL4 }})
|
|
||||||
{{- end }}
|
|
||||||
_ -> Skill-id(0)
|
|
||||||
|
|
||||||
// Get the potential level 5 skill for an Uma.
|
|
||||||
// If no Uma matches the ID, the result is an invalid ID.
|
|
||||||
pub fun skill-pl5(uma: uma-id): skill-id
|
|
||||||
match uma.game-id
|
|
||||||
{{- range $uma := $.Umas }}
|
|
||||||
{{ $uma.ID }} -> Skill-id({{ $uma.SkillPL5 }})
|
|
||||||
{{- end }}
|
|
||||||
_ -> Skill-id(0)
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{- define "koka-aptitude-level" -}}
|
|
||||||
{{- if eq . 1 -}} G
|
|
||||||
{{- else if eq . 2 -}} F
|
|
||||||
{{- else if eq . 3 -}} E
|
|
||||||
{{- else if eq . 4 -}} D
|
|
||||||
{{- else if eq . 5 -}} C
|
|
||||||
{{- else if eq . 6 -}} B
|
|
||||||
{{- else if eq . 7 -}} A
|
|
||||||
{{- else if eq . 8 -}} S
|
|
||||||
{{- else -}} ??? aptitude={{ . }}
|
|
||||||
{{- end -}}
|
|
||||||
{{- end -}}
|
|
||||||
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>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user