Files
horse/cmd/horsegen/generate.go

122 lines
3.9 KiB
Go

package main
import (
"bufio"
"context"
_ "embed"
"encoding/json"
"errors"
"flag"
"log/slog"
"os"
"os/signal"
"path/filepath"
"golang.org/x/sync/errgroup"
"zombiezen.com/go/sqlite"
"zombiezen.com/go/sqlite/sqlitex"
"git.sunturtle.xyz/zephyr/horse/mdb"
)
func main() {
var (
mdbf string
out string
region string
)
flag.StringVar(&mdbf, "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(&region, "region", "global", "region the database is for (global, jp)")
flag.Parse()
slog.Info("open", slog.String("mdb", mdbf))
db, err := sqlitex.NewPool(mdbf, sqlitex.PoolOptions{Flags: sqlite.OpenReadOnly})
if err != nil {
slog.Error("opening mdb", slog.String("mdb", mdbf), 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", mdb.Characters)
aff := load(ctx1, loadgroup, db, "pair affinity", mdb.AffinitySummary)
umas := load(ctx1, loadgroup, db, "umas", mdb.Umas)
sg := load(ctx1, loadgroup, db, "skill groups", mdb.SkillGroups)
skills := load(ctx1, loadgroup, db, "skills", mdb.Skills)
races := load(ctx1, loadgroup, db, "races", mdb.Races)
saddles := load(ctx1, loadgroup, db, "saddles", mdb.Saddles)
scenarios := load(ctx1, loadgroup, db, "scenarios", mdb.Scenarios)
sparks := load(ctx1, loadgroup, db, "sparks", mdb.Sparks)
convos := load(ctx1, loadgroup, db, "lobby conversations", mdb.Conversations)
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", sparks) })
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")
}
func load[T any](ctx context.Context, group *errgroup.Group, db *sqlitex.Pool, kind string, get func(context.Context, *sqlitex.Pool) ([]T, error)) func() ([]T, error) {
slog.InfoContext(ctx, "load", slog.String("kind", kind))
var r []T
group.Go(func() error {
got, err := get(ctx, db)
r = got
return err
})
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
}