Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d14919f6a | ||
|
|
381df5bd35 | ||
|
|
e5ebd1894c | ||
|
|
c43f6b8292 | ||
|
|
cccf0cc838 | ||
|
|
64f82136cb | ||
|
|
53c7079f46 | ||
|
|
1cfabb18f5 | ||
|
|
bf4e750825 | ||
|
|
57ffadb06a |
+188
@@ -0,0 +1,188 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
_ "embed"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"zombiezen.com/go/sqlite"
|
||||||
|
"zombiezen.com/go/sqlite/sqlitemigration"
|
||||||
|
"zombiezen.com/go/sqlite/sqlitex"
|
||||||
|
|
||||||
|
"git.sunturtle.xyz/zephyr/horse/umadump/ism"
|
||||||
|
)
|
||||||
|
|
||||||
|
const appID int32 = 0x4acedb
|
||||||
|
|
||||||
|
//go:embed schema-00.sql
|
||||||
|
var schema0 string
|
||||||
|
|
||||||
|
//go:embed schema-final.sql
|
||||||
|
var schemaFinal string
|
||||||
|
|
||||||
|
func opendb(uri, journalMode string) *sqlitemigration.Pool {
|
||||||
|
schema := sqlitemigration.Schema{
|
||||||
|
Migrations: []string{
|
||||||
|
schema0,
|
||||||
|
},
|
||||||
|
AppID: appID,
|
||||||
|
RepeatableMigration: schemaFinal,
|
||||||
|
}
|
||||||
|
j := "PRAGMA journal_mode=" + journalMode
|
||||||
|
opts := sqlitemigration.Options{
|
||||||
|
Flags: sqlite.OpenReadWrite | sqlite.OpenCreate | sqlite.OpenURI,
|
||||||
|
PrepareConn: func(conn *sqlite.Conn) error {
|
||||||
|
if journalMode == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
stmt, _, err := conn.PrepareTransient(j)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = stmt.Step()
|
||||||
|
return errors.Join(err, stmt.Finalize())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
db := sqlitemigration.NewPool(uri, schema, opts)
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func insert(ctx context.Context, db *sqlitemigration.Pool, log *slog.Logger, career *ism.WorkIdleSingleMode, trainerID int64) (err error) {
|
||||||
|
conn, err := db.Take(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer db.Put(conn)
|
||||||
|
|
||||||
|
defer sqlitex.Transaction(conn)(&err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:embed insert-career.sql
|
||||||
|
var insertCareerSQL string
|
||||||
|
|
||||||
|
func insertCareer(conn *sqlite.Conn, career *ism.WorkIdleSingleMode, trainerID int64) error {
|
||||||
|
stmt := conn.Prep(insertCareerSQL)
|
||||||
|
stmt.SetInt64("$trainer_id", trainerID)
|
||||||
|
stmt.SetInt64("$trained_chara_id", int64(career.End.Chara.SingleModeCharaID))
|
||||||
|
stmt.SetInt64("$card_id", int64(career.End.Chara.CardID))
|
||||||
|
stmt.SetInt64("$scenario_id", int64(career.End.Chara.ScenarioID))
|
||||||
|
stmt.SetInt64("$route_id", int64(career.End.Chara.RouteID))
|
||||||
|
stmt.SetText("$start_time", career.ProgressInfo.StartTime)
|
||||||
|
stmt.SetText("$end_time", career.ProgressInfo.EndTime)
|
||||||
|
stmt.SetInt64("$chara_grade", int64(career.End.Chara.CharaGrade))
|
||||||
|
stmt.SetInt64("$star_level", int64(career.End.Chara.Rarity))
|
||||||
|
stmt.SetInt64("$potential_level", int64(career.End.Chara.TalentLevel))
|
||||||
|
stmt.SetInt64("$speed", int64(career.End.Chara.Speed))
|
||||||
|
stmt.SetInt64("$stamina", int64(career.End.Chara.Stamina))
|
||||||
|
stmt.SetInt64("$power", int64(career.End.Chara.Power))
|
||||||
|
stmt.SetInt64("$guts", int64(career.End.Chara.Guts))
|
||||||
|
stmt.SetInt64("$wit", int64(career.End.Chara.Wiz))
|
||||||
|
stmt.SetInt64("$energy", int64(career.End.Chara.Vital))
|
||||||
|
stmt.SetInt64("$max_speed", int64(career.End.Chara.MaxSpeed))
|
||||||
|
stmt.SetInt64("$max_stamina", int64(career.End.Chara.MaxStamina))
|
||||||
|
stmt.SetInt64("$max_power", int64(career.End.Chara.MaxPower))
|
||||||
|
stmt.SetInt64("$max_guts", int64(career.End.Chara.MaxGuts))
|
||||||
|
stmt.SetInt64("$max_wit", int64(career.End.Chara.MaxWiz))
|
||||||
|
stmt.SetInt64("$max_energy", int64(career.End.Chara.MaxVital))
|
||||||
|
stmt.SetInt64("$skill_point", int64(career.ProgressLog.TotalSkillPoint))
|
||||||
|
stmt.SetInt64("$mood", int64(career.End.Chara.Motivation))
|
||||||
|
stmt.SetInt64("$turf", int64(career.End.Chara.ProperGroundTurf))
|
||||||
|
stmt.SetInt64("$dirt", int64(career.End.Chara.ProperGroundDirt))
|
||||||
|
stmt.SetInt64("$sprint", int64(career.End.Chara.ProperDistanceShort))
|
||||||
|
stmt.SetInt64("$mile", int64(career.End.Chara.ProperDistanceMile))
|
||||||
|
stmt.SetInt64("$medium", int64(career.End.Chara.ProperDistanceMiddle))
|
||||||
|
stmt.SetInt64("$long", int64(career.End.Chara.ProperDistanceLong))
|
||||||
|
stmt.SetInt64("$front_runner", int64(career.End.Chara.ProperRunningStyleNige))
|
||||||
|
stmt.SetInt64("$pace_chaser", int64(career.End.Chara.ProperRunningStyleSenko))
|
||||||
|
stmt.SetInt64("$late_surger", int64(career.End.Chara.ProperRunningStyleSashi))
|
||||||
|
stmt.SetInt64("$end_closer", int64(career.End.Chara.ProperRunningStyleOikomi))
|
||||||
|
stmt.SetInt64("$start_speed", int64(career.ProgressInfo.Chara.Speed))
|
||||||
|
stmt.SetInt64("$start_stamina", int64(career.ProgressInfo.Chara.Stamina))
|
||||||
|
stmt.SetInt64("$start_power", int64(career.ProgressInfo.Chara.Power))
|
||||||
|
stmt.SetInt64("$start_guts", int64(career.ProgressInfo.Chara.Guts))
|
||||||
|
stmt.SetInt64("$start_wit", int64(career.ProgressInfo.Chara.Wiz))
|
||||||
|
stmt.SetInt64("$start_max_speed", int64(career.ProgressInfo.Chara.MaxSpeed))
|
||||||
|
stmt.SetInt64("$start_max_stamina", int64(career.ProgressInfo.Chara.MaxStamina))
|
||||||
|
stmt.SetInt64("$start_max_power", int64(career.ProgressInfo.Chara.MaxPower))
|
||||||
|
stmt.SetInt64("$start_max_guts", int64(career.ProgressInfo.Chara.MaxGuts))
|
||||||
|
stmt.SetInt64("$start_max_wit", int64(career.ProgressInfo.Chara.MaxWiz))
|
||||||
|
stmt.SetInt64("$start_turf", int64(career.ProgressInfo.Chara.ProperGroundTurf))
|
||||||
|
stmt.SetInt64("$start_dirt", int64(career.ProgressInfo.Chara.ProperGroundDirt))
|
||||||
|
stmt.SetInt64("$start_sprint", int64(career.ProgressInfo.Chara.ProperDistanceShort))
|
||||||
|
stmt.SetInt64("$start_mile", int64(career.ProgressInfo.Chara.ProperDistanceMile))
|
||||||
|
stmt.SetInt64("$start_medium", int64(career.ProgressInfo.Chara.ProperDistanceMiddle))
|
||||||
|
stmt.SetInt64("$start_long", int64(career.ProgressInfo.Chara.ProperDistanceLong))
|
||||||
|
stmt.SetInt64("$start_front_runner", int64(career.ProgressInfo.Chara.ProperRunningStyleNige))
|
||||||
|
stmt.SetInt64("$start_pace_chaser", int64(career.ProgressInfo.Chara.ProperRunningStyleSenko))
|
||||||
|
stmt.SetInt64("$start_late_surger", int64(career.ProgressInfo.Chara.ProperRunningStyleSashi))
|
||||||
|
stmt.SetInt64("$start_end_closer", int64(career.ProgressInfo.Chara.ProperRunningStyleOikomi))
|
||||||
|
stmt.SetInt64("$fans", int64(career.End.Chara.Fans))
|
||||||
|
stmt.SetInt64("$race_program_id", int64(career.End.Chara.RaceProgramID))
|
||||||
|
stmt.SetInt64("$reserve_race_program_id", int64(career.End.Chara.ReserveRaceProgramID))
|
||||||
|
stmt.SetInt64("$race_running_style", int64(career.End.Chara.RaceRunningStyle))
|
||||||
|
stmt.SetInt64("$is_short_race", int64(career.End.Chara.IsShortRace))
|
||||||
|
stmt.SetInt64("$succession_trained_chara_id_1", int64(career.OldChara.SuccessionTrainedCharaID1))
|
||||||
|
stmt.SetInt64("$succession_trained_chara_id_2", int64(career.OldChara.SuccessionTrainedCharaID2))
|
||||||
|
_, err := stmt.Step()
|
||||||
|
err = errors.Join(err, stmt.Reset())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("inserting race course: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertConditions(conn *sqlite.Conn, cs []ism.CharaEffectLog, trainerID int64, trainedChara int32) error {
|
||||||
|
const sql = `INSERT INTO condition VALUES (?, ?, ?, ?)`
|
||||||
|
stmt := conn.Prep(sql)
|
||||||
|
stmt.BindInt64(1, trainerID)
|
||||||
|
stmt.BindInt64(2, int64(trainedChara))
|
||||||
|
for _, c := range cs {
|
||||||
|
stmt.BindInt64(3, int64(c.CharaEffectID))
|
||||||
|
stmt.BindBool(4, c.IsActive)
|
||||||
|
_, err := stmt.Step()
|
||||||
|
err = errors.Join(err, stmt.Reset())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("inserting condition: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:embed insert-gain.sql
|
||||||
|
var insertGainSQL string
|
||||||
|
|
||||||
|
func insertGain(conn *sqlite.Conn, gain *ism.IdleSingleModeGainInfo, trainerID int64, trainedChara int32, source int32) error {
|
||||||
|
stmt := conn.Prep(insertGainSQL)
|
||||||
|
stmt.SetInt64("$trainer_id", trainerID)
|
||||||
|
stmt.SetInt64("$trained_chara_id", int64(trainedChara))
|
||||||
|
stmt.SetInt64("$source", int64(source))
|
||||||
|
stmt.SetInt64("$gain_speed", int64(gain.Speed.Value))
|
||||||
|
stmt.SetInt64("$gain_stamina", int64(gain.Stamina.Value))
|
||||||
|
stmt.SetInt64("$gain_power", int64(gain.Power.Value))
|
||||||
|
stmt.SetInt64("$gain_guts", int64(gain.Guts.Value))
|
||||||
|
stmt.SetInt64("$gain_wit", int64(gain.Wiz.Value))
|
||||||
|
stmt.SetInt64("$gain_skill_point", int64(gain.SkillPoint))
|
||||||
|
stmt.SetInt64("$gain_max_speed", int64(gain.MaxSpeed))
|
||||||
|
stmt.SetInt64("$gain_max_stamina", int64(gain.MaxStamina))
|
||||||
|
stmt.SetInt64("$gain_max_power", int64(gain.MaxPower))
|
||||||
|
stmt.SetInt64("$gain_max_guts", int64(gain.MaxGuts))
|
||||||
|
stmt.SetInt64("$gain_max_wit", int64(gain.MaxWiz))
|
||||||
|
stmt.SetInt64("$gain_turf", int64(gain.ProperGroundTurf))
|
||||||
|
stmt.SetInt64("$gain_dirt", int64(gain.ProperGroundDirt))
|
||||||
|
stmt.SetInt64("$gain_sprint", int64(gain.ProperDistanceShort))
|
||||||
|
stmt.SetInt64("$gain_mile", int64(gain.ProperDistanceMile))
|
||||||
|
stmt.SetInt64("$gain_medium", int64(gain.ProperDistanceMiddle))
|
||||||
|
stmt.SetInt64("$gain_long", int64(gain.ProperDistanceLong))
|
||||||
|
stmt.SetInt64("$gain_front_runner", int64(gain.ProperRunningStyleNige))
|
||||||
|
stmt.SetInt64("$gain_pace_chaser", int64(gain.ProperRunningStyleSenko))
|
||||||
|
stmt.SetInt64("$gain_late_surger", int64(gain.ProperRunningStyleSashi))
|
||||||
|
stmt.SetInt64("$gain_end_closer", int64(gain.ProperRunningStyleOikomi))
|
||||||
|
_, err := stmt.Step()
|
||||||
|
err = errors.Join(err, stmt.Reset())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("inserting gain info: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
INSERT INTO career VALUES (
|
||||||
|
$trainer_id,
|
||||||
|
$trained_chara_id,
|
||||||
|
$card_id,
|
||||||
|
$scenario_id,
|
||||||
|
$route_id,
|
||||||
|
$start_time,
|
||||||
|
$end_time,
|
||||||
|
|
||||||
|
$chara_grade,
|
||||||
|
$star_level,
|
||||||
|
$potential_level,
|
||||||
|
|
||||||
|
$speed,
|
||||||
|
$stamina,
|
||||||
|
$power,
|
||||||
|
$guts,
|
||||||
|
$wit,
|
||||||
|
$energy,
|
||||||
|
$max_speed,
|
||||||
|
$max_stamina,
|
||||||
|
$max_power,
|
||||||
|
$max_guts,
|
||||||
|
$max_wit,
|
||||||
|
$max_energy,
|
||||||
|
$skill_point,
|
||||||
|
$mood,
|
||||||
|
|
||||||
|
$turf,
|
||||||
|
$dirt,
|
||||||
|
$sprint,
|
||||||
|
$mile,
|
||||||
|
$medium,
|
||||||
|
$long,
|
||||||
|
$front_runner,
|
||||||
|
$pace_chaser,
|
||||||
|
$late_surger,
|
||||||
|
$end_closer,
|
||||||
|
|
||||||
|
$start_speed,
|
||||||
|
$start_stamina,
|
||||||
|
$start_power,
|
||||||
|
$start_guts,
|
||||||
|
$start_wit,
|
||||||
|
$start_max_speed,
|
||||||
|
$start_max_stamina,
|
||||||
|
$start_max_power,
|
||||||
|
$start_max_guts,
|
||||||
|
$start_max_wit,
|
||||||
|
$start_turf,
|
||||||
|
$start_dirt,
|
||||||
|
$start_sprint,
|
||||||
|
$start_mile,
|
||||||
|
$start_medium,
|
||||||
|
$start_long,
|
||||||
|
$start_front_runner,
|
||||||
|
$start_pace_chaser,
|
||||||
|
$start_late_surger,
|
||||||
|
$start_end_closer,
|
||||||
|
|
||||||
|
$fans,
|
||||||
|
$race_program_id,
|
||||||
|
$reserve_race_program_id,
|
||||||
|
$race_running_style,
|
||||||
|
$is_short_race,
|
||||||
|
|
||||||
|
$succession_trained_chara_id_1,
|
||||||
|
$succession_trained_chara_id_2
|
||||||
|
)
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
INSERT INTO gain VALUES (
|
||||||
|
$trainer_id,
|
||||||
|
$trained_chara_id,
|
||||||
|
$source,
|
||||||
|
|
||||||
|
$gain_speed,
|
||||||
|
$gain_stamina,
|
||||||
|
$gain_power,
|
||||||
|
$gain_guts,
|
||||||
|
$gain_wit,
|
||||||
|
$gain_skill_point,
|
||||||
|
|
||||||
|
$gain_max_speed,
|
||||||
|
$gain_max_stamina,
|
||||||
|
$gain_max_power,
|
||||||
|
$gain_max_guts,
|
||||||
|
$gain_max_wit,
|
||||||
|
|
||||||
|
$gain_turf,
|
||||||
|
$gain_dirt,
|
||||||
|
|
||||||
|
$gain_sprint,
|
||||||
|
$gain_mile,
|
||||||
|
$gain_medium,
|
||||||
|
$gain_long,
|
||||||
|
|
||||||
|
$gain_front_runner,
|
||||||
|
$gain_pace_chaser,
|
||||||
|
$gain_late_surger,
|
||||||
|
$gain_end_closer
|
||||||
|
)
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS career (
|
||||||
|
trainer_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
trained_chara_id INTEGER NOT NULL,
|
||||||
|
card_id INTEGER NOT NULL,
|
||||||
|
scenario_id INTEGER NOT NULL,
|
||||||
|
route_id INTEGER NOT NULL,
|
||||||
|
start_time TEXT NOT NULL,
|
||||||
|
end_time TEXT NOT NULL,
|
||||||
|
|
||||||
|
chara_grade INTEGER NOT NULL,
|
||||||
|
star_level INTEGER NOT NULL,
|
||||||
|
potential_level INTEGER NOT NULL,
|
||||||
|
|
||||||
|
speed INTEGER NOT NULL,
|
||||||
|
stamina INTEGER NOT NULL,
|
||||||
|
power INTEGER NOT NULL,
|
||||||
|
guts INTEGER NOT NULL,
|
||||||
|
wit INTEGER NOT NULL,
|
||||||
|
energy INTEGER NOT NULL,
|
||||||
|
max_speed INTEGER NOT NULL,
|
||||||
|
max_stamina INTEGER NOT NULL,
|
||||||
|
max_power INTEGER NOT NULL,
|
||||||
|
max_guts INTEGER NOT NULL,
|
||||||
|
max_wit INTEGER NOT NULL,
|
||||||
|
max_energy INTEGER NOT NULL,
|
||||||
|
skill_point INTEGER NOT NULL,
|
||||||
|
mood INTEGER NOT NULL,
|
||||||
|
|
||||||
|
turf INTEGER NOT NULL,
|
||||||
|
dirt INTEGER NOT NULL,
|
||||||
|
sprint INTEGER NOT NULL,
|
||||||
|
mile INTEGER NOT NULL,
|
||||||
|
medium INTEGER NOT NULL,
|
||||||
|
long INTEGER NOT NULL,
|
||||||
|
front_runner INTEGER NOT NULL,
|
||||||
|
pace_chaser INTEGER NOT NULL,
|
||||||
|
late_surger INTEGER NOT NULL,
|
||||||
|
end_closer INTEGER NOT NULL,
|
||||||
|
|
||||||
|
-- These fields are 0 if the career finished while it was not actively
|
||||||
|
-- displayed on the client used to dump it.
|
||||||
|
start_speed INTEGER NOT NULL,
|
||||||
|
start_stamina INTEGER NOT NULL,
|
||||||
|
start_power INTEGER NOT NULL,
|
||||||
|
start_guts INTEGER NOT NULL,
|
||||||
|
start_wit INTEGER NOT NULL,
|
||||||
|
start_max_speed INTEGER NOT NULL,
|
||||||
|
start_max_stamina INTEGER NOT NULL,
|
||||||
|
start_max_power INTEGER NOT NULL,
|
||||||
|
start_max_guts INTEGER NOT NULL,
|
||||||
|
start_max_wit INTEGER NOT NULL,
|
||||||
|
start_turf INTEGER NOT NULL,
|
||||||
|
start_dirt INTEGER NOT NULL,
|
||||||
|
start_sprint INTEGER NOT NULL,
|
||||||
|
start_mile INTEGER NOT NULL,
|
||||||
|
start_medium INTEGER NOT NULL,
|
||||||
|
start_long INTEGER NOT NULL,
|
||||||
|
start_front_runner INTEGER NOT NULL,
|
||||||
|
start_pace_chaser INTEGER NOT NULL,
|
||||||
|
start_late_surger INTEGER NOT NULL,
|
||||||
|
start_end_closer INTEGER NOT NULL,
|
||||||
|
|
||||||
|
fans INTEGER NOT NULL,
|
||||||
|
race_program_id INTEGER NOT NULL,
|
||||||
|
reserve_race_program_id INTEGER NOT NULL,
|
||||||
|
race_running_style INTEGER NOT NULL,
|
||||||
|
is_short_race INTEGER NOT NULL,
|
||||||
|
|
||||||
|
succession_trained_chara_id_1 INTEGER NOT NULL,
|
||||||
|
succession_trained_chara_id_2 INTEGER NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (trainer_id, trained_chara_id)
|
||||||
|
) STRICT;
|
||||||
|
CREATE INDEX IF NOT EXISTS career_by_scenario ON career (scenario_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS career_by_start_time ON career (start_time);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS condition (
|
||||||
|
trainer_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
trained_chara_id INTEGER NOT NULL,
|
||||||
|
condition INTEGER NOT NULL,
|
||||||
|
is_active INTEGER NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (trainer_id, trained_chara_id, condition),
|
||||||
|
FOREIGN KEY (trainer_id, trained_chara_id) REFERENCES career ON DELETE CASCADE
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS gain (
|
||||||
|
trainer_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
trained_chara_id INTEGER NOT NULL,
|
||||||
|
source INTEGER NOT NULL, -- 0 for events, 1 for inspiration, otherwise support card id
|
||||||
|
|
||||||
|
gain_speed INTEGER NOT NULL,
|
||||||
|
gain_stamina INTEGER NOT NULL,
|
||||||
|
gain_power INTEGER NOT NULL,
|
||||||
|
gain_guts INTEGER NOT NULL,
|
||||||
|
gain_wit INTEGER NOT NULL,
|
||||||
|
gain_skill_point INTEGER NOT NULL,
|
||||||
|
|
||||||
|
gain_max_speed INTEGER NOT NULL,
|
||||||
|
gain_max_stamina INTEGER NOT NULL,
|
||||||
|
gain_max_power INTEGER NOT NULL,
|
||||||
|
gain_max_guts INTEGER NOT NULL,
|
||||||
|
gain_max_wit INTEGER NOT NULL,
|
||||||
|
|
||||||
|
gain_turf INTEGER NOT NULL,
|
||||||
|
gain_dirt INTEGER NOT NULL,
|
||||||
|
|
||||||
|
gain_sprint INTEGER NOT NULL,
|
||||||
|
gain_mile INTEGER NOT NULL,
|
||||||
|
gain_medium INTEGER NOT NULL,
|
||||||
|
gain_long INTEGER NOT NULL,
|
||||||
|
|
||||||
|
gain_front_runner INTEGER NOT NULL,
|
||||||
|
gain_pace_chaser INTEGER NOT NULL,
|
||||||
|
gain_late_surger INTEGER NOT NULL,
|
||||||
|
gain_end_closer INTEGER NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (trainer_id, trained_chara_id, source),
|
||||||
|
FOREIGN KEY (trainer_id, trained_chara_id) REFERENCES career ON DELETE CASCADE
|
||||||
|
) STRICT;
|
||||||
|
CREATE INDEX IF NOT EXISTS gain_by_source ON gain (source);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS gain_skill_hint (
|
||||||
|
trainer_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
trained_chara_id INTEGER NOT NULL,
|
||||||
|
source INTEGER NOT NULL,
|
||||||
|
|
||||||
|
group_id INTEGER NOT NULL,
|
||||||
|
rarity INTEGER NOT NULL,
|
||||||
|
level INTEGER NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (trainer_id, trained_chara_id, source, group_id, rarity),
|
||||||
|
FOREIGN KEY (trainer_id, trained_chara_id, source) REFERENCES gain ON DELETE CASCADE
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS inspiration (
|
||||||
|
trainer_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
trained_chara_id INTEGER NOT NULL,
|
||||||
|
year INTEGER NOT NULL,
|
||||||
|
factor_id INTEGER NOT NULL,
|
||||||
|
level INTEGER NOT NULL, -- always 0 but just in case we get it in the future
|
||||||
|
|
||||||
|
FOREIGN KEY (trainer_id, trained_chara_id) REFERENCES career ON DELETE CASCADE
|
||||||
|
) STRICT;
|
||||||
|
CREATE INDEX IF NOT EXISTS inspiration_by_career ON inspiration (trainer_id, trained_chara_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS inspiration_by_factor_id ON inspiration (factor_id);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS race_history (
|
||||||
|
trainer_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
trained_chara_id INTEGER NOT NULL,
|
||||||
|
|
||||||
|
turn INTEGER NOT NULL,
|
||||||
|
program_id INTEGER NOT NULL,
|
||||||
|
weather INTEGER NOT NULL,
|
||||||
|
ground_condition INTEGER NOT NULL,
|
||||||
|
running_style INTEGER NOT NULL,
|
||||||
|
result_rank INTEGER NOT NULL,
|
||||||
|
frame_order INTEGER NOT NULL,
|
||||||
|
gained_fans INTEGER NOT NULL,
|
||||||
|
lose_tips_id INTEGER NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (trainer_id, trained_chara_id, turn),
|
||||||
|
FOREIGN KEY (trainer_id, trained_chara_id) REFERENCES career ON DELETE CASCADE
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS race_reward (
|
||||||
|
trainer_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
trained_chara_id INTEGER NOT NULL,
|
||||||
|
turn INTEGER NOT NULL,
|
||||||
|
|
||||||
|
item_type INTEGER NOT NULL,
|
||||||
|
item_id INTEGER NOT NULL,
|
||||||
|
item_num INTEGER NOT NULL,
|
||||||
|
|
||||||
|
is_bonus INTEGER NOT NULL,
|
||||||
|
is_plus_bonus INTEGER NOT NULL,
|
||||||
|
is_bonus_win INTEGER NOT NULL,
|
||||||
|
|
||||||
|
FOREIGN KEY (trainer_id, trained_chara_id, turn) REFERENCES race_history ON DELETE CASCADE
|
||||||
|
) STRICT;
|
||||||
|
CREATE INDEX IF NOT EXISTS race_reward_by_career_turn ON race_reward (trainer_id, trained_chara_id, turn);
|
||||||
|
CREATE INDEX IF NOT EXISTS race_reward_by_item ON race_reward (item_id);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS gain_skill (
|
||||||
|
trainer_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
trained_chara_id INTEGER NOT NULL,
|
||||||
|
source INTEGER NOT NULL,
|
||||||
|
skill_id INTEGER NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (trainer_id, trained_chara_id, source, skill_id),
|
||||||
|
FOREIGN KEY (trainer_id, trained_chara_id, source) REFERENCES gain ON DELETE CASCADE
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS career_skill (
|
||||||
|
trainer_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
trained_chara_id INTEGER NOT NULL,
|
||||||
|
skill_id INTEGER NOT NULL,
|
||||||
|
level INTEGER NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (trainer_id, trained_chara_id, skill_id),
|
||||||
|
FOREIGN KEY (trainer_id, trained_chara_id) REFERENCES career ON DELETE CASCADE
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS career_skill_hint (
|
||||||
|
trainer_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
trained_chara_id INTEGER NOT NULL,
|
||||||
|
|
||||||
|
group_id INTEGER NOT NULL,
|
||||||
|
rarity INTEGER NOT NULL,
|
||||||
|
level INTEGER NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (trainer_id, trained_chara_id, group_id, rarity),
|
||||||
|
FOREIGN KEY (trainer_id, trained_chara_id) REFERENCES career ON DELETE CASCADE
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS career_support_card (
|
||||||
|
trainer_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
trained_chara_id INTEGER NOT NULL,
|
||||||
|
position INTEGER NOT NULL,
|
||||||
|
support_card_id INTEGER NOT NULL,
|
||||||
|
limit_break_count INTEGER NOT NULL,
|
||||||
|
exp INTEGER NOT NULL,
|
||||||
|
owner_viewer_id INTEGER NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (trainer_id, trained_chara_id, position),
|
||||||
|
FOREIGN KEY (trainer_id, trained_chara_id) REFERENCES career ON DELETE CASCADE,
|
||||||
|
UNIQUE (trainer_id, trained_chara_id, support_card_id)
|
||||||
|
) STRICT;
|
||||||
|
CREATE INDEX IF NOT EXISTS career_support_card_by_card ON career_support_card (support_card_id, exp);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS start_skill (
|
||||||
|
trainer_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
trained_chara_id INTEGER NOT NULL,
|
||||||
|
skill_id INTEGER NOT NULL,
|
||||||
|
level INTEGER NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (trainer_id, trained_chara_id, skill_id),
|
||||||
|
FOREIGN KEY (trainer_id, trained_chara_id) REFERENCES career ON DELETE CASCADE
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS start_skill_hint (
|
||||||
|
trainer_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
trained_chara_id INTEGER NOT NULL,
|
||||||
|
|
||||||
|
group_id INTEGER NOT NULL,
|
||||||
|
rarity INTEGER NOT NULL,
|
||||||
|
level INTEGER NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (trainer_id, trained_chara_id, group_id, rarity),
|
||||||
|
FOREIGN KEY (trainer_id, trained_chara_id) REFERENCES career ON DELETE CASCADE
|
||||||
|
) STRICT;
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
DROP VIEW IF EXISTS gain_event;
|
||||||
|
DROP VIEW IF EXISTS gain_inspiration;
|
||||||
|
DROP VIEW IF EXISTS gain_support_card;
|
||||||
|
|
||||||
|
CREATE VIEW gain_event AS
|
||||||
|
SELECT
|
||||||
|
trainer_id,
|
||||||
|
trained_chara_id,
|
||||||
|
gain_speed,
|
||||||
|
gain_stamina,
|
||||||
|
gain_power,
|
||||||
|
gain_guts,
|
||||||
|
gain_wit,
|
||||||
|
gain_skill_point,
|
||||||
|
gain_max_speed,
|
||||||
|
gain_max_stamina,
|
||||||
|
gain_max_power,
|
||||||
|
gain_max_guts,
|
||||||
|
gain_max_wit,
|
||||||
|
gain_turf,
|
||||||
|
gain_dirt,
|
||||||
|
gain_sprint,
|
||||||
|
gain_mile,
|
||||||
|
gain_medium,
|
||||||
|
gain_long,
|
||||||
|
gain_front_runner,
|
||||||
|
gain_pace_chaser,
|
||||||
|
gain_late_surger,
|
||||||
|
gain_end_closer
|
||||||
|
FROM gain
|
||||||
|
WHERE source = 0;
|
||||||
|
|
||||||
|
CREATE VIEW gain_inspiration AS
|
||||||
|
SELECT
|
||||||
|
trainer_id,
|
||||||
|
trained_chara_id,
|
||||||
|
gain_speed,
|
||||||
|
gain_stamina,
|
||||||
|
gain_power,
|
||||||
|
gain_guts,
|
||||||
|
gain_wit,
|
||||||
|
gain_skill_point,
|
||||||
|
gain_max_speed,
|
||||||
|
gain_max_stamina,
|
||||||
|
gain_max_power,
|
||||||
|
gain_max_guts,
|
||||||
|
gain_max_wit,
|
||||||
|
gain_turf,
|
||||||
|
gain_dirt,
|
||||||
|
gain_sprint,
|
||||||
|
gain_mile,
|
||||||
|
gain_medium,
|
||||||
|
gain_long,
|
||||||
|
gain_front_runner,
|
||||||
|
gain_pace_chaser,
|
||||||
|
gain_late_surger,
|
||||||
|
gain_end_closer
|
||||||
|
FROM gain
|
||||||
|
WHERE source = 1;
|
||||||
|
|
||||||
|
CREATE VIEW gain_support_card AS
|
||||||
|
SELECT
|
||||||
|
trainer_id,
|
||||||
|
trained_chara_id,
|
||||||
|
source AS support_card_id,
|
||||||
|
gain_speed,
|
||||||
|
gain_stamina,
|
||||||
|
gain_power,
|
||||||
|
gain_guts,
|
||||||
|
gain_wit,
|
||||||
|
gain_skill_point,
|
||||||
|
gain_max_speed,
|
||||||
|
gain_max_stamina,
|
||||||
|
gain_max_power,
|
||||||
|
gain_max_guts,
|
||||||
|
gain_max_wit,
|
||||||
|
gain_turf,
|
||||||
|
gain_dirt,
|
||||||
|
gain_sprint,
|
||||||
|
gain_mile,
|
||||||
|
gain_medium,
|
||||||
|
gain_long,
|
||||||
|
gain_front_runner,
|
||||||
|
gain_pace_chaser,
|
||||||
|
gain_late_surger,
|
||||||
|
gain_end_closer
|
||||||
|
FROM gain
|
||||||
|
WHERE source > 1;
|
||||||
@@ -0,0 +1,325 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cmp"
|
||||||
|
"context"
|
||||||
|
_ "embed"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"zombiezen.com/go/sqlite"
|
||||||
|
"zombiezen.com/go/sqlite/sqlitemigration"
|
||||||
|
"zombiezen.com/go/sqlite/sqlitex"
|
||||||
|
|
||||||
|
"git.sunturtle.xyz/zephyr/horse/umadump/replay"
|
||||||
|
)
|
||||||
|
|
||||||
|
const appID int32 = 0x4acedb
|
||||||
|
|
||||||
|
//go:embed schema-00.sql
|
||||||
|
var schema0 string
|
||||||
|
|
||||||
|
//go:embed schema-final.sql
|
||||||
|
var schemaFinal string
|
||||||
|
|
||||||
|
func opendb(uri, journalMode string) *sqlitemigration.Pool {
|
||||||
|
schema := sqlitemigration.Schema{
|
||||||
|
Migrations: []string{
|
||||||
|
schema0,
|
||||||
|
},
|
||||||
|
AppID: appID,
|
||||||
|
RepeatableMigration: schemaFinal,
|
||||||
|
}
|
||||||
|
j := "PRAGMA journal_mode=" + journalMode
|
||||||
|
opts := sqlitemigration.Options{
|
||||||
|
Flags: sqlite.OpenReadWrite | sqlite.OpenCreate | sqlite.OpenURI,
|
||||||
|
PrepareConn: func(conn *sqlite.Conn) error {
|
||||||
|
if journalMode == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
stmt, _, err := conn.PrepareTransient(j)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = stmt.Step()
|
||||||
|
return errors.Join(err, stmt.Finalize())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
db := sqlitemigration.NewPool(uri, schema, opts)
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func insert(ctx context.Context, db *sqlitemigration.Pool, log *slog.Logger, race *replay.Race, simdata *replay.RaceSimulateData) (err error) {
|
||||||
|
conn, err := db.Take(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer db.Put(conn)
|
||||||
|
|
||||||
|
defer sqlitex.Transaction(conn)(&err)
|
||||||
|
|
||||||
|
log.DebugContext(ctx, "horses",
|
||||||
|
slog.Int("num", int(race.NumRaceHorses)),
|
||||||
|
slog.Int("actual", len(race.RaceHorse)),
|
||||||
|
)
|
||||||
|
var trainedCharas [18]int64
|
||||||
|
for i := range race.RaceHorse {
|
||||||
|
c := &race.RaceHorse[i]
|
||||||
|
id, err := insertTrainedChara(conn, c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
trainedCharas[i] = id
|
||||||
|
if err := insertTrainedCharaSkill(conn, c, id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.DebugContext(ctx, "horse",
|
||||||
|
slog.String("trainer", c.TrainerName),
|
||||||
|
slog.Int("trained", int(c.ResponseHorseData.TrainedCharaID)),
|
||||||
|
slog.Int("card", int(c.ResponseHorseData.CardID)),
|
||||||
|
slog.Int64("id", id),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if err = insertRaceCourseSet(conn, race); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.DebugContext(ctx, "course", slog.Int("id", int(race.RaceCourseSet.ID)))
|
||||||
|
configID, err := insertRaceConfig(conn, race)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.DebugContext(ctx, "config", slog.Int64("id", configID))
|
||||||
|
raceID, err := insertRace(conn, race, simdata, &trainedCharas, configID)
|
||||||
|
if err != nil {
|
||||||
|
code := sqlite.ErrCode(err)
|
||||||
|
if code == sqlite.ResultConstraintUnique {
|
||||||
|
log.InfoContext(ctx, "already loaded")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.DebugContext(ctx, "race", slog.Int64("id", raceID))
|
||||||
|
err = errors.Join(
|
||||||
|
insertRaceHorses(ctx, log, conn, race, simdata, &trainedCharas, raceID),
|
||||||
|
insertRaceFrames(ctx, log, conn, simdata, raceID),
|
||||||
|
insertRaceEvents(ctx, log, conn, simdata, raceID),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:embed insert-trained_chara.sql
|
||||||
|
var insertTrainedCharaSQL string
|
||||||
|
|
||||||
|
func insertTrainedChara(conn *sqlite.Conn, c *replay.HorseData) (int64, error) {
|
||||||
|
stmt := conn.Prep(insertTrainedCharaSQL)
|
||||||
|
trainer := cmp.Or(c.ResponseHorseData.ViewerID, c.ResponseHorseData.OwnerViewerID)
|
||||||
|
stmt.SetText("$trainer", c.TrainerName)
|
||||||
|
stmt.SetInt64("$trainer_id", trainer)
|
||||||
|
stmt.SetInt64("$trained_chara_id", int64(c.ResponseHorseData.TrainedCharaID))
|
||||||
|
stmt.SetInt64("$chara_id", int64(c.CharaID))
|
||||||
|
stmt.SetText("$chara_name", c.CharaName)
|
||||||
|
stmt.SetInt64("$card_id", int64(c.ResponseHorseData.CardID))
|
||||||
|
stmt.SetInt64("$rarity", int64(c.Rarity))
|
||||||
|
stmt.SetInt64("$mob_id", int64(c.MobID))
|
||||||
|
stmt.SetInt64("$raw_speed", int64(c.ResponseHorseData.Speed))
|
||||||
|
stmt.SetInt64("$raw_stamina", int64(c.ResponseHorseData.Stamina))
|
||||||
|
stmt.SetInt64("$raw_power", int64(c.ResponseHorseData.Pow))
|
||||||
|
stmt.SetInt64("$raw_guts", int64(c.ResponseHorseData.Guts))
|
||||||
|
stmt.SetInt64("$raw_wit", int64(c.ResponseHorseData.Wiz))
|
||||||
|
stmt.SetInt64("$turf", int64(c.ResponseHorseData.ProperGroundTurf))
|
||||||
|
stmt.SetInt64("$dirt", int64(c.ResponseHorseData.ProperGroundDirt))
|
||||||
|
stmt.SetInt64("$sprint", int64(c.ResponseHorseData.ProperDistanceShort))
|
||||||
|
stmt.SetInt64("$mile", int64(c.ResponseHorseData.ProperDistanceMile))
|
||||||
|
stmt.SetInt64("$medium", int64(c.ResponseHorseData.ProperDistanceMiddle))
|
||||||
|
stmt.SetInt64("$long", int64(c.ResponseHorseData.ProperDistanceLong))
|
||||||
|
stmt.SetInt64("$front_runner", int64(c.ResponseHorseData.ProperRunningStyleNige))
|
||||||
|
stmt.SetInt64("$pace_chaser", int64(c.ResponseHorseData.ProperRunningStyleSenko))
|
||||||
|
stmt.SetInt64("$late_surger", int64(c.ResponseHorseData.ProperRunningStyleSashi))
|
||||||
|
stmt.SetInt64("$end_closer", int64(c.ResponseHorseData.ProperRunningStyleOikomi))
|
||||||
|
stmt.SetInt64("$fans", int64(c.TrainedCharaData.Fans))
|
||||||
|
stmt.SetInt64("$rank", int64(c.TrainedCharaData.Rank))
|
||||||
|
stmt.SetInt64("$rank_score", int64(c.TrainedCharaData.RankScore))
|
||||||
|
r, err := sqlitex.ResultInt64(stmt)
|
||||||
|
err = errors.Join(err, stmt.Reset())
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("inserting trained chara: %w", err)
|
||||||
|
}
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertTrainedCharaSkill(conn *sqlite.Conn, c *replay.HorseData, id int64) error {
|
||||||
|
const sql = `INSERT OR IGNORE INTO trained_chara_skill VALUES (?, ?, ?)`
|
||||||
|
stmt := conn.Prep(sql)
|
||||||
|
stmt.BindInt64(1, id)
|
||||||
|
for _, sk := range c.ResponseHorseData.SkillArray {
|
||||||
|
stmt.BindInt64(2, int64(sk.SkillID))
|
||||||
|
stmt.BindInt64(3, int64(sk.Level))
|
||||||
|
_, err := stmt.Step()
|
||||||
|
err = errors.Join(err, stmt.Reset())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("inserting trained chara id %d skill %d: %w", id, sk.SkillID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertRaceCourseSet(conn *sqlite.Conn, race *replay.Race) error {
|
||||||
|
const sql = `INSERT OR IGNORE INTO race_course_set VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||||
|
stmt := conn.Prep(sql)
|
||||||
|
stmt.BindInt64(1, int64(race.RaceCourseSet.ID))
|
||||||
|
stmt.BindInt64(2, int64(race.RaceCourseSet.RaceTrackID))
|
||||||
|
stmt.BindInt64(3, int64(race.RaceCourseSet.Distance))
|
||||||
|
stmt.BindInt64(4, int64(race.RaceCourseSet.Ground))
|
||||||
|
stmt.BindInt64(5, int64(race.RaceCourseSet.Inout))
|
||||||
|
stmt.BindInt64(6, int64(race.RaceCourseSet.Turn))
|
||||||
|
stmt.BindInt64(7, int64(race.RaceCourseSet.FenceSet))
|
||||||
|
stmt.BindInt64(8, int64(race.RaceCourseSet.FloatLaneMax))
|
||||||
|
stmt.BindInt64(9, int64(race.RaceCourseSet.CourseSetStatusID))
|
||||||
|
stmt.BindInt64(10, int64(race.RaceCourseSet.FinishTimeMin))
|
||||||
|
stmt.BindInt64(11, int64(race.RaceCourseSet.FinishTimeMinRandomRange))
|
||||||
|
stmt.BindInt64(12, int64(race.RaceCourseSet.FinishTimeMax))
|
||||||
|
stmt.BindInt64(13, int64(race.RaceCourseSet.FinishTimeMaxRandomRange))
|
||||||
|
stmt.BindFloat(14, float64(race.CourseSectionDistance))
|
||||||
|
stmt.BindInt64(15, int64(race.CourseDistanceType))
|
||||||
|
stmt.BindInt64(16, int64(race.CourseFurlongNum))
|
||||||
|
stmt.BindFloat(17, float64(race.BaseSpeed))
|
||||||
|
_, err := stmt.Step()
|
||||||
|
err = errors.Join(err, stmt.Reset())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("inserting race course: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:embed insert-race_config.sql
|
||||||
|
var insertRaceConfigSQL string
|
||||||
|
|
||||||
|
func insertRaceConfig(conn *sqlite.Conn, race *replay.Race) (int64, error) {
|
||||||
|
stmt := conn.Prep(insertRaceConfigSQL)
|
||||||
|
stmt.SetInt64("$type", int64(race.RaceType))
|
||||||
|
stmt.SetInt64("$course_set_id", int64(race.RaceCourseSet.ID))
|
||||||
|
stmt.SetInt64("$ground_condition", int64(race.GroundCondition))
|
||||||
|
stmt.SetInt64("$weather", int64(race.Weather))
|
||||||
|
stmt.SetInt64("$season", int64(race.Season))
|
||||||
|
stmt.SetInt64("$time", int64(race.Time))
|
||||||
|
stmt.SetInt64("$num_horse", int64(race.NumRaceHorses))
|
||||||
|
r, err := sqlitex.ResultInt64(stmt)
|
||||||
|
err = errors.Join(err, stmt.Reset())
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("inserting race config: %w", err)
|
||||||
|
}
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:embed insert-race.sql
|
||||||
|
var insertRaceSQL string
|
||||||
|
|
||||||
|
func insertRace(conn *sqlite.Conn, race *replay.Race, simdata *replay.RaceSimulateData, trainedCharas *[18]int64, configID int64) (int64, error) {
|
||||||
|
stmt := conn.Prep(insertRaceSQL)
|
||||||
|
stmt.SetInt64("$config_id", configID)
|
||||||
|
stmt.SetInt64("$seed", int64(race.RandomSeed))
|
||||||
|
stmt.SetFloat("$distance_diff_max", float64(simdata.DistanceDiffMax))
|
||||||
|
stmt.SetInt64("$frame_count", int64(simdata.FrameCount))
|
||||||
|
// zombiezen/sqlite doesn't seem to expose an API to find the index of a
|
||||||
|
// named parameter, so even though we were using names before, we switch
|
||||||
|
// to indices for trained chara IDs.
|
||||||
|
for i, id := range trainedCharas {
|
||||||
|
stmt.BindInt64(5+i, id)
|
||||||
|
}
|
||||||
|
r, err := sqlitex.ResultInt64(stmt)
|
||||||
|
err = errors.Join(err, stmt.Reset())
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("inserting race: %w", err)
|
||||||
|
}
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:embed insert-race_horse.sql
|
||||||
|
var insertRaceHorseSQL string
|
||||||
|
|
||||||
|
func insertRaceHorses(ctx context.Context, log *slog.Logger, conn *sqlite.Conn, race *replay.Race, simdata *replay.RaceSimulateData, trainedCharas *[18]int64, raceID int64) error {
|
||||||
|
stmt := conn.Prep(insertRaceHorseSQL)
|
||||||
|
stmt.SetInt64("$race", raceID)
|
||||||
|
for i, c := range race.RaceHorse {
|
||||||
|
log.DebugContext(ctx, "race horse", slog.Int64("trained", trainedCharas[i]), slog.Int("index", int(c.HorseIndex)))
|
||||||
|
stmt.SetInt64("$horse", trainedCharas[i])
|
||||||
|
stmt.SetInt64("$horse_index", int64(c.HorseIndex))
|
||||||
|
stmt.SetInt64("$post", int64(c.PostNumber))
|
||||||
|
stmt.SetInt64("$finish_order", int64(c.FinishOrder))
|
||||||
|
stmt.SetFloat("$finish_time_raw", float64(c.FinishTimeRaw))
|
||||||
|
stmt.SetFloat("$finish_time_scaled", float64(c.FinishTimeScaled))
|
||||||
|
stmt.SetFloat("$finish_diff_time_from_prev", float64(c.FinishDiffTimeFromPrev))
|
||||||
|
stmt.SetInt64("$running_style", int64(c.ResponseHorseData.RunningStyle))
|
||||||
|
stmt.SetInt64("$running_style_ex", int64(c.RunningStyleEx))
|
||||||
|
stmt.SetInt64("$active_distance_aptitude", int64(c.ActiveProperDistance))
|
||||||
|
stmt.SetInt64("$active_surface_aptitude", int64(c.ActiveProperGroundType))
|
||||||
|
stmt.SetInt64("$mood", int64(c.RaceParam.Motivation))
|
||||||
|
stmt.SetFloat("$mood_coef", float64(c.RaceParam.MotivationCoef))
|
||||||
|
stmt.SetFloat("$base_speed", float64(c.RaceParam.BaseSpeed))
|
||||||
|
stmt.SetFloat("$base_stamina", float64(c.RaceParam.BaseStamina))
|
||||||
|
stmt.SetFloat("$base_power", float64(c.RaceParam.BasePow))
|
||||||
|
stmt.SetFloat("$base_guts", float64(c.RaceParam.BaseGuts))
|
||||||
|
stmt.SetFloat("$base_wit", float64(c.RaceParam.BaseWiz))
|
||||||
|
stmt.SetInt64("$popularity", int64(c.Popularity))
|
||||||
|
stmt.SetInt64("$popularity_rank_left", int64(c.PopularityRankLeft))
|
||||||
|
stmt.SetInt64("$popularity_rank_center", int64(c.PopularityRankCenter))
|
||||||
|
stmt.SetInt64("$popularity_rank_right", int64(c.PopularityRankRight))
|
||||||
|
stmt.SetFloat("$start_delay", float64(simdata.HorseResult[i].StartDelayTime))
|
||||||
|
stmt.SetFloat("$spurt_start_distance", float64(simdata.HorseResult[i].LastSpurtStartDistance))
|
||||||
|
_, err := stmt.Step()
|
||||||
|
err = errors.Join(err, stmt.Reset())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("inserting race horses: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertRaceFrames(ctx context.Context, log *slog.Logger, conn *sqlite.Conn, simdata *replay.RaceSimulateData, raceID int64) error {
|
||||||
|
const sql = `INSERT INTO race_frame VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||||
|
stmt := conn.Prep(sql)
|
||||||
|
stmt.BindInt64(1, raceID)
|
||||||
|
for _, f := range simdata.Frame {
|
||||||
|
log.DebugContext(ctx, "frame", slog.Float64("time", float64(f.Time)))
|
||||||
|
stmt.BindFloat(2, float64(f.Time))
|
||||||
|
for i, h := range f.HorseFrame {
|
||||||
|
stmt.BindInt64(3, int64(i))
|
||||||
|
stmt.BindFloat(4, float64(h.Distance))
|
||||||
|
stmt.BindInt64(5, int64(h.LanePosition))
|
||||||
|
stmt.BindInt64(6, int64(h.Speed))
|
||||||
|
stmt.BindInt64(7, int64(h.HP))
|
||||||
|
stmt.BindInt64(8, int64(h.TemptationMode))
|
||||||
|
stmt.BindInt64(9, int64(h.BlockFrontHorseIndex))
|
||||||
|
_, err := stmt.Step()
|
||||||
|
err = errors.Join(err, stmt.Reset())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("inserting race frames: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertRaceEvents(ctx context.Context, log *slog.Logger, conn *sqlite.Conn, simdata *replay.RaceSimulateData, raceID int64) error {
|
||||||
|
const sql = `INSERT INTO race_event VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||||
|
stmt := conn.Prep(sql)
|
||||||
|
for _, e := range simdata.Event {
|
||||||
|
log.DebugContext(ctx, "event", slog.Float64("time", float64(e.Event.FrameTime)), slog.Int("type", int(e.Event.Type)))
|
||||||
|
// Easier to clear all bindings every time now
|
||||||
|
// than to bind nulls to all the parameters that need them below.
|
||||||
|
stmt.ClearBindings()
|
||||||
|
stmt.BindInt64(1, raceID)
|
||||||
|
stmt.BindFloat(2, float64(e.Event.FrameTime))
|
||||||
|
stmt.BindInt64(3, int64(e.Event.Type))
|
||||||
|
for i, p := range e.Event.Param {
|
||||||
|
stmt.BindInt64(4+i, int64(p))
|
||||||
|
}
|
||||||
|
_, err := stmt.Step()
|
||||||
|
err = errors.Join(err, stmt.Reset())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("inserting race events: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
INSERT OR ROLLBACK INTO race (
|
||||||
|
config_id,
|
||||||
|
seed,
|
||||||
|
distance_diff_max,
|
||||||
|
frame_count,
|
||||||
|
horse_1,
|
||||||
|
horse_2,
|
||||||
|
horse_3,
|
||||||
|
horse_4,
|
||||||
|
horse_5,
|
||||||
|
horse_6,
|
||||||
|
horse_7,
|
||||||
|
horse_8,
|
||||||
|
horse_9,
|
||||||
|
horse_10,
|
||||||
|
horse_11,
|
||||||
|
horse_12,
|
||||||
|
horse_13,
|
||||||
|
horse_14,
|
||||||
|
horse_15,
|
||||||
|
horse_16,
|
||||||
|
horse_17,
|
||||||
|
horse_18
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$config_id,
|
||||||
|
$seed,
|
||||||
|
$distance_diff_max,
|
||||||
|
$frame_count,
|
||||||
|
$horse_1,
|
||||||
|
$horse_2,
|
||||||
|
$horse_3,
|
||||||
|
$horse_4,
|
||||||
|
$horse_5,
|
||||||
|
$horse_6,
|
||||||
|
$horse_7,
|
||||||
|
$horse_8,
|
||||||
|
$horse_9,
|
||||||
|
$horse_10,
|
||||||
|
$horse_11,
|
||||||
|
$horse_12,
|
||||||
|
$horse_13,
|
||||||
|
$horse_14,
|
||||||
|
$horse_15,
|
||||||
|
$horse_16,
|
||||||
|
$horse_17,
|
||||||
|
$horse_18
|
||||||
|
)
|
||||||
|
RETURNING id
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
INSERT INTO race_config (type, course_set_id, ground_condition, weather, season, time, num_horse)
|
||||||
|
VALUES ($type, $course_set_id, $ground_condition, $weather, $season, $time, $num_horse)
|
||||||
|
ON CONFLICT DO UPDATE SET id=id
|
||||||
|
RETURNING id
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
INSERT INTO race_horse
|
||||||
|
VALUES (
|
||||||
|
$race, $horse,
|
||||||
|
$horse_index, $post,
|
||||||
|
$finish_order, $finish_time_raw, $finish_time_scaled, $finish_diff_time_from_prev,
|
||||||
|
$running_style, $running_style_ex,
|
||||||
|
$active_distance_aptitude, $active_surface_aptitude,
|
||||||
|
$mood, $mood_coef,
|
||||||
|
$base_speed, $base_stamina, $base_power, $base_guts, $base_wit,
|
||||||
|
$popularity, $popularity_rank_left, $popularity_rank_center, $popularity_rank_right,
|
||||||
|
$start_delay, $spurt_start_distance
|
||||||
|
)
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
INSERT INTO trained_chara (
|
||||||
|
trainer, trainer_id, trained_chara_id,
|
||||||
|
chara_id, chara_name, card_id, rarity, mob_id,
|
||||||
|
raw_speed, raw_stamina, raw_power, raw_guts, raw_wit,
|
||||||
|
turf, dirt,
|
||||||
|
sprint, mile, medium, long,
|
||||||
|
front_runner, pace_chaser, late_surger, end_closer,
|
||||||
|
fans, rank, rank_score
|
||||||
|
) VALUES (
|
||||||
|
$trainer, $trainer_id, $trained_chara_id,
|
||||||
|
$chara_id, $chara_name, $card_id, $rarity, $mob_id,
|
||||||
|
$raw_speed, $raw_stamina, $raw_power, $raw_guts, $raw_wit,
|
||||||
|
$turf, $dirt,
|
||||||
|
$sprint, $mile, $medium, $long,
|
||||||
|
$front_runner, $pace_chaser, $late_surger, $end_closer,
|
||||||
|
$fans, $rank, $rank_score
|
||||||
|
)
|
||||||
|
ON CONFLICT DO UPDATE SET id=id
|
||||||
|
RETURNING id
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"git.sunturtle.xyz/zephyr/horse/umadump/replay"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var (
|
||||||
|
uri, journalMode string
|
||||||
|
fs []string
|
||||||
|
)
|
||||||
|
flag.StringVar(&uri, "db", "race.sqlite3", "output database `URI` or filename")
|
||||||
|
flag.StringVar(&journalMode, "journal", "memory", "journal `mode` to apply to database connections")
|
||||||
|
flag.Func("d", "`dir`ectory containing race dumps to load (repeatable)", func(s string) error {
|
||||||
|
es, err := os.ReadDir(s)
|
||||||
|
for _, e := range es {
|
||||||
|
if e.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fs = append(fs, filepath.Join(s, e.Name()))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
flag.Parse()
|
||||||
|
fs = append(fs, flag.Args()...)
|
||||||
|
|
||||||
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
stop()
|
||||||
|
}()
|
||||||
|
|
||||||
|
db := opendb(uri, journalMode)
|
||||||
|
ok := 0
|
||||||
|
for _, n := range fs {
|
||||||
|
log := slog.With(slog.String("file", n))
|
||||||
|
log.Info("begin")
|
||||||
|
f, err := os.Open(n)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("open", slog.Any("err", err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var race replay.Race
|
||||||
|
d := json.NewDecoder(f)
|
||||||
|
d.DisallowUnknownFields()
|
||||||
|
log.Debug("decode")
|
||||||
|
if err := d.Decode(&race); err != nil {
|
||||||
|
log.Error("load", slog.Any("err", err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Debug("simdata")
|
||||||
|
simdata, err := replay.DecodeSim(race.SimDataBase64)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("simdata", slog.Any("err", err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Debug("insert")
|
||||||
|
err = insert(ctx, db, log, &race, simdata)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("insert", slog.Any("err", err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ok++
|
||||||
|
}
|
||||||
|
slog.Info("done", slog.Int("races", ok))
|
||||||
|
}
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
-- trained_chara lists horses who appear in races.
|
||||||
|
CREATE TABLE IF NOT EXISTS trained_chara (
|
||||||
|
-- id is the DB-internal ID of the Uma.
|
||||||
|
-- It does not come from the game and should not be supplied in DDL.
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
|
||||||
|
trainer TEXT NOT NULL,
|
||||||
|
trainer_id INTEGER NOT NULL, -- 0 for NPC umas e.g. in practice races
|
||||||
|
trained_chara_id INTEGER NOT NULL,
|
||||||
|
chara_id INTEGER NOT NULL,
|
||||||
|
chara_name TEXT NOT NULL,
|
||||||
|
-- card_id is the Uma herself, e.g. 100201 for [Innocent Silence] Silence Suzuka.
|
||||||
|
card_id INTEGER NOT NULL,
|
||||||
|
rarity INTEGER NOT NULL,
|
||||||
|
mob_id INTEGER NOT NULL, -- 0 for Umas that are not mobs
|
||||||
|
|
||||||
|
-- Raw stats.
|
||||||
|
raw_speed INTEGER NOT NULL,
|
||||||
|
raw_stamina INTEGER NOT NULL,
|
||||||
|
raw_power INTEGER NOT NULL,
|
||||||
|
raw_guts INTEGER NOT NULL,
|
||||||
|
raw_wit INTEGER NOT NULL,
|
||||||
|
|
||||||
|
-- Aptitudes.
|
||||||
|
turf INTEGER NOT NULL,
|
||||||
|
dirt INTEGER NOT NULL,
|
||||||
|
sprint INTEGER NOT NULL,
|
||||||
|
mile INTEGER NOT NULL,
|
||||||
|
medium INTEGER NOT NULL,
|
||||||
|
long INTEGER NOT NULL,
|
||||||
|
front_runner INTEGER NOT NULL,
|
||||||
|
pace_chaser INTEGER NOT NULL,
|
||||||
|
late_surger INTEGER NOT NULL,
|
||||||
|
end_closer INTEGER NOT NULL,
|
||||||
|
|
||||||
|
-- Other data.
|
||||||
|
fans INTEGER NOT NULL,
|
||||||
|
rank INTEGER NOT NULL,
|
||||||
|
rank_score INTEGER NOT NULL,
|
||||||
|
|
||||||
|
UNIQUE (trainer_id, trained_chara_id)
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
-- Insert trained chara ID 0 to represent no Uma.
|
||||||
|
INSERT OR IGNORE INTO trained_chara (
|
||||||
|
id,
|
||||||
|
trainer,
|
||||||
|
trainer_id,
|
||||||
|
trained_chara_id,
|
||||||
|
chara_id, chara_name,
|
||||||
|
card_id,
|
||||||
|
mob_id,
|
||||||
|
raw_speed, raw_stamina, raw_power, raw_guts, raw_wit,
|
||||||
|
turf, dirt,
|
||||||
|
sprint, mile, medium, long,
|
||||||
|
front_runner, pace_chaser, late_surger, end_closer,
|
||||||
|
fans,
|
||||||
|
rank, rank_score
|
||||||
|
)
|
||||||
|
VALUES (0, '', 0, 0, 0, '', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
-- trained_chara_skill lists the skills each observed horse owns.
|
||||||
|
CREATE TABLE IF NOT EXISTS trained_chara_skill (
|
||||||
|
trained_chara INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||||
|
skill_id INTEGER NOT NULL,
|
||||||
|
level INTEGER NOT NULL,
|
||||||
|
|
||||||
|
UNIQUE (trained_chara, skill_id)
|
||||||
|
) STRICT;
|
||||||
|
CREATE INDEX IF NOT EXISTS trained_chara_skill_by_trained_chara ON trained_chara_skill (trained_chara);
|
||||||
|
|
||||||
|
-- race_course_set is the accumulated information of observed race courses.
|
||||||
|
CREATE TABLE IF NOT EXISTS race_course_set (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
race_track_id INTEGER NOT NULL,
|
||||||
|
distance INTEGER NOT NULL,
|
||||||
|
ground INTEGER NOT NULL,
|
||||||
|
inout INTEGER NOT NULL,
|
||||||
|
turn INTEGER NOT NULL,
|
||||||
|
fence_set INTEGER NOT NULL,
|
||||||
|
float_lane_max INTEGER NOT NULL,
|
||||||
|
course_set_status_id INTEGER NOT NULL,
|
||||||
|
finish_time_min INTEGER NOT NULL,
|
||||||
|
finish_time_min_random_range INTEGER NOT NULL,
|
||||||
|
finish_time_max INTEGER NOT NULL,
|
||||||
|
finish_time_max_random_range INTEGER NOT NULL,
|
||||||
|
|
||||||
|
section_distance REAL NOT NULL,
|
||||||
|
distance_type INTEGER NOT NULL,
|
||||||
|
furlong_number INTEGER NOT NULL,
|
||||||
|
base_speed REAL NOT NULL
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
-- race_config is observed configurations of races including the race type.
|
||||||
|
CREATE TABLE IF NOT EXISTS race_config (
|
||||||
|
-- id is the DB-internal ID of the race config.
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
type INTEGER NOT NULL,
|
||||||
|
course_set_id INTEGER NOT NULL REFERENCES race_course_set (id) ON DELETE RESTRICT,
|
||||||
|
ground_condition INTEGER NOT NULL,
|
||||||
|
weather INTEGER NOT NULL,
|
||||||
|
season INTEGER NOT NULL,
|
||||||
|
time INTEGER NOT NULL,
|
||||||
|
num_horse INTEGER NOT NULL,
|
||||||
|
|
||||||
|
UNIQUE (type, course_set_id, ground_condition, weather, season, time, num_horse)
|
||||||
|
) STRICT;
|
||||||
|
CREATE INDEX IF NOT EXISTS race_config_by_type ON race_config (type);
|
||||||
|
|
||||||
|
-- race is all observed races.
|
||||||
|
CREATE TABLE IF NOT EXISTS race (
|
||||||
|
-- id is the DB-internal ID of the race.
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
config_id INTEGER NOT NULL REFERENCES race_config (id),
|
||||||
|
seed INTEGER NOT NULL,
|
||||||
|
|
||||||
|
distance_diff_max REAL NOT NULL,
|
||||||
|
frame_count INTEGER NOT NULL,
|
||||||
|
|
||||||
|
-- There is no consistent ID for races, and even race seeds have been found
|
||||||
|
-- to collide well above the expected rate for a uniform distribution.
|
||||||
|
-- In order to prevent adding duplicates of races each time the tool is run,
|
||||||
|
-- we include the list of all horse IDs present in the race.
|
||||||
|
-- Analysis on the horses in the race should instead use the race_horse table.
|
||||||
|
-- Horse entries here are 0 where there is actually no horse at that gate.
|
||||||
|
horse_1 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||||
|
horse_2 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||||
|
horse_3 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||||
|
horse_4 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||||
|
horse_5 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||||
|
horse_6 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||||
|
horse_7 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||||
|
horse_8 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||||
|
horse_9 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||||
|
horse_10 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||||
|
horse_11 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||||
|
horse_12 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||||
|
horse_13 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||||
|
horse_14 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||||
|
horse_15 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||||
|
horse_16 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||||
|
horse_17 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||||
|
horse_18 INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
UNIQUE (
|
||||||
|
config_id,
|
||||||
|
seed,
|
||||||
|
horse_1,
|
||||||
|
horse_2,
|
||||||
|
horse_3,
|
||||||
|
horse_4,
|
||||||
|
horse_5,
|
||||||
|
horse_6,
|
||||||
|
horse_7,
|
||||||
|
horse_8,
|
||||||
|
horse_9,
|
||||||
|
horse_10,
|
||||||
|
horse_11,
|
||||||
|
horse_12,
|
||||||
|
horse_13,
|
||||||
|
horse_14,
|
||||||
|
horse_15,
|
||||||
|
horse_16,
|
||||||
|
horse_17,
|
||||||
|
horse_18
|
||||||
|
)
|
||||||
|
) STRICT;
|
||||||
|
CREATE INDEX IF NOT EXISTS race_by_config ON race (config_id);
|
||||||
|
|
||||||
|
-- race_horse pairs each instance of a horse with its condition and performance
|
||||||
|
-- in a given race.
|
||||||
|
CREATE TABLE IF NOT EXISTS race_horse (
|
||||||
|
race INTEGER NOT NULL REFERENCES race (id) ON DELETE CASCADE,
|
||||||
|
horse INTEGER NOT NULL REFERENCES trained_chara (id) ON DELETE CASCADE,
|
||||||
|
horse_index INTEGER NOT NULL,
|
||||||
|
post INTEGER NOT NULL,
|
||||||
|
finish_order INTEGER NOT NULL,
|
||||||
|
finish_time_raw REAL NOT NULL,
|
||||||
|
finish_time_scaled REAL NOT NULL,
|
||||||
|
finish_diff_time_from_prev REAL NOT NULL,
|
||||||
|
running_style INTEGER NOT NULL,
|
||||||
|
running_style_ex INTEGER NOT NULL,
|
||||||
|
active_distance_aptitude INTEGER NOT NULL,
|
||||||
|
active_surface_aptitude INTEGER NOT NULL,
|
||||||
|
mood INTEGER NOT NULL,
|
||||||
|
mood_coef REAL NOT NULL,
|
||||||
|
base_speed REAL NOT NULL,
|
||||||
|
base_stamina REAL NOT NULL,
|
||||||
|
base_power REAL NOT NULL,
|
||||||
|
base_guts REAL NOT NULL,
|
||||||
|
base_wit REAL NOT NULL,
|
||||||
|
popularity INTEGER NOT NULL,
|
||||||
|
popularity_rank_left INTEGER NOT NULL,
|
||||||
|
popularity_rank_center INTEGER NOT NULL,
|
||||||
|
popularity_rank_right INTEGER NOT NULL,
|
||||||
|
start_delay REAL NOT NULL,
|
||||||
|
spurt_start_distance REAL NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (race, horse),
|
||||||
|
UNIQUE (race, horse_index),
|
||||||
|
CHECK (horse != 0)
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
-- race_frame holds the actual received frames.
|
||||||
|
CREATE TABLE IF NOT EXISTS race_frame (
|
||||||
|
race INTEGER NOT NULL REFERENCES race (id) ON DELETE CASCADE,
|
||||||
|
frame_time REAL NOT NULL,
|
||||||
|
horse_index INTEGER NOT NULL,
|
||||||
|
distance REAL NOT NULL,
|
||||||
|
lane INTEGER NOT NULL, -- scale -4
|
||||||
|
speed INTEGER NOT NULL, -- scale -2
|
||||||
|
hp INTEGER NOT NULL,
|
||||||
|
temptation INTEGER NOT NULL,
|
||||||
|
block_front_horse_index INTEGER NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (race, frame_time, horse_index)
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
-- race_event holds raw received race events.
|
||||||
|
-- Views such as race_event_skill_activate, race_event_spot_struggle_activate, &c.
|
||||||
|
-- interpret events per type.
|
||||||
|
CREATE TABLE IF NOT EXISTS race_event (
|
||||||
|
race INTEGER NOT NULL REFERENCES race (id) ON DELETE CASCADE,
|
||||||
|
frame_time REAL NOT NULL,
|
||||||
|
type INTEGER NOT NULL,
|
||||||
|
param_0 INTEGER,
|
||||||
|
param_1 INTEGER,
|
||||||
|
param_2 INTEGER,
|
||||||
|
param_3 INTEGER,
|
||||||
|
param_4 INTEGER,
|
||||||
|
param_5 INTEGER
|
||||||
|
) STRICT;
|
||||||
|
CREATE INDEX IF NOT EXISTS race_event_by_race ON race_event (race);
|
||||||
|
CREATE INDEX IF NOT EXISTS race_event_by_type ON race_event (type);
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
DROP VIEW IF EXISTS race_event_skill_activate;
|
||||||
|
DROP VIEW IF EXISTS race_event_spot_struggle_activate;
|
||||||
|
DROP VIEW IF EXISTS race_event_duel_activate;
|
||||||
|
DROP VIEW IF EXISTS race_event_release_conserve_power;
|
||||||
|
|
||||||
|
CREATE VIEW race_event_skill_activate AS
|
||||||
|
SELECT
|
||||||
|
race,
|
||||||
|
frame_time,
|
||||||
|
param_0 AS user_index,
|
||||||
|
param_1 AS skill,
|
||||||
|
param_2 * 0.0001 AS duration,
|
||||||
|
param_3 AS branch,
|
||||||
|
param_4 AS hit_bitmask
|
||||||
|
FROM race_event WHERE type = 3;
|
||||||
|
|
||||||
|
CREATE VIEW race_event_spot_struggle_activate AS
|
||||||
|
SELECT
|
||||||
|
race,
|
||||||
|
frame_time,
|
||||||
|
param_0 AS user_index
|
||||||
|
FROM race_event WHERE type = 4;
|
||||||
|
|
||||||
|
CREATE VIEW race_event_duel_activate AS
|
||||||
|
SELECT
|
||||||
|
race,
|
||||||
|
frame_time,
|
||||||
|
param_0 AS user_index
|
||||||
|
FROM race_event WHERE type = 5;
|
||||||
|
|
||||||
|
CREATE VIEW race_event_release_conserve_power AS
|
||||||
|
SELECT
|
||||||
|
race,
|
||||||
|
frame_time,
|
||||||
|
param_0 AS user_index
|
||||||
|
FROM race_event WHERE type = 6;
|
||||||
+97
-1
@@ -1,6 +1,9 @@
|
|||||||
package ism
|
package ism
|
||||||
|
|
||||||
import "git.sunturtle.xyz/zephyr/horse"
|
import (
|
||||||
|
"git.sunturtle.xyz/zephyr/horse"
|
||||||
|
"git.sunturtle.xyz/zephyr/horse/umadump"
|
||||||
|
)
|
||||||
|
|
||||||
type Skill struct {
|
type Skill struct {
|
||||||
ID horse.SkillID `json:"skill_id"`
|
ID horse.SkillID `json:"skill_id"`
|
||||||
@@ -225,13 +228,106 @@ type AddItem struct {
|
|||||||
Number int32 `json:"number"`
|
Number int32 `json:"number"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PieceData struct {
|
||||||
|
PieceID int32 `json:"piece_id"`
|
||||||
|
PieceNum int32 `json:"piece_num"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserCard struct {
|
||||||
|
CardID horse.UmaID `json:"card_id"`
|
||||||
|
Rarity int8 `json:"rarity"`
|
||||||
|
TalentLevel int8 `json:"talent_level"`
|
||||||
|
Skills []Skill `json:"skill_data_array"`
|
||||||
|
CreateTime string `json:"create_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserSupportCard struct {
|
||||||
|
ViewerID int64 `json:"viewer_id"`
|
||||||
|
SupportCardID horse.SupportCardID `json:"support_card_id"`
|
||||||
|
Exp int32 `json:"exp"`
|
||||||
|
LimitBreak int32 `json:"limit_break_count"`
|
||||||
|
FavoriteFlag int32 `json:"favorite_flag"`
|
||||||
|
Stock int32 `json:"stock"`
|
||||||
|
PossessTime string `json:"possess_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RewardAddSupportCardNum struct {
|
||||||
|
SupportCardID horse.SupportCardID `json:"support_card_id"`
|
||||||
|
Number int32 `json:"number"`
|
||||||
|
ItemType int32 `json:"item_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HonorData struct {
|
||||||
|
HonorID int32 `json:"honor_id"`
|
||||||
|
Step int32 `json:"step"`
|
||||||
|
CreateTime string `json:"create_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserChara struct {
|
||||||
|
CharaID horse.CharacterID `json:"chara_id"`
|
||||||
|
TrainingNum int32 `json:"training_num"`
|
||||||
|
LovePoint int32 `json:"love_point"`
|
||||||
|
LovePointPool int32 `json:"love_point_pool"`
|
||||||
|
Fan uint64 `json:"fan"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserCloth struct {
|
||||||
|
ClothID int32 `json:"cloth_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserMusic struct {
|
||||||
|
MusicID int32 `json:"music_id"`
|
||||||
|
AcquisitionTime string `json:"acquisition_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CharaProfileData struct {
|
||||||
|
CharaID horse.CharacterID `json:"chara_id"`
|
||||||
|
DataID int32 `json:"data_id"`
|
||||||
|
NewFlag int32 `json:"new_flag"`
|
||||||
|
}
|
||||||
|
|
||||||
type RewardSummary struct {
|
type RewardSummary struct {
|
||||||
AddItem []AddItem `json:"add_item_list"`
|
AddItem []AddItem `json:"add_item_list"`
|
||||||
|
AddPiece []PieceData `json:"add_piece_list"`
|
||||||
|
AddCard []UserCard `json:"add_card_list"`
|
||||||
|
AddCardBonus *RewardSummary `json:"add_card_bonus_info"`
|
||||||
|
AddSupportCard []UserSupportCard `json:"add_support_card_list"`
|
||||||
|
AddSupportCardNum []RewardAddSupportCardNum `json:"add_support_card_num_array"`
|
||||||
|
AddHonor []HonorData `json:"add_honor_list"`
|
||||||
|
AddChara []UserChara `json:"add_chara_list"`
|
||||||
|
AddCloth []UserCloth `json:"add_cloth_list"`
|
||||||
|
AddMusic []UserMusic `json:"add_music_list"`
|
||||||
|
AddStoryIDs []umadump.Never `json:"add_story_id_array"` // not in my disassembly
|
||||||
|
AddFCoin int32 `json:"add_fcoin"` // carats
|
||||||
|
AddPresents int32 `json:"add_present_num"`
|
||||||
|
AddTotalFan int32 `json:"add_total_fan"`
|
||||||
|
NewCharaProfiles []CharaProfileData `json:"new_chara_profile_array"`
|
||||||
|
ForceUpdateHonorID int32 `json:"force_update_honor_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type End struct {
|
type End struct {
|
||||||
Chara SingleModeChara `json:"chara_info"`
|
Chara SingleModeChara `json:"chara_info"`
|
||||||
|
RaceConditions []umadump.Never `json:"race_condition_array"`
|
||||||
|
UncheckedEvents []umadump.Never `json:"unchecked_event_array"`
|
||||||
|
HomeInfo *umadump.Never `json:"home_info"`
|
||||||
|
WinSaddleIDs []umadump.Never `json:"win_saddle_id_array"`
|
||||||
|
EffectedFactors []umadump.Never `json:"effected_factor_array"`
|
||||||
|
RaceStartInfo *umadump.Never `json:"race_start_info"`
|
||||||
|
RaceScenario *umadump.Never `json:"race_scenario"`
|
||||||
|
AddTrophyInfo *umadump.Never `json:"add_trophy_info"`
|
||||||
|
TrophyRewardInfo *umadump.Never `json:"trophy_reward_info"`
|
||||||
|
PrevCharaGrade *umadump.Never `json:"prev_chara_grade"`
|
||||||
|
RaceAddRewardInfo []umadump.Never `json:"race_add_reward_info"`
|
||||||
|
Missions []umadump.Never `json:"mission_list"`
|
||||||
|
StoryEventMissions []umadump.Never `json:"story_event_mission_list"`
|
||||||
|
StoryEventCharaBonuses []umadump.Never `json:"story_event_chara_bonus_list"`
|
||||||
|
StartDressInfo []umadump.Never `json:"start_dress_info"`
|
||||||
|
ResumeFactorSelect *umadump.Never `json:"resume_factor_select"`
|
||||||
|
RaceRandomPrograms []umadump.Never `json:"race_random_program_array"`
|
||||||
|
RaceRewardLimitMore []umadump.Never `json:"race_reward_limit_more_list"`
|
||||||
|
SkillFilterSettings []umadump.Never `json:"skill_filter_setting_array"`
|
||||||
RewardSummary RewardSummary `json:"reward_summary_info"`
|
RewardSummary RewardSummary `json:"reward_summary_info"`
|
||||||
|
IsUmaplan bool `json:"is_umaplan"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProgressInfo struct {
|
type ProgressInfo struct {
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package umadump
|
||||||
|
|
||||||
|
type Never struct{}
|
||||||
|
|
||||||
|
func (Never) AppendText(b []byte) ([]byte, error) {
|
||||||
|
panic("umadump: attempt to encode value that should never exist")
|
||||||
|
}
|
||||||
|
func (Never) MarshalText() ([]byte, error) {
|
||||||
|
panic("umadump: attempt to encode value that should never exist")
|
||||||
|
}
|
||||||
|
func (Never) UnmarshalText(b []byte) error {
|
||||||
|
panic("umadump: attempt to decode value that should never exist")
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,29 @@
|
|||||||
|
// Code generated by "stringer -type CardRarity -linecomment -trimprefix Card"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package replay
|
||||||
|
|
||||||
|
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[CardNone-0]
|
||||||
|
_ = x[CardRare1-1]
|
||||||
|
_ = x[CardRare2-2]
|
||||||
|
_ = x[CardRare3-3]
|
||||||
|
_ = x[CardRare4-4]
|
||||||
|
_ = x[CardRare5-5]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _CardRarity_name = "NONERare1Rare2Rare3Rare4Rare5"
|
||||||
|
|
||||||
|
var _CardRarity_index = [...]uint8{0, 4, 9, 14, 19, 24, 29}
|
||||||
|
|
||||||
|
func (i CardRarity) String() string {
|
||||||
|
idx := int(i) - 0
|
||||||
|
if i < 0 || idx >= len(_CardRarity_index)-1 {
|
||||||
|
return "CardRarity(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _CardRarity_name[_CardRarity_index[idx]:_CardRarity_index[idx+1]]
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// Code generated by "stringer -type CharaGradeType -linecomment -trimprefix CharaGrade"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package replay
|
||||||
|
|
||||||
|
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[CharaGradeNone-0]
|
||||||
|
_ = x[CharaGradeDebut-1]
|
||||||
|
_ = x[CharaGradeNoWin-2]
|
||||||
|
_ = x[CharaGradeOpen-3]
|
||||||
|
_ = x[CharaGradeG3Silver-4]
|
||||||
|
_ = x[CharaGradeG3Gold-5]
|
||||||
|
_ = x[CharaGradeG2Silver-6]
|
||||||
|
_ = x[CharaGradeG2Gold-7]
|
||||||
|
_ = x[CharaGradeG1Bronze-8]
|
||||||
|
_ = x[CharaGradeG1Silver-9]
|
||||||
|
_ = x[CharaGradeG1Gold-10]
|
||||||
|
_ = x[charaGradeMax-11]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _CharaGradeType_name = "NONEDebutNoWinOpenG3SilverG3GoldG2SilverG2GoldG1BronzeG1SilverG1GoldcharaGradeMax"
|
||||||
|
|
||||||
|
var _CharaGradeType_index = [...]uint8{0, 4, 9, 14, 18, 26, 32, 40, 46, 54, 62, 68, 81}
|
||||||
|
|
||||||
|
func (i CharaGradeType) String() string {
|
||||||
|
idx := int(i) - 0
|
||||||
|
if i < 0 || idx >= len(_CharaGradeType_index)-1 {
|
||||||
|
return "CharaGradeType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _CharaGradeType_name[_CharaGradeType_index[idx]:_CharaGradeType_index[idx+1]]
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// Code generated by "stringer -type CourseDistanceType -linecomment -trimprefix Distance"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package replay
|
||||||
|
|
||||||
|
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[DistanceSprint-1]
|
||||||
|
_ = x[DistanceMile-2]
|
||||||
|
_ = x[DistanceMedium-3]
|
||||||
|
_ = x[DistanceLong-4]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _CourseDistanceType_name = "ShortMileMiddleLong"
|
||||||
|
|
||||||
|
var _CourseDistanceType_index = [...]uint8{0, 5, 9, 15, 19}
|
||||||
|
|
||||||
|
func (i CourseDistanceType) String() string {
|
||||||
|
idx := int(i) - 1
|
||||||
|
if i < 1 || idx >= len(_CourseDistanceType_index)-1 {
|
||||||
|
return "CourseDistanceType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _CourseDistanceType_name[_CourseDistanceType_index[idx]:_CourseDistanceType_index[idx+1]]
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// Code generated by "stringer -type GroundCondition -linecomment"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package replay
|
||||||
|
|
||||||
|
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[GroundFirm-1]
|
||||||
|
_ = x[GroundGood-2]
|
||||||
|
_ = x[GroundSoft-3]
|
||||||
|
_ = x[GroundHeavy-4]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _GroundCondition_name = "GoodSoftHardBad"
|
||||||
|
|
||||||
|
var _GroundCondition_index = [...]uint8{0, 4, 8, 12, 15}
|
||||||
|
|
||||||
|
func (i GroundCondition) String() string {
|
||||||
|
idx := int(i) - 1
|
||||||
|
if i < 1 || idx >= len(_GroundCondition_index)-1 {
|
||||||
|
return "GroundCondition(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _GroundCondition_name[_GroundCondition_index[idx]:_GroundCondition_index[idx+1]]
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Code generated by "stringer -type InitialLaneType -trimprefix InitialLane"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package replay
|
||||||
|
|
||||||
|
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[InitialLaneExtraSpaceAfter9-1]
|
||||||
|
_ = x[InitialLaneEquidistant-2]
|
||||||
|
_ = x[InitialLaneExtraSpaceAfter14-3]
|
||||||
|
_ = x[InitialLaneExtraSpaceAfter8-4]
|
||||||
|
_ = x[initialLaneMax-5]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _InitialLaneType_name = "ExtraSpaceAfter9EquidistantExtraSpaceAfter14ExtraSpaceAfter8initialLaneMax"
|
||||||
|
|
||||||
|
var _InitialLaneType_index = [...]uint8{0, 16, 27, 44, 60, 74}
|
||||||
|
|
||||||
|
func (i InitialLaneType) String() string {
|
||||||
|
idx := int(i) - 1
|
||||||
|
if i < 1 || idx >= len(_InitialLaneType_index)-1 {
|
||||||
|
return "InitialLaneType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _InitialLaneType_name[_InitialLaneType_index[idx]:_InitialLaneType_index[idx+1]]
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Code generated by "stringer -type MainStoryRaceGimmickType -linecomment"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package replay
|
||||||
|
|
||||||
|
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[GimmickNone-0]
|
||||||
|
_ = x[GimmickSpecial00-1]
|
||||||
|
_ = x[gimmickMax-2]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _MainStoryRaceGimmickType_name = "NONESpecial_00gimmickMax"
|
||||||
|
|
||||||
|
var _MainStoryRaceGimmickType_index = [...]uint8{0, 4, 14, 24}
|
||||||
|
|
||||||
|
func (i MainStoryRaceGimmickType) String() string {
|
||||||
|
idx := int(i) - 0
|
||||||
|
if i < 0 || idx >= len(_MainStoryRaceGimmickType_index)-1 {
|
||||||
|
return "MainStoryRaceGimmickType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _MainStoryRaceGimmickType_name[_MainStoryRaceGimmickType_index[idx]:_MainStoryRaceGimmickType_index[idx+1]]
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// Code generated by "stringer -type Motivation -linecomment"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package replay
|
||||||
|
|
||||||
|
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[MotivationNone-0]
|
||||||
|
_ = x[MotivationAwful-1]
|
||||||
|
_ = x[MotivationBad-2]
|
||||||
|
_ = x[MotivationNormal-3]
|
||||||
|
_ = x[MotivationGood-4]
|
||||||
|
_ = x[MotivationGreat-5]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _Motivation_name = "NoneMinLowMiddleHighMax"
|
||||||
|
|
||||||
|
var _Motivation_index = [...]uint8{0, 4, 7, 10, 16, 20, 23}
|
||||||
|
|
||||||
|
func (i Motivation) String() string {
|
||||||
|
idx := int(i) - 0
|
||||||
|
if i < 0 || idx >= len(_Motivation_index)-1 {
|
||||||
|
return "Motivation(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _Motivation_name[_Motivation_index[idx]:_Motivation_index[idx+1]]
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Code generated by "stringer -type ProperGrade -trimprefix Aptitude"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package replay
|
||||||
|
|
||||||
|
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[AptitudeNull-0]
|
||||||
|
_ = x[AptitudeG-1]
|
||||||
|
_ = x[AptitudeF-2]
|
||||||
|
_ = x[AptitudeE-3]
|
||||||
|
_ = x[AptitudeD-4]
|
||||||
|
_ = x[AptitudeC-5]
|
||||||
|
_ = x[AptitudeB-6]
|
||||||
|
_ = x[AptitudeA-7]
|
||||||
|
_ = x[AptitudeS-8]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _ProperGrade_name = "NullGFEDCBAS"
|
||||||
|
|
||||||
|
var _ProperGrade_index = [...]uint8{0, 4, 5, 6, 7, 8, 9, 10, 11, 12}
|
||||||
|
|
||||||
|
func (i ProperGrade) String() string {
|
||||||
|
idx := int(i) - 0
|
||||||
|
if i < 0 || idx >= len(_ProperGrade_index)-1 {
|
||||||
|
return "ProperGrade(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _ProperGrade_name[_ProperGrade_index[idx]:_ProperGrade_index[idx+1]]
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// Code generated by "stringer -type RaceDifficulty -trimprefix Difficulty"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package replay
|
||||||
|
|
||||||
|
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[DifficultyEasy-1]
|
||||||
|
_ = x[DifficultyNormal-2]
|
||||||
|
_ = x[DifficultyHard-3]
|
||||||
|
_ = x[DifficultyVeryHard-4]
|
||||||
|
_ = x[DifficultyExtreme-5]
|
||||||
|
_ = x[difficultyMax-6]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _RaceDifficulty_name = "EasyNormalHardVeryHardExtremedifficultyMax"
|
||||||
|
|
||||||
|
var _RaceDifficulty_index = [...]uint8{0, 4, 10, 14, 22, 29, 42}
|
||||||
|
|
||||||
|
func (i RaceDifficulty) String() string {
|
||||||
|
idx := int(i) - 1
|
||||||
|
if i < 1 || idx >= len(_RaceDifficulty_index)-1 {
|
||||||
|
return "RaceDifficulty(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _RaceDifficulty_name[_RaceDifficulty_index[idx]:_RaceDifficulty_index[idx+1]]
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// Code generated by "stringer -type RaceTime -trimprefix Time"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package replay
|
||||||
|
|
||||||
|
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[TimeMin-0]
|
||||||
|
_ = x[TimeMorning-1]
|
||||||
|
_ = x[TimeDaytime-2]
|
||||||
|
_ = x[TimeEvening-3]
|
||||||
|
_ = x[TimeNight-4]
|
||||||
|
_ = x[TimeMax-5]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _RaceTime_name = "MinMorningDaytimeEveningNightMax"
|
||||||
|
|
||||||
|
var _RaceTime_index = [...]uint8{0, 3, 10, 17, 24, 29, 32}
|
||||||
|
|
||||||
|
func (i RaceTime) String() string {
|
||||||
|
idx := int(i) - 0
|
||||||
|
if i < 0 || idx >= len(_RaceTime_index)-1 {
|
||||||
|
return "RaceTime(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _RaceTime_name[_RaceTime_index[idx]:_RaceTime_index[idx+1]]
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
// Code generated by "stringer -type RaceType -linecomment -trimprefix RaceType"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package replay
|
||||||
|
|
||||||
|
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[RaceTypeNone-0]
|
||||||
|
_ = x[RaceTypePvP-1]
|
||||||
|
_ = x[RaceTypeTutorial-2]
|
||||||
|
_ = x[RaceTypeStory-3]
|
||||||
|
_ = x[RaceTypeStoryCondition-4]
|
||||||
|
_ = x[RaceTypeChampions-5]
|
||||||
|
_ = x[RaceTypeSingle-6]
|
||||||
|
_ = x[RaceTypeSingleModeScenarioTeamRace-7]
|
||||||
|
_ = x[RaceTypeRoomMatch-8]
|
||||||
|
_ = x[RaceTypePractice-9]
|
||||||
|
_ = x[RaceTypeDaily-10]
|
||||||
|
_ = x[RaceTypeTeamBuilding-11]
|
||||||
|
_ = x[RaceTypeLegend-12]
|
||||||
|
_ = x[RaceTypeChallengeMatch-13]
|
||||||
|
_ = x[RaceTypeTeamStadium-14]
|
||||||
|
_ = x[RaceTypeHeroes-16]
|
||||||
|
_ = x[raceTypeMaxInclusive-16]
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
_RaceType_name_0 = "NONEPvPTutorialStoryStoryConditionChampionsSingleSingleModeScenarioTeamRaceRoomMatchPracticeDailyTeamBuildingLegendChallengeMatchTeamStadium"
|
||||||
|
_RaceType_name_1 = "Heroes"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_RaceType_index_0 = [...]uint8{0, 4, 7, 15, 20, 34, 43, 49, 75, 84, 92, 97, 109, 115, 129, 140}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (i RaceType) String() string {
|
||||||
|
switch {
|
||||||
|
case 0 <= i && i <= 14:
|
||||||
|
return _RaceType_name_0[_RaceType_index_0[i]:_RaceType_index_0[i+1]]
|
||||||
|
case i == 16:
|
||||||
|
return _RaceType_name_1
|
||||||
|
default:
|
||||||
|
return "RaceType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// Code generated by "stringer -type RaceWeather -trimprefix Weather"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package replay
|
||||||
|
|
||||||
|
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[WeatherMin-0]
|
||||||
|
_ = x[WeatherSunny-1]
|
||||||
|
_ = x[WeatherCloudy-2]
|
||||||
|
_ = x[WeatherRainy-3]
|
||||||
|
_ = x[WeatherSnow-4]
|
||||||
|
_ = x[WeatherMax-5]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _RaceWeather_name = "MinSunnyCloudyRainySnowMax"
|
||||||
|
|
||||||
|
var _RaceWeather_index = [...]uint8{0, 3, 8, 14, 19, 23, 26}
|
||||||
|
|
||||||
|
func (i RaceWeather) String() string {
|
||||||
|
idx := int(i) - 0
|
||||||
|
if i < 0 || idx >= len(_RaceWeather_index)-1 {
|
||||||
|
return "RaceWeather(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _RaceWeather_name[_RaceWeather_index[idx]:_RaceWeather_index[idx+1]]
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,182 @@
|
|||||||
|
package replay_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.sunturtle.xyz/zephyr/horse/umadump/replay"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed Mihono_Bourbon-74.2421s-20260829.json
|
||||||
|
var testRace string
|
||||||
|
|
||||||
|
func TestRaceUnmarshal(t *testing.T) {
|
||||||
|
// A replay is simply too large to bother converting by hand for a unit test.
|
||||||
|
// Just test that we can unmarshal and certain important fields are populated.
|
||||||
|
d := json.NewDecoder(strings.NewReader(testRace))
|
||||||
|
d.DisallowUnknownFields()
|
||||||
|
var got replay.Race
|
||||||
|
if err := d.Decode(&got); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if got.RaceType != replay.RaceTypeChampions {
|
||||||
|
t.Errorf("wrong race type: want %v, got %v", replay.RaceTypeChampions, got.RaceType)
|
||||||
|
}
|
||||||
|
if got.RandomSeed != 1667907263 {
|
||||||
|
t.Errorf("wrong random seed: want 1667907263, got %d", got.RandomSeed)
|
||||||
|
}
|
||||||
|
if got.RaceCourseSet.ID != 10903 {
|
||||||
|
t.Errorf("wrong race course: want 10903, got %d", got.RaceCourseSet.ID)
|
||||||
|
}
|
||||||
|
if got.GroundCondition != replay.GroundFirm {
|
||||||
|
t.Errorf("wrong ground condition: want %v, got %v", replay.GroundFirm, got.GroundCondition)
|
||||||
|
}
|
||||||
|
if got.Weather != replay.WeatherCloudy {
|
||||||
|
t.Errorf("wrong weather: want %v, got %v", replay.WeatherCloudy, got.Weather)
|
||||||
|
}
|
||||||
|
if got.NumRaceHorses != 9 {
|
||||||
|
t.Errorf("wrong number of race horses: want 9, got %d", got.NumRaceHorses)
|
||||||
|
}
|
||||||
|
if len(got.PlayerTeamMemberArray) != 3 {
|
||||||
|
t.Errorf("wrong number of team members: want 3, got %d", len(got.PlayerTeamMemberArray))
|
||||||
|
}
|
||||||
|
for _, m := range got.PlayerTeamMemberArray {
|
||||||
|
if m.CharaID == 0 {
|
||||||
|
t.Error("missing team member chara id")
|
||||||
|
}
|
||||||
|
if m.FinishTimeRaw == 0 {
|
||||||
|
t.Error("missing team member finish time raw")
|
||||||
|
}
|
||||||
|
if m.RaceParam.RawSpeed == 0 {
|
||||||
|
t.Error("missing team member race param raw speed")
|
||||||
|
}
|
||||||
|
if m.RaceParam.BaseSpeed == 0 {
|
||||||
|
t.Error("missing team member race param base speed")
|
||||||
|
}
|
||||||
|
if m.ResponseHorseData.ViewerID == 0 {
|
||||||
|
t.Error("missing team member response horse data viewer id")
|
||||||
|
}
|
||||||
|
if m.ResponseHorseData.TrainedCharaID == 0 {
|
||||||
|
t.Error("missing team member response horse data trained chara id")
|
||||||
|
}
|
||||||
|
if m.ResponseHorseData.CardID == 0 {
|
||||||
|
t.Error("missing team member response horse data card id")
|
||||||
|
}
|
||||||
|
if len(m.ResponseHorseData.SkillArray) == 0 {
|
||||||
|
t.Error("missing team member response horse data skill array")
|
||||||
|
}
|
||||||
|
if m.ResponseHorseData.Speed == 0 {
|
||||||
|
t.Error("missing team member response horse data speed")
|
||||||
|
}
|
||||||
|
if m.ResponseHorseData.RunningStyle == 0 {
|
||||||
|
t.Error("missing team member response horse data running style")
|
||||||
|
}
|
||||||
|
if m.ActiveProperDistance == 0 {
|
||||||
|
t.Error("missing team member active distance aptitude")
|
||||||
|
}
|
||||||
|
if m.ActiveProperGroundType == 0 {
|
||||||
|
t.Error("missing team member active surface aptitude")
|
||||||
|
}
|
||||||
|
if m.TrainedCharaData.ID == 0 {
|
||||||
|
t.Error("missing team member trained chara id")
|
||||||
|
}
|
||||||
|
if m.TrainedCharaData.CardID == 0 {
|
||||||
|
t.Error("missing team member trained chara card id")
|
||||||
|
}
|
||||||
|
if m.TrainedCharaData.Speed == 0 {
|
||||||
|
t.Error("missing team member trained chara speed")
|
||||||
|
}
|
||||||
|
if m.TrainedCharaData.RunningStyle == 0 {
|
||||||
|
t.Error("missing team member trained chara running style")
|
||||||
|
}
|
||||||
|
if m.TrainedCharaData.CreateTime == "" {
|
||||||
|
t.Error("missing team member trained chara create time")
|
||||||
|
}
|
||||||
|
if len(m.TrainedCharaData.AcquiredSkillArray) == 0 {
|
||||||
|
t.Error("missing team member trained chara acquired skill array")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(got.RaceHorse) != int(got.NumRaceHorses) {
|
||||||
|
t.Errorf("wrong number of race horses: want %d, got %d", got.NumRaceHorses, len(got.RaceHorse))
|
||||||
|
}
|
||||||
|
zeroFinish, zeroDelta := 0, 0
|
||||||
|
for _, m := range got.RaceHorse {
|
||||||
|
if m.CharaID == 0 {
|
||||||
|
t.Error("missing race horse chara id")
|
||||||
|
}
|
||||||
|
if m.FinishOrder == 0 {
|
||||||
|
zeroFinish++
|
||||||
|
}
|
||||||
|
if m.FinishTimeRaw == 0 {
|
||||||
|
t.Error("missing race horse finish time raw")
|
||||||
|
}
|
||||||
|
if m.FinishDiffTimeFromPrev == 0 {
|
||||||
|
zeroDelta++
|
||||||
|
}
|
||||||
|
if m.RaceParam.RawSpeed == 0 {
|
||||||
|
t.Error("missing race horse race param raw speed")
|
||||||
|
}
|
||||||
|
if m.RaceParam.BaseSpeed == 0 {
|
||||||
|
t.Error("missing race horse race param base speed")
|
||||||
|
}
|
||||||
|
if m.ResponseHorseData.ViewerID == 0 {
|
||||||
|
t.Error("missing race horse response horse data viewer id")
|
||||||
|
}
|
||||||
|
if m.ResponseHorseData.TrainedCharaID == 0 {
|
||||||
|
t.Error("missing race horse response horse data trained chara id")
|
||||||
|
}
|
||||||
|
if m.ResponseHorseData.CardID == 0 {
|
||||||
|
t.Error("missing race horse response horse data card id")
|
||||||
|
}
|
||||||
|
if len(m.ResponseHorseData.SkillArray) == 0 {
|
||||||
|
t.Error("missing race horse response horse data skill array")
|
||||||
|
}
|
||||||
|
if m.ResponseHorseData.Speed == 0 {
|
||||||
|
t.Error("missing race horse response horse data speed")
|
||||||
|
}
|
||||||
|
if m.ResponseHorseData.RunningStyle == 0 {
|
||||||
|
t.Error("missing race horse response horse data running style")
|
||||||
|
}
|
||||||
|
if m.ActiveProperDistance == 0 {
|
||||||
|
t.Error("missing race horse active distance aptitude")
|
||||||
|
}
|
||||||
|
if m.ActiveProperGroundType == 0 {
|
||||||
|
t.Error("missing race horse active surface aptitude")
|
||||||
|
}
|
||||||
|
if m.TrainedCharaData.ID == 0 {
|
||||||
|
t.Error("missing race horse trained chara id")
|
||||||
|
}
|
||||||
|
if m.TrainedCharaData.CardID == 0 {
|
||||||
|
t.Error("missing race horse trained chara card id")
|
||||||
|
}
|
||||||
|
if m.TrainedCharaData.Speed == 0 {
|
||||||
|
t.Error("missing race horse trained chara speed")
|
||||||
|
}
|
||||||
|
if m.TrainedCharaData.RunningStyle == 0 {
|
||||||
|
t.Error("missing race horse trained chara running style")
|
||||||
|
}
|
||||||
|
if m.TrainedCharaData.CreateTime == "" {
|
||||||
|
t.Error("missing race horse trained chara create time")
|
||||||
|
}
|
||||||
|
if len(m.TrainedCharaData.AcquiredSkillArray) == 0 {
|
||||||
|
t.Error("missing race horse trained chara acquired skill array")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if zeroFinish != 1 {
|
||||||
|
t.Errorf("%d horses in index 0 finish", zeroFinish)
|
||||||
|
}
|
||||||
|
if zeroDelta != 1 {
|
||||||
|
t.Errorf("%d horses with zero finish time delta", zeroDelta)
|
||||||
|
}
|
||||||
|
if got.SimDataBase64 != encodedSimdata {
|
||||||
|
t.Errorf("wrong simdata:\nwant %s\n got %s", encodedSimdata, got.SimDataBase64)
|
||||||
|
}
|
||||||
|
if len(got.HorseIndexByFinishOrder) != len(got.RaceHorse) {
|
||||||
|
t.Errorf("length of horseIndexByFinishOrder (%d) doesn't match length of raceHorse (%d)", len(got.HorseIndexByFinishOrder), len(got.RaceHorse))
|
||||||
|
}
|
||||||
|
if len(got.HorseIndexByPopularity) != len(got.RaceHorse) {
|
||||||
|
t.Errorf("length of horseIndexByPopularity (%d) doesn't match length of raceHorse (%d)", len(got.HorseIndexByPopularity), len(got.RaceHorse))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// Code generated by "stringer -type ResultBoardConditionType -linecomment"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package replay
|
||||||
|
|
||||||
|
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[ResultBoardTurfNone-1]
|
||||||
|
_ = x[ResultBoardTurfDirt-2]
|
||||||
|
_ = x[ResultBoardDirtNone-3]
|
||||||
|
_ = x[ResultBoardDirtTurf-4]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _ResultBoardConditionType_name = "Turf_NoneTurf_DirtDirt_NoneDirt_Turf"
|
||||||
|
|
||||||
|
var _ResultBoardConditionType_index = [...]uint8{0, 9, 18, 27, 36}
|
||||||
|
|
||||||
|
func (i ResultBoardConditionType) String() string {
|
||||||
|
idx := int(i) - 1
|
||||||
|
if i < 1 || idx >= len(_ResultBoardConditionType_index)-1 {
|
||||||
|
return "ResultBoardConditionType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _ResultBoardConditionType_name[_ResultBoardConditionType_index[idx]:_ResultBoardConditionType_index[idx+1]]
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// Code generated by "stringer -type Rotation -trimprefix Rotation"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package replay
|
||||||
|
|
||||||
|
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[RotationRight-1]
|
||||||
|
_ = x[RotationLeft-2]
|
||||||
|
_ = x[RotationStraightRight-3]
|
||||||
|
_ = x[RotationStraightLeft-4]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _Rotation_name = "RightLeftStraightRightStraightLeft"
|
||||||
|
|
||||||
|
var _Rotation_index = [...]uint8{0, 5, 9, 22, 34}
|
||||||
|
|
||||||
|
func (i Rotation) String() string {
|
||||||
|
idx := int(i) - 1
|
||||||
|
if i < 1 || idx >= len(_Rotation_index)-1 {
|
||||||
|
return "Rotation(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _Rotation_name[_Rotation_index[idx]:_Rotation_index[idx+1]]
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Code generated by "stringer -type RunningStyleEx -linecomment"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package replay
|
||||||
|
|
||||||
|
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[StyleExNone-0]
|
||||||
|
_ = x[StyleExRunaway-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _RunningStyleEx_name = "NONEOonige"
|
||||||
|
|
||||||
|
var _RunningStyleEx_index = [...]uint8{0, 4, 10}
|
||||||
|
|
||||||
|
func (i RunningStyleEx) String() string {
|
||||||
|
idx := int(i) - 0
|
||||||
|
if i < 0 || idx >= len(_RunningStyleEx_index)-1 {
|
||||||
|
return "RunningStyleEx(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _RunningStyleEx_name[_RunningStyleEx_index[idx]:_RunningStyleEx_index[idx+1]]
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// Code generated by "stringer -type Season -trimprefix Season"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package replay
|
||||||
|
|
||||||
|
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[SeasonMin-0]
|
||||||
|
_ = x[SeasonSpring-1]
|
||||||
|
_ = x[SeasonSummer-2]
|
||||||
|
_ = x[SeasonFall-3]
|
||||||
|
_ = x[SeasonWinter-4]
|
||||||
|
_ = x[SeasonCherryBlossom-5]
|
||||||
|
_ = x[SeasonMax-6]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _Season_name = "MinSpringSummerFallWinterCherryBlossomMax"
|
||||||
|
|
||||||
|
var _Season_index = [...]uint8{0, 3, 9, 15, 19, 25, 38, 41}
|
||||||
|
|
||||||
|
func (i Season) String() string {
|
||||||
|
idx := int(i) - 0
|
||||||
|
if i < 0 || idx >= len(_Season_index)-1 {
|
||||||
|
return "Season(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _Season_name[_Season_index[idx]:_Season_index[idx+1]]
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,27 @@
|
|||||||
|
// Code generated by "stringer -type TurfVisionType -trimprefix TurfVision"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package replay
|
||||||
|
|
||||||
|
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[TurfVisionURA-1]
|
||||||
|
_ = x[TurfVisionNAU-2]
|
||||||
|
_ = x[TurfVisionStand-3]
|
||||||
|
_ = x[turfVisionMax-4]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _TurfVisionType_name = "URANAUStandturfVisionMax"
|
||||||
|
|
||||||
|
var _TurfVisionType_index = [...]uint8{0, 3, 6, 11, 24}
|
||||||
|
|
||||||
|
func (i TurfVisionType) String() string {
|
||||||
|
idx := int(i) - 1
|
||||||
|
if i < 1 || idx >= len(_TurfVisionType_index)-1 {
|
||||||
|
return "TurfVisionType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _TurfVisionType_name[_TurfVisionType_index[idx]:_TurfVisionType_index[idx+1]]
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
skill_id: 0,
|
skill_id: 0,
|
||||||
name: 'Skill data is loading...',
|
name: 'Skill data is loading...',
|
||||||
description: 'Skill data is still loading, or else something went wrong.',
|
description: 'Skill data is still loading, or else something went wrong.',
|
||||||
|
group_skills: [],
|
||||||
group: 0,
|
group: 0,
|
||||||
rarity: 1,
|
rarity: 1,
|
||||||
group_rate: 1,
|
group_rate: 1,
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import * as z from 'zod';
|
||||||
|
|
||||||
|
const Entry = z.strictObject({
|
||||||
|
home_story_trigger_id: z.int32(),
|
||||||
|
new_flag: z.literal(0).or(z.literal(1)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Entry = z.infer<typeof Entry>;
|
||||||
|
|
||||||
|
export const Gallery = z.object({
|
||||||
|
talk_gallery_list: z.array(Entry),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Gallery = z.infer<typeof Gallery>;
|
||||||
@@ -9,25 +9,51 @@
|
|||||||
requiredCharas,
|
requiredCharas,
|
||||||
convoCharas,
|
convoCharas,
|
||||||
} from '$lib/data/convo';
|
} from '$lib/data/convo';
|
||||||
|
import * as talk from '$lib/umadump/talk';
|
||||||
import CharaPick from '$lib/CharaPick.svelte';
|
import CharaPick from '$lib/CharaPick.svelte';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import CharaIcon from '$lib/CharaIcon.svelte';
|
import CharaIcon from '$lib/CharaIcon.svelte';
|
||||||
|
import { SvelteSet } from 'svelte/reactivity';
|
||||||
|
|
||||||
let conversations: Conversation[] = $state([]);
|
let allConvos: Conversation[] = $state([]);
|
||||||
const convoMap = $derived(byChara(conversations));
|
const popular = $derived(groupPopulars(allConvos));
|
||||||
const popular = $derived(groupPopulars(conversations));
|
|
||||||
let characters: Character[] = $state([]);
|
let characters: Character[] = $state([]);
|
||||||
const selCharas = $derived(characters.filter((c) => convoMap.has(c.chara_id)));
|
|
||||||
const names = $derived(charaNames(characters));
|
const names = $derived(charaNames(characters));
|
||||||
const minSuggest = 8;
|
const minSuggest = 8;
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
[conversations, characters] = await Promise.all([conversation(), character()]);
|
[allConvos, characters] = await Promise.all([conversation(), character()]);
|
||||||
});
|
});
|
||||||
|
|
||||||
let chara: Character | undefined = $state();
|
let chara: Character | undefined = $state();
|
||||||
let convo = $state(1);
|
let convo = $state(1);
|
||||||
|
|
||||||
|
let talkFiles: FileList | undefined = $state();
|
||||||
|
let readIDs = new SvelteSet<number>();
|
||||||
|
let talkFileInvalid = $state(false);
|
||||||
|
async function setRead(f: FileList) {
|
||||||
|
talkFiles = f;
|
||||||
|
readIDs.clear();
|
||||||
|
talkFileInvalid = false;
|
||||||
|
if (talkFiles == null || talkFiles.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const t = await talkFiles[0].text();
|
||||||
|
const d = JSON.parse(t);
|
||||||
|
const g = talk.Gallery.safeDecode(d);
|
||||||
|
if (!g.success) {
|
||||||
|
talkFileInvalid = true;
|
||||||
|
console.warn(g.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const e of g.data.talk_gallery_list) {
|
||||||
|
readIDs.add(e.home_story_trigger_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const conversations = $derived(allConvos.filter((c) => !readIDs.has(c.id)));
|
||||||
|
const convoMap = $derived(byChara(conversations));
|
||||||
|
const selCharas = $derived(characters.filter((c) => convoMap.has(c.chara_id)));
|
||||||
|
|
||||||
const options = $derived(convoMap.get(chara?.chara_id ?? 0) ?? []);
|
const options = $derived(convoMap.get(chara?.chara_id ?? 0) ?? []);
|
||||||
const cur = $derived(options.find((c) => c.number === convo));
|
const cur = $derived(options.find((c) => c.number === convo));
|
||||||
const charaIDs = $derived(cur != null ? convoCharas(cur) : []);
|
const charaIDs = $derived(cur != null ? convoCharas(cur) : []);
|
||||||
@@ -47,12 +73,12 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1 class="text-4xl">Lobby Conversations</h1>
|
<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="mx-auto mt-8 grid grid-cols-1 gap-4 rounded-md p-4 text-center shadow-md ring md:max-w-xl md:grid-cols-6">
|
||||||
<div class="m-4 flex-1 md:mt-3">
|
<div class="md:col-span-3">
|
||||||
<label for="chara" class="hidden md:inline">Character</label>
|
<label for="chara" class="hidden md:inline">Character</label>
|
||||||
<CharaPick id="chara" characters={selCharas} class="w-full" bind:value={chara} required />
|
<CharaPick id="chara" characters={selCharas} class="w-full" bind:value={chara} required />
|
||||||
</div>
|
</div>
|
||||||
<div class="m-4 flex-1 md:mt-3">
|
<div class="md:col-span-3">
|
||||||
<label for="convo" class="hidden md:inline">Conversation</label>
|
<label for="convo" class="hidden md:inline">Conversation</label>
|
||||||
<select id="convo" bind:value={convo} class="w-full">
|
<select id="convo" bind:value={convo} class="w-full">
|
||||||
{#each options as opt (opt.number)}
|
{#each options as opt (opt.number)}
|
||||||
@@ -60,6 +86,29 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-span-full">
|
||||||
|
<label for="talkgallery" class="block">
|
||||||
|
Select
|
||||||
|
<a href="https://werseter.github.io/umadump/" target="_blank" rel="noopener noreferrer">
|
||||||
|
<span class="inline-block font-mono">talk_gallery_data.json</span>
|
||||||
|
</a>
|
||||||
|
to hide read conversations.
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
class="rounded-full p-4"
|
||||||
|
accept="application/json"
|
||||||
|
id="talkgallery"
|
||||||
|
bind:files={() => talkFiles ?? new DataTransfer().files, setRead}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
class="ml-4 rounded-full bg-mist-300 p-2 hover:cursor-pointer dark:bg-mist-900"
|
||||||
|
onclick={() => setRead(new DataTransfer().files)}>Reset</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{#if talkFileInvalid}
|
||||||
|
<div class="col-span-full">Invalid File</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if cur}
|
{#if cur}
|
||||||
<div class="shadow-sm transition-shadow hover:shadow-md">
|
<div class="shadow-sm transition-shadow hover:shadow-md">
|
||||||
|
|||||||
Reference in New Issue
Block a user