Compare commits
2 Commits
b55e1bc200
...
c00d3d0186
| Author | SHA1 | Date | |
|---|---|---|---|
| c00d3d0186 | |||
| a534975601 |
1
cmd/horsebot/.gitignore
vendored
Normal file
1
cmd/horsebot/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
token
|
||||||
10
cmd/horsebot/README.md
Normal file
10
cmd/horsebot/README.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# horsebot
|
||||||
|
|
||||||
|
Discord bot serving horse game data.
|
||||||
|
|
||||||
|
Production instance is named Zenno Rob Roy, because she has read all about Umamusume and is always happy to share her knowledge and give recommendations.
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
The bot always uses the Gateway API.
|
||||||
|
If the `-http` argument is provided, it will also use the HTTP API, and `-key` must also be provided.
|
||||||
56
cmd/horsebot/autocomplete/autocomplete.go
Normal file
56
cmd/horsebot/autocomplete/autocomplete.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package autocomplete
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"cmp"
|
||||||
|
"slices"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/algo"
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set is an autocomplete set.
|
||||||
|
type Set[V any] struct {
|
||||||
|
keys []util.Chars
|
||||||
|
vals []V
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add associates a value with a key in the autocomplete set.
|
||||||
|
// The behavior is undefined if the key already has a value.
|
||||||
|
func (s *Set[V]) Add(key string, val V) {
|
||||||
|
k := util.ToChars([]byte(key))
|
||||||
|
i, _ := slices.BinarySearchFunc(s.keys, k, func(a, b util.Chars) int {
|
||||||
|
return bytes.Compare(a.Bytes(), b.Bytes())
|
||||||
|
})
|
||||||
|
s.keys = slices.Insert(s.keys, i, k)
|
||||||
|
s.vals = slices.Insert(s.vals, i, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find appends to r all values in the set with keys that key matches.
|
||||||
|
func (s *Set[V]) Find(r []V, key string) []V {
|
||||||
|
initFzf()
|
||||||
|
var (
|
||||||
|
p = []rune(key)
|
||||||
|
|
||||||
|
got []V
|
||||||
|
t []algo.Result
|
||||||
|
slab util.Slab
|
||||||
|
)
|
||||||
|
for i := range s.keys {
|
||||||
|
res, _ := algo.FuzzyMatchV2(false, true, true, &s.keys[i], p, false, &slab)
|
||||||
|
if res.Score <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
j, _ := slices.BinarySearchFunc(t, res, func(a, b algo.Result) int { return -cmp.Compare(a.Score, b.Score) })
|
||||||
|
// Insert after all other matches with the same score for stability.
|
||||||
|
for j < len(t) && t[j].Score == res.Score {
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
t = slices.Insert(t, j, res)
|
||||||
|
got = slices.Insert(got, j, s.vals[i])
|
||||||
|
}
|
||||||
|
return append(r, got...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var initFzf = sync.OnceFunc(func() { algo.Init("default") })
|
||||||
70
cmd/horsebot/autocomplete/autocomplete_test.go
Normal file
70
cmd/horsebot/autocomplete/autocomplete_test.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package autocomplete_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.sunturtle.xyz/zephyr/horse/cmd/horsebot/autocomplete"
|
||||||
|
)
|
||||||
|
|
||||||
|
func these(s ...string) []string { return s }
|
||||||
|
|
||||||
|
func TestAutocomplete(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
add []string
|
||||||
|
search string
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
add: nil,
|
||||||
|
search: "",
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exact",
|
||||||
|
add: these("bocchi"),
|
||||||
|
search: "bocchi",
|
||||||
|
want: these("bocchi"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "extra",
|
||||||
|
add: these("bocchi", "ryo", "nijika", "kita"),
|
||||||
|
search: "bocchi",
|
||||||
|
want: these("bocchi"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "short",
|
||||||
|
add: these("bocchi", "ryo", "nijika", "kita"),
|
||||||
|
search: "o",
|
||||||
|
want: these("bocchi", "ryo"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unrelated",
|
||||||
|
add: these("bocchi", "ryo", "nijika", "kita"),
|
||||||
|
search: "x",
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "map",
|
||||||
|
add: these("Corazón ☆ Ardiente"),
|
||||||
|
search: "corazo",
|
||||||
|
want: these("Corazón ☆ Ardiente"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
var set autocomplete.Set[string]
|
||||||
|
for _, s := range c.add {
|
||||||
|
set.Add(s, s)
|
||||||
|
}
|
||||||
|
got := set.Find(nil, c.search)
|
||||||
|
slices.Sort(c.want)
|
||||||
|
slices.Sort(got)
|
||||||
|
if !slices.Equal(c.want, got) {
|
||||||
|
t.Errorf("wrong results: want %q, got %q", c.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
55
cmd/horsebot/log.go
Normal file
55
cmd/horsebot/log.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"github.com/disgoorg/disgo/discord"
|
||||||
|
"github.com/disgoorg/disgo/handler"
|
||||||
|
)
|
||||||
|
|
||||||
|
func logMiddleware(next handler.Handler) handler.Handler {
|
||||||
|
return func(e *handler.InteractionEvent) error {
|
||||||
|
var msg string
|
||||||
|
attrs := make([]slog.Attr, 0, 8)
|
||||||
|
attrs = append(attrs,
|
||||||
|
slog.Uint64("interaction", uint64(e.Interaction.ID())),
|
||||||
|
slog.Uint64("user", uint64(e.Interaction.User().ID)),
|
||||||
|
)
|
||||||
|
if guild := e.Interaction.GuildID(); guild != nil {
|
||||||
|
attrs = append(attrs, slog.String("guild", guild.String()))
|
||||||
|
}
|
||||||
|
switch i := e.Interaction.(type) {
|
||||||
|
case discord.ApplicationCommandInteraction:
|
||||||
|
msg = "command"
|
||||||
|
attrs = append(attrs,
|
||||||
|
slog.String("name", i.Data.CommandName()),
|
||||||
|
slog.Int("type", int(i.Data.Type())),
|
||||||
|
)
|
||||||
|
switch data := i.Data.(type) {
|
||||||
|
case discord.SlashCommandInteractionData:
|
||||||
|
attrs = append(attrs, slog.String("path", data.CommandPath()))
|
||||||
|
}
|
||||||
|
|
||||||
|
case discord.AutocompleteInteraction:
|
||||||
|
msg = "autocomplete"
|
||||||
|
attrs = append(attrs,
|
||||||
|
slog.String("name", i.Data.CommandName),
|
||||||
|
slog.String("path", i.Data.CommandPath()),
|
||||||
|
slog.String("focus", i.Data.Focused().Name),
|
||||||
|
)
|
||||||
|
|
||||||
|
case discord.ComponentInteraction:
|
||||||
|
msg = "component"
|
||||||
|
attrs = append(attrs,
|
||||||
|
slog.Int("type", int(i.Data.Type())),
|
||||||
|
slog.String("custom", i.Data.CustomID()),
|
||||||
|
)
|
||||||
|
|
||||||
|
default:
|
||||||
|
slog.WarnContext(e.Ctx, "unknown interaction", slog.Any("event", e))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
slog.LogAttrs(e.Ctx, slog.LevelInfo, msg, attrs...)
|
||||||
|
return next(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
183
cmd/horsebot/main.go
Normal file
183
cmd/horsebot/main.go
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/disgoorg/disgo"
|
||||||
|
"github.com/disgoorg/disgo/bot"
|
||||||
|
"github.com/disgoorg/disgo/discord"
|
||||||
|
"github.com/disgoorg/disgo/handler"
|
||||||
|
"github.com/disgoorg/disgo/handler/middleware"
|
||||||
|
"github.com/disgoorg/disgo/httpserver"
|
||||||
|
"github.com/disgoorg/disgo/rest"
|
||||||
|
|
||||||
|
"git.sunturtle.xyz/zephyr/horse/horse"
|
||||||
|
"git.sunturtle.xyz/zephyr/horse/horse/global"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var (
|
||||||
|
tokenFile string
|
||||||
|
// http api options
|
||||||
|
addr string
|
||||||
|
route string
|
||||||
|
pubkey string
|
||||||
|
// logging options
|
||||||
|
level slog.Level
|
||||||
|
textfmt string
|
||||||
|
)
|
||||||
|
flag.StringVar(&tokenFile, "token", "", "`file` containing the Discord bot token")
|
||||||
|
flag.StringVar(&addr, "http", "", "`address` to bind HTTP API server")
|
||||||
|
flag.StringVar(&route, "route", "/interactions/callback", "`path` to serve HTTP API calls")
|
||||||
|
flag.StringVar(&pubkey, "key", "", "Discord public key")
|
||||||
|
flag.TextVar(&level, "log", slog.LevelInfo, "slog logging `level`")
|
||||||
|
flag.StringVar(&textfmt, "log-format", "text", "slog logging `format`, text or json")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
var lh slog.Handler
|
||||||
|
switch textfmt {
|
||||||
|
case "text":
|
||||||
|
lh = slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: level})
|
||||||
|
case "json":
|
||||||
|
lh = slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: level})
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(os.Stderr, "invalid log format %q, must be text or json", textfmt)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
slog.SetDefault(slog.New(lh))
|
||||||
|
|
||||||
|
token, err := os.ReadFile(tokenFile)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("reading token", slog.Any("err", err))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
token = bytes.TrimSuffix(token, []byte{'\n'})
|
||||||
|
|
||||||
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||||
|
|
||||||
|
r := handler.New()
|
||||||
|
r.DefaultContext(func() context.Context { return ctx })
|
||||||
|
r.Use(middleware.Go)
|
||||||
|
r.Use(logMiddleware)
|
||||||
|
r.Route("/skill", func(r handler.Router) {
|
||||||
|
r.SlashCommand("/", skillHandler)
|
||||||
|
r.Autocomplete("/", skillAutocomplete)
|
||||||
|
r.ButtonComponent("/{id}", skillButton)
|
||||||
|
})
|
||||||
|
|
||||||
|
opts := []bot.ConfigOpt{bot.WithDefaultGateway(), bot.WithEventListeners(r)}
|
||||||
|
if addr != "" {
|
||||||
|
if pubkey == "" {
|
||||||
|
slog.Error("Discord public key must be provided when using HTTP API")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
opts = append(opts, bot.WithHTTPServerConfigOpts(pubkey,
|
||||||
|
httpserver.WithAddress(addr),
|
||||||
|
httpserver.WithURL(route),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("connect", slog.String("disgo", disgo.Version))
|
||||||
|
client, err := disgo.New(string(token), opts...)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("building bot", slog.Any("err", err))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := handler.SyncCommands(client, commands, nil, rest.WithCtx(ctx)); err != nil {
|
||||||
|
slog.Error("syncing commands", slog.Any("err", err))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if addr != "" {
|
||||||
|
slog.Info("start HTTP server", slog.String("address", addr), slog.String("route", route))
|
||||||
|
if err := client.OpenHTTPServer(); err != nil {
|
||||||
|
slog.Error("starting HTTP server", slog.Any("err", err))
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slog.Info("start gateway")
|
||||||
|
if err := client.OpenGateway(ctx); err != nil {
|
||||||
|
slog.Error("starting gateway", slog.Any("err", err))
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
slog.Info("ready")
|
||||||
|
<-ctx.Done()
|
||||||
|
stop()
|
||||||
|
|
||||||
|
ctx, stop = context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer stop()
|
||||||
|
client.Close(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
var commands = []discord.ApplicationCommandCreate{
|
||||||
|
discord.SlashCommandCreate{
|
||||||
|
Name: "skill",
|
||||||
|
Description: "Umamusume skill data",
|
||||||
|
Options: []discord.ApplicationCommandOption{
|
||||||
|
discord.ApplicationCommandOptionString{
|
||||||
|
Name: "query",
|
||||||
|
Description: "Skill name or ID",
|
||||||
|
Required: true,
|
||||||
|
Autocomplete: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func skillHandler(data discord.SlashCommandInteractionData, e *handler.CommandEvent) error {
|
||||||
|
q := data.String("query")
|
||||||
|
id, err := strconv.ParseInt(q, 10, 32)
|
||||||
|
if err == nil {
|
||||||
|
// note inverted condition; this is when we have an id
|
||||||
|
id = int64(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 {
|
||||||
|
m := discord.MessageCreate{
|
||||||
|
Content: "That button produced an invalid skill ID. That's not supposed to happen.",
|
||||||
|
Flags: discord.MessageFlagEphemeral,
|
||||||
|
}
|
||||||
|
return e.CreateMessage(m)
|
||||||
|
}
|
||||||
|
m := discord.MessageUpdate{
|
||||||
|
Components: &[]discord.LayoutComponent{RenderSkill(horse.SkillID(id), global.AllSkills, global.SkillGroups)},
|
||||||
|
}
|
||||||
|
return e.UpdateMessage(m)
|
||||||
|
}
|
||||||
148
cmd/horsebot/skill.go
Normal file
148
cmd/horsebot/skill.go
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/disgoorg/disgo/discord"
|
||||||
|
|
||||||
|
"git.sunturtle.xyz/zephyr/horse/cmd/horsebot/autocomplete"
|
||||||
|
"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 {
|
||||||
|
s, ok := all[id]
|
||||||
|
if !ok {
|
||||||
|
return discord.NewContainer(discord.NewTextDisplayf("invalid skill ID %v made it to RenderSkill", id))
|
||||||
|
}
|
||||||
|
|
||||||
|
thumburl := fmt.Sprintf("https://gametora.com/images/umamusume/skill_icons/utx_ico_skill_%d.png", s.IconID)
|
||||||
|
top := "## " + s.Name
|
||||||
|
if s.UniqueOwner != "" {
|
||||||
|
top += "\n-# " + s.UniqueOwner
|
||||||
|
}
|
||||||
|
r := discord.NewContainer(
|
||||||
|
discord.NewSection(
|
||||||
|
discord.NewTextDisplay(top),
|
||||||
|
discord.NewTextDisplay(s.Description),
|
||||||
|
).WithAccessory(discord.NewThumbnail(thumburl)),
|
||||||
|
)
|
||||||
|
var skilltype string
|
||||||
|
switch {
|
||||||
|
case s.Rarity == 3, s.Rarity == 4, s.Rarity == 5:
|
||||||
|
// unique of various star levels
|
||||||
|
r.AccentColor = 0xaca4d4
|
||||||
|
skilltype = "Unique Skill"
|
||||||
|
case s.UniqueOwner != "":
|
||||||
|
r.AccentColor = 0xcccccc
|
||||||
|
skilltype = "Inherited Unique"
|
||||||
|
case s.Rarity == 2:
|
||||||
|
// rare (gold)
|
||||||
|
r.AccentColor = 0xd7c25b
|
||||||
|
skilltype = "Rare Skill"
|
||||||
|
case s.GroupRate == -1:
|
||||||
|
// negative (purple) skill
|
||||||
|
r.AccentColor = 0x9151d4
|
||||||
|
skilltype = "Negative Skill"
|
||||||
|
case !s.WitCheck:
|
||||||
|
// should be passive (green)
|
||||||
|
r.AccentColor = 0x66ae1c
|
||||||
|
skilltype = "Passive Skill"
|
||||||
|
case isDebuff(s):
|
||||||
|
// debuff (red)
|
||||||
|
r.AccentColor = 0xe34747
|
||||||
|
skilltype = "Debuff Skill"
|
||||||
|
case s.Rarity == 1:
|
||||||
|
// common (white)
|
||||||
|
r.AccentColor = 0xcccccc
|
||||||
|
skilltype = "Common Skill"
|
||||||
|
}
|
||||||
|
r.Components = append(r.Components, discord.NewSmallSeparator())
|
||||||
|
text := make([]string, 0, 3)
|
||||||
|
abils := make([]string, 0, 3)
|
||||||
|
for _, act := range s.Activations {
|
||||||
|
text, abils = text[:0], abils[:0]
|
||||||
|
if act.Precondition != "" {
|
||||||
|
text = append(text, "Precondition: "+formatCondition(act.Precondition))
|
||||||
|
}
|
||||||
|
text = append(text, "Condition: "+formatCondition(act.Condition))
|
||||||
|
var t string
|
||||||
|
switch {
|
||||||
|
case act.Duration < 0:
|
||||||
|
// passive; do nothing
|
||||||
|
case act.Duration == 0:
|
||||||
|
t = "Instantaneous "
|
||||||
|
case act.Duration >= 500e4:
|
||||||
|
t = "Permanent "
|
||||||
|
default:
|
||||||
|
t = "For " + act.Duration.String() + "s, "
|
||||||
|
}
|
||||||
|
for _, a := range act.Abilities {
|
||||||
|
abils = append(abils, a.String())
|
||||||
|
}
|
||||||
|
t += strings.Join(abils, ", ")
|
||||||
|
if act.Cooldown > 0 && act.Cooldown < 500e4 {
|
||||||
|
t += " on " + act.Cooldown.String() + "s cooldown"
|
||||||
|
}
|
||||||
|
text = append(text, t)
|
||||||
|
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)
|
||||||
|
r.Components = append(r.Components, discord.NewSmallSeparator(), l)
|
||||||
|
rel := make([]horse.Skill, 0, 4)
|
||||||
|
for _, id := range groups[s.Group] {
|
||||||
|
if id != 0 {
|
||||||
|
rel = append(rel, all[id])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(rel) > 1 {
|
||||||
|
buttons := make([]discord.InteractiveComponent, 0, 4)
|
||||||
|
for _, rs := range rel {
|
||||||
|
b := discord.NewSecondaryButton(rs.Name, fmt.Sprintf("/skill/%d", rs.ID))
|
||||||
|
if rs.ID == id {
|
||||||
|
b = b.AsDisabled()
|
||||||
|
}
|
||||||
|
buttons = append(buttons, b)
|
||||||
|
}
|
||||||
|
r.Components = append(r.Components, discord.NewActionRow(buttons...))
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatCondition(s string) string {
|
||||||
|
s = strings.ReplaceAll(s, "&", " & ")
|
||||||
|
if strings.ContainsRune(s, '@') {
|
||||||
|
return "```\n" + strings.ReplaceAll(s, "@", "\n@\n") + "```"
|
||||||
|
}
|
||||||
|
return "`" + s + "`"
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDebuff(s horse.Skill) bool {
|
||||||
|
for _, act := range s.Activations {
|
||||||
|
for _, a := range act.Abilities {
|
||||||
|
if a.Value < 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
})
|
||||||
14
go.mod
14
go.mod
@@ -1,20 +1,30 @@
|
|||||||
module git.sunturtle.xyz/zephyr/horse
|
module git.sunturtle.xyz/zephyr/horse
|
||||||
|
|
||||||
go 1.24.1
|
go 1.25.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/disgoorg/disgo v0.19.0-rc.15
|
||||||
|
github.com/junegunn/fzf v0.67.0
|
||||||
golang.org/x/sync v0.14.0
|
golang.org/x/sync v0.14.0
|
||||||
zombiezen.com/go/sqlite v1.4.2
|
zombiezen.com/go/sqlite v1.4.2
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/disgoorg/json/v2 v2.0.0 // indirect
|
||||||
|
github.com/disgoorg/omit v1.0.0 // indirect
|
||||||
|
github.com/disgoorg/snowflake/v2 v2.0.3 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
|
github.com/klauspost/compress v1.18.2 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
|
github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad // indirect
|
||||||
|
golang.org/x/crypto v0.46.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||||
golang.org/x/sys v0.33.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
modernc.org/libc v1.65.7 // indirect
|
modernc.org/libc v1.65.7 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.11.0 // indirect
|
modernc.org/memory v1.11.0 // indirect
|
||||||
|
|||||||
36
go.sum
36
go.sum
@@ -1,15 +1,41 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/disgoorg/disgo v0.19.0-rc.15 h1:x0NsV2gcbdjwuztsg2wYXw76p1Cpc8f6ByDrkPcfQtU=
|
||||||
|
github.com/disgoorg/disgo v0.19.0-rc.15/go.mod h1:14mgXzenkJqifkDmsEgU0zI1di6jNXodwX6L8geW33A=
|
||||||
|
github.com/disgoorg/json/v2 v2.0.0 h1:U16yy/ARK7/aEpzjjqK1b/KaqqGHozUdeVw/DViEzQI=
|
||||||
|
github.com/disgoorg/json/v2 v2.0.0/go.mod h1:jZTBC0nIE1WeetSEI3/Dka8g+qglb4FPVmp5I5HpEfI=
|
||||||
|
github.com/disgoorg/omit v1.0.0 h1:y0LkVUOyUHT8ZlnhIAeOZEA22UYykeysK8bLJ0SfT78=
|
||||||
|
github.com/disgoorg/omit v1.0.0/go.mod h1:RTmSARkf6PWT/UckwI0bV8XgWkWQoPppaT01rYKLcFQ=
|
||||||
|
github.com/disgoorg/snowflake/v2 v2.0.3 h1:3B+PpFjr7j4ad7oeJu4RlQ+nYOTadsKapJIzgvSI2Ro=
|
||||||
|
github.com/disgoorg/snowflake/v2 v2.0.3/go.mod h1:W6r7NUA7DwfZLwr00km6G4UnZ0zcoLBRufhkFWgAc4c=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/junegunn/fzf v0.67.0 h1:naiOdIkV5/ZCfHgKQIV/f5YDWowl95G6yyOQqW8FeSo=
|
||||||
|
github.com/junegunn/fzf v0.67.0/go.mod h1:xlXX2/rmsccKQUnr9QOXPDi5DyV9cM0UjKy/huScBeE=
|
||||||
|
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||||
|
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad h1:qIQkSlF5vAUHxEmTbaqt1hkJ/t6skqEGYiMag343ucI=
|
||||||
|
github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad/go.mod h1:/pA7k3zsXKdjjAiUhB5CjuKib9KJGCaLvZwtxGC8U0s=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||||
|
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||||
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=
|
||||||
@@ -17,12 +43,14 @@ 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.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
|
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
|
||||||
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||||
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
|
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ const (
|
|||||||
RaceAsahiHaiFuturityStakes RaceID = 1022 // Asahi Hai Futurity Stakes
|
RaceAsahiHaiFuturityStakes RaceID = 1022 // Asahi Hai Futurity Stakes
|
||||||
RaceArimaKinen RaceID = 1023 // Arima Kinen
|
RaceArimaKinen RaceID = 1023 // Arima Kinen
|
||||||
RaceHopefulStakes RaceID = 1024 // Hopeful Stakes
|
RaceHopefulStakes RaceID = 1024 // Hopeful Stakes
|
||||||
RaceTakarazukaKinenAlternate RaceID = 1025 // Takarazuka Kinen
|
RaceTakarazukaKinenAlt1025 RaceID = 1025 // Takarazuka Kinen
|
||||||
RaceKikukaShoAlternate RaceID = 1026 // Kikuka Sho
|
RaceKikukaShoAlt1026 RaceID = 1026 // Kikuka Sho
|
||||||
RaceTennoShoSpringAlternate RaceID = 1027 // Tenno Sho (Spring)
|
RaceTennoShoSpringAlt1027 RaceID = 1027 // Tenno Sho (Spring)
|
||||||
RaceSatsukiShoAlternate RaceID = 1028 // Satsuki Sho
|
RaceSatsukiShoAlt1028 RaceID = 1028 // Satsuki Sho
|
||||||
RaceTeioSho RaceID = 1101 // Teio Sho
|
RaceTeioSho RaceID = 1101 // Teio Sho
|
||||||
RaceJapanDirtDerby RaceID = 1102 // Japan Dirt Derby
|
RaceJapanDirtDerby RaceID = 1102 // Japan Dirt Derby
|
||||||
RaceJBCLadiesClassic RaceID = 1103 // JBC Ladies’ Classic
|
RaceJBCLadiesClassic RaceID = 1103 // JBC Ladies’ Classic
|
||||||
@@ -73,7 +73,7 @@ const (
|
|||||||
RaceDailyHaiJuniorStakes RaceID = 2032 // Daily Hai Junior Stakes
|
RaceDailyHaiJuniorStakes RaceID = 2032 // Daily Hai Junior Stakes
|
||||||
RaceStayersStakes RaceID = 2033 // Stayers Stakes
|
RaceStayersStakes RaceID = 2033 // Stayers Stakes
|
||||||
RaceHanshinCup RaceID = 2034 // Hanshin Cup
|
RaceHanshinCup RaceID = 2034 // Hanshin Cup
|
||||||
RaceSpringStakesAlternate RaceID = 2035 // Spring Stakes
|
RaceSpringStakesAlt2035 RaceID = 2035 // Spring Stakes
|
||||||
RaceKyotoKimpai RaceID = 3001 // Kyoto Kimpai
|
RaceKyotoKimpai RaceID = 3001 // Kyoto Kimpai
|
||||||
RaceNakayamaKimpai RaceID = 3002 // Nakayama Kimpai
|
RaceNakayamaKimpai RaceID = 3002 // Nakayama Kimpai
|
||||||
RaceShinzanKinen RaceID = 3003 // Shinzan Kinen
|
RaceShinzanKinen RaceID = 3003 // Shinzan Kinen
|
||||||
@@ -413,27 +413,27 @@ var AllRaces = map[RaceID]Race{
|
|||||||
Name: "Hopeful Stakes",
|
Name: "Hopeful Stakes",
|
||||||
Thumbnail: 1024,
|
Thumbnail: 1024,
|
||||||
},
|
},
|
||||||
RaceTakarazukaKinenAlternate: {
|
RaceTakarazukaKinenAlt1025: {
|
||||||
ID: 1025,
|
ID: 1025,
|
||||||
Name: "Takarazuka Kinen" + " (Alternate)",
|
Name: "Takarazuka Kinen" + " (Alternate 1025)",
|
||||||
Thumbnail: 1012,
|
Thumbnail: 1012,
|
||||||
Primary: 1012,
|
Primary: 1012,
|
||||||
},
|
},
|
||||||
RaceKikukaShoAlternate: {
|
RaceKikukaShoAlt1026: {
|
||||||
ID: 1026,
|
ID: 1026,
|
||||||
Name: "Kikuka Sho" + " (Alternate)",
|
Name: "Kikuka Sho" + " (Alternate 1026)",
|
||||||
Thumbnail: 1015,
|
Thumbnail: 1015,
|
||||||
Primary: 1015,
|
Primary: 1015,
|
||||||
},
|
},
|
||||||
RaceTennoShoSpringAlternate: {
|
RaceTennoShoSpringAlt1027: {
|
||||||
ID: 1027,
|
ID: 1027,
|
||||||
Name: "Tenno Sho (Spring)" + " (Alternate)",
|
Name: "Tenno Sho (Spring)" + " (Alternate 1027)",
|
||||||
Thumbnail: 1027,
|
Thumbnail: 1027,
|
||||||
Primary: 1006,
|
Primary: 1006,
|
||||||
},
|
},
|
||||||
RaceSatsukiShoAlternate: {
|
RaceSatsukiShoAlt1028: {
|
||||||
ID: 1028,
|
ID: 1028,
|
||||||
Name: "Satsuki Sho" + " (Alternate)",
|
Name: "Satsuki Sho" + " (Alternate 1028)",
|
||||||
Thumbnail: 1028,
|
Thumbnail: 1028,
|
||||||
Primary: 1005,
|
Primary: 1005,
|
||||||
},
|
},
|
||||||
@@ -637,9 +637,9 @@ var AllRaces = map[RaceID]Race{
|
|||||||
Name: "Hanshin Cup",
|
Name: "Hanshin Cup",
|
||||||
Thumbnail: 2034,
|
Thumbnail: 2034,
|
||||||
},
|
},
|
||||||
RaceSpringStakesAlternate: {
|
RaceSpringStakesAlt2035: {
|
||||||
ID: 2035,
|
ID: 2035,
|
||||||
Name: "Spring Stakes" + " (Alternate)",
|
Name: "Spring Stakes" + " (Alternate 2035)",
|
||||||
Thumbnail: 2010,
|
Thumbnail: 2010,
|
||||||
Primary: 2010,
|
Primary: 2010,
|
||||||
},
|
},
|
||||||
@@ -1750,10 +1750,10 @@ var RaceNameToID = map[string]RaceID{
|
|||||||
"Asahi Hai Futurity Stakes": 1022,
|
"Asahi Hai Futurity Stakes": 1022,
|
||||||
"Arima Kinen": 1023,
|
"Arima Kinen": 1023,
|
||||||
"Hopeful Stakes": 1024,
|
"Hopeful Stakes": 1024,
|
||||||
"Takarazuka Kinen" + " (Alternate)": 1025,
|
"Takarazuka Kinen" + " (Alternate 1025)": 1025,
|
||||||
"Kikuka Sho" + " (Alternate)": 1026,
|
"Kikuka Sho" + " (Alternate 1026)": 1026,
|
||||||
"Tenno Sho (Spring)" + " (Alternate)": 1027,
|
"Tenno Sho (Spring)" + " (Alternate 1027)": 1027,
|
||||||
"Satsuki Sho" + " (Alternate)": 1028,
|
"Satsuki Sho" + " (Alternate 1028)": 1028,
|
||||||
"Teio Sho": 1101,
|
"Teio Sho": 1101,
|
||||||
"Japan Dirt Derby": 1102,
|
"Japan Dirt Derby": 1102,
|
||||||
"JBC Ladies’ Classic": 1103,
|
"JBC Ladies’ Classic": 1103,
|
||||||
@@ -1794,7 +1794,7 @@ var RaceNameToID = map[string]RaceID{
|
|||||||
"Daily Hai Junior Stakes": 2032,
|
"Daily Hai Junior Stakes": 2032,
|
||||||
"Stayers Stakes": 2033,
|
"Stayers Stakes": 2033,
|
||||||
"Hanshin Cup": 2034,
|
"Hanshin Cup": 2034,
|
||||||
"Spring Stakes" + " (Alternate)": 2035,
|
"Spring Stakes" + " (Alternate 2035)": 2035,
|
||||||
"Kyoto Kimpai": 3001,
|
"Kyoto Kimpai": 3001,
|
||||||
"Nakayama Kimpai": 3002,
|
"Nakayama Kimpai": 3002,
|
||||||
"Shinzan Kinen": 3003,
|
"Shinzan Kinen": 3003,
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ pub type race
|
|||||||
Asahi-Hai-Futurity-Stakes
|
Asahi-Hai-Futurity-Stakes
|
||||||
Arima-Kinen
|
Arima-Kinen
|
||||||
Hopeful-Stakes
|
Hopeful-Stakes
|
||||||
Takarazuka-Kinen-Alternate
|
Takarazuka-Kinen-Alt1025
|
||||||
Kikuka-Sho-Alternate
|
Kikuka-Sho-Alt1026
|
||||||
Tenno-Sho-Spring-Alternate
|
Tenno-Sho-Spring-Alt1027
|
||||||
Satsuki-Sho-Alternate
|
Satsuki-Sho-Alt1028
|
||||||
Teio-Sho
|
Teio-Sho
|
||||||
Japan-Dirt-Derby
|
Japan-Dirt-Derby
|
||||||
JBC-Ladies-Classic
|
JBC-Ladies-Classic
|
||||||
@@ -76,7 +76,7 @@ pub type race
|
|||||||
Daily-Hai-Junior-Stakes
|
Daily-Hai-Junior-Stakes
|
||||||
Stayers-Stakes
|
Stayers-Stakes
|
||||||
Hanshin-Cup
|
Hanshin-Cup
|
||||||
Spring-Stakes-Alternate
|
Spring-Stakes-Alt2035
|
||||||
Kyoto-Kimpai
|
Kyoto-Kimpai
|
||||||
Nakayama-Kimpai
|
Nakayama-Kimpai
|
||||||
Shinzan-Kinen
|
Shinzan-Kinen
|
||||||
@@ -321,10 +321,10 @@ pub fun race-id(r: race): race-id
|
|||||||
Asahi-Hai-Futurity-Stakes -> Race-id(1022)
|
Asahi-Hai-Futurity-Stakes -> Race-id(1022)
|
||||||
Arima-Kinen -> Race-id(1023)
|
Arima-Kinen -> Race-id(1023)
|
||||||
Hopeful-Stakes -> Race-id(1024)
|
Hopeful-Stakes -> Race-id(1024)
|
||||||
Takarazuka-Kinen-Alternate -> Race-id(1025)
|
Takarazuka-Kinen-Alt1025 -> Race-id(1025)
|
||||||
Kikuka-Sho-Alternate -> Race-id(1026)
|
Kikuka-Sho-Alt1026 -> Race-id(1026)
|
||||||
Tenno-Sho-Spring-Alternate -> Race-id(1027)
|
Tenno-Sho-Spring-Alt1027 -> Race-id(1027)
|
||||||
Satsuki-Sho-Alternate -> Race-id(1028)
|
Satsuki-Sho-Alt1028 -> Race-id(1028)
|
||||||
Teio-Sho -> Race-id(1101)
|
Teio-Sho -> Race-id(1101)
|
||||||
Japan-Dirt-Derby -> Race-id(1102)
|
Japan-Dirt-Derby -> Race-id(1102)
|
||||||
JBC-Ladies-Classic -> Race-id(1103)
|
JBC-Ladies-Classic -> Race-id(1103)
|
||||||
@@ -365,7 +365,7 @@ pub fun race-id(r: race): race-id
|
|||||||
Daily-Hai-Junior-Stakes -> Race-id(2032)
|
Daily-Hai-Junior-Stakes -> Race-id(2032)
|
||||||
Stayers-Stakes -> Race-id(2033)
|
Stayers-Stakes -> Race-id(2033)
|
||||||
Hanshin-Cup -> Race-id(2034)
|
Hanshin-Cup -> Race-id(2034)
|
||||||
Spring-Stakes-Alternate -> Race-id(2035)
|
Spring-Stakes-Alt2035 -> Race-id(2035)
|
||||||
Kyoto-Kimpai -> Race-id(3001)
|
Kyoto-Kimpai -> Race-id(3001)
|
||||||
Nakayama-Kimpai -> Race-id(3002)
|
Nakayama-Kimpai -> Race-id(3002)
|
||||||
Shinzan-Kinen -> Race-id(3003)
|
Shinzan-Kinen -> Race-id(3003)
|
||||||
@@ -609,10 +609,10 @@ pub val all = [
|
|||||||
Asahi-Hai-Futurity-Stakes,
|
Asahi-Hai-Futurity-Stakes,
|
||||||
Arima-Kinen,
|
Arima-Kinen,
|
||||||
Hopeful-Stakes,
|
Hopeful-Stakes,
|
||||||
Takarazuka-Kinen-Alternate,
|
Takarazuka-Kinen-Alt1025,
|
||||||
Kikuka-Sho-Alternate,
|
Kikuka-Sho-Alt1026,
|
||||||
Tenno-Sho-Spring-Alternate,
|
Tenno-Sho-Spring-Alt1027,
|
||||||
Satsuki-Sho-Alternate,
|
Satsuki-Sho-Alt1028,
|
||||||
Teio-Sho,
|
Teio-Sho,
|
||||||
Japan-Dirt-Derby,
|
Japan-Dirt-Derby,
|
||||||
JBC-Ladies-Classic,
|
JBC-Ladies-Classic,
|
||||||
@@ -653,7 +653,7 @@ pub val all = [
|
|||||||
Daily-Hai-Junior-Stakes,
|
Daily-Hai-Junior-Stakes,
|
||||||
Stayers-Stakes,
|
Stayers-Stakes,
|
||||||
Hanshin-Cup,
|
Hanshin-Cup,
|
||||||
Spring-Stakes-Alternate,
|
Spring-Stakes-Alt2035,
|
||||||
Kyoto-Kimpai,
|
Kyoto-Kimpai,
|
||||||
Nakayama-Kimpai,
|
Nakayama-Kimpai,
|
||||||
Shinzan-Kinen,
|
Shinzan-Kinen,
|
||||||
@@ -897,10 +897,10 @@ val name2id: rbmap<string, race-id> = rb-map/empty()
|
|||||||
.set("Asahi Hai Futurity Stakes", Race-id(1022))
|
.set("Asahi Hai Futurity Stakes", Race-id(1022))
|
||||||
.set("Arima Kinen", Race-id(1023))
|
.set("Arima Kinen", Race-id(1023))
|
||||||
.set("Hopeful Stakes", Race-id(1024))
|
.set("Hopeful Stakes", Race-id(1024))
|
||||||
.set("Takarazuka Kinen" ++ " (Alternate)", Race-id(1025))
|
.set("Takarazuka Kinen" ++ " (Alternate 1025)", Race-id(1025))
|
||||||
.set("Kikuka Sho" ++ " (Alternate)", Race-id(1026))
|
.set("Kikuka Sho" ++ " (Alternate 1026)", Race-id(1026))
|
||||||
.set("Tenno Sho (Spring)" ++ " (Alternate)", Race-id(1027))
|
.set("Tenno Sho (Spring)" ++ " (Alternate 1027)", Race-id(1027))
|
||||||
.set("Satsuki Sho" ++ " (Alternate)", Race-id(1028))
|
.set("Satsuki Sho" ++ " (Alternate 1028)", Race-id(1028))
|
||||||
.set("Teio Sho", Race-id(1101))
|
.set("Teio Sho", Race-id(1101))
|
||||||
.set("Japan Dirt Derby", Race-id(1102))
|
.set("Japan Dirt Derby", Race-id(1102))
|
||||||
.set("JBC Ladies’ Classic", Race-id(1103))
|
.set("JBC Ladies’ Classic", Race-id(1103))
|
||||||
@@ -941,7 +941,7 @@ val name2id: rbmap<string, race-id> = rb-map/empty()
|
|||||||
.set("Daily Hai Junior Stakes", Race-id(2032))
|
.set("Daily Hai Junior Stakes", Race-id(2032))
|
||||||
.set("Stayers Stakes", Race-id(2033))
|
.set("Stayers Stakes", Race-id(2033))
|
||||||
.set("Hanshin Cup", Race-id(2034))
|
.set("Hanshin Cup", Race-id(2034))
|
||||||
.set("Spring Stakes" ++ " (Alternate)", Race-id(2035))
|
.set("Spring Stakes" ++ " (Alternate 2035)", Race-id(2035))
|
||||||
.set("Kyoto Kimpai", Race-id(3001))
|
.set("Kyoto Kimpai", Race-id(3001))
|
||||||
.set("Nakayama Kimpai", Race-id(3002))
|
.set("Nakayama Kimpai", Race-id(3002))
|
||||||
.set("Shinzan Kinen", Race-id(3003))
|
.set("Shinzan Kinen", Race-id(3003))
|
||||||
@@ -1160,13 +1160,13 @@ val name2id: rbmap<string, race-id> = rb-map/empty()
|
|||||||
.set("Senryo Sho", Race-id(4526))
|
.set("Senryo Sho", Race-id(4526))
|
||||||
|
|
||||||
// Get the race ID that has the given exact name.
|
// Get the race ID that has the given exact name.
|
||||||
// Alternate versions of races have " (Alternate)" in their names.
|
// 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.
|
// If no race matches the name, the result is an invalid ID.
|
||||||
pub fun from-name(name: string): race-id
|
pub fun from-name(name: string): race-id
|
||||||
name2id.lookup(name).default(Race-id(0))
|
name2id.lookup(name).default(Race-id(0))
|
||||||
|
|
||||||
// Get the name for a race.
|
// Get the name for a race.
|
||||||
// Alternate versions of races have " (Alternate)" in their names.
|
// 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.
|
// If no race matches the ID, the result is the numeric ID.
|
||||||
pub fun show(r: race-id): string
|
pub fun show(r: race-id): string
|
||||||
match r.game-id
|
match r.game-id
|
||||||
@@ -1194,10 +1194,10 @@ pub fun show(r: race-id): string
|
|||||||
1022 -> "Asahi Hai Futurity Stakes"
|
1022 -> "Asahi Hai Futurity Stakes"
|
||||||
1023 -> "Arima Kinen"
|
1023 -> "Arima Kinen"
|
||||||
1024 -> "Hopeful Stakes"
|
1024 -> "Hopeful Stakes"
|
||||||
1025 -> "Takarazuka Kinen" ++ " (Alternate)"
|
1025 -> "Takarazuka Kinen" ++ " (Alternate 1025)"
|
||||||
1026 -> "Kikuka Sho" ++ " (Alternate)"
|
1026 -> "Kikuka Sho" ++ " (Alternate 1026)"
|
||||||
1027 -> "Tenno Sho (Spring)" ++ " (Alternate)"
|
1027 -> "Tenno Sho (Spring)" ++ " (Alternate 1027)"
|
||||||
1028 -> "Satsuki Sho" ++ " (Alternate)"
|
1028 -> "Satsuki Sho" ++ " (Alternate 1028)"
|
||||||
1101 -> "Teio Sho"
|
1101 -> "Teio Sho"
|
||||||
1102 -> "Japan Dirt Derby"
|
1102 -> "Japan Dirt Derby"
|
||||||
1103 -> "JBC Ladies’ Classic"
|
1103 -> "JBC Ladies’ Classic"
|
||||||
@@ -1238,7 +1238,7 @@ pub fun show(r: race-id): string
|
|||||||
2032 -> "Daily Hai Junior Stakes"
|
2032 -> "Daily Hai Junior Stakes"
|
||||||
2033 -> "Stayers Stakes"
|
2033 -> "Stayers Stakes"
|
||||||
2034 -> "Hanshin Cup"
|
2034 -> "Hanshin Cup"
|
||||||
2035 -> "Spring Stakes" ++ " (Alternate)"
|
2035 -> "Spring Stakes" ++ " (Alternate 2035)"
|
||||||
3001 -> "Kyoto Kimpai"
|
3001 -> "Kyoto Kimpai"
|
||||||
3002 -> "Nakayama Kimpai"
|
3002 -> "Nakayama Kimpai"
|
||||||
3003 -> "Shinzan Kinen"
|
3003 -> "Shinzan Kinen"
|
||||||
|
|||||||
@@ -151,18 +151,18 @@ pub type saddle
|
|||||||
Chunichi-Shimbun-Hai
|
Chunichi-Shimbun-Hai
|
||||||
Capella-S
|
Capella-S
|
||||||
Turquoise-S
|
Turquoise-S
|
||||||
Classic-Triple-Crown-Alt1
|
Classic-Triple-Crown-Alt144
|
||||||
Senior-Spring-Triple-Crown-Alt1
|
Senior-Spring-Triple-Crown-Alt145
|
||||||
Dual-Grand-Prix-Alt1
|
Dual-Grand-Prix-Alt146
|
||||||
Takarazuka-Kinen-Alt1
|
Takarazuka-Kinen-Alt147
|
||||||
Kikuka-Sho-Alt1
|
Kikuka-Sho-Alt148
|
||||||
Spring-S-Alt1
|
Spring-S-Alt149
|
||||||
Aoi-S
|
Aoi-S
|
||||||
Senior-Spring-Triple-Crown-Alt2
|
Senior-Spring-Triple-Crown-Alt151
|
||||||
Tenno-Sweep-Alt1
|
Tenno-Sweep-Alt152
|
||||||
Tenno-Sho-Spring-Alt1
|
Tenno-Sho-Spring-Alt153
|
||||||
Classic-Triple-Crown-Alt2
|
Classic-Triple-Crown-Alt154
|
||||||
Satsuki-Sho-Alt1
|
Satsuki-Sho-Alt155
|
||||||
|
|
||||||
// Get the saddle ID for a saddle.
|
// Get the saddle ID for a saddle.
|
||||||
pub fun saddle-id(s: saddle): saddle-id
|
pub fun saddle-id(s: saddle): saddle-id
|
||||||
@@ -310,18 +310,18 @@ pub fun saddle-id(s: saddle): saddle-id
|
|||||||
Chunichi-Shimbun-Hai -> Saddle-id(141)
|
Chunichi-Shimbun-Hai -> Saddle-id(141)
|
||||||
Capella-S -> Saddle-id(142)
|
Capella-S -> Saddle-id(142)
|
||||||
Turquoise-S -> Saddle-id(143)
|
Turquoise-S -> Saddle-id(143)
|
||||||
Classic-Triple-Crown-Alt1 -> Saddle-id(144)
|
Classic-Triple-Crown-Alt144 -> Saddle-id(144)
|
||||||
Senior-Spring-Triple-Crown-Alt1 -> Saddle-id(145)
|
Senior-Spring-Triple-Crown-Alt145 -> Saddle-id(145)
|
||||||
Dual-Grand-Prix-Alt1 -> Saddle-id(146)
|
Dual-Grand-Prix-Alt146 -> Saddle-id(146)
|
||||||
Takarazuka-Kinen-Alt1 -> Saddle-id(147)
|
Takarazuka-Kinen-Alt147 -> Saddle-id(147)
|
||||||
Kikuka-Sho-Alt1 -> Saddle-id(148)
|
Kikuka-Sho-Alt148 -> Saddle-id(148)
|
||||||
Spring-S-Alt1 -> Saddle-id(149)
|
Spring-S-Alt149 -> Saddle-id(149)
|
||||||
Aoi-S -> Saddle-id(150)
|
Aoi-S -> Saddle-id(150)
|
||||||
Senior-Spring-Triple-Crown-Alt2 -> Saddle-id(151)
|
Senior-Spring-Triple-Crown-Alt151 -> Saddle-id(151)
|
||||||
Tenno-Sweep-Alt1 -> Saddle-id(152)
|
Tenno-Sweep-Alt152 -> Saddle-id(152)
|
||||||
Tenno-Sho-Spring-Alt1 -> Saddle-id(153)
|
Tenno-Sho-Spring-Alt153 -> Saddle-id(153)
|
||||||
Classic-Triple-Crown-Alt2 -> Saddle-id(154)
|
Classic-Triple-Crown-Alt154 -> Saddle-id(154)
|
||||||
Satsuki-Sho-Alt1 -> Saddle-id(155)
|
Satsuki-Sho-Alt155 -> Saddle-id(155)
|
||||||
|
|
||||||
// List of all saddles in ID order for easy iterating.
|
// List of all saddles in ID order for easy iterating.
|
||||||
pub val all = [
|
pub val all = [
|
||||||
@@ -468,22 +468,22 @@ pub val all = [
|
|||||||
Chunichi-Shimbun-Hai,
|
Chunichi-Shimbun-Hai,
|
||||||
Capella-S,
|
Capella-S,
|
||||||
Turquoise-S,
|
Turquoise-S,
|
||||||
Classic-Triple-Crown-Alt1,
|
Classic-Triple-Crown-Alt144,
|
||||||
Senior-Spring-Triple-Crown-Alt1,
|
Senior-Spring-Triple-Crown-Alt145,
|
||||||
Dual-Grand-Prix-Alt1,
|
Dual-Grand-Prix-Alt146,
|
||||||
Takarazuka-Kinen-Alt1,
|
Takarazuka-Kinen-Alt147,
|
||||||
Kikuka-Sho-Alt1,
|
Kikuka-Sho-Alt148,
|
||||||
Spring-S-Alt1,
|
Spring-S-Alt149,
|
||||||
Aoi-S,
|
Aoi-S,
|
||||||
Senior-Spring-Triple-Crown-Alt2,
|
Senior-Spring-Triple-Crown-Alt151,
|
||||||
Tenno-Sweep-Alt1,
|
Tenno-Sweep-Alt152,
|
||||||
Tenno-Sho-Spring-Alt1,
|
Tenno-Sho-Spring-Alt153,
|
||||||
Classic-Triple-Crown-Alt2,
|
Classic-Triple-Crown-Alt154,
|
||||||
Satsuki-Sho-Alt1,
|
Satsuki-Sho-Alt155,
|
||||||
]
|
]
|
||||||
|
|
||||||
// Get the name for a saddle.
|
// Get the name for a saddle.
|
||||||
// Alternate versions of saddles have an indication of such in their names.
|
// 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.
|
// If no saddle matches the ID, the result contains the numeric ID.
|
||||||
pub fun show(s: saddle-id): string
|
pub fun show(s: saddle-id): string
|
||||||
match s.game-id
|
match s.game-id
|
||||||
@@ -630,18 +630,18 @@ pub fun show(s: saddle-id): string
|
|||||||
141 -> "Chunichi Shimbun Hai"
|
141 -> "Chunichi Shimbun Hai"
|
||||||
142 -> "Capella S."
|
142 -> "Capella S."
|
||||||
143 -> "Turquoise S."
|
143 -> "Turquoise S."
|
||||||
144 -> "Classic Triple Crown" ++ " (Alternate 1)"
|
144 -> "Classic Triple Crown" ++ " (Alternate 144)"
|
||||||
145 -> "Senior Spring Triple Crown" ++ " (Alternate 1)"
|
145 -> "Senior Spring Triple Crown" ++ " (Alternate 145)"
|
||||||
146 -> "Dual Grand Prix" ++ " (Alternate 1)"
|
146 -> "Dual Grand Prix" ++ " (Alternate 146)"
|
||||||
147 -> "Takarazuka Kinen" ++ " (Alternate 1)"
|
147 -> "Takarazuka Kinen" ++ " (Alternate 147)"
|
||||||
148 -> "Kikuka Sho" ++ " (Alternate 1)"
|
148 -> "Kikuka Sho" ++ " (Alternate 148)"
|
||||||
149 -> "Spring S." ++ " (Alternate 1)"
|
149 -> "Spring S." ++ " (Alternate 149)"
|
||||||
150 -> "Aoi S."
|
150 -> "Aoi S."
|
||||||
151 -> "Senior Spring Triple Crown" ++ " (Alternate 2)"
|
151 -> "Senior Spring Triple Crown" ++ " (Alternate 151)"
|
||||||
152 -> "Tenno Sweep" ++ " (Alternate 1)"
|
152 -> "Tenno Sweep" ++ " (Alternate 152)"
|
||||||
153 -> "Tenno Sho (Spring)" ++ " (Alternate 1)"
|
153 -> "Tenno Sho (Spring)" ++ " (Alternate 153)"
|
||||||
154 -> "Classic Triple Crown" ++ " (Alternate 2)"
|
154 -> "Classic Triple Crown" ++ " (Alternate 154)"
|
||||||
155 -> "Satsuki Sho" ++ " (Alternate 1)"
|
155 -> "Satsuki Sho" ++ " (Alternate 155)"
|
||||||
x -> "saddle " ++ x.show
|
x -> "saddle " ++ x.show
|
||||||
|
|
||||||
// Get the list of races that entitle a horse to a saddle.
|
// Get the list of races that entitle a horse to a saddle.
|
||||||
|
|||||||
@@ -328,6 +328,7 @@ type Race struct {
|
|||||||
Grade int
|
Grade int
|
||||||
ThumbnailID int
|
ThumbnailID int
|
||||||
Primary int
|
Primary int
|
||||||
|
Alternate int
|
||||||
}
|
}
|
||||||
|
|
||||||
func Races(ctx context.Context, db *sqlitex.Pool) ([]Race, error) {
|
func Races(ctx context.Context, db *sqlitex.Pool) ([]Race, error) {
|
||||||
@@ -357,6 +358,7 @@ func Races(ctx context.Context, db *sqlitex.Pool) ([]Race, error) {
|
|||||||
Grade: stmt.ColumnInt(2),
|
Grade: stmt.ColumnInt(2),
|
||||||
ThumbnailID: stmt.ColumnInt(3),
|
ThumbnailID: stmt.ColumnInt(3),
|
||||||
Primary: stmt.ColumnInt(4),
|
Primary: stmt.ColumnInt(4),
|
||||||
|
Alternate: stmt.ColumnInt(5),
|
||||||
}
|
}
|
||||||
r = append(r, race)
|
r = append(r, race)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,15 +7,15 @@ import . "git.sunturtle.xyz/zephyr/horse/horse"
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
{{- range $r := $.Races }}
|
{{- range $r := $.Races }}
|
||||||
Race{{ goenum $r.Name }}{{ if ne $r.Primary $r.ID }}Alternate{{ end }} RaceID = {{ $r.ID }} // {{ $r.Name }}
|
Race{{ goenum $r.Name }}{{ if $r.Alternate }}Alt{{ $r.ID }}{{ end }} RaceID = {{ $r.ID }} // {{ $r.Name }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
)
|
)
|
||||||
|
|
||||||
var AllRaces = map[RaceID]Race{
|
var AllRaces = map[RaceID]Race{
|
||||||
{{- range $r := $.Races }}
|
{{- range $r := $.Races }}
|
||||||
Race{{ goenum $r.Name }}{{ if ne $r.Primary $r.ID }}Alternate{{ end }}: {
|
Race{{ goenum $r.Name }}{{ if $r.Alternate }}Alt{{ $r.ID }}{{ end }}: {
|
||||||
ID: {{ $r.ID }},
|
ID: {{ $r.ID }},
|
||||||
Name: {{ printf "%q" $r.Name }}{{ if ne $r.Primary $r.ID }} + " (Alternate)"{{ end }},
|
Name: {{ printf "%q" $r.Name }}{{ if $r.Alternate }} + " (Alternate {{ $r.ID }})"{{ end }},
|
||||||
Thumbnail: {{ $r.ThumbnailID }},
|
Thumbnail: {{ $r.ThumbnailID }},
|
||||||
{{- if ne $r.Primary $r.ID }}
|
{{- if ne $r.Primary $r.ID }}
|
||||||
Primary: {{ $r.Primary }},
|
Primary: {{ $r.Primary }},
|
||||||
@@ -26,7 +26,7 @@ var AllRaces = map[RaceID]Race{
|
|||||||
|
|
||||||
var RaceNameToID = map[string]RaceID{
|
var RaceNameToID = map[string]RaceID{
|
||||||
{{- range $r := $.Races }}
|
{{- range $r := $.Races }}
|
||||||
{{ printf "%q" $r.Name }}{{ if ne $r.Primary $r.ID }} + " (Alternate)"{{ end }}: {{ $r.ID }},
|
{{ printf "%q" $r.Name }}{{ if $r.Alternate }} + " (Alternate {{ $r.ID }})"{{ end }}: {{ $r.ID }},
|
||||||
{{- end }}
|
{{- end }}
|
||||||
}
|
}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|||||||
@@ -10,41 +10,41 @@ pub import horse/race
|
|||||||
// Enumeration of all races for type-safe programming.
|
// Enumeration of all races for type-safe programming.
|
||||||
pub type race
|
pub type race
|
||||||
{{- range $r := $.Races }}
|
{{- range $r := $.Races }}
|
||||||
{{ kkenum $r.Name }}{{ if ne $r.Primary $r.ID }}-Alternate{{ end }}
|
{{ kkenum $r.Name }}{{ if $r.Alternate }}-Alt{{ $r.ID }}{{ end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
// Get the race ID for a race.
|
// Get the race ID for a race.
|
||||||
pub fun race-id(r: race): race-id
|
pub fun race-id(r: race): race-id
|
||||||
match r
|
match r
|
||||||
{{- range $r := $.Races }}
|
{{- range $r := $.Races }}
|
||||||
{{ kkenum $r.Name }}{{ if ne $r.Primary $r.ID }}-Alternate{{ end }} -> Race-id({{ $r.ID }})
|
{{ kkenum $r.Name }}{{ if $r.Alternate }}-Alt{{ $r.ID }}{{ end }} -> Race-id({{ $r.ID }})
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
// List of all races in ID order for easy iterating.
|
// List of all races in ID order for easy iterating.
|
||||||
pub val all = [
|
pub val all = [
|
||||||
{{- range $r := $.Races }}
|
{{- range $r := $.Races }}
|
||||||
{{ kkenum $r.Name }}{{ if ne $r.Primary $r.ID }}-Alternate{{ end }},
|
{{ kkenum $r.Name }}{{ if $r.Alternate }}-Alt{{ $r.ID }}{{ end }},
|
||||||
{{- end }}
|
{{- end }}
|
||||||
]
|
]
|
||||||
|
|
||||||
val name2id: rbmap<string, race-id> = rb-map/empty()
|
val name2id: rbmap<string, race-id> = rb-map/empty()
|
||||||
{{- range $r := $.Races }}
|
{{- range $r := $.Races }}
|
||||||
.set({{ printf "%q" $r.Name }}{{ if ne $r.Primary $r.ID }} ++ " (Alternate)"{{ end }}, Race-id({{ $r.ID }}))
|
.set({{ printf "%q" $r.Name }}{{ if $r.Alternate }} ++ " (Alternate {{ $r.ID }})"{{ end }}, Race-id({{ $r.ID }}))
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
// Get the race ID that has the given exact name.
|
// Get the race ID that has the given exact name.
|
||||||
// Alternate versions of races have " (Alternate)" in their names.
|
// 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.
|
// If no race matches the name, the result is an invalid ID.
|
||||||
pub fun from-name(name: string): race-id
|
pub fun from-name(name: string): race-id
|
||||||
name2id.lookup(name).default(Race-id(0))
|
name2id.lookup(name).default(Race-id(0))
|
||||||
|
|
||||||
// Get the name for a race.
|
// Get the name for a race.
|
||||||
// Alternate versions of races have " (Alternate)" in their names.
|
// 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.
|
// If no race matches the ID, the result is the numeric ID.
|
||||||
pub fun show(r: race-id): string
|
pub fun show(r: race-id): string
|
||||||
match r.game-id
|
match r.game-id
|
||||||
{{- range $r := $.Races }}
|
{{- range $r := $.Races }}
|
||||||
{{ $r.ID }} -> {{ printf "%q" $r.Name }}{{ if ne $r.Primary $r.ID }} ++ " (Alternate)"{{ end }}
|
{{ $r.ID }} -> {{ printf "%q" $r.Name }}{{ if $r.Alternate }} ++ " (Alternate {{ $r.ID }})"{{ end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
x -> "race " ++ x.show
|
x -> "race " ++ x.show
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ pub fun thumbnail(r: race-id): race-thumbnail-id
|
|||||||
pub fun primary(r: race-id): race-id
|
pub fun primary(r: race-id): race-id
|
||||||
match r.game-id
|
match r.game-id
|
||||||
{{- range $r := $.Races }}
|
{{- range $r := $.Races }}
|
||||||
{{- if ne $r.ID $r.Primary }}
|
{{- if $r.Alternate }}
|
||||||
{{ $r.ID }} -> Race-id({{ $r.Primary }})
|
{{ $r.ID }} -> Race-id({{ $r.Primary }})
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ SELECT
|
|||||||
race_names.name,
|
race_names.name,
|
||||||
race.grade,
|
race.grade,
|
||||||
race.thumbnail_id,
|
race.thumbnail_id,
|
||||||
MIN(race.id) OVER (PARTITION BY race_names.name) AS "primary"
|
MIN(race.id) OVER (PARTITION BY race_names.name) AS "primary",
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY race_names.name ORDER BY race.id) - 1 AS "alternate"
|
||||||
FROM race
|
FROM race
|
||||||
JOIN race_names ON race.id = race_names.id
|
JOIN race_names ON race.id = race_names.id
|
||||||
WHERE race."group" = 1
|
WHERE race."group" = 1
|
||||||
|
|||||||
@@ -10,30 +10,30 @@ pub import horse/{{ $.Region }}/race
|
|||||||
// Enumeration of all saddles for type-safe programming.
|
// Enumeration of all saddles for type-safe programming.
|
||||||
pub type saddle
|
pub type saddle
|
||||||
{{- range $s := $.Saddles }}
|
{{- range $s := $.Saddles }}
|
||||||
{{ kkenum $s.Name }}{{ if $s.Alternate }}-Alt{{ $s.Alternate }}{{ end }}
|
{{ kkenum $s.Name }}{{ if $s.Alternate }}-Alt{{ $s.ID }}{{ end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
// Get the saddle ID for a saddle.
|
// Get the saddle ID for a saddle.
|
||||||
pub fun saddle-id(s: saddle): saddle-id
|
pub fun saddle-id(s: saddle): saddle-id
|
||||||
match s
|
match s
|
||||||
{{- range $s := $.Saddles }}
|
{{- range $s := $.Saddles }}
|
||||||
{{ kkenum $s.Name }}{{ if $s.Alternate }}-Alt{{ $s.Alternate }}{{ end }} -> Saddle-id({{ $s.ID }})
|
{{ kkenum $s.Name }}{{ if $s.Alternate }}-Alt{{ $s.ID }}{{ end }} -> Saddle-id({{ $s.ID }})
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
// List of all saddles in ID order for easy iterating.
|
// List of all saddles in ID order for easy iterating.
|
||||||
pub val all = [
|
pub val all = [
|
||||||
{{- range $s := $.Saddles }}
|
{{- range $s := $.Saddles }}
|
||||||
{{ kkenum $s.Name }}{{ if $s.Alternate }}-Alt{{ $s.Alternate }}{{ end }},
|
{{ kkenum $s.Name }}{{ if $s.Alternate }}-Alt{{ $s.ID }}{{ end }},
|
||||||
{{- end }}
|
{{- end }}
|
||||||
]
|
]
|
||||||
|
|
||||||
// Get the name for a saddle.
|
// Get the name for a saddle.
|
||||||
// Alternate versions of saddles have an indication of such in their names.
|
// 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.
|
// If no saddle matches the ID, the result contains the numeric ID.
|
||||||
pub fun show(s: saddle-id): string
|
pub fun show(s: saddle-id): string
|
||||||
match s.game-id
|
match s.game-id
|
||||||
{{- range $s := $.Saddles }}
|
{{- range $s := $.Saddles }}
|
||||||
{{ $s.ID }} -> {{ printf "%q" $s.Name }}{{ if $s.Alternate }} ++ " (Alternate {{ $s.Alternate }})"{{ end }}
|
{{ $s.ID }} -> {{ printf "%q" $s.Name }}{{ if $s.Alternate }} ++ " (Alternate {{ $s.ID }})"{{ end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
x -> "saddle " ++ x.show
|
x -> "saddle " ++ x.show
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user