horse, horsegen: redesign approach for koka

This commit is contained in:
2026-01-27 08:34:11 -05:00
parent 0126101b1b
commit e890108591
11 changed files with 14312 additions and 4449 deletions

49
horse/game-id.kk Normal file
View File

@@ -0,0 +1,49 @@
module horse/game-id
// Game ID for characters, cards, skills, races, &c.
// Values for different categories may overlap.
alias game-id = int
// Specific game ID types.
// I've already made mistakes with ID categories and I haven't even committed this file yet.
// Game ID for characters.
// Generally numbers in the range 1000-9999.
pub struct character-id
game-id: game-id
// Game ID for trainees, i.e. costume instances of characters.
// Generally a character ID with two digits appended.
pub struct trainee-id
game-id: game-id
// Game ID for skills.
pub struct skill-id
game-id: game-id
// Game ID for skill groups.
pub struct skill-group-id
game-id: game-id
// Game ID for skill icons.
pub struct skill-icon-id
game-id: game-id
// order2 comparison between any game ID types.
pub inline fun order2(x: a, y: a, ?a/game-id: (a) -> game-id): order2<a>
match x.game-id.cmp(y.game-id)
Lt -> Lt2(x, y)
Eq -> Eq2(x)
Gt -> Gt2(x, y)
// Comparison between any game ID types.
pub inline fun cmp(x: a, y: a, ?a/game-id: (a) -> game-id): order
x.game-id.cmp(y.game-id)
// Equality between any game ID types.
pub inline fun (==)(x: a, y: a, ?a/game-id: (a) -> game-id): bool
x.game-id == y.game-id
// Check whether a game ID is valid, i.e. nonzero.
pub inline fun is-valid(x: a, ?a/game-id: (a) -> game-id): bool
x.game-id != 0

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

104
horse/movement.kk Normal file
View File

@@ -0,0 +1,104 @@
module horse/movement
// Running styles.
pub type style
Front-Runner
Pace-Chaser
Late-Surger
End-Closer
// Automatically generated.
// Equality comparison of the `style` type.
pub fun style/(==)(this : style, other : style) : e bool
match (this, other)
(Front-Runner, Front-Runner) -> True
(Pace-Chaser, Pace-Chaser) -> True
(Late-Surger, Late-Surger) -> True
(End-Closer, End-Closer) -> True
(_, _) -> False
// Shows a string representation of the `style` type.
pub fun style/show(this : style) : e string
match this
Front-Runner -> "Front Runner"
Pace-Chaser -> "Pace Chaser"
Late-Surger -> "Late Surger"
End-Closer -> "End Closer"
// Starting aptitude levels.
pub type level
G
F
E
D
C
B
A
S
// Automatically generated.
// Comparison of the `level` type.
pub fun level/cmp(this : level, other : level) : e order
match (this, other)
(G, G) -> Eq
(G, _) -> Lt
(_, G) -> Gt
(F, F) -> Eq
(F, _) -> Lt
(_, F) -> Gt
(E, E) -> Eq
(E, _) -> Lt
(_, E) -> Gt
(D, D) -> Eq
(D, _) -> Lt
(_, D) -> Gt
(C, C) -> Eq
(C, _) -> Lt
(_, C) -> Gt
(B, B) -> Eq
(B, _) -> Lt
(_, B) -> Gt
(A, A) -> Eq
(A, _) -> Lt
(_, A) -> Gt
(S, S) -> Eq
// Automatically generated.
// Fip comparison of the `level` type.
pub fun level/order2(this : level, other : level) : order2<level>
match (this, other)
(G, G) -> Eq2(G)
(G, other') -> Lt2(G, other')
(this', G) -> Gt2(G, this')
(F, F) -> Eq2(F)
(F, other') -> Lt2(F, other')
(this', F) -> Gt2(F, this')
(E, E) -> Eq2(E)
(E, other') -> Lt2(E, other')
(this', E) -> Gt2(E, this')
(D, D) -> Eq2(D)
(D, other') -> Lt2(D, other')
(this', D) -> Gt2(D, this')
(C, C) -> Eq2(C)
(C, other') -> Lt2(C, other')
(this', C) -> Gt2(C, this')
(B, B) -> Eq2(B)
(B, other') -> Lt2(B, other')
(this', B) -> Gt2(B, this')
(A, A) -> Eq2(A)
(A, other') -> Lt2(A, other')
(this', A) -> Gt2(A, this')
(S, S) -> Eq2(S)
// Automatically generated.
// Shows a string representation of the `level` type.
pub fun level/show(this : level) : string
match this
G -> "G"
F -> "F"
E -> "E"
D -> "D"
C -> "C"
B -> "B"
A -> "A"
S -> "S"

226
horse/skill.kk Normal file
View File

