Compare commits

7 Commits
Author SHA1 Message Date
zephyr 55aa03aa41 umadump/replay: start of race replay json decoding 2026-09-20 11:44:09 -04:00
zephyr 0ffd2c099a horse, mdb: include id in lobby conversations
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
2026-09-17 12:59:28 -04:00
zephyr 6828b1bd88 horse, mdb: put skill groups in skills
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
Fixes #13.
2026-09-15 11:00:10 -04:00
zephyr 78aee43e5f zenno: format
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
why the h*ck does vscode not format my .ts files on save even though i
have it configured to always format on save!!!!!!
2026-09-14 17:21:51 -04:00
zephyr 745c9d01dd zenno/doc/frbm: delete jbc sprint
Fixes #46.
2026-09-14 17:20:40 -04:00
zephyr e00fe0965a zenno/doc/mdb: emphasize that support card event outcomes are not in the mdb
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline failed
2026-09-14 17:07:25 -04:00
zephyr caa19dd85a zenno/doc/mdb: document races
ci/woodpecker/push/horsebot Pipeline failed
ci/woodpecker/push/zenno Pipeline failed
2026-09-11 18:14:14 -04:00
25 changed files with 2167 additions and 200 deletions
+2
View File
@@ -28,6 +28,8 @@ type AffinityDetail struct {
// Conversation describes a lobby conversation. // Conversation describes a lobby conversation.
type Conversation struct { type Conversation struct {
// ID is the conversation ID.
ID int32 `json:"id"`
// CharacterID is the ID of the character who has the conversation as // CharacterID is the ID of the character who has the conversation as
// a gallery entry. // a gallery entry.
CharacterID CharacterID `json:"chara_id"` CharacterID CharacterID `json:"chara_id"`
+8 -7
View File
@@ -89,13 +89,14 @@ func Umas(ctx context.Context, db *sqlitex.Pool) ([]horse.Uma, error) {
func Conversations(ctx context.Context, db *sqlitex.Pool) ([]horse.Conversation, error) { func Conversations(ctx context.Context, db *sqlitex.Pool) ([]horse.Conversation, error) {
return load(ctx, db, make([]horse.Conversation, 0, 1024), conversationSQL, func(s *sqlite.Stmt) horse.Conversation { return load(ctx, db, make([]horse.Conversation, 0, 1024), conversationSQL, func(s *sqlite.Stmt) horse.Conversation {
return horse.Conversation{ return horse.Conversation{
CharacterID: horse.CharacterID(s.ColumnInt(0)), ID: s.ColumnInt32(0),
Number: s.ColumnInt(1), CharacterID: horse.CharacterID(s.ColumnInt(1)),
Location: horse.LobbyConversationLocationID(s.ColumnInt(2)), Number: s.ColumnInt(2),
Chara1: horse.CharacterID(s.ColumnInt(3)), Location: horse.LobbyConversationLocationID(s.ColumnInt(3)),
Chara2: horse.CharacterID(s.ColumnInt(4)), Chara1: horse.CharacterID(s.ColumnInt(4)),
Chara3: horse.CharacterID(s.ColumnInt(5)), Chara2: horse.CharacterID(s.ColumnInt(5)),
ConditionType: s.ColumnInt(6), Chara3: horse.CharacterID(s.ColumnInt(6)),
ConditionType: s.ColumnInt(7),
} }
}) })
} }
+11
View File
@@ -238,12 +238,14 @@ func TestConversations(t *testing.T) {
} }
want := []horse.Conversation{ want := []horse.Conversation{
{ {
ID: 1,
CharacterID: 1001, CharacterID: 1001,
Number: 1, Number: 1,
Location: 410, Location: 410,
Chara1: 1001, Chara1: 1001,
}, },
{ {
ID: 2,
CharacterID: 1001, CharacterID: 1001,
Number: 2, Number: 2,
Location: 510, Location: 510,
@@ -251,6 +253,7 @@ func TestConversations(t *testing.T) {
ConditionType: 1, ConditionType: 1,
}, },
{ {
ID: 3,
CharacterID: 1001, CharacterID: 1001,
Number: 3, Number: 3,
Location: 310, Location: 310,
@@ -258,6 +261,7 @@ func TestConversations(t *testing.T) {
ConditionType: 1, ConditionType: 1,
}, },
{ {
ID: 76,
CharacterID: 1001, CharacterID: 1001,
Number: 4, Number: 4,
Location: 120, Location: 120,
@@ -266,6 +270,7 @@ func TestConversations(t *testing.T) {
ConditionType: 2, ConditionType: 2,
}, },
{ {
ID: 77,
CharacterID: 1001, CharacterID: 1001,
Number: 5, Number: 5,
Location: 520, Location: 520,
@@ -274,6 +279,7 @@ func TestConversations(t *testing.T) {
ConditionType: 3, ConditionType: 3,
}, },
{ {
ID: 126,
CharacterID: 1001, CharacterID: 1001,
Number: 6, Number: 6,
Location: 430, Location: 430,
@@ -283,12 +289,14 @@ func TestConversations(t *testing.T) {
ConditionType: 1, ConditionType: 1,
}, },
{ {
ID: 4,
CharacterID: 1002, CharacterID: 1002,
Number: 1, Number: 1,
Location: 310, Location: 310,
Chara1: 1002, Chara1: 1002,
}, },
{ {
ID: 5,
CharacterID: 1002, CharacterID: 1002,
Number: 2, Number: 2,
Location: 210, Location: 210,
@@ -296,6 +304,7 @@ func TestConversations(t *testing.T) {
ConditionType: 1, ConditionType: 1,
}, },
{ {
ID: 6,
CharacterID: 1002, CharacterID: 1002,
Number: 3, Number: 3,
Location: 110, Location: 110,
@@ -303,6 +312,7 @@ func TestConversations(t *testing.T) {
ConditionType: 1, ConditionType: 1,
}, },
{ {
ID: 78,
CharacterID: 1002, CharacterID: 1002,
Number: 4, Number: 4,
Location: 520, Location: 520,
@@ -311,6 +321,7 @@ func TestConversations(t *testing.T) {
ConditionType: 3, ConditionType: 3,
}, },
{ {
ID: 79,
CharacterID: 1002, CharacterID: 1002,
Number: 5, Number: 5,
Location: 220, Location: 220,
+123 -77
View File
@@ -18,6 +18,8 @@ var (
skillGroupSQL string skillGroupSQL string
//go:embed sql/skill.sql //go:embed sql/skill.sql
skillSQL string skillSQL string
//go:embed sql/skill-group-skills.sql
skillGroupSkillsSQL string
) )
// SkillGroups retrieves all skill groups. // SkillGroups retrieves all skill groups.
@@ -35,84 +37,128 @@ func SkillGroups(ctx context.Context, db *sqlitex.Pool) ([]horse.SkillGroup, err
// Skills retrieves all skills. // Skills retrieves all skills.
func Skills(ctx context.Context, db *sqlitex.Pool) ([]horse.Skill, error) { func Skills(ctx context.Context, db *sqlitex.Pool) ([]horse.Skill, error) {
return load(ctx, db, nil, skillSQL, func(s *sqlite.Stmt) horse.Skill { // We don't use func load here because we have multiple queries to run.
return horse.Skill{ conn, err := db.Take(ctx)
ID: horse.SkillID(s.ColumnInt(0)), defer db.Put(conn)
Name: s.ColumnText(1), if err != nil {
Description: s.ColumnText(2), return nil, err
Group: horse.SkillGroupID(s.ColumnInt32(3)), }
Rarity: int8(s.ColumnInt(5)), var sk []horse.Skill
GroupRate: int8(s.ColumnInt(6)), {
GradeValue: s.ColumnInt32(7), stmt := conn.Prep(skillSQL)
WitCheck: s.ColumnBool(8), defer stmt.Reset()
Activations: trimActivations([]horse.Activation{ for {
{ ok, err := stmt.Step()
Precondition: s.ColumnText(9), if err != nil {
Condition: s.ColumnText(10), return nil, err
Duration: horse.TenThousandths(s.ColumnInt(11)), }
DurScale: horse.DurScale(s.ColumnInt(12)), if !ok {
Cooldown: horse.TenThousandths(s.ColumnInt(13)), break
Abilities: trimAbilities([]horse.Ability{ }
{ s := horse.Skill{
Type: horse.AbilityType(s.ColumnInt(14)), ID: horse.SkillID(stmt.ColumnInt(0)),
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(15)), Name: stmt.ColumnText(1),
Value: horse.TenThousandths(s.ColumnInt(16)), Description: stmt.ColumnText(2),
Target: horse.AbilityTarget(s.ColumnInt(17)), GroupSkills: make([]horse.GroupSkill, 0, 3),
TargetValue: s.ColumnInt32(18), Group: horse.SkillGroupID(stmt.ColumnInt32(3)),
}, Rarity: int8(stmt.ColumnInt(5)),
{ GroupRate: int8(stmt.ColumnInt(6)),
Type: horse.AbilityType(s.ColumnInt(19)), GradeValue: stmt.ColumnInt32(7),
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(20)), WitCheck: stmt.ColumnBool(8),
Value: horse.TenThousandths(s.ColumnInt(21)), Activations: trimActivations([]horse.Activation{
Target: horse.AbilityTarget(s.ColumnInt(22)), {
TargetValue: s.ColumnInt32(23), Precondition: stmt.ColumnText(9),
}, Condition: stmt.ColumnText(10),
{ Duration: horse.TenThousandths(stmt.ColumnInt(11)),
Type: horse.AbilityType(s.ColumnInt(24)), DurScale: horse.DurScale(stmt.ColumnInt(12)),
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(25)), Cooldown: horse.TenThousandths(stmt.ColumnInt(13)),
Value: horse.TenThousandths(s.ColumnInt(26)), Abilities: trimAbilities([]horse.Ability{
Target: horse.AbilityTarget(s.ColumnInt(27)), {
TargetValue: s.ColumnInt32(28), Type: horse.AbilityType(stmt.ColumnInt(14)),
}, ValueUsage: horse.AbilityValueUsage(stmt.ColumnInt(15)),
}), Value: horse.TenThousandths(stmt.ColumnInt(16)),
}, Target: horse.AbilityTarget(stmt.ColumnInt(17)),
{ TargetValue: stmt.ColumnInt32(18),
Precondition: s.ColumnText(29), },
Condition: s.ColumnText(30), {
Duration: horse.TenThousandths(s.ColumnInt(31)), Type: horse.AbilityType(stmt.ColumnInt(19)),
DurScale: horse.DurScale(s.ColumnInt(32)), ValueUsage: horse.AbilityValueUsage(stmt.ColumnInt(20)),
Cooldown: horse.TenThousandths(s.ColumnInt(33)), Value: horse.TenThousandths(stmt.ColumnInt(21)),
Abilities: trimAbilities([]horse.Ability{ Target: horse.AbilityTarget(stmt.ColumnInt(22)),
{ TargetValue: stmt.ColumnInt32(23),
Type: horse.AbilityType(s.ColumnInt(34)), },
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(35)), {
Value: horse.TenThousandths(s.ColumnInt(36)), Type: horse.AbilityType(stmt.ColumnInt(24)),
Target: horse.AbilityTarget(s.ColumnInt(37)), ValueUsage: horse.AbilityValueUsage(stmt.ColumnInt(25)),
TargetValue: s.ColumnInt32(38), Value: horse.TenThousandths(stmt.ColumnInt(26)),
}, Target: horse.AbilityTarget(stmt.ColumnInt(27)),
{ TargetValue: stmt.ColumnInt32(28),
Type: horse.AbilityType(s.ColumnInt(39)), },
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(40)), }),
Value: horse.TenThousandths(s.ColumnInt(41)), },
Target: horse.AbilityTarget(s.ColumnInt(42)), {
TargetValue: s.ColumnInt32(43), Precondition: stmt.ColumnText(29),
}, Condition: stmt.ColumnText(30),
{ Duration: horse.TenThousandths(stmt.ColumnInt(31)),
Type: horse.AbilityType(s.ColumnInt(44)), DurScale: horse.DurScale(stmt.ColumnInt(32)),
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(45)), Cooldown: horse.TenThousandths(stmt.ColumnInt(33)),
Value: horse.TenThousandths(s.ColumnInt(46)), Abilities: trimAbilities([]horse.Ability{
Target: horse.AbilityTarget(s.ColumnInt(47)), {
TargetValue: s.ColumnInt32(48), Type: horse.AbilityType(stmt.ColumnInt(34)),
}, ValueUsage: horse.AbilityValueUsage(stmt.ColumnInt(35)),
}), Value: horse.TenThousandths(stmt.ColumnInt(36)),
}, Target: horse.AbilityTarget(stmt.ColumnInt(37)),
}), TargetValue: stmt.ColumnInt32(38),
UniqueOwner: s.ColumnText(52), // TODO(zeph): should be id, not name },
Tags: parseTags(s.ColumnText(54)), {
SPCost: s.ColumnInt(49), Type: horse.AbilityType(stmt.ColumnInt(39)),
IconID: s.ColumnInt(53), ValueUsage: horse.AbilityValueUsage(stmt.ColumnInt(40)),
Value: horse.TenThousandths(stmt.ColumnInt(41)),
Target: horse.AbilityTarget(stmt.ColumnInt(42)),
TargetValue: stmt.ColumnInt32(43),
},
{
Type: horse.AbilityType(stmt.ColumnInt(44)),
ValueUsage: horse.AbilityValueUsage(stmt.ColumnInt(45)),
Value: horse.TenThousandths(stmt.ColumnInt(46)),
Target: horse.AbilityTarget(stmt.ColumnInt(47)),
TargetValue: stmt.ColumnInt32(48),
},
}),
},
}),
UniqueOwner: stmt.ColumnText(52), // TODO(zeph): should be id, not name
Tags: parseTags(stmt.ColumnText(54)),
SPCost: stmt.ColumnInt(49),
IconID: stmt.ColumnInt(53),
}
sk = append(sk, s)
} }
}) }
stmt := conn.Prep(skillGroupSkillsSQL)
defer stmt.Reset()
cur := sk
for {
ok, err := stmt.Step()
if err != nil {
return nil, err
}
if !ok {
break
}
// We sort skills by ID first, so we can add group skills in a single pass.
id := stmt.ColumnInt(0)
for len(cur) != 0 && cur[0].ID != horse.SkillID(id) {
cur = cur[1:]
}
g := horse.GroupSkill{
ID: horse.SkillID(stmt.ColumnInt(1)),
GroupRate: int8(stmt.ColumnInt(2)),
Rarity: int8(stmt.ColumnInt(3)),
}
cur[0].GroupSkills = append(cur[0].GroupSkills, g)
}
return sk, nil
} }
func parseTags(s string) []uint16 { func parseTags(s string) []uint16 {
+182 -95
View File
@@ -49,11 +49,16 @@ func TestSkills(t *testing.T) {
ID: 10351, ID: 10351,
Name: "V Is for Victory!", Name: "V Is for Victory!",
Description: "Moderately increase velocity with winning ambition when positioned toward the front on the final straight after engaging in a challenge on the final corner or later.", Description: "Moderately increase velocity with winning ambition when positioned toward the front on the final straight after engaging in a challenge on the final corner or later.",
Group: 1035, GroupSkills: []horse.GroupSkill{
Rarity: 3, {ID: 100351, GroupRate: 1, Rarity: 4},
GroupRate: 1, {ID: 10351, GroupRate: 1, Rarity: 3},
GradeValue: 240, {ID: 900351, GroupRate: 2, Rarity: 1},
WitCheck: false, },
Group: 1035,
Rarity: 3,
GroupRate: 1,
GradeValue: 240,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "is_finalcorner==1&blocked_side_continuetime>=2", Precondition: "is_finalcorner==1&blocked_side_continuetime>=2",
@@ -81,11 +86,15 @@ func TestSkills(t *testing.T) {
ID: 100011, ID: 100011,
Name: "Shooting Star", Name: "Shooting Star",
Description: "Ride the momentum to increase velocity and very slightly increase acceleration after passing another runner toward the front late-race.", Description: "Ride the momentum to increase velocity and very slightly increase acceleration after passing another runner toward the front late-race.",
Group: 10001, GroupSkills: []horse.GroupSkill{
Rarity: 5, {ID: 100011, GroupRate: 1, Rarity: 5},
GroupRate: 1, {ID: 900011, GroupRate: 2, Rarity: 1},
GradeValue: 340, },
WitCheck: false, Group: 10001,
Rarity: 5,
GroupRate: 1,
GradeValue: 340,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -120,11 +129,16 @@ func TestSkills(t *testing.T) {
ID: 100351, ID: 100351,
Name: "Our Ticket to Win!", Name: "Our Ticket to Win!",
Description: "Increase velocity with winning ambition when positioned toward the front on the final straight after engaging in a challenge on the final corner or later.", Description: "Increase velocity with winning ambition when positioned toward the front on the final straight after engaging in a challenge on the final corner or later.",
Group: 10035, GroupSkills: []horse.GroupSkill{
Rarity: 4, {ID: 100351, GroupRate: 1, Rarity: 4},
GroupRate: 1, {ID: 10351, GroupRate: 1, Rarity: 3},
GradeValue: 340, {ID: 900351, GroupRate: 2, Rarity: 1},
WitCheck: false, },
Group: 10035,
Rarity: 4,
GroupRate: 1,
GradeValue: 340,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "is_finalcorner==1&blocked_side_continuetime>=2", Precondition: "is_finalcorner==1&blocked_side_continuetime>=2",
@@ -152,11 +166,15 @@ func TestSkills(t *testing.T) {
ID: 110241, ID: 110241,
Name: "Flowery☆Maneuver", Name: "Flowery☆Maneuver",
Description: "Increase velocity when passing another runner toward the front on the final corner. If passing toward the back, increase acceleration instead.", Description: "Increase velocity when passing another runner toward the front on the final corner. If passing toward the back, increase acceleration instead.",
Group: 11024, GroupSkills: []horse.GroupSkill{
Rarity: 5, {ID: 110241, GroupRate: 1, Rarity: 5},
GroupRate: 1, {ID: 910241, GroupRate: 2, Rarity: 1},
GradeValue: 340, },
WitCheck: false, Group: 11024,
Rarity: 5,
GroupRate: 1,
GradeValue: 340,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -200,11 +218,17 @@ func TestSkills(t *testing.T) {
ID: 200011, ID: 200011,
Name: "Right-Handed ◎", Name: "Right-Handed ◎",
Description: "Increase performance on right-handed tracks.", Description: "Increase performance on right-handed tracks.",
Group: 20001, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 200013, GroupRate: -1, Rarity: 1},
GroupRate: 2, {ID: 200012, GroupRate: 1, Rarity: 1},
GradeValue: 174, {ID: 200011, GroupRate: 2, Rarity: 1},
WitCheck: false, {ID: 200014, GroupRate: 3, Rarity: 2},
},
Group: 20001,
Rarity: 1,
GroupRate: 2,
GradeValue: 174,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -231,11 +255,17 @@ func TestSkills(t *testing.T) {
ID: 200012, ID: 200012,
Name: "Right-Handed ○", Name: "Right-Handed ○",
Description: "Moderately increase performance on right-handed tracks.", Description: "Moderately increase performance on right-handed tracks.",
Group: 20001, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 200013, GroupRate: -1, Rarity: 1},
GroupRate: 1, {ID: 200012, GroupRate: 1, Rarity: 1},
GradeValue: 129, {ID: 200011, GroupRate: 2, Rarity: 1},
WitCheck: false, {ID: 200014, GroupRate: 3, Rarity: 2},
},
Group: 20001,
Rarity: 1,
GroupRate: 1,
GradeValue: 129,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -262,11 +292,17 @@ func TestSkills(t *testing.T) {
ID: 200013, ID: 200013,
Name: "Right-Handed ×", Name: "Right-Handed ×",
Description: "Moderately decrease performance on right-handed tracks.", Description: "Moderately decrease performance on right-handed tracks.",
Group: 20001, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 200013, GroupRate: -1, Rarity: 1},
GroupRate: -1, {ID: 200012, GroupRate: 1, Rarity: 1},
GradeValue: -129, {ID: 200011, GroupRate: 2, Rarity: 1},
WitCheck: false, {ID: 200014, GroupRate: 3, Rarity: 2},
},
Group: 20001,
Rarity: 1,
GroupRate: -1,
GradeValue: -129,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -293,11 +329,17 @@ func TestSkills(t *testing.T) {
ID: 200014, ID: 200014,
Name: "Right-Handed Demon", Name: "Right-Handed Demon",
Description: "Increase proficiency in right-handed tracks, increasing Speed and Power.", Description: "Increase proficiency in right-handed tracks, increasing Speed and Power.",
Group: 20001, GroupSkills: []horse.GroupSkill{
Rarity: 2, {ID: 200013, GroupRate: -1, Rarity: 1},
GroupRate: 3, {ID: 200012, GroupRate: 1, Rarity: 1},
GradeValue: 461, {ID: 200011, GroupRate: 2, Rarity: 1},
WitCheck: false, {ID: 200014, GroupRate: 3, Rarity: 2},
},
Group: 20001,
Rarity: 2,
GroupRate: 3,
GradeValue: 461,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -331,11 +373,16 @@ func TestSkills(t *testing.T) {
ID: 200021, ID: 200021,
Name: "Left-Handed ◎", Name: "Left-Handed ◎",
Description: "Increase performance on left-handed tracks.", Description: "Increase performance on left-handed tracks.",
Group: 20002, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 200023, GroupRate: -1, Rarity: 1},
GroupRate: 2, {ID: 200022, GroupRate: 1, Rarity: 1},
GradeValue: 174, {ID: 200021, GroupRate: 2, Rarity: 1},
WitCheck: false, },
Group: 20002,
Rarity: 1,
GroupRate: 2,
GradeValue: 174,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -362,11 +409,16 @@ func TestSkills(t *testing.T) {
ID: 200022, ID: 200022,
Name: "Left-Handed ○", Name: "Left-Handed ○",
Description: "Moderately increase performance on left-handed tracks.", Description: "Moderately increase performance on left-handed tracks.",
Group: 20002, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 200023, GroupRate: -1, Rarity: 1},
GroupRate: 1, {ID: 200022, GroupRate: 1, Rarity: 1},
GradeValue: 129, {ID: 200021, GroupRate: 2, Rarity: 1},
WitCheck: false, },
Group: 20002,
Rarity: 1,
GroupRate: 1,
GradeValue: 129,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -393,11 +445,16 @@ func TestSkills(t *testing.T) {
ID: 200023, ID: 200023,
Name: "Left-Handed ×", Name: "Left-Handed ×",
Description: "Moderately decrease performance on left-handed tracks.", Description: "Moderately decrease performance on left-handed tracks.",
Group: 20002, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 200023, GroupRate: -1, Rarity: 1},
GroupRate: -1, {ID: 200022, GroupRate: 1, Rarity: 1},
GradeValue: -129, {ID: 200021, GroupRate: 2, Rarity: 1},
WitCheck: false, },
Group: 20002,
Rarity: 1,
GroupRate: -1,
GradeValue: -129,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -424,11 +481,15 @@ func TestSkills(t *testing.T) {
ID: 200361, ID: 200361,
Name: "Beeline Burst", Name: "Beeline Burst",
Description: "Increase velocity on a straight.", Description: "Increase velocity on a straight.",
Group: 20036, GroupSkills: []horse.GroupSkill{
Rarity: 2, {ID: 200362, GroupRate: 1, Rarity: 1},
GroupRate: 2, {ID: 200361, GroupRate: 2, Rarity: 2},
GradeValue: 508, },
WitCheck: true, Group: 20036,
Rarity: 2,
GroupRate: 2,
GradeValue: 508,
WitCheck: true,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -455,11 +516,15 @@ func TestSkills(t *testing.T) {
ID: 200362, ID: 200362,
Name: "Straightaway Adept", Name: "Straightaway Adept",
Description: "Slightly increase velocity on a straight.", Description: "Slightly increase velocity on a straight.",
Group: 20036, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 200362, GroupRate: 1, Rarity: 1},
GroupRate: 1, {ID: 200361, GroupRate: 2, Rarity: 2},
GradeValue: 217, },
WitCheck: true, Group: 20036,
Rarity: 1,
GroupRate: 1,
GradeValue: 217,
WitCheck: true,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -486,11 +551,14 @@ func TestSkills(t *testing.T) {
ID: 200831, ID: 200831,
Name: "Subdued Front Runners", Name: "Subdued Front Runners",
Description: "Slightly increase fatigue for front runners early-race.", Description: "Slightly increase fatigue for front runners early-race.",
Group: 20083, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 200831, GroupRate: 1, Rarity: 1},
GroupRate: 1, },
GradeValue: 217, Group: 20083,
WitCheck: true, Rarity: 1,
GroupRate: 1,
GradeValue: 217,
WitCheck: true,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -517,11 +585,14 @@ func TestSkills(t *testing.T) {
ID: 201801, ID: 201801,
Name: "♡ 3D Nail Art", Name: "♡ 3D Nail Art",
Description: "Moderately decrease performance on firm ground.", Description: "Moderately decrease performance on firm ground.",
Group: 20180, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 201801, GroupRate: -1, Rarity: 1},
GroupRate: -1, },
GradeValue: -129, Group: 20180,
WitCheck: false, Rarity: 1,
GroupRate: -1,
GradeValue: -129,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -548,11 +619,14 @@ func TestSkills(t *testing.T) {
ID: 300011, ID: 300011,
Name: "Unquenched Thirst", Name: "Unquenched Thirst",
Description: "Moderately increase performance with the desire to race.", Description: "Moderately increase performance with the desire to race.",
Group: 30001, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 300011, GroupRate: 1, Rarity: 1},
GroupRate: 1, },
GradeValue: 0, Group: 30001,
WitCheck: false, Rarity: 1,
GroupRate: 1,
GradeValue: 0,
WitCheck: false,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -579,11 +653,15 @@ func TestSkills(t *testing.T) {
ID: 900011, ID: 900011,
Name: "Shooting Star", Name: "Shooting Star",
Description: "Slightly increase velocity and very minimally increase acceleration after passing another runner toward the front late-race.", Description: "Slightly increase velocity and very minimally increase acceleration after passing another runner toward the front late-race.",
Group: 10001, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 100011, GroupRate: 1, Rarity: 5},
GroupRate: 2, {ID: 900011, GroupRate: 2, Rarity: 1},
GradeValue: 180, },
WitCheck: true, Group: 10001,
Rarity: 1,
GroupRate: 2,
GradeValue: 180,
WitCheck: true,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
@@ -618,11 +696,16 @@ func TestSkills(t *testing.T) {
ID: 900351, ID: 900351,
Name: "Our Ticket to Win!", Name: "Our Ticket to Win!",
Description: "Slightly increase velocity when positioned toward the front on the final straight after engaging in a challenge on the final corner or later.", Description: "Slightly increase velocity when positioned toward the front on the final straight after engaging in a challenge on the final corner or later.",
Group: 10035, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 100351, GroupRate: 1, Rarity: 4},
GroupRate: 2, {ID: 10351, GroupRate: 1, Rarity: 3},
GradeValue: 180, {ID: 900351, GroupRate: 2, Rarity: 1},
WitCheck: true, },
Group: 10035,
Rarity: 1,
GroupRate: 2,
GradeValue: 180,
WitCheck: true,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "is_finalcorner==1&blocked_side_continuetime>=2", Precondition: "is_finalcorner==1&blocked_side_continuetime>=2",
@@ -650,11 +733,15 @@ func TestSkills(t *testing.T) {
ID: 910241, ID: 910241,
Name: "Flowery☆Maneuver", Name: "Flowery☆Maneuver",
Description: "Slightly increase velocity when passing another runner toward the front on the final corner. If passing toward the back, slightly increase acceleration instead.", Description: "Slightly increase velocity when passing another runner toward the front on the final corner. If passing toward the back, slightly increase acceleration instead.",
Group: 11024, GroupSkills: []horse.GroupSkill{
Rarity: 1, {ID: 110241, GroupRate: 1, Rarity: 5},
GroupRate: 2, {ID: 910241, GroupRate: 2, Rarity: 1},
GradeValue: 180, },
WitCheck: true, Group: 11024,
Rarity: 1,
GroupRate: 2,
GradeValue: 180,
WitCheck: true,
Activations: []horse.Activation{ Activations: []horse.Activation{
{ {
Precondition: "", Precondition: "",
+1
View File
@@ -1,4 +1,5 @@
SELECT SELECT
id,
gallery_chara_id, gallery_chara_id,
disp_order, disp_order,
pos_id, pos_id,
+43
View File
@@ -0,0 +1,43 @@
WITH g AS (
SELECT
l.id AS l,
r.id AS r,
r.group_rate,
r.rarity
FROM skill_data l
JOIN skill_data r ON l.group_id = r.group_id
UNION ALL
SELECT
s.id AS l,
i.id AS r,
i.group_rate,
i.rarity
FROM skill_data s
JOIN skill_data i ON s.id IN (i.unique_skill_id_1, i.unique_skill_id_2)
UNION ALL
SELECT
s.id AS l,
i.id AS r,
i.group_rate,
i.rarity
FROM skill_data s
JOIN skill_data i ON i.id IN (s.unique_skill_id_1, s.unique_skill_id_2)
UNION ALL
SELECT
l.id AS l,
r.id AS r,
r.group_rate,
r.rarity
FROM skill_data i
JOIN skill_data l ON l.id IN (i.unique_skill_id_1, i.unique_skill_id_2)
JOIN skill_data r ON r.id IN (i.unique_skill_id_1, i.unique_skill_id_2)
WHERE l.id != r.id
)
SELECT * FROM g
ORDER BY l, group_rate, rarity DESC
+7
View File
@@ -30,6 +30,7 @@ type Skill struct {
ID SkillID `json:"skill_id"` ID SkillID `json:"skill_id"`
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
GroupSkills []GroupSkill `json:"group_skills"`
Group SkillGroupID `json:"group"` Group SkillGroupID `json:"group"`
Rarity int8 `json:"rarity"` Rarity int8 `json:"rarity"`
GroupRate int8 `json:"group_rate"` GroupRate int8 `json:"group_rate"`
@@ -42,6 +43,12 @@ type Skill struct {
IconID int `json:"icon_id"` IconID int `json:"icon_id"`
} }
type GroupSkill struct {
ID SkillID `json:"skill_id"`
GroupRate int8 `json:"group_rate"`
Rarity int8 `json:"rarity"`
}
// Activation is the parameters controlling when a skill activates. // Activation is the parameters controlling when a skill activates.
type Activation struct { type Activation struct {
Precondition string `json:"precondition,omitzero"` Precondition string `json:"precondition,omitzero"`
+35
View File
@@ -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]]
}
+27
View File
@@ -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]]
}
+28
View File
@@ -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]]
}
+29
View File
@@ -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]]
}
+29
View File
@@ -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]]
}
+48
View File
@@ -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) + ")"
}
}
+29
View File
@@ -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]]
}
+920
View File
@@ -0,0 +1,920 @@
package replay
import (
"fmt"
"strings"
"sync"
"git.sunturtle.xyz/zephyr/horse"
)
type Race struct {
RaceType RaceType `json:"raceType"`
IsExistPlayerRace bool `json:"isExistPlayerRace"`
IsExistGhostRace bool `json:"isExistGhostRace"`
IsExistFollowRace bool `json:"isExistFollowRace"`
IsMultiplePlayerRace bool `json:"isMultiplePlayerRace"`
RandomSeed int32 `json:"randomSeed"`
SingleRaceProgramID int32 `json:"singleRaceProgramId"`
OpponentEvaluate int32 `json:"opponentEvaluate"`
SelfEvaluate int32 `json:"selfEvaluate"`
SupportCardScoreBonus int32 `json:"supportCardScoreBonus"`
ScoreCalcTeamID int32 `json:"scoreCalcTeamId"`
RaceNo int32 `json:"raceNo"`
RaceCourseSet RaceCourseSet `json:"raceCourseSet"`
FenceSet struct{} `json:"fenceSet"`
RaceTrack struct{} `json:"raceTrack"`
GoalGate int32 `json:"goalGate"`
GoalGateFlower int32 `json:"goalGateFlower"`
InitialLaneType InitialLaneType `json:"initialLaneType"`
RotationCategory Rotation `json:"rotationCategory"`
ResultBoardConditionType ResultBoardConditionType `json:"resultBoardConditionType"`
CourseSectionDistance float32 `json:"courseSectionDistance"`
CourseDistanceType CourseDistanceType `json:"courseDistanceType"`
CourseFurlongNum int32 `json:"courseFurlongNum"`
IsHalfGate bool `json:"isHalfGate"`
IsHorseNumVariationGate bool `json:"isHorseNumVariationGate"`
TurfVisionType TurfVisionType `json:"turfVisionType"`
GroundCondition GroundCondition `json:"groundCondition"`
Weather RaceWeather `json:"weather"`
Season Season `json:"season"`
Time RaceTime `json:"time"`
BaseSpeed float32 `json:"baseSpeed"`
BorderTimeScaled float32 `json:"borderTimeScaled"`
ChallengeMatchDifficulty RaceDifficulty `json:"challengeMatchDifficulty"`
NumRaceHorses int32 `json:"numRaceHorses"`
PostNumberMax int32 `json:"postNumberMax"`
PlayerHorseIndex int32 `json:"playerHorseIndex"`
OverridePlayerHorseIndex int32 `json:"overridePlayerHorseIndex"`
PlayerTeamMemberArray []RaceHorse `json:"playerTeamMemberArray"`
PlayerTeamTopFinishOrderHorse RaceHorse `json:"playerTeamTopFinishOrderHorse"`
IsGateInPopularityInitialized bool `json:"isGateInPopularityInitialized"`
RaceHorse []RaceHorse `json:"raceHorse"`
RaceBibMaster struct{} `json:"raceBibMaster"`
RaceMaster struct{} `json:"raceMaster"`
RaceInstanceMaster struct{} `json:"raceInstanceMaster"`
SimDataBase64 string `json:"simDataBase64"`
SimData struct{} `json:"simData"`
SimReader struct{} `json:"simReader"`
EpisodeRaceReplayID int32 `json:"episodeRaceReplayId"`
IsNotSimulateExport bool `json:"isNotSimulateExport"`
LaneDistanceMax float32 `json:"laneDistanceMax"`
ReplayCheckInfo struct{} `json:"replayCheckInfo"`
ReplayCheckInfoDaily struct{} `json:"replayCheckInfoDaily"`
ReplayCheckInfoLegend struct{} `json:"replayCheckInfoLegend"`
IsDailyLegendRace bool `json:"isDailyLegendRace"`
ReplayCheckInfoChallengeMatch struct{} `json:"replayCheckInfoChallengeMatch"`
RaceRewardSingle RaceRewardSingle `json:"raceRewardSingle"`
ResultHorseIndex int32 `json:"resultHorseIndex"`
PrevGradeType CharaGradeType `json:"prevGradeType"`
MainStoryRaceGimmickType MainStoryRaceGimmickType `json:"mainStoryRaceGimmickType"`
IsMainStoryRaceMatchGimmick bool `json:"isMainStoryRaceMatchGimmick"`
UnlockFlags uint32 `json:"unlockFlags"`
PhaseCalculator struct{} `json:"phaseCalculator"`
HorseIndexByFinishOrder []int32 `json:"horseIndexByFinishOrder"`
HorseIndexByPopularity []int32 `json:"horseIndexByPopularity"`
}
type RaceType int32
//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type RaceType -linecomment -trimprefix RaceType
const (
RaceTypeNone RaceType = iota // NONE
RaceTypePvP
RaceTypeTutorial
RaceTypeStory
RaceTypeStoryCondition
RaceTypeChampions
RaceTypeSingle
RaceTypeSingleModeScenarioTeamRace
RaceTypeRoomMatch
RaceTypePractice
RaceTypeDaily
RaceTypeTeamBuilding
RaceTypeLegend
RaceTypeChallengeMatch
RaceTypeTeamStadium
// no value 15
RaceTypeHeroes RaceType = 16
raceTypeMaxInclusive
)
func (r RaceType) AppendText(b []byte) ([]byte, error) {
return append(b, r.String()...), nil
}
func (r RaceType) MarshalText() ([]byte, error) {
return r.AppendText(nil)
}
var raceTypeNames = sync.OnceValue(func() map[string]RaceType {
m := make(map[string]RaceType, raceTypeMaxInclusive)
for r := RaceTypeNone; r <= raceTypeMaxInclusive; r++ {
s := r.String()
if strings.HasPrefix(s, "RaceType(") {
continue
}
m[r.String()] = r
}
return m
})
func (r *RaceType) UnmarshalText(b []byte) error {
n, ok := raceTypeNames()[string(b)]
*r = n
if !ok {
return fmt.Errorf("unknown race type %q", b)
}
return nil
}
type RaceCourseSet struct {
ID horse.RacecourseID `json:"id"`
RaceTrackID horse.RaceTrackID `json:"raceTrackId"`
Distance int32 `json:"distance"`
Ground int `json:"ground"`
Inout int `json:"inout"`
Turn int `json:"turn"`
FenceSet int `json:"fenceSet"`
FloatLaneMax int `json:"floatLaneMax"`
CourseSetStatusID int `json:"courseSetStatusId"`
FinishTimeMin int `json:"finishTimeMin"`
FinishTimeMinRandomRange int `json:"finishTimeMinRandomRange"`
FinishTimeMax int `json:"finishTimeMax"`
FinishTimeMaxRandomRange int `json:"finishTimeMaxRandomRange"`
}
type InitialLaneType int8
//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type InitialLaneType -trimprefix InitialLane
const (
InitialLaneExtraSpaceAfter9 InitialLaneType = iota + 1
InitialLaneEquidistant
InitialLaneExtraSpaceAfter14
InitialLaneExtraSpaceAfter8
initialLaneMax
)
func (r InitialLaneType) AppendText(b []byte) ([]byte, error) {
return append(b, r.String()...), nil
}
func (r InitialLaneType) MarshalText() ([]byte, error) {
return r.AppendText(nil)
}
var initialLaneNames = sync.OnceValue(func() map[string]InitialLaneType {
m := make(map[string]InitialLaneType, initialLaneMax)
for r := InitialLaneExtraSpaceAfter9; r < initialLaneMax; r++ {
s := r.String()
if strings.HasPrefix(s, "InitialLaneType(") {
continue
}
m[r.String()] = r
}
return m
})
func (r *InitialLaneType) UnmarshalText(b []byte) error {
n, ok := initialLaneNames()[string(b)]
*r = n
if !ok {
return fmt.Errorf("unknown initial lane type type %q", b)
}
return nil
}
type Rotation int8
//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type Rotation -trimprefix Rotation
const (
RotationRight Rotation = iota + 1
RotationLeft
RotationStraightRight
RotationStraightLeft
)
func (r Rotation) AppendText(b []byte) ([]byte, error) {
return append(b, r.String()...), nil
}
func (r Rotation) MarshalText() ([]byte, error) {
return r.AppendText(nil)
}
func (r *Rotation) UnmarshalText(b []byte) error {
switch string(b) {
case "Right":
*r = RotationRight
case "Left":
*r = RotationLeft
case "StraightRight":
*r = RotationStraightRight
case "StraightLeft":
*r = RotationStraightLeft
default:
*r = 0
return fmt.Errorf("unknown rotation type %q", b)
}
return nil
}
type ResultBoardConditionType int8
//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type ResultBoardConditionType -linecomment
const (
ResultBoardTurfNone ResultBoardConditionType = iota + 1 // Turf_None
ResultBoardTurfDirt // Turf_Dirt
ResultBoardDirtNone // Dirt_None
ResultBoardDirtTurf // Dirt_Turf
)
func (r ResultBoardConditionType) AppendText(b []byte) ([]byte, error) {
return append(b, r.String()...), nil
}
func (r ResultBoardConditionType) MarshalText() ([]byte, error) {
return r.AppendText(nil)
}
func (r *ResultBoardConditionType) UnmarshalText(b []byte) error {
switch string(b) {
case "Turf_None":
*r = ResultBoardTurfNone
case "Turf_Dirt":
*r = ResultBoardTurfDirt
case "Dirt_None":
*r = ResultBoardDirtNone
case "Dirt_Turf":
*r = ResultBoardDirtTurf
default:
*r = 0
return fmt.Errorf("unknown result board type %q", b)
}
return nil
}
type CourseDistanceType int8
//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type CourseDistanceType -linecomment -trimprefix Distance
const (
DistanceSprint CourseDistanceType = iota + 1 // Short
DistanceMile
DistanceMedium // Middle
DistanceLong
)
func (r CourseDistanceType) AppendText(b []byte) ([]byte, error) {
return append(b, r.String()...), nil
}
func (r CourseDistanceType) MarshalText() ([]byte, error) {
return r.AppendText(nil)
}
func (r *CourseDistanceType) UnmarshalText(b []byte) error {
switch string(b) {
case "Short":
*r = DistanceSprint
case "Mile":
*r = DistanceMile
case "Middle":
*r = DistanceMedium
case "Long":
*r = DistanceLong
default:
*r = 0
return fmt.Errorf("unknown distance type %q", b)
}
return nil
}
type TurfVisionType int8
//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type TurfVisionType -trimprefix TurfVision
const (
TurfVisionURA TurfVisionType = iota + 1
TurfVisionNAU
TurfVisionStand
turfVisionMax
)
func (r TurfVisionType) AppendText(b []byte) ([]byte, error) {
return append(b, r.String()...), nil
}
func (r TurfVisionType) MarshalText() ([]byte, error) {
return r.AppendText(nil)
}
var turfVisionNames = sync.OnceValue(func() map[string]TurfVisionType {
m := make(map[string]TurfVisionType, turfVisionMax)
for r := TurfVisionURA; r < turfVisionMax; r++ {
s := r.String()
if strings.HasPrefix(s, "TurfVisionType(") {
continue
}
m[r.String()] = r
}
return m
})
func (r *TurfVisionType) UnmarshalText(b []byte) error {
n, ok := turfVisionNames()[string(b)]
*r = n
if !ok {
return fmt.Errorf("unknown turf vision type %q", b)
}
return nil
}
type GroundCondition int8
//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type GroundCondition -linecomment
const (
GroundFirm GroundCondition = iota + 1 // Good
GroundGood // Soft
GroundSoft // Hard
GroundHeavy // Bad
)
func (r GroundCondition) AppendText(b []byte) ([]byte, error) {
return append(b, r.String()...), nil
}
func (r GroundCondition) MarshalText() ([]byte, error) {
return r.AppendText(nil)
}
func (r *GroundCondition) UnmarshalText(b []byte) error {
switch string(b) {
case "Good":
*r = GroundFirm
case "Soft":
*r = GroundGood
case "Hard":
*r = GroundSoft
case "Bad":
*r = GroundHeavy
default:
*r = 0
return fmt.Errorf("unknown ground condition type %q", b)
}
return nil
}
type RaceWeather int8
//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type RaceWeather -trimprefix Weather
const (
WeatherMin RaceWeather = iota
WeatherSunny
WeatherCloudy
WeatherRainy
WeatherSnow
WeatherMax
)
func (r RaceWeather) AppendText(b []byte) ([]byte, error) {
return append(b, r.String()...), nil
}
func (r RaceWeather) MarshalText() ([]byte, error) {
return r.AppendText(nil)
}
func (r *RaceWeather) UnmarshalText(b []byte) error {
switch string(b) {
case "Min", "NONE":
*r = WeatherMin
case "Sunny":
*r = WeatherSunny
case "Cloudy":
*r = WeatherCloudy
case "Rainy":
*r = WeatherRainy
case "Snow":
*r = WeatherSnow
case "Max":
*r = WeatherMax
default:
*r = 0
return fmt.Errorf("unknown race weather %q", b)
}
return nil
}
type Season int8
//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type Season -trimprefix Season
const (
SeasonMin Season = iota
SeasonSpring
SeasonSummer
SeasonFall
SeasonWinter
SeasonCherryBlossom
SeasonMax
)
func (r Season) AppendText(b []byte) ([]byte, error) {
return append(b, r.String()...), nil
}
func (r Season) MarshalText() ([]byte, error) {
return r.AppendText(nil)
}
func (r *Season) UnmarshalText(b []byte) error {
switch string(b) {
case "Min", "NONE":
*r = SeasonMin
case "Spring":
*r = SeasonSpring
case "Summer":
*r = SeasonSummer
case "Fall":
*r = SeasonFall
case "Winter":
*r = SeasonWinter
case "CherryBlossom":
*r = SeasonCherryBlossom
case "Max":
*r = SeasonMax
default:
*r = 0
return fmt.Errorf("unknown season %q", b)
}
return nil
}
type RaceTime int8
//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type RaceTime -trimprefix Time
const (
TimeMin RaceTime = iota
TimeMorning
TimeDaytime
TimeEvening
TimeNight
TimeMax
)
func (r RaceTime) AppendText(b []byte) ([]byte, error) {
return append(b, r.String()...), nil
}
func (r RaceTime) MarshalText() ([]byte, error) {
return r.AppendText(nil)
}
func (r *RaceTime) UnmarshalText(b []byte) error {
switch string(b) {
case "Min":
*r = TimeMin
case "Morning":
*r = TimeMorning
case "Daytime":
*r = TimeDaytime
case "Evening":
*r = TimeEvening
case "Night":
*r = TimeNight
case "Max":
*r = TimeMax
default:
*r = 0
return fmt.Errorf("unknown race time %q", b)
}
return nil
}
type RaceDifficulty int8
//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type RaceDifficulty -trimprefix Difficulty
const (
DifficultyEasy RaceDifficulty = iota + 1
DifficultyNormal
DifficultyHard
DifficultyVeryHard
DifficultyExtreme
difficultyMax
)
func (r RaceDifficulty) AppendText(b []byte) ([]byte, error) {
return append(b, r.String()...), nil
}
func (r RaceDifficulty) MarshalText() ([]byte, error) {
return r.AppendText(nil)
}
var difficultyNames = sync.OnceValue(func() map[string]RaceDifficulty {
m := make(map[string]RaceDifficulty, difficultyMax)
for r := DifficultyEasy; r < difficultyMax; r++ {
s := r.String()
if strings.HasPrefix(s, "RaceDifficulty(") {
continue
}
m[r.String()] = r
}
// extra one for pure pvp races
m["UNKNOWN"] = 0
return m
})
func (r *RaceDifficulty) UnmarshalText(b []byte) error {
n, ok := difficultyNames()[string(b)]
*r = n
if !ok {
return fmt.Errorf("unknown turf vision type %q", b)
}
return nil
}
type RaceParam struct {
RawSpeed int `json:"rawSpeed"`
RawStamina int `json:"rawStamina"`
RawPow int `json:"rawPow"`
RawGuts int `json:"rawGuts"`
RawWiz int `json:"rawWiz"`
BaseSpeed float32 `json:"baseSpeed"`
BaseStamina float32 `json:"baseStamina"`
BasePow float32 `json:"basePow"`
BaseGuts float32 `json:"baseGuts"`
BaseWiz float32 `json:"baseWiz"`
Motivation string `json:"motivation"`
MotivationCoef float32 `json:"motivationCoef"`
}
type SkillArray struct {
SkillID int `json:"skill_id"`
Level int `json:"level"`
}
type RaceResultArray struct {
Turn int `json:"turn"`
ProgramID int `json:"program_id"`
Weather int `json:"weather"`
GroundCondition int `json:"ground_condition"`
RunningStyle int `json:"running_style"`
Popularity int `json:"popularity"`
ResultRank int `json:"result_rank"`
ResultTime int `json:"result_time"`
PrizeMoney int `json:"prize_money"`
}
type ResponseHorseData struct {
FrameOrder int `json:"frame_order"`
ViewerID int64 `json:"viewer_id"`
TrainerName string `json:"trainer_name"`
OwnerViewerID int `json:"owner_viewer_id"`
OwnerTrainerName string `json:"owner_trainer_name"`
SingleModeCharaID int `json:"single_mode_chara_id"`
TrainedCharaID int `json:"trained_chara_id"`
NicknameID int `json:"nickname_id"`
CharaID int `json:"chara_id"`
CardID int `json:"card_id"`
MobID int `json:"mob_id"`
Rarity int `json:"rarity"`
TalentLevel int `json:"talent_level"`
SkillArray []SkillArray `json:"skill_array"`
Stamina int `json:"stamina"`
Speed int `json:"speed"`
Pow int `json:"pow"`
Guts int `json:"guts"`
Wiz int `json:"wiz"`
RunningStyle int `json:"running_style"`
RaceDressID int `json:"race_dress_id"`
CharaColorType int `json:"chara_color_type"`
NpcType int `json:"npc_type"`
FinalGrade int `json:"final_grade"`
Popularity int `json:"popularity"`
PopularityMarkRankArray []int `json:"popularity_mark_rank_array"`
ProperDistanceShort int `json:"proper_distance_short"`
ProperDistanceMile int `json:"proper_distance_mile"`
ProperDistanceMiddle int `json:"proper_distance_middle"`
ProperDistanceLong int `json:"proper_distance_long"`
ProperRunningStyleNige int `json:"proper_running_style_nige"`
ProperRunningStyleSenko int `json:"proper_running_style_senko"`
ProperRunningStyleSashi int `json:"proper_running_style_sashi"`
ProperRunningStyleOikomi int `json:"proper_running_style_oikomi"`
ProperGroundTurf int `json:"proper_ground_turf"`
ProperGroundDirt int `json:"proper_ground_dirt"`
Motivation int `json:"motivation"`
WinSaddleIDArray []int `json:"win_saddle_id_array"`
RaceResultArray []RaceResultArray `json:"race_result_array"`
TeamID int `json:"team_id"`
TeamMemberID int `json:"team_member_id"`
TeamRank int `json:"team_rank"`
SingleModeWinCount int `json:"single_mode_win_count"`
ItemIDArray []any `json:"item_id_array"`
MotivationChangeFlag int `json:"motivation_change_flag"`
FrameOrderChangeFlag int `json:"frame_order_change_flag"`
}
type RaceRecord struct {
}
type FactorDataArray struct {
FactorLv int `json:"factorLv"`
FactorID int `json:"factorId"`
BaseFactorID int `json:"baseFactorId"`
UpgradeHistoryList []any `json:"upgradeHistoryList"`
}
type FavoriteData struct {
}
type SuccessionCharaList struct {
PositionID int `json:"positionId"`
CardID int `json:"cardId"`
Rarity int `json:"rarity"`
Level int `json:"level"`
Rank int `json:"rank"`
FactorDataArray []FactorDataArray `json:"factorDataArray"`
SortedFactorList []any `json:"sortedFactorList"`
SortedFactorListForProfileCard []any `json:"sortedFactorListForProfileCard"`
OwnerViewerID int `json:"ownerViewerId"`
IsPlayer bool `json:"isPlayer"`
WinSaddleArray []any `json:"winSaddleArray"`
WinSaddleIDArray []int `json:"winSaddleIdArray"`
}
type Master struct {
}
type AcquiredSkillArray struct {
MasterID int `json:"masterId"`
Level int `json:"level"`
Master Master `json:"master"`
}
type SupportCardArray struct {
Position int `json:"position"`
SupportCardID int `json:"supportCardId"`
LimitBreakCount int `json:"limitBreakCount"`
Exp int `json:"exp"`
}
type SingleModeRaceResultArray struct {
Turn int `json:"turn"`
ProgramID int `json:"programId"`
RaceInstanceID int `json:"raceInstanceId"`
FrameOrder int `json:"frameOrder"`
NpcCount int `json:"npcCount"`
Weather int `json:"weather"`
GroundCondition int `json:"groundCondition"`
RunningStyle int `json:"runningStyle"`
ResultRank int `json:"resultRank"`
ScenarioID int `json:"scenarioId"`
}
type MasterCardData struct {
}
type MasterCharaData struct {
}
type MasterCardRarityData struct {
}
type TrainedCharaDataAccessor struct {
}
type TrainedCharaData struct {
ID int `json:"id"`
IsSaved bool `json:"isSaved"`
ViewerID int64 `json:"viewerId"`
OwnerViewerID int `json:"ownerViewerId"`
OwnerTrainedCharaID int `json:"ownerTrainedCharaId"`
UseType int `json:"useType"`
CardID int `json:"cardId"`
NickNameID int `json:"nickNameId"`
NickNameIDArray []int `json:"nickNameIdArray"`
Stamina int `json:"stamina"`
Speed int `json:"speed"`
Power int `json:"power"`
Guts int `json:"guts"`
Wiz int `json:"wiz"`
Fans int `json:"fans"`
Rank int `json:"rank"`
RankScore int `json:"rankScore"`
RunningStyle int `json:"runningStyle"`
ProperGroundTurf int `json:"properGroundTurf"`
ProperGroundDirt int `json:"properGroundDirt"`
ProperDistanceShort int `json:"properDistanceShort"`
ProperDistanceMile int `json:"properDistanceMile"`
ProperDistanceMiddle int `json:"properDistanceMiddle"`
ProperDistanceLong int `json:"properDistanceLong"`
ProperRunningStyleNige int `json:"properRunningStyleNige"`
ProperRunningStyleSenko int `json:"properRunningStyleSenko"`
ProperRunningStyleSashi int `json:"properRunningStyleSashi"`
ProperRunningStyleOikomi int `json:"properRunningStyleOikomi"`
SuccessionCount int `json:"successionCount"`
FactorDataArray []FactorDataArray `json:"factorDataArray"`
CreateTime string `json:"createTime"`
ScenarioID int `json:"scenarioId"`
TalentLevel int `json:"talentLevel"`
CharaGrade int `json:"charaGrade"`
Rarity int `json:"rarity"`
IsLock bool `json:"isLock"`
FavoriteData FavoriteData `json:"favoriteData"`
CachedCreateTimeTimeStamp int `json:"cachedCreateTimeTimeStamp"`
SortedFactorList []any `json:"sortedFactorList"`
SortedFactorProfileCardList []any `json:"sortedFactorProfileCardList"`
FactorListIncludingSuccession []any `json:"factorListIncludingSuccession"`
SuccessionCharaList []SuccessionCharaList `json:"successionCharaList"`
IsSuccessionHistoryInitialized bool `json:"isSuccessionHistoryInitialized"`
SuccessionHistoryList []any `json:"successionHistoryList"`
AcquiredSkillArray []AcquiredSkillArray `json:"acquiredSkillArray"`
SupportCardArray []SupportCardArray `json:"supportCardArray"`
SingleModeRaceResultArray []SingleModeRaceResultArray `json:"singleModeRaceResultArray"`
WinSaddleArray []any `json:"winSaddleArray"`
WinSaddleIDArray []int `json:"winSaddleIdArray"`
CacheCharaID int `json:"cacheCharaId"`
MasterCardData MasterCardData `json:"masterCardData"`
MasterCharaData MasterCharaData `json:"masterCharaData"`
MasterCardRarityData MasterCardRarityData `json:"masterCardRarityData"`
SingleTotalRaceNum int `json:"singleTotalRaceNum"`
SingleWinNum int `json:"singleWinNum"`
TrainedCharaDataAccessor TrainedCharaDataAccessor `json:"trainedCharaDataAccessor"`
}
type PlayerTeamMember struct {
HorseIndex int `json:"horseIndex"`
PostNumber int `json:"postNumber"`
CharaID int `json:"charaId"`
CharaName string `json:"charaName"`
FinishOrder int `json:"finishOrder"`
FinishTimeRaw float32 `json:"finishTimeRaw"`
FinishTimeScaled float32 `json:"finishTimeScaled"`
FinishDiffTimeFromPrev float32 `json:"finishDiffTimeFromPrev"`
RaceParam RaceParam `json:"raceParam"`
ResponseHorseData ResponseHorseData `json:"responseHorseData"`
Popularity int `json:"popularity"`
PopularityRankLeft int `json:"popularityRankLeft"`
PopularityRankCenter int `json:"popularityRankCenter"`
PopularityRankRight int `json:"popularityRankRight"`
GateInPopularity int `json:"gateInPopularity"`
Rarity string `json:"rarity"`
TrainerName string `json:"trainerName"`
IsGhost bool `json:"isGhost"`
IsRunningStyleExInitialized bool `json:"isRunningStyleExInitialized"`
RunningStyleEx string `json:"runningStyleEx"`
Defeat string `json:"defeat"`
RaceDressID int `json:"raceDressId"`
RaceDressIDWithOption int `json:"raceDressIdWithOption"`
RunningType string `json:"runningType"`
ActiveProperDistance string `json:"activeProperDistance"`
ActiveProperGroundType string `json:"activeProperGroundType"`
MobID int `json:"mobId"`
RaceRecord RaceRecord `json:"raceRecord"`
FinishOrderRawScore int `json:"finishOrderRawScore"`
TrainedCharaData TrainedCharaData `json:"trainedCharaData"`
}
type PlayerTeamTopFinishOrderHorse struct {
HorseIndex int `json:"horseIndex"`
PostNumber int `json:"postNumber"`
CharaID int `json:"charaId"`
CharaName string `json:"charaName"`
FinishOrder int `json:"finishOrder"`
FinishTimeRaw float32 `json:"finishTimeRaw"`
FinishTimeScaled float32 `json:"finishTimeScaled"`
FinishDiffTimeFromPrev float32 `json:"finishDiffTimeFromPrev"`
RaceParam RaceParam `json:"raceParam"`
ResponseHorseData ResponseHorseData `json:"responseHorseData"`
Popularity int `json:"popularity"`
PopularityRankLeft int `json:"popularityRankLeft"`
PopularityRankCenter int `json:"popularityRankCenter"`
PopularityRankRight int `json:"popularityRankRight"`
GateInPopularity int `json:"gateInPopularity"`
Rarity string `json:"rarity"`
TrainerName string `json:"trainerName"`
IsGhost bool `json:"isGhost"`
IsRunningStyleExInitialized bool `json:"isRunningStyleExInitialized"`
RunningStyleEx string `json:"runningStyleEx"`
Defeat string `json:"defeat"`
RaceDressID int `json:"raceDressId"`
RaceDressIDWithOption int `json:"raceDressIdWithOption"`
RunningType string `json:"runningType"`
ActiveProperDistance string `json:"activeProperDistance"`
ActiveProperGroundType string `json:"activeProperGroundType"`
MobID int `json:"mobId"`
RaceRecord RaceRecord `json:"raceRecord"`
FinishOrderRawScore int `json:"finishOrderRawScore"`
TrainedCharaData TrainedCharaData `json:"trainedCharaData"`
}
type RaceHorse struct {
HorseIndex int `json:"horseIndex"`
PostNumber int `json:"postNumber"`
CharaID int `json:"charaId"`
CharaName string `json:"charaName"`
FinishOrder int `json:"finishOrder"`
FinishTimeRaw float32 `json:"finishTimeRaw"`
FinishTimeScaled float32 `json:"finishTimeScaled"`
FinishDiffTimeFromPrev float32 `json:"finishDiffTimeFromPrev"`
RaceParam RaceParam `json:"raceParam"`
ResponseHorseData ResponseHorseData `json:"responseHorseData"`
Popularity int `json:"popularity"`
PopularityRankLeft int `json:"popularityRankLeft"`
PopularityRankCenter int `json:"popularityRankCenter"`
PopularityRankRight int `json:"popularityRankRight"`
GateInPopularity int `json:"gateInPopularity"`
Rarity string `json:"rarity"`
TrainerName string `json:"trainerName"`
IsGhost bool `json:"isGhost"`
IsRunningStyleExInitialized bool `json:"isRunningStyleExInitialized"`
RunningStyleEx string `json:"runningStyleEx"`
Defeat string `json:"defeat"`
RaceDressID int `json:"raceDressId"`
RaceDressIDWithOption int `json:"raceDressIdWithOption"`
RunningType string `json:"runningType"`
ActiveProperDistance string `json:"activeProperDistance"`
ActiveProperGroundType string `json:"activeProperGroundType"`
MobID int `json:"mobId"`
RaceRecord RaceRecord `json:"raceRecord"`
FinishOrderRawScore int `json:"finishOrderRawScore"`
TrainedCharaData TrainedCharaData `json:"trainedCharaData,omitempty"`
}
type RaceRewardSingle struct {
// TODO(zeph): this
}
type CharaGradeType int8
//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type CharaGradeType -linecomment -trimprefix CharaGrade
const (
CharaGradeNone CharaGradeType = iota // NONE
CharaGradeDebut
CharaGradeNoWin
CharaGradeOpen
CharaGradeG3Silver
CharaGradeG3Gold
CharaGradeG2Silver
CharaGradeG2Gold
CharaGradeG1Bronze
CharaGradeG1Silver
CharaGradeG1Gold
charaGradeMax
)
func (r CharaGradeType) AppendText(b []byte) ([]byte, error) {
return append(b, r.String()...), nil
}
func (r CharaGradeType) MarshalText() ([]byte, error) {
return r.AppendText(nil)
}
var charaGradeNames = sync.OnceValue(func() map[string]CharaGradeType {
m := make(map[string]CharaGradeType, raceTypeMaxInclusive)
for r := CharaGradeNone; r < charaGradeMax; r++ {
s := r.String()
if strings.HasPrefix(s, "CharaGradeType(") {
continue
}
m[r.String()] = r
}
return m
})
func (r *CharaGradeType) UnmarshalText(b []byte) error {
n, ok := charaGradeNames()[string(b)]
*r = n
if !ok {
return fmt.Errorf("unknown chara grade type %q", b)
}
return nil
}
type MainStoryRaceGimmickType int8
//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type MainStoryRaceGimmickType -linecomment
const (
GimmickNone MainStoryRaceGimmickType = iota // NONE
GimmickSpecial00 // Special_00
gimmickMax
)
func (r MainStoryRaceGimmickType) AppendText(b []byte) ([]byte, error) {
return append(b, r.String()...), nil
}
func (r MainStoryRaceGimmickType) MarshalText() ([]byte, error) {
return r.AppendText(nil)
}
var mainStoryGimmickNames = sync.OnceValue(func() map[string]MainStoryRaceGimmickType {
m := make(map[string]MainStoryRaceGimmickType, raceTypeMaxInclusive)
for r := GimmickNone; r < gimmickMax; r++ {
s := r.String()
if strings.HasPrefix(s, "MainStoryRaceGimmickType(") {
continue
}
m[r.String()] = r
}
return m
})
func (r *MainStoryRaceGimmickType) UnmarshalText(b []byte) error {
n, ok := mainStoryGimmickNames()[string(b)]
*r = n
if !ok {
return fmt.Errorf("unknown main story race gimmick type %q", b)
}
return nil
}
@@ -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]]
}
+27
View File
@@ -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]]
}
+30
View File
@@ -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]]
}
+27
View File
@@ -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]]
}
+10 -5
View File
@@ -49,11 +49,16 @@ export function convoCharas(c: Conversation): number[] {
export function requiredCharas(c: Conversation): number[] { export function requiredCharas(c: Conversation): number[] {
switch (c.condition_type) { switch (c.condition_type) {
case Required.None: return [] case Required.None:
case Required.All: return convoCharas(c); return [];
case Required.Chara1: return [c.chara_1]; case Required.All:
case Required.Chara2: return [c.chara_2!]; return convoCharas(c);
case Required.Chara3: return [c.chara_3!]; case Required.Chara1:
return [c.chara_1];
case Required.Chara2:
return [c.chara_2!];
case Required.Chara3:
return [c.chara_3!];
} }
} }
+8 -16
View File
@@ -1143,22 +1143,14 @@
>. (This race is why I made it.) >. (This race is why I made it.)
</p> </p>
<Sec h={3} id="jbc-sprint">JBC Sprint (Ooi ver.)</Sec> <div id="jbc-sprint" class="hidden target:block">
<p> <h3>JBC Sprint</h3>
An even more anti-front track is Ooi 1200 Dirt. This one is actively malicious. Visually, it looks like late race starts on a <p>
corner, but the portion before the stretch is a special <i>neither corner nor straight</i> property. That means Angling won't activate, It used to be the case that Oi 1200 Dirt had a strange neither-straight-nor-corner segment at the start of late race so that
and VPP is delayed (though still within the accel period). Angling wouldn't activate. As of the 1 Jul update, the corner is now connected to the final straight, so there is no longer
</p> anything remarkable about this track.
<p> </p>
Fortunately, JBC Sprint is after summer, which means you should be able to stat diff your opponents. If you are planning to </div>
win this race, you may want to prioritize a bit of extra speed training, or take Front Straights/Corners. If MANT, don't be
too surprised if you lose a clock or five to a Taiki Shuttle rival.
</p>
<p>
Since the 1 Jul update, the JBC races take place at a randomly selected one of the NAR courses (Ooi, Kawasaki, Funabashi,
Morioka) each year. If you're hunting for a completionist title on a front runner, you have the option of just not picking an
Ooi JBC Sprint; the others are all Angling tracks.
</p>
<Sec h={3} id="3k">Kikuka Sho &amp; Tenno Sho (Spring)</Sec> <Sec h={3} id="3k">Kikuka Sho &amp; Tenno Sho (Spring)</Sec>
<p> <p>
+463
View File
@@ -17,8 +17,107 @@
} }
}); });
const raceGroups = [
[1, 'Common'],
[2, 'Daily Race'],
[3, '"Training Practice"'],
[4, '"Story Condition"'],
[5, '"Team Building"'],
[6, 'Practice'],
[7, 'Career-Exclusive'],
[8, 'Legend Race'],
[9, 'Team Trials'],
[10, 'Unity Cup Team Race'],
[11, "Champions' Meet"],
[12, 'Room Match'],
[13, '"Challenge Match"'],
[14, 'League of Heroes Race'],
[61, 'Custom G1'],
] as const;
const raceGrades = [
[100, 'G1'],
[200, 'G2'],
[300, 'G3'],
[400, 'OP'],
[700, 'Pre-OP'],
[800, 'Maiden'],
[900, 'Debut'],
[999, 'Daily Race or Room Match (?)'],
[1000, 'Unity Cup Team Race (?)'],
] as const;
const raceTimes = [
[1, 'Morning (unused)'],
[2, 'Daytime'],
[3, 'Evening'],
[4, 'Night'],
] as const;
const raceInitialLanes = [
[1, 9, 0.03333333],
[2, 0, 0],
[3, 14, 0.10333333],
[4, 8, 0.03333333],
] as const;
const raceAreas = [
[0, 'Hokkaido'],
[1, 'Tohoku'],
[2, 'Kanto'],
[3, 'Western Japan'],
[4, 'Kokura'],
[5, 'France'],
[6, 'America'],
] as const;
const raceLoops = [
[1, 'None'],
[2, 'Inner'],
[3, 'Outer'],
[4, 'Outer→Inner'],
] as const;
const raceRotations = [
[1, 'Right'],
[2, 'Left'],
[3, 'Rightward Stretch (unused)'],
[4, 'Leftward Stretch'],
] as const;
const raceCourseThresholds = [
[1, 1, 0],
[2, 2, 0],
[3, 3, 0],
[4, 4, 0],
[5, 5, 0],
[6, 1, 2],
[7, 2, 3],
[8, 2, 4],
[9, 3, 5],
[10, 2, 5],
[11, 4, 5],
] as const;
const raceSaddleTypes = [
[0, 'Special'],
[1, 'G3'],
[2, 'G2'],
[3, 'G1'],
] as const;
const racePermissions = [
[1, 'Junior'],
[2, 'Classic'],
[3, 'Classic and Senior'],
[4, 'Senior'],
[5, 'Finale'],
] as const;
const skills = $derived({ const skills = $derived({
rickey: skillMap.get(100981), rickey: skillMap.get(100981),
right1: skillMap.get(200012),
left1: skillMap.get(200022),
fall2: skillMap.get(200191), fall2: skillMap.get(200191),
fall1: skillMap.get(200192), fall1: skillMap.get(200192),
fallX: skillMap.get(200193), fallX: skillMap.get(200193),
@@ -299,6 +398,10 @@
in-game shows a "Connecting..." indicator while it loads, it probably isn't in the mdb. Notably, this includes everything in-game shows a "Connecting..." indicator while it loads, it probably isn't in the mdb. Notably, this includes everything
related to career event outcomes. (Event information on sites like GameTora and GameWith are massive crowd-sourced efforts.) related to career event outcomes. (Event information on sites like GameTora and GameWith are massive crowd-sourced efforts.)
</p> </p>
<p>Since I see this question about weekly, allow me to reiterate.</p>
<p class="text-4xl font-bold">
Support card event outcomes are not in master.mdb or any other locally stored file. Not even the list of possible skills.
</p>
<p> <p>
This document is oriented toward programmers with understanding of SQL databases. Mentions of <Mono>Gallop::</Mono> are references This document is oriented toward programmers with understanding of SQL databases. Mentions of <Mono>Gallop::</Mono> are references
to types visible in decompilations of the game, but no C# knowledge is needed. to types visible in decompilations of the game, but no C# knowledge is needed.
@@ -330,6 +433,363 @@
>, but a few categories that have useful strings are missing from there. >, but a few categories that have useful strings are missing from there.
</p> </p>
<Sec h={2} id="race">Races</Sec>
<p>First, some terminology, as the game uses it internally. (Some UI phrasing disagrees.)</p>
<p>
A <i>race</i> is an organized running competition, typically held annually. A <i>race instance</i> is a single holding of a
race at a specific date and time; this includes special race instances like Team Trials races, Aim for the Stars races,
&amp;c. Race instances are held on a given <i>race course</i>, which is a fixed-length segment of a physical location called a
<i>race track</i>. A <i>win saddle</i> is a given horse's victory of a race or a specific group of races, e.g. Classic Triple Crown
or Dual Grand Prix. This section will cover races, race instances, race tracks, race courses, and win saddles.
</p>
<p>
<Mono>text_data</Mono> categories relevant to races include:
</p>
<ul class="ml-4 list-disc pb-4">
<li>32 and 33 are both race names, the latter for jikkyo (the announcer's lines).</li>
<li>
28 is race instance names. 29 is abbreviated race instance names, e.g. <Mono>Mile Ch.</Mono> instead of <Mono
>Mile Championship</Mono
>.
</li>
<li>
31 and 34 are race track full names, the former for jikkyo, e.g. <Mono>Kyoto Racecourse</Mono>. 35 is race track names
without "Racecourse".
</li>
<li>111 is win saddle names.</li>
</ul>
<Sec h={3} id="race-race">Race Info</Sec>
<p>
Races are defined in <Mono>race</Mono>, which is quite a direct name innit.
</p>
<ul class="ml-4 list-disc pb-4">
<li>
Race ID is just <Mono>id</Mono>.
</li>
<li>
<Mono>group</Mono> is the game situation where the race occurs.
<table class="table-fixed border text-center">
<thead>
<tr>
<th scope="col" class="px-2">Group</th>
<th scope="col" class="px-2">Meaning</th>
</tr>
</thead>
<tbody>
{#each raceGroups as [id, name] (id)}
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
<td class="px-2">{id}</td>
<td class="px-2">{name}</td>
</tr>
{/each}
</tbody>
</table>
</li>
<li>
<Mono>grade</Mono> defines the race grade.
<table class="table-fixed border text-center">
<thead>
<tr>
<th scope="col" class="px-2">Grade ID</th>
<th scope="col" class="px-2">Meaning</th>
</tr>
</thead>
<tbody>
{#each raceGrades as [id, name] (id)}
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
<td class="px-2">{id}</td>
<td class="px-2">{name}</td>
</tr>
{/each}
</tbody>
</table>
</li>
<li><Mono>course_set</Mono> gives the <a href="#race-course">race course</a> where the race occurs.</li>
<li><Mono>thumbnail_id</Mono> is the resource ID of the race's thumbnail image.</li>
<li><Mono>entry_num</Mono> is the maximum number of horses that can run the race.</li>
</ul>
<p>
Some races are ostensibly defined twice to account for variant races run during some characters' careers, e.g. Maruzensky's
five opponent Spring Stakes, McQueen/Ryan/Rice's Kyoto Takarazuka Kinen, &amp;c.
</p>
<Sec h={3} id="race-instances">Race Instances</Sec>
<p>
The table is <Mono>race_instance</Mono>.
</p>
<ul class="ml-4 list-disc pb-4">
<li><Mono>id</Mono> is the race instance ID.</li>
<li><Mono>race_id</Mono> joins with <Mono>race.id</Mono>.</li>
<li>
<Mono>npc_group_id</Mono> should define the NPC racers allowed to appear in the race instance, although I haven't looked into
it.
</li>
<li>
<Mono>date</Mono> gives the month and day-of-month when the race instance occurs, expressed as M&times;100+d &ndash; 1 Jan is
101 and 31 Dec is 1231. This appears to decide the season that the race instance occurs in and thus the applicable green skill.
E.g., we can see in career that Challenge Cup is offered on the Early December turn, but its race instance has a <Mono
>date</Mono
> of 1130 (i.e. 30 Nov), so it is a fall rather than winter race. All races occur in 2019.
</li>
<li>
<Mono>time</Mono> is the time of day that the race instance is held.
<table class="table-fixed border text-center">
<thead>
<tr>
<th scope="col" class="px-2">Time</th>
<th scope="col" class="px-2">Meaning</th>
</tr>
</thead>
<tbody>
{#each raceTimes as [id, name] (id)}
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
<td class="px-2">{id}</td>
<td class="px-2">{name}</td>
</tr>
{/each}
</tbody>
</table>
</li>
<li>
<Mono>clock_time</Mono> has a value of 1605 for one race and is otherwise always 0. It might be a specific hour and minute that
a race instance occurs, but I'm not sure why that would ever matter.
</li>
<li>
<Mono>race_number</Mono> takes every value between -1 and 12, but I couldn't find any explanation for it.
</li>
</ul>
<p>
Similarly to races, some race instances are ostensibly defined twice to account for variant races run during some characters'
careers.
</p>
<Sec h={3} id="race-track">Race Tracks</Sec>
<p>
Since race tracks are the physical locations where races are held, there are not many rows in <Mono>race_track</Mono>. Most of
its purpose is to define appearance characteristics, but some fields are mechanically relevant.
</p>
<ul class="ml-4 list-disc pb-4">
<li><Mono>id</Mono> is the race track ID.</li>
<li>
<Mono>initial_lane_type</Mono> describes the starting lane positions of each horse by determining the location and width of the
gap between posts.
<table class="table-fixed border text-center">
<thead>
<tr>
<th scope="col" class="px-2">ID</th>
<th scope="col" class="px-2">Gap After Gate</th>
<th scope="col" class="px-2">Gap Width (CW)</th>
</tr>
</thead>
<tbody>
{#each raceInitialLanes as [id, gate, w] (id)}
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
<td>{id}</td>
<td>{gate}</td>
<td>{w}</td>
</tr>
{/each}
</tbody>
</table>
Horses in gates
<i>after</i> the Gap After Gate listed in this table have the gap width added to their starting lane.
<Mono>initial_lane_type</Mono> is 2 on Oi, Kawasaki, and Funabashi; 3 on Longchamp; 4 on Morioka; and 1 elsewhere. (The race mechanics
doc's
<a
href="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.ohdxzwro17n5"
target="_blank"
rel="noopener noreferrer">description of initial lane</a
>
uses horse index rather than gate number and describes lanes in units of horse lanes rather than course widths.)
</li>
<li>
<Mono>area</Mono> defines the geographic region where the race track exists.
<table class="table-fixed border text-center">
<thead>
<tr>
<th scope="col" class="px-2">ID</th>
<th scope="col" class="px-2">Region</th>
</tr>
</thead>
<tbody>
{#each raceAreas as [id, name] (id)}
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
<td class="px-2">{id}</td>
<td class="px-2">{name}</td>
</tr>
{/each}
</tbody>
</table>
</li>
</ul>
<Sec h={3} id="race-course">Race Courses</Sec>
<p>
Race courses are physical segments of race tracks. They are defined in <Mono>race_course_set</Mono>.
</p>
<p>
Most tracks have one or two turf loops (the "(Inner)" and "(Outer)" or sometimes "(Outer&rarr;Inner)") and one dirt loop. The
length of a race course is determined solely by the position of the gates: since the race course is a physical structure with
the audience stands in a specific location, and since said audience wants to see the race finish clearly, courses of all
lengths end at the same place on the front stretch. The straight opposite the front stretch is called the backstretch. Corners
are counted backward from the front stretch in the order 4, 3, 2, 1; all races except Ibis Summer Dash include corners 3 and
4, while long and sometimes medium races run corners multiple times. Some courses start on an extra segment of the track which
is <i>neither straight nor corner</i> (which means <Mono>straight_random==1</Mono> skills won't fire there).
</p>
<ul class="ml-4 list-disc pb-4">
<li><Mono>id</Mono> gives the race course ID.</li>
<li><Mono>race_track_id</Mono> joins with <Mono>race_track.id</Mono>.</li>
<li><Mono>distance</Mono> is the race course length in meters.</li>
<li><Mono>ground</Mono> is 1 for turf and 2 for dirt.</li>
<li>
<Mono>inout</Mono> gives the loop for the surface type where the race occurs.
<table class="table-fixed border text-center">
<thead>
<tr>
<th scope="col" class="px-2">ID</th>
<th scope="col" class="px-2">Loop</th>
</tr>
</thead>
<tbody>
{#each raceLoops as [id, name] (id)}
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
<td class="px-2">{id}</td>
<td class="px-2">{name}</td>
</tr>
{/each}
</tbody>
</table>
</li>
<li>
<Mono>turn</Mono> gives the handedness of the track, i.e. the direction from horses to the rail as they round the track's corners.
<table class="table-fixed border text-center">
<thead>
<tr>
<th scope="col" class="px-2">ID</th>
<th scope="col" class="px-2">Handedness</th>
</tr>
</thead>
<tbody>
{#each raceRotations as [id, name] (id)}
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
<td class="px-2">{id}</td>
<td class="px-2">{name}</td>
</tr>
{/each}
</tbody>
</table>
Stretch types have no corners and do not activate <Skill skill={skills.right1} /> or <Skill skill={skills.left1} /> or their variants.
Niigata 1000 is the only such race course in the game.
</li>
<li>
<Mono>float_lane_max</Mono> gives the <i>initial</i> race course width in units of ten thousandths of course widths. (Confusingly,
no race course has a width of 1 course width.) Some race courses have locations where the lane max changes, but these are defined
as events and are not in the mdb.
</li>
<li>
<Mono>course_set_status_id</Mono> joins with <Mono>race_course_set_status.course_set_status_id</Mono> to define the stat thresholds.
A value of 0 means no thresholds.
<table class="table-fixed border text-center">
<caption><Mono>race_course_set_status</Mono></caption>
<thead>
<tr>
<th scope="col" class="px-2"><Mono>course_set_status_id</Mono></th>
<th scope="col" class="px-2"><Mono>target_status_1</Mono></th>
<th scope="col" class="px-2"><Mono>target_status_2</Mono></th>
</tr>
</thead>
<tbody>
{#each raceCourseThresholds as [id, stat1, stat2] (id)}
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
<td class="px-2">{id}</td>
<td class="px-2">{stat1}</td>
<td class="px-2">{stat2}</td>
</tr>
{/each}
</tbody>
</table>
Status of 1 is Speed, 2 is Power, and so on up to 5 for Wit. 0 means no (second) threshold.
</li>
</ul>
<p>
Characteristics like straight and corner locations, slopes, lane max changes, &amp;c. are <i>not</i> in the mdb. They are defined
as assets instead and must be extracted using special tools.
</p>
<Sec h={3} id="race-saddle">Win Saddles</Sec>
<p>
<Mono>single_mode_wins_saddle</Mono> defines the awards given to the horse (not the player) for winning a race or specific group
of races. These are the medals displayed as important wins for a horse at the end of a career. They are now purely aesthetic, but
I am documenting them anyway because I have the notes already since they used to determine affinity.
</p>
<ul class="ml-4 list-disc pb-4">
<li><Mono>id</Mono> is the win saddle ID.</li>
<li><Mono>priority</Mono> is the sort order for win saddles.</li>
<li>
<Mono>group_id</Mono> associates multiple win saddles for the same race, for horses whose careers have special versions of some
races.
</li>
<li>
<Mono>win_saddle_type</Mono> is the type of the win saddle, used to determine the color of the medal displayed.
<table class="table-fixed border text-center">
<thead>
<tr>
<th scope="col" class="px-2">ID</th>
<th scope="col" class="px-2">Type</th>
</tr>
</thead>
<tbody>
{#each raceSaddleTypes as [id, name] (id)}
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
<td class="px-2">{id}</td>
<td class="px-2">{name}</td>
</tr>
{/each}
</tbody>
</table>
Special win saddles are for win saddles requiring multiple wins, e.g. Classic Triple Crown.
</li>
<li>
<Mono>race_instance_id_1</Mono> through <Mono>race_instance_id_8</Mono> define the race instance IDs that must be won to be awarded
the win saddle. Columns beyond <Mono>race_instance_id_3</Mono> are unused.
</li>
</ul>
<Sec h={3} id="race-program">Single Mode Program</Sec>
<p>
The table <Mono>single_mode_program</Mono> defines the race instances offered each turn in a career. Confusingly, races that permit
both classic and senior year horses are only defined here once.
</p>
<ul class="ml-4 list-disc pb-4">
<li><Mono>race_instance_id</Mono> joins with <Mono>race_instance.id</Mono>.</li>
<li>
<Mono>race_permission</Mono> gives the career years that offer the race. (More precisely, it gives the age ranges of horses that
are permitted to enter.)
<table class="table-fixed border text-center">
<thead>
<tr>
<th scope="col" class="px-2">ID</th>
<th scope="col" class="px-2">Meaning</th>
</tr>
</thead>
<tbody>
{#each racePermissions as [id, name] (id)}
<tr class="even:bg-mist-300 dark:even:bg-mist-900">
<td class="px-2">{id}</td>
<td class="px-2">{name}</td>
</tr>
{/each}
</tbody>
</table>
</li>
<li><Mono>month</Mono> and <Mono>half</Mono> give the turn that the race is held.</li>
</ul>
<p>
I was unable to identify whether any of the columns indicate maiden races, despite the UI restrictions on when they can
appear.
</p>
<Sec h={2} id="skills">Skills</Sec> <Sec h={2} id="skills">Skills</Sec>
<p> <p>
Relevant <Mono>text_data</Mono> categories include 47 for skill names and 48 for skill descriptions. Otherwise, info for skills Relevant <Mono>text_data</Mono> categories include 47 for skill names and 48 for skill descriptions. Otherwise, info for skills
@@ -816,6 +1276,9 @@
Relevant <Mono>text_data</Mono> categories include 75 for name including variant, 76 for variant alone, 77 for character name. Category Relevant <Mono>text_data</Mono> categories include 75 for name including variant, 76 for variant alone, 77 for character name. Category
151 gives support card effect names, 154 gives effect descriptions, 150 gives unique effect names, and 155 gives unique effect descriptions. 151 gives support card effect names, 154 gives effect descriptions, 150 gives unique effect names, and 155 gives unique effect descriptions.
</p> </p>
<p class="text-4xl font-bold">
Support card event outcomes are not in master.mdb or any other locally stored file. Not even the list of possible skills.
</p>
<Sec h={3} id="support_card_data">Support Card Data</Sec> <Sec h={3} id="support_card_data">Support Card Data</Sec>
<p> <p>