Compare commits

237 Commits
Author SHA1 Message Date
zephyr 55aa03aa41 umadump/replay: start of race replay json decoding 2026-09-20 11:44:09 -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
zephyr 78aee43e5f zenno: format
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
why the h*ck does vscode not format my .ts files on save even though i
have it configured to always format on save!!!!!!
2026-09-14 17:21:51 -04:00
zephyr 745c9d01dd zenno/doc/frbm: delete jbc sprint
Fixes #46.
2026-09-14 17:20:40 -04:00
zephyr e00fe0965a zenno/doc/mdb: emphasize that support card event outcomes are not in the mdb
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline failed
2026-09-14 17:07:25 -04:00
zephyr caa19dd85a zenno/doc/mdb: document races
ci/woodpecker/push/horsebot Pipeline failed
ci/woodpecker/push/zenno Pipeline failed
2026-09-11 18:14:14 -04:00
zephyr 3bf17944b3 horse, mdb, cmd/horsebot: racecourses
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline failed
2026-09-08 15:56:01 -04:00
zephyr 30f89e86f0 doc: add a recent dump 2026-09-08 15:56:01 -04:00
zephyr 8850dd4a08 zenno/doc/mdb: document skills
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline failed
2026-09-07 16:12:32 -04:00
zephyr 8946091b93 zenno: add ability type for all stats up 2026-09-07 16:08:52 -04:00
zephyr 9e3e06f0a0 zenno: rename missed Greate Escape -> Runaway 2026-09-07 12:28:49 -04:00
zephyr 12fea4c37e zenno/doc/tagged-skills: tag 403 is lane movement too 2026-09-06 22:08:37 -04:00
zephyr ad02e0711d zenno/chara/convo: include character icons
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline failed
2026-09-06 14:21:09 -04:00
zephyr 869235a8c2 zenno/chara/convo: use condition_type
Fixes #8.
2026-09-06 11:56:50 -04:00
zephyr 15f6d2c59a zenno/doc/frbm: update for GL
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
2026-09-04 21:47:32 -04:00
zephyr d0c11aa527 zenno: section speed does not use base wit
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
Per analysis by Aya, only final wit is used for section speed.

Fixes #40.
2026-09-03 15:30:29 -04:00
zephyr c7a847f677 zenno/race/stam: add disclaimers
Also minor adjustments to chart size and how effective HP is calculated.
2026-09-03 15:10:47 -04:00
zephyr 0791fdd3d4 zenno: stamina calculator first pass
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
2026-09-03 06:51:47 -04:00
zephyr e5f18d3c78 zenno/race/stam: remove the hard parts for now 2026-08-30 16:08:58 -04:00
zephyr 0d3cf24714 zenno: rebase stamina calculator & format 2026-08-30 15:45:42 -04:00
zephyr a8ed0dbbe0 zenno/race/stam: start showing outputs 2026-08-30 15:45:42 -04:00
zephyr 84f4f8e358 zenno/race/stam: beginning of stamina calculator 2026-08-30 15:45:42 -04:00
zephyr 7085fea1b7 umadump/replay: implement race sim decoding
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
2026-08-30 01:56:43 -04:00
zephyr 1f195024e1 umadump/ism: idle single mode model 2026-08-26 21:08:46 -04:00
zephyr a91980e603 doc: mention race_course_set
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
2026-08-26 20:29:14 -04:00
zephyr 8de957b59b zenno: allow both stats in threshold modifier to be null
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
2026-08-26 18:57:31 -04:00
zephyr 206fee797c zenno/race/carryover: stat threshold modifier 2026-08-26 18:54:25 -04:00
zephyr 6ef2a190cd zenno: format
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
2026-08-26 18:22:03 -04:00
zephyr 9a7028a44f zenno/race/carryover: add tables and disclaimers
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline failed
2026-08-26 18:01:10 -04:00
zephyr a7218c68ed zenno/race/carryover: calculator for late race carrover and accel
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline failed
2026-08-26 17:46:14 -04:00
zephyr 1f31ef223d zenno/racelib: allow accel skills to be delayed 2026-08-26 17:12:56 -04:00
zephyr 444fea85f0 zenno: lint fixes
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
2026-08-21 15:45:53 -04:00
zephyr e92ce64b17 zenno: fix aptitude level type for umas
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline failed
2026-08-21 15:34:39 -04:00
zephyr 8d2a0e6c0d zenno/doc/mdb: include succession_initial_factor 2026-08-21 15:31:22 -04:00
zephyr 6b8b494739 zenno/doc/mdb: divide support card docs into sections 2026-08-21 15:12:42 -04:00
zephyr d8d9ec6543 zenno/doc/mdb: document sparks 2026-08-21 15:02:57 -04:00
zephyr cb48e238a8 zenno: account for ism data with no start stats
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline failed
2026-08-19 16:30:49 -04:00
zephyr 2bf229b5da zenno/career/ismdata: start visualization
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline failed
2026-08-19 16:10:54 -04:00
zephyr bde080eadd zenno: add support card level and effect functonality
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline failed
2026-08-13 17:39:23 -04:00
zephyr 1fe397ca81 zenno/career/ismdata: start of auto career analysis
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline failed
2026-08-07 17:01:33 -04:00
zephyr 5a13519189 zenno: scenario data 2026-08-05 16:00:59 -04:00
zephyr 9f4574e176 cmd/horsebot: also download support type icons 2026-08-04 17:14:57 -04:00
zephyr eaee64dca4 cmd/horsebot: download support card icons
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline failed
2026-08-02 23:36:32 -04:00
zephyr a232f78ac7 zenno: support card type definitions 2026-08-02 23:27:43 -04:00
zephyr 061fe89b7f remove vestiges of an ancient past 2026-08-02 22:48:02 -04:00
zephyr c0ddc32997 horse, mdb, cmd/horsebot: support cards
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
Fixes #37.
2026-08-02 22:45:34 -04:00
zephyr 9521b5e7c5 horse: go generate 2026-08-02 02:37:24 -04:00
zephyr efcdf5d525 zenno/doc/mdb: document support card hints 2026-08-02 02:37:10 -04:00
zephyr 8faf5cadc7 horse: improve single target ahead/behind skills wording
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
Fixes #28.
2026-07-31 22:33:59 -04:00
zephyr 656e155625 zenno: improve single target ahead/behind skills wording
For #28.
2026-07-31 22:30:44 -04:00
zephyr ff07b90c73 horse, zenno: fans ability value scaling
Fixes #30.
2026-07-31 22:27:52 -04:00
zephyr d265c81735 horse: add 564 ability type
While we're here adding the formatting, also adjust DD's wording and fix
the units on lane change speed skills.

Fixes #25.
2026-07-31 22:24:03 -04:00
zephyr 998156d345 zenno: gl skills are tag 803
Fixes #32.
2026-07-31 22:14:25 -04:00
zephyr aaa7c6d5b5 zenno/doc/mdb: start the mdb doc
For #12.
2026-07-31 22:12:38 -04:00
zephyr 48118efeeb doc: document grand live mechanics
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
2026-07-28 00:19:14 -04:00
zephyr 79d4e76090 mdb: sort scenarios by sort id
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
Fixes #33.
2026-07-25 11:57:24 -04:00
zephyr ea39be0510 zenno: include character icons in character picks
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
2026-07-24 23:09:24 -04:00
zephyr 7bac55ca43 zenno: show icons on skills 2026-07-24 22:56:31 -04:00
zephyr 435da61405 cmd: move toradl into horsebot
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
Fixes #31.
2026-07-24 17:18:44 -04:00
zephyr f3e7c7b43b all: update deps
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
2026-07-23 22:55:17 -04:00
zephyr eded415df4 cmd/toradl: program for downloading icons from gametora 2026-07-23 22:48:12 -04:00
zephyr 34029fe001 zenno/doc/tagged-skills: more skill info
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
2026-07-21 17:01:23 -04:00
zephyr ffb700545f zenno/doc/tagged-skills: new page
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
2026-07-19 22:29:15 -04:00
zephyr 70ad09a13e zenno/chara/convo: list characters in convos even when they don't have their own
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
Fixes #24.
2026-07-19 15:46:41 -04:00
zephyr 74c22c7313 zenno: rename Great Escape -> Runaway everywhere
Fixes #26.
2026-07-19 15:38:01 -04:00
zephyr 2c1b4b9261 zenno/doc/race: lint fix
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
2026-07-18 16:58:20 -04:00
zephyr e5128b6783 zenno/race/spurt: add dueling info 2026-07-18 16:58:12 -04:00
zephyr b22c8307fd zenno/race: common component for calculator outputs 2026-07-18 16:38:07 -04:00
zephyr cb54bbe414 zenno/doc/frbm: add doccy references to some sections
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline failed
2026-07-13 18:59:52 -04:00
zephyr 096cff28b6 zenno/doc/frbm: change of cm16 plans 2026-07-13 18:45:51 -04:00
zephyr 197df8f022 zenno/doc/race: add reference links to doccy 2026-07-13 18:41:18 -04:00
zephyr 009dd1d695 zenno: add page titles and descriptions
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
2026-07-12 14:37:43 -04:00
zephyr bdafc93315 zenno: lint fix
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
2026-07-10 20:01:20 -04:00
zephyr ef5b06d66e zenno: pull vite config from stam calc branch
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline failed
ci/woodpecker/manual/horsebot Pipeline was successful
ci/woodpecker/manual/zenno Pipeline failed
2026-07-10 17:34:02 -04:00
zephyr 65b81612bc ci: allow running manually
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline failed
2026-07-10 17:29:35 -04:00
zephyr 6269665e79 ci: always run zenno workflow 2026-07-10 16:20:02 -04:00
zephyr cae7d5b8f4 zenno: format 2026-07-10 15:37:29 -04:00
zephyr fca43e64b5 zenno/doc/spark: make spark select searchable 2026-07-09 15:40:07 -04:00
zephyr bc395a0788 zenno: refactor CharaPick to generic component 2026-07-09 14:54:03 -04:00
zephyr 1d879cbf9f zenno/race/gate: add velocity/time chart 2026-07-07 20:47:20 -04:00
zephyr 240a3d9a73 zenno/doc/spark: decribe & clarify 2026-07-07 16:23:15 -04:00
zephyr 3d73460934 zenno: link up doc/spark 2026-07-07 15:55:31 -04:00
zephyr 0c518a9ce2 zenno/doc/spark: spark explorer 2026-07-07 15:51:28 -04:00
zephyr 5cbb6d7b1d zenno/doc/frbm: updates for 7-1 patch 2026-07-07 11:16:09 -04:00
zephyr b0bd16fd04 zenno/index: move discord bot to its own section 2026-07-06 09:45:07 -04:00
zephyr 55175c9c40 zenno: format & lint 2026-07-06 09:41:11 -04:00
zephyr 06e5a06680 zenno/race/gate: caveats 2026-07-06 09:39:36 -04:00
zephyr dbb0dcd2dc zenno: link up race/gate 2026-07-06 09:23:37 -04:00
zephyr 02f723b83a zenno/race/gate: new calculator 2026-07-06 09:01:11 -04:00
zephyr 1f58dec491 zenno/doc/race: conserve power 2026-07-01 23:39:31 -04:00
zephyr a6d264c79d zenno: pull race mechanics lib update from stam-calc 2026-07-01 22:40:11 -04:00
zephyr d62e729052 zenno: update stat charts for new limits 2026-07-01 22:12:10 -04:00
zephyr 9ce665865e zenno/doc/race: fix 4 skill activation chance chart 2026-06-26 13:13:30 -04:00
zephyr 7d395b2cc7 ci: set up
ci/woodpecker/push/horsebot Pipeline was successful
2026-06-17 10:50:04 -04:00
zephyr 2b69211a6c horse: update deps 2026-06-17 09:58:02 -04:00
zephyr 67377c4690 zenno: format 2026-06-16 14:20:03 -04:00
zephyr 1d65a0ec14 zenno/doc/race: add missing chart controls
Fixes #21.
2026-06-16 13:40:16 -04:00
zephyr ad58edbfe1 zenno: make character selects searchable
Fixes #20.
2026-06-16 13:37:59 -04:00
zephyr f8e7bfbec6 zenno/doc/race: skill activation chance is not integer percent 2026-06-14 20:00:27 -04:00
zephyr 2e618b613f zenno/lib/race: constrain poskeep chances to hundredths 2026-06-14 19:05:52 -04:00
zephyr cc2b5fcfcb zenno: make stat chart tooltips attach vertically 2026-06-14 18:59:33 -04:00
zephyr 8ef3936be5 zenno/doc/race: fix mobile 2026-06-14 18:57:43 -04:00
zephyr b06e4b537b zenno: link up doc/race
Fixes #16.
2026-06-14 17:55:21 -04:00
zephyr 7b4cd41d3b zenno/doc/frbm: remove stat summaries
For #16.
2026-06-14 17:45:29 -04:00
zephyr 2c9434729a zenno/doc/race: interactive graphs for race mechanics
For #16.
2026-06-14 17:36:00 -04:00
zephyr 7f02925ff7 horse: define rushed chance mod and current speed up ability types
Fixes #17.
2026-06-14 12:04:07 -04:00
zephyr b217691c8a zenno/doc/lanecalc: lint fixes 2026-06-14 10:45:39 -04:00
zephyr 4bce196c09 zenno/doc/frbm: effects on other styles in cm comps
Fixes #18.
2026-06-14 10:34:34 -04:00
zephyr aa17d8ccf3 zenno: beg
Fixes #15.
2026-06-13 22:59:49 -04:00
zephyr 26a5ca4496 zenno/doc/lanecalc: typo 2026-06-13 22:12:59 -04:00
zephyr 51f942ee18 zenno: mention doc/lanecalc as appropriate
Fixes #14.
2026-06-13 22:09:09 -04:00
zephyr 2c773391f0 zenno: link up doc/lanecalc 2026-06-13 22:04:49 -04:00
zephyr c66d969c82 zenno/doc/lanecalc: english manual for lanecalc
For #14.
2026-06-13 22:02:16 -04:00
zephyr bc9d722ee0 zenno/doc/frbm: cm14 results and cm15 plans 2026-06-13 13:20:54 -04:00
zephyr 6482f11620 zenno/doc/frbm: rename chariot -> rabbit
jern on the Hakuraku discord informs me that chariot is a different
style of build.

Fixes #19.
2026-06-13 12:35:07 -04:00
zephyr 75024c7c11 zenno: categorize tools 2026-06-10 14:03:42 -04:00
zephyr dc78d51def zenno: link up affinity tool 2026-06-08 16:45:12 -04:00
zephyr 5f0ad1b0e0 zenno/affinity: new tool 2026-06-08 16:42:04 -04:00
zephyr 6db0ca5230 mdb, cmd/horsebot: affinity details 2026-06-07 15:58:29 -04:00
zephyr 049617c22e zenno/doc/frbm: cm14 round 1 win rates 2026-06-07 15:04:29 -04:00
zephyr d431d3382a global, cmd/horsegen: don't generate json anymore
Everything gets the data from the mdb directly instead now.
2026-06-05 15:13:36 -04:00
zephyr a848c8eb72 zenno: use data from api 2026-06-05 15:13:11 -04:00
zephyr 871fd5fdcc cmd/horsebot: allow acting only as api server 2026-06-05 15:13:11 -04:00
zephyr e3dd37f515 cmd/horsebot: serve data under /api/global 2026-06-05 15:13:11 -04:00
zephyr 3557b32298 cmd/horsebot: serve data from mdb instead of json 2026-06-05 15:13:11 -04:00
zephyr 20db674d25 cmd/horsegen: generate using package mdb 2026-06-05 15:13:11 -04:00
zephyr 2a4da0a73e mdb: don't list carnival bonus sparks to exclude them 2026-06-05 15:13:11 -04:00
zephyr e6335c1d6e global: generate with deleted lobby location names 2026-06-05 15:13:11 -04:00
zephyr f40fea0ec5 mdb: package for interacting with game's local database 2026-06-05 15:13:10 -04:00
zephyr 96503e40f6 doc: add query to get skill condition types 2026-06-05 15:13:10 -04:00
zephyr c95e9bb4cc global: generate with 2026-06-04 db 2026-06-04 14:53:28 -04:00
zephyr 825fe0f2fb zenno: make article toc only render upon first view 2026-05-31 20:35:02 -04:00
zephyr 4ea689f426 zenno/doc/frbm: mention 1.5anni solo front adjustment 2026-05-31 16:55:59 -04:00
zephyr f0bb6d1926 zenno/doc/frbm: include cm14 falco stats 2026-05-31 16:52:07 -04:00
zephyr 13436abd1a zenno/doc/frbm: add section on parenting 2026-05-31 16:49:42 -04:00
zephyr bed60a8a67 zenno/doc/frbm: no zone -> no-overtake zone 2026-05-29 16:41:41 -04:00
zephyr 2b8ef759df zenno/doc/frbm: explicitly compare pp and ignited wit for lane combo 2026-05-29 16:38:28 -04:00
zephyr 9318bb9091 zenno/doc: auto-toc 2026-05-29 16:32:35 -04:00
zephyr 847f2eb8ca zenno: link up doc/frbm 2026-05-29 15:09:10 -04:00
zephyr 0b7201779c zenno: use spans for skill tooltips 2026-05-29 14:57:48 -04:00
zephyr 2b37c00ed0 zenno/doc/frbm: stat reference 2026-05-29 13:59:35 -04:00
zephyr 2e5a78e1d8 zenno/doc/frbm: implement additional feedback 2026-05-29 01:33:16 -04:00
zephyr 277c904bc5 zenno/doc/frbm: some feedback edits & more past cm teams 2026-05-28 19:26:50 -04:00
zephyr 8945a3943b global: generate with 2026-05-28 db 2026-05-28 15:43:35 -04:00
zephyr 1cfd80ac3d zenno/doc/frbm: add some support cards to career 2026-05-28 15:35:48 -04:00
zephyr b4e6a34aca zenno/doc/frbm: cm teams 2026-05-27 20:36:18 -04:00
zephyr b3c8699635 zenno/doc/frbm: reorganize as positioning -> skills -> builds 2026-05-27 13:05:57 -04:00
zephyr 223495a04a zenno: fix margins on skill tips with multiple activations 2026-05-27 02:26:49 -04:00
zephyr 40a015363c zenno/doc/frbm: precise example for uphill power diff 2026-05-26 17:30:30 -04:00
zephyr 2869e7c160 zenno: format and lint fixes 2026-05-26 00:30:28 -04:00
zephyr 30a72d91b8 zenno/mspeed: also show base acceleration 2026-05-26 00:29:11 -04:00
zephyr efb36f705c zenno/mspeed: fix display on mobile, maybe 2026-05-25 22:59:16 -04:00
zephyr 6a30b1d6f0 zenno/doc/frbm: mention gold skills bad for lane combo 2026-05-25 22:02:01 -04:00
zephyr 7b45b8761b zenno: tooltips on skills 2026-05-25 15:36:25 -04:00
zephyr a89ef8fc4c zenno/doc/frbm: lucky 7 is not a gate skill 2026-05-24 18:21:35 -04:00
zephyr bae79a5320 zenno: format & lint fixes 2026-05-24 18:06:51 -04:00
zephyr 0f14ece4da zenno: include rightmost endpoint in stat charts 2026-05-24 18:06:40 -04:00
zephyr b1c6850ce1 zenno/doc/frbm: chart and calcs for section speed 2026-05-24 17:55:57 -04:00
zephyr bd99cfaa6d zenno/doc/frbm: update with charts &c. 2026-05-23 16:18:24 -04:00
zephyr 3ab17cf9b0 zenno: clip marks in charts by default 2026-05-23 00:15:46 -04:00
zephyr 4bd7962182 zenno/mspeed: show spot struggle and lane combo on each others' charts 2026-05-23 00:13:21 -04:00
zephyr 09f099171b zenno/mspeed: wording improvements 2026-05-23 00:01:19 -04:00
zephyr 4fff7069a8 zenno/mspeed, zenno/spurt: step race length inputs by 100 2026-05-22 23:56:30 -04:00
zephyr 3e2153b39c zenno/lib: rearrange parameters in race.speedGain 2026-05-22 23:52:21 -04:00
zephyr d0fa6ab15c zenno/mspeed: account for accelTime >= dur 2026-05-22 23:39:17 -04:00
zephyr 9f8024d488 zenno/lib: style 2026-05-22 23:22:28 -04:00
zephyr 1df3bc1db9 zenno/spurt: take adjusted speed, not raw speed 2026-05-22 23:19:23 -04:00
zephyr a8c1b9c754 zenno: remove the expand button from stat charts 2026-05-22 23:16:09 -04:00
zephyr 7600c48cc7 zenno: lint fixes 2026-05-22 20:25:22 -04:00
zephyr 2e31560d6c zenno/mspeed: fix mobile layout 2026-05-22 19:41:10 -04:00
zephyr 2a07f193ec zenno/mspeed: calculator for front runner mechanical speed bonuses 2026-05-22 19:22:17 -04:00
zephyr b720b325b3 zenno: add stat charts 2026-05-19 19:45:02 -04:00
zephyr 80573a84ea zenno: update dependencies 2026-05-18 11:21:47 -04:00
zephyr bc94d66002 zenno/lib/race: add stat type 2026-05-18 11:20:59 -04:00
zephyr aca5fccaa7 global: generate with 2026-05-18 db 2026-05-18 10:30:30 -04:00
zephyr ee6ced1390 global: generate with 2026-05-08 db 2026-05-08 09:30:03 -04:00
zephyr af8e5907b9 cmd/horsebot: serve data json on http
For #7.
2026-05-07 19:40:25 -04:00
zephyr 9cf9fd198f horse, cmd/horsegen: include tags in generated skills
Fixes #6.
2026-05-04 16:46:24 -04:00
zephyr 0c2db10082 horse, cmd/horsebot: various fixes for skill formatting 2026-05-04 15:35:54 -04:00
zephyr fae9c38098 zenno/doc/frbm: mechanics and cm13 2026-05-03 18:44:25 -04:00
zephyr 0799bf658f all: remove koka code, move go to repo root 2026-04-29 23:28:13 -04:00
zephyr 2cec7c5699 cmd/horsebot: use purple circles for negative skills in related menu 2026-04-29 23:17:19 -04:00
zephyr b10a2572ec horse: add flat gate delay ability type 2026-04-29 23:08:09 -04:00
zephyr 657cf22f71 zenno/doc/frbm: initial draft of most mechanics sections 2026-04-27 18:03:24 -04:00
zephyr dce58357ae zenno/doc: initial work on front runner black magic 2026-04-27 03:25:53 -04:00
zephyr 7abe427c03 zenno: runner model 2026-04-26 22:01:22 -04:00
zephyr 1a55150ddd global: generate with 2026-04-24 db 2026-04-24 10:29:11 -04:00
zephyr 3e879f3687 zenno: lint fixes 2026-04-20 11:57:15 -04:00
zephyr 0eb932dc3c zenno: remove inheritance and spark pages
uma.moe and chronogenesis both have this now, at least in progress.
2026-04-20 11:38:31 -04:00
zephyr eb26b42543 global: generate with 2026-04-20 db 2026-04-20 10:19:36 -04:00
zephyr d082ce3374 zenno/spurt: compare speed skills 2026-04-15 13:32:43 -04:00
zephyr faccc3e912 global: generate with 2026-04-10 db 2026-04-11 15:24:16 -04:00
zephyr af8edb8999 cmd/horsebot: don't state instantaneous for recoveries 2026-04-07 19:25:28 -04:00
zephyr 0abe5fc3e4 zenno: npm up --save 2026-04-06 16:51:31 -04:00
zephyr 5a444ea08c horsegen, zenno/convo: get all character names 2026-04-06 16:46:37 -04:00
zephyr dfb2ad2950 zenno/convo: refactor name logic 2026-04-06 16:09:46 -04:00
zephyr 256e11d320 zenno/spurt: calculator for last spurt speed 2026-04-04 01:45:14 -04:00
zephyr 467098a9e4 zenno/lib: spark component 2026-04-03 18:01:00 -04:00
zephyr 9532e0ec89 zenno/lib: legacy and affinity stuff 2026-04-03 11:42:13 -04:00
zephyr c14850a048 global: generate with 2026-04-03 db 2026-04-03 11:10:56 -04:00
zephyr d0aec5c501 cmd/horsebot: compress served files 2026-04-02 10:45:30 -04:00
zephyr ccef87f0a3 zenno: resolve lint errors 2026-04-02 10:45:30 -04:00
zephyr 34140c0bba cmd/horsebot: fix autocomplete for uniques 2026-04-01 18:48:04 -04:00
zephyr 0ad064725f zenno: link to discord bot 2026-03-31 23:21:29 -04:00
zephyr 886dccb6b8 zenno: title and favicon 2026-03-31 19:33:24 -04:00
zephyr 57e8a06383 zenno: generate pages as directory/index.html 2026-03-31 19:23:10 -04:00
zephyr ab14f58079 cmd/horsebot: fix http server in http interactions mode 2026-03-31 19:03:51 -04:00
zephyr 4106215180 zenno, cmd/horsebot: host website in horsebot 2026-03-31 18:43:17 -04:00
zephyr cdea376f94 zenno: make footer links open new tabs 2026-03-31 17:27:45 -04:00
zephyr d157dfc9b6 zenno: don't use sakura 2026-03-31 16:54:51 -04:00
zephyr 773625b842 zenno: allow styling CharaPick 2026-03-31 12:32:04 -04:00
zephyr 22ca5c98f3 zenno: format 2026-03-31 12:08:36 -04:00
zephyr 08deedea8f zenno: nicer lobby conversation page 2026-03-31 01:19:13 -04:00
zephyr 86b769d7ed zenno: lobby conversation page 2026-03-30 23:22:43 -04:00
zephyr e139eae06d zenno: start conversations tool 2026-03-30 21:53:03 -04:00
zephyr 34e8c1f812 zenno: implement character picker 2026-03-30 12:13:20 -04:00
zephyr cc3128d65a zenno: better mobile nav 2026-03-30 10:13:17 -04:00
zephyr d04544030a zenno: create sveltekit website 2026-03-29 23:14:55 -04:00
zephyr e13c435afa global: generate with 2026-03-26 db 2026-03-26 04:05:39 -04:00
zephyr 4429bbecd1 global: generate with 2026-03-19 db 2026-03-19 11:29:46 -04:00
zephyr 2099eeb97e schema: describe json schemas in typescript 2026-03-18 00:43:51 -04:00
zephyr c22ed0dc83 horse: adjust fields omitted from skill json 2026-03-18 00:42:08 -04:00
zephyr 494aeeb401 doc: document skill tag meanings 2026-03-17 19:09:04 -04:00
zephyr 298b1368e8 horse, horsegen: generate lobby conversation data 2026-03-17 17:15:07 -04:00
zephyr b20a1a5964 doc: add mant db dump, document epithet text_data 2026-03-17 17:08:06 -04:00
zephyr af8e04473a horsebot: shrink skill displays 2026-03-12 18:35:51 -04:00
zephyr 351f606d29 global: generate with 2026-03-12 db adding mant 2026-03-12 10:42:14 -04:00
zephyr da376647af horsebot: restore inherit indicators on skill names 2026-03-09 13:48:28 -04:00
zephyr 2ec8d9cfdb horsegen: include uniques and inherits in each others' skill groups 2026-03-09 13:00:29 -04:00
zephyr 2dd75edc03 horsebot: use a command option to decide whether ephemeral 2026-03-09 12:35:39 -04:00
zephyr e08580925d horsebot: make skill responses ephemeral with share button 2026-03-09 11:17:59 -04:00
zephyr 63659a4934 horsebot: refactor skill server 2026-03-09 10:05:07 -04:00
zephyr af4e06411d horsebot: take horsegen output directory instead of each json file 2026-03-09 09:04:02 -04:00
zephyr 4426925ebb horsegen: output to ./global instead of horse/global 2026-03-09 08:58:49 -04:00
zephyr 8632bb8c3c all: generate json, not code
This includes modifying horsebot to use the generated JSON, as well as
moving the generator to another cmd/ directory.

