package mdb import ( "context" _ "embed" "fmt" "strconv" "strings" "zombiezen.com/go/sqlite" "zombiezen.com/go/sqlite/sqlitex" "git.sunturtle.xyz/zephyr/horse" ) var ( //go:embed sql/skill-group.sql skillGroupSQL string //go:embed sql/skill.sql skillSQL string //go:embed sql/skill-group-skills.sql skillGroupSkillsSQL string ) // SkillGroups retrieves all skill groups. func SkillGroups(ctx context.Context, db *sqlitex.Pool) ([]horse.SkillGroup, error) { return load(ctx, db, nil, skillGroupSQL, func(s *sqlite.Stmt) horse.SkillGroup { return horse.SkillGroup{ ID: horse.SkillGroupID(s.ColumnInt(0)), Skill1: horse.SkillID(s.ColumnInt(1)), Skill2: horse.SkillID(s.ColumnInt(2)), Skill3: horse.SkillID(s.ColumnInt(3)), SkillBad: horse.SkillID(s.ColumnInt(4)), } }) } // Skills retrieves all skills. func Skills(ctx context.Context, db *sqlitex.Pool) ([]horse.Skill, error) { // We don't use func load here because we have multiple queries to run. conn, err := db.Take(ctx) defer db.Put(conn) if err != nil { return nil, err } var sk []horse.Skill { stmt := conn.Prep(skillSQL) 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{ { Precondition: stmt.ColumnText(9), Condition: stmt.ColumnText(10), Duration: horse.TenThousandths(stmt.ColumnInt(11)), DurScale: horse.DurScale(stmt.ColumnInt(12)), Cooldown: horse.TenThousandths(stmt.ColumnInt(13)), Abilities: trimAbilities([]horse.Ability{ { 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), }, { Type: horse.AbilityType(stmt.ColumnInt(19)), ValueUsage: horse.AbilityValueUsage(stmt.ColumnInt(20)), Value: horse.TenThousandths(stmt.ColumnInt(21)), Target: horse.AbilityTarget(stmt.ColumnInt(22)), TargetValue: stmt.ColumnInt32(23), }, { Type: horse.AbilityType(stmt.ColumnInt(24)), ValueUsage: horse.AbilityValueUsage(stmt.ColumnInt(25)), Value: horse.TenThousandths(stmt.ColumnInt(26)), Target: horse.AbilityTarget(stmt.ColumnInt(27)), TargetValue: stmt.ColumnInt32(28), }, }), }, { Precondition: stmt.ColumnText(29), Condition: stmt.ColumnText(30), Duration: horse.TenThousandths(stmt.ColumnInt(31)), DurScale: horse.DurScale(stmt.ColumnInt(32)), Cooldown: horse.TenThousandths(stmt.ColumnInt(33)), Abilities: trimAbilities([]horse.Ability{ { 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), }, { Type: horse.AbilityType(stmt.ColumnInt(39)), 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 { r := make([]uint16, 0, 8) for s != "" { t, u, _ := strings.Cut(s, "/") s = u v, err := strconv.ParseUint(t, 10, 16) if err != nil { panic(fmt.Errorf("parsing skill tags: %w", err)) } r = append(r, uint16(v)) } return trimZeros(r...) } func trimAbilities(s []horse.Ability) []horse.Ability { for len(s) > 0 && s[len(s)-1].Type == 0 { s = s[:len(s)-1] } return s } func trimActivations(s []horse.Activation) []horse.Activation { for len(s) > 0 && s[len(s)-1].Condition == "" { s = s[:len(s)-1] } return s } func trimZeros[T comparable](s ...T) []T { var zero T for len(s) > 0 && s[len(s)-1] == zero { s = s[:len(s)-1] } return s }