3 Commits
Author SHA1 Message Date
zephyr 1cf3af5f79 zenno: update full charge info with new knowledge
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
2026-09-21 09:48:56 -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
11 changed files with 398 additions and 195 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,
+101 -55
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()
for {
ok, err := stmt.Step()
if err != nil {
return nil, err
}
if !ok {
break
}
s := horse.Skill{
ID: horse.SkillID(stmt.ColumnInt(0)),
Name: stmt.ColumnText(1),
Description: stmt.ColumnText(2),
GroupSkills: make([]horse.GroupSkill, 0, 3),
Group: horse.SkillGroupID(stmt.ColumnInt32(3)),
Rarity: int8(stmt.ColumnInt(5)),
GroupRate: int8(stmt.ColumnInt(6)),
GradeValue: stmt.ColumnInt32(7),
WitCheck: stmt.ColumnBool(8),
Activations: trimActivations([]horse.Activation{ Activations: trimActivations([]horse.Activation{
{ {
Precondition: s.ColumnText(9), Precondition: stmt.ColumnText(9),
Condition: s.ColumnText(10), Condition: stmt.ColumnText(10),
Duration: horse.TenThousandths(s.ColumnInt(11)), Duration: horse.TenThousandths(stmt.ColumnInt(11)),
DurScale: horse.DurScale(s.ColumnInt(12)), DurScale: horse.DurScale(stmt.ColumnInt(12)),
Cooldown: horse.TenThousandths(s.ColumnInt(13)), Cooldown: horse.TenThousandths(stmt.ColumnInt(13)),
Abilities: trimAbilities([]horse.Ability{ Abilities: trimAbilities([]horse.Ability{
{ {
Type: horse.AbilityType(s.ColumnInt(14)), Type: horse.AbilityType(stmt.ColumnInt(14)),
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(15)), ValueUsage: horse.AbilityValueUsage(stmt.ColumnInt(15)),
Value: horse.TenThousandths(s.ColumnInt(16)), Value: horse.TenThousandths(stmt.ColumnInt(16)),
Target: horse.AbilityTarget(s.ColumnInt(17)), Target: horse.AbilityTarget(stmt.ColumnInt(17)),
TargetValue: s.ColumnInt32(18), TargetValue: stmt.ColumnInt32(18),
}, },
{ {
Type: horse.AbilityType(s.ColumnInt(19)), Type: horse.AbilityType(stmt.ColumnInt(19)),
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(20)), ValueUsage: horse.AbilityValueUsage(stmt.ColumnInt(20)),
Value: horse.TenThousandths(s.ColumnInt(21)), Value: horse.TenThousandths(stmt.ColumnInt(21)),
Target: horse.AbilityTarget(s.ColumnInt(22)), Target: horse.AbilityTarget(stmt.ColumnInt(22)),
TargetValue: s.ColumnInt32(23), TargetValue: stmt.ColumnInt32(23),
}, },
{ {
Type: horse.AbilityType(s.ColumnInt(24)), Type: horse.AbilityType(stmt.ColumnInt(24)),
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(25)), ValueUsage: horse.AbilityValueUsage(stmt.ColumnInt(25)),
Value: horse.TenThousandths(s.ColumnInt(26)), Value: horse.TenThousandths(stmt.ColumnInt(26)),
Target: horse.AbilityTarget(s.ColumnInt(27)), Target: horse.AbilityTarget(stmt.ColumnInt(27)),
TargetValue: s.ColumnInt32(28), TargetValue: stmt.ColumnInt32(28),
}, },
}), }),
}, },
{ {
Precondition: s.ColumnText(29), Precondition: stmt.ColumnText(29),
Condition: s.ColumnText(30), Condition: stmt.ColumnText(30),
Duration: horse.TenThousandths(s.ColumnInt(31)), Duration: horse.TenThousandths(stmt.ColumnInt(31)),
DurScale: horse.DurScale(s.ColumnInt(32)), DurScale: horse.DurScale(stmt.ColumnInt(32)),
Cooldown: horse.TenThousandths(s.ColumnInt(33)), Cooldown: horse.TenThousandths(stmt.ColumnInt(33)),
Abilities: trimAbilities([]horse.Ability{ Abilities: trimAbilities([]horse.Ability{
{ {
Type: horse.AbilityType(s.ColumnInt(34)), Type: horse.AbilityType(stmt.ColumnInt(34)),
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(35)), ValueUsage: horse.AbilityValueUsage(stmt.ColumnInt(35)),
Value: horse.TenThousandths(s.ColumnInt(36)), Value: horse.TenThousandths(stmt.ColumnInt(36)),
Target: horse.AbilityTarget(s.ColumnInt(37)), Target: horse.AbilityTarget(stmt.ColumnInt(37)),
TargetValue: s.ColumnInt32(38), TargetValue: stmt.ColumnInt32(38),
}, },
{ {
Type: horse.AbilityType(s.ColumnInt(39)), Type: horse.AbilityType(stmt.ColumnInt(39)),
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(40)), ValueUsage: horse.AbilityValueUsage(stmt.ColumnInt(40)),
Value: horse.TenThousandths(s.ColumnInt(41)), Value: horse.TenThousandths(stmt.ColumnInt(41)),
Target: horse.AbilityTarget(s.ColumnInt(42)), Target: horse.AbilityTarget(stmt.ColumnInt(42)),
TargetValue: s.ColumnInt32(43), TargetValue: stmt.ColumnInt32(43),
}, },
{ {
Type: horse.AbilityType(s.ColumnInt(44)), Type: horse.AbilityType(stmt.ColumnInt(44)),
ValueUsage: horse.AbilityValueUsage(s.ColumnInt(45)), ValueUsage: horse.AbilityValueUsage(stmt.ColumnInt(45)),
Value: horse.TenThousandths(s.ColumnInt(46)), Value: horse.TenThousandths(stmt.ColumnInt(46)),
Target: horse.AbilityTarget(s.ColumnInt(47)), Target: horse.AbilityTarget(stmt.ColumnInt(47)),
TargetValue: s.ColumnInt32(48), TargetValue: stmt.ColumnInt32(48),
}, },
}), }),
}, },
}), }),
UniqueOwner: s.ColumnText(52), // TODO(zeph): should be id, not name UniqueOwner: stmt.ColumnText(52), // TODO(zeph): should be id, not name
Tags: parseTags(s.ColumnText(54)), Tags: parseTags(stmt.ColumnText(54)),
SPCost: s.ColumnInt(49), SPCost: stmt.ColumnInt(49),
IconID: s.ColumnInt(53), 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 {
+87
View File
@@ -49,6 +49,11 @@ 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.",
GroupSkills: []horse.GroupSkill{
{ID: 100351, GroupRate: 1, Rarity: 4},
{ID: 10351, GroupRate: 1, Rarity: 3},
{ID: 900351, GroupRate: 2, Rarity: 1},
},
Group: 1035, Group: 1035,
Rarity: 3, Rarity: 3,
GroupRate: 1, GroupRate: 1,
@@ -81,6 +86,10 @@ 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.",
GroupSkills: []horse.GroupSkill{
{ID: 100011, GroupRate: 1, Rarity: 5},
{ID: 900011, GroupRate: 2, Rarity: 1},
},
Group: 10001, Group: 10001,
Rarity: 5, Rarity: 5,
GroupRate: 1, GroupRate: 1,
@@ -120,6 +129,11 @@ 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.",
GroupSkills: []horse.GroupSkill{
{ID: 100351, GroupRate: 1, Rarity: 4},
{ID: 10351, GroupRate: 1, Rarity: 3},
{ID: 900351, GroupRate: 2, Rarity: 1},
},
Group: 10035, Group: 10035,
Rarity: 4, Rarity: 4,
GroupRate: 1, GroupRate: 1,
@@ -152,6 +166,10 @@ 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.",
GroupSkills: []horse.GroupSkill{
{ID: 110241, GroupRate: 1, Rarity: 5},
{ID: 910241, GroupRate: 2, Rarity: 1},
},
Group: 11024, Group: 11024,
Rarity: 5, Rarity: 5,
GroupRate: 1, GroupRate: 1,
@@ -200,6 +218,12 @@ 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.",
GroupSkills: []horse.GroupSkill{
{ID: 200013, GroupRate: -1, Rarity: 1},
{ID: 200012, GroupRate: 1, Rarity: 1},
{ID: 200011, GroupRate: 2, Rarity: 1},
{ID: 200014, GroupRate: 3, Rarity: 2},
},
Group: 20001, Group: 20001,
Rarity: 1, Rarity: 1,
GroupRate: 2, GroupRate: 2,
@@ -231,6 +255,12 @@ 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.",
GroupSkills: []horse.GroupSkill{
{ID: 200013, GroupRate: -1, Rarity: 1},
{ID: 200012, GroupRate: 1, Rarity: 1},
{ID: 200011, GroupRate: 2, Rarity: 1},
{ID: 200014, GroupRate: 3, Rarity: 2},
},
Group: 20001, Group: 20001,
Rarity: 1, Rarity: 1,
GroupRate: 1, GroupRate: 1,
@@ -262,6 +292,12 @@ 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.",
GroupSkills: []horse.GroupSkill{
{ID: 200013, GroupRate: -1, Rarity: 1},
{ID: 200012, GroupRate: 1, Rarity: 1},
{ID: 200011, GroupRate: 2, Rarity: 1},
{ID: 200014, GroupRate: 3, Rarity: 2},
},
Group: 20001, Group: 20001,
Rarity: 1, Rarity: 1,
GroupRate: -1, GroupRate: -1,
@@ -293,6 +329,12 @@ 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.",
GroupSkills: []horse.GroupSkill{
{ID: 200013, GroupRate: -1, Rarity: 1},
{ID: 200012, GroupRate: 1, Rarity: 1},
{ID: 200011, GroupRate: 2, Rarity: 1},
{ID: 200014, GroupRate: 3, Rarity: 2},
},
Group: 20001, Group: 20001,
Rarity: 2, Rarity: 2,
GroupRate: 3, GroupRate: 3,
@@ -331,6 +373,11 @@ 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.",
GroupSkills: []horse.GroupSkill{
{ID: 200023, GroupRate: -1, Rarity: 1},
{ID: 200022, GroupRate: 1, Rarity: 1},
{ID: 200021, GroupRate: 2, Rarity: 1},
},
Group: 20002, Group: 20002,
Rarity: 1, Rarity: 1,
GroupRate: 2, GroupRate: 2,
@@ -362,6 +409,11 @@ 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.",
GroupSkills: []horse.GroupSkill{
{ID: 200023, GroupRate: -1, Rarity: 1},
{ID: 200022, GroupRate: 1, Rarity: 1},
{ID: 200021, GroupRate: 2, Rarity: 1},
},
Group: 20002, Group: 20002,
Rarity: 1, Rarity: 1,
GroupRate: 1, GroupRate: 1,
@@ -393,6 +445,11 @@ 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.",
GroupSkills: []horse.GroupSkill{
{ID: 200023, GroupRate: -1, Rarity: 1},
{ID: 200022, GroupRate: 1, Rarity: 1},
{ID: 200021, GroupRate: 2, Rarity: 1},
},
Group: 20002, Group: 20002,
Rarity: 1, Rarity: 1,
GroupRate: -1, GroupRate: -1,
@@ -424,6 +481,10 @@ 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.",
GroupSkills: []horse.GroupSkill{
{ID: 200362, GroupRate: 1, Rarity: 1},
{ID: 200361, GroupRate: 2, Rarity: 2},
},
Group: 20036, Group: 20036,
Rarity: 2, Rarity: 2,
GroupRate: 2, GroupRate: 2,
@@ -455,6 +516,10 @@ 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.",
GroupSkills: []horse.GroupSkill{
{ID: 200362, GroupRate: 1, Rarity: 1},
{ID: 200361, GroupRate: 2, Rarity: 2},
},
Group: 20036, Group: 20036,
Rarity: 1, Rarity: 1,
GroupRate: 1, GroupRate: 1,
@@ -486,6 +551,9 @@ 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.",
GroupSkills: []horse.GroupSkill{
{ID: 200831, GroupRate: 1, Rarity: 1},
},
Group: 20083, Group: 20083,
Rarity: 1, Rarity: 1,
GroupRate: 1, GroupRate: 1,
@@ -517,6 +585,9 @@ 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.",
GroupSkills: []horse.GroupSkill{
{ID: 201801, GroupRate: -1, Rarity: 1},
},
Group: 20180, Group: 20180,
Rarity: 1, Rarity: 1,
GroupRate: -1, GroupRate: -1,
@@ -548,6 +619,9 @@ 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.",
GroupSkills: []horse.GroupSkill{
{ID: 300011, GroupRate: 1, Rarity: 1},
},
Group: 30001, Group: 30001,
Rarity: 1, Rarity: 1,
GroupRate: 1, GroupRate: 1,
@@ -579,6 +653,10 @@ 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.",
GroupSkills: []horse.GroupSkill{
{ID: 100011, GroupRate: 1, Rarity: 5},
{ID: 900011, GroupRate: 2, Rarity: 1},
},
Group: 10001, Group: 10001,
Rarity: 1, Rarity: 1,
GroupRate: 2, GroupRate: 2,
@@ -618,6 +696,11 @@ 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.",
GroupSkills: []horse.GroupSkill{
{ID: 100351, GroupRate: 1, Rarity: 4},
{ID: 10351, GroupRate: 1, Rarity: 3},
{ID: 900351, GroupRate: 2, Rarity: 1},
},
Group: 10035, Group: 10035,
Rarity: 1, Rarity: 1,
GroupRate: 2, GroupRate: 2,
@@ -650,6 +733,10 @@ 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.",
GroupSkills: []horse.GroupSkill{
{ID: 110241, GroupRate: 1, Rarity: 5},
{ID: 910241, GroupRate: 2, Rarity: 1},
},
Group: 11024, Group: 11024,
Rarity: 1, Rarity: 1,
GroupRate: 2, GroupRate: 2,
+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"`
+3 -2
View File
@@ -771,16 +771,17 @@ export function conservePowerAccel(
distanceType: Distance, distanceType: Distance,
style: RunningStyle, style: RunningStyle,
rawPower: number, rawPower: number,
basePower: number,
powerSkillBonus?: number, powerSkillBonus?: number,
spotStruggled?: boolean, spotStruggled?: boolean,
rushed?: boolean, rushed?: boolean,
): number { ): number {
const power = rawPower + (powerSkillBonus ?? 0); const power = basePower + 0.5 * Math.max(rawPower - 1200, 0) + (powerSkillBonus ?? 0);
if (power <= 1200) { if (power <= 1200) {
return 0; return 0;
} }
const sdc = conserveStratDistCoef[style][distanceType]; const sdc = conserveStratDistCoef[style][distanceType];
const activity = rushed ? 0.8 : spotStruggled ? 0.98 : 1; const activity = (rushed ? 0.8 : 1) * (spotStruggled ? 0.98 : 1);
return Math.sqrt((power - 1200) * 130) * 0.001 * sdc * activity; return Math.sqrt((power - 1200) * 130) * 0.001 * sdc * activity;
} }
+6 -7
View File
@@ -224,10 +224,10 @@
}, },
]); ]);
const fcSeries: ComputedSeries[] = $derived([ const fcSeries: ComputedSeries[] = $derived([
{ label: 'Front Runner', y: (x) => conservePowerAccel(distance(raceLen), RunningStyle.FrontRunner, x) }, { label: 'Front Runner', y: (x, raw) => conservePowerAccel(distance(raceLen), RunningStyle.FrontRunner, raw, x) },
{ label: 'Pace Chaser', y: (x) => conservePowerAccel(distance(raceLen), RunningStyle.PaceChaser, x) }, { label: 'Pace Chaser', y: (x, raw) => conservePowerAccel(distance(raceLen), RunningStyle.PaceChaser, raw, x) },
{ label: 'Late Surger', y: (x) => conservePowerAccel(distance(raceLen), RunningStyle.LateSurger, x) }, { label: 'Late Surger', y: (x, raw) => conservePowerAccel(distance(raceLen), RunningStyle.LateSurger, raw, x) },
{ label: 'End Closer', y: (x) => conservePowerAccel(distance(raceLen), RunningStyle.EndCloser, x) }, { label: 'End Closer', y: (x, raw) => conservePowerAccel(distance(raceLen), RunningStyle.EndCloser, raw, x) },
]); ]);
</script> </script>
@@ -528,9 +528,8 @@
</div> </div>
</div> </div>
<p> <p>
The exact mechanics of conserving power are unclear at this time. What has been confirmed is that rushed and <a Rushed and <a href="#spot-struggle">spot struggle</a> reduce both the duration and effectiveness of full charge, potentially to
href="#spot-struggle">spot struggle</a zero. In practice, this appears to be a concern primarily on sprints, which already have a -55% penalty to full charge duration.
> both reduce the chance for it to fire.
</p> </p>
<Sec h={3} id="lane-combo">Lane Combo</Sec> <Sec h={3} id="lane-combo">Lane Combo</Sec>
+12 -7
View File
@@ -58,10 +58,10 @@
]); ]);
const laneChange: ComputedSeries[] = [{ label: 'Lane Change Target Speed', y: (x) => race.laneChangeSpeed(x) }]; const laneChange: ComputedSeries[] = [{ label: 'Lane Change Target Speed', y: (x) => race.laneChangeSpeed(x) }];
const conservePower: Array<ComputedSeries | null> = $derived([ const conservePower: Array<ComputedSeries | null> = $derived([
{ label: 'Conserved Power', y: (_raw, x) => race.conservePowerAccel(distanceType, style, x, 0, false, false) }, { label: 'Conserved Power', y: (x, raw) => race.conservePowerAccel(distanceType, style, raw, x, 0, false, false) },
{ label: 'Rushed', y: (_raw, x) => race.conservePowerAccel(distanceType, style, x, 0, false, true) }, { label: 'Rushed', y: (x, raw) => race.conservePowerAccel(distanceType, style, raw, x, 0, false, true) },
styleIsFront styleIsFront
? { label: 'Spot Struggled', y: (_raw, x) => race.conservePowerAccel(distanceType, style, x, 0, true, false) } ? { label: 'Spot Struggled', y: (x, raw) => race.conservePowerAccel(distanceType, style, raw, x, 0, true, false) }
: null, : null,
]); ]);
const gutsSpurt: Array<ComputedSeries | null> = $derived([ const gutsSpurt: Array<ComputedSeries | null> = $derived([
@@ -329,11 +329,16 @@
Bonus acceleration at spurt start from the Charge Up/Fully Charged mechanic. Scales with base power plus passive skills, so Bonus acceleration at spurt start from the Charge Up/Fully Charged mechanic. Scales with base power plus passive skills, so
modifiers for ground conditions and career mode do not apply. modifiers for ground conditions and career mode do not apply.
</p> </p>
<p>
This mechanic does not appear to be well understood, in particular how long the acceleration bonus lasts and how the rushed
and spot struggle multipliers apply.
</p>
{@render statChart(race.Stat.Power, conservePower, 'Acceleration (m/s²)', [0, 0.35], { len: true, style: true })} {@render statChart(race.Stat.Power, conservePower, 'Acceleration (m/s²)', [0, 0.35], { len: true, style: true })}
<p>
The duration of Fully Charged does not depend directly on stats, but rather on a hidden <i>conserved power</i> value which
increases per horse throughout the race. Conserved power does not increase while <a href="#spot-struggle">spot struggling</a>,
so for front runners, the duration may scale negatively with guts. It also does not increase while running in any
<a href="#poskeep">position keep mode</a>
other than pace-down, so its duration may have a very slight negative correlation with wit.
<a href="#rushed">Rushed</a> additionally prevents accumulating conserved power, so full charge duration may scale positively with
wit amortized across races. Otherwise, power conservation rate depends on running style.
</p>
<Sec h={2} id="guts">Guts</Sec> <Sec h={2} id="guts">Guts</Sec>