212 lines
5.5 KiB
Go
212 lines
5.5 KiB
Go
package main
|
|
|
|
import (
|
|
"embed"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"regexp"
|
|
"strings"
|
|
"text/template"
|
|
"unicode"
|
|
)
|
|
|
|
//go:embed *.template
|
|
var templates embed.FS
|
|
|
|
// LoadTemplates sets up templates to render game data to source code.
|
|
func LoadTemplates() (*template.Template, error) {
|
|
t := template.New("root")
|
|
t.Funcs(template.FuncMap{
|
|
"kkenum": kkenum,
|
|
"goenum": goenum,
|
|
})
|
|
return t.ParseFS(templates, "*")
|
|
}
|
|
|
|
// ExecCharacter renders the Koka character module to kk and the Go character file to g.
|
|
// If either is nil, it is skipped.
|
|
func ExecCharacter(t *template.Template, region string, kk, g io.Writer, c []NamedID[Character], pairs, trios []AffinityRelation) error {
|
|
if len(pairs) != len(c)*len(c) {
|
|
return fmt.Errorf("there are %d pairs but there must be %d for %d characters", len(pairs), len(c)*len(c), len(c))
|
|
}
|
|
if len(trios) != len(c)*len(c)*len(c) {
|
|
return fmt.Errorf("there are %d trios but there must be %d for %d characters", len(trios), len(c)*len(c)*len(c), len(c))
|
|
}
|
|
|
|
maxid := 0
|
|
pm := make(map[int]map[int]int, len(c))
|
|
tm := make(map[int]map[int]map[int]int, len(c))
|
|
for _, u := range c {
|
|
maxid = max(maxid, u.ID)
|
|
pm[u.ID] = make(map[int]int, len(c))
|
|
tm[u.ID] = make(map[int]map[int]int, len(c))
|
|
for _, v := range c {
|
|
tm[u.ID][v.ID] = make(map[int]int, len(c))
|
|
}
|
|
}
|
|
for _, p := range pairs {
|
|
pm[p.IDA][p.IDB] = p.Affinity
|
|
}
|
|
for _, t := range trios {
|
|
tm[t.IDA][t.IDB][t.IDC] = t.Affinity
|
|
}
|
|
|
|
data := struct {
|
|
Region string
|
|
Characters []NamedID[Character]
|
|
Pairs []AffinityRelation
|
|
Trios []AffinityRelation
|
|
PairMaps map[int]map[int]int
|
|
TrioMaps map[int]map[int]map[int]int
|
|
Count int
|
|
MaxID int
|
|
}{region, c, pairs, trios, pm, tm, len(c), maxid}
|
|
var err error
|
|
if kk != nil {
|
|
err = errors.Join(err, t.ExecuteTemplate(kk, "koka-character", &data))
|
|
}
|
|
if g != nil {
|
|
err = errors.Join(err, t.ExecuteTemplate(g, "go-character", &data))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func ExecSkill(t *template.Template, region string, kk, g io.Writer, groups []NamedID[SkillGroup], skills []Skill) error {
|
|
m := make(map[int][]Skill, len(groups))
|
|
for _, t := range skills {
|
|
m[t.GroupID] = append(m[t.GroupID], t)
|
|
}
|
|
data := struct {
|
|
Region string
|
|
Groups []NamedID[SkillGroup]
|
|
Skills []Skill
|
|
Related map[int][]Skill
|
|
}{region, groups, skills, m}
|
|
var err error
|
|
if kk != nil {
|
|
err = errors.Join(err, t.ExecuteTemplate(kk, "koka-skill", &data))
|
|
}
|
|
if g != nil {
|
|
err = errors.Join(err, t.ExecuteTemplate(g, "go-skill-data", &data))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func ExecRace(t *template.Template, region string, kk, g io.Writer, races []Race) error {
|
|
data := struct {
|
|
Region string
|
|
Races []Race
|
|
}{region, races}
|
|
var err error
|
|
if kk != nil {
|
|
err = errors.Join(err, t.ExecuteTemplate(kk, "koka-race", &data))
|
|
}
|
|
if g != nil {
|
|
err = errors.Join(err, t.ExecuteTemplate(g, "go-race", &data))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func ExecSaddle(t *template.Template, region string, kk, g io.Writer, saddles []Saddle) error {
|
|
data := struct {
|
|
Region string
|
|
Saddles []Saddle
|
|
}{region, saddles}
|
|
var err error
|
|
if kk != nil {
|
|
err = errors.Join(err, t.ExecuteTemplate(kk, "koka-saddle", &data))
|
|
}
|
|
if g != nil {
|
|
err = errors.Join(err, t.ExecuteTemplate(g, "go-saddle", &data))
|
|
}
|
|
return err
|
|
}
|
|
|
|
const wordSeps = " ,!?/-+();#○☆♡'=♪∀゚∴"
|
|
|
|
var (
|
|
kkReplace = func() *strings.Replacer {
|
|
r := []string{
|
|
"Triple 7s", "Triple-Sevens", // hard to replace with the right thing automatically
|
|
"1,500,000 CC", "One-Million-CC",
|
|
"15,000,000 CC", "Fifteen-Million-CC",
|
|
"1st", "First",
|
|
"114th", "Hundred-Fourteenth",
|
|
"♡ 3D Nail Art", "Nail-Art",
|
|
".", "",
|
|
"\u2019", "",
|
|
"&", "-and-",
|
|
"'s", "s",
|
|
"ó", "o",
|
|
"∞", "Infinity",
|
|
"\u00d7", "x",
|
|
"◎", "Lv2",
|
|
}
|
|
for _, c := range wordSeps {
|
|
r = append(r, string(c), "-")
|
|
}
|
|
return strings.NewReplacer(r...)
|
|
}()
|
|
kkMultidash = regexp.MustCompile(`-+`)
|
|
kkDashNonletter = regexp.MustCompile(`-[^A-Za-z]`)
|
|
|
|
goReplace = func() *strings.Replacer {
|
|
r := []string{
|
|
"Triple 7s", "TripleSevens",
|
|
"1,500,000 CC", "OneMillionCC",
|
|
"15,000,000 CC", "FifteenMillionCC",
|
|
"1st", "First",
|
|
"♡ 3D Nail Art", "NailArt",
|
|
".", "",
|
|
"\u2019", "",
|
|
"&", "And",
|
|
"'s", "s",
|
|
"∞", "Infinity",
|
|
"\u00d7", "X",
|
|
"◎", "Lv2",
|
|
}
|
|
for _, c := range wordSeps {
|
|
r = append(r, string(c), "")
|
|
}
|
|
return strings.NewReplacer(r...)
|
|
}()
|
|
)
|
|
|
|
func kkenum(name string) string {
|
|
orig := name
|
|
name = kkReplace.Replace(name)
|
|
name = kkMultidash.ReplaceAllLiteralString(name, "-")
|
|
name = strings.Trim(name, "-")
|
|
if len(name) == 0 {
|
|
panic(fmt.Errorf("%q became empty as Koka enum variant", orig))
|
|
}
|
|
name = strings.ToUpper(name[:1]) + name[1:]
|
|
if !unicode.IsLetter(rune(name[0])) {
|
|
//lint:ignore ST1005 proper name
|
|
panic(fmt.Errorf("Koka enum variant %q (from %q) starts with a non-letter", name, orig))
|
|
}
|
|
for _, c := range name {
|
|
if c > 127 {
|
|
// Koka does not allow non-ASCII characters in source code.
|
|
// Don't proceed if we've missed one.
|
|
panic(fmt.Errorf("non-ASCII character %q (%[1]U) in Koka enum variant %q (from %q)", c, name, orig))
|
|
}
|
|
}
|
|
if kkDashNonletter.MatchString(name) {
|
|
panic(fmt.Errorf("non-letter character after a dash in Koka enum variant %q (from %q)", name, orig))
|
|
}
|
|
return name
|
|
}
|
|
|
|
func goenum(name string) string {
|
|
// go names are a bit more lax, so we need fewer checks
|
|
orig := name
|
|
name = goReplace.Replace(name)
|
|
if len(name) == 0 {
|
|
panic(fmt.Errorf("%q became empty as Go enum variant", orig))
|
|
}
|
|
name = strings.ToUpper(name[:1]) + name[1:]
|
|
return name
|
|
}
|