@@ -0,0 +1,226 @@
module horse/skill
// This module contains skill-related definitions
// common to all versions of the game.
import std/num/decimal
import horse/game-id
import horse/movement
// Full details about a skill.
pub struct skill-detail
skill-id: skill-id
name: string
description: string
group-id: skill-group-id
rarity: rarity
group-rate: int
grade-value: int
wit-check: bool
activations: list<activation>
owner: maybe<trainee-id>
sp-cost: int
icon-id: skill-icon-id
pub fun detail(
s: skill-id,
?skill/show: (skill-id) -> string,
?skill/description: (skill-id) -> string,
?skill/group: (skill-id) -> skill-group-id,
?skill/rarity: (skill-id) -> rarity,
?skill/group-rate: (skill-id) -> int,
?skill/grade-value: (skill-id) -> int,
?skill/wit-check: (skill-id) -> bool,
?skill/activations: (skill-id) -> list<activation>,
?skill/unique-owner: (skill-id) -> maybe<trainee-id>,
?skill/sp-cost: (skill-id) -> int,
?skill/icon-id: (skill-id) -> skill-icon-id
): skill-detail
Skill-detail(
s,
s.show,
s.description,
s.group,
s.rarity,
s.group-rate,
s.grade-value,
s.wit-check,
s.activations,
s.unique-owner,
s.sp-cost,
s.icon-id
)
pub fun skill-detail/show(d: skill-detail, ?character/show: (character-id) -> string, ?trainee/show: (trainee-id) -> string): string
val Skill-detail(Skill-id(id), name, desc, _, rarity, _, grade-value, wit-check, activations, owner, sp-cost, _) = d
val r = name ++ " (ID " ++ id.show ++ "): " ++ desc ++ " " ++ activations.map(activation/show).join(". ") ++ (if wit-check then ". Wit check. " else ". No wit check. ") ++ rarity.show ++ " costing " ++ sp-cost.show ++ " SP, worth " ++ grade-value.show ++ " grade value."
match owner
Nothing -> r
Just(owner-id) -> match owner-id.show
"" -> r ++ " Unique skill of trainee with ID " ++ owner-id.show ++ "."
owner-name -> r ++ " Unique skill of " ++ owner-name ++ "."
// Skill rarity levels.
pub type rarity
Common // white
Rare // gold
Unique-Low // 1*/2* unique
Unique-Upgraded // 3*+ unique on a trainee upgraded from 1*/2*
Unique // base 3* unique
pub fun rarity/show(r: rarity): string
match r
Common -> "Common"
Rare -> "Rare"
Unique-Low -> "Unique (1\u2606/2\u2606)"
Unique-Upgraded -> "Unique (3\u2606+ from 1\u2606/2\u2606 upgraded)"
Unique -> "Unique (3\u2606+)"
// Condition and precondition logic.
pub alias condition = string
// Activation conditions and effects.
// A skill has one or two activations.
pub struct activation
precondition: condition
condition: condition
duration: decimal // seconds
cooldown: decimal // seconds
abilities: list<ability> // one to three elements
pub fun activation/show(a: activation, ?character/show: (character-id) -> string): string
match a
Activation("", condition, duration, _, abilities) | !duration.is-pos -> condition ++ " -> " ++ abilities.show
Activation("", condition, duration, cooldown, abilities) | cooldown >= 500.decimal -> condition ++ " -> for " ++ duration.show ++ "s, " ++ abilities.show
Activation("", condition, duration, cooldown, abilities) -> condition ++ " -> for " ++ duration.show ++ "s on " ++ cooldown.show ++ "s cooldown, " ++ abilities.show
Activation(precondition, condition, duration, _, abilities) | !duration.is-pos -> precondition ++ " -> " ++ condition ++ " -> " ++ abilities.show
Activation(precondition, condition, duration, cooldown, abilities) | cooldown >= 500.decimal -> precondition ++ " -> " ++ condition ++ " -> for " ++ duration.show ++ "s, " ++ abilities.show
Activation(precondition, condition, duration, cooldown, abilities) -> precondition ++ " -> " ++ condition ++ " -> for " ++ duration.show ++ "s on " ++ cooldown.show ++ "s cooldown, " ++ abilities.show
// Effects of activating a skill.
pub struct ability
ability-type: ability-type
value-usage: value-usage
target: target
pub fun ability/show(a: ability, ?character/show: (character-id) -> string): string
match a
Ability(t, Direct, Self) -> t.show
Ability(t, Direct, target) -> t.show ++ " " ++ target.show
Ability(t, v, Self) -> t.show ++ " " ++ v.show
Ability(t, v, target) -> t.show ++ " " ++ target.show ++ " " ++ v.show
// Skill ability effects.
pub type ability-type
Passive-Speed(bonus: decimal)
Passive-Stamina(bonus: decimal)
Passive-Power(bonus: decimal)
Passive-Guts(bonus: decimal)
Passive-Wit(bonus: decimal)
Great-Escape
Vision(bonus: decimal)
HP(rate: decimal)
Gate-Delay(rate: decimal)
Frenzy(add: decimal)
Current-Speed(add: decimal)
Target-Speed(add: decimal)
Lane-Speed(add: decimal)
Accel(add: decimal)
Lane-Change(add: decimal)
pub fun ability-type/show(a: ability-type): string
match a
Passive-Speed(bonus) -> bonus.show ++ " Speed"
Passive-Stamina(bonus) -> bonus.show ++ " Stamina"
Passive-Power(bonus) -> bonus.show ++ " Power"
Passive-Guts(bonus) -> bonus.show ++ " Guts"
Passive-Wit(bonus) -> bonus.show ++ " Wit"
Great-Escape -> "enable Great Escape style"
Vision(bonus) -> bonus.show ++ " vision"
HP(rate) | rate.is-pos -> show(rate * 100.decimal) ++ "% HP recovery"
HP(rate) -> show(rate * 100.decimal) ++ "% HP loss"
Gate-Delay(rate) -> rate.show ++ "× gate delay"
Frenzy(add) -> add.show ++ "s longer Rushed"
Current-Speed(rate) -> rate.show ++ "m/s current speed"
Target-Speed(rate) -> rate.show ++ "m/s target speed"
Lane-Speed(rate) -> rate.show ++ "m/s lane change speed"
Accel(rate) -> rate.show ++ "m/s² acceleration"
Lane-Change(rate) -> rate.show ++ " course width movement"
// Special scaling types for skill abilities.
pub type value-usage
Direct
Team-Speed
Team-Stamina
Team-Power
Team-Guts
Team-Wit
Multiply-Random
Multiply-Random2
Climax
Max-Stat
Passive-Count
Front-Distance-Add
Midrace-Side-Block-Time
Speed-Scaling
Speed-Scaling2
Arc-Global-Potential
Max-Lead-Distance
pub fun value-usage/show(v: value-usage): string
match v
Direct -> "with no scaling"
Team-Speed -> "scaling with team Speed"
Team-Stamina -> "scaling with team Stamina"
Team-Power -> "scaling with team Power"
Team-Guts -> "scaling with team Guts"
Team-Wit -> "scaling with team Wit"
Multiply-Random -> "scaling with a random multiplier (0×, 0.02×, or 0.04×)"
Multiply-Random2 -> "scaling with a random multiplier (0×, 0.02×, or 0.04×)"
Climax -> "scaling with the number of races won during training"
Max-Stat -> "scaling with the value of the user's highest stat"
Passive-Count -> "scaling with the number of Passive skills activated"
Front-Distance-Add -> "scaling with distance from the leader"
Midrace-Side-Block-Time -> "scaling with mid-race phase blocked side time"
Speed-Scaling -> "scaling with overall speed"
Speed-Scaling2 -> "scaling with overall speed"
Arc-Global-Potential -> "scaling with L'Arc global potential"
Max-Lead-Distance -> "scaling with the distance of the longest lead obtained in the first two thirds of the race"
// Who a skill ability targets.
pub type target
Self
Sympathizers
In-View
Frontmost(limit: int)
Ahead(limit: int)
Behind(limit: int)
All-Teammates
Style(style: style)
Rushing-Ahead(limit: int)
Rushing-Behind(limit: int)
Rushing-Style(style: style)
Specific-Character(who: character-id)
Triggering
pub fun target/show(t: target, ?character/show: (character-id) -> string): string
match t
Self -> "self"
Sympathizers -> "others with Sympathy"
In-View -> "others in field of view"
Frontmost(limit) -> "frontmost " ++ limit.show
Ahead(limit) | limit >= 18 -> "others ahead"
Ahead(limit) -> "next " ++ limit.show ++ " others ahead"
Behind(limit) | limit >= 18 -> "others behind"
Behind(limit) -> "next " ++ limit.show ++ " others behind"
All-Teammates -> "all teammates"
Style(s) -> "other " ++ s.show ++ "s"
Rushing-Ahead(limit) | limit >= 18 -> "others rushing ahead"
Rushing-Ahead(limit) -> "next " ++ limit.show ++ " others rushing ahead"
Rushing-Behind(limit) | limit >= 18 -> "others rushing behind"
Rushing-Behind(limit) -> "next " ++ limit.show ++ " others rushing behind"
Rushing-Style(s) -> "rushing " ++ s.show ++ "s"
Specific-Character(who) -> match who.show
"" -> "character with ID " ++ who.show
name -> name
Triggering -> "whosoever triggered this skill"

