horse, mdb: put skill groups in skills
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful

Fixes #13.
This commit is contained in:
2026-09-15 11:00:10 -04:00
parent 78aee43e5f
commit 6828b1bd88
4 changed files with 355 additions and 172 deletions
+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,
+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"`