Remove the generated code while we're here.
Koka tests still have to be updated, but it requires a JSON parser.
2026-03-08 21:33:46 -04:00
zephyr 7ff271ff2d horse: generate json 2026-03-08 14:04:28 -04:00
368 changed files with 383183 additions and 64139 deletions
-3
View File
@@ -1,3 +0,0 @@
[submodule "std"]
path = std
url = git@github.com:koka-community/std.git
+12
View File
@@ -0,0 +1,12 @@
when:
- event: push
branch: main
- event: pull_request
- event: manual
steps:
- name: test
image: golang:1.26-alpine
commands:
- go vet ./...
- go test ./...
+15
View File
@@ -0,0 +1,15 @@
when:
- event: push
branch: main
- event: pull_request
- event: manual
steps:
- name: test
image: node:24-alpine
commands:
- cd zenno
- npm ci
- npm run check
- npm run lint
- npm run test
@@ -27,13 +27,13 @@ const (
_AbilityTarget_name_0 = "selfothers with Sympathy" _AbilityTarget_name_0 = "selfothers with Sympathy"
_AbilityTarget_name_1 = "others in view" _AbilityTarget_name_1 = "others in view"
_AbilityTarget_name_2 = "frontmost" _AbilityTarget_name_2 = "frontmost"
_AbilityTarget_name_3 = "others aheadothers behindall teammates" _AbilityTarget_name_3 = "aheadbehindall teammates"
_AbilityTarget_name_4 = "using stylerushing others aheadrushing others behindrushing using stylespecific characterwhosoever triggered this skill" _AbilityTarget_name_4 = "using stylerushing others aheadrushing others behindrushing using stylespecific characterwhosoever triggered this skill"
) )
var ( var (
_AbilityTarget_index_0 = [...]uint8{0, 4, 24} _AbilityTarget_index_0 = [...]uint8{0, 4, 24}
_AbilityTarget_index_3 = [...]uint8{0, 12, 25, 38} _AbilityTarget_index_3 = [...]uint8{0, 5, 11, 24}
_AbilityTarget_index_4 = [...]uint8{0, 11, 31, 52, 71, 89, 119} _AbilityTarget_index_4 = [...]uint8{0, 11, 31, 52, 71, 89, 119}
) )
@@ -18,27 +18,34 @@ func _() {
_ = x[AbilityHP-9] _ = x[AbilityHP-9]
_ = x[AbilityGateDelay-10] _ = x[AbilityGateDelay-10]
_ = x[AbilityFrenzy-13] _ = x[AbilityFrenzy-13]
_ = x[AbilityAddGateDelay-14]
_ = x[AbilityCurrentSpeed-21] _ = x[AbilityCurrentSpeed-21]
_ = x[AbilityCurrentSpeedUp-22]
_ = x[AbilityTargetSpeed-27] _ = x[AbilityTargetSpeed-27]
_ = x[AbilityLaneSpeed-28] _ = x[AbilityLaneSpeed-28]
_ = x[AbilityRushedChance-29]
_ = x[AbilityAccel-31] _ = x[AbilityAccel-31]
_ = x[AbilityLaneChange-35] _ = x[AbilityLaneChange-35]
_ = x[AbilityActivateRare-37]
} }
const ( const (
_AbilityType_name_0 = "SpeedStaminaPowerGutsWitEnable Great Escape" _AbilityType_name_0 = "SpeedStaminaPowerGutsWitEnable Great Escape"
_AbilityType_name_1 = "VisionHPGate delay multiplier" _AbilityType_name_1 = "VisionHPGate delay multiplier"
_AbilityType_name_2 = "Frenzy" _AbilityType_name_2 = "FrenzyAdded gate delay"
_AbilityType_name_3 = "Current speed" _AbilityType_name_3 = "Current speedCurrent speed"
_AbilityType_name_4 = "Target speedLane change speed" _AbilityType_name_4 = "Target speedLane change speedRushed chance"
_AbilityType_name_5 = "Acceleration" _AbilityType_name_5 = "Acceleration"
_AbilityType_name_6 = "Forced lane change" _AbilityType_name_6 = "Set target lane"
_AbilityType_name_7 = "Activate rare skills"
) )
var ( var (
_AbilityType_index_0 = [...]uint8{0, 5, 12, 17, 21, 24, 43} _AbilityType_index_0 = [...]uint8{0, 5, 12, 17, 21, 24, 43}
_AbilityType_index_1 = [...]uint8{0, 6, 8, 29} _AbilityType_index_1 = [...]uint8{0, 6, 8, 29}
_AbilityType_index_4 = [...]uint8{0, 12, 29} _AbilityType_index_2 = [...]uint8{0, 6, 22}
_AbilityType_index_3 = [...]uint8{0, 13, 26}
_AbilityType_index_4 = [...]uint8{0, 12, 29, 42}
) )
func (i AbilityType) String() string { func (i AbilityType) String() string {
@@ -49,17 +56,21 @@ func (i AbilityType) String() string {
case 8 <= i && i <= 10: case 8 <= i && i <= 10:
i -= 8 i -= 8
return _AbilityType_name_1[_AbilityType_index_1[i]:_AbilityType_index_1[i+1]] return _AbilityType_name_1[_AbilityType_index_1[i]:_AbilityType_index_1[i+1]]
case i == 13: case 13 <= i && i <= 14:
return _AbilityType_name_2 i -= 13
case i == 21: return _AbilityType_name_2[_AbilityType_index_2[i]:_AbilityType_index_2[i+1]]
return _AbilityType_name_3 case 21 <= i && i <= 22:
case 27 <= i && i <= 28: i -= 21
return _AbilityType_name_3[_AbilityType_index_3[i]:_AbilityType_index_3[i+1]]
case 27 <= i && i <= 29:
i -= 27 i -= 27
return _AbilityType_name_4[_AbilityType_index_4[i]:_AbilityType_index_4[i+1]] return _AbilityType_name_4[_AbilityType_index_4[i]:_AbilityType_index_4[i+1]]
case i == 31: case i == 31:
return _AbilityType_name_5 return _AbilityType_name_5
case i == 35: case i == 35:
return _AbilityType_name_6 return _AbilityType_name_6
case i == 37:
return _AbilityType_name_7
default: default:
return "AbilityType(" + strconv.FormatInt(int64(i), 10) + ")" return "AbilityType(" + strconv.FormatInt(int64(i), 10) + ")"
} }
@@ -18,6 +18,7 @@ func _() {
_ = x[ValueUsageRandom-8] _ = x[ValueUsageRandom-8]
_ = x[ValueUsageRandom2-9] _ = x[ValueUsageRandom2-9]
_ = x[ValueUsageClimax-10] _ = x[ValueUsageClimax-10]
_ = x[ValueUsageFans-12]
_ = x[ValueUsageMaxStat-13] _ = x[ValueUsageMaxStat-13]
_ = x[ValueUsageGreenCount-14] _ = x[ValueUsageGreenCount-14]
_ = x[ValueUsageDistAdd-19] _ = x[ValueUsageDistAdd-19]
@@ -29,15 +30,15 @@ func _() {
} }
const ( const (
_AbilityValueUsage_name_0 = "directlyscaling with the number of skillsscaling with team Speedscaling with team Staminascaling with team Powerscaling with team Gutsscaling with team Witwith a random 0× to 0.04× multiplierwith a random 0× to 0.04× multiplierscaling with the number of races won in training" _AbilityValueUsage_name_0 = "directlyscaling with the number of skillsscaling with team Speedscaling with team Staminascaling with team Powerscaling with team Gutsscaling with team Witwith a random 0× to 0.04× multiplierwith a random 0× to 0.04× multiplierscaling with the number of races won in the career"
_AbilityValueUsage_name_1 = "scaling with the highest raw statscaling with the number of Passive skills activated" _AbilityValueUsage_name_1 = "scaling with the number of fans earned in the careerscaling with the highest raw statscaling with the number of Passive skills activated"
_AbilityValueUsage_name_2 = "plus extra when far from the leadscaling with mid-race phase blocked side time" _AbilityValueUsage_name_2 = "plus extra when far from the leadscaling with mid-race phase blocked side time"
_AbilityValueUsage_name_3 = "scaling with overall speedscaling with overall speedscaling with L'Arc global potentialscaling with the longest lead obtained in the first ⅔" _AbilityValueUsage_name_3 = "scaling with overall speedscaling with overall speedscaling with L'Arc global potentialscaling with the longest lead obtained in the first ⅔"
) )
var ( var (
_AbilityValueUsage_index_0 = [...]uint16{0, 8, 41, 64, 89, 112, 134, 155, 193, 231, 279} _AbilityValueUsage_index_0 = [...]uint16{0, 8, 41, 64, 89, 112, 134, 155, 193, 231, 281}
_AbilityValueUsage_index_1 = [...]uint8{0, 33, 84} _AbilityValueUsage_index_1 = [...]uint8{0, 52, 85, 136}
_AbilityValueUsage_index_2 = [...]uint8{0, 33, 78} _AbilityValueUsage_index_2 = [...]uint8{0, 33, 78}
_AbilityValueUsage_index_3 = [...]uint8{0, 26, 52, 87, 142} _AbilityValueUsage_index_3 = [...]uint8{0, 26, 52, 87, 142}
) )
@@ -47,8 +48,8 @@ func (i AbilityValueUsage) String() string {
case 1 <= i && i <= 10: case 1 <= i && i <= 10:
i -= 1 i -= 1
return _AbilityValueUsage_name_0[_AbilityValueUsage_index_0[i]:_AbilityValueUsage_index_0[i+1]] return _AbilityValueUsage_name_0[_AbilityValueUsage_index_0[i]:_AbilityValueUsage_index_0[i+1]]
case 13 <= i && i <= 14: case 12 <= i && i <= 14:
i -= 13 i -= 12
return _AbilityValueUsage_name_1[_AbilityValueUsage_index_1[i]:_AbilityValueUsage_index_1[i+1]] return _AbilityValueUsage_name_1[_AbilityValueUsage_index_1[i]:_AbilityValueUsage_index_1[i+1]]
case 19 <= i && i <= 20: case 19 <= i && i <= 20:
i -= 19 i -= 19
+67
View File
@@ -0,0 +1,67 @@
package horse
type CharacterID int16
type Character struct {
ID CharacterID `json:"chara_id"`
Name string `json:"name"`
}
func (c Character) String() string {
return c.Name
}
// AffinityRelation holds precomputed affinity values for a pair or trio.
type AffinityRelation struct {
IDA CharacterID `json:"chara_a"`
IDB CharacterID `json:"chara_b"`
IDC CharacterID `json:"chara_c,omitzero"`
Affinity int `json:"affinity"`
}
// AffinityDetail holds a single affinity entry.
type AffinityDetail struct {
CharaID CharacterID `json:"chara_id"`
Relation uint16 `json:"relation"`
Affinity uint16 `json:"affinity"`
}
// Conversation describes a lobby conversation.
type Conversation struct {
// ID is the conversation ID.
ID int32 `json:"id"`
// CharacterID is the ID of the character who has the conversation as
// a gallery entry.
CharacterID CharacterID `json:"chara_id"`
// Number is the conversation number within the character's gallery.
Number int `json:"number"`
// Location is the ID of the location the conversation occurs in the lobby.
Location LobbyConversationLocationID `json:"location"`
// Chara1 is the first conversation participant ID.
// It does not necessarily match CharacterID.
Chara1 CharacterID `json:"chara_1"`
// Chara2 is the second conversation participant ID.
Chara2 CharacterID `json:"chara_2,omitzero"`
// Chara3 is the third conversation participant ID.
Chara3 CharacterID `json:"chara_3,omitzero"`
// ConditionType is a complete mystery to me.
ConditionType int `json:"condition_type"`
}
type LobbyConversationLocationID int
//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type LobbyConversationLocationID -trimprefix Lobby -linecomment
const (
LobbyRightFront1 LobbyConversationLocationID = 110 // right side front
LobbyRightFront2 LobbyConversationLocationID = 120 // right side front
LobbyRightFront3 LobbyConversationLocationID = 130 // right side front
LobbyLeftTable1 LobbyConversationLocationID = 210 // left side table
LobbyLeftTable2 LobbyConversationLocationID = 220 // left side table
LobbyBackSeat LobbyConversationLocationID = 310 // center back seat
LobbyPosters1 LobbyConversationLocationID = 410 // center posters
LobbyPosters2 LobbyConversationLocationID = 420 // center posters
LobbyPosters3 LobbyConversationLocationID = 430 // center posters
LobbySchoolMap1 LobbyConversationLocationID = 510 // left side school map
LobbySchoolMap2 LobbyConversationLocationID = 520 // left side school map
LobbySchoolMap3 LobbyConversationLocationID = 530 // left side school map
)
+12 -2
View File
@@ -1,10 +1,20 @@
# horsebot # horsebot
Discord bot serving horse game data. Website and Discord bot serving horse game data.
Production instance is named Zenno Rob Roy, because she has read all about Umamusume and is always happy to share her knowledge and give recommendations. Production instance is named Zenno Rob Roy, because she has read all about Umamusume and is always happy to share her knowledge and give recommendations.
## Running The `-mdb` flag is mandatory for both website and Discord bot modes.
It provides data for both the horsebot API and the discord bot.
## Running the Website
If the `-public` flag is provided, its value gives the directory to serve at `/`.
The `-img` flag gives a separate directory into which images are synced from GameTora; obtain permission from Gertas prior to using it.
The images are served on `/img/`.
## Running the Discord Bot
Provide the `-token` flag pointing to a file containing the token.
The bot always uses the Gateway API. The bot always uses the Gateway API.
If the `-http` argument is provided, it will also use the HTTP API, and `-key` must also be provided. If the `-http` argument is provided, it will also use the HTTP API, and `-key` must also be provided.
+148 -76
View File
@@ -3,9 +3,14 @@ package main
import ( import (
"bytes" "bytes"
"context" "context"
"encoding/hex"
"encoding/json"
"errors"
"flag" "flag"
"fmt" "fmt"
"log/slog" "log/slog"
"net"
"net/http"
"os" "os"
"os/signal" "os/signal"
"strconv" "strconv"
@@ -18,25 +23,34 @@ import (
"github.com/disgoorg/disgo/handler/middleware" "github.com/disgoorg/disgo/handler/middleware"
"github.com/disgoorg/disgo/httpserver" "github.com/disgoorg/disgo/httpserver"
"github.com/disgoorg/disgo/rest" "github.com/disgoorg/disgo/rest"
httpmiddle "github.com/go-chi/chi/v5/middleware"
"zombiezen.com/go/sqlite"
"zombiezen.com/go/sqlite/sqlitex"
"git.sunturtle.xyz/zephyr/horse/horse" "git.sunturtle.xyz/zephyr/horse/mdb"
"git.sunturtle.xyz/zephyr/horse/horse/global"
) )
func main() { func main() {
var ( var (
tokenFile string // public site
// http api options
addr string addr string
route string mdbf string
public string
img string
// discord
tokenFile string
apiRoute string
pubkey string pubkey string
// logging options // logging
level slog.Level level slog.Level
textfmt string textfmt string
) )
flag.StringVar(&addr, "http", ":13669", "`address` to bind HTTP server")
flag.StringVar(&mdbf, "mdb", "", "`path` to master.mdb")
flag.StringVar(&public, "public", "", "`dir`ectory containing the website to serve")
flag.StringVar(&img, "img", "", "`dir`ectory to save and serve images")
flag.StringVar(&tokenFile, "token", "", "`file` containing the Discord bot token") flag.StringVar(&tokenFile, "token", "", "`file` containing the Discord bot token")
flag.StringVar(&addr, "http", "", "`address` to bind HTTP API server") flag.StringVar(&apiRoute, "route", "/interactions/callback", "`path` to serve Discord HTTP API calls")
flag.StringVar(&route, "route", "/interactions/callback", "`path` to serve HTTP API calls")
flag.StringVar(&pubkey, "key", "", "Discord public key") flag.StringVar(&pubkey, "key", "", "Discord public key")
flag.TextVar(&level, "log", slog.LevelInfo, "slog logging `level`") flag.TextVar(&level, "log", slog.LevelInfo, "slog logging `level`")
flag.StringVar(&textfmt, "log-format", "text", "slog logging `format`, text or json") flag.StringVar(&textfmt, "log-format", "text", "slog logging `format`, text or json")
@@ -54,69 +68,152 @@ func main() {
} }
slog.SetDefault(slog.New(lh)) slog.SetDefault(slog.New(lh))
token, err := os.ReadFile(tokenFile) if public != "" {
stat, err := os.Stat(public)
if err != nil { if err != nil {
slog.Error("reading token", slog.Any("err", err)) slog.Error("public", slog.Any("err", err))
os.Exit(1) os.Exit(1)
} }
token = bytes.TrimSuffix(token, []byte{'\n'}) if !stat.IsDir() {
slog.Error("public", slog.String("err", "not a directory"))
os.Exit(1)
}
}
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
db, err := sqlitex.NewPool(mdbf, sqlitex.PoolOptions{Flags: sqlite.OpenReadOnly})
if err != nil {
slog.Error("mdb", slog.Any("err", err))
os.Exit(1)
}
if img != "" {
// Ensure the img directory is in fact a directory
// before we start trying to fill it out.
err := os.MkdirAll(img, 0777)
if err != nil {
slog.Error("img", slog.Any("err", err))
os.Exit(1)
}
go saveImgs(ctx, db, img)
}
var client *bot.Client
if tokenFile != "" {
skills, err := mdb.Skills(ctx, db)
slog.Info("loaded skills", slog.Int("count", len(skills)))
groups, err2 := mdb.SkillGroups(ctx, db)
slog.Info("loaded skill groups", slog.Int("count", len(groups)))
if err = errors.Join(err, err2); err != nil {
slog.Error("loading data", slog.Any("err", err))
os.Exit(1)
}
skillSrv := newSkillServer(skills, groups)
slog.Info("skill server ready")
r := handler.New() r := handler.New()
r.DefaultContext(func() context.Context { return ctx }) r.DefaultContext(func() context.Context { return ctx })
r.Use(middleware.Go) r.Use(middleware.Go)
r.Use(logMiddleware) r.Use(logMiddleware)
r.Route("/skill", func(r handler.Router) { r.Route("/skill", func(r handler.Router) {
r.SlashCommand("/", skillHandler) r.SlashCommand("/", skillSrv.slash)
r.Autocomplete("/", skillAutocomplete) r.Autocomplete("/", skillSrv.autocomplete)
r.ButtonComponent("/{id}", skillButton) r.SelectMenuComponent("/swap", skillSrv.menu)
r.ButtonComponent("/swap/{id}", skillSrv.button)
}) })
opts := []bot.ConfigOpt{bot.WithDefaultGateway(), bot.WithEventListeners(r)} slog.Info("connect", slog.String("disgo", disgo.Version))
if addr != "" { token, err := os.ReadFile(tokenFile)
if pubkey == "" { if err != nil {
slog.Error("Discord public key must be provided when using HTTP API") slog.Error("reading token", slog.Any("err", err))
os.Exit(1) os.Exit(1)
} }
opts = append(opts, bot.WithHTTPServerConfigOpts(pubkey, token = bytes.TrimSuffix(token, []byte{'\n'})
httpserver.WithAddress(addr), client, err = disgo.New(string(token), bot.WithDefaultGateway(), bot.WithEventListeners(r))
httpserver.WithURL(route),
))
}
slog.Info("connect", slog.String("disgo", disgo.Version))
client, err := disgo.New(string(token), opts...)
if err != nil { if err != nil {
slog.Error("building bot", slog.Any("err", err)) slog.Error("building bot", slog.Any("err", err))
os.Exit(1) os.Exit(1)
} }
}
mux := http.NewServeMux()
if public != "" {
mux.Handle("GET /", httpmiddle.Compress(5)(http.FileServerFS(os.DirFS(public))))
}
if img != "" {
mux.Handle("GET /img/", http.StripPrefix("/img", http.FileServerFS(os.DirFS(img))))
}
mux.Handle("GET /api/global/affinity", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.AffinitySummary)))
mux.Handle("GET /api/global/affinity-detail", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.AffinityDetail)))
mux.Handle("GET /api/global/character", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Characters)))
mux.Handle("GET /api/global/conversation", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Conversations)))
mux.Handle("GET /api/global/race", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Races)))
mux.Handle("GET /api/global/racecourse", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Racecourses)))
mux.Handle("GET /api/global/saddle", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Saddles)))
mux.Handle("GET /api/global/scenario", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Scenarios)))
mux.Handle("GET /api/global/skill", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Skills)))
mux.Handle("GET /api/global/skill-group", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.SkillGroups)))
mux.Handle("GET /api/global/spark", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Sparks)))
mux.Handle("GET /api/global/support", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.SupportCards)))
mux.Handle("GET /api/global/uma", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Umas)))
if pubkey != "" {
pk, err := hex.DecodeString(pubkey)
if err != nil {
slog.Error("pubkey", slog.Any("err", err))
os.Exit(1)
}
mux.Handle("POST "+apiRoute, httpserver.HandleInteraction(httpserver.DefaultVerifier{}, pk, slog.Default(), client.EventManager.HandleHTTPEvent))
slog.Info("Discord HTTP API enabled", slog.String("pubkey", pubkey))
}
l, err := net.Listen("tcp", addr)
if err != nil {
slog.Error("listen", slog.String("addr", addr), slog.Any("err", err))
os.Exit(1)
}
srv := http.Server{
Addr: addr,
Handler: httpmiddle.Logger(mux),
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
BaseContext: func(l net.Listener) context.Context { return ctx },
}
go func() {
slog.Info("HTTP", slog.Any("addr", l.Addr()))
err := srv.Serve(l)
if err == http.ErrServerClosed {
return
}
slog.Error("HTTP server closed", slog.Any("err", err))
}()
if client != nil {
if err := handler.SyncCommands(client, commands, nil, rest.WithCtx(ctx)); err != nil { if err := handler.SyncCommands(client, commands, nil, rest.WithCtx(ctx)); err != nil {
slog.Error("syncing commands", slog.Any("err", err)) slog.Error("syncing commands", slog.Any("err", err))
os.Exit(1) os.Exit(1)
} }
if addr != "" {
slog.Info("start HTTP server", slog.String("address", addr), slog.String("route", route))
if err := client.OpenHTTPServer(); err != nil {
slog.Error("starting HTTP server", slog.Any("err", err))
stop()
}
}
slog.Info("start gateway") slog.Info("start gateway")
if err := client.OpenGateway(ctx); err != nil { if err := client.OpenGateway(ctx); err != nil {
slog.Error("starting gateway", slog.Any("err", err)) slog.Error("starting gateway", slog.Any("err", err))
stop() stop()
} }
}
slog.Info("ready") slog.Info("ready")
<-ctx.Done() <-ctx.Done()
stop() stop()
ctx, stop = context.WithTimeout(context.Background(), 5*time.Second) ctx, stop = context.WithTimeout(context.Background(), 5*time.Second)
defer stop() defer stop()
if client != nil {
client.Close(ctx) client.Close(ctx)
} }
if err := srv.Shutdown(ctx); err != nil {
slog.Error("HTTP API shutdown", slog.Any("err", err))
os.Exit(1)
}
}
var commands = []discord.ApplicationCommandCreate{ var commands = []discord.ApplicationCommandCreate{
discord.SlashCommandCreate{ discord.SlashCommandCreate{
@@ -129,55 +226,30 @@ var commands = []discord.ApplicationCommandCreate{
Required: true, Required: true,
Autocomplete: true, Autocomplete: true,
}, },
discord.ApplicationCommandOptionBool{
Name: "share",
Description: "Share the skill info",
},
}, },
}, },
} }
func skillHandler(data discord.SlashCommandInteractionData, e *handler.CommandEvent) error { func dataroute[T any](ctx context.Context, db *sqlitex.Pool, f func(context.Context, *sqlitex.Pool) ([]T, error)) http.HandlerFunc {
q := data.String("query") return func(w http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(q, 10, 32) data, err := f(ctx, db)
if err == nil {
// note inverted condition; this is when we have an id
id = int64(global.AllSkills[horse.SkillID(id)].ID)
}
if id == 0 {
// Either we weren't given a number or the number doesn't match any skill ID.
v := global.SkillNameToID[q]
if v == 0 {
// No such skill.
m := discord.MessageCreate{
Content: "No such skill.",
Flags: discord.MessageFlagEphemeral,
}
return e.CreateMessage(m)
}
id = int64(v)
}
// TODO(zeph): search conditions and effects, give a list
m := discord.MessageCreate{
Components: []discord.LayoutComponent{RenderSkill(horse.SkillID(id), global.AllSkills, global.SkillGroups)},
Flags: discord.MessageFlagIsComponentsV2,
}
return e.CreateMessage(m)
}
func skillAutocomplete(e *handler.AutocompleteEvent) error {
q := e.Data.String("query")
opts := skillGlobalAuto().Find(nil, q)
return e.AutocompleteResult(opts[:min(len(opts), 25)])
}
func skillButton(data discord.ButtonInteractionData, e *handler.ComponentEvent) error {
id, err := strconv.ParseInt(e.Vars["id"], 10, 32)
if err != nil { if err != nil {
m := discord.MessageCreate{ http.Error(w, err.Error(), http.StatusInternalServerError)
Content: "That button produced an invalid skill ID. That's not supposed to happen.", return
Flags: discord.MessageFlagEphemeral,
} }
return e.CreateMessage(m) b, err := json.Marshal(data)
if err != nil {
panic(err)
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Length", strconv.Itoa(len(b)))
if _, err := w.Write(b); err != nil {
slog.Error("writing response", slog.Any("err", err))
return
} }
m := discord.MessageUpdate{
Components: &[]discord.LayoutComponent{RenderSkill(horse.SkillID(id), global.AllSkills, global.SkillGroups)},
} }
return e.UpdateMessage(m)
} }
+152 -52
View File
@@ -2,67 +2,171 @@ package main
import ( import (
"fmt" "fmt"
"log/slog"
"strconv"
"strings" "strings"
"sync"
"github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/discord"
"github.com/disgoorg/disgo/handler"
"git.sunturtle.xyz/zephyr/horse"
"git.sunturtle.xyz/zephyr/horse/cmd/horsebot/autocomplete" "git.sunturtle.xyz/zephyr/horse/cmd/horsebot/autocomplete"
"git.sunturtle.xyz/zephyr/horse/horse"
"git.sunturtle.xyz/zephyr/horse/horse/global"
) )
func RenderSkill(id horse.SkillID, all map[horse.SkillID]horse.Skill, groups map[int32][4]horse.SkillID) discord.ContainerComponent { type skillServer struct {
s, ok := all[id] skills map[horse.SkillID]horse.Skill
if !ok { byName map[string]horse.SkillID
return discord.NewContainer(discord.NewTextDisplayf("invalid skill ID %v made it to RenderSkill", id)) groups map[horse.SkillGroupID]horse.SkillGroup
autocom autocomplete.Set[discord.AutocompleteChoice]
} }
thumburl := fmt.Sprintf("https://gametora.com/images/umamusume/skill_icons/utx_ico_skill_%d.png", s.IconID) func newSkillServer(skills []horse.Skill, groups []horse.SkillGroup) *skillServer {
top := "## " + s.Name s := skillServer{
if s.UniqueOwner != "" { skills: make(map[horse.SkillID]horse.Skill, len(skills)),
top += "\n-# " + s.UniqueOwner byName: make(map[string]horse.SkillID, len(skills)),
groups: make(map[horse.SkillGroupID]horse.SkillGroup, len(groups)),
}
for _, skill := range skills {
s.skills[skill.ID] = skill
id := strconv.Itoa(int(skill.ID))
switch {
case skill.UniqueOwner == "":
s.byName[skill.Name] = skill.ID
s.autocom.Add(skill.Name, discord.AutocompleteChoiceString{Name: skill.Name, Value: id})
case skill.Rarity >= 3:
s.byName[skill.Name] = skill.ID
s.autocom.Add(skill.Name, discord.AutocompleteChoiceString{Name: skill.Name, Value: id})
s.autocom.Add(skill.UniqueOwner, discord.AutocompleteChoiceString{Name: "Unique: " + skill.UniqueOwner, Value: id})
default:
name := skill.Name + " (Inherited)"
s.byName[name] = skill.ID
s.autocom.Add(name, discord.AutocompleteChoiceString{Name: name, Value: id})
s.autocom.Add(skill.UniqueOwner, discord.AutocompleteChoiceString{Name: "Inherited unique: " + skill.UniqueOwner, Value: id})
}
}
for _, g := range groups {
s.groups[g.ID] = g
}
return &s
}
func (s *skillServer) slash(data discord.SlashCommandInteractionData, e *handler.CommandEvent) error {
q := data.String("query")
id, err := strconv.ParseInt(q, 10, 32)
if err == nil {
// note inverted condition; this is when we have an id
id = int64(s.skills[horse.SkillID(id)].ID)
}
if id == 0 {
// Either we weren't given a number or the number doesn't match any skill ID.
v := s.byName[q]
if v == 0 {
// No such skill.
m := discord.MessageCreate{
Content: "No such skill.",
Flags: discord.MessageFlagEphemeral,
}
return e.CreateMessage(m)
}
id = int64(v)
}
m := discord.MessageCreate{
Components: []discord.LayoutComponent{s.render(horse.SkillID(id))},
Flags: discord.MessageFlagIsComponentsV2,
}
if !data.Bool("share") {
m.Flags |= discord.MessageFlagEphemeral
}
return e.CreateMessage(m)
}
func (s *skillServer) autocomplete(e *handler.AutocompleteEvent) error {
q := e.Data.String("query")
opts := s.autocom.Find(nil, q)
return e.AutocompleteResult(opts[:min(len(opts), 25)])
}
func (s *skillServer) button(data discord.ButtonInteractionData, e *handler.ComponentEvent) error {
id, err := strconv.ParseInt(e.Vars["id"], 10, 32)
if err != nil {
m := discord.MessageCreate{
Content: "That button produced an invalid skill ID. That's not supposed to happen.",
Flags: discord.MessageFlagEphemeral,
}
return e.CreateMessage(m)
}
m := discord.MessageUpdate{
Components: &[]discord.LayoutComponent{s.render(horse.SkillID(id))},
}
return e.UpdateMessage(m)
}
func (s *skillServer) menu(data discord.SelectMenuInteractionData, e *handler.ComponentEvent) error {
d, ok := data.(discord.StringSelectMenuInteractionData)
if !ok {
return fmt.Errorf("wrong select menu type %T", data)
}
if len(d.Values) != 1 {
return fmt.Errorf("wrong number of values: %q", d.Values)
}
id, err := strconv.ParseInt(d.Values[0], 10, 32)
if err != nil {
return err
}
m := discord.MessageUpdate{
Components: &[]discord.LayoutComponent{s.render(horse.SkillID(id))},
}
return e.UpdateMessage(m)
}
func (s *skillServer) render(id horse.SkillID) discord.ContainerComponent {
skill, ok := s.skills[id]
if !ok {
slog.Error("invalid skill id", slog.Int("id", int(id)))
return discord.NewContainer(discord.NewTextDisplayf("invalid skill ID %v made it to render", id))
}
top := "### " + skill.Name
if skill.UniqueOwner != "" {
top += "\n-# " + skill.UniqueOwner
} }
r := discord.NewContainer( r := discord.NewContainer(
discord.NewSection(
discord.NewTextDisplay(top), discord.NewTextDisplay(top),
discord.NewTextDisplay(s.Description), discord.NewSmallSeparator(),
).WithAccessory(discord.NewThumbnail(thumburl)),
) )
var skilltype string var skilltype string
switch { switch {
case s.Rarity == 3, s.Rarity == 4, s.Rarity == 5: case skill.Rarity == 3, skill.Rarity == 4, skill.Rarity == 5:
// unique of various star levels // unique of various star levels
r.AccentColor = 0xaca4d4 r.AccentColor = 0xaca4d4
skilltype = "Unique Skill" skilltype = "Unique Skill"
case s.UniqueOwner != "": case skill.UniqueOwner != "":
r.AccentColor = 0xcccccc r.AccentColor = 0xcccccc
skilltype = "Inherited Unique" skilltype = "Inherited Unique"
case s.Rarity == 2: case skill.Rarity == 2:
// rare (gold) // rare (gold)
r.AccentColor = 0xd7c25b r.AccentColor = 0xd7c25b
skilltype = "Rare Skill" skilltype = "Rare Skill"
case s.GroupRate == -1: case skill.GroupRate == -1:
// negative (purple) skill // negative (purple) skill
r.AccentColor = 0x9151d4 r.AccentColor = 0x9151d4
skilltype = "Negative Skill" skilltype = "Negative Skill"
case !s.WitCheck: case !skill.WitCheck:
// should be passive (green) // should be passive (green)
r.AccentColor = 0x66ae1c r.AccentColor = 0x66ae1c
skilltype = "Passive Skill" skilltype = "Passive Skill"
case isDebuff(s): case isDebuff(skill):
// debuff (red) // debuff (red)
r.AccentColor = 0xe34747 r.AccentColor = 0xe34747
skilltype = "Debuff Skill" skilltype = "Debuff Skill"
case s.Rarity == 1: case skill.Rarity == 1:
// common (white) // common (white)
r.AccentColor = 0xcccccc r.AccentColor = 0xcccccc
skilltype = "Common Skill" skilltype = "Common Skill"
} }
r.Components = append(r.Components, discord.NewSmallSeparator())
text := make([]string, 0, 3) text := make([]string, 0, 3)
abils := make([]string, 0, 3) abils := make([]string, 0, 3)
for _, act := range s.Activations { for _, act := range skill.Activations {
text, abils = text[:0], abils[:0] text, abils = text[:0], abils[:0]
if act.Precondition != "" { if act.Precondition != "" {
text = append(text, "Precondition: "+formatCondition(act.Precondition)) text = append(text, "Precondition: "+formatCondition(act.Precondition))
@@ -70,10 +174,8 @@ func RenderSkill(id horse.SkillID, all map[horse.SkillID]horse.Skill, groups map
text = append(text, "Condition: "+formatCondition(act.Condition)) text = append(text, "Condition: "+formatCondition(act.Condition))
var t string var t string
switch { switch {
case act.Duration < 0: case act.Duration <= 0:
// passive; do nothing // passive or instantaneous, e.g. recoveries; do nothing
case act.Duration == 0:
t = "Instantaneous "
case act.Duration >= 500e4: case act.Duration >= 500e4:
t = "Permanent " t = "Permanent "
case act.DurScale == horse.DurationDirect: case act.DurScale == horse.DurationDirect:
@@ -92,24 +194,38 @@ func RenderSkill(id horse.SkillID, all map[horse.SkillID]horse.Skill, groups map
r.Components = append(r.Components, discord.NewTextDisplay(strings.Join(text, "\n"))) r.Components = append(r.Components, discord.NewTextDisplay(strings.Join(text, "\n")))
} }
l := discord.NewTextDisplayf("%s ・ SP cost %d ・ Grade value %d ・ [Conditions on GameTora](https://gametora.com/umamusume/skill-condition-viewer?skill=%d)", skilltype, s.SPCost, s.GradeValue, s.ID) l := discord.NewTextDisplayf("%s ・ SP cost %d ・ Grade value %d ・ [Conditions on GameTora](https://gametora.com/umamusume/skill-condition-viewer?skill=%d)", skilltype, skill.SPCost, skill.GradeValue, skill.ID)
r.Components = append(r.Components, discord.NewSmallSeparator(), l) r.Components = append(r.Components, discord.NewSmallSeparator(), l)
rel := make([]horse.Skill, 0, 4) rel := make([]horse.Skill, 0, 4)
for _, id := range groups[s.Group] { group := s.groups[skill.Group]
for _, id := range [...]horse.SkillID{group.Skill1, group.Skill2, group.Skill3, group.SkillBad} {
if id != 0 { if id != 0 {
rel = append(rel, all[id]) rel = append(rel, s.skills[id])
} }
} }
if len(rel) > 1 { if len(rel) > 1 {
buttons := make([]discord.InteractiveComponent, 0, 4) opts := make([]discord.StringSelectMenuOption, 0, 4)
for _, rs := range rel { for _, rs := range rel {
b := discord.NewSecondaryButton(rs.Name, fmt.Sprintf("/skill/%d", rs.ID)) name := rs.Name
if rs.ID == id { emoji := "⚪"
b = b.AsDisabled() switch {
case rs.Rarity == 3, rs.Rarity == 4, rs.Rarity == 5:
emoji = "🟠"
case rs.UniqueOwner != "":
name += " (Inherited)"
case rs.Rarity == 2:
emoji = "🟡"
case rs.GroupRate == -1:
emoji = "🟣"
} }
buttons = append(buttons, b) b := discord.NewStringSelectMenuOption(name, strconv.Itoa(int(rs.ID))).WithEmoji(discord.NewComponentEmoji(emoji))
if rs.ID == skill.ID {
b = b.WithDefault(true)
} }
r.Components = append(r.Components, discord.NewActionRow(buttons...)) opts = append(opts, b)
}
row := discord.NewActionRow(discord.NewStringSelectMenu("/skill/swap", "Related skills", opts...))
r.Components = append(r.Components, row)
} }
return r return r
} }
@@ -132,19 +248,3 @@ func isDebuff(s horse.Skill) bool {
} }
return false return false
} }
var skillGlobalAuto = sync.OnceValue(func() *autocomplete.Set[discord.AutocompleteChoice] {
var set autocomplete.Set[discord.AutocompleteChoice]
for _, id := range global.OrderedSkills {
s := global.AllSkills[id]
set.Add(s.Name, discord.AutocompleteChoiceString{Name: s.Name, Value: s.Name})
if s.UniqueOwner != "" {
if s.Rarity >= 3 {
set.Add(s.UniqueOwner, discord.AutocompleteChoiceString{Name: "Unique: " + s.UniqueOwner, Value: s.Name})
} else {
set.Add(s.UniqueOwner, discord.AutocompleteChoiceString{Name: "Inherited unique: " + s.UniqueOwner, Value: s.Name})
}
}
}
return &set
})
+185
View File
@@ -0,0 +1,185 @@
package main
import (
"context"
"errors"
"fmt"
"image"
"image/png"
"log/slog"
"net/http"
"os"
"path/filepath"
"strconv"
"time"
"github.com/gen2brain/avif"
"golang.org/x/image/draw"
"golang.org/x/sync/errgroup"
"zombiezen.com/go/sqlite"
"zombiezen.com/go/sqlite/sqlitex"
)
func saveImgs(ctx context.Context, db *sqlitex.Pool, out string) {
skill, err := skillIconIDs(ctx, db)
if err != nil {
slog.Error("skills", slog.Any("err", err))
}
chara, err := charaIDs(ctx, db)
if err != nil {
slog.Error("characters", slog.Any("err", err))
}
rank, err := rankIDs(ctx, db)
if err != nil {
slog.Error("ranks", slog.Any("err", err))
}
supps, err := suppIDs(ctx, db)
if err != nil {
slog.Error("supports", slog.Any("err", err))
}
g, ctx := errgroup.WithContext(ctx)
tick := time.NewTicker(250 * time.Millisecond)
idouts(ctx, g, tick, skill, filepath.Join(out, "skill"), "https://media.gametora.com/umamusume/skills/icon/%d.png", image.Pt(64, 64), image.Pt(32, 32), image.Pt(16, 16))
idouts(ctx, g, tick, chara, filepath.Join(out, "chara"), "https://gametora.com/images/umamusume/characters/icons/chr_icon_%d.png", image.Pt(256, 256), image.Pt(64, 64), image.Pt(32, 32), image.Pt(16, 16))
idouts(ctx, g, tick, rank, filepath.Join(out, "rank"), "https://media.gametora.com/umamusume/ui/rank/%02d.png", image.Pt(128, 128), image.Pt(64, 64), image.Pt(32, 32), image.Pt(16, 16))
idouts(ctx, g, tick, rank, filepath.Join(out, "rank", "simple"), "https://media.gametora.com/umamusume/ui/rank/simple/%02d.png", image.Pt(50, 50), image.Pt(32, 32), image.Pt(16, 16))
idouts(ctx, g, tick, supps, filepath.Join(out, "support"), "https://gametora.com/images/umamusume/supports/support_card_s_%d.png", image.Pt(128, 128), image.Pt(64, 64), image.Pt(32, 32))
idouts(ctx, g, tick, []int32{0, 1, 2, 3, 4, 5, 6}, filepath.Join(out, "support", "type"), "https://gametora.com/images/umamusume/icons/utx_ico_obtain_%02d.png", image.Pt(70, 70), image.Pt(35, 35), image.Pt(16, 16))
if err := g.Wait(); err != nil {
slog.Error("output", slog.Any("err", err))
}
}
// load scans all results of sql and appends them to r.
func load[T any](ctx context.Context, db *sqlitex.Pool, r []T, sql string, row func(*sqlite.Stmt) T) ([]T, error) {
conn, err := db.Take(ctx)
defer db.Put(conn)
if err != nil {
return nil, err
}
stmt, err := conn.Prepare(sql)
if err != nil {
return nil, err
}
for {
ok, err := stmt.Step()
if err != nil {
return r, err
}
if !ok {
break
}
r = append(r, row(stmt))
}
return r, err
}
func skillIconIDs(ctx context.Context, db *sqlitex.Pool) ([]int32, error) {
return load(ctx, db, nil, "SELECT DISTINCT icon_id FROM skill_data", func(s *sqlite.Stmt) int32 {
return s.ColumnInt32(0)
})
}
func charaIDs(ctx context.Context, db *sqlitex.Pool) ([]int32, error) {
return load(ctx, db, nil, "SELECT id FROM chara_data", func(s *sqlite.Stmt) int32 {
return s.ColumnInt32(0)
})
}
func rankIDs(ctx context.Context, db *sqlitex.Pool) ([]int32, error) {
return load(ctx, db, nil, "SELECT id-1 FROM single_mode_rank", func(s *sqlite.Stmt) int32 {
return s.ColumnInt32(0)
})
}
func suppIDs(ctx context.Context, db *sqlitex.Pool) ([]int32, error) {
return load(ctx, db, nil, "SELECT id FROM support_card_data", func(s *sqlite.Stmt) int32 {
return s.ColumnInt32(0)
})
}
var pngenc = &png.Encoder{CompressionLevel: png.BestCompression}
func idouts(ctx context.Context, g *errgroup.Group, tick *time.Ticker, ids []int32, p, urlf string, sizes ...image.Point) {
for _, id := range ids {
g.Go(func() error {
n := strconv.Itoa(int(id))
p := filepath.Join(p, n)
_, err := os.Stat(p)
if !errors.Is(err, os.ErrNotExist) {
slog.DebugContext(ctx, "exists", slog.String("path", p))
return nil
}
if err := os.MkdirAll(p, 0777); err != nil {
return err
}
url := fmt.Sprintf(urlf, id)
select {
case <-ctx.Done():
return ctx.Err()
case <-tick.C: // continue on
}
img, err := dlimg(ctx, url)
if err != nil {
return err
}
slog.InfoContext(ctx, "resize", slog.String("url", url))
rs := resize(img, sizes...)
for _, r := range rs {
g.Go(func() error {
m := r.Bounds().Max
o := filepath.Join(p, fmt.Sprintf("%dx%d.png", m.X, m.Y))
slog.InfoContext(ctx, "write", slog.String("path", o))
f, err := os.Create(o)
if err != nil {
return err
}
defer f.Close()
return pngenc.Encode(f, r)
})
g.Go(func() error {
m := r.Bounds().Max
o := filepath.Join(p, fmt.Sprintf("%dx%d.avif", m.X, m.Y))
slog.InfoContext(ctx, "write", slog.String("path", o))
f, err := os.Create(o)
if err != nil {
return err
}
defer f.Close()
return avif.Encode(f, r)
})
}
return nil
})
}
}
func dlimg(ctx context.Context, url string) (image.Image, error) {
slog.InfoContext(ctx, "download", slog.String("url", url))
ctx, stop := context.WithTimeout(ctx, 5*time.Second)
defer stop()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
slog.InfoContext(ctx, "decode", slog.String("url", url))
img, _, err := image.Decode(resp.Body)
return img, err
}
func resize(img image.Image, sizes ...image.Point) []image.Image {
rs := make([]image.Image, 0, len(sizes))
for _, sz := range sizes {
d := image.NewNRGBA(image.Rectangle{Min: image.Point{}, Max: sz})
draw.CatmullRom.Scale(d, d.Bounds(), img, img.Bounds(), draw.Src, nil)
rs = append(rs, d)
}
return rs
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+126 -2
View File
@@ -13,6 +13,10 @@ This file is my notes from exploring the database.
- 33 is race names by race id, 28 is race names by race instance id, 31 is race courses, 111 is saddle names - 33 is race names by race id, 28 is race names by race instance id, 31 is race courses, 111 is saddle names
- 65 is player titles, 66 is title descriptions - ties with honor_data? - 65 is player titles, 66 is title descriptions - ties with honor_data?
- 119 is scenario full titles (e.g. The Beginning: URA Finale), 120 is scenario descriptions, 237 is scenario names (e.g. URA Finale) - 119 is scenario full titles (e.g. The Beginning: URA Finale), 120 is scenario descriptions, 237 is scenario names (e.g. URA Finale)
- 130 is epithet names, 131 is epithet descriptions
in the decomp, text data categories are named in `public enum MasterString.Category`.
some categories seem to be missing, though.
# succession factor (sparks) # succession factor (sparks)
@@ -192,12 +196,98 @@ target types:
- 22 specific character, target value is character id - 22 specific character, target value is character id
- 23 other who triggered the skill - 23 other who triggered the skill
TODO target types only in jp: 2, 7, 11, 22, 23
ability_value_usage can be 1 for plain or 2-6 for aoharu stat skill stat scaling ability_value_usage can be 1 for plain or 2-6 for aoharu stat skill stat scaling
seems to be activate_lot = 1 means wit check, 0 means guaranteed seems to be activate_lot = 1 means wit check, 0 means guaranteed
tag_id is a slash-separated list of numeric IDs that roughly describe all effects relevant to the skill.
- 101..104 style front, pace, late, end
- 201..204 distance sprint, mile, medium, long
- 301..303 timing early, mid, late race
- 401 affects speed stat or target speed
- 402 affects stamina stat or hp
- 403 affects power stat or acceleration
- 404 affects guts stat
- 405 affects wit stat or vision
- 406 is a debuff of any type, or is a main story self-debuff (creeping anxiety, blatant fear), or is Behold Thine Emperor's Divine Might (both own unique and inherited versions are tagged only 406??)
- 407 modifies gate delay
- 501..502 turf/dirt
- 801..812 scenario skill
600 series applies to skills available in global but are only used in jp.
- 601 ground condition passive
- 602 weather passive
- 603 season passive
- 604 race track passive
- 605 time of day passive
- 606 exchange race passive (kawasaki, funabashi, morioka, ooi)
- 607 distance passive (non/standard)
- 608 track direction or corner count passive
- 609 gate position passive
- 610 ground type passive
- 611 popularity passive
- 612 running style passive
- 613 passive depending on other horses' styles or skills
- 614 base stat threshold passive
- 615 mood passive
unrelated, query to get all condition types:
```sql
WITH RECURSIVE condition AS (
SELECT DISTINCT precondition_1 AS c FROM skill_data
UNION
SELECT DISTINCT condition_1 AS c FROM skill_data
UNION
SELECT DISTINCT precondition_2 AS c FROM skill_data
UNION
SELECT DISTINCT condition_2 AS c FROM skill_data
), disj AS (
SELECT
IIF(INSTR(c, '@') != 0, SUBSTR(c, 1, INSTR(c, '@')-1), c) AS c,
IIF(INSTR(c, '@') != 0, SUBSTR(c, 1 + INSTR(c, '@')), '') AS rest
FROM condition
UNION
SELECT
IIF(INSTR(rest, '@') != 0, SUBSTR(rest, 1, INSTR(rest, '@')-1), rest) AS c,
IIF(INSTR(rest, '@') != 0, SUBSTR(rest, 1 + INSTR(rest, '@')), '') AS rest
FROM disj
WHERE rest != ''
), term AS (
SELECT
IIF(INSTR(c, '&') != 0, SUBSTR(c, 1, INSTR(c, '&')-1), c) AS term,
IIF(INSTR(c, '&') != 0, SUBSTR(c, 1 + INSTR(c, '&')), '') AS rest
FROM disj
UNION
SELECT
IIF(INSTR(rest, '&') != 0, SUBSTR(rest, 1, INSTR(rest, '&')-1), rest) AS term,
IIF(INSTR(rest, '&') != 0, SUBSTR(rest, 1 + INSTR(rest, '&')), '') AS rest
FROM term
WHERE rest != ''
), op AS (
SELECT
term,
CASE
WHEN INSTR(term, '==') != 0 THEN '=='
WHEN INSTR(term, '!=') != 0 THEN '!='
WHEN INSTR(term, '<=') != 0 THEN '<='
WHEN INSTR(term, '>=') != 0 THEN '>='
WHEN INSTR(term, '<') != 0 THEN '<'
WHEN INSTR(term, '>') != 0 THEN '>'
END AS op
FROM term WHERE term != ''
), type_op_arg AS (
SELECT DISTINCT
term,
SUBSTR(term, 1, INSTR(term, op) - 1) AS "type",
op,
CAST(SUBSTR(term, INSTR(term, op) + LENGTH(op)) AS INTEGER) AS arg
FROM op
)
SELECT DISTINCT CONCAT(type, op), arg FROM type_op_arg
```
final select can be adjusted to get different info, but type+op together seems most useful
# races # races
- group 1, grade: g1 100, g2 200, g3 300, op 400, pre-op 700 - group 1, grade: g1 100, g2 200, g3 300, op 400, pre-op 700
@@ -222,6 +312,9 @@ grade_rate_id appears to be consistently 800 iff maiden race and 900 iff debut r
recommend_class_id appears to be consistently 1 iff maiden race or debut race, 2 iff pre-op, 3 iff op; but other values are confusing. recommend_class_id appears to be consistently 1 iff maiden race or debut race, 2 iff pre-op, 3 iff op; but other values are confusing.
so, it doesn't seem like there's a particular flag that identifies maiden races, despite the restrictions on when they appear in the ui. so, it doesn't seem like there's a particular flag that identifies maiden races, despite the restrictions on when they appear in the ui.
race_course_set defines the racecourse, e.g. hanshin 1600 turf, that a given race occurs on.
notably, stat thresholds are defined here via join to race_course_set_status, rather than in course event data.
# trainee definitions # trainee definitions
- card_data has universal trainee stats: base skill set, stat growth bonuses ("talent"), default running style - card_data has universal trainee stats: base skill set, stat growth bonuses ("talent"), default running style
@@ -230,6 +323,37 @@ so, it doesn't seem like there's a particular flag that identifies maiden races,
- card_talent_hint_upgrade has costs to raise hint levels, but it's actually universal, only six rows - card_talent_hint_upgrade has costs to raise hint levels, but it's actually universal, only six rows
- single_mode_route_race is career goals (not only races) - single_mode_route_race is career goals (not only races)
- available_skill_set has starting skills including those unlocked by potential level given under need_rank (0 for pl1, 2 for pl2) - available_skill_set has starting skills including those unlocked by potential level given under need_rank (0 for pl1, 2 for pl2)
- single_mode_special_chara is scenario links
# gl
single_mode_live_song_list has the song list for lives ha ha.
this is defined from the perspective of bonuses they grant when you have them, not the shop per se.
- text_data category for song names is 210, look up by live_id; for post-concert effect text, category 208 indexed by song list id
- command_id and live_id are always equal
- level is 1 for all songs except the two instances of girls' legend u
- master_bonus_content_text_id corresponds to text_data categories 207 for pickup effect text and 209 for song name
- live_bonus_type is 1 for specialty prio, 2 for chain frequency, 3 for fb
- girls' legend u is defined twice as ids 22 and 24
costs are in single_mode_live_square.
- square_type is 1 for stat up, 2 for skill up, 3 for energy up, 4 for live
- perf_type_n is token type: 1 for dance, 2 for passion, 3 for vocals, 4 for visuals, 5 for composure
- perf_value_n is number of tokens
immediate bonuses are in single_mode_live_master_bonus.
- master_bonus_type is 1 for stat up, 2 for skill hint, 3 for energy up, 4 for song
- master_bonus_type_value is 1 for type 1, live id for type 4, 0 otherwise
- master_bonus_gain_type_n is 1 for stat, 2 for skill hint, 3 for energy, 5 for permanent training bonus
+ 1 for stat: then master_bonus_gain_value_n_1 is 1-5 for stat id or 6 for sp, n_2 is 1 for fixed or 2 for random (not actually in the game), n_3 is lower bound on gain, n_4 is 0 for fixed or upper bound for random
+ 2 for skill hint: then gain_value_n_1 is 3(*), n_2 is skill tag, n_3 is hint level
+ 3 for energy: then gain_value_n_1 is amount
+ 5 for permanent training bonus: then gain_value_n_1 is stat id (6 for sp), n_2 is amount
(*) MasterSingleModeLiveMasterBonus.SingleModeLiveMasterBonus.GainSkillHintTypeEnum defines values of Random = 1 and SkillId = 2, but they are unused in the db.
there is some ostensibly unused data.
- master bonus ids 12001-12206 grant random amounts of stats
- id 20000 corresponds to "Group Lesson" "Skill hint appropriate for aptitude"
# lobby conversations!!! # lobby conversations!!!
-8
View File
@@ -1,8 +0,0 @@
#!/bin/sh
set -ex
go run ./horsegen "$@"
go generate ./horse/...
go fmt ./...
go test ./...
-13
View File
@@ -1,13 +0,0 @@
package main
import "os"
//go:generate go run ./horsegen
//go:generate go generate ./horse/...
//go:generate go fmt ./...
//go:generate go test ./...
func main() {
os.Stderr.WriteString("go generate, not go run\n")
os.Exit(2)
}
+19 -11
View File
@@ -3,30 +3,38 @@ module git.sunturtle.xyz/zephyr/horse
go 1.25.5 go 1.25.5
require ( require (
github.com/disgoorg/disgo v0.19.0-rc.15 github.com/disgoorg/disgo v0.19.6
github.com/junegunn/fzf v0.67.0 github.com/gen2brain/avif v0.6.0
golang.org/x/sync v0.14.0 github.com/go-chi/chi/v5 v5.3.1
github.com/google/go-cmp v0.7.0
github.com/junegunn/fzf v0.74.1
golang.org/x/image v0.44.0
golang.org/x/sync v0.22.0
zombiezen.com/go/sqlite v1.4.2 zombiezen.com/go/sqlite v1.4.2
) )
require ( require (
github.com/disgoorg/godave v0.3.0 // indirect
github.com/disgoorg/json/v2 v2.0.0 // indirect github.com/disgoorg/json/v2 v2.0.0 // indirect
github.com/disgoorg/omit v1.0.0 // indirect github.com/disgoorg/omit v1.0.0 // indirect
github.com/disgoorg/snowflake/v2 v2.0.3 // indirect github.com/disgoorg/snowflake/v2 v2.0.3 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.10.2 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect github.com/gorilla/websocket v1.5.3 // indirect
github.com/klauspost/compress v1.18.2 // indirect github.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/klauspost/compress v1.19.1 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect github.com/mattn/go-isatty v0.0.24 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad // indirect github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad // indirect
golang.org/x/crypto v0.46.0 // indirect github.com/tetratelabs/wazero v1.12.0 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect golang.org/x/crypto v0.54.0 // indirect
golang.org/x/sys v0.39.0 // indirect golang.org/x/sys v0.47.0 // indirect
modernc.org/libc v1.65.7 // indirect golang.org/x/tools v0.48.0 // indirect
modernc.org/libc v1.74.3 // indirect
modernc.org/mathutil v1.7.1 // indirect modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.37.1 // indirect modernc.org/sqlite v1.54.0 // indirect
) )
+56 -39
View File
@@ -1,7 +1,9 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disgoorg/disgo v0.19.0-rc.15 h1:x0NsV2gcbdjwuztsg2wYXw76p1Cpc8f6ByDrkPcfQtU= github.com/disgoorg/disgo v0.19.6 h1:1xJQt6KZgiqvBOnjuziG1gE4Iqd0aJn8DNfE6eE+0qw=
github.com/disgoorg/disgo v0.19.0-rc.15/go.mod h1:14mgXzenkJqifkDmsEgU0zI1di6jNXodwX6L8geW33A= github.com/disgoorg/disgo v0.19.6/go.mod h1:NnV63iw4lJdF1fnV0gX27XR43ZgRGqnL122svRMTgTE=
github.com/disgoorg/godave v0.3.0 h1:37F3ZuiMd8/EXeoCtTeE8iP2eT2nWsfEa4/ITwoKfe4=
github.com/disgoorg/godave v0.3.0/go.mod h1:OreAC3hpabr39bMVA+jwOVDq1EUPXH5A0XUBiZaDI1Y=
github.com/disgoorg/json/v2 v2.0.0 h1:U16yy/ARK7/aEpzjjqK1b/KaqqGHozUdeVw/DViEzQI= github.com/disgoorg/json/v2 v2.0.0 h1:U16yy/ARK7/aEpzjjqK1b/KaqqGHozUdeVw/DViEzQI=
github.com/disgoorg/json/v2 v2.0.0/go.mod h1:jZTBC0nIE1WeetSEI3/Dka8g+qglb4FPVmp5I5HpEfI= github.com/disgoorg/json/v2 v2.0.0/go.mod h1:jZTBC0nIE1WeetSEI3/Dka8g+qglb4FPVmp5I5HpEfI=
github.com/disgoorg/omit v1.0.0 h1:y0LkVUOyUHT8ZlnhIAeOZEA22UYykeysK8bLJ0SfT78= github.com/disgoorg/omit v1.0.0 h1:y0LkVUOyUHT8ZlnhIAeOZEA22UYykeysK8bLJ0SfT78=
@@ -10,20 +12,30 @@ github.com/disgoorg/snowflake/v2 v2.0.3 h1:3B+PpFjr7j4ad7oeJu4RlQ+nYOTadsKapJIzg
github.com/disgoorg/snowflake/v2 v2.0.3/go.mod h1:W6r7NUA7DwfZLwr00km6G4UnZ0zcoLBRufhkFWgAc4c= github.com/disgoorg/snowflake/v2 v2.0.3/go.mod h1:W6r7NUA7DwfZLwr00km6G4UnZ0zcoLBRufhkFWgAc4c=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/ebitengine/purego v0.10.2 h1:W809HbnvzAxgdm+aOvlSekrM16wGCdT/e76+9tS7gzE=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/ebitengine/purego v0.10.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/gen2brain/avif v0.6.0 h1:/8WSgcU+IEF0jhKYsUZ/mzlziFuTeJFpIKBj2siTQps=
github.com/gen2brain/avif v0.6.0/go.mod h1:QgrYqdVE9y40PCfArK9VakcMIpYeDYpZmCSLkW6C1n8=
github.com/go-chi/chi/v5 v5.3.1 h1:3j4HZLGZQ3JpMCrPJF/Jl3mYJfWLKBfNJ6quurUGCf8=
github.com/go-chi/chi/v5 v5.3.1/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/junegunn/fzf v0.67.0 h1:naiOdIkV5/ZCfHgKQIV/f5YDWowl95G6yyOQqW8FeSo= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/junegunn/fzf v0.67.0/go.mod h1:xlXX2/rmsccKQUnr9QOXPDi5DyV9cM0UjKy/huScBeE= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= github.com/junegunn/fzf v0.74.1 h1:rCGVdCSoOkfcfTxtxCO/vG2yceDjRlKSlv7IfCGkc/Y=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/junegunn/fzf v0.74.1/go.mod h1:hKDkO8orBWT5VLIZOL8oyhxn7mRLXdGyu+gz1Ss58pU=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741 h1:7dYDtfMDfKzjT+DVfIS4iqknSEKtZpEcXtu6vuaasHs=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741/go.mod h1:6EILKtGpo5t+KLb85LNZLAF6P9LKp78hJI80PXMcn3c=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
@@ -34,43 +46,48 @@ github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad h1:qIQkSlF5vAUHxE
github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad/go.mod h1:/pA7k3zsXKdjjAiUhB5CjuKib9KJGCaLvZwtxGC8U0s= github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad/go.mod h1:/pA7k3zsXKdjjAiUhB5CjuKib9KJGCaLvZwtxGC8U0s=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/image v0.44.0 h1:+tDekMZED9+LrtB3G5xzRggpVh9CARjZqROla3R3R+I=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/image v0.44.0/go.mod h1:V8K3KE9KKKE+pLpQDOeN18w9oacNSvy1tDOirTu4xtY=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s= modernc.org/cc/v4 v4.29.1 h1:MKgdCV3WykTSPqpVrnxdEDS0HEd2FHpKZDzxzU5LyeI=
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/cc/v4 v4.29.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU=
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE= modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk=
modernc.org/fileutil v1.3.1 h1:8vq5fe7jdtEvoCf3Zf9Nm0Q05sH6kGx0Op2CPx1wTC8= modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.3.1/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.65.7 h1:Ia9Z4yzZtWNtUIuiPuQ7Qf7kxYrxP1/jeHZzG8bFu00= modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI=
modernc.org/libc v1.65.7/go.mod h1:011EQibzzio/VX3ygj1qGFt5kMjP0lHb0qCW5/D/pQU= modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.74.3 h1:a4J+Z8aVaxPyjyxRAdJzw246PqpcFGvVPnfT/AuM5Ws=
modernc.org/libc v1.74.3/go.mod h1:4H7h/MJ8wnjL8RAbp9v3OXgnk22X7MouHIhDbvP3gj4=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.37.1 h1:EgHJK/FPoqC+q2YBXg7fUmES37pCHFc97sI7zSayBEs= modernc.org/sqlite v1.54.0 h1:JCxR4qwkJvOaqAoYcgDoO25Nc+ROg6EJ2LfBVzdrgog=
modernc.org/sqlite v1.37.1/go.mod h1:XwdRtsE1MpiBcL54+MbKcaDvcuej+IYSMfLN6gSKV8g= modernc.org/sqlite v1.54.0/go.mod h1:4ntCLuNmnH8+GNqjka1wNg7KJd5/Hi5FYp8K+XQ7GZw=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
-6
View File
@@ -1,6 +0,0 @@
# horse
This directory contains manually written code and types on which the generated code depends.
The generated code is in ./global; other regions will follow the same convention once they are supported.
It is always safe to delete the entire directories and regenerate them.
-12
View File
@@ -1,12 +0,0 @@
package horse
type CharacterID int16
type Character struct {
ID CharacterID
Name string
}
func (c Character) String() string {
return c.Name
}
-17
View File
@@ -1,17 +0,0 @@
module horse/character
import horse/game-id
pub struct character-detail
character-id: character-id
name: string
pub fun detail(
c: character-id,
?character/show: (character-id) -> string
): character-detail
Character-detail(c, c.show)
pub fun character-detail/show(d: character-detail): string
val Character-detail(Character-id(id), name) = d
name ++ " (ID " ++ id.show ++ ")"
-80
View File
@@ -1,80 +0,0 @@
module horse/game-id
// Game ID for characters, cards, skills, races, &c.
// Values for different categories may overlap.
pub 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.
pub struct scenario-id
game-id: game-id
// 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 uma-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
// Game ID for races,
// i.e. "Tenno Sho (Spring)" and not "Tenno Sho (Spring) at Kyoto Racecourse."
pub struct race-id
game-id: game-id
// Game ID for race thumbnails.
pub struct race-thumbnail-id
game-id: game-id
// Game ID for saddles,
// i.e. one or more race wins that appear as a title.
pub struct saddle-id
game-id: game-id
// Game ID for sparks,
// i.e. succession factors.
pub struct spark-id
game-id: game-id
// Game ID for spark groups,
// i.e. all rarities (star counts) of a single spark.
pub struct spark-group-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
// Construct an invalid game ID.
pub inline fun default/game-id(): game-id
0
-8
View File
@@ -1,8 +0,0 @@
module horse/global
import horse/game-id
// Shared saddle affinity bonus.
// `s` should be the complete list of all saddles shared between the veterans.
pub fun saddle-bonus(s: list<saddle-id>): int
s.length
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-2014
View File
File diff suppressed because it is too large Load Diff
-913
View File
@@ -1,913 +0,0 @@
module horse/global/race
// Automatically generated with horsegen; DO NOT EDIT
import std/core/delayed
import std/core/vector
import std/core-extras
import std/data/rb-map
import horse/game-id
pub import horse/race
extern create-id-table(): vector<int>
c inline "int32_t arr[] = {1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,1014,1015,1016,1017,1018,1019,1020,1021,1022,1023,1024,1025,1026,1027,1028,1101,1102,1103,1104,1105,1106,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024,2025,2026,2027,2028,2029,2030,2031,2032,2033,2034,2035,3001,3002,3003,3004,3005,3006,3007,3008,3009,3010,3011,3012,3013,3014,3015,3016,3017,3018,3019,3020,3021,3022,3023,3024,3025,3026,3027,3028,3029,3030,3031,3032,3033,3034,3035,3036,3037,3038,3039,3040,3041,3042,3043,3044,3045,3046,3047,3048,3049,3050,3051,3052,3053,3054,3055,3056,3057,3058,3059,3060,3061,3062,3063,3064,3065,3066,3067,3068,3069,3070,4001,4002,4003,4004,4005,4006,4007,4008,4009,4010,4011,4012,4013,4014,4015,4016,4017,4018,4019,4020,4021,4022,4023,4024,4025,4026,4027,4028,4030,4031,4032,4033,4035,4036,4037,4038,4039,4040,4041,4042,4043,4044,4045,4046,4047,4048,4049,4050,4051,4052,4053,4054,4055,4056,4057,4058,4059,4060,4061,4062,4063,4064,4065,4066,4068,4069,4070,4071,4072,4073,4074,4075,4076,4077,4078,4079,4080,4081,4082,4083,4084,4085,4086,4087,4088,4089,4090,4091,4092,4093,4094,4095,4096,4097,4098,4099,4100,4101,4102,4103,4104,4105,4106,4107,4108,4109,4110,4111,4112,4113,4114,4115,4116,4118,4119,4120,4121,4122,4123,4124,4501,4502,4503,4504,4505,4506,4507,4508,4509,4510,4511,4512,4513,4514,4515,4516,4517,4518,4519,4520,4521,4522,4523,4524,4525,4526,};\nkk_vector_from_cint32array(arr, (kk_ssize_t)285, kk_context())"
js inline "[1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,1014,1015,1016,1017,1018,1019,1020,1021,1022,1023,1024,1025,1026,1027,1028,1101,1102,1103,1104,1105,1106,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024,2025,2026,2027,2028,2029,2030,2031,2032,2033,2034,2035,3001,3002,3003,3004,3005,3006,3007,3008,3009,3010,3011,3012,3013,3014,3015,3016,3017,3018,3019,3020,3021,3022,3023,3024,3025,3026,3027,3028,3029,3030,3031,3032,3033,3034,3035,3036,3037,3038,3039,3040,3041,3042,3043,3044,3045,3046,3047,3048,3049,3050,3051,3052,3053,3054,3055,3056,3057,3058,3059,3060,3061,3062,3063,3064,3065,3066,3067,3068,3069,3070,4001,4002,4003,4004,4005,4006,4007,4008,4009,4010,4011,4012,4013,4014,4015,4016,4017,4018,4019,4020,4021,4022,4023,4024,4025,4026,4027,4028,4030,4031,4032,4033,4035,4036,4037,4038,4039,4040,4041,4042,4043,4044,4045,4046,4047,4048,4049,4050,4051,4052,4053,4054,4055,4056,4057,4058,4059,4060,4061,4062,4063,4064,4065,4066,4068,4069,4070,4071,4072,4073,4074,4075,4076,4077,4078,4079,4080,4081,4082,4083,4084,4085,4086,4087,4088,4089,4090,4091,4092,4093,4094,4095,4096,4097,4098,4099,4100,4101,4102,4103,4104,4105,4106,4107,4108,4109,4110,4111,4112,4113,4114,4115,4116,4118,4119,4120,4121,4122,4123,4124,4501,4502,4503,4504,4505,4506,4507,4508,4509,4510,4511,4512,4513,4514,4515,4516,4517,4518,4519,4520,4521,4522,4523,4524,4525,4526,]"
// Vector of all race IDs in order for easy iterating.
pub val all = once(create-id-table)
val name2id = once()
var m: rbmap<string, int> := empty()
all().foreach() fn(id) m := m.set(Race-id(id).show, id)
m
// Get the race ID that has the given exact name.
// Alternate versions of races have an indication of their ID in their names.
// If no race matches the name, the result is an invalid ID.
pub fun from-name(name: string): race-id
Race-id(name2id().rb-map/lookup(name).default(0))
// Get the name for a race.
// Alternate versions of races have an indication of their ID in their names.
// If no race matches the ID, the result is the numeric ID.
pub fun show(r: race-id): string
match r.game-id
1001 -> "February Stakes"
1002 -> "Takamatsunomiya Kinen"
1003 -> "Osaka Hai"
1004 -> "Oka Sho"
1005 -> "Satsuki Sho"
1006 -> "Tenno Sho (Spring)"
1007 -> "NHK Mile Cup"
1008 -> "Victoria Mile"
1009 -> "Japanese Oaks"
1010 -> "Tokyo Yushun (Japanese Derby)"
1011 -> "Yasuda Kinen"
1012 -> "Takarazuka Kinen"
1013 -> "Sprinters Stakes"
1014 -> "Shuka Sho"
1015 -> "Kikuka Sho"
1016 -> "Tenno Sho (Autumn)"
1017 -> "Queen Elizabeth II Cup"
1018 -> "Mile Championship"
1019 -> "Japan Cup"
1020 -> "Champions Cup"
1021 -> "Hanshin Juvenile Fillies"
1022 -> "Asahi Hai Futurity Stakes"
1023 -> "Arima Kinen"
1024 -> "Hopeful Stakes"
1025 -> "Takarazuka Kinen" ++ " (Alternate 1025)"
1026 -> "Kikuka Sho" ++ " (Alternate 1026)"
1027 -> "Tenno Sho (Spring)" ++ " (Alternate 1027)"
1028 -> "Satsuki Sho" ++ " (Alternate 1028)"
1101 -> "Teio Sho"
1102 -> "Japan Dirt Derby"
1103 -> "JBC Ladies Classic"
1104 -> "JBC Sprint"
1105 -> "JBC Classic"
1106 -> "Tokyo Daishoten"
2001 -> "Nikkei Shinshun Hai"
2002 -> "Tokai Stakes"
2003 -> "American JCC"
2004 -> "Kyoto Kinen"
2005 -> "Nakayama Kinen"
2006 -> "Yayoi Sho"
2007 -> "Kinko Sho"
2008 -> "Fillies' Revue"
2009 -> "Hanshin Daishoten"
2010 -> "Spring Stakes"
2011 -> "Nikkei Sho"
2012 -> "Hanshin Umamusume Stakes"
2013 -> "New Zealand Trophy"
2014 -> "Milers Cup"
2015 -> "Flora Stakes"
2016 -> "Aoba Sho"
2017 -> "Kyoto Shimbun Hai"
2018 -> "Keio Hai Spring Cup"
2019 -> "Meguro Kinen"
2020 -> "Sapporo Kinen"
2021 -> "Centaur Stakes"
2022 -> "Rose Stakes"
2023 -> "St. Lite Kinen"
2024 -> "Kobe Shimbun Hai"
2025 -> "All Comers"
2026 -> "Mainichi Okan"
2027 -> "Kyoto Daishoten"
2028 -> "Fuchu Umamusume Stakes"
2029 -> "Swan Stakes"
2030 -> "Keio Hai Junior Stakes"
2031 -> "Copa Republica Argentina"
2032 -> "Daily Hai Junior Stakes"
2033 -> "Stayers Stakes"
2034 -> "Hanshin Cup"
2035 -> "Spring Stakes" ++ " (Alternate 2035)"
3001 -> "Kyoto Kimpai"
3002 -> "Nakayama Kimpai"
3003 -> "Shinzan Kinen"
3004 -> "Fairy Stakes"
3005 -> "Aichi Hai"
3006 -> "Keisei Hai"
3007 -> "Silk Road Stakes"
3008 -> "Negishi Stakes"
3009 -> "Kisaragi Sho"
3010 -> "Tokyo Shimbun Hai"
3011 -> "Queen Cup"
3012 -> "Kyodo News Hai"
3013 -> "Kyoto Umamusume Stakes"
3014 -> "Diamond Stakes"
3015 -> "Kokura Daishoten"
3016 -> "Arlington Cup"
3017 -> "Hankyu Hai"
3018 -> "Tulip Sho"
3019 -> "Ocean Stakes"
3020 -> "Nakayama Umamusume Stakes"
3021 -> "Falcon Stakes"
3022 -> "Flower Cup"
3023 -> "Mainichi Hai"
3024 -> "March Stakes"
3025 -> "Lord Derby Challenge Trophy"
3026 -> "Antares Stakes"
3027 -> "Fukushima Umamusume Stakes"
3028 -> "Niigata Daishoten"
3029 -> "Heian Stakes"
3030 -> "Naruo Kinen"
3031 -> "Mermaid Stakes"
3032 -> "Epsom Cup"
3033 -> "Unicorn Stakes"
3034 -> "Hakodate Sprint Stakes"
3035 -> "CBC Sho"
3036 -> "Radio Nikkei Sho"
3037 -> "Procyon Stakes"
3038 -> "Tanabata Sho"
3039 -> "Hakodate Kinen"
3040 -> "Chukyo Kinen"
3041 -> "Hakodate Junior Stakes"
3042 -> "Ibis Summer Dash"
3043 -> "Queen Stakes"
3044 -> "Kokura Kinen"
3045 -> "Leopard Stakes"
3046 -> "Sekiya Kinen"
3047 -> "Elm Stakes"
3048 -> "Kitakyushu Kinen"
3049 -> "Niigata Junior Stakes"
3050 -> "Keeneland Cup"
3051 -> "Sapporo Junior Stakes"
3052 -> "Kokura Junior Stakes"
3053 -> "Niigata Kinen"
3054 -> "Shion Stakes"
3055 -> "Keisei Hai Autumn Handicap"
3056 -> "Sirius Stakes"
3057 -> "Saudi Arabia Royal Cup"
3058 -> "Fuji Stakes"
3059 -> "Artemis Stakes"
3060 -> "Fantasy Stakes"
3061 -> "Miyako Stakes"
3062 -> "Musashino Stakes"
3063 -> "Fukushima Kinen"
3064 -> "Tokyo Sports Hai Junior Stakes"
3065 -> "Kyoto Junior Stakes"
3066 -> "Keihan Hai"
3067 -> "Challenge Cup"
3068 -> "Chunichi Shimbun Hai"
3069 -> "Capella Stakes"
3070 -> "Turquoise Stakes"
4001 -> "Manyo Stakes"
4002 -> "Junior Cup"
4003 -> "Yodo Tankyori Stakes"
4004 -> "Pollux Stakes"
4005 -> "January Stakes"
4006 -> "New Year Stakes"
4007 -> "Kobai Stakes"
4008 -> "Subaru Stakes"
4009 -> "Wakagoma Stakes"
4010 -> "Carbuncle Stakes"
4011 -> "Shirafuji Stakes"
4012 -> "Crocus Stakes"
4013 -> "Yamato Stakes"
4014 -> "Elfin Stakes"
4015 -> "Rakuyo Stakes"
4016 -> "Aldebaran Stakes"
4017 -> "Valentine Stakes"
4018 -> "Hyacinth Stakes"
4019 -> "Sobu Stakes"
4020 -> "Sumire Stakes"
4021 -> "Osakajo Stakes"
4022 -> "Polaris Stakes"
4023 -> "Nigawa Stakes"
4024 -> "Anemone Stakes"
4025 -> "Shoryu Stakes"
4026 -> "Kochi Stakes"
4027 -> "Wakaba Stakes"
4028 -> "Chiba Stakes"
4030 -> "Rokko Stakes"
4031 -> "Coral Stakes"
4032 -> "Marguerite Stakes"
4033 -> "Fukuryu Stakes"
4035 -> "Wasurenagusa Sho"
4036 -> "Keiyo Stakes"
4037 -> "Shunrai Stakes"
4038 -> "Fukushima Mimpo Hai"
4039 -> "Tachibana Stakes"
4040 -> "Oasis Stakes"
4041 -> "Tennozan Stakes"
4042 -> "Tango Stakes"
4043 -> "Sweetpea Stakes"
4044 -> "Tanigawadake Stakes"
4045 -> "Principal Stakes"
4046 -> "Metropolitan Stakes"
4047 -> "Kurama Stakes"
4048 -> "Brilliant Stakes"
4049 -> "Miyakooji Stakes"
4050 -> "Aoi Stakes"
4051 -> "Ritto Stakes"
4052 -> "Seiryu Stakes"
4053 -> "May Stakes"
4054 -> "Hosu Stakes"
4055 -> "Idaten Stakes"
4056 -> "Shirayuri Stakes"
4057 -> "Keyaki Stakes"
4058 -> "Azuchijo Stakes"
4059 -> "Akhalteke Stakes"
4060 -> "Tempozan Stakes"
4061 -> "Yonago Stakes"
4062 -> "Onuma Stakes"
4063 -> "Paradise Stakes"
4064 -> "Tomoe Sho"
4065 -> "Marine Stakes"
4066 -> "Meitetsu Hai"
4068 -> "Chukyo Junior Stakes"
4069 -> "Fukushima TV Open"
4070 -> "Dahlia Sho"
4071 -> "Sapporo Nikkei Open"
4072 -> "UHB Sho"
4073 -> "Aso Stakes"
4074 -> "Phoenix Sho"
4075 -> "Cosmos Sho"
4076 -> "NST Sho"
4077 -> "Clover Sho"
4078 -> "Himawari Sho"
4079 -> "BSN Sho"
4080 -> "Kokura Nikkei Open"
4081 -> "Toki Stakes"
4082 -> "Tancho Stakes"
4083 -> "Suzuran Sho"
4084 -> "Enif Stakes"
4085 -> "Nojigiku Stakes"
4086 -> "Radio Nippon Sho"
4087 -> "Kikyo Stakes"
4088 -> "Fuyo Stakes"
4089 -> "Canna Stakes"
4090 -> "Port Island Stakes"
4091 -> "Opal Stakes"
4092 -> "Green Channel Cup"
4093 -> "Momiji Stakes"
4094 -> "October Stakes"
4095 -> "Shinetsu Stakes"
4096 -> "Ivy Stakes"
4097 -> "Muromachi Stakes"
4098 -> "Brazil Cup"
4099 -> "Hagi Stakes"
4100 -> "Cassiopeia Stakes"
4101 -> "Lumiere Autumn Dash"
4102 -> "Oro Cup"
4103 -> "Fukushima Junior Stakes"
4104 -> "Andromeda Stakes"
4105 -> "Shimotsuki Stakes"
4106 -> "Fukushima Minyu Cup"
4107 -> "Capital Stakes"
4108 -> "Autumn Leaf Stakes"
4109 -> "Lapis Lazuli Stakes"
4110 -> "Shiwasu Stakes"
4111 -> "Rigel Stakes"
4112 -> "Tanzanite Stakes"
4113 -> "December Stakes"
4114 -> "Christmas Rose Stakes"
4115 -> "Galaxy Stakes"
4116 -> "Betelgeuse Stakes"
4118 -> "Kitakyushu Tankyori Stakes"
4119 -> "Azumakofuji Stakes"
4120 -> "Sleipnir Stakes"
4121 -> "Sannomiya Stakes"
4122 -> "Kanetsu Stakes"
4123 -> "Nagatsuki Stakes"
4124 -> "Uzumasa Stakes"
4501 -> "Aster Sho"
4502 -> "Saffron Sho"
4503 -> "Rindo Sho"
4504 -> "Shigiku Sho"
4505 -> "Platanus Sho"
4506 -> "Nadeshiko Sho"
4507 -> "Hyakunichiso Tokubetsu"
4508 -> "Kimmokusei Tokubetsu"
4509 -> "Oxalis Sho"
4510 -> "Kigiku Sho"
4511 -> "Mochinoki Sho"
4512 -> "Akamatsu Sho"
4513 -> "Shumeigiku Sho"
4514 -> "Cattleya Sho"
4515 -> "Begonia Sho"
4516 -> "Shiragiku Sho"
4517 -> "Habotan Sho"
4518 -> "Koyamaki Sho"
4519 -> "Manryo Sho"
4520 -> "Kuromatsu Sho"
4521 -> "Erica Sho"
4522 -> "Tsuwabuki Sho"
4523 -> "Hiiragi Sho"
4524 -> "Sazanka Sho"
4525 -> "Kantsubaki Sho"
4526 -> "Senryo Sho"
x -> "race " ++ x.show
// Get the grade for a race.
// If no race matches the ID, the result is Pre-OP.
pub fun grade(r: race-id): grade
match r.game-id
1001 -> G1
1002 -> G1
1003 -> G1
1004 -> G1
1005 -> G1
1006 -> G1
1007 -> G1
1008 -> G1
1009 -> G1
1010 -> G1
1011 -> G1
1012 -> G1
1013 -> G1
1014 -> G1
1015 -> G1
1016 -> G1
1017 -> G1
1018 -> G1
1019 -> G1
1020 -> G1
1021 -> G1
1022 -> G1
1023 -> G1
1024 -> G1
1025 -> G1
1026 -> G1
1027 -> G1
1028 -> G1
1101 -> G1
1102 -> G1
1103 -> G1
1104 -> G1
1105 -> G1
1106 -> G1
2001 -> G2
2002 -> G2
2003 -> G2
2004 -> G2
2005 -> G2
2006 -> G2
2007 -> G2
2008 -> G2
2009 -> G2
2010 -> G2
2011 -> G2
2012 -> G2
2013 -> G2
2014 -> G2
2015 -> G2
2016 -> G2
2017 -> G2
2018 -> G2
2019 -> G2
2020 -> G2
2021 -> G2
2022 -> G2
2023 -> G2
2024 -> G2
2025 -> G2
2026 -> G2
2027 -> G2
2028 -> G2
2029 -> G2
2030 -> G2
2031 -> G2
2032 -> G2
2033 -> G2
2034 -> G2
2035 -> G2
3001 -> G3
3002 -> G3
3003 -> G3
3004 -> G3
3005 -> G3
3006 -> G3
3007 -> G3
3008 -> G3
3009 -> G3
3010 -> G3
3011 -> G3
3012 -> G3
3013 -> G3
3014 -> G3
3015 -> G3
3016 -> G3
3017 -> G3
3018 -> G2
3019 -> G3
3020 -> G3
3021 -> G3
3022 -> G3
3023 -> G3
3024 -> G3
3025 -> G3
3026 -> G3
3027 -> G3
3028 -> G3
3029 -> G3
3030 -> G3
3031 -> G3
3032 -> G3
3033 -> G3
3034 -> G3
3035 -> G3
3036 -> G3
3037 -> G3
3038 -> G3
3039 -> G3
3040 -> G3
3041 -> G3
3042 -> G3
3043 -> G3
3044 -> G3
3045 -> G3
3046 -> G3
3047 -> G3
3048 -> G3
3049 -> G3
3050 -> G3
3051 -> G3
3052 -> G3
3053 -> G3
3054 -> G3
3055 -> G3
3056 -> G3
3057 -> G3
3058 -> G2
3059 -> G3
3060 -> G3
3061 -> G3
3062 -> G3
3063 -> G3
3064 -> G3
3065 -> G3
3066 -> G3
3067 -> G3
3068 -> G3
3069 -> G3
3070 -> G3
4001 -> OP
4002 -> OP
4003 -> OP
4004 -> OP
4005 -> OP
4006 -> OP
4007 -> OP
4008 -> OP
4009 -> OP
4010 -> OP
4011 -> OP
4012 -> OP
4013 -> OP
4014 -> OP
4015 -> OP
4016 -> OP
4017 -> OP
4018 -> OP
4019 -> OP
4020 -> OP
4021 -> OP
4022 -> OP
4023 -> OP
4024 -> OP
4025 -> OP
4026 -> OP
4027 -> OP
4028 -> OP
4030 -> OP
4031 -> OP
4032 -> OP
4033 -> OP
4035 -> OP
4036 -> OP
4037 -> OP
4038 -> OP
4039 -> OP
4040 -> OP
4041 -> OP
4042 -> OP
4043 -> OP
4044 -> OP
4045 -> OP
4046 -> OP
4047 -> OP
4048 -> OP
4049 -> OP
4050 -> G3
4051 -> OP
4052 -> OP
4053 -> OP
4054 -> OP
4055 -> OP
4056 -> OP
4057 -> OP
4058 -> OP
4059 -> OP
4060 -> OP
4061 -> OP
4062 -> OP
4063 -> OP
4064 -> OP
4065 -> OP
4066 -> OP
4068 -> OP
4069 -> OP
4070 -> OP
4071 -> OP
4072 -> OP
4073 -> OP
4074 -> OP
4075 -> OP
4076 -> OP
4077 -> OP
4078 -> OP
4079 -> OP
4080 -> OP
4081 -> OP
4082 -> OP
4083 -> OP
4084 -> OP
4085 -> OP
4086 -> OP
4087 -> OP
4088 -> OP
4089 -> OP
4090 -> OP
4091 -> OP
4092 -> OP
4093 -> OP
4094 -> OP
4095 -> OP
4096 -> OP
4097 -> OP
4098 -> OP
4099 -> OP
4100 -> OP
4101 -> OP
4102 -> OP
4103 -> OP
4104 -> OP
4105 -> OP
4106 -> OP
4107 -> OP
4108 -> OP
4109 -> OP
4110 -> OP
4111 -> OP
4112 -> OP
4113 -> OP
4114 -> OP
4115 -> OP
4116 -> OP
4118 -> OP
4119 -> OP
4120 -> OP
4121 -> OP
4122 -> OP
4123 -> OP
4124 -> OP
4501 -> Pre-OP
4502 -> Pre-OP
4503 -> Pre-OP
4504 -> Pre-OP
4505 -> Pre-OP
4506 -> Pre-OP
4507 -> Pre-OP
4508 -> Pre-OP
4509 -> Pre-OP
4510 -> Pre-OP
4511 -> Pre-OP
4512 -> Pre-OP
4513 -> Pre-OP
4514 -> Pre-OP
4515 -> Pre-OP
4516 -> Pre-OP
4517 -> Pre-OP
4518 -> Pre-OP
4519 -> Pre-OP
4520 -> Pre-OP
4521 -> Pre-OP
4522 -> Pre-OP
4523 -> Pre-OP
4524 -> Pre-OP
4525 -> Pre-OP
4526 -> Pre-OP
_ -> Pre-OP
// Get the thumbnail ID for a race.
// If no race matches the ID, the result is an invalid ID.
pub fun thumbnail(r: race-id): race-thumbnail-id
match r.game-id
1001 -> Race-thumbnail-id(1001)
1002 -> Race-thumbnail-id(1002)
1003 -> Race-thumbnail-id(1003)
1004 -> Race-thumbnail-id(1004)
1005 -> Race-thumbnail-id(1005)
1006 -> Race-thumbnail-id(1006)
1007 -> Race-thumbnail-id(1007)
1008 -> Race-thumbnail-id(1008)
1009 -> Race-thumbnail-id(1009)
1010 -> Race-thumbnail-id(1010)
1011 -> Race-thumbnail-id(1011)
1012 -> Race-thumbnail-id(1012)
1013 -> Race-thumbnail-id(1013)
1014 -> Race-thumbnail-id(1014)
1015 -> Race-thumbnail-id(1015)
1016 -> Race-thumbnail-id(1016)
1017 -> Race-thumbnail-id(1017)
1018 -> Race-thumbnail-id(1018)
1019 -> Race-thumbnail-id(1019)
1020 -> Race-thumbnail-id(1020)
1021 -> Race-thumbnail-id(1021)
1022 -> Race-thumbnail-id(1022)
1023 -> Race-thumbnail-id(1023)
1024 -> Race-thumbnail-id(1024)
1025 -> Race-thumbnail-id(1012)
1026 -> Race-thumbnail-id(1015)
1027 -> Race-thumbnail-id(1027)
1028 -> Race-thumbnail-id(1028)
1101 -> Race-thumbnail-id(1101)
1102 -> Race-thumbnail-id(1102)
1103 -> Race-thumbnail-id(1103)
1104 -> Race-thumbnail-id(1104)
1105 -> Race-thumbnail-id(1105)
1106 -> Race-thumbnail-id(1106)
2001 -> Race-thumbnail-id(2001)
2002 -> Race-thumbnail-id(2002)
2003 -> Race-thumbnail-id(2003)
2004 -> Race-thumbnail-id(2004)
2005 -> Race-thumbnail-id(2005)
2006 -> Race-thumbnail-id(2006)
2007 -> Race-thumbnail-id(2007)
2008 -> Race-thumbnail-id(2008)
2009 -> Race-thumbnail-id(2009)
2010 -> Race-thumbnail-id(2010)
2011 -> Race-thumbnail-id(2011)
2012 -> Race-thumbnail-id(2012)
2013 -> Race-thumbnail-id(2013)
2014 -> Race-thumbnail-id(2014)
2015 -> Race-thumbnail-id(2015)
2016 -> Race-thumbnail-id(2016)
2017 -> Race-thumbnail-id(2017)
2018 -> Race-thumbnail-id(2018)
2019 -> Race-thumbnail-id(2019)
2020 -> Race-thumbnail-id(2020)
2021 -> Race-thumbnail-id(2021)
2022 -> Race-thumbnail-id(2022)
2023 -> Race-thumbnail-id(2023)
2024 -> Race-thumbnail-id(2024)
2025 -> Race-thumbnail-id(2025)
2026 -> Race-thumbnail-id(2026)
2027 -> Race-thumbnail-id(2027)
2028 -> Race-thumbnail-id(2028)
2029 -> Race-thumbnail-id(2029)
2030 -> Race-thumbnail-id(2030)
2031 -> Race-thumbnail-id(2031)
2032 -> Race-thumbnail-id(2032)
2033 -> Race-thumbnail-id(2033)
2034 -> Race-thumbnail-id(2034)
2035 -> Race-thumbnail-id(2010)
3001 -> Race-thumbnail-id(3001)
3002 -> Race-thumbnail-id(3002)
3003 -> Race-thumbnail-id(3003)
3004 -> Race-thumbnail-id(3004)
3005 -> Race-thumbnail-id(3005)
3006 -> Race-thumbnail-id(3006)
3007 -> Race-thumbnail-id(3007)
3008 -> Race-thumbnail-id(3008)
3009 -> Race-thumbnail-id(3009)
3010 -> Race-thumbnail-id(3010)
3011 -> Race-thumbnail-id(3011)
3012 -> Race-thumbnail-id(3012)
3013 -> Race-thumbnail-id(3013)
3014 -> Race-thumbnail-id(3014)
3015 -> Race-thumbnail-id(3015)
3016 -> Race-thumbnail-id(3016)
3017 -> Race-thumbnail-id(3017)
3018 -> Race-thumbnail-id(3018)
3019 -> Race-thumbnail-id(3019)
3020 -> Race-thumbnail-id(3020)
3021 -> Race-thumbnail-id(3021)
3022 -> Race-thumbnail-id(3022)
3023 -> Race-thumbnail-id(3023)
3024 -> Race-thumbnail-id(3024)
3025 -> Race-thumbnail-id(3025)
3026 -> Race-thumbnail-id(3026)
3027 -> Race-thumbnail-id(3027)
3028 -> Race-thumbnail-id(3028)
3029 -> Race-thumbnail-id(3029)
3030 -> Race-thumbnail-id(3030)
3031 -> Race-thumbnail-id(3031)
3032 -> Race-thumbnail-id(3032)
3033 -> Race-thumbnail-id(3033)
3034 -> Race-thumbnail-id(3034)
3035 -> Race-thumbnail-id(3035)
3036 -> Race-thumbnail-id(3036)
3037 -> Race-thumbnail-id(3037)
3038 -> Race-thumbnail-id(3038)
3039 -> Race-thumbnail-id(3039)
3040 -> Race-thumbnail-id(3040)
3041 -> Race-thumbnail-id(3041)
3042 -> Race-thumbnail-id(3042)
3043 -> Race-thumbnail-id(3043)
3044 -> Race-thumbnail-id(3044)
3045 -> Race-thumbnail-id(3045)
3046 -> Race-thumbnail-id(3046)
3047 -> Race-thumbnail-id(3047)
3048 -> Race-thumbnail-id(3048)
3049 -> Race-thumbnail-id(3049)
3050 -> Race-thumbnail-id(3050)
3051 -> Race-thumbnail-id(3051)
3052 -> Race-thumbnail-id(3052)
3053 -> Race-thumbnail-id(3053)
3054 -> Race-thumbnail-id(3054)
3055 -> Race-thumbnail-id(3055)
3056 -> Race-thumbnail-id(3056)
3057 -> Race-thumbnail-id(3057)
3058 -> Race-thumbnail-id(3058)
3059 -> Race-thumbnail-id(3059)
3060 -> Race-thumbnail-id(3060)
3061 -> Race-thumbnail-id(3061)
3062 -> Race-thumbnail-id(3062)
3063 -> Race-thumbnail-id(3063)
3064 -> Race-thumbnail-id(3064)
3065 -> Race-thumbnail-id(3065)
3066 -> Race-thumbnail-id(3066)
3067 -> Race-thumbnail-id(3067)
3068 -> Race-thumbnail-id(3068)
3069 -> Race-thumbnail-id(3069)
3070 -> Race-thumbnail-id(3070)
4001 -> Race-thumbnail-id(4001)
4002 -> Race-thumbnail-id(4002)
4003 -> Race-thumbnail-id(4003)
4004 -> Race-thumbnail-id(4004)
4005 -> Race-thumbnail-id(4005)
4006 -> Race-thumbnail-id(4006)
4007 -> Race-thumbnail-id(4007)
4008 -> Race-thumbnail-id(4008)
4009 -> Race-thumbnail-id(4009)
4010 -> Race-thumbnail-id(4010)
4011 -> Race-thumbnail-id(4011)
4012 -> Race-thumbnail-id(4012)
4013 -> Race-thumbnail-id(4013)
4014 -> Race-thumbnail-id(4014)
4015 -> Race-thumbnail-id(4015)
4016 -> Race-thumbnail-id(4016)
4017 -> Race-thumbnail-id(4017)
4018 -> Race-thumbnail-id(4018)
4019 -> Race-thumbnail-id(4019)
4020 -> Race-thumbnail-id(4020)
4021 -> Race-thumbnail-id(4021)
4022 -> Race-thumbnail-id(4022)
4023 -> Race-thumbnail-id(4023)
4024 -> Race-thumbnail-id(4024)
4025 -> Race-thumbnail-id(4025)
4026 -> Race-thumbnail-id(4026)
4027 -> Race-thumbnail-id(4027)
4028 -> Race-thumbnail-id(4028)
4030 -> Race-thumbnail-id(4030)
4031 -> Race-thumbnail-id(4031)
4032 -> Race-thumbnail-id(4032)
4033 -> Race-thumbnail-id(4033)
4035 -> Race-thumbnail-id(4035)
4036 -> Race-thumbnail-id(4036)
4037 -> Race-thumbnail-id(4037)
4038 -> Race-thumbnail-id(4038)
4039 -> Race-thumbnail-id(4039)
4040 -> Race-thumbnail-id(4040)
4041 -> Race-thumbnail-id(4041)
4042 -> Race-thumbnail-id(4042)
4043 -> Race-thumbnail-id(4043)
4044 -> Race-thumbnail-id(4044)
4045 -> Race-thumbnail-id(4045)
4046 -> Race-thumbnail-id(4046)
4047 -> Race-thumbnail-id(4047)
4048 -> Race-thumbnail-id(4048)
4049 -> Race-thumbnail-id(4049)
4050 -> Race-thumbnail-id(4050)
4051 -> Race-thumbnail-id(4051)
4052 -> Race-thumbnail-id(4052)
4053 -> Race-thumbnail-id(4053)
4054 -> Race-thumbnail-id(4054)
4055 -> Race-thumbnail-id(4055)
4056 -> Race-thumbnail-id(4056)
4057 -> Race-thumbnail-id(4057)
4058 -> Race-thumbnail-id(4058)
4059 -> Race-thumbnail-id(4059)
4060 -> Race-thumbnail-id(4060)
4061 -> Race-thumbnail-id(4061)
4062 -> Race-thumbnail-id(4062)
4063 -> Race-thumbnail-id(4063)
4064 -> Race-thumbnail-id(4064)
4065 -> Race-thumbnail-id(4065)
4066 -> Race-thumbnail-id(4066)
4068 -> Race-thumbnail-id(4068)
4069 -> Race-thumbnail-id(4069)
4070 -> Race-thumbnail-id(4070)
4071 -> Race-thumbnail-id(4071)
4072 -> Race-thumbnail-id(4072)
4073 -> Race-thumbnail-id(4073)
4074 -> Race-thumbnail-id(4074)
4075 -> Race-thumbnail-id(4075)
4076 -> Race-thumbnail-id(4076)
4077 -> Race-thumbnail-id(4077)
4078 -> Race-thumbnail-id(4078)
4079 -> Race-thumbnail-id(4079)
4080 -> Race-thumbnail-id(4080)
4081 -> Race-thumbnail-id(4081)
4082 -> Race-thumbnail-id(4082)
4083 -> Race-thumbnail-id(4083)
4084 -> Race-thumbnail-id(4084)
4085 -> Race-thumbnail-id(4085)
4086 -> Race-thumbnail-id(4086)
4087 -> Race-thumbnail-id(4087)
4088 -> Race-thumbnail-id(4088)
4089 -> Race-thumbnail-id(4089)
4090 -> Race-thumbnail-id(4090)
4091 -> Race-thumbnail-id(4091)
4092 -> Race-thumbnail-id(4092)
4093 -> Race-thumbnail-id(4093)
4094 -> Race-thumbnail-id(4094)
4095 -> Race-thumbnail-id(4095)
4096 -> Race-thumbnail-id(4096)
4097 -> Race-thumbnail-id(4097)
4098 -> Race-thumbnail-id(4098)
4099 -> Race-thumbnail-id(4099)
4100 -> Race-thumbnail-id(4100)
4101 -> Race-thumbnail-id(4101)
4102 -> Race-thumbnail-id(4102)
4103 -> Race-thumbnail-id(4103)
4104 -> Race-thumbnail-id(4104)
4105 -> Race-thumbnail-id(4105)
4106 -> Race-thumbnail-id(4106)
4107 -> Race-thumbnail-id(4107)
4108 -> Race-thumbnail-id(4108)
4109 -> Race-thumbnail-id(4109)
4110 -> Race-thumbnail-id(4110)
4111 -> Race-thumbnail-id(4111)
4112 -> Race-thumbnail-id(4112)
4113 -> Race-thumbnail-id(4113)
4114 -> Race-thumbnail-id(4114)
4115 -> Race-thumbnail-id(4115)
4116 -> Race-thumbnail-id(4116)
4118 -> Race-thumbnail-id(4117)
4119 -> Race-thumbnail-id(4118)
4120 -> Race-thumbnail-id(4119)
4121 -> Race-thumbnail-id(4120)
4122 -> Race-thumbnail-id(4121)
4123 -> Race-thumbnail-id(4122)
4124 -> Race-thumbnail-id(4123)
4501 -> Race-thumbnail-id(4501)
4502 -> Race-thumbnail-id(4502)
4503 -> Race-thumbnail-id(4503)
4504 -> Race-thumbnail-id(4504)
4505 -> Race-thumbnail-id(4505)
4506 -> Race-thumbnail-id(4506)
4507 -> Race-thumbnail-id(4507)
4508 -> Race-thumbnail-id(4508)
4509 -> Race-thumbnail-id(4509)
4510 -> Race-thumbnail-id(4510)
4511 -> Race-thumbnail-id(4511)
4512 -> Race-thumbnail-id(4512)
4513 -> Race-thumbnail-id(4513)
4514 -> Race-thumbnail-id(4514)
4515 -> Race-thumbnail-id(4515)
4516 -> Race-thumbnail-id(4516)
4517 -> Race-thumbnail-id(4517)
4518 -> Race-thumbnail-id(4518)
4519 -> Race-thumbnail-id(4519)
4520 -> Race-thumbnail-id(4520)
4521 -> Race-thumbnail-id(4521)
4522 -> Race-thumbnail-id(4522)
4523 -> Race-thumbnail-id(4523)
4524 -> Race-thumbnail-id(4524)
4525 -> Race-thumbnail-id(4525)
4526 -> Race-thumbnail-id(4526)
_ -> Race-thumbnail-id(0)
// Get the primary ID for a race.
// For races which are the primary version, or if no race matches the given ID,
// the result is the input.
pub fun primary(r: race-id): race-id
match r.game-id
1025 -> Race-id(1012)
1026 -> Race-id(1015)
1027 -> Race-id(1006)
1028 -> Race-id(1005)
2035 -> Race-id(2010)
_ -> r
File diff suppressed because it is too large Load Diff
-518
View File
@@ -1,518 +0,0 @@
module horse/global/saddle
// Automatically generated with horsegen; DO NOT EDIT
import std/core/delayed
import std/core/vector
import std/core-extras
import horse/game-id
pub import horse/race
pub import horse/global/race
extern create-id-table(): vector<int>
c inline "int32_t arr[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,};\nkk_vector_from_cint32array(arr, (kk_ssize_t)155, kk_context())"
js inline "[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,]"
// Vector of all saddle IDs in order for easy iterating.
pub val all = once(create-id-table)
// Get the name for a saddle.
// Alternate versions of saddles have an indication of their ID in their names.
// If no saddle matches the ID, the result contains the numeric ID.
pub fun show(s: saddle-id): string
match s.game-id
1 -> "Classic Triple Crown"
2 -> "Senior Autumn Triple Crown"
3 -> "Triple Tiara"
4 -> "Senior Spring Triple Crown"
5 -> "Tenno Sweep"
6 -> "Dual Grand Prix"
7 -> "Dual Miles"
8 -> "Dual Sprints"
9 -> "Dual Dirts"
10 -> "Arima Kinen"
11 -> "Japan C."
12 -> "Japanese Derby"
13 -> "Tenno Sho (Spring)"
14 -> "Takarazuka Kinen"
15 -> "Tenno Sho (Autumn)"
16 -> "Kikuka Sho"
17 -> "Osaka Hai"
18 -> "Satsuki Sho"
19 -> "Japanese Oaks"
20 -> "Takamatsunomiya Kinen"
21 -> "Yasuda Kinen"
22 -> "Sprinters S."
23 -> "Mile Ch."
24 -> "Oka Sho"
25 -> "Victoria Mile"
26 -> "Queen Elizabeth II Cup"
27 -> "NHK Mile C."
28 -> "Shuka Sho"
29 -> "Champions C."
30 -> "February S."
31 -> "JBC Classic"
32 -> "Tokyo Daishoten"
33 -> "Asahi Hai F.S."
34 -> "Hopeful S."
35 -> "Hanshin J.F."
36 -> "Teio Sho"
37 -> "JBC Sprint"
38 -> "J.D. Derby"
39 -> "JBC L. Classic"
40 -> "Nikkei Shinshun Hai"
41 -> "Tokai S."
42 -> "American JCC"
43 -> "Kyoto Kinen"
44 -> "Nakayama Kinen"
45 -> "Yayoi Sho"
46 -> "Kinko Sho"
47 -> "Fillies' Revue"
48 -> "Hanshin Daishoten"
49 -> "Spring S."
50 -> "Nikkei Sho"
51 -> "Hanshin Umamusume S."
52 -> "New Zealand T."
53 -> "Yomiuri Milers C."
54 -> "Flora S."
55 -> "Aoba Sho"
56 -> "Kyoto Shimbun Hai"
57 -> "Keio Hai Spring C."
58 -> "Meguro Kinen"
59 -> "Sapporo Kinen"
60 -> "Centaur S."
61 -> "Rose S."
62 -> "St. Lite Kinen"
63 -> "Kobe Shimbun Hai"
64 -> "All Comers"
65 -> "Mainichi Okan"
66 -> "Kyoto Daishoten"
67 -> "Fuchu Umamusume S."
68 -> "Swan S."
69 -> "Keio Hai Junior S."
70 -> "Copa Republica Argentina"
71 -> "Daily Hai Junior S."
72 -> "Stayers S."
73 -> "Hanshin C."
74 -> "Kyoto Kimpai"
75 -> "Nakayama Kimpai"
76 -> "Shinzan Kinen"
77 -> "Fairy S."
78 -> "Aichi Hai"
79 -> "Keisei Hai"
80 -> "Silk Road S."
81 -> "Negishi S."
82 -> "Kisaragi Sho"
83 -> "Tokyo Shimbun Hai"
84 -> "Queen C."
85 -> "Kyodo News Hai"
86 -> "Kyoto Umamusume S."
87 -> "Diamond S."
88 -> "Kokura Daishoten"
89 -> "Arlington C."
90 -> "Hankyu Hai"
91 -> "Tulip Sho"
92 -> "Ocean S."
93 -> "Nakayama Umamusume S."
94 -> "Falcon S."
95 -> "Flower C."
96 -> "Mainichi Hai"
97 -> "March S."
98 -> "Lord Derby C.T."
99 -> "Antares S."
100 -> "Fukushima Umamusume S."
101 -> "Niigata Daishoten"
102 -> "Heian S."
103 -> "Naruo Kinen"
104 -> "Mermaid S."
105 -> "Epsom C."
106 -> "Unicorn S."
107 -> "Hakodate Sprint S."
108 -> "CBC Sho"
109 -> "Radio Nikkei Sho"
110 -> "Procyon S."
111 -> "Tanabata Sho"
112 -> "Hakodate Kinen"
113 -> "Chukyo Kinen"
114 -> "Hakodate Junior S."
115 -> "Ibis Summer D."
116 -> "Queen S."
117 -> "Kokura Kinen"
118 -> "Leopard S."
119 -> "Sekiya Kinen"
120 -> "Elm S."
121 -> "Kitakyushu Kinen"
122 -> "Niigata Junior S."
123 -> "Keeneland C."
124 -> "Sapporo Junior S."
125 -> "Kokura Junior S."
126 -> "Niigata Kinen"
127 -> "Shion S."
128 -> "Keisei Hai A.H."
129 -> "Sirius S."
130 -> "Saudi Arabia R.C."
131 -> "Fuji S."
132 -> "Artemis S."
133 -> "Fantasy S."
134 -> "Miyako S."
135 -> "Musashino S."
136 -> "Fukushima Kinen"
137 -> "Tokyo Sports Hai Junior S."
138 -> "Kyoto Junior S."
139 -> "Keihan Hai"
140 -> "Challenge C."
141 -> "Chunichi Shimbun Hai"
142 -> "Capella S."
143 -> "Turquoise S."
144 -> "Classic Triple Crown" ++ " (Alternate 144)"
145 -> "Senior Spring Triple Crown" ++ " (Alternate 145)"
146 -> "Dual Grand Prix" ++ " (Alternate 146)"
147 -> "Takarazuka Kinen" ++ " (Alternate 147)"
148 -> "Kikuka Sho" ++ " (Alternate 148)"
149 -> "Spring S." ++ " (Alternate 149)"
150 -> "Aoi S."
151 -> "Senior Spring Triple Crown" ++ " (Alternate 151)"
152 -> "Tenno Sweep" ++ " (Alternate 152)"
153 -> "Tenno Sho (Spring)" ++ " (Alternate 153)"
154 -> "Classic Triple Crown" ++ " (Alternate 154)"
155 -> "Satsuki Sho" ++ " (Alternate 155)"
x -> "saddle " ++ x.show
// Get the list of races that entitle a horse to a saddle.
// If no saddle matches the ID, the result is the empty list.
pub fun races(s: saddle-id): list<race-id>
match s.game-id
1 -> [Race-id(100501), Race-id(101001), Race-id(101501), ]
2 -> [Race-id(101601), Race-id(101901), Race-id(102301), ]
3 -> [Race-id(100401), Race-id(100901), Race-id(101401), ]
4 -> [Race-id(100301), Race-id(100601), Race-id(101201), ]
5 -> [Race-id(100601), Race-id(101601), ]
6 -> [Race-id(101201), Race-id(102301), ]
7 -> [Race-id(101101), Race-id(101801), ]
8 -> [Race-id(101301), Race-id(100201), ]
9 -> [Race-id(100101), Race-id(102001), ]
10 -> [Race-id(102301), ]
11 -> [Race-id(101901), ]
12 -> [Race-id(101001), ]
13 -> [Race-id(100601), ]
14 -> [Race-id(101201), ]
15 -> [Race-id(101601), ]
16 -> [Race-id(101501), ]
17 -> [Race-id(100301), ]
18 -> [Race-id(100501), ]
19 -> [Race-id(100901), ]
20 -> [Race-id(100201), ]
21 -> [Race-id(101101), ]
22 -> [Race-id(101301), ]
23 -> [Race-id(101801), ]
24 -> [Race-id(100401), ]
25 -> [Race-id(100801), ]
26 -> [Race-id(101701), ]
27 -> [Race-id(100701), ]
28 -> [Race-id(101401), ]
29 -> [Race-id(102001), ]
30 -> [Race-id(100101), ]
31 -> [Race-id(110501), ]
32 -> [Race-id(110601), ]
33 -> [Race-id(102201), ]
34 -> [Race-id(102401), ]
35 -> [Race-id(102101), ]
36 -> [Race-id(110101), ]
37 -> [Race-id(110401), ]
38 -> [Race-id(110201), ]
39 -> [Race-id(110301), ]
40 -> [Race-id(200101), ]
41 -> [Race-id(200201), ]
42 -> [Race-id(200301), ]
43 -> [Race-id(200401), ]
44 -> [Race-id(200501), ]
45 -> [Race-id(200601), ]
46 -> [Race-id(200701), ]
47 -> [Race-id(200801), ]
48 -> [Race-id(200901), ]
49 -> [Race-id(201001), ]
50 -> [Race-id(201101), ]
51 -> [Race-id(201201), ]
52 -> [Race-id(201301), ]
53 -> [Race-id(201401), ]
54 -> [Race-id(201501), ]
55 -> [Race-id(201601), ]
56 -> [Race-id(201701), ]
57 -> [Race-id(201801), ]
58 -> [Race-id(201901), ]
59 -> [Race-id(202001), ]
60 -> [Race-id(202101), ]
61 -> [Race-id(202201), ]
62 -> [Race-id(202301), ]
63 -> [Race-id(202401), ]
64 -> [Race-id(202501), ]
65 -> [Race-id(202601), ]
66 -> [Race-id(202701), ]
67 -> [Race-id(202801), ]
68 -> [Race-id(202901), ]
69 -> [Race-id(203001), ]
70 -> [Race-id(203101), ]
71 -> [Race-id(203201), ]
72 -> [Race-id(203301), ]
73 -> [Race-id(203401), ]
74 -> [Race-id(300101), ]
75 -> [Race-id(300201), ]
76 -> [Race-id(300301), ]
77 -> [Race-id(300401), ]
78 -> [Race-id(300501), ]
79 -> [Race-id(300601), ]
80 -> [Race-id(300701), ]
81 -> [Race-id(300801), ]
82 -> [Race-id(300901), ]
83 -> [Race-id(301001), ]
84 -> [Race-id(301101), ]
85 -> [Race-id(301201), ]
86 -> [Race-id(301301), ]
87 -> [Race-id(301401), ]
88 -> [Race-id(301501), ]
89 -> [Race-id(301601), ]
90 -> [Race-id(301701), ]
91 -> [Race-id(301801), ]
92 -> [Race-id(301901), ]
93 -> [Race-id(302001), ]
94 -> [Race-id(302101), ]
95 -> [Race-id(302201), ]
96 -> [Race-id(302301), ]
97 -> [Race-id(302401), ]
98 -> [Race-id(302501), ]
99 -> [Race-id(302601), ]
100 -> [Race-id(302701), ]
101 -> [Race-id(302801), ]
102 -> [Race-id(302901), ]
103 -> [Race-id(303001), ]
104 -> [Race-id(303101), ]
105 -> [Race-id(303201), ]
106 -> [Race-id(303301), ]
107 -> [Race-id(303401), ]
108 -> [Race-id(303501), ]
109 -> [Race-id(303601), ]
110 -> [Race-id(303701), ]
111 -> [Race-id(303801), ]
112 -> [Race-id(303901), ]
113 -> [Race-id(304001), ]
114 -> [Race-id(304101), ]
115 -> [Race-id(304201), ]
116 -> [Race-id(304301), ]
117 -> [Race-id(304401), ]
118 -> [Race-id(304501), ]
119 -> [Race-id(304601), ]
120 -> [Race-id(304701), ]
121 -> [Race-id(304801), ]
122 -> [Race-id(304901), ]
123 -> [Race-id(305001), ]
124 -> [Race-id(305101), ]
125 -> [Race-id(305201), ]
126 -> [Race-id(305301), ]
127 -> [Race-id(305401), ]
128 -> [Race-id(305501), ]
129 -> [Race-id(305601), ]
130 -> [Race-id(305701), ]
131 -> [Race-id(305801), ]
132 -> [Race-id(305901), ]
133 -> [Race-id(306001), ]
134 -> [Race-id(306101), ]
135 -> [Race-id(306201), ]
136 -> [Race-id(306301), ]
137 -> [Race-id(306401), ]
138 -> [Race-id(306501), ]
139 -> [Race-id(306601), ]
140 -> [Race-id(306701), ]
141 -> [Race-id(306801), ]
142 -> [Race-id(306901), ]
143 -> [Race-id(307001), ]
144 -> [Race-id(100501), Race-id(101001), Race-id(102601), ]
145 -> [Race-id(100301), Race-id(100601), Race-id(102501), ]
146 -> [Race-id(102501), Race-id(102301), ]
147 -> [Race-id(102501), ]
148 -> [Race-id(102601), ]
149 -> [Race-id(203501), ]
150 -> [Race-id(405001), ]
151 -> [Race-id(100301), Race-id(102701), Race-id(101201), ]
152 -> [Race-id(102701), Race-id(101601), ]
153 -> [Race-id(102701), ]
154 -> [Race-id(102801), Race-id(101001), Race-id(101501), ]
155 -> [Race-id(102801), ]
_ -> []
// Get a saddle's type.
// If no saddle matches the ID, the result is Honor.
pub fun saddle-type(s: saddle-id): saddle-type
match s.game-id
1 -> Honor
2 -> Honor
3 -> Honor
4 -> Honor
5 -> Honor
6 -> Honor
7 -> Honor
8 -> Honor
9 -> Honor
10 -> G1-Win
11 -> G1-Win
12 -> G1-Win
13 -> G1-Win
14 -> G1-Win
15 -> G1-Win
16 -> G1-Win
17 -> G1-Win
18 -> G1-Win
19 -> G1-Win
20 -> G1-Win
21 -> G1-Win
22 -> G1-Win
23 -> G1-Win
24 -> G1-Win
25 -> G1-Win
26 -> G1-Win
27 -> G1-Win
28 -> G1-Win
29 -> G1-Win
30 -> G1-Win
31 -> G1-Win
32 -> G1-Win
33 -> G1-Win
34 -> G1-Win
35 -> G1-Win
36 -> G1-Win
37 -> G1-Win
38 -> G1-Win
39 -> G1-Win
40 -> G2-Win
41 -> G2-Win
42 -> G2-Win
43 -> G2-Win
44 -> G2-Win
45 -> G2-Win
46 -> G2-Win
47 -> G2-Win
48 -> G2-Win
49 -> G2-Win
50 -> G2-Win
51 -> G2-Win
52 -> G2-Win
53 -> G2-Win
54 -> G2-Win
55 -> G2-Win
56 -> G2-Win
57 -> G2-Win
58 -> G2-Win
59 -> G2-Win
60 -> G2-Win
61 -> G2-Win
62 -> G2-Win
63 -> G2-Win
64 -> G2-Win
65 -> G2-Win
66 -> G2-Win
67 -> G2-Win
68 -> G2-Win
69 -> G2-Win
70 -> G2-Win
71 -> G2-Win
72 -> G2-Win
73 -> G2-Win
74 -> G3-Win
75 -> G3-Win
76 -> G3-Win
77 -> G3-Win
78 -> G3-Win
79 -> G3-Win
80 -> G3-Win
81 -> G3-Win
82 -> G3-Win
83 -> G3-Win
84 -> G3-Win
85 -> G3-Win
86 -> G3-Win
87 -> G3-Win
88 -> G3-Win
89 -> G3-Win
90 -> G3-Win
91 -> G2-Win
92 -> G3-Win
93 -> G3-Win
94 -> G3-Win
95 -> G3-Win
96 -> G3-Win
97 -> G3-Win
98 -> G3-Win
99 -> G3-Win
100 -> G3-Win
101 -> G3-Win
102 -> G3-Win
103 -> G3-Win
104 -> G3-Win
105 -> G3-Win
106 -> G3-Win
107 -> G3-Win
108 -> G3-Win
109 -> G3-Win
110 -> G3-Win
111 -> G3-Win
112 -> G3-Win
113 -> G3-Win
114 -> G3-Win
115 -> G3-Win
116 -> G3-Win
117 -> G3-Win
118 -> G3-Win
119 -> G3-Win
120 -> G3-Win
121 -> G3-Win
122 -> G3-Win
123 -> G3-Win
124 -> G3-Win
125 -> G3-Win
126 -> G3-Win
127 -> G3-Win
128 -> G3-Win
129 -> G3-Win
130 -> G3-Win
131 -> G2-Win
132 -> G3-Win
133 -> G3-Win
134 -> G3-Win
135 -> G3-Win
136 -> G3-Win
137 -> G3-Win
138 -> G3-Win
139 -> G3-Win
140 -> G3-Win
141 -> G3-Win
142 -> G3-Win
143 -> G3-Win
144 -> Honor
145 -> Honor
146 -> Honor
147 -> G1-Win
148 -> G1-Win
149 -> G2-Win
150 -> G3-Win
151 -> Honor
152 -> Honor
153 -> G1-Win
154 -> Honor
155 -> G1-Win
_ -> Honor
// Get the primary ID for a saddle.
// For saddles which are the primary version, or if no saddle matches the given ID,
// the result is the input.
pub fun primary(s: saddle-id): saddle-id
match s.game-id
144 -> Saddle-id(1)
145 -> Saddle-id(4)
146 -> Saddle-id(6)
147 -> Saddle-id(14)
148 -> Saddle-id(16)
149 -> Saddle-id(49)
151 -> Saddle-id(4)
152 -> Saddle-id(5)
153 -> Saddle-id(13)
154 -> Saddle-id(1)
155 -> Saddle-id(18)
_ -> s
-23
View File
@@ -1,23 +0,0 @@
package global
// Automatically generated with horsegen; DO NOT EDIT
import . "git.sunturtle.xyz/zephyr/horse/horse"
const (
ScenarioURAFinale ScenarioID = 1 // URA Finale
ScenarioUnityCup ScenarioID = 2 // Unity Cup
)
var AllScenarios = map[ScenarioID]Scenario{
ScenarioURAFinale: {
ID: 1,
Name: "URA Finale",
Title: "The Beginning: URA Finale",
},
ScenarioUnityCup: {
ID: 2,
Name: "Unity Cup",
Title: "Unity Cup: Shine On, Team Spirit!",
},
}
-30
View File
@@ -1,30 +0,0 @@
module horse/global/scenario
// Automatically generated with horsegen; DO NOT EDIT
import std/core/delayed
import std/core/vector
import std/core-extras
import horse/game-id
extern create-id-table(): vector<int>
c inline "int32_t arr[] = {1,2,};\nkk_vector_from_cint32array(arr, (kk_ssize_t)2, kk_context())"
js inline "[1,2,]"
// Vector of all scenario IDs in order for easy iterating.
pub val all = once(create-id-table)
// Get the name for a scenario.
// If no scenario matches the ID, the result contains the numeric ID.
pub fun show(s: scenario-id): string
match s.game-id
1 -> "URA Finale"
2 -> "Unity Cup"
x -> "scenario " ++ x.show
// Get the full title for a scenario, e.g. "The Beginning: URA Finale".
// If no scenario matches the ID, the result contains the numeric ID.
pub fun title(s: scenario-id): string
match s.game-id
1 -> "The Beginning: URA Finale"
2 -> "Unity Cup: Shine On, Team Spirit!"
x -> "scenario " ++ x.show
-13794
View File
File diff suppressed because it is too large Load Diff
-14417
View File
File diff suppressed because it is too large Load Diff
-13268
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
-1711
View File
File diff suppressed because it is too large Load Diff
-1571
View File
File diff suppressed because it is too large Load Diff
-124
View File
@@ -1,124 +0,0 @@
module horse/legacy
import std/num/decimal
import std/data/linearmap
import std/data/linearset
import horse/game-id
import horse/spark
import horse/prob/dist
// A legacy, or parent and grandparents.
pub struct legacy
uma: veteran
sub1: veteran
sub2: veteran
// A veteran, or the result of a completed career.
pub struct veteran
uma: uma-id
sparks: list<spark-id>
saddles: list<saddle-id>
// Get all saddles shared between two lists thereof.
pub fun shared-saddles(a: list<saddle-id>, b: list<saddle-id>): list<saddle-id>
val sa: linearSet<saddle-id> = a.foldl(linear-set(Nil)) fn(s, id) if id.is-valid then s.add(id) else s
val c: linearSet<saddle-id> = b.foldl(linear-set(Nil)) fn(s, id) if sa.member(id) then s.add(id) else s
c.list
// Get the individual affinity for a legacy.
// Any invalid ID is treated as giving 0.
pub fun parent-affinity(
trainee: uma-id,
legacy: legacy,
other-parent: uma-id,
?character-id: (uma-id) -> character-id,
?saddle-bonus: (list<saddle-id>) -> int,
?pair-affinity: (a: character-id, b: character-id) -> int,
?trio-affinity: (a: character-id, b: character-id, c: character-id) -> int
): int
val t = trainee.character-id
val p1 = legacy.uma.uma.character-id
val s1 = legacy.sub1.uma.character-id
val s2 = legacy.sub2.uma.character-id
val p2 = other-parent.character-id
pair-affinity(t, p1) + pair-affinity(p1, p2)
+ trio-affinity(t, p1, s1) + trio-affinity(t, p1, s2)
+ saddle-bonus(shared-saddles(legacy.uma.saddles, legacy.sub1.saddles)) + saddle-bonus(shared-saddles(legacy.uma.saddles, legacy.sub2.saddles))
// Get the individual affinities for a legacy's sub-legacies.
// The first value is the legacy for the `legacy.sub1` and the second is for
// `legacy.sub2`.
// Any invalid ID is treated as giving 0.
pub fun sub-affinity(
trainee: uma-id,
legacy: legacy,
?character-id: (uma-id) -> character-id,
?saddle-bonus: (list<saddle-id>) -> int,
?trio-affinity: (a: character-id, b: character-id, c: character-id) -> int
): (int, int)
val t = trainee.character-id
val p = legacy.uma.uma.character-id
val s1 = legacy.sub1.uma.character-id
val s2 = legacy.sub2.uma.character-id
val r1 = trio-affinity(t, p, s1) + saddle-bonus(shared-saddles(legacy.uma.saddles, legacy.sub1.saddles))
val r2 = trio-affinity(t, p, s2) + saddle-bonus(shared-saddles(legacy.uma.saddles, legacy.sub2.saddles))
(r1, r2)
// Associate each spark with its actual chance to activate given an individual
// affinity value and the possible effects when it does.
pub fun uma/inspiration(l: list<spark-id>, affinity: int, ?spark-type: (spark-id) -> spark-type, ?rarity: (spark-id) -> rarity, ?effects: (spark-id) -> list<list<spark-effect>>): list<(spark-id, decimal, list<list<spark-effect>>)>
val a = decimal(1 + affinity, -2)
l.map() fn(id) (id, min(id.base-proc * a, 1.decimal), id.effects)
// Get the complete list of effects that may occur in an inspiration event
// and the respective probability of activation.
// Duplicates, i.e. multiple veterans with the same spark, are preserved.
pub fun inspiration(
trainee: uma-id,
parent1: legacy,
parent2: legacy,
?character-id: (uma-id) -> character-id,
?saddle-bonus: (list<saddle-id>) -> int,
?pair-affinity: (a: character-id, b: character-id) -> int,
?trio-affinity: (a: character-id, b: character-id, c: character-id) -> int,
?spark-type: (spark-id) -> spark-type,
?rarity: (spark-id) -> rarity,
?effects: (spark-id) -> list<list<spark-effect>>
): list<(spark-id, decimal, list<list<spark-effect>>)>
val p1a = parent-affinity(trainee, parent1, parent2.uma.uma)
val p2a = parent-affinity(trainee, parent2, parent1.uma.uma)
val (s11a, s12a) = sub-affinity(trainee, parent1)
val (s21a, s22a) = sub-affinity(trainee, parent2)
[
inspiration(parent1.uma.sparks, p1a),
inspiration(parent1.sub1.sparks, s11a),
inspiration(parent1.sub2.sparks, s12a),
inspiration(parent2.uma.sparks, p2a),
inspiration(parent2.sub1.sparks, s21a),
inspiration(parent2.sub2.sparks, s22a),
].concat
// Reduce a spark effect list to the skill it is able to give.
pub fun skills(l: list<list<spark-effect>>): maybe<skill-id>
val r: linearSet<skill-id> = l.head(Nil).foldl(linear-set(Nil)) fn(s, eff)
match eff
Skill-Hint(id, _) -> s + id
_ -> s
r.list.head
// Reduce a spark effect list to the aptitude it is able to give.
pub fun aptitudes(l: list<list<spark-effect>>): maybe<aptitude>
val r: linearSet<aptitude> = l.head(Nil).foldl(linear-set(Nil)) fn(s, eff)
match eff
Aptitude-Up(apt) -> s + apt
_ -> s
r.list.head
// Get the overall chance of each count of sparks, including zero, providing a
// given type of effect activating in a single inspiration event.
pub fun inspiration-gives(l: list<(spark-id, decimal, list<list<spark-effect>>)>, f: (list<list<spark-effect>>) -> maybe<a>, ?a/(==): (a, a) -> bool): linearMap<a, list<decimal>>
val m: linearMap<_, list<decimal>> = l.foldl(LinearMap(Nil)) fn(m, (_, p, eff))
match f(eff)
Nothing -> m
Just(a) -> m.map/update(a, [p]) fn(cur, pp) pp.append(cur)
m.map() fn(_, v) poisson-binomial(v)
-134
View File
@@ -1,134 +0,0 @@
module horse/movement
// Surface types.
pub type surface
Turf
Dirt
// Automatically generated.
// Shows a string representation of the `surface` type.
pub fun surface/show(this : surface) : e string
match this
Turf -> "Turf"
Dirt -> "Dirt"
// Race distance types.
pub type distance
Sprint
Mile
Medium
Long
// Automatically generated.
// Shows a string representation of the `distance` type.
pub fun distance/show(this : distance) : e string
match this
Sprint -> "Sprint"
Mile -> "Mile"
Medium -> "Medium"
Long -> "Long"
// 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"
// Aptitude levels.
pub type aptitude-level
G
F
E
D
C
B
A
S
// Get the integer value for an aptitude level, starting at G -> 1.
pub fun aptitude-level/int(l: aptitude-level): int
match l
G -> 1
F -> 2
E -> 3
D -> 4
C -> 5
B -> 6
A -> 7
S -> 8
// Get the aptitude level corresponding to an integer, starting at 1 -> G.
pub fun int/aptitude-level(l: int): maybe<aptitude-level>
match l
1 -> Just(G)
2 -> Just(F)
3 -> Just(E)
4 -> Just(D)
5 -> Just(C)
6 -> Just(B)
7 -> Just(A)
8 -> Just(S)
_ -> Nothing
// Comparison of the `aptitude-level` type.
pub fun aptitude-level/cmp(this : aptitude-level, other : aptitude-level) : e order
cmp(this.int, other.int)
// Automatically generated.
// Fip comparison of the `aptitude-level` type.
pub fun aptitude-level/order2(this : aptitude-level, other : aptitude-level) : order2<aptitude-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 `aptitude-level` type.
pub fun aptitude-level/show(this : aptitude-level) : string
match this
G -> "G"
F -> "F"
E -> "E"
D -> "D"
C -> "C"
B -> "B"
A -> "A"
S -> "S"
-21
View File
@@ -1,21 +0,0 @@
module horse/prob/dist
import std/num/decimal
tail fun pb-step(pn: list<decimal>, pi: decimal, pmfkm1: decimal, pmf: list<decimal>, next: ctx<list<decimal>>): list<decimal>
match pn
Nil -> next ++. Nil // final step overall
Cons(_, pp) -> match pmf
Cons(pmfk, pmf') ->
val next' = next ++ ctx Cons(pi * pmfkm1 + (1.decimal - pi) * pmfk, hole)
pb-step(pp, pi, pmfk, pmf', next')
Nil -> next ++. Cons(pi * pmfkm1, Nil) // last step of this iteration
// Given `n` different Bernoulli processes with respective probabilities in `pn`,
// find the distribution of `k` successes for `k` ranging from 0 to `n` inclusive.
// The index in the result list corresponds to `k`.
pub fun pmf/poisson-binomial(pn: list<decimal>): list<decimal>
pn.foldl([1.decimal]) fn(pmf, pi)
match pmf
Cons(pmf0, pmf') -> pb-step(pn, pi, pmf0, pmf', ctx Cons((1.decimal - pi) * pmf0, hole))
Nil -> impossible("fold started with non-empty pmf but got empty pmf")
-158
View File
@@ -1,158 +0,0 @@
module horse/prob/kfl
// kfl is a semiring of probabilities formed by vibes.
pub type kfl
// Effectively if not literally impossible events.
Impossible
// Not worth aiming for, but can technically still happen.
Probably-Not
// You expect it not to happen most of the time, but it might still be worth
// trying for it if you're being forced to play to your outs.
Doubtful
// More likely that it won't happen, but a success isn't surprising.
Unlikely
// Either it does or it doesn't.
Mayhapsibly
// Decent chance it doesn't happen, but you still expect it to.
Probably
// You expect it to happen most of the time, but accept that there will be failures.
Most-Likely
// Very close to guaranteed, but technically with a small chance to fail.
Cry-If-Not
// Absolutely guaranteed events.
Guaranteed
// Automatically generated.
// Comparison of the `kfl` type.
pub fun cmp(this : kfl, other : kfl) : e order
match (this, other)
(Impossible, Impossible) -> Eq
(Impossible, _) -> Lt
(_, Impossible) -> Gt
(Probably-Not, Probably-Not) -> Eq
(Probably-Not, _) -> Lt
(_, Probably-Not) -> Gt
(Doubtful, Doubtful) -> Eq
(Doubtful, _) -> Lt
(_, Doubtful) -> Gt
(Unlikely, Unlikely) -> Eq
(Unlikely, _) -> Lt
(_, Unlikely) -> Gt
(Mayhapsibly, Mayhapsibly) -> Eq
(Mayhapsibly, _) -> Lt
(_, Mayhapsibly) -> Gt
(Probably, Probably) -> Eq
(Probably, _) -> Lt
(_, Probably) -> Gt
(Most-Likely, Most-Likely) -> Eq
(Most-Likely, _) -> Lt
(_, Most-Likely) -> Gt
(Cry-If-Not, Cry-If-Not) -> Eq
(Cry-If-Not, _) -> Lt
(_, Cry-If-Not) -> Gt
(Guaranteed, Guaranteed) -> Eq
// Shows a string representation of the `kfl` type.
pub fun show(this : kfl) : e string
match this
Impossible -> "impossible"
Probably-Not -> "probably not"
Doubtful -> "doubtful"
Unlikely -> "unlikely"
Mayhapsibly -> "mayhapsibly"
Probably -> "probably"
Most-Likely -> "most likely"
Cry-If-Not -> "cry if not"
Guaranteed -> "guaranteed"
// KFL multiplication, or the probability of cooccurrence of two independent events.
pub fun (*)(a: kfl, b: kfl): e kfl
val (l, h) = match a.cmp(b) // this operation is commutative
Gt -> (b, a)
_ -> (a, b)
match (l, h)
(r, Guaranteed) -> r // factor out Guaranteed cases
(Impossible, _) -> Impossible
(Probably-Not, _) -> Impossible
(r, Cry-If-Not) -> r // factor out further Cry-If-Not cases
(Doubtful, Most-Likely) -> Probably-Not
(Doubtful, _) -> Impossible
(Unlikely, Most-Likely) -> Doubtful
(Unlikely, Probably) -> Doubtful
(Unlikely, Mayhapsibly) -> Probably-Not
(Unlikely, _) -> Probably-Not // (Unlikely, Unlikely) because commutative
(Mayhapsibly, Most-Likely) -> Unlikely
(Mayhapsibly, Probably) -> Unlikely
(Mayhapsibly, _) -> Unlikely
(Probably, Most-Likely) -> Mayhapsibly
(Probably, _) -> Unlikely
(Most-Likely, _) -> Probably
// These two are only needed because the type system doesn't understand commutativity.
(Cry-If-Not, _) -> Cry-If-Not
(Guaranteed, _) -> Guaranteed
// KFL addition, or the probability of occurrence of at least one of two independent events.
pub fun (+)(a: kfl, b: kfl): e kfl
val (l, h) = match a.cmp(b) // this operation is commutative
Gt -> (b, a)
_ -> (a, b)
match (l, h)
// Cases with _ on the right are (a, a) due to commutativity.
// Cases with _ on the left simplify later cases that all absorb to the right.
(Guaranteed, _) -> Guaranteed
(_, Guaranteed) -> Guaranteed
(Cry-If-Not, _) -> Guaranteed
(Most-Likely, Cry-If-Not) -> Cry-If-Not
(Most-Likely, _) -> Cry-If-Not
(_, Cry-If-Not) -> Cry-If-Not
(Probably, Most-Likely) -> Cry-If-Not
(Probably, _) -> Most-Likely
(_, Most-Likely) -> Most-Likely
(Mayhapsibly, Probably) -> Most-Likely
(Mayhapsibly, _) -> Probably
(Unlikely, Probably) -> Most-Likely
(Unlikely, Mayhapsibly) -> Probably
(Unlikely, _) -> Mayhapsibly
(_, Probably) -> Probably
(Doubtful, Mayhapsibly) -> Probably
(Doubtful, Unlikely) -> Mayhapsibly
(Doubtful, _) -> Unlikely
(_, Mayhapsibly) -> Mayhapsibly
(_, Unlikely) -> Unlikely
(Probably-Not, Doubtful) -> Unlikely
(Probably-Not, _) -> Probably-Not
(_, Doubtful) -> Doubtful
(_, Probably-Not) -> Probably-Not
(_, Impossible) -> Impossible
// KFL union, or the probability of occurrence of exactly one of two independent events.
pub fun either(a: kfl, b: kfl): e kfl
val (l, h) = match a.cmp(b) // this operation is commutative
Gt -> (b, a)
_ -> (a, b)
match (l, h)
(Impossible, r) -> r
(Probably-Not, Guaranteed) -> Cry-If-Not
(Probably-Not, r) -> r
(Doubtful, Guaranteed) -> Most-Likely
(Doubtful, Cry-If-Not) -> Most-Likely
(Doubtful, Most-Likely) -> Probably
(Doubtful, Probably) -> Mayhapsibly
(Doubtful, Mayhapsibly) -> Mayhapsibly
(Doubtful, Unlikely) -> Mayhapsibly
(Doubtful, _) -> Unlikely
(Unlikely, Guaranteed) -> Probably
(Unlikely, Cry-If-Not) -> Mayhapsibly
(Unlikely, Most-Likely) -> Mayhapsibly
(Unlikely, _) -> Probably
(Mayhapsibly, Guaranteed) -> Mayhapsibly
(Mayhapsibly, Cry-If-Not) -> Mayhapsibly
(Mayhapsibly, Most-Likely) -> Mayhapsibly
(Mayhapsibly, _) -> Probably
(Probably, Guaranteed) -> Unlikely
(Probably, Cry-If-Not) -> Unlikely
(Probably, Most-Likely) -> Unlikely
(Probably, _) -> Mayhapsibly
(Most-Likely, _) -> Doubtful
(Cry-If-Not, _) -> Probably-Not
(Guaranteed, _) -> Impossible
-58
View File
@@ -1,58 +0,0 @@
module horse/prob/pmf
import std/core/list
// Discrete-support probability distribution implemented as a list with the invariant
// that support is always given in increasing order.
pub type pmf<s, v>
Event(s: s, v: v, next: pmf<s, v>)
End
// Add an independent event to the distribution.
pub fun add(p: pmf<s, v>, s: s, v: v, ?s/cmp: (a: s, b: s) -> order, ?v/(+): (new: v, old: v) -> e v): e pmf<s, v>
match p
End -> Event(s, v, End)
Event(s', v', next) -> match s.cmp(s')
Lt -> Event(s, v, Event(s', v', next))
Eq -> Event(s, v + v', next)
Gt -> Event(s', v', add(next, s, v))
// Replace an event in the distribution.
pub inline fun set(p: pmf<s, v>, s: s, v: v, ?s/cmp: (a: s, b: s) -> order): e pmf<s, v>
p.add(s, v, cmp, fn(new, old) new)
// Construct a pmf from a list of (support, value) entries.
pub fun list/pmf(l: list<(s, v)>, ?s/cmp: (a: s, b: s) -> order, ?v/(+): (new: v, old: v) -> e v): e pmf<s, v>
l.foldl(End) fn(p, (s, v)) p.add(s, v)
// Fold over the entries of the distribution.
pub tail fun foldl(p: pmf<s, v>, init: a, f: (a, s, v) -> e a): e a
match p
End -> init
Event(s, v, next) -> foldl(next, f(init, s, v), f)
// Convert the distribution to a list of entries.
pub fun pmf/list(p: pmf<s, v>): list<(s, v)>
p.foldl(Nil) fn(l, s, v) Cons((s, v), l)
// Distribution of cooccurrence of two events described by their distributions.
pub fun (*)(a: pmf<s, v>, b: pmf<s, v>, ?s/cmp: (a: s, b: s) -> order, ?v/(*): (a: v, b: v) -> e v): e pmf<s, v>
match a
End -> End
Event(sa, va, nexta) -> match b
End -> End
Event(sb, vb, nextb) -> match sa.cmp(sb)
Lt -> nexta * b
Eq -> Event(sa, va * vb, nexta * nextb)
Gt -> a * nextb
// Distribution of occurrence of at least one of two events described by their distributions.
pub fun (+)(a: pmf<s, v>, b: pmf<s, v>, ?s/cmp: (a: s, b: s) -> order, ?v/(+): (a: v, b: v) -> e v): e pmf<s, v>
match a
End -> b
Event(sa, va, nexta) -> match b
End -> a
Event(sb, vb, nextb) -> match sa.cmp(sb)
Lt -> Event(sa, va, nexta + b)
Eq -> Event(sa, va + vb, nexta + nextb)
Gt -> Event(sb, vb, a + nextb)
-46
View File
@@ -1,46 +0,0 @@
package horse
type RaceID int32
// Race is the internal data about a race.
type Race struct {
ID RaceID
Name string
Thumbnail int
// Some careers contain unusual versions of races, e.g. Tenno Sho (Spring)
// in Hanshin instead of Kyoto for Narita Taishin and Biwa Hayahide.
// For such races, this field holds the normal race ID.
Primary RaceID
}
type SaddleID int32
// Saddle is the internal data about a race win saddle.
type Saddle struct {
ID SaddleID
Name string
Races []RaceID
Type SaddleType
// Saddles that involve alternate races are themselves alternate.
// For such saddles, this field holds the normal saddle ID.
Primary SaddleID
}
type SaddleType int8
const (
// Saddle for multiple race wins, e.g. Classic Triple Crown, Dual Grand Prix, &c.
SaddleTypeHonor SaddleType = iota
SaddleTypeG3
SaddleTypeG2
SaddleTypeG1
)
type ScenarioID int8
// Scenario is metadata about a career scenario.
type Scenario struct {
ID ScenarioID
Name string
Title string
}
-301
View File
@@ -1,301 +0,0 @@
module horse/race
import std/data/linearset
import horse/game-id
pub struct race-detail
race-id: race-id
name: string
grade: grade
thumbnail-id: race-thumbnail-id
// Some careers contain unusual versions of races, e.g. Tenno Sho (Spring)
// in Hanshin instead of Kyoto for Narita Taishin and Biwa Hayahide.
// For such races, this field holds the normal race ID.
primary: race-id
pub fun detail(
r: race-id,
?race/show: (race-id) -> string,
?race/grade: (race-id) -> grade,
?race/thumbnail: (race-id) -> race-thumbnail-id,
?race/primary: (race-id) -> race-id
): race-detail
Race-detail(r, r.show, r.grade, r.thumbnail, r.primary)
pub fun race-detail/show(r: race-detail): string
val Race-detail(Race-id(id), name) = r
name ++ " (ID " ++ id.show ++ ")"
// Race grades.
pub type grade
Pre-OP
OP
G3
G2
G1
EX
// Automatically generated.
// Comparison of the `grade` type.
pub fun grade/cmp(this : grade, other : grade) : e order
match (this, other)
(Pre-OP, Pre-OP) -> Eq
(Pre-OP, _) -> Lt
(_, Pre-OP) -> Gt
(OP, OP) -> Eq
(OP, _) -> Lt
(_, OP) -> Gt
(G3, G3) -> Eq
(G3, _) -> Lt
(_, G3) -> Gt
(G2, G2) -> Eq
(G2, _) -> Lt
(_, G2) -> Gt
(G1, G1) -> Eq
(G1, _) -> Lt
(_, G1) -> Gt
(EX, EX) -> Eq
// Automatically generated.
// Shows a string representation of the `grade` type.
pub fun grade/show(this : grade) : e string
match this
Pre-OP -> "Pre-OP"
OP -> "OP"
G3 -> "G3"
G2 -> "G2"
G1 -> "G1"
EX -> "EX"
pub struct saddle-detail
saddle-id: saddle-id
name: string
races: list<race-id>
saddle-type: saddle-type
// For careers with unusual races, granted saddles also differ.
// This field holds the normal saddle's ID for such cases.
primary: saddle-id
pub fun saddle/detail(
id: saddle-id,
?saddle/show: (saddle-id) -> string,
?saddle/races: (saddle-id) -> list<race-id>,
?saddle/saddle-type: (saddle-id) -> saddle-type,
?saddle/primary: (saddle-id) -> saddle-id
): saddle-detail
Saddle-detail(id, id.show, id.races, id.saddle-type, id.primary)
pub fun saddle-detail/show(s: saddle-detail): string
val Saddle-detail(Saddle-id(id), name, _, _, Saddle-id(primary)) = s
if id == primary then name else name ++ " (Alternate " ++ id.show ++ ")"
// Types of saddles.
pub type saddle-type
Honor // multiple race wins: classic triple crown, dual grand prix, &c.
G3-Win
G2-Win
G1-Win
// Automatically generated.
// Shows a string representation of the `saddle-type` type.
pub fun saddle-type/show(this : saddle-type) : e string
match this
Honor -> "Honor"
G3-Win -> "G3"
G2-Win -> "G2"
G1-Win -> "G1"
// Automatically generated.
// Equality comparison of the `saddle-type` type.
pub fun saddle-type/(==)(this : saddle-type, other : saddle-type) : e bool
match (this, other)
(Honor, Honor) -> True
(G3-Win, G3-Win) -> True
(G2-Win, G2-Win) -> True
(G1-Win, G1-Win) -> True
(_, _) -> False
// Turn that a race occurred.
pub struct turn
year: turn-year
month: turn-month
half: turn-half
// Automatically generated.
// Equality comparison of the `turn` type.
pub fun turn/(==)(this : turn, other : turn) : e bool
match (this, other)
(Turn(year, month, half), Turn(year', month', half')) -> year == year' && month == month' && half == half'
// Automatically generated.
// Fip comparison of the `turn` type.
pub fun turn/order2(this : turn, other : turn) : e order2<turn>
match (this, other)
(Turn(year, month, half), Turn(year', month', half')) ->
match year.order2(year')
Eq2(year_eq) ->
match month.order2(month')
Eq2(month_eq) ->
match half.order2(half')
Eq2(half_eq) -> Eq2(Turn(year_eq, month_eq, half_eq))
Lt2(half_lt, half_gt) -> Lt2(Turn(year_eq, month_eq, half_lt), Turn(year_eq, month_eq, half_gt))
Gt2(half_lt, half_gt) -> Gt2(Turn(year_eq, month_eq, half_lt), Turn(year_eq, month_eq, half_gt))
Lt2(month_lt, month_gt) -> Lt2(Turn(year_eq, month_lt, half), Turn(year_eq, month_gt, half'))
Gt2(month_lt, month_gt) -> Gt2(Turn(year_eq, month_lt, half'), Turn(year_eq, month_gt, half))
Lt2(year_lt, year_gt) -> Lt2(Turn(year_lt, month, half), Turn(year_gt, month', half'))
Gt2(year_lt, year_gt) -> Gt2(Turn(year_lt, month', half'), Turn(year_gt, month, half))
// Automatically generated.
// Shows a string representation of the `turn` type.
pub fun turn/show(this : turn) : e string
this.year.show ++ " " ++ this.half.show ++ " " ++ this.month.show
pub type turn-year
Junior
Classic
Senior
Finale
// Automatically generated.
// Equality comparison of the `turn-year` type.
pub fun turn-year/(==)(this : turn-year, other : turn-year) : e bool
match (this, other)
(Junior, Junior) -> True
(Classic, Classic) -> True
(Senior, Senior) -> True
(Finale, Finale) -> True
(_, _) -> False
// Automatically generated.
// Fip comparison of the `turn-year` type.
pub fun turn-year/order2(this : turn-year, other : turn-year) : e order2<turn-year>
match (this, other)
(Junior, Junior) -> Eq2(Junior)
(Junior, other') -> Lt2(Junior, other')
(this', Junior) -> Gt2(Junior, this')
(Classic, Classic) -> Eq2(Classic)
(Classic, other') -> Lt2(Classic, other')
(this', Classic) -> Gt2(Classic, this')
(Senior, Senior) -> Eq2(Senior)
(Senior, other') -> Lt2(Senior, other')
(this', Senior) -> Gt2(Senior, this')
(Finale, Finale) -> Eq2(Finale)
// Automatically generated.
// Shows a string representation of the `turn-year` type.
pub fun turn-year/show(this : turn-year) : e string
match this
Junior -> "Junior Year"
Classic -> "Classic Year"
Senior -> "Senior Year"
Finale -> "Finale 1" // the 1 is in the game
pub type turn-month
January
February
March
April
May
June
July
August
September
November
December
// Automatically generated.
// Equality comparison of the `turn-month` type.
pub fun turn-month/(==)(this : turn-month, other : turn-month) : e bool
match (this, other)
(January, January) -> True
(February, February) -> True
(March, March) -> True
(April, April) -> True
(May, May) -> True
(June, June) -> True
(July, July) -> True
(August, August) -> True
(September, September) -> True
(November, November) -> True
(December, December) -> True
(_, _) -> False
// Automatically generated.
// Fip comparison of the `turn-month` type.
pub fun turn-month/order2(this : turn-month, other : turn-month) : e order2<turn-month>
match (this, other)
(January, January) -> Eq2(January)
(January, other') -> Lt2(January, other')
(this', January) -> Gt2(January, this')
(February, February) -> Eq2(February)
(February, other') -> Lt2(February, other')
(this', February) -> Gt2(February, this')
(March, March) -> Eq2(March)
(March, other') -> Lt2(March, other')
(this', March) -> Gt2(March, this')
(April, April) -> Eq2(April)
(April, other') -> Lt2(April, other')
(this', April) -> Gt2(April, this')
(May, May) -> Eq2(May)
(May, other') -> Lt2(May, other')
(this', May) -> Gt2(May, this')
(June, June) -> Eq2(June)
(June, other') -> Lt2(June, other')
(this', June) -> Gt2(June, this')
(July, July) -> Eq2(July)
(July, other') -> Lt2(July, other')
(this', July) -> Gt2(July, this')
(August, August) -> Eq2(August)
(August, other') -> Lt2(August, other')
(this', August) -> Gt2(August, this')
(September, September) -> Eq2(September)
(September, other') -> Lt2(September, other')
(this', September) -> Gt2(September, this')
(November, November) -> Eq2(November)
(November, other') -> Lt2(November, other')
(this', November) -> Gt2(November, this')
(December, December) -> Eq2(December)
// Automatically generated.
// Shows a string representation of the `turn-month` type.
pub fun turn-month/show(this : turn-month) : e string
match this
January -> "January"
February -> "February"
March -> "March"
April -> "April"
May -> "May"
June -> "June"
July -> "July"
August -> "August"
September -> "September"
November -> "November"
December -> "December"
pub type turn-half
Early
Late
// Automatically generated.
// Equality comparison of the `turn-half` type.
pub fun turn-half/(==)(this : turn-half, other : turn-half) : e bool
match (this, other)
(Early, Early) -> True
(Late, Late) -> True
(_, _) -> False
// Automatically generated.
// Fip comparison of the `turn-half` type.
pub fun turn-half/order2(this : turn-half, other : turn-half) : e order2<turn-half>
match (this, other)
(Early, Early) -> Eq2(Early)
(Early, other') -> Lt2(Early, other')
(this', Early) -> Gt2(Early, this')
(Late, Late) -> Eq2(Late)
// Automatically generated.
// Shows a string representation of the `turn-half` type.
pub fun turn-half/show(this : turn-half) : e string
match this
Early -> "Early"
Late -> "Late"
-249
View File
@@ -1,249 +0,0 @@
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<uma-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<uma-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, ?uma/show: (uma-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 Uma 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
dur-scale: dur-scale
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, Direct-Dur, cooldown, abilities) | cooldown >= 500.decimal -> condition ++ " -> for " ++ duration.show ++ "s, " ++ abilities.show
Activation("", condition, duration, dur-scale, cooldown, abilities) | cooldown >= 500.decimal -> condition ++ " -> for " ++ duration.show ++ "s " ++ dur-scale.show ++ ", " ++ abilities.show
Activation("", condition, duration, Direct-Dur, cooldown, abilities) -> condition ++ " -> for " ++ duration.show ++ "s on " ++ cooldown.show ++ "s cooldown, " ++ abilities.show
Activation("", condition, duration, dur-scale, cooldown, abilities) -> condition ++ " -> for " ++ duration.show ++ "s " ++ dur-scale.show ++ " on " ++ cooldown.show ++ "s cooldown, " ++ abilities.show
Activation(precondition, condition, duration, _, _, abilities) | !duration.is-pos -> precondition ++ " -> " ++ condition ++ " -> " ++ abilities.show
Activation(precondition, condition, duration, Direct-Dur, cooldown, abilities) | cooldown >= 500.decimal -> precondition ++ " -> " ++ condition ++ " -> for " ++ duration.show ++ "s, " ++ abilities.show
Activation(precondition, condition, duration, dur-scale, cooldown, abilities) | cooldown >= 500.decimal -> precondition ++ " -> " ++ condition ++ " -> for " ++ duration.show ++ "s " ++ dur-scale.show ++ ", " ++ abilities.show
Activation(precondition, condition, duration, Direct-Dur, cooldown, abilities) -> precondition ++ " -> " ++ condition ++ " -> for " ++ duration.show ++ "s on " ++ cooldown.show ++ "s cooldown, " ++ abilities.show
Activation(precondition, condition, duration, dur-scale, cooldown, abilities) -> precondition ++ " -> " ++ condition ++ " -> for " ++ duration.show ++ "s " ++ dur-scale.show ++ " on " ++ cooldown.show ++ "s cooldown, " ++ abilities.show
// Special scaling types for skill activation durations.
pub type dur-scale
Direct-Dur
Front-Distance-Dur
Multiply-Remaining-HP
Increment-Pass
Midrace-Side-Block-Time-Dur
Multiply-Remaining-HP2
pub fun dur-scale/show(s: dur-scale): string
match s
Direct-Dur -> "with no scaling"
Front-Distance-Dur -> "scaling with distance from the front"
Multiply-Remaining-HP -> "scaling with remaining HP"
Increment-Pass -> "increasing with each pass while active"
Midrace-Side-Block-Time-Dur -> "scaling with mid-race phase blocked side time"
Multiply-Remaining-HP2 -> "scaling with remaining HP"
// 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"
-58
View File
@@ -1,58 +0,0 @@
package horse_test
import (
"cmp"
"slices"
"strings"
"sync"
"testing"
"git.sunturtle.xyz/zephyr/horse/horse"
"git.sunturtle.xyz/zephyr/horse/horse/global"
)
var SortedSkills = sync.OnceValue(func() []horse.Skill {
skills := make([]horse.Skill, 0, len(global.AllSkills))
for _, v := range global.AllSkills {
skills = append(skills, v)
}
slices.SortFunc(skills, func(a, b horse.Skill) int { return cmp.Compare(a.ID, b.ID) })
return skills
})
func TestSkillStrings(t *testing.T) {
t.Parallel()
for _, s := range SortedSkills() {
for _, a := range s.Activations {
for _, abil := range a.Abilities {
if n := abil.Type.String(); strings.HasPrefix(n, "AbilityType(") {
t.Errorf("%v %s: %s", s.ID, s.Name, n)
}
}
}
}
}
func TestTenThousandthsString(t *testing.T) {
t.Parallel()
cases := []struct {
val horse.TenThousandths
want string
}{
{0, "0"},
{500, "0.05"},
{-500, "-0.05"},
{10000, "1"},
{-10000, "-1"},
{15000, "1.5"},
{-15000, "-1.5"},
{10001, "1.0001"},
{5000000, "500"},
}
for _, c := range cases {
got := c.val.String()
if got != c.want {
t.Errorf("%d: want %q, got %q", c.val, c.want, got)
}
}
}
-175
View File
@@ -1,175 +0,0 @@
module horse/spark
import std/num/decimal
import horse/game-id
import horse/movement
// A spark on a veteran.
pub struct spark-detail
spark-id: spark-id
typ: spark-type
rarity: rarity
pub fun detail(id: spark-id, ?spark/spark-type: (spark-id) -> spark-type, ?spark/rarity: (spark-id) -> rarity): spark-detail
Spark-detail(id, id.spark-type, id.rarity)
pub fun spark-detail/show(s: spark-detail, ?spark/show: (spark-id) -> string): string
s.spark-id.show ++ " " ++ "\u2605".repeat(s.rarity.int)
// The category of a spark; roughly, blue, pink, green, or white, with some
// further subdivisions.
pub type spark-type
Stat // blue
Aptitude // red/pink
Unique // green
Race
Skill
// skip Carnival Bonus
Scenario
Surface
Distance
Style
Hidden
// Spark targets and effects.
pub type spark-effect
Stat-Up(s: stat, amount: int)
SP-Up(amount: int)
// skip Carnival Bonus
Random-Stat-Up(amount: int)
Aptitude-Up(a: aptitude, amount: int)
Skill-Hint(s: skill-id, levels: int)
Stat-Cap-Up(s: stat, amount: int)
// Get the base probability for a spark to trigger during a single inheritance.
pub fun decimal/base-proc(id: spark-id, ?spark-type: (spark-id) -> spark-type, ?rarity: (spark-id) -> rarity): decimal
val t = id.spark-type
val r = id.rarity
match (t, r)
(Stat, One) -> 70.decimal(-2)
(Stat, Two) -> 80.decimal(-2)
(Stat, Three) -> 90.decimal(-2)
(Aptitude, One) -> 1.decimal(-2)
(Aptitude, Two) -> 3.decimal(-2)
(Aptitude, Three) -> 5.decimal(-2)
(Unique, One) -> 5.decimal(-2)
(Unique, Two) -> 10.decimal(-2)
(Unique, Three) -> 15.decimal(-2)
(Race, One) -> 1.decimal(-2)
(Race, Two) -> 2.decimal(-2)
(Race, Three) -> 3.decimal(-2)
(_, One) -> 3.decimal(-2)
(_, Two) -> 6.decimal(-2)
(_, Three) -> 9.decimal(-2)
// The level or star count of a spark.
pub type rarity
One
Two
Three
pub fun rarity/int(l: rarity): int
match l
One -> 1
Two -> 2
Three -> 3
pub fun rarity/show(l: rarity): string
match l
One -> "1"
Two -> "2"
Three -> "3"
// Stat (blue) spark.
pub type stat
Speed
Stamina
Power
Guts
Wit
// Automatically generated.
// Shows a string representation of the `stat` type.
pub fun stat/show(this : stat) : e string
match this
Speed -> "Speed"
Stamina -> "Stamina"
Power -> "Power"
Guts -> "Guts"
Wit -> "Wit"
// Aptitude (red/pink) spark.
pub type aptitude
Turf
Dirt
Sprint
Mile
Medium
Long
Front-Runner
Pace-Chaser
Late-Surger
End-Closer
// Automatically generated.
// Fip comparison of the `aptitude` type.
pub fun aptitude/order2(this : aptitude, other : aptitude) : e order2<aptitude>
match (this, other)
(Turf, Turf) -> Eq2(Turf)
(Turf, other') -> Lt2(Turf, other')
(this', Turf) -> Gt2(Turf, this')
(Dirt, Dirt) -> Eq2(Dirt)
(Dirt, other') -> Lt2(Dirt, other')
(this', Dirt) -> Gt2(Dirt, this')
(Sprint, Sprint) -> Eq2(Sprint)
(Sprint, other') -> Lt2(Sprint, other')
(this', Sprint) -> Gt2(Sprint, this')
(Mile, Mile) -> Eq2(Mile)
(Mile, other') -> Lt2(Mile, other')
(this', Mile) -> Gt2(Mile, this')
(Medium, Medium) -> Eq2(Medium)
(Medium, other') -> Lt2(Medium, other')
(this', Medium) -> Gt2(Medium, this')
(Long, Long) -> Eq2(Long)
(Long, other') -> Lt2(Long, other')
(this', Long) -> Gt2(Long, this')
(Front-Runner, Front-Runner) -> Eq2(Front-Runner)
(Front-Runner, other') -> Lt2(Front-Runner, other')
(this', Front-Runner) -> Gt2(Front-Runner, this')
(Pace-Chaser, Pace-Chaser) -> Eq2(Pace-Chaser)
(Pace-Chaser, other') -> Lt2(Pace-Chaser, other')
(this', Pace-Chaser) -> Gt2(Pace-Chaser, this')
(Late-Surger, Late-Surger) -> Eq2(Late-Surger)
(Late-Surger, other') -> Lt2(Late-Surger, other')
(this', Late-Surger) -> Gt2(Late-Surger, this')
(End-Closer, End-Closer) -> Eq2(End-Closer)
// Automatically generated.
// Equality comparison of the `aptitude` type.
pub fun aptitude/(==)(this : aptitude, other : aptitude) : e bool
match (this, other)
(Turf, Turf) -> True
(Dirt, Dirt) -> True
(Sprint, Sprint) -> True
(Mile, Mile) -> True
(Medium, Medium) -> True
(Long, Long) -> True
(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 `aptitude` type.
pub fun aptitude/show(this : aptitude): string
match this
Turf -> "Turf"
Dirt -> "Dirt"
Sprint -> "Sprint"
Mile -> "Mile"
Medium -> "Medium"
Long -> "Long"
Front-Runner -> "Front Runner"
Pace-Chaser -> "Pace Chaser"
Late-Surger -> "Late Surger"
End-Closer -> "End Closer"
-32
View File
@@ -1,32 +0,0 @@
package horse
type UmaID int32
type Uma struct {
ID UmaID
CharacterID CharacterID
Name string
Variant string
Sprint, Mile, Medium, Long AptitudeLevel
Front, Pace, Late, End AptitudeLevel
Turf, Dirt AptitudeLevel
Unique SkillID
Skill1, Skill2, Skill3 SkillID
SkillPL2, SkillPL3, SkillPL4, SkillPL5 SkillID
}
type AptitudeLevel int8
//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type AptitudeLevel -trimprefix AptitudeLv
const (
AptitudeLvG AptitudeLevel = iota + 1
AptitudeLvF
AptitudeLvE
AptitudeLvD
AptitudeLvC
AptitudeLvB
AptitudeLvA
AptitudeLvS
)
-27
View File
@@ -1,27 +0,0 @@
module horse/uma
import horse/game-id
import horse/movement
// Details of an uma, or character card.
pub struct uma-detail
uma-id: uma-id
character-id: character-id
sprint: aptitude-level
mile: aptitude-level
medium: aptitude-level
long: aptitude-level
front-runner: aptitude-level
pace-chaser: aptitude-level
late-surger: aptitude-level
end-closer: aptitude-level
turf: aptitude-level
dirt: aptitude-level
unique: skill-id
skill1: skill-id
skill2: skill-id
skill3: skill-id
skill-pl2: skill-id
skill-pl3: skill-id
skill-pl4: skill-id
skill-pl5: skill-id
-6
View File
@@ -1,6 +0,0 @@
# gen
Go tool to generate the Koka source code from the game's SQLite database.
Code is generated using Go templates.
Templates use a `kkenum` function which converts a name `Mr. C.B.` to a Koka enumerant name `Mr-CB`.
-44
View File
@@ -1,44 +0,0 @@
WITH uma_names AS (
SELECT
"index" AS "id",
"text" AS "name"
FROM text_data
WHERE category = 6 AND "index" BETWEEN 1000 AND 1999
-- Exclude characters who have no succession relations defined.
AND "index" IN (SELECT chara_id FROM succession_relation_member)
), pairs AS (
SELECT
a.id AS id_a,
a.name AS name_a,
b.id AS id_b,
b.name AS name_b
FROM uma_names a
JOIN uma_names b ON a.id != b.id -- exclude reflexive cases
), relation_pairs AS (
SELECT
ra.relation_type,
ra.chara_id AS id_a,
rb.chara_id AS id_b
FROM succession_relation_member ra
JOIN succession_relation_member rb ON ra.relation_type = rb.relation_type
), affinity AS (
SELECT
pairs.*,
SUM(IFNULL(relation_point, 0)) AS base_affinity
FROM pairs
LEFT JOIN relation_pairs rp ON pairs.id_a = rp.id_a AND pairs.id_b = rp.id_b
LEFT JOIN succession_relation sr ON rp.relation_type = sr.relation_type
GROUP BY pairs.id_a, pairs.id_b
UNION ALL
-- Reflexive cases.
SELECT
uma_names.id AS id_a,
uma_names.name AS name_a,
uma_names.id AS id_b,
uma_names.name AS name_b,
0 AS base_affinity
FROM uma_names
)
SELECT * FROM affinity
ORDER BY id_a, id_b
-87
View File
@@ -1,87 +0,0 @@
WITH uma_names AS (
SELECT
"index" AS "id",
"text" AS "name"
FROM text_data
WHERE category = 6 AND "index" BETWEEN 1000 AND 1999
-- Exclude characters who have no succession relations defined.
AND "index" IN (SELECT chara_id FROM succession_relation_member)
), trios AS (
SELECT
a.id AS id_a,
a.name AS name_a,
b.id AS id_b,
b.name AS name_b,
c.id AS id_c,
c.name AS name_c
FROM uma_names a
JOIN uma_names b ON a.id != b.id -- exclude pairwise reflexive cases
JOIN uma_names c ON a.id != c.id AND b.id != c.id
), relation_trios AS (
SELECT
ra.relation_type,
ra.chara_id AS id_a,
rb.chara_id AS id_b,
rc.chara_id AS id_c
FROM succession_relation_member ra
JOIN succession_relation_member rb ON ra.relation_type = rb.relation_type
JOIN succession_relation_member rc ON ra.relation_type = rc.relation_type
), affinity AS (
SELECT
trios.*,
SUM(IFNULL(relation_point, 0)) AS base_affinity
FROM trios
LEFT JOIN relation_trios rt ON trios.id_a = rt.id_a AND trios.id_b = rt.id_b AND trios.id_c = rt.id_c
LEFT JOIN succession_relation sr ON rt.relation_type = sr.relation_type
GROUP BY trios.id_a, trios.id_b, trios.id_c
UNION ALL
-- A = B = C
SELECT
n.id AS id_a,
n.name AS name_a,
n.id AS id_b,
n.name AS name_b,
n.id AS id_c,
n.name AS name_c,
0 AS base_affinity
FROM uma_names n
UNION ALL
-- A = B
SELECT
n.id AS id_a,
n.name AS name_a,
n.id AS id_a,
n.name AS id_b,
m.id AS id_c,
m.name AS name_c,
0 AS base_affinity
FROM uma_names n JOIN uma_names m ON n.id != m.id
UNION ALL
-- A = C
SELECT
n.id AS id_a,
n.name AS name_a,
m.id AS id_a,
m.name AS id_b,
n.id AS id_c,
n.name AS name_c,
0 AS base_affinity
FROM uma_names n JOIN uma_names m ON n.id != m.id
UNION ALL
-- B = C
SELECT
m.id AS id_a,
m.name AS name_a,
n.id AS id_a,
n.name AS id_b,
n.id AS id_c,
n.name AS name_c,
0 AS base_affinity
FROM uma_names n JOIN uma_names m ON n.id != m.id
)
SELECT * FROM affinity
ORDER BY id_a, id_b, id_c
-72
View File
@@ -1,72 +0,0 @@
{{ define "go-character" -}}
package {{ $.Region }}
// Automatically generated with horsegen; DO NOT EDIT
import . "git.sunturtle.xyz/zephyr/horse/horse"
const (
{{- range $c := $.Characters }}
Character{{ goenum $c.Name }} CharacterID = {{ $c.ID }} // {{ $c.Name }}
{{- end }}
)
var OrderedCharacters = [...]CharacterID{
{{- range $c := $.Characters }}
Character{{ goenum $c.Name }},
{{- end }}
}
var Characters = map[CharacterID]Character{
{{- range $c := $.Characters }}
Character{{ goenum $c.Name }}: {ID: {{ $c.ID }}, Name: {{ printf "%q" $c.Name -}} },
{{- end }}
}
var CharacterNameToID = map[string]CharacterID{
{{- range $c := $.Characters }}
{{ printf "%q" $c.Name }}: {{ $c.ID }},
{{- end }}
}
var pairAffinity = []int8{
{{- range $a := $.Characters -}}
{{- range $b := $.Characters -}}
{{- index $.PairMaps $a.ID $b.ID -}},
{{- end -}}
{{- end -}}
}
var trioAffinity = []int8{
{{- range $a := $.Characters -}}
{{- range $b := $.Characters -}}
{{- range $c := $.Characters -}}
{{- index $.TrioMaps $a.ID $b.ID $c.ID -}},
{{- end -}}
{{- end -}}
{{- end -}}
}
func PairAffinity(a, b CharacterID) int {
if _, ok := Characters[a]; !ok {
return 0
}
if _, ok := Characters[b]; !ok {
return 0
}
return int(pairAffinity[a*{{ $.CharaCount }} + b])
}
func TrioAffinity(a, b, c CharacterID) int {
if _, ok := Characters[a]; !ok {
return 0
}
if _, ok := Characters[b]; !ok {
return 0
}
if _, ok := Characters[c]; !ok {
return 0
}
return int(trioAffinity[a*{{ $.CharaCount }}*{{ $.CharaCount }} + b*{{ $.CharaCount }} + c])
}
{{ end }}
-94
View File
@@ -1,94 +0,0 @@
{{ define "koka-character" -}}
module horse/{{ $.Region }}/character
// Automatically generated with horsegen; DO NOT EDIT
import std/core/delayed
import std/core/vector
import std/core-extras
import std/data/rb-map
import horse/game-id
pub import horse/character
extern create-id-table(): vector<int>
c inline "int32_t arr[] = { {{- range $chara := $.Characters }}{{ $chara.ID }},{{ end -}} };\nkk_vector_from_cint32array(arr, (kk_ssize_t){{ $.CharaCount }}, kk_context())"
js inline "[{{ range $chara := $.Characters }}{{ $chara.ID }},{{ end }}]"
// Vector of all character ID values in order for easy iterating.
pub val all = once(create-id-table)
val name2id = once()
var m: rbmap<string, int> := empty()
all().foreach() fn(id) m := m.set(Character-id(id).show, id)
m
// Get the character ID that has the given exact name.
// If no character matches the name, the result is an invalid ID.
pub fun from-name(name: string): character-id
Character-id(name2id().rb-map/lookup(name).default(0))
// Get the name for a character.
// If no character matches the ID, the result is the numeric ID.
pub fun show(c: character-id): string
match c.game-id
{{- range $chara := $.Characters }}
{{ $chara.ID }} -> {{ printf "%q" $chara.Name }}
{{- end }}
x -> "character " ++ x.show
fun character/index(c: character-id): int
match c.game-id
{{- range $chara := $.Characters }}
{{ $chara.ID }} -> {{ $chara.Index }}
{{- end }}
_ -> -99999999
// Create the table of all pair affinities.
// The affinity is the value at a.index*count + b.index.
extern global/create-pair-table(): vector<int>
c inline "int32_t arr[] = {
{{- range $a := $.Characters }}
{{- range $b := $.Characters }}
{{- index $.PairMaps $a.ID $b.ID }},
{{- end }}
{{- end -}}
};\nkk_vector_from_cint32array(arr, (kk_ssize_t){{ $.CharaCount }} * (kk_ssize_t){{ $.CharaCount }}, kk_context())"
js inline "[
{{- range $a := $.Characters }}
{{- range $b := $.Characters }}
{{- index $.PairMaps $a.ID $b.ID }},
{{- end }}
{{- end -}}
]"
val global/pair-table = global/create-pair-table()
// Base affinity between a pair using the global ruleset.
pub fun global/pair-affinity(a: character-id, b: character-id): int
global/pair-table.at(a.index * {{ $.CharaCount }} + b.index).default(0)
// Create the table of all trio affinities.
// The affinity is the value at a.index*count*count + b.index*count + c.index.
extern global/create-trio-table(): vector<int>
c inline "int32_t arr[] = {
{{- range $a := $.Characters }}
{{- range $b := $.Characters }}
{{- range $c := $.Characters }}
{{- index $.TrioMaps $a.ID $b.ID $c.ID }},
{{- end }}
{{- end }}
{{- end -}}
};\nkk_vector_from_cint32array(arr, (kk_ssize_t){{ $.CharaCount }} * (kk_ssize_t){{ $.CharaCount }} * (kk_ssize_t){{ $.CharaCount }}, kk_context())"
js inline "[
{{- range $a := $.Characters }}
{{- range $b := $.Characters }}
{{- range $c := $.Characters }}
{{- index $.TrioMaps $a.ID $b.ID $c.ID }},
{{- end }}
{{- end }}
{{- end -}}
]"
val global/trio-table = global/create-trio-table()
// Base affinity for a trio using the global ruleset.
pub fun global/trio-affinity(a: character-id, b: character-id, c: character-id): int
global/trio-table.at(a.index * {{ $.CharaCount }} * {{ $.CharaCount }} + b.index * {{ $.CharaCount }} + c.index).default(0)
{{ end }}
-9
View File
@@ -1,9 +0,0 @@
SELECT
"index" AS "id",
"text" AS "name",
ROW_NUMBER() OVER (ORDER BY "index") - 1 AS "index"
FROM text_data
WHERE category = 6 AND "index" BETWEEN 1000 AND 1999
-- Exclude characters who have no succession relations defined.
AND "index" IN (SELECT chara_id FROM succession_relation_member)
ORDER BY "id"
-263
View File
@@ -1,263 +0,0 @@
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
CharaCount 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
SkillCount int
}{region, groups, skills, m, len(skills)}
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
RaceCount int
}{region, races, len(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
SaddleCount int
}{region, saddles, len(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
}
func ExecScenario(t *template.Template, region string, kk, g io.Writer, scen []Scenario) error {
data := struct {
Region string
Scenarios []Scenario
ScenarioCount int
}{region, scen, len(scen)}
var err error
if kk != nil {
err = errors.Join(err, t.ExecuteTemplate(kk, "koka-scenario", &data))
}
if g != nil {
err = errors.Join(err, t.ExecuteTemplate(g, "go-scenario", &data))
}
return err
}
func ExecSparks(t *template.Template, region string, kk, g io.Writer, sparks []Spark, effects map[int]map[int][]SparkEffect) error {
data := struct {
Region string
Sparks []Spark
SparkEffects map[int]map[int][]SparkEffect
SparkCount int
}{region, sparks, effects, len(sparks)}
var err error
if kk != nil {
err = errors.Join(err, t.ExecuteTemplate(kk, "koka-spark", &data))
}
if g != nil {
err = errors.Join(err, t.ExecuteTemplate(g, "go-spark", &data))
}
return err
}
func ExecUmas(t *template.Template, region string, kk, g io.Writer, umas []Uma) error {
data := struct {
Region string
Umas []Uma
UmaCount int
}{region, umas, len(umas)}
var err error
if kk != nil {
err = errors.Join(err, t.ExecuteTemplate(kk, "koka-uma", &data))
}
if g != nil {
err = errors.Join(err, t.ExecuteTemplate(g, "go-uma", &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
}
-613
View File
@@ -1,613 +0,0 @@
package main
import (
"context"
_ "embed"
"fmt"
"zombiezen.com/go/sqlite/sqlitex"
)
//go:embed character.sql
var characterSQL string
//go:embed character.affinity2.sql
var characterAffinity2SQL string
//go:embed character.affinity3.sql
var characterAffinity3SQL string
//go:embed uma.sql
var umaSQL string
//go:embed skill-group.sql
var skillGroupSQL string
//go:embed skill.sql
var skillSQL string
//go:embed race.sql
var raceSQL string
//go:embed saddle.sql
var saddleSQL string
//go:embed scenario.sql
var scenarioSQL string
//go:embed spark.sql
var sparkSQL string
//go:embed spark-effect.sql
var sparkEffectSQL string
type (
Character struct{}
SkillGroup struct{}
)
type NamedID[T any] struct {
// Disallow conversions between NamedID types.
_ [0]*T
ID int
Name string
// For internal use, the index of the identity, when it's needed.
// We don't show this in public API, but it lets us use vectors for lookups.
Index int
}
func Characters(ctx context.Context, db *sqlitex.Pool) ([]NamedID[Character], error) {
conn, err := db.Take(ctx)
defer db.Put(conn)
if err != nil {
return nil, fmt.Errorf("couldn't get connection for characters: %w", err)
}
stmt, _, err := conn.PrepareTransient(characterSQL)
if err != nil {
return nil, fmt.Errorf("couldn't prepare statement for characters: %w", err)
}
defer stmt.Finalize()
var r []NamedID[Character]
for {
ok, err := stmt.Step()
if err != nil {
return nil, fmt.Errorf("error stepping characters: %w", err)
}
if !ok {
break
}
c := NamedID[Character]{
ID: stmt.ColumnInt(0),
Name: stmt.ColumnText(1),
Index: stmt.ColumnInt(2),
}
r = append(r, c)
}
return r, nil
}
type AffinityRelation struct {
IDA int
NameA string
IDB int
NameB string
IDC int
NameC string
Affinity int
}
func CharacterPairs(ctx context.Context, db *sqlitex.Pool) ([]AffinityRelation, error) {
conn, err := db.Take(ctx)
defer db.Put(conn)
if err != nil {
return nil, fmt.Errorf("couldn't get connection for character pairs: %w", err)
}
stmt, _, err := conn.PrepareTransient(characterAffinity2SQL)
if err != nil {
return nil, fmt.Errorf("couldn't prepare statement for character pairs: %w", err)
}
defer stmt.Finalize()
var r []AffinityRelation
for {
ok, err := stmt.Step()
if err != nil {
return nil, fmt.Errorf("error stepping character pairs: %w", err)
}
if !ok {
break
}
p := AffinityRelation{
IDA: stmt.ColumnInt(0),
NameA: stmt.ColumnText(1),
IDB: stmt.ColumnInt(2),
NameB: stmt.ColumnText(3),
Affinity: stmt.ColumnInt(4),
}
r = append(r, p)
}
return r, nil
}
func CharacterTrios(ctx context.Context, db *sqlitex.Pool) ([]AffinityRelation, error) {
conn, err := db.Take(ctx)
defer db.Put(conn)
if err != nil {
return nil, fmt.Errorf("couldn't get connection for character trios: %w", err)
}
stmt, _, err := conn.PrepareTransient(characterAffinity3SQL)
if err != nil {
return nil, fmt.Errorf("couldn't prepare statement for character trios: %w", err)
}
defer stmt.Finalize()
var r []AffinityRelation
for {
ok, err := stmt.Step()
if err != nil {
return nil, fmt.Errorf("error stepping character trios: %w", err)
}
if !ok {
break
}
p := AffinityRelation{
IDA: stmt.ColumnInt(0),
NameA: stmt.ColumnText(1),
IDB: stmt.ColumnInt(2),
NameB: stmt.ColumnText(3),
IDC: stmt.ColumnInt(4),
NameC: stmt.ColumnText(5),
Affinity: stmt.ColumnInt(6),
}
r = append(r, p)
}
return r, nil
}
func SkillGroups(ctx context.Context, db *sqlitex.Pool) ([]NamedID[SkillGroup], error) {
conn, err := db.Take(ctx)
defer db.Put(conn)
if err != nil {
return nil, fmt.Errorf("couldn't get connection for skill groups: %w", err)
}
stmt, _, err := conn.PrepareTransient(skillGroupSQL)
if err != nil {
return nil, fmt.Errorf("couldn't prepare statement for skill groups: %w", err)
}
defer stmt.Finalize()
var r []NamedID[SkillGroup]
for {
ok, err := stmt.Step()
if err != nil {
return nil, fmt.Errorf("error stepping skill groups: %w", err)
}
if !ok {
break
}
g := NamedID[SkillGroup]{
ID: stmt.ColumnInt(0),
Name: stmt.ColumnText(1),
}
r = append(r, g)
}
return r, nil
}
type Skill struct {
ID int
Name string
Description string
GroupID int
GroupName string
Rarity int
GroupRate int
GradeValue int
WitCheck bool
Activations [2]SkillActivation
SPCost int
InheritID int
UniqueOwnerID int
UniqueOwner string
IconID int
Index int
}
type SkillActivation struct {
Precondition string
Condition string
Duration int
DurScale int
Cooldown int
Abilities [3]SkillAbility
}
type SkillAbility struct {
Type int
ValueUsage int
Value int
Target int
TargetValue int
}
func Skills(ctx context.Context, db *sqlitex.Pool) ([]Skill, error) {
conn, err := db.Take(ctx)
defer db.Put(conn)
if err != nil {
return nil, fmt.Errorf("couldn't get connection for skills: %w", err)
}
stmt, _, err := conn.PrepareTransient(skillSQL)
if err != nil {
return nil, fmt.Errorf("couldn't prepare statement for skills: %w", err)
}
defer stmt.Finalize()
var r []Skill
for {
ok, err := stmt.Step()
if err != nil {
return nil, fmt.Errorf("error stepping skills: %w", err)
}
if !ok {
break
}
s := Skill{
ID: stmt.ColumnInt(0),
Name: stmt.ColumnText(1),
Description: stmt.ColumnText(2),
GroupID: stmt.ColumnInt(3),
GroupName: stmt.ColumnText(4),
Rarity: stmt.ColumnInt(5),
GroupRate: stmt.ColumnInt(6),
GradeValue: stmt.ColumnInt(7),
WitCheck: stmt.ColumnInt(8) != 0,
Activations: [2]SkillActivation{
{
Precondition: stmt.ColumnText(9),
Condition: stmt.ColumnText(10),
Duration: stmt.ColumnInt(11),
DurScale: stmt.ColumnInt(12),
Cooldown: stmt.ColumnInt(13),
Abilities: [3]SkillAbility{
{
Type: stmt.ColumnInt(14),
ValueUsage: stmt.ColumnInt(15),
Value: stmt.ColumnInt(16),
Target: stmt.ColumnInt(17),
TargetValue: stmt.ColumnInt(18),
},
{
Type: stmt.ColumnInt(19),
ValueUsage: stmt.ColumnInt(20),
Value: stmt.ColumnInt(21),
Target: stmt.ColumnInt(22),
TargetValue: stmt.ColumnInt(23),
},
{
Type: stmt.ColumnInt(24),
ValueUsage: stmt.ColumnInt(25),
Value: stmt.ColumnInt(26),
Target: stmt.ColumnInt(27),
TargetValue: stmt.ColumnInt(28),
},
},
},
{
Precondition: stmt.ColumnText(29),
Condition: stmt.ColumnText(30),
Duration: stmt.ColumnInt(31),
DurScale: stmt.ColumnInt(32),
Cooldown: stmt.ColumnInt(33),
Abilities: [3]SkillAbility{
{
Type: stmt.ColumnInt(34),
ValueUsage: stmt.ColumnInt(35),
Value: stmt.ColumnInt(36),
Target: stmt.ColumnInt(37),
TargetValue: stmt.ColumnInt(38),
},
{
Type: stmt.ColumnInt(39),
ValueUsage: stmt.ColumnInt(40),
Value: stmt.ColumnInt(41),
Target: stmt.ColumnInt(42),
TargetValue: stmt.ColumnInt(43),
},
{
Type: stmt.ColumnInt(44),
ValueUsage: stmt.ColumnInt(45),
Value: stmt.ColumnInt(46),
Target: stmt.ColumnInt(47),
TargetValue: stmt.ColumnInt(48),
},
},
},
},
SPCost: stmt.ColumnInt(49),
InheritID: stmt.ColumnInt(50),
UniqueOwnerID: stmt.ColumnInt(51),
UniqueOwner: stmt.ColumnText(52),
IconID: stmt.ColumnInt(53),
Index: stmt.ColumnInt(54),
}
r = append(r, s)
}
return r, nil
}
type Race struct {
ID int
Name string
Grade int
ThumbnailID int
Primary int
Alternate int
}
func Races(ctx context.Context, db *sqlitex.Pool) ([]Race, error) {
conn, err := db.Take(ctx)
defer db.Put(conn)
if err != nil {
return nil, fmt.Errorf("couldn't get connection for races: %w", err)
}
stmt, _, err := conn.PrepareTransient(raceSQL)
if err != nil {
return nil, fmt.Errorf("couldn't prepare statement for races: %w", err)
}
defer stmt.Finalize()
var r []Race
for {
ok, err := stmt.Step()
if err != nil {
return nil, fmt.Errorf("error stepping races: %w", err)
}
if !ok {
break
}
race := Race{
ID: stmt.ColumnInt(0),
Name: stmt.ColumnText(1),
Grade: stmt.ColumnInt(2),
ThumbnailID: stmt.ColumnInt(3),
Primary: stmt.ColumnInt(4),
Alternate: stmt.ColumnInt(5),
}
r = append(r, race)
}
return r, nil
}
type Saddle struct {
ID int
Name string
Races [3]int
Type int
Primary int
Alternate int
}
func Saddles(ctx context.Context, db *sqlitex.Pool) ([]Saddle, error) {
conn, err := db.Take(ctx)
defer db.Put(conn)
if err != nil {
return nil, fmt.Errorf("couldn't get connection for saddles: %w", err)
}
stmt, _, err := conn.PrepareTransient(saddleSQL)
if err != nil {
return nil, fmt.Errorf("couldn't prepare statement for saddles: %w", err)
}
defer stmt.Finalize()
var r []Saddle
for {
ok, err := stmt.Step()
if err != nil {
return nil, fmt.Errorf("error stepping saddles: %w", err)
}
if !ok {
break
}
s := Saddle{
ID: stmt.ColumnInt(0),
Name: stmt.ColumnText(1),
Races: [3]int{stmt.ColumnInt(2), stmt.ColumnInt(3), stmt.ColumnInt(4)},
Type: stmt.ColumnInt(5),
Primary: stmt.ColumnInt(6),
Alternate: stmt.ColumnInt(7),
}
r = append(r, s)
}
return r, nil
}
type Scenario struct {
ID int
Name string
Title string
}
func Scenarios(ctx context.Context, db *sqlitex.Pool) ([]Scenario, error) {
conn, err := db.Take(ctx)
defer db.Put(conn)
if err != nil {
return nil, fmt.Errorf("couldn't get connection for scenario: %w", err)
}
stmt, _, err := conn.PrepareTransient(scenarioSQL)
if err != nil {
return nil, fmt.Errorf("couldn't prepare statement for scenario: %w", err)
}
defer stmt.Finalize()
var r []Scenario
for {
ok, err := stmt.Step()
if err != nil {
return nil, fmt.Errorf("error stepping scenarios: %w", err)
}
if !ok {
break
}
s := Scenario{
ID: stmt.ColumnInt(0),
Name: stmt.ColumnText(1),
Title: stmt.ColumnText(2),
}
r = append(r, s)
}
return r, nil
}
type Spark struct {
ID int
Name string
Description string
Group int
Rarity int
Type int
}
type SparkEffect struct {
Target int
Value1 int
Value2 int
}
func Sparks(ctx context.Context, db *sqlitex.Pool) ([]Spark, error) {
conn, err := db.Take(ctx)
defer db.Put(conn)
if err != nil {
return nil, fmt.Errorf("couldn't get connection for sparks: %w", err)
}
stmt, _, err := conn.PrepareTransient(sparkSQL)
if err != nil {
return nil, fmt.Errorf("couldn't prepare statement for sparks: %w", err)
}
defer stmt.Finalize()
var r []Spark
for {
ok, err := stmt.Step()
if err != nil {
return nil, fmt.Errorf("error stepping sparks: %w", err)
}
if !ok {
break
}
s := Spark{
ID: stmt.ColumnInt(0),
Name: stmt.ColumnText(1),
Description: stmt.ColumnText(2),
Group: stmt.ColumnInt(3),
Rarity: stmt.ColumnInt(4),
Type: stmt.ColumnInt(5),
}
r = append(r, s)
}
return r, nil
}
func SparkEffects(ctx context.Context, db *sqlitex.Pool) (map[int]map[int][]SparkEffect, error) {
conn, err := db.Take(ctx)
defer db.Put(conn)
if err != nil {
return nil, fmt.Errorf("couldn't get connection for spark effects: %w", err)
}
stmt, _, err := conn.PrepareTransient(sparkEffectSQL)
if err != nil {
return nil, fmt.Errorf("couldn't prepare statement for spark effects: %w", err)
}
defer stmt.Finalize()
r := make(map[int]map[int][]SparkEffect)
for {
ok, err := stmt.Step()
if err != nil {
return nil, fmt.Errorf("error stepping spark effects: %w", err)
}
if !ok {
break
}
group := stmt.ColumnInt(0)
eff := stmt.ColumnInt(1)
s := SparkEffect{
Target: stmt.ColumnInt(2),
Value1: stmt.ColumnInt(3),
Value2: stmt.ColumnInt(4),
}
if r[group] == nil {
r[group] = make(map[int][]SparkEffect)
}
r[group][eff] = append(r[group][eff], s)
}
return r, nil
}
type Uma struct {
ID int
CharacterID int
Name string
Variant string
CharacterName string
Sprint, Mile, Medium, Long int
Front, Pace, Late, End int
Turf, Dirt int
UniqueID int
Skill1, Skill2, Skill3 int
SkillPL2, SkillPL3, SkillPL4, SkillPL5 int
}
func Umas(ctx context.Context, db *sqlitex.Pool) ([]Uma, error) {
conn, err := db.Take(ctx)
defer db.Put(conn)
if err != nil {
return nil, fmt.Errorf("couldn't get connection for umas: %w", err)
}
stmt, _, err := conn.PrepareTransient(umaSQL)
if err != nil {
return nil, fmt.Errorf("couldn't prepare statement for umas: %w", err)
}
defer stmt.Finalize()
var r []Uma
for {
ok, err := stmt.Step()
if err != nil {
return nil, fmt.Errorf("error stepping umas: %w", err)
}
if !ok {
break
}
uma := Uma{
ID: stmt.ColumnInt(0),
CharacterID: stmt.ColumnInt(1),
Name: stmt.ColumnText(2),
Variant: stmt.ColumnText(3),
CharacterName: stmt.ColumnText(4),
Sprint: stmt.ColumnInt(5),
Mile: stmt.ColumnInt(6),
Medium: stmt.ColumnInt(7),
Long: stmt.ColumnInt(8),
Front: stmt.ColumnInt(9),
Pace: stmt.ColumnInt(10),
Late: stmt.ColumnInt(11),
End: stmt.ColumnInt(12),
Turf: stmt.ColumnInt(13),
Dirt: stmt.ColumnInt(14),
UniqueID: stmt.ColumnInt(15),
Skill1: stmt.ColumnInt(16),
Skill2: stmt.ColumnInt(17),
Skill3: stmt.ColumnInt(18),
SkillPL2: stmt.ColumnInt(19),
SkillPL3: stmt.ColumnInt(20),
SkillPL4: stmt.ColumnInt(21),
SkillPL5: stmt.ColumnInt(22),
}
r = append(r, uma)
}
return r, nil
}
-227
View File
@@ -1,227 +0,0 @@
package main
import (
"context"
"flag"
"log/slog"
"os"
"os/signal"
"path/filepath"
"golang.org/x/sync/errgroup"
"zombiezen.com/go/sqlite"
"zombiezen.com/go/sqlite/sqlitex"
)
func main() {
var (
mdb string
out string
region string
)
flag.StringVar(&mdb, "mdb", os.ExpandEnv(`$USERPROFILE\AppData\LocalLow\Cygames\Umamusume\master\master.mdb`), "`path` to Umamusume master.mdb")
flag.StringVar(&out, "o", `horse`, "`dir`ectory for output files")
flag.StringVar(&region, "region", "global", "region the database is for (global, jp)")
flag.Parse()
pctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
go func() {
<-pctx.Done()
stop()
}()
t, err := LoadTemplates()
if err != nil {
slog.Error("loading templates", slog.Any("err", err))
os.Exit(2)
}
slog.Info("open", slog.String("mdb", mdb))
db, err := sqlitex.NewPool(mdb, sqlitex.PoolOptions{Flags: sqlite.OpenReadOnly})
if err != nil {
slog.Error("opening mdb", slog.String("mdb", mdb), slog.Any("err", err))
os.Exit(1)
}
eg, ctx := errgroup.WithContext(pctx)
var (
charas []NamedID[Character]
pairs []AffinityRelation
trios []AffinityRelation
sg []NamedID[SkillGroup]
skills []Skill
races []Race
saddles []Saddle
scens []Scenario
sparks []Spark
sparkeff map[int]map[int][]SparkEffect
umas []Uma
)
eg.Go(func() error {
slog.Info("get characters")
r, err := Characters(ctx, db)
charas = r
return err
})
eg.Go(func() error {
slog.Info("get pairs")
r, err := CharacterPairs(ctx, db)
pairs = r
return err
})
eg.Go(func() error {
slog.Info("get trios")
r, err := CharacterTrios(ctx, db)
trios = r
return err
})
eg.Go(func() error {
slog.Info("get skill groups")
r, err := SkillGroups(ctx, db)
sg = r
return err
})
eg.Go(func() error {
slog.Info("get skills")
r, err := Skills(ctx, db)
skills = r
return err
})
eg.Go(func() error {
slog.Info("get races")
r, err := Races(ctx, db)
races = r
return err
})
eg.Go(func() error {
slog.Info("get saddles")
r, err := Saddles(ctx, db)
saddles = r
return err
})
eg.Go(func() error {
slog.Info("get scenarios")
r, err := Scenarios(ctx, db)
scens = r
return err
})
eg.Go(func() error {
slog.Info("get sparks")
r, err := Sparks(ctx, db)
sparks = r
return err
})
eg.Go(func() error {
slog.Info("get spark effects")
r, err := SparkEffects(ctx, db)
sparkeff = r
return err
})
eg.Go(func() error {
slog.Info("get umas")
r, err := Umas(ctx, db)
umas = r
return err
})
if err := eg.Wait(); err != nil {
slog.Error("load", slog.Any("err", err))
os.Exit(1)
}
if err := os.MkdirAll(filepath.Join(out, region), 0775); err != nil {
slog.Error("create output dir", slog.Any("err", err))
os.Exit(1)
}
eg, ctx = errgroup.WithContext(pctx)
eg.Go(func() error {
cf, err := os.Create(filepath.Join(out, region, "character.kk"))
if err != nil {
return err
}
gf, err := os.Create(filepath.Join(out, region, "character.go"))
if err != nil {
return err
}
slog.Info("write characters")
return ExecCharacter(t, region, cf, gf, charas, pairs, trios)
})
eg.Go(func() error {
sf, err := os.Create(filepath.Join(out, region, "skill.kk"))
if err != nil {
return err
}
gf, err := os.Create(filepath.Join(out, region, "skill.go"))
if err != nil {
return err
}
slog.Info("write skills")
return ExecSkill(t, region, sf, gf, sg, skills)
})
eg.Go(func() error {
kf, err := os.Create(filepath.Join(out, region, "race.kk"))
if err != nil {
return err
}
gf, err := os.Create(filepath.Join(out, region, "race.go"))
if err != nil {
return err
}
slog.Info("write races")
return ExecRace(t, region, kf, gf, races)
})
eg.Go(func() error {
kf, err := os.Create(filepath.Join(out, region, "saddle.kk"))
if err != nil {
return err
}
gf, err := os.Create(filepath.Join(out, region, "saddle.go"))
if err != nil {
return err
}
slog.Info("write saddles")
return ExecSaddle(t, region, kf, gf, saddles)
})
eg.Go(func() error {
kf, err := os.Create(filepath.Join(out, region, "scenario.kk"))
if err != nil {
return err
}
gf, err := os.Create(filepath.Join(out, region, "scenario.go"))
if err != nil {
return err
}
slog.Info("write scenarios")
return ExecScenario(t, region, kf, gf, scens)
})
eg.Go(func() error {
kf, err := os.Create(filepath.Join(out, region, "spark.kk"))
if err != nil {
return err
}
gf, err := os.Create(filepath.Join(out, region, "spark.go"))
if err != nil {
return err
}
slog.Info("write sparks")
return ExecSparks(t, region, kf, gf, sparks, sparkeff)
})
eg.Go(func() error {
kf, err := os.Create(filepath.Join(out, region, "uma.kk"))
if err != nil {
return err
}
gf, err := os.Create(filepath.Join(out, region, "uma.go"))
if err != nil {
return err
}
slog.Info("write umas")
return ExecUmas(t, region, kf, gf, umas)
})
if err := eg.Wait(); err != nil {
slog.Error("generate", slog.Any("err", err))
os.Exit(1)
} else {
slog.Info("done")
}
}
-32
View File
@@ -1,32 +0,0 @@
{{- define "go-race" -}}
package {{ $.Region }}
// Automatically generated with horsegen; DO NOT EDIT
import . "git.sunturtle.xyz/zephyr/horse/horse"
const (
{{- range $r := $.Races }}
Race{{ goenum $r.Name }}{{ if $r.Alternate }}Alt{{ $r.ID }}{{ end }} RaceID = {{ $r.ID }} // {{ $r.Name }}
{{- end }}
)
var AllRaces = map[RaceID]Race{
{{- range $r := $.Races }}
Race{{ goenum $r.Name }}{{ if $r.Alternate }}Alt{{ $r.ID }}{{ end }}: {
ID: {{ $r.ID }},
Name: {{ printf "%q" $r.Name }}{{ if $r.Alternate }} + " (Alternate {{ $r.ID }})"{{ end }},
Thumbnail: {{ $r.ThumbnailID }},
{{- if ne $r.Primary $r.ID }}
Primary: {{ $r.Primary }},
{{- end }}
},
{{- end }}
}
var RaceNameToID = map[string]RaceID{
{{- range $r := $.Races }}
{{ printf "%q" $r.Name }}{{ if $r.Alternate }} + " (Alternate {{ $r.ID }})"{{ end }}: {{ $r.ID }},
{{- end }}
}
{{ end }}
-69
View File
@@ -1,69 +0,0 @@
{{- define "koka-race" -}}
module horse/{{ $.Region }}/race
// Automatically generated with horsegen; DO NOT EDIT
import std/core/delayed
import std/core/vector
import std/core-extras
import std/data/rb-map
import horse/game-id
pub import horse/race
extern create-id-table(): vector<int>
c inline "int32_t arr[] = { {{- range $r := $.Races }}{{ $r.ID }},{{ end -}} };\nkk_vector_from_cint32array(arr, (kk_ssize_t){{ $.RaceCount }}, kk_context())"
js inline "[{{ range $r := $.Races }}{{ $r.ID }},{{ end }}]"
// Vector of all race IDs in order for easy iterating.
pub val all = once(create-id-table)
val name2id = once()
var m: rbmap<string, int> := empty()
all().foreach() fn(id) m := m.set(Race-id(id).show, id)
m
// Get the race ID that has the given exact name.
// Alternate versions of races have an indication of their ID in their names.
// If no race matches the name, the result is an invalid ID.
pub fun from-name(name: string): race-id
Race-id(name2id().rb-map/lookup(name).default(0))
// Get the name for a race.
// Alternate versions of races have an indication of their ID in their names.
// If no race matches the ID, the result is the numeric ID.
pub fun show(r: race-id): string
match r.game-id
{{- range $r := $.Races }}
{{ $r.ID }} -> {{ printf "%q" $r.Name }}{{ if $r.Alternate }} ++ " (Alternate {{ $r.ID }})"{{ end }}
{{- end }}
x -> "race " ++ x.show
// Get the grade for a race.
// If no race matches the ID, the result is Pre-OP.
pub fun grade(r: race-id): grade
match r.game-id
{{- range $r := $.Races }}
{{ $r.ID }} -> {{ if eq $r.Grade 100 }}G1{{ else if eq $r.Grade 200 }}G2{{ else if eq $r.Grade 300 }}G3{{ else if eq $r.Grade 400 }}OP{{ else if eq $r.Grade 700 }}Pre-OP{{ else }}??? $r.Grade={{ $r.Grade }}{{ end }}
{{- end }}
_ -> Pre-OP
// Get the thumbnail ID for a race.
// If no race matches the ID, the result is an invalid ID.
pub fun thumbnail(r: race-id): race-thumbnail-id
match r.game-id
{{- range $r := $.Races }}
{{ $r.ID }} -> Race-thumbnail-id({{ $r.ThumbnailID }})
{{- end }}
_ -> Race-thumbnail-id(0)
// Get the primary ID for a race.
// For races which are the primary version, or if no race matches the given ID,
// the result is the input.
pub fun primary(r: race-id): race-id
match r.game-id
{{- range $r := $.Races }}
{{- if $r.Alternate }}
{{ $r.ID }} -> Race-id({{ $r.Primary }})
{{- end }}
{{- end }}
_ -> r
{{ end }}
-27
View File
@@ -1,27 +0,0 @@
{{- define "go-saddle" -}}
package {{ $.Region }}
// Automatically generated with horsegen; DO NOT EDIT
import . "git.sunturtle.xyz/zephyr/horse/horse"
const (
{{- range $s := $.Saddles }}
Saddle{{ goenum $s.Name }}{{ if $s.Alternate }}Alt{{ $s.Alternate }}{{ end }} SaddleID = {{ $s.ID }} // {{ $s.Name }}
{{- end }}
)
var AllSaddles = map[SaddleID]Saddle{
{{- range $s := $.Saddles }}
Saddle{{ goenum $s.Name }}{{ if $s.Alternate }}Alt{{ $s.Alternate }}{{ end }}: {
ID: {{ $s.ID }},
Name: {{ printf "%q" $s.Name }}{{ if $s.Alternate }} + " (Alternate {{ $s.Alternate }})"{{ end }},
Races: []RaceID{ {{- range $id := $s.Races }}{{ if $id }}{{ $id }}, {{ end }}{{ end -}} },
Type: SaddleType{{ if eq $s.Type 0 }}Honor{{ else if eq $s.Type 1 }}G3{{ else if eq $s.Type 2 }}G2{{ else if eq $s.Type 3 }}G1{{ else }}??? $s.Type={{ $s.Type }}{{ end }},
{{- if $s.Alternate }}
Primary: {{ $s.Primary }},
{{- end }}
},
{{- end }}
}
{{ end }}
-58
View File
@@ -1,58 +0,0 @@
{{- define "koka-saddle" -}}
module horse/{{ $.Region }}/saddle
// Automatically generated with horsegen; DO NOT EDIT
import std/core/delayed
import std/core/vector
import std/core-extras
import horse/game-id
pub import horse/race
pub import horse/{{ $.Region }}/race
extern create-id-table(): vector<int>
c inline "int32_t arr[] = { {{- range $s := $.Saddles }}{{ $s.ID }},{{ end -}} };\nkk_vector_from_cint32array(arr, (kk_ssize_t){{ $.SaddleCount }}, kk_context())"
js inline "[{{ range $s := $.Saddles }}{{ $s.ID }},{{ end }}]"
// Vector of all saddle IDs in order for easy iterating.
pub val all = once(create-id-table)
// Get the name for a saddle.
// Alternate versions of saddles have an indication of their ID in their names.
// If no saddle matches the ID, the result contains the numeric ID.
pub fun show(s: saddle-id): string
match s.game-id
{{- range $s := $.Saddles }}
{{ $s.ID }} -> {{ printf "%q" $s.Name }}{{ if $s.Alternate }} ++ " (Alternate {{ $s.ID }})"{{ end }}
{{- end }}
x -> "saddle " ++ x.show
// Get the list of races that entitle a horse to a saddle.
// If no saddle matches the ID, the result is the empty list.
pub fun races(s: saddle-id): list<race-id>
match s.game-id
{{- range $s := $.Saddles }}
{{ $s.ID }} -> [{{ range $id := $s.Races }}{{ if $id }}Race-id({{ $id }}), {{ end }}{{ end }}]
{{- end }}
_ -> []
// Get a saddle's type.
// If no saddle matches the ID, the result is Honor.
pub fun saddle-type(s: saddle-id): saddle-type
match s.game-id
{{- range $s := $.Saddles }}
{{ $s.ID }} -> {{ if eq $s.Type 0 }}Honor{{ else if eq $s.Type 1 }}G3-Win{{ else if eq $s.Type 2 }}G2-Win{{ else if eq $s.Type 3 }}G1-Win{{ else }}??? $s.Type={{ $s.Type }}{{ end }}
{{- end }}
_ -> Honor
// Get the primary ID for a saddle.
// For saddles which are the primary version, or if no saddle matches the given ID,
// the result is the input.
pub fun primary(s: saddle-id): saddle-id
match s.game-id
{{- range $s := $.Saddles }}
{{- if $s.Alternate }}
{{ $s.ID }} -> Saddle-id({{ $s.Primary }})
{{- end }}
{{- end }}
_ -> s
{{ end }}
-23
View File
@@ -1,23 +0,0 @@
{{- define "go-scenario" -}}
package {{ $.Region }}
// Automatically generated with horsegen; DO NOT EDIT
import . "git.sunturtle.xyz/zephyr/horse/horse"
const (
{{- range $s := $.Scenarios }}
Scenario{{ goenum $s.Name }} ScenarioID = {{ $s.ID }} // {{ $s.Name }}
{{- end }}
)
var AllScenarios = map[ScenarioID]Scenario{
{{- range $s := $.Scenarios }}
Scenario{{ goenum $s.Name }}: {
ID: {{ $s.ID }},
Name: {{ printf "%q" $s.Name }},
Title: {{ printf "%q" $s.Title }},
},
{{- end }}
}
{{ end }}
-34
View File
@@ -1,34 +0,0 @@
{{- define "koka-scenario" -}}
module horse/{{ $.Region }}/scenario
// Automatically generated with horsegen; DO NOT EDIT
import std/core/delayed
import std/core/vector
import std/core-extras
import horse/game-id
extern create-id-table(): vector<int>
c inline "int32_t arr[] = { {{- range $s := $.Scenarios }}{{ $s.ID }},{{ end -}} };\nkk_vector_from_cint32array(arr, (kk_ssize_t){{ $.ScenarioCount }}, kk_context())"
js inline "[{{ range $s := $.Scenarios }}{{ $s.ID }},{{ end }}]"
// Vector of all scenario IDs in order for easy iterating.
pub val all = once(create-id-table)
// Get the name for a scenario.
// If no scenario matches the ID, the result contains the numeric ID.
pub fun show(s: scenario-id): string
match s.game-id
{{- range $s := $.Scenarios }}
{{ $s.ID }} -> {{ printf "%q" $s.Name }}
{{- end }}
x -> "scenario " ++ x.show
// Get the full title for a scenario, e.g. "The Beginning: URA Finale".
// If no scenario matches the ID, the result contains the numeric ID.
pub fun title(s: scenario-id): string
match s.game-id
{{- range $s := $.Scenarios }}
{{ $s.ID }} -> {{ printf "%q" $s.Title }}
{{- end }}
x -> "scenario " ++ x.show
{{ end }}
-14
View File
@@ -1,14 +0,0 @@
WITH skill_names AS (
SELECT
"index" AS "id",
"text" AS "name"
FROM text_data
WHERE category = 47
)
SELECT
group_id,
name
FROM skill_data d
JOIN skill_names n ON d.id = n.id
WHERE d.group_rate = 1
ORDER BY group_id
-79
View File
@@ -1,79 +0,0 @@
{{- define "go-skill-data" -}}
package {{ $.Region }}
// Automatically generated with horsegen; DO NOT EDIT
import . "git.sunturtle.xyz/zephyr/horse/horse"
const (
{{- range $s := $.Skills }}
Skill{{ goenum $s.Name }}{{ if ne $s.InheritID 0 }}Inherit{{ end }} SkillID = {{ $s.ID }} // {{ $s.Name }}
{{- end }}
)
var OrderedSkills = [...]SkillID{
{{- range $s := $.Skills }}
Skill{{ goenum $s.Name }}{{ if ne $s.InheritID 0 }}Inherit{{ end }},
{{- end }}
}
var AllSkills = map[SkillID]Skill{
{{- range $s := $.Skills }}
Skill{{ goenum $s.Name }}{{ if ne $s.InheritID 0 }}Inherit{{ end }}: {
ID: {{ $s.ID }},
Name: {{ printf "%q" $s.Name }}{{ if ne $s.InheritID 0 }} + " (Inherited)"{{ end }},
Description: {{ printf "%q" $s.Description }},
Group: {{ $s.GroupID }},
Rarity: {{ $s.Rarity }},
GroupRate: {{ $s.GroupRate }},
GradeValue: {{ $s.GradeValue }},
{{- if $s.WitCheck }}
WitCheck: {{ $s.WitCheck }},
{{- end }}
Activations: []Activation{
{{- range $a := $s.Activations }}
{{- if ne $a.Condition "" }}
{
{{- if $a.Precondition }}
Precondition: {{ printf "%q" $a.Precondition }},
{{- end }}
Condition: {{ printf "%q" $a.Condition }},
Duration: {{ $a.Duration }},
DurScale: {{ $a.DurScale }},
{{- if $a.Cooldown }}
Cooldown: {{ $a.Cooldown }},
{{- end }}
Abilities: []Ability{
{{- range $abil := $a.Abilities }}
{{- if ne $abil.Type 0 }}
{Type: {{ $abil.Type }}, ValueUsage: {{ $abil.ValueUsage }}, Value: {{ $abil.Value }}, Target: {{ $abil.Target }}, TargetValue: {{ $abil.TargetValue -}} },
{{- end }}
{{- end }}
},
},
{{- end }}
{{- end }}
},
{{- if $s.UniqueOwner }}
UniqueOwner: {{ printf "%q" $s.UniqueOwner }},
{{- end }}
{{- if $s.SPCost }}
SPCost: {{ $s.SPCost }},
{{- end }}
IconID: {{ $s.IconID }},
},
{{- end }}
}
var SkillNameToID = map[string]SkillID{
{{- range $s := $.Skills }}
{{ printf "%q" $s.Name }}{{ if ne $s.InheritID 0 }} + " (Inherited)"{{ end }}: {{ $s.ID }},
{{- end }}
}
var SkillGroups = map[int32][4]SkillID{
{{- range $g := $.Groups }}
{{ $g.ID }}: { {{- range $s := index $.Related $g.ID }}Skill{{ goenum $s.Name }}{{ if ne $s.InheritID 0 }}Inherit{{ end }}, {{ end -}} },
{{- end }}
}
{{ end }}
-230
View File
@@ -1,230 +0,0 @@
{{- define "koka-skill" -}}
module horse/{{ $.Region }}/skill
// Automatically generated with horsegen; DO NOT EDIT
import std/core/delayed
import std/core/vector
import std/core-extras
import std/data/rb-map
import std/num/decimal
import horse/game-id
import horse/movement
pub import horse/skill
extern create-id-table(): vector<int>
c inline "int32_t arr[] = { {{- range $s := $.Skills }}{{ $s.ID }},{{ end -}} };\nkk_vector_from_cint32array(arr, (kk_ssize_t){{ $.SkillCount }}, kk_context())"
js inline "[{{ range $s := $.Skills }}{{ $s.ID }},{{ end }}]"
// Vector of all skill ID values in order for easy iterating.
pub val all = once(create-id-table)
val name2id = once()
var m: rbmap<string, int> := empty()
all().foreach() fn(id) m := m.set(Skill-id(id).show, id)
m
// Get the skill ID that has the given exact name.
// Inherited skills have `" (Inherited)"` appended to their names.
// If no skill matches the name, the result is an invalid ID.
pub fun from-name(name: string): skill-id
Skill-id(name2id().rb-map/lookup(name).default(0))
// Get the name for a skill.
// Inherited skills have `" (Inherited)"` appended to their names.
// 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 }}
{{ $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 }},
dur-scale = {{ if eq $a.DurScale 1 }}Direct-Dur
{{- else if eq $a.DurScale 2 }}Front-Distance-Dur
{{- else if eq $a.DurScale 3 }}Multiply-Remaining-HP
{{- else if eq $a.DurScale 4 }}Increment-Pass
{{- else if eq $a.DurScale 5 }}Midrace-Side-Block-Time-Dur
{{- else if eq $a.DurScale 7 }}Multiply-Remaining-HP2
{{- else }}??? $a.DurScale={{ $a.DurScale }}
{{- 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<uma-id>
match s.game-id
{{- range $s := $.Skills }}
{{- if $s.UniqueOwnerID }}
{{ $s.ID }} -> Just(Uma-id({{ $s.UniqueOwnerID }}))
{{- end }}
{{- end }}
_ -> Nothing
// Get the SP cost of a skill.
// If there is no skill with the given ID, the result is 0.
pub fun sp-cost(s: skill-id): int
match s.game-id
{{- range $s := $.Skills }}
{{ $s.ID }} -> {{ $s.SPCost }}
{{- end }}
_ -> 0
// Get the icon ID of a skill.
// If there is no skill with the given ID, the result is an invalid ID.
pub fun icon-id(s: skill-id): skill-icon-id
match s.game-id
{{- range $s := $.Skills }}
{{ $s.ID }} -> Skill-icon-id({{ $s.IconID }})
{{- end }}
_ -> Skill-icon-id(0)
// Get the name for a skill group.
// 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.
pub fun skill-group/show(sg: skill-group-id): string
match sg.game-id
{{- range $g := $.Groups }}
{{ $g.ID }} -> {{- printf "%q" $g.Name -}}
{{- end }}
x -> "skill group " ++ x.show
// Get the list of skills in a skill group.
pub fun skill-group/skills(sg: skill-group-id): list<skill-id>
match sg.game-id
{{- range $g := $.Groups }}
{{ $g.ID }} -> [ {{- range $s := index $.Related $g.ID }}Skill-id({{ $s.ID }}), {{ end -}} ]
{{- end }}
_ -> Nil
{{- end }}
-9
View File
@@ -1,9 +0,0 @@
SELECT
factor_group_id,
effect_id,
target_type,
value_1,
value_2
FROM succession_factor_effect
WHERE factor_group_id NOT IN (40001) -- exclude Carnival Bonus
ORDER BY factor_group_id, effect_id, target_type
-35
View File
@@ -1,35 +0,0 @@
{{- define "go-spark" -}}
package {{ $.Region }}
// Automatically generated with horsegen; DO NOT EDIT
import . "git.sunturtle.xyz/zephyr/horse/horse"
const (
{{- range $s := $.Sparks }}
Spark{{ goenum $s.Name }}Lv{{ $s.Rarity }} SparkID = {{ $s.ID }} // {{ $s.Name }}
{{- end }}
)
var AllSparks = map[SparkID]Spark{
{{- range $s := $.Sparks }}
Spark{{ goenum $s.Name }}Lv{{ $s.Rarity }}: {
ID: {{ $s.ID }},
Name: {{ printf "%q" $s.Name }},
Description: {{ printf "%q" $s.Description }},
Group: {{ $s.Group }},
Rarity: {{ $s.Rarity }},
Type: {{ $s.Type }},
Effects: [][]SparkEffect{
{{- range $r := index $.SparkEffects $s.Group }}
{
{{- range $e := $r -}}
{ {{- $e.Target }}, {{ $e.Value1 }}, {{ $e.Value2 -}} },
{{- end -}}
},
{{- end }}
},
},
{{- end }}
}
{{ end }}
-121
View File
@@ -1,121 +0,0 @@
{{- define "koka-spark" -}}
module horse/{{ $.Region }}/spark
// Automatically generated with horsegen; DO NOT EDIT
import std/core/delayed
import std/core/vector
import std/core-extras
import horse/game-id
pub import horse/spark
extern create-id-table(): vector<int>
c inline "int32_t arr[] = { {{- range $s := $.Sparks }}{{ $s.ID }},{{ end -}} };\nkk_vector_from_cint32array(arr, (kk_ssize_t){{ $.SparkCount }}, kk_context())"
js inline "[{{ range $s := $.Sparks }}{{ $s.ID }},{{ end }}]"
// Vector of all spark IDs in order for easy iterating.
pub val all = once(create-id-table)
// Get the name for a spark.
// The name does not indicate the spark level.
// If no spark matches the ID, the result contains the numeric ID.
pub fun show(s: spark-id): string
match s.game-id
{{- range $s := $.Sparks }}
{{ $s.ID }} -> {{ printf "%q" $s.Name }}
{{- end }}
x -> "spark " ++ x.show
// Get the description for a spark.
// The description does not indicate the spark level.
// If no spark matches the ID, the result contains the numeric ID.
pub fun description(s: spark-id): string
match s.game-id
{{- range $s := $.Sparks }}
{{ $s.ID }} -> {{ printf "%q" $s.Description }}
{{- end }}
x -> "spark " ++ x.show
// Get the spark group ID of a spark.
// If no spark matches the ID, the result is an invalid ID.
pub fun spark-group(s: spark-id): spark-group-id
match s.game-id
{{- range $s := $.Sparks }}
{{ $s.ID }} -> Spark-group-id({{ $s.Group }})
{{- end }}
_ -> Spark-group-id(0)
// Get the rarity (level or star count) of a spark.
// If no spark matches the ID, the result is One.
pub fun rarity(s: spark-id): rarity
match s.game-id
{{- range $s := $.Sparks }}
{{ $s.ID }} -> {{ if eq $s.Rarity 1 }}One{{ else if eq $s.Rarity 2 }}Two{{ else if eq $s.Rarity 3 }}Three{{ else }}??? $s.Rarity={{ $s.Rarity }}{{ end }}
{{- end }}
_ -> One
// Get the type of a spark.
// If no spark matches the ID, the result is Stat.
pub fun spark-type(s: spark-id): spark-type
match s.game-id
{{- range $s := $.Sparks }}
{{ $s.ID }} -> {{ if eq $s.Type 1 }}Stat
{{- else if eq $s.Type 2 }}Aptitude
{{- else if eq $s.Type 5 }}Race
{{- else if eq $s.Type 4 }}Skill
{{- else if eq $s.Type 6 }}Scenario
{{- else if eq $s.Type 7 }}Carnival-Bonus
{{- else if eq $s.Type 10 }}Surface
{{- else if eq $s.Type 8 }}Distance
{{- else if eq $s.Type 11 }}Style
{{- else if eq $s.Type 9 }}Hidden
{{- else if eq $s.Type 3 }}Unique
{{- else }}??? $s.Type={{ $s.Type }}
{{- end }}
{{- end }}
_ -> Stat
// Get the list of all effects a spark can apply during inheritance.
// When a spark procs, a random element is chosen from the list yielded by this
// function according to a hidden distribution, then all effects in that are applied.
// If no spark matches the ID, the result is the empty list.
pub fun effects(s: spark-id): list<list<spark-effect>>
match s.game-id
{{- range $s := $.Sparks }}
{{ $s.ID }} -> [
{{- range $r := index $.SparkEffects $s.Group }}
[
{{- range $e := $r -}}
{{- if eq $e.Target 1 -}}Stat-Up(Speed, {{ $e.Value1 }}),
{{- else if eq $e.Target 2 -}}Stat-Up(Stamina, {{ $e.Value1 }}),
{{- else if eq $e.Target 3 -}}Stat-Up(Power, {{ $e.Value1 }}),
{{- else if eq $e.Target 4 -}}Stat-Up(Guts, {{ $e.Value1 }}),
{{- else if eq $e.Target 5 -}}Stat-Up(Wit, {{ $e.Value1 }}),
{{- else if eq $e.Target 6 -}}SP-Up({{ $e.Value1 }}),
{{- else if eq $e.Target 7 -}}Random-Stat-Up({{ $e.Value1 }}),
{{- else if eq $e.Target 11 -}}Aptitude-Up(Turf, {{ $e.Value1 }}),
{{- else if eq $e.Target 12 -}}Aptitude-Up(Dirt, {{ $e.Value1 }}),
{{- else if eq $e.Target 21 -}}Aptitude-Up(Front-Runner, {{ $e.Value1 }}),
{{- else if eq $e.Target 22 -}}Aptitude-Up(Pace-Chaser, {{ $e.Value1 }}),
{{- else if eq $e.Target 23 -}}Aptitude-Up(Late-Surger, {{ $e.Value1 }}),
{{- else if eq $e.Target 24 -}}Aptitude-Up(End-Closer, {{ $e.Value1 }}),
{{- else if eq $e.Target 31 -}}Aptitude-Up(Sprint, {{ $e.Value1 }}),
{{- else if eq $e.Target 32 -}}Aptitude-Up(Mile, {{ $e.Value1 }}),
{{- else if eq $e.Target 33 -}}Aptitude-Up(Medium, {{ $e.Value1 }}),
{{- else if eq $e.Target 34 -}}Aptitude-Up(Long, {{ $e.Value1 }}),
{{- else if eq $e.Target 41 -}}Skill-Hint(Skill-id({{ $e.Value1 }}), {{ $e.Value2 }}),
{{- else if eq $e.Target 51 -}}Carnival-Bonus {{/*- skipped, but doesn't hurt to put it here -*/}}
{{- else if eq $e.Target 61 -}}Stat-Cap-Up(Speed, {{ $e.Value1 }}),
{{- else if eq $e.Target 62 -}}Stat-Cap-Up(Stamina, {{ $e.Value1 }}),
{{- else if eq $e.Target 63 -}}Stat-Cap-Up(Power, {{ $e.Value1 }}),
{{- else if eq $e.Target 64 -}}Stat-Cap-Up(Guts, {{ $e.Value1 }}),
{{- else if eq $e.Target 65 -}}Stat-Cap-Up(Wit, {{ $e.Value1 }}),
{{- else -}}
??? $e.Target={{- $e.Target -}}
{{- end -}}
{{- end -}}
],
{{- end }}
]
{{- end }}
_ -> []
{{ end }}
-42
View File
@@ -1,42 +0,0 @@
{{- define "go-uma" -}}
package {{ $.Region }}
// Automatically generated with horsegen; DO NOT EDIT
import . "git.sunturtle.xyz/zephyr/horse/horse"
const (
{{- range $uma := $.Umas }}
{{ goenum $uma.CharacterName }}{{ goenum $uma.Variant }} UmaID = {{ $uma.ID }} // {{ $uma.Name }}
{{- end }}
)
var AllUmas = map[UmaID]Uma{
{{- range $uma := $.Umas }}
{{ goenum $uma.CharacterName }}{{ goenum $uma.Variant }}: {
ID: {{ $uma.ID }},
CharacterID: {{ $uma.CharacterID }},
Name: {{ printf "%q" $uma.Name }},
Variant: {{ printf "%q" $uma.Variant }},
Sprint: {{ $uma.Sprint }},
Mile: {{ $uma.Mile }},
Medium: {{ $uma.Medium }},
Long: {{ $uma.Long }},
Front: {{ $uma.Front }},
Pace: {{ $uma.Pace }},
Late: {{ $uma.Late }},
End: {{ $uma.End }},
Turf: {{ $uma.Turf }},
Dirt: {{ $uma.Dirt }},
Unique: {{ $uma.UniqueID }},
Skill1: {{ $uma.Skill1 }},
Skill2: {{ $uma.Skill2 }},
Skill3: {{ $uma.Skill3 }},
SkillPL2: {{ $uma.SkillPL2 }},
SkillPL3: {{ $uma.SkillPL3 }},
SkillPL4: {{ $uma.SkillPL4 }},
SkillPL5: {{ $uma.SkillPL5 }},
},
{{- end }}
}
{{ end }}
-221
View File
@@ -1,221 +0,0 @@
{{- define "koka-uma" -}}
module horse/{{ $.Region }}/uma
// Automatically generated with horsegen; DO NOT EDIT
import std/core/delayed
import std/core/vector
import std/core-extras
import horse/game-id
import horse/movement
pub import horse/uma
extern create-id-table(): vector<int>
c inline "int32_t arr[] = { {{- range $uma := $.Umas }}{{ $uma.ID }},{{ end -}} };\nkk_vector_from_cint32array(arr, (kk_ssize_t){{ $.UmaCount }}, kk_context())"
js inline "[{{ range $uma := $.Umas }}{{ $uma.ID }},{{ end }}]"
// Vector of all Uma IDs in order for easy iterating.
pub val all = once(create-id-table)
// Get the name for an Uma.
// The name includes the costume variant, e.g. `[Special Dreamer] Special Week`.
// If no Uma matches the ID, the result contains the numeric ID.
pub fun show(uma: uma-id): string
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ printf "%q" $uma.Name }}
{{- end }}
x -> "uma " ++ x.show
// Get the costume variant for an Uma, e.g. `[Special Dreamer]`.
// If no Uma matches the ID, the result contains the numeric ID.
pub fun variant(uma: uma-id): string
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ printf "%q" $uma.Variant }}
{{- end }}
x -> "uma " ++ x.show
// Get the character ID for an Uma.
// If no Uma matches the ID, the result is an invalid ID.
pub fun character-id(uma: uma-id): character-id
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> Character-id({{ $uma.CharacterID }})
{{- end }}
_ -> Character-id(0)
// Get the sprint aptitude for an Uma.
// If no Uma matches the ID, the result is G.
pub fun sprint(uma: uma-id): aptitude-level
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Sprint }}
{{- end }}
_ -> G
// Get the mile aptitude for an Uma.
// If no Uma matches the ID, the result is G.
pub fun mile(uma: uma-id): aptitude-level
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Mile }}
{{- end }}
_ -> G
// Get the medium aptitude for an Uma.
// If no Uma matches the ID, the result is G.
pub fun medium(uma: uma-id): aptitude-level
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Medium }}
{{- end }}
_ -> G
// Get the long aptitude for an Uma.
// If no Uma matches the ID, the result is G.
pub fun long(uma: uma-id): aptitude-level
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Long }}
{{- end }}
_ -> G
// Get the front runner aptitude for an Uma.
// If no Uma matches the ID, the result is G.
pub fun front-runner(uma: uma-id): aptitude-level
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Front }}
{{- end }}
_ -> G
// Get the pace chaser aptitude for an Uma.
// If no Uma matches the ID, the result is G.
pub fun pace-chaser(uma: uma-id): aptitude-level
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Pace }}
{{- end }}
_ -> G
// Get the late surger aptitude for an Uma.
// If no Uma matches the ID, the result is G.
pub fun late-surger(uma: uma-id): aptitude-level
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Late }}
{{- end }}
_ -> G
// Get the end closer aptitude for an Uma.
// If no Uma matches the ID, the result is G.
pub fun end-closer(uma: uma-id): aptitude-level
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.End }}
{{- end }}
_ -> G
// Get the turf aptitude for an Uma.
// If no Uma matches the ID, the result is G.
pub fun turf(uma: uma-id): aptitude-level
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Turf }}
{{- end }}
_ -> G
// Get the dirt aptitude for an Uma.
// If no Uma matches the ID, the result is G.
pub fun dirt(uma: uma-id): aptitude-level
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> {{ template "koka-aptitude-level" $uma.Dirt }}
{{- end }}
_ -> G
// Get the unique skill for an Uma.
// If no Uma matches the ID, the result is an invalid ID.
pub fun unique(uma: uma-id): skill-id
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> Skill-id({{ $uma.UniqueID }})
{{- end }}
_ -> Skill-id(0)
// Get the first built-in skill for an Uma.
// If no Uma matches the ID, the result is an invalid ID.
pub fun skill1(uma: uma-id): skill-id
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> Skill-id({{ $uma.Skill1 }})
{{- end }}
_ -> Skill-id(0)
// Get the second built-in skill for an Uma.
// If no Uma matches the ID, the result is an invalid ID.
pub fun skill2(uma: uma-id): skill-id
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> Skill-id({{ $uma.Skill2 }})
{{- end }}
_ -> Skill-id(0)
// Get the third built-in skill for an Uma.
// If no Uma matches the ID, the result is an invalid ID.
pub fun skill3(uma: uma-id): skill-id
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> Skill-id({{ $uma.Skill3 }})
{{- end }}
_ -> Skill-id(0)
// Get the potential level 2 skill for an Uma.
// If no Uma matches the ID, the result is an invalid ID.
pub fun skill-pl2(uma: uma-id): skill-id
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> Skill-id({{ $uma.SkillPL2 }})
{{- end }}
_ -> Skill-id(0)
// Get the potential level 3 skill for an Uma.
// If no Uma matches the ID, the result is an invalid ID.
pub fun skill-pl3(uma: uma-id): skill-id
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> Skill-id({{ $uma.SkillPL3 }})
{{- end }}
_ -> Skill-id(0)
// Get the potential level 4 skill for an Uma.
// If no Uma matches the ID, the result is an invalid ID.
pub fun skill-pl4(uma: uma-id): skill-id
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> Skill-id({{ $uma.SkillPL4 }})
{{- end }}
_ -> Skill-id(0)
// Get the potential level 5 skill for an Uma.
// If no Uma matches the ID, the result is an invalid ID.
pub fun skill-pl5(uma: uma-id): skill-id
match uma.game-id
{{- range $uma := $.Umas }}
{{ $uma.ID }} -> Skill-id({{ $uma.SkillPL5 }})
{{- end }}
_ -> Skill-id(0)
{{ end }}
{{- define "koka-aptitude-level" -}}
{{- if eq . 1 -}} G
{{- else if eq . 2 -}} F
{{- else if eq . 3 -}} E
{{- else if eq . 4 -}} D
{{- else if eq . 5 -}} C
{{- else if eq . 6 -}} B
{{- else if eq . 7 -}} A
{{- else if eq . 8 -}} S
{{- else -}} ??? aptitude={{ . }}
{{- end -}}
{{- end -}}
+47
View File
@@ -0,0 +1,47 @@
// Code generated by "stringer -type LobbyConversationLocationID -trimprefix Lobby -linecomment"; DO NOT EDIT.
package horse
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[LobbyRightFront1-110]
_ = x[LobbyRightFront2-120]
_ = x[LobbyRightFront3-130]
_ = x[LobbyLeftTable1-210]
_ = x[LobbyLeftTable2-220]
_ = x[LobbyBackSeat-310]
_ = x[LobbyPosters1-410]
_ = x[LobbyPosters2-420]
_ = x[LobbyPosters3-430]
_ = x[LobbySchoolMap1-510]
_ = x[LobbySchoolMap2-520]
_ = x[LobbySchoolMap3-530]
}
const _LobbyConversationLocationID_name = "right side frontright side frontright side frontleft side tableleft side tablecenter back seatcenter posterscenter posterscenter postersleft side school mapleft side school mapleft side school map"
var _LobbyConversationLocationID_map = map[LobbyConversationLocationID]string{
110: _LobbyConversationLocationID_name[0:16],
120: _LobbyConversationLocationID_name[16:32],
130: _LobbyConversationLocationID_name[32:48],
210: _LobbyConversationLocationID_name[48:63],
220: _LobbyConversationLocationID_name[63:78],
310: _LobbyConversationLocationID_name[78:94],
410: _LobbyConversationLocationID_name[94:108],
420: _LobbyConversationLocationID_name[108:122],
430: _LobbyConversationLocationID_name[122:136],
510: _LobbyConversationLocationID_name[136:156],
520: _LobbyConversationLocationID_name[156:176],
530: _LobbyConversationLocationID_name[176:196],
}
func (i LobbyConversationLocationID) String() string {
if str, ok := _LobbyConversationLocationID_map[i]; ok {
return str
}
return "LobbyConversationLocationID(" + strconv.FormatInt(int64(i), 10) + ")"
}
+102
View File
@@ -0,0 +1,102 @@
package mdb
import (
"context"
_ "embed"
"zombiezen.com/go/sqlite"
"zombiezen.com/go/sqlite/sqlitex"
"git.sunturtle.xyz/zephyr/horse"
)
var (
//go:embed sql/character.sql
characterSQL string
//go:embed sql/affinity.sql
affinitySQL string
//go:embed sql/affinity-detail.sql
affinityDetailSQL string
//go:embed sql/uma.sql
umaSQL string
//go:embed sql/conversation.sql
conversationSQL string
)
// Characters retrieves all named characters.
func Characters(ctx context.Context, db *sqlitex.Pool) ([]horse.Character, error) {
return load(ctx, db, make([]horse.Character, 0, 128), characterSQL, func(s *sqlite.Stmt) horse.Character {
return horse.Character{
ID: horse.CharacterID(s.ColumnInt(0)),
Name: s.ColumnText(1),
}
})
}
// AffinitySummary gets precomputed base affinity for all pairs and trios of characters.
func AffinitySummary(ctx context.Context, db *sqlitex.Pool) ([]horse.AffinityRelation, error) {
return load(ctx, db, nil, affinitySQL, func(s *sqlite.Stmt) horse.AffinityRelation {
return horse.AffinityRelation{
IDA: horse.CharacterID(s.ColumnInt(0)),
IDB: horse.CharacterID(s.ColumnInt(1)),
IDC: horse.CharacterID(s.ColumnInt(2)),
Affinity: s.ColumnInt(3),
}
})
}
// AffinityDetail gets all individual affinity group entries.
func AffinityDetail(ctx context.Context, db *sqlitex.Pool) ([]horse.AffinityDetail, error) {
return load(ctx, db, nil, affinityDetailSQL, func(s *sqlite.Stmt) horse.AffinityDetail {
return horse.AffinityDetail{
CharaID: horse.CharacterID(s.ColumnInt(0)),
Relation: uint16(s.ColumnInt(1)),
Affinity: uint16(s.ColumnInt(2)),
}
})
}
// Umas retrieves all trainable Uma variants.
func Umas(ctx context.Context, db *sqlitex.Pool) ([]horse.Uma, error) {
return load(ctx, db, make([]horse.Uma, 0, 128), umaSQL, func(s *sqlite.Stmt) horse.Uma {
return horse.Uma{
ID: horse.UmaID(s.ColumnInt(0)),
CharacterID: horse.CharacterID(s.ColumnInt(1)),
Name: s.ColumnText(2),
Variant: s.ColumnText(3),
Sprint: horse.AptitudeLevel(s.ColumnInt(5)),
Mile: horse.AptitudeLevel(s.ColumnInt(6)),
Medium: horse.AptitudeLevel(s.ColumnInt(7)),
Long: horse.AptitudeLevel(s.ColumnInt(8)),
Front: horse.AptitudeLevel(s.ColumnInt(9)),
Pace: horse.AptitudeLevel(s.ColumnInt(10)),
Late: horse.AptitudeLevel(s.ColumnInt(11)),
End: horse.AptitudeLevel(s.ColumnInt(12)),
Turf: horse.AptitudeLevel(s.ColumnInt(13)),
Dirt: horse.AptitudeLevel(s.ColumnInt(14)),
Unique: horse.SkillID(s.ColumnInt(15)),
Skill1: horse.SkillID(s.ColumnInt(16)),
Skill2: horse.SkillID(s.ColumnInt(17)),
Skill3: horse.SkillID(s.ColumnInt(18)),
SkillPL2: horse.SkillID(s.ColumnInt(19)),
SkillPL3: horse.SkillID(s.ColumnInt(20)),
SkillPL4: horse.SkillID(s.ColumnInt(21)),
SkillPL5: horse.SkillID(s.ColumnInt(22)),
}
})
}
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 horse.Conversation{
ID: s.ColumnInt32(0),
CharacterID: horse.CharacterID(s.ColumnInt(1)),
Number: s.ColumnInt(2),
Location: horse.LobbyConversationLocationID(s.ColumnInt(3)),
Chara1: horse.CharacterID(s.ColumnInt(4)),
Chara2: horse.CharacterID(s.ColumnInt(5)),
Chara3: horse.CharacterID(s.ColumnInt(6)),
ConditionType: s.ColumnInt(7),
}
})
}
+336
View File
@@ -0,0 +1,336 @@
package mdb_test
import (
_ "embed"
"testing"
"git.sunturtle.xyz/zephyr/horse"
"git.sunturtle.xyz/zephyr/horse/mdb"
"github.com/google/go-cmp/cmp"
)
//go:embed testdata/character.sql
var characterSQL string
func TestCharacters(t *testing.T) {
db := testdb(t.Context(), "file:TestCharacters?mode=memory&cache=shared", characterSQL)
got, err := mdb.Characters(t.Context(), db)
if err != nil {
t.Error(err)
}
want := []horse.Character{
{ID: 1001, Name: "Special Week"},
{ID: 1002, Name: "Silence Suzuka"},
{ID: 1078, Name: "Yamanin Zephyr"},
{ID: 2001, Name: "Happy Meek"},
{ID: 9001, Name: "Tazuna Hayakawa"},
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("wrong characters (+got/-want):\n%s", diff)
}
}
func TestAffinitySummary(t *testing.T) {
db := testdb(t.Context(), "file:TestAffinitySummary?mode=memory&cache=shared", characterSQL)
got, err := mdb.AffinitySummary(t.Context(), db)
if err != nil {
t.Error(err)
}
want := []horse.AffinityRelation{
{IDA: 1001, IDB: 1002, Affinity: 23},
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("wrong affinity (+got/-want):\n%s", diff)
}
}
func TestAffinityDetail(t *testing.T) {
db := testdb(t.Context(), "file:TestAffinityDetail?mode=memory&cache=shared", characterSQL)
got, err := mdb.AffinityDetail(t.Context(), db)
if err != nil {
t.Error(err)
}
want := []horse.AffinityDetail{
{CharaID: 1001, Relation: 103, Affinity: 2},
{CharaID: 1001, Relation: 202, Affinity: 2},
{CharaID: 1001, Relation: 301, Affinity: 2},
{CharaID: 1001, Relation: 401, Affinity: 2},
{CharaID: 1001, Relation: 502, Affinity: 2},
{CharaID: 1001, Relation: 510, Affinity: 2},
{CharaID: 1001, Relation: 2001, Affinity: 1},
{CharaID: 1001, Relation: 2102, Affinity: 1},
{CharaID: 1001, Relation: 2301, Affinity: 1},
{CharaID: 1001, Relation: 2406, Affinity: 1},
{CharaID: 1001, Relation: 2506, Affinity: 1},
{CharaID: 1001, Relation: 2510, Affinity: 1},
{CharaID: 1001, Relation: 2516, Affinity: 1},
{CharaID: 1001, Relation: 2519, Affinity: 1},
{CharaID: 1001, Relation: 2601, Affinity: 1},
{CharaID: 1001, Relation: 2603, Affinity: 1},
{CharaID: 1001, Relation: 2811, Affinity: 1},
{CharaID: 1001, Relation: 2903, Affinity: 7},
{CharaID: 1001, Relation: 3003, Affinity: 7},
{CharaID: 1001, Relation: 3004, Affinity: 7},
{CharaID: 1001, Relation: 3101, Affinity: 7},
{CharaID: 1001, Relation: 3205, Affinity: 1},
{CharaID: 1001, Relation: 7003, Affinity: 1},
{CharaID: 1001, Relation: 7004, Affinity: 1},
{CharaID: 1001, Relation: 7005, Affinity: 1},
{CharaID: 1001, Relation: 7006, Affinity: 1},
{CharaID: 1001, Relation: 7007, Affinity: 1},
{CharaID: 1001, Relation: 8001, Affinity: 1},
{CharaID: 1001, Relation: 8002, Affinity: 1},
{CharaID: 1001, Relation: 8003, Affinity: 1},
{CharaID: 1001, Relation: 8004, Affinity: 1},
{CharaID: 1001, Relation: 8005, Affinity: 1},
{CharaID: 1001, Relation: 8006, Affinity: 1},
{CharaID: 1001, Relation: 8007, Affinity: 1},
{CharaID: 1001, Relation: 8008, Affinity: 1},
{CharaID: 1001, Relation: 8009, Affinity: 1},
{CharaID: 1001, Relation: 8010, Affinity: 1},
{CharaID: 1001, Relation: 8011, Affinity: 1},
{CharaID: 1001, Relation: 8012, Affinity: 1},
{CharaID: 1001, Relation: 8013, Affinity: 1},
{CharaID: 1001, Relation: 8014, Affinity: 1},
{CharaID: 1001, Relation: 8015, Affinity: 1},
{CharaID: 1001, Relation: 8016, Affinity: 1},
{CharaID: 1001, Relation: 8443, Affinity: 1},
{CharaID: 1001, Relation: 8479, Affinity: 1},
{CharaID: 1001, Relation: 8511, Affinity: 1},
{CharaID: 1001, Relation: 8536, Affinity: 1},
{CharaID: 1001, Relation: 8640, Affinity: 1},
{CharaID: 1002, Relation: 104, Affinity: 2},
{CharaID: 1002, Relation: 202, Affinity: 2},
{CharaID: 1002, Relation: 301, Affinity: 2},
{CharaID: 1002, Relation: 402, Affinity: 2},
{CharaID: 1002, Relation: 2001, Affinity: 1},
{CharaID: 1002, Relation: 2406, Affinity: 1},
{CharaID: 1002, Relation: 2512, Affinity: 1},
{CharaID: 1002, Relation: 2601, Affinity: 1},
{CharaID: 1002, Relation: 2603, Affinity: 1},
{CharaID: 1002, Relation: 2810, Affinity: 1},
{CharaID: 1002, Relation: 2901, Affinity: 7},
{CharaID: 1002, Relation: 3003, Affinity: 7},
{CharaID: 1002, Relation: 3101, Affinity: 7},
{CharaID: 1002, Relation: 3205, Affinity: 1},
{CharaID: 1002, Relation: 7000, Affinity: 1},
{CharaID: 1002, Relation: 7001, Affinity: 1},
{CharaID: 1002, Relation: 7002, Affinity: 1},
{CharaID: 1002, Relation: 7013, Affinity: 1},
{CharaID: 1002, Relation: 7015, Affinity: 1},
{CharaID: 1002, Relation: 8017, Affinity: 1},
{CharaID: 1002, Relation: 8018, Affinity: 1},
{CharaID: 1002, Relation: 8019, Affinity: 1},
{CharaID: 1002, Relation: 8020, Affinity: 1},
{CharaID: 1002, Relation: 8021, Affinity: 1},
{CharaID: 1002, Relation: 8022, Affinity: 1},
{CharaID: 1002, Relation: 8023, Affinity: 1},
{CharaID: 1002, Relation: 8024, Affinity: 1},
{CharaID: 1002, Relation: 8025, Affinity: 1},
{CharaID: 1002, Relation: 8026, Affinity: 1},
{CharaID: 1002, Relation: 8027, Affinity: 1},
{CharaID: 1002, Relation: 8028, Affinity: 1},
{CharaID: 1002, Relation: 8029, Affinity: 1},
{CharaID: 1002, Relation: 8030, Affinity: 1},
{CharaID: 1002, Relation: 8031, Affinity: 1},
{CharaID: 1002, Relation: 8032, Affinity: 1},
{CharaID: 1002, Relation: 8444, Affinity: 1},
{CharaID: 1002, Relation: 8480, Affinity: 1},
{CharaID: 1002, Relation: 8512, Affinity: 1},
{CharaID: 1002, Relation: 8537, Affinity: 1},
{CharaID: 1002, Relation: 8641, Affinity: 1},
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("wrong affinity details (+got/-want):\n%s", diff)
}
}
func TestUmas(t *testing.T) {
db := testdb(t.Context(), "file:TestUmas?mode=memory&cache=shared", characterSQL)
got, err := mdb.Umas(t.Context(), db)
if err != nil {
t.Error(err)
}
want := []horse.Uma{
{
ID: 100101,
CharacterID: 1001,
Name: `[Special Dreamer] Special Week`,
Variant: `[Special Dreamer]`,
Sprint: 2,
Mile: 5,
Medium: 7,
Long: 7,
Front: 1,
Pace: 7,
Late: 7,
End: 5,
Turf: 7,
Dirt: 1,
Unique: 100011,
Skill1: 200512,
Skill2: 201352,
Skill3: 200732,
SkillPL2: 200162,
SkillPL3: 201351,
SkillPL4: 200612,
SkillPL5: 200511,
},
{
ID: 100102,
CharacterID: 1001,
Name: `[Hopp'n♪Happy Heart] Special Week`,
Variant: `[Hopp'n♪Happy Heart]`,
Sprint: 2,
Mile: 5,
Medium: 7,
Long: 7,
Front: 1,
Pace: 7,
Late: 7,
End: 5,
Turf: 7,
Dirt: 1,
Unique: 110011,
Skill1: 200462,
Skill2: 200592,
Skill3: 201132,
SkillPL2: 200212,
SkillPL3: 200591,
SkillPL4: 201611,
SkillPL5: 200461,
},
{
ID: 100201,
CharacterID: 1002,
Name: `[Innocent Silence] Silence Suzuka`,
Variant: `[Innocent Silence]`,
Sprint: 4,
Mile: 7,
Medium: 7,
Long: 3,
Front: 7,
Pace: 5,
Late: 3,
End: 1,
Turf: 7,
Dirt: 1,
Unique: 100021,
Skill1: 200432,
Skill2: 200552,
Skill3: 200712,
SkillPL2: 200022,
SkillPL3: 200431,
SkillPL4: 200542,
SkillPL5: 200551,
},
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("wrong umas (+got/-want):\n%s", diff)
}
}
func TestConversations(t *testing.T) {
db := testdb(t.Context(), "file:TestConversations?mode=memory&cache=shared", characterSQL)
got, err := mdb.Conversations(t.Context(), db)
if err != nil {
t.Error(err)
}
want := []horse.Conversation{
{
ID: 1,
CharacterID: 1001,
Number: 1,
Location: 410,
Chara1: 1001,
},
{
ID: 2,
CharacterID: 1001,
Number: 2,
Location: 510,
Chara1: 1001,
ConditionType: 1,
},
{
ID: 3,
CharacterID: 1001,
Number: 3,
Location: 310,
Chara1: 1001,
ConditionType: 1,
},
{
ID: 76,
CharacterID: 1001,
Number: 4,
Location: 120,
Chara1: 1001,
Chara2: 1002,
ConditionType: 2,
},
{
ID: 77,
CharacterID: 1001,
Number: 5,
Location: 520,
Chara1: 1003,
Chara2: 1001,
ConditionType: 3,
},
{
ID: 126,
CharacterID: 1001,
Number: 6,
Location: 430,
Chara1: 1001,
Chara2: 1014,
Chara3: 1011,
ConditionType: 1,
},
{
ID: 4,
CharacterID: 1002,
Number: 1,
Location: 310,
Chara1: 1002,
},
{
ID: 5,
CharacterID: 1002,
Number: 2,
Location: 210,
Chara1: 1002,
ConditionType: 1,
},
{
ID: 6,
CharacterID: 1002,
Number: 3,
Location: 110,
Chara1: 1002,
ConditionType: 1,
},
{
ID: 78,
CharacterID: 1002,
Number: 4,
Location: 520,
Chara1: 1010,
Chara2: 1002,
ConditionType: 3,
},
{
ID: 79,
CharacterID: 1002,
Number: 5,
Location: 220,
Chara1: 1002,
Chara2: 1018,
ConditionType: 2,
},
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("wrong conversations (+got/-want):\n%s", diff)
}
}
+72
View File
@@ -0,0 +1,72 @@
{
"courseParams": [
{
"_paramType": 0,
"_values": [
3,
275
],
"_distance": 400.0
},
{
"_paramType": 0,
"_values": [
4,
259
],
"_distance": 675.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 0.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 400.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 934.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 1200.0
},
{
"_paramType": 4,
"_values": [],
"_distance": 725.0
},
{
"_paramType": 5,
"_values": [],
"_distance": 735.0
},
{
"_paramType": 8,
"_values": [],
"_distance": 725.0
},
{
"_paramType": 9,
"_values": [],
"_distance": 735.0
}
]
}
+80
View File
@@ -0,0 +1,80 @@
{
"courseParams": [
{
"_paramType": 0,
"_values": [
2,
275
],
"_distance": 150.0
},
{
"_paramType": 0,
"_values": [
3,
275
],
"_distance": 700.0
},
{
"_paramType": 0,
"_values": [
4,
259
],
"_distance": 975.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 425.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 700.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 1234.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 1500.0
},
{
"_paramType": 4,
"_values": [],
"_distance": 1025.0
},
{
"_paramType": 5,
"_values": [],
"_distance": 1035.0
},
{
"_paramType": 8,
"_values": [],
"_distance": 1025.0
},
{
"_paramType": 9,
"_values": [],
"_distance": 1035.0
}
]
}
+104
View File
@@ -0,0 +1,104 @@
{
"courseParams": [
{
"_paramType": 0,
"_values": [
1,
275
],
"_distance": 175.0
},
{
"_paramType": 0,
"_values": [
2,
275
],
"_distance": 450.0
},
{
"_paramType": 0,
"_values": [
3,
275
],
"_distance": 1000.0
},
{
"_paramType": 0,
"_values": [
4,
259
],
"_distance": 1275.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 0.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 175.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 725.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 1000.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 1534.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 1800.0
},
{
"_paramType": 4,
"_values": [],
"_distance": 1325.0
},
{
"_paramType": 5,
"_values": [],
"_distance": 1335.0
},
{
"_paramType": 8,
"_values": [],
"_distance": 1325.0
},
{
"_paramType": 9,
"_values": [],
"_distance": 1335.0
}
]
}
+104
View File
@@ -0,0 +1,104 @@
{
"courseParams": [
{
"_paramType": 0,
"_values": [
1,
275
],
"_distance": 375.0
},
{
"_paramType": 0,
"_values": [
2,
275
],
"_distance": 650.0
},
{
"_paramType": 0,
"_values": [
3,
275
],
"_distance": 1200.0
},
{
"_paramType": 0,
"_values": [
4,
259
],
"_distance": 1475.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 0.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 375.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 925.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 1200.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 1734.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 2000.0
},
{
"_paramType": 4,
"_values": [],
"_distance": 1525.0
},
{
"_paramType": 5,
"_values": [],
"_distance": 1535.0
},
{
"_paramType": 8,
"_values": [],
"_distance": 1525.0
},
{
"_paramType": 9,
"_values": [],
"_distance": 1535.0
}
]
}
+136
View File
@@ -0,0 +1,136 @@
{
"courseParams": [
{
"_paramType": 0,
"_values": [
3,
275
],
"_distance": 175.0
},
{
"_paramType": 0,
"_values": [
4,
275
],
"_distance": 450.0
},
{
"_paramType": 0,
"_values": [
1,
275
],
"_distance": 975.0
},
{
"_paramType": 0,
"_values": [
2,
275
],
"_distance": 1250.0
},
{
"_paramType": 0,
"_values": [
3,
275
],
"_distance": 1800.0
},
{
"_paramType": 0,
"_values": [
4,
259
],
"_distance": 2075.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 0.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 175.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 725.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 975.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 1525.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 1800.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 2334.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 2600.0
},
{
"_paramType": 4,
"_values": [],
"_distance": 2125.0
},
{
"_paramType": 5,
"_values": [],
"_distance": 2135.0
},
{
"_paramType": 8,
"_values": [],
"_distance": 2125.0
},
{
"_paramType": 9,
"_values": [],
"_distance": 2135.0
}
]
}
+72
View File
@@ -0,0 +1,72 @@
{
"courseParams": [
{
"_paramType": 0,
"_values": [
3,
230
],
"_distance": 280.0
},
{
"_paramType": 0,
"_values": [
4,
226
],
"_distance": 510.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 0.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 280.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 736.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 1000.0
},
{
"_paramType": 4,
"_values": [],
"_distance": 550.0
},
{
"_paramType": 5,
"_values": [],
"_distance": 560.0
},
{
"_paramType": 8,
"_values": [],
"_distance": 550.0
},
{
"_paramType": 9,
"_values": [],
"_distance": 560.0
}
]
}
+104
View File
@@ -0,0 +1,104 @@
{
"courseParams": [
{
"_paramType": 0,
"_values": [
1,
230
],
"_distance": 240.0
},
{
"_paramType": 0,
"_values": [
2,
230
],
"_distance": 470.0
},
{
"_paramType": 0,
"_values": [
3,
230
],
"_distance": 980.0
},
{
"_paramType": 0,
"_values": [
4,
226
],
"_distance": 1210.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 0.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 240.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 700.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 980.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 1436.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 1700.0
},
{
"_paramType": 4,
"_values": [],
"_distance": 1250.0
},
{
"_paramType": 5,
"_values": [],
"_distance": 1260.0
},
{
"_paramType": 8,
"_values": [],
"_distance": 1250.0
},
{
"_paramType": 9,
"_values": [],
"_distance": 1260.0
}
]
}
+136
View File
@@ -0,0 +1,136 @@
{
"courseParams": [
{
"_paramType": 0,
"_values": [
3,
230
],
"_distance": 200.0
},
{
"_paramType": 0,
"_values": [
4,
230
],
"_distance": 430.0
},
{
"_paramType": 0,
"_values": [
1,
230
],
"_distance": 940.0
},
{
"_paramType": 0,
"_values": [
2,
230
],
"_distance": 1170.0
},
{
"_paramType": 0,
"_values": [
3,
230
],
"_distance": 1680.0
},
{
"_paramType": 0,
"_values": [
4,
226
],
"_distance": 1910.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 0.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 200.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 660.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 940.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 1408.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 1680.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 2136.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 2400.0
},
{
"_paramType": 4,
"_values": [],
"_distance": 1950.0
},
{
"_paramType": 5,
"_values": [],
"_distance": 1960.0
},
{
"_paramType": 8,
"_values": [],
"_distance": 1950.0
},
{
"_paramType": 9,
"_values": [],
"_distance": 1960.0
}
]
}
+80
View File
@@ -0,0 +1,80 @@
{
"courseParams": [
{
"_paramType": 0,
"_values": [
3,
220
],
"_distance": 310.0
},
{
"_paramType": 0,
"_values": [
4,
208
],
"_distance": 530.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 0.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 310.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 738.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 1000.0
},
{
"_paramType": 4,
"_values": [],
"_distance": 600.0
},
{
"_paramType": 5,
"_values": [],
"_distance": 610.0
},
{
"_paramType": 8,
"_values": [],
"_distance": 600.0
},
{
"_paramType": 9,
"_values": [],
"_distance": 610.0
},
{
"_paramType": 11,
"_values": [
10000,
555
],
"_distance": 0.0
}
]
}
+80
View File
@@ -0,0 +1,80 @@
{
"courseParams": [
{
"_paramType": 0,
"_values": [
3,
220
],
"_distance": 510.0
},
{
"_paramType": 0,
"_values": [
4,
208
],
"_distance": 730.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 0.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 510.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 938.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 1200.0
},
{
"_paramType": 4,
"_values": [],
"_distance": 800.0
},
{
"_paramType": 5,
"_values": [],
"_distance": 810.0
},
{
"_paramType": 8,
"_values": [],
"_distance": 800.0
},
{
"_paramType": 9,
"_values": [],
"_distance": 810.0
},
{
"_paramType": 11,
"_values": [
10000,
755
],
"_distance": 0.0
}
]
}
+120
View File
@@ -0,0 +1,120 @@
{
"courseParams": [
{
"_paramType": 0,
"_values": [
1,
220
],
"_distance": 320.0
},
{
"_paramType": 0,
"_values": [
2,
220
],
"_distance": 540.0
},
{
"_paramType": 0,
"_values": [
3,
220
],
"_distance": 1110.0
},
{
"_paramType": 0,
"_values": [
4,
208
],
"_distance": 1330.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 0.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 320.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 760.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 1110.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 1538.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 1800.0
},
{
"_paramType": 4,
"_values": [],
"_distance": 1400.0
},
{
"_paramType": 5,
"_values": [],
"_distance": 1410.0
},
{
"_paramType": 8,
"_values": [],
"_distance": 1400.0
},
{
"_paramType": 9,
"_values": [],
"_distance": 1410.0
},
{
"_paramType": 11,
"_values": [
-10000,
200
],
"_distance": 220.0
},
{
"_paramType": 11,
"_values": [
10000,
720
],
"_distance": 665.0
}
]
}
+120
View File
@@ -0,0 +1,120 @@
{
"courseParams": [
{
"_paramType": 0,
"_values": [
1,
220
],
"_distance": 520.0
},
{
"_paramType": 0,
"_values": [
2,
220
],
"_distance": 740.0
},
{
"_paramType": 0,
"_values": [
3,
220
],
"_distance": 1310.0
},
{
"_paramType": 0,
"_values": [
4,
208
],
"_distance": 1530.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 0.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 520.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 960.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 1310.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 1738.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 2000.0
},
{
"_paramType": 4,
"_values": [],
"_distance": 1600.0
},
{
"_paramType": 5,
"_values": [],
"_distance": 1610.0
},
{
"_paramType": 8,
"_values": [],
"_distance": 1600.0
},
{
"_paramType": 9,
"_values": [],
"_distance": 1610.0
},
{
"_paramType": 11,
"_values": [
-10000,
200
],
"_distance": 420.0
},
{
"_paramType": 11,
"_values": [
10000,
720
],
"_distance": 865.0
}
]
}
+160
View File
@@ -0,0 +1,160 @@
{
"courseParams": [
{
"_paramType": 0,
"_values": [
3,
240
],
"_distance": 260.0
},
{
"_paramType": 0,
"_values": [
4,
230
],
"_distance": 500.0
},
{
"_paramType": 0,
"_values": [
1,
220
],
"_distance": 1120.0
},
{
"_paramType": 0,
"_values": [
2,
220
],
"_distance": 1340.0
},
{
"_paramType": 0,
"_values": [
3,
220
],
"_distance": 1910.0
},
{
"_paramType": 0,
"_values": [
4,
208
],
"_distance": 2130.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 0.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 260.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 730.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 1120.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 1560.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 1910.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 2338.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 2600.0
},
{
"_paramType": 4,
"_values": [],
"_distance": 2200.0
},
{
"_paramType": 5,
"_values": [],
"_distance": 2210.0
},
{
"_paramType": 8,
"_values": [],
"_distance": 2200.0
},
{
"_paramType": 9,
"_values": [],
"_distance": 2210.0
},
{
"_paramType": 11,
"_values": [
10000,
495
],
"_distance": 0.0
},
{
"_paramType": 11,
"_values": [
-10000,
200
],
"_distance": 970.0
},
{
"_paramType": 11,
"_values": [
10000,
720
],
"_distance": 1425.0
}
]
}
+72
View File
@@ -0,0 +1,72 @@
{
"courseParams": [
{
"_paramType": 0,
"_values": [
3,
190
],
"_distance": 370.0
},
{
"_paramType": 0,
"_values": [
4,
180
],
"_distance": 560.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 0.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 370.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 740.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 1000.0
},
{
"_paramType": 4,
"_values": [],
"_distance": 600.0
},
{
"_paramType": 5,
"_values": [],
"_distance": 610.0
},
{
"_paramType": 8,
"_values": [],
"_distance": 600.0
},
{
"_paramType": 9,
"_values": [],
"_distance": 610.0
}
]
}
+120
View File
@@ -0,0 +1,120 @@
{
"courseParams": [
{
"_paramType": 0,
"_values": [
1,
190
],
"_distance": 350.0
},
{
"_paramType": 0,
"_values": [
2,
190
],
"_distance": 540.0
},
{
"_paramType": 0,
"_values": [
3,
190
],
"_distance": 1070.0
},
{
"_paramType": 0,
"_values": [
4,
180
],
"_distance": 1260.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 0.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 350.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 730.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 1070.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 1440.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 1700.0
},
{
"_paramType": 4,
"_values": [],
"_distance": 1300.0
},
{
"_paramType": 5,
"_values": [],
"_distance": 1310.0
},
{
"_paramType": 8,
"_values": [],
"_distance": 1300.0
},
{
"_paramType": 9,
"_values": [],
"_distance": 1310.0
},
{
"_paramType": 11,
"_values": [
-10000,
340
],
"_distance": 275.0
},
{
"_paramType": 11,
"_values": [
10000,
670
],
"_distance": 615.0
}
]
}
+136
View File
@@ -0,0 +1,136 @@
{
"courseParams": [
{
"_paramType": 0,
"_values": [
3,
190
],
"_distance": 292.0
},
{
"_paramType": 0,
"_values": [
4,
190
],
"_distance": 482.0
},
{
"_paramType": 0,
"_values": [
1,
190
],
"_distance": 1040.0
},
{
"_paramType": 0,
"_values": [
2,
190
],
"_distance": 1230.0
},
{
"_paramType": 0,
"_values": [
3,
190
],
"_distance": 1770.0
},
{
"_paramType": 0,
"_values": [
4,
180
],
"_distance": 1960.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 0.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 292.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 672.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 1040.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 1420.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 1770.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 2140.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 2400.0
},
{
"_paramType": 4,
"_values": [],
"_distance": 2000.0
},
{
"_paramType": 5,
"_values": [],
"_distance": 2010.0
},
{
"_paramType": 8,
"_values": [],
"_distance": 2000.0
},
{
"_paramType": 9,
"_values": [],
"_distance": 2010.0
}
]
}
+63
View File
@@ -0,0 +1,63 @@
{
"courseParams": [
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 0.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 649.9000244140625
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 650.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 1000.0
},
{
"_paramType": 4,
"_values": [],
"_distance": 690.0
},
{
"_paramType": 5,
"_values": [],
"_distance": 700.0
},
{
"_paramType": 7,
"_values": [
2
],
"_distance": 30.0
},
{
"_paramType": 8,
"_values": [],
"_distance": 696.0
},
{
"_paramType": 9,
"_values": [],
"_distance": 706.0
}
]
}
+72
View File
@@ -0,0 +1,72 @@
{
"courseParams": [
{
"_paramType": 0,
"_values": [
3,
200
],
"_distance": 450.0
},
{
"_paramType": 0,
"_values": [
4,
192
],
"_distance": 650.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 0.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 450.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 842.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 1200.0
},
{
"_paramType": 4,
"_values": [],
"_distance": 725.0
},
{
"_paramType": 5,
"_values": [],
"_distance": 735.0
},
{
"_paramType": 8,
"_values": [],
"_distance": 750.0
},
{
"_paramType": 9,
"_values": [],
"_distance": 760.0
}
]
}
+72
View File
@@ -0,0 +1,72 @@
{
"courseParams": [
{
"_paramType": 0,
"_values": [
3,
200
],
"_distance": 650.0
},
{
"_paramType": 0,
"_values": [
4,
192
],
"_distance": 850.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 0.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 650.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 1042.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 1400.0
},
{
"_paramType": 4,
"_values": [],
"_distance": 925.0
},
{
"_paramType": 5,
"_values": [],
"_distance": 935.0
},
{
"_paramType": 8,
"_values": [],
"_distance": 950.0
},
{
"_paramType": 9,
"_values": [],
"_distance": 960.0
}
]
}
+88
View File
@@ -0,0 +1,88 @@
{
"courseParams": [
{
"_paramType": 0,
"_values": [
3,
200
],
"_distance": 550.0
},
{
"_paramType": 0,
"_values": [
4,
192
],
"_distance": 750.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 0.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 550.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 942.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 1600.0
},
{
"_paramType": 4,
"_values": [],
"_distance": 800.0
},
{
"_paramType": 5,
"_values": [],
"_distance": 810.0
},
{
"_paramType": 8,
"_values": [],
"_distance": 800.0
},
{
"_paramType": 9,
"_values": [],
"_distance": 810.0
},
{
"_paramType": 11,
"_values": [
10000,
350
],
"_distance": 250.0
},
{
"_paramType": 11,
"_values": [
-15000,
300
],
"_distance": 600.0
}
]
}
+88
View File
@@ -0,0 +1,88 @@
{
"courseParams": [
{
"_paramType": 0,
"_values": [
3,
200
],
"_distance": 750.0
},
{
"_paramType": 0,
"_values": [
4,
192
],
"_distance": 950.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 0.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 750.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 1142.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 1800.0
},
{
"_paramType": 4,
"_values": [],
"_distance": 1000.0
},
{
"_paramType": 5,
"_values": [],
"_distance": 1010.0
},
{
"_paramType": 8,
"_values": [],
"_distance": 1000.0
},
{
"_paramType": 9,
"_values": [],
"_distance": 1010.0
},
{
"_paramType": 11,
"_values": [
10000,
350
],
"_distance": 450.0
},
{
"_paramType": 11,
"_values": [
-15000,
300
],
"_distance": 800.0
}
]
}
+104
View File
@@ -0,0 +1,104 @@
{
"courseParams": [
{
"_paramType": 0,
"_values": [
1,
200
],
"_distance": 420.0
},
{
"_paramType": 0,
"_values": [
2,
200
],
"_distance": 620.0
},
{
"_paramType": 0,
"_values": [
3,
200
],
"_distance": 1250.0
},
{
"_paramType": 0,
"_values": [
4,
192
],
"_distance": 1450.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 0.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 420.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 820.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 1250.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 1642.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 2000.0
},
{
"_paramType": 4,
"_values": [],
"_distance": 1525.0
},
{
"_paramType": 5,
"_values": [],
"_distance": 1535.0
},
{
"_paramType": 8,
"_values": [],
"_distance": 1525.0
},
{
"_paramType": 9,
"_values": [],
"_distance": 1535.0
}
]
}
+88
View File
@@ -0,0 +1,88 @@
{
"courseParams": [
{
"_paramType": 0,
"_values": [
3,
200
],
"_distance": 950.0
},
{
"_paramType": 0,
"_values": [
4,
192
],
"_distance": 1150.0
},
{
"_paramType": 2,
"_values": [
1,
2
],
"_distance": 0.0
},
{
"_paramType": 2,
"_values": [
2,
2
],
"_distance": 950.0
},
{
"_paramType": 2,
"_values": [
1,
1
],
"_distance": 1342.0
},
{
"_paramType": 2,
"_values": [
2,
1
],
"_distance": 2000.0
},
{
"_paramType": 4,
"_values": [],
"_distance": 1200.0
},
{
"_paramType": 5,
"_values": [],
"_distance": 1210.0
},
{
"_paramType": 8,
"_values": [],
"_distance": 1200.0
},
{
"_paramType": 9,
"_values": [],
"_distance": 1210.0
},
{
"_paramType": 11,
"_values": [
-15000,
300
],
"_distance": 1000.0
},
{
"_paramType": 11,
"_values": [
10000,
350
],
"_distance": 650.0
}
]
}

Some files were not shown because too many files have changed in this diff Show More