View File

@@ -1,70 +1,16 @@
module horse/trainee module horse/trainee
import std/data/rb-map import horse/movement
// Aptitudes of an umamusume being trained. // Details of a trainee.
pub struct uma pub struct trainee-detail
turf: aptitudes turf: level
dirt: aptitudes dirt: level
sprint: aptitudes sprint: level
mile: aptitudes mile: level
medium: aptitudes medium: level
long: aptitudes long: level
front-runner: aptitudes front-runner: level
pace-chaser: aptitudes pace-chaser: level
late-surger: aptitudes late-surger: level
end-closer: aptitudes end-closer: level
// Aptitude level distribution.
pub alias aptitudes = rbmap<level, float64>
// Starting aptitude levels.
pub type level
G
F
E
D
C
B
A
S
// Automatically generated.
// Fip comparison of the `level` type.
pub fun level/order2(this : level, other : level) : order2<level>
match (this, other)
(G, G) -> Eq2(G)
(G, other') -> Lt2(G, other')
(this', G) -> Gt2(G, this')
(F, F) -> Eq2(F)
(F, other') -> Lt2(F, other')
(this', F) -> Gt2(F, this')
(E, E) -> Eq2(E)
(E, other') -> Lt2(E, other')
(this', E) -> Gt2(E, this')
(D, D) -> Eq2(D)
(D, other') -> Lt2(D, other')
(this', D) -> Gt2(D, this')
(C, C) -> Eq2(C)
(C, other') -> Lt2(C, other')
(this', C) -> Gt2(C, this')
(B, B) -> Eq2(B)
(B, other') -> Lt2(B, other')
(this', B) -> Gt2(B, this')
(A, A) -> Eq2(A)
(A, other') -> Lt2(A, other')
(this', A) -> Gt2(A, this')
(S, S) -> Eq2(S)
// Automatically generated.
// Shows a string representation of the `level` type.
pub fun level/show(this : level) : string
match this
G -> "G"
F -> "F"
E -> "E"
D -> "D"
C -> "C"
B -> "B"
A -> "A"
S -> "S"

View File

@@ -93,15 +93,6 @@ func ExecSkill(t *template.Template, region string, kk, g io.Writer, groups []Na
return err return err
} }
func ExecSkillGroupKK(t *template.Template, region string, w io.Writer, g []NamedID[SkillGroup], s []Skill) error {
data := struct {
Region string
Groups []NamedID[SkillGroup]
Skills []Skill
}{region, g, s}
return t.ExecuteTemplate(w, "koka-skill-group", &data)
}
const wordSeps = " ,!?/-+();#○☆♡'=♪∀゚∴" const wordSeps = " ,!?/-+();#○☆♡'=♪∀゚∴"
var ( var (

View File

@@ -179,21 +179,22 @@ func SkillGroups(ctx context.Context, db *sqlitex.Pool) ([]NamedID[SkillGroup],
} }
type Skill struct { type Skill struct {
ID int ID int
Name string Name string
Description string Description string
GroupID int GroupID int
GroupName string GroupName string
Rarity int Rarity int
GroupRate int GroupRate int
GradeValue int GradeValue int
WitCheck bool WitCheck bool
Activations [2]SkillActivation Activations [2]SkillActivation
SPCost int SPCost int
InheritID int InheritID int
UniqueOwner string UniqueOwnerID int
IconID int UniqueOwner string
Index int IconID int
Index int
} }
type SkillActivation struct { type SkillActivation struct {
@@ -303,11 +304,12 @@ func Skills(ctx context.Context, db *sqlitex.Pool) ([]Skill, error) {
}, },
}, },
}, },
SPCost: stmt.ColumnInt(47), SPCost: stmt.ColumnInt(47),
InheritID: stmt.ColumnInt(48), InheritID: stmt.ColumnInt(48),
UniqueOwner: stmt.ColumnText(49), UniqueOwnerID: stmt.ColumnInt(49),
IconID: stmt.ColumnInt(50), UniqueOwner: stmt.ColumnText(50),
Index: stmt.ColumnInt(51), IconID: stmt.ColumnInt(51),
Index: stmt.ColumnInt(52),
} }
r = append(r, s) r = append(r, s)
} }

View File

@@ -116,14 +116,6 @@ func main() {
slog.Info("write skills") slog.Info("write skills")
return ExecSkill(t, region, sf, gf, sg, skills) return ExecSkill(t, region, sf, gf, sg, skills)
}) })
eg.Go(func() error {
sf, err := os.Create(filepath.Join(out, region, "skill-group.kk"))
if err != nil {
return err
}
slog.Info("write skill groups")
return ExecSkillGroupKK(t, region, sf, sg, skills)
})
if err := eg.Wait(); err != nil { if err := eg.Wait(); err != nil {
slog.Error("generate", slog.Any("err", err)) slog.Error("generate", slog.Any("err", err))
} else { } else {

View File

@@ -1,347 +1,213 @@
{{- define "koka-skill-group" -}}
module horse/{{ $.Region }}/skill-group
// Automatically generated with horsegen; DO NOT EDIT
// Skill groups.
// A skill group may contain white, circle, double-circle, gold, and purple skills
// for the same effect.
// Sparks that grant skills refer to a skill group.
pub type skill-group
{{- range $g := $.Groups }}
{{ kkenum $g.Name }}
{{- end }}
// Map a skill group to its ID.
pub fip fun skill-group/group-id(^sg: skill-group): int
match sg
{{- range $g := $.Groups }}
{{ kkenum $g.Name }} -> {{ $g.ID }}
{{- end }}
// Get the skill group for an ID.
pub fip(1) fun skill-group/from-id(^id: int): maybe<skill-group>
match id
{{- range $g := $.Groups }}
{{ $g.ID }} -> Just( {{- kkenum $g.Name -}} )
{{- end }}
_ -> Nothing
// Get the name for a skill group.
// Skill group names are the name of the base skill in the group.
pub fun skill-group/show(sg: skill-group): string
match sg
{{- range $g := $.Groups }}
{{ kkenum $g.Name }} -> {{ printf "%q" $g.Name }}
{{- end }}
// Compare two skill groups by ID order.
pub fip fun skill-group/order2(a: skill-group, b: skill-group): order2<skill-group>
match cmp(a.group-id, b.group-id)
Lt -> Lt2(a, b)
Eq -> Eq2(a)
Gt -> Gt2(a, b)
pub fun skill-group/(==)(a: skill-group, b: skill-group): bool
a.group-id == b.group-id
{{- end -}}
{{- define "koka-skill" -}} {{- define "koka-skill" -}}
module horse/{{ $.Region }}/skill module horse/{{ $.Region }}/skill
// Automatically generated with horsegen; DO NOT EDIT // Automatically generated with horsegen; DO NOT EDIT
import std/data/rb-map
import std/num/decimal import std/num/decimal
pub import horse/{{ $.Region }}/skill-group import horse/game-id
import horse/movement
pub import horse/skill
// Skill instances. val name2id: rbmap<string, skill-id> = rb-map/empty()
pub type skill
{{- range $s := $.Skills }} {{- range $s := $.Skills }}
{{ kkenum $s.Name }}{{ if ne $s.InheritID 0 }}-Inherit{{ end -}} .set({{ printf "%q" $s.Name }}{{ if $s.InheritID }} ++ " (Inherited)"{{ end }}, Skill-id({{ $s.ID }}))
{{- end }} {{- end }}
// Map a skill to its ID. // Get the skill ID that has the given exact name.
pub fip fun skill/skill-id(^s: skill): int // Inherited skills have `" (Inherited)"` appended to their names.
match s // If no skill matches the ID, the result is an invalid ID.
{{- range $s := $.Skills }} pub fun from-name(name: string): skill-id
{{ kkenum $s.Name }}{{ if ne $s.InheritID 0 }}-Inherit{{ end }} -> {{ $s.ID }} name2id.lookup(name).default(Skill-id(0))
{{- end }}
// Get the skill for an ID. // Get the name for a skill.
pub fip(1) fun skill/from-id(^id: int): maybe<skill> // Inherited skills have `" (Inherited)"` appended to their names.
match id // If no skill matches the ID, the result is the numeric ID.
pub fun show(s: skill-id): string
match s.game-id
{{- range $s := $.Skills }} {{- range $s := $.Skills }}
{{ $s.ID }} -> Just( {{- kkenum $s.Name -}}{{ if ne $s.InheritID 0 }}-Inherit{{ end -}} ) {{ $s.ID }} -> {{ printf "%q" $s.Name }}{{ if $s.InheritID }} ++ " (Inherited)"{{ end }}
{{- end }}
x -> "skill " ++ x.show
// Get the description for a skill.
// If no skill matches the ID, the result is the empty string.
pub fun description(s: skill-id): string
match s.game-id
{{- range $s := $.Skills }}
{{ $s.ID }} -> {{ printf "%q" $s.Description }}
{{- end }}
_ -> ""
// Get the skill group ID for a skill.
// If no skill matches the ID, the result is an invalid ID.
pub fun group(s: skill-id): skill-group-id
match s.game-id
{{- range $s := $.Skills }}
{{ $s.ID }} -> Skill-group-id( {{- $s.GroupID -}} )
{{- end }}
_ -> Skill-group-id(0)
// Get the rarity of a skill.
// If no skill matches the ID, the result is Common.
pub fun rarity(s: skill-id): rarity
match s.game-id
{{- range $s := $.Skills }}
{{ $s.ID }} -> {{ if eq $s.Rarity 1 }}Common{{ else if eq $s.Rarity 2 }}Rare{{ else if eq $s.Rarity 3 }}Unique-Low{{ else if eq $s.Rarity 4 }}Unique-Upgraded{{ else if eq $s.Rarity 5 }}Unique{{ else }}??? $s.Rarity={{ $s.Rarity }}{{ end }}
{{- end }}
_ -> Common
// Get the group rate of a skill.
// If no skill matches the ID, the result is 0.
pub fun group-rate(s: skill-id): int
match s.game-id
{{- range $s := $.Skills }}
{{ $s.ID }} -> {{ $s.GroupRate }}
{{- end }}
_ -> 0
// Get the grade value of a skill.
// If no skill matches the ID, the result is 0.
pub fun grade-value(s: skill-id): int
match s.game-id
{{- range $s := $.Skills }}
{{ $s.ID }} -> {{ $s.GradeValue }}
{{- end }}
_ -> 0
// Get whether a skill is a wit check.
// If no skill matches the ID, the result is False.
pub fun wit-check(s: skill-id): bool
match s.game-id
{{- range $s := $.Skills }}
{{ $s.ID }} -> {{ if $s.WitCheck }}True{{ else }}False{{ end }}
{{- end }}
_ -> False
// Get the activations of a skill.
// If no skill matches the ID, the result is an empty list.
pub fun activations(s: skill-id): list<activation>
match s.game-id
{{- range $s := $.Skills }}
{{ $s.ID }} -> [
{{- range $a := $s.Activations }}
{{- if $a.Condition }}
Activation(
precondition = {{ printf "%q" $a.Precondition }},
condition = {{ printf "%q" $a.Condition }},
duration = {{ $a.Duration }}.decimal{{ if gt $a.Duration 0 }}(-4){{ end }},
cooldown = {{ $a.Cooldown }}.decimal{{ if gt $a.Cooldown 0 }}(-4){{ end }},
abilities = [
{{- range $abil := $a.Abilities }}
{{- if $abil.Type }}
Ability(
ability-type = {{ if eq $abil.Type 1 }}Passive-Speed({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 2 }}Passive-Stamina({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 3 }}Passive-Power({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 4 }}Passive-Guts({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 5 }}Passive-Wit({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 6 }}Great-Escape
{{- else if eq $abil.Type 8 }}Vision({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 9 }}HP({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 10 }}Gate-Delay({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 13 }}Frenzy({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 21 }}Current-Speed({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 27 }}Target-Speed({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 28 }}Lane-Speed({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 31 }}Accel({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 35 }}Lane-Change({{ $abil.Value }}.decimal(-4))
{{- else }}??? $abil.Type={{$abil.Type}}
{{- end }},
value-usage = {{ if eq $abil.ValueUsage 1 }}Direct
{{- else if eq $abil.ValueUsage 3 }}Team-Speed
{{- else if eq $abil.ValueUsage 4 }}Team-Stamina
{{- else if eq $abil.ValueUsage 5 }}Team-Power
{{- else if eq $abil.ValueUsage 6 }}Team-Guts
{{- else if eq $abil.ValueUsage 7 }}Team-Wit
{{- else if eq $abil.ValueUsage 8 }}Multiply-Random
{{- else if eq $abil.ValueUsage 9 }}Multiply-Random2
{{- else if eq $abil.ValueUsage 10 }}Climax
{{- else if eq $abil.ValueUsage 13 }}Max-Stat
{{- else if eq $abil.ValueUsage 14 }}Passive-Count
{{- else if eq $abil.ValueUsage 19 }}Front-Distance-Add
{{- else if eq $abil.ValueUsage 20 }}Midrace-Side-Block-Time
{{- else if eq $abil.ValueUsage 22 }}Speed-Scaling
{{- else if eq $abil.ValueUsage 23 }}Speed-Scaling2
{{- else if eq $abil.ValueUsage 24 }}Arc-Global-Potential
{{- else if eq $abil.ValueUsage 25 }}Max-Lead-Distance
{{- else }}??? $abil.ValueUsage={{ $abil.ValueUsage }}
{{- end }},
target = {{ if eq $abil.Target 1}}Self
{{- else if eq $abil.Target 4 }}Sympathizers
{{- else if eq $abil.Target 4 }}In-View
{{- else if eq $abil.Target 4 }}Frontmost
{{- else if eq $abil.Target 9 }}Ahead({{ $abil.TargetValue }})
{{- else if eq $abil.Target 10 }}Behind({{ $abil.TargetValue }})
{{- else if eq $abil.Target 4 }}All-Teammates
{{- else if eq $abil.Target 18 }}Style({{ if eq $abil.TargetValue 1 }}Front-Runner{{ else if eq $abil.TargetValue 2 }}Pace-Chaser{{ else if eq $abil.TargetValue 3 }}Late-Surger{{ else if eq $abil.TargetValue 4 }}End-Closer{{ else }}??? $abil.TargetValue={{ $abil.TargetValue }}{{ end }})
{{- else if eq $abil.Target 19 }}Rushing-Ahead({{ $abil.TargetValue }})
{{- else if eq $abil.Target 20 }}Rushing-Behind({{ $abil.TargetValue }})
{{- else if eq $abil.Target 21 }}Rushing-Style({{ if eq $abil.TargetValue 1 }}Front-Runner{{ else if eq $abil.TargetValue 2 }}Pace-Chaser{{ else if eq $abil.TargetValue 3 }}Late-Surger{{ else if eq $abil.TargetValue 4 }}End-Closer{{ else }}??? $abil.TargetValue={{ $abil.TargetValue }}{{ end }})
{{- else if eq $abil.Target 22 }}Specific-Character(Character-id({{ $abil.TargetValue }}))
{{- else if eq $abil.Target 23 }}Triggering
{{- end }}
),
{{- end }}
{{- end }}
]
),
{{- end }}
{{- end }}
]
{{- end }}
_ -> Nil
// Get the owner of a unique skill.
// If the skill is not unique, or if there is no skill with the given ID,
// the result is Nothing.
pub fun unique-owner(s: skill-id): maybe<trainee-id>
match s.game-id
{{- range $s := $.Skills }}
{{- if $s.UniqueOwnerID }}
{{ $s.ID }} -> Just(Trainee-id({{ $s.UniqueOwnerID }}))
{{- end }}
{{- end }} {{- end }}
_ -> Nothing _ -> Nothing
// Get the name of a skill. // Get the SP cost of a skill.
// Inherited skills have the same names as their original counterparts. // If there is no skill with the given ID, the result is 0.
pub fun skill/show(s: skill): string pub fun sp-cost(s: skill-id): int
match s match s.game-id
{{- range $s := $.Skills }} {{- range $s := $.Skills }}
{{ kkenum $s.Name }}{{ if ne $s.InheritID 0 }}-Inherit{{ end }} -> {{ printf "%q" $s.Name }} {{ $s.ID }} -> {{ $s.SPCost }}
{{- end }} {{- end }}
_ -> 0
// Compare two skills by ID order. // Get the icon ID of a skill.
pub fip fun skill/order2(a: skill, b: skill): order2<skill> // If there is no skill with the given ID, the result is an invalid ID.
match cmp(a.skill-id, b.skill-id) pub fun icon-id(s: skill-id): skill-icon-id
Lt -> Lt2(a, b) match s.game-id
Eq -> Eq2(a) {{- range $s := $.Skills }}
Gt -> Gt2(a, b) {{ $s.ID }} -> Skill-icon-id({{ $s.IconID }})
{{- end }}
_ -> Skill-icon-id(0)
pub fun skill/(==)(a: skill, b: skill): bool // Get the name for a skill group.
a.skill-id == b.skill-id // Skill group names are the name of the base skill in the group.
// If there is no skill group with the given ID, the result is the numeric ID.
// Get the skills in a skill group. pub fun skill-group/show(sg: skill-group-id): string
pub fun skill-group/skills(g: skill-group): list<skill> match sg.game-id
match g
{{- range $g := $.Groups }} {{- range $g := $.Groups }}
{{ kkenum $g.Name }} -> [ {{- range $s := index $.Related $g.ID }}{{ kkenum $s.Name }}{{ if ne $s.InheritID 0 }}-Inherit{{ end }}, {{ end }}] {{ $g.ID }} -> {{- printf "%q" $g.Name -}}
{{- end }} {{- end }}
x -> "skill group " ++ x.show
// Get complete skill info. // Get the list of skills in a skill group.
pub fun skill/detail(^s: skill): skill-detail pub fun skill-group/skills(sg: skill-group-id): list<skill-id>
match s match sg.game-id
{{- range $s := $.Skills }} {{- range $g := $.Groups }}
{{ kkenum $s.Name }}{{ if ne $s.InheritID 0 }}-Inherit{{ end }} -> {{ template "kk-render-skill-detail" $s }} {{ $g.ID }} -> [ {{- range $s := index $.Related $g.ID }}Skill-id({{ $s.ID }}), {{ end -}} ]
{{- end }} {{- end }}
_ -> Nil
// Details about a skill. {{- end }}
pub struct skill-detail
skill-id: int
name: string
description: string
group: maybe<skill-group>
rarity: rarity
group-rate: int
grade-value: int
wit-check: bool
activations: list<activation>
sp-cost: int
icon-id: int
// Automatically generated.
// Shows a string representation of the `skill-detail` type.
pub fun skill-detail/show(this : skill-detail) : e string
match this
Skill-detail(skill-id, name, description, group, rarity, group-rate, grade-value, wit-check, activations, sp-cost, icon-id) -> "Skill-detail(skill-id: " ++ skill-id.show ++ ", name: " ++ name.show ++ ", description: " ++ description.show ++ ", group: " ++ group.show ++ ", rarity: " ++ rarity.show ++ ", group-rate: " ++ group-rate.show ++ ", grade-value: " ++ grade-value.show ++ ", wit-check: " ++ wit-check.show ++ ", activations: " ++ activations.show ++ ", sp-cost: " ++ sp-cost.show ++ ", icon-id: " ++ icon-id.show ++ ")"
// Skill rarity.
pub type rarity
Common // white
Rare // gold
Unique-Low // 1*/2* unique
Unique-Upgraded // 3*+ unique on a trainee upgraded from 1*/2*
Unique // base 3* unique
pub fun rarity/show(r: rarity): string
match r
Common -> "Common"
Rare -> "Rare"
Unique-Low -> "Unique (1\u2606/2\u2606)"
Unique-Upgraded -> "Unique (3\u2606+ from 1\u2606/2\u2606 upgraded)"
Unique -> "Unique (3\u2606+)"
// Condition and precondition logic.
pub alias condition = string
// Activation conditions and effects.
// A skill has one or two activations.
pub struct activation
precondition: condition
condition: condition
duration: decimal // seconds
cooldown: decimal // seconds
abilities: list<ability> // one to three elements
pub fun activation/show(a: activation): string
match a
Activation("", condition, duration, _, abilities) | duration <= 0.decimal -> condition ++ " -> " ++ abilities.show
Activation("", condition, duration, cooldown, abilities) | cooldown >= 500.decimal -> condition ++ " -> " ++ abilities.show ++ " for " ++ duration.show ++ "s"
Activation("", condition, duration, cooldown, abilities) -> condition ++ " -> " ++ abilities.show ++ " for " ++ duration.show ++ "s on " ++ cooldown.show ++ "s cooldown"
Activation(precondition, condition, duration, _, abilities) | duration <= 0.decimal -> precondition ++ " -> " ++ condition ++ " -> " ++ abilities.show
Activation(precondition, condition, duration, cooldown, abilities) | cooldown >= 500.decimal -> precondition ++ " -> " ++ condition ++ " -> " ++ abilities.show ++ " for " ++ duration.show ++ "s"
Activation(precondition, condition, duration, cooldown, abilities) -> precondition ++ "-> " ++ condition ++ " -> " ++ abilities.show ++ " for " ++ duration.show ++ "s on " ++ cooldown.show ++ "s cooldown"
// Effects of activating a skill.
pub struct ability
ability-type: ability-type
value-usage: value-usage
target: target
pub fun ability/show(a: ability): string
match a
Ability(t, Direct, Self) -> t.show
Ability(t, Direct, target) -> t.show ++ " " ++ target.show
Ability(t, v, Self) -> t.show ++ " scaling by " ++ v.show
Ability(t, v, target) -> t.show ++ " " ++ target.show ++ " scaling by " ++ v.show
// Target of a skill activation effect.
pub type ability-type
Passive-Speed(bonus: decimal)
Passive-Stamina(bonus: decimal)
Passive-Power(bonus: decimal)
Passive-Guts(bonus: decimal)
Passive-Wit(bonus: decimal)
Great-Escape
Vision(bonus: decimal)
HP(rate: decimal)
Gate-Delay(rate: decimal)
Frenzy(add: decimal)
Current-Speed(rate: decimal)
Target-Speed(rate: decimal)
Lane-Speed(rate: decimal)
Accel(rate: decimal)
Lane-Change(rate: decimal)
pub fun ability-type/show(a: ability-type): string
match a
Passive-Speed(bonus) -> "passive " ++ bonus.show ++ " Speed"
Passive-Stamina(bonus) -> "passive " ++ bonus.show ++ " Stamina"
Passive-Power(bonus) -> "passive " ++ bonus.show ++ " Power"
Passive-Guts(bonus) -> "passive " ++ bonus.show ++ " Guts"
Passive-Wit(bonus) -> "passive " ++ bonus.show ++ " Wit"
Great-Escape -> "enable Great Escape style"
Vision(bonus) -> bonus.show ++ " vision"
HP(rate) | rate >= 0.decimal -> show(rate * 100.decimal) ++ "% HP recovery"
HP(rate) -> show(rate * 100.decimal) ++ "% HP loss"
Gate-Delay(rate) -> rate.show ++ "× gate delay"
Frenzy(add) -> add.show ++ "s longer Rushed"
Current-Speed(rate) -> show(rate * 100.decimal) ++ "% current speed"
Target-Speed(rate) -> show(rate * 100.decimal) ++ "% target speed"
Lane-Speed(rate) -> show(rate * 100.decimal) ++ "% lane speed"
Accel(rate) -> show(rate * 100.decimal) ++ "% acceleration"
Lane-Change(rate) -> rate.show ++ " course width movement"
// Special scaling for skill activation effects.
pub type value-usage
Direct
Team-Speed
Team-Stamina
Team-Power
Team-Guts
Team-Wit
Multiply-Random
pub fun value-usage/show(v: value-usage): string
match v
Direct -> "no scaling"
Team-Speed -> "team's Speed"
Team-Stamina -> "team's Stamina"
Team-Power -> "team's Power"
Team-Guts -> "team's Guts"
Team-Wit -> "team's Wit"
Multiply-Random -> "random multiplier (0×, 0.02×, or 0.04×)"
// Who a skill activation targets.
pub type target
Self
In-View
Ahead(limit: int)
Behind(limit: int)
Style(style: style)
Rushing-Ahead(limit: int)
Rushing-Behind(limit: int)
Rushing-Style(style: style)
pub fun target/show(t: target): string
match t
Self -> "self"
In-View -> "others in field of view"
Ahead(limit) | limit >= 18 -> "others ahead"
Ahead(limit) -> "next " ++ limit.show ++ " others ahead"
Behind(limit) | limit >= 18 -> "others behind"
Behind(limit) -> "next " ++ limit.show ++ " others behind"
Style(Front-Runner) -> "other Front Runners"
Style(Pace-Chaser) -> "other Pace Chasers"
Style(Late-Surger) -> "other Late Surgers"
Style(End-Closer) -> "other End Closers"
Rushing-Ahead(limit) | limit >= 18 -> "others rushing ahead"
Rushing-Ahead(limit) -> "next " ++ limit.show ++ " others rushing ahead"
Rushing-Behind(limit) | limit >= 18 -> "others rushing behind"
Rushing-Behind(limit) -> "next " ++ limit.show ++ " others rushing behind"
Rushing-Style(Front-Runner) -> "rushing Front Runners"
Rushing-Style(Pace-Chaser) -> "rushing Pace Chasers"
Rushing-Style(Late-Surger) -> "rushing Late Surgers"
Rushing-Style(End-Closer) -> "rushing End Closers"
// Running style for skill targets.
{{- /* TODO(zeph): there is definitely a better place for this to live */}}
pub type style
Front-Runner
Pace-Chaser
Late-Surger
End-Closer
{{- end -}}
{{ define "kk-render-skill-detail" }}
{{- /* Call with Skill structure as argument. */ -}}
Skill-detail(skill-id = {{ $.ID -}}
, name = {{ printf "%q" $.Name -}}
, description = {{ printf "%q" $.Description -}}
, group = {{ if ne $.GroupName "" }}Just({{ kkenum $.GroupName }}){{ else }}Nothing{{ end -}}
, rarity = {{ if eq $.Rarity 1 }}Common{{ else if eq $.Rarity 2 }}Rare{{ else if eq $.Rarity 3 }}Unique-Low{{ else if eq $.Rarity 4 }}Unique-Upgraded{{ else if eq $.Rarity 5 }}Unique{{ else }}??? $.Rarity={{ $.Rarity }}{{ end -}}
, group-rate = {{ $.GroupRate -}}
, grade-value = {{ $.GradeValue -}}
, wit-check = {{ if $.WitCheck }}True{{ else }}False{{ end -}}
, activations = [
{{- range $a := $.Activations -}}
{{- if ne $a.Condition "" -}}
Activation(precondition = {{ printf "%q" $a.Precondition -}}
, condition = {{ printf "%q" $a.Condition -}}
, duration = {{ $a.Duration -}}{{ if gt $a.Duration 0 }}.decimal(-4){{ else }}.decimal{{ end -}}
, cooldown = {{ $a.Cooldown -}}{{ if gt $a.Cooldown 0 }}.decimal(-4){{ else }}.decimal{{ end -}}
, abilities = [
{{- range $abil := $a.Abilities -}}
{{- if ne $abil.Type 0 -}}
Ability(ability-type =
{{- if eq $abil.Type 1 -}}Passive-Speed({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 2 -}}Passive-Stamina({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 3 -}}Passive-Power({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 4 -}}Passive-Guts({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 5 -}}Passive-Wit({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 6 -}}Great-Escape
{{- else if eq $abil.Type 8 -}}Vision({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 9 -}}HP({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 10 -}}Gate-Delay({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 13 -}}Frenzy({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 21 -}}Current-Speed({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 27 -}}Target-Speed({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 28 -}}Lane-Speed({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 31 -}}Accel({{ $abil.Value }}.decimal(-4))
{{- else if eq $abil.Type 35 -}}Lane-Change({{ $abil.Value }}.decimal(-4))
{{- else -}}??? $abil.Type={{$abil.Type}}
{{- end -}}
, value-usage =
{{- if eq $abil.ValueUsage 1 -}}Direct
{{- else if eq $abil.ValueUsage 3 -}}Team-Speed
{{- else if eq $abil.ValueUsage 4 -}}Team-Stamina
{{- else if eq $abil.ValueUsage 5 -}}Team-Power
{{- else if eq $abil.ValueUsage 6 -}}Team-Guts
{{- else if eq $abil.ValueUsage 7 -}}Team-Wit
{{- else if eq $abil.ValueUsage 8 -}}Multiply-Random
{{- else -}}??? $abil.ValueUsage={{ $abil.ValueUsage }}
{{- end -}}
, target =
{{- if eq $abil.Target 1 -}}Self
{{- else if eq $abil.Target 4 -}}In-View
{{- else if eq $abil.Target 9 -}}Ahead({{ $abil.TargetValue }})
{{- else if eq $abil.Target 10 -}}Behind({{ $abil.TargetValue }})
{{- else if eq $abil.Target 18 -}}Style({{ if eq $abil.TargetValue 1 }}Front-Runner{{ else if eq $abil.TargetValue 2 }}Pace-Chaser{{ else if eq $abil.TargetValue 3 }}Late-Surger{{ else if eq $abil.TargetValue 4 }}End-Closer{{ else }}??? $abil.TargetValue={{ $abil.TargetValue }}{{ end }})
{{- else if eq $abil.Target 19 -}}Rushing-Ahead({{ $abil.TargetValue }})
{{- else if eq $abil.Target 20 -}}Rushing-Behind({{ $abil.TargetValue }})
{{- else if eq $abil.Target 21 -}}Rushing-Style({{ if eq $abil.TargetValue 1 }}Front-Runner{{ else if eq $abil.TargetValue 2 }}Pace-Chaser{{ else if eq $abil.TargetValue 3 }}Late-Surger{{ else if eq $abil.TargetValue 4 }}End-Closer{{ else }}??? $abil.TargetValue={{ $abil.TargetValue }}{{ end }})
{{- end -}}
),
{{- end -}}
{{- end -}}
]),
{{- end -}}
{{- end -}}
], sp-cost = {{ $.SPCost -}}
, icon-id = {{ $.IconID -}}
)
{{- end -}}

View File

@@ -21,6 +21,7 @@ WITH skill_names AS (
), card_unique AS ( ), card_unique AS (
SELECT DISTINCT SELECT DISTINCT
ss.skill_id1 AS unique_id, ss.skill_id1 AS unique_id,
card_name.id AS owner_id,
card_name.name card_name.name
FROM card_data card FROM card_data card
JOIN card_name ON card.id = card_name.id JOIN card_name ON card.id = card_name.id
@@ -81,6 +82,7 @@ SELECT
d.target_value_2_3, d.target_value_2_3,
IFNULL(p.need_skill_point, 0) AS sp_cost, IFNULL(p.need_skill_point, 0) AS sp_cost,
d.unique_skill_id_1, d.unique_skill_id_1,
COALESCE(u.owner_id, iu.owner_id, 0) AS unique_owner_id,
COALESCE(u.name, iu.name, '') AS unique_owner, COALESCE(u.name, iu.name, '') AS unique_owner,
d.icon_id, d.icon_id,
ROW_NUMBER() OVER (ORDER BY d.id) - 1 AS "index" ROW_NUMBER() OVER (ORDER BY d.id) - 1 AS "index"