@v12-engine/create 0.1.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/README.md +4 -1
  2. package/base/_gitignore +5 -0
  3. package/base/package.json +3 -3
  4. package/base/src/main.ts +36 -10
  5. package/base/src/navigation.worker.ts +7 -0
  6. package/base/vite.config.ts +24 -2
  7. package/cli.mjs +21 -6
  8. package/index.mjs +27 -7
  9. package/layers/desktop/electron/app.mjs +237 -0
  10. package/layers/desktop/electron/config.mjs +16 -0
  11. package/layers/desktop/electron/dev.mjs +63 -0
  12. package/layers/desktop/electron/files.mjs +86 -0
  13. package/layers/desktop/electron/icons/icon.icns +0 -0
  14. package/layers/desktop/electron/icons/icon.png +0 -0
  15. package/layers/desktop/electron/main.mjs +12 -0
  16. package/layers/desktop/electron/package.mjs +75 -0
  17. package/layers/desktop/electron/preload.cjs +41 -0
  18. package/layers/desktop/electron/presentation.m +87 -0
  19. package/layers/desktop/electron/serve.mjs +76 -0
  20. package/layers/desktop/electron/tools.mjs +63 -0
  21. package/layers/desktop/electron/updates.mjs +71 -0
  22. package/layers/desktop/package.json +13 -0
  23. package/layers/desktop/src/desktop.ts +175 -0
  24. package/layers/desktop/src/shell.ts +279 -0
  25. package/layers/desktop/tests/main.test.ts +189 -0
  26. package/layers/desktop/tests/shell.test.ts +152 -0
  27. package/layers/rooms/server.ts +13 -10
  28. package/layers/server/server.ts +4 -5
  29. package/layers/source/vite.config.ts +21 -1
  30. package/package.json +1 -1
  31. package/starters/campaign/README.md +9 -2
  32. package/starters/campaign/layers/rooms/server.ts +3 -3
  33. package/starters/campaign/layers/server/server.ts +3 -5
  34. package/starters/campaign/layers/tests/tests/rules.test.ts +40 -21
  35. package/starters/campaign/src/campaign/game.ts +26 -5
  36. package/starters/campaign/src/campaign/index.ts +104 -68
  37. package/starters/campaign/src/campaign/kills.ts +1 -1
  38. package/starters/campaign/src/campaign/missions/homecoming.ts +7 -7
  39. package/starters/campaign/src/campaign/missions/siege.ts +6 -6
  40. package/starters/campaign/src/campaign/missions/warlord.ts +13 -14
  41. package/starters/campaign/src/campaign/scenes.ts +8 -7
  42. package/starters/campaign/src/campaign/units.ts +59 -20
  43. package/starters/campaign/src/campaign/view/attacks.ts +19 -0
  44. package/starters/campaign/src/campaign/view/fire.ts +2 -2
  45. package/starters/campaign/src/campaign/view/index.ts +54 -43
  46. package/starters/campaign/src/campaign/waves.ts +18 -17
  47. package/starters/campaign/src/game.ts +8 -1
  48. package/starters/campaign/src/main.ts +38 -19
  49. package/starters/campaign/src/maps/fort.names.ts +2 -2
  50. package/starters/campaign/src/maps/fort.vgmap.json +102 -2
  51. package/starters/campaign/src/maps/homestead.names.ts +2 -2
  52. package/starters/campaign/src/maps/homestead.vgmap.json +85 -1
  53. package/starters/campaign/src/maps/pass.names.ts +2 -2
  54. package/starters/campaign/src/maps/pass.vgmap.json +69 -1
  55. package/starters/campaign/src/waves-panel.ts +2 -1
  56. package/starters/empty/README.md +18 -10
  57. package/starters/empty/layers/tests/tests/rules.test.ts +16 -17
  58. package/starters/empty/src/game.ts +11 -75
  59. package/starters/empty/src/generate.worker.ts +5 -5
  60. package/starters/empty/src/rules.ts +83 -25
  61. package/starters/empty/src/view.ts +82 -0
  62. package/starters/examples/README.md +83 -0
  63. package/starters/examples/layers/tests/tests/examples.test.ts +30 -0
  64. package/starters/examples/package.json +5 -0
  65. package/starters/examples/src/examples/abilities/areas.ts +165 -0
  66. package/starters/examples/src/examples/abilities/areasLooks.ts +30 -0
  67. package/starters/examples/src/examples/abilities/buffs.ts +287 -0
  68. package/starters/examples/src/examples/abilities/buffsLooks.ts +46 -0
  69. package/starters/examples/src/examples/abilities/changing.ts +228 -0
  70. package/starters/examples/src/examples/abilities/changingLooks.ts +61 -0
  71. package/starters/examples/src/examples/abilities/control.ts +158 -0
  72. package/starters/examples/src/examples/abilities/controlLooks.ts +78 -0
  73. package/starters/examples/src/examples/abilities/damage.ts +156 -0
  74. package/starters/examples/src/examples/abilities/damageLooks.ts +37 -0
  75. package/starters/examples/src/examples/abilities/family.ts +29 -0
  76. package/starters/examples/src/examples/abilities/index.ts +32 -0
  77. package/starters/examples/src/examples/abilities/kit.ts +179 -0
  78. package/starters/examples/src/examples/abilities/lookKit.ts +71 -0
  79. package/starters/examples/src/examples/abilities/looks.ts +47 -0
  80. package/starters/examples/src/examples/abilities/missiles.ts +203 -0
  81. package/starters/examples/src/examples/abilities/missilesLooks.ts +38 -0
  82. package/starters/examples/src/examples/abilities/moves.ts +206 -0
  83. package/starters/examples/src/examples/abilities/movesLooks.ts +52 -0
  84. package/starters/examples/src/examples/abilities/recasts.ts +181 -0
  85. package/starters/examples/src/examples/abilities/recastsLooks.ts +41 -0
  86. package/starters/examples/src/examples/abilities/resources.ts +166 -0
  87. package/starters/examples/src/examples/abilities/resourcesLooks.ts +42 -0
  88. package/starters/examples/src/examples/abilities/summons.ts +296 -0
  89. package/starters/examples/src/examples/abilities/summonsLooks.ts +115 -0
  90. package/starters/examples/src/examples/abilities/triggers.ts +204 -0
  91. package/starters/examples/src/examples/abilities/triggersLooks.ts +53 -0
  92. package/starters/examples/src/examples/abilities/vision.ts +137 -0
  93. package/starters/examples/src/examples/abilities/visionLooks.ts +50 -0
  94. package/starters/examples/src/examples/animation/index.ts +235 -0
  95. package/starters/examples/src/examples/animation/looks.ts +444 -0
  96. package/starters/examples/src/examples/catalog.ts +52 -0
  97. package/starters/examples/src/examples/cinematics/index.ts +217 -0
  98. package/starters/examples/src/examples/cinematics/looks.ts +476 -0
  99. package/starters/examples/src/examples/cinematics/tour.json +43 -0
  100. package/starters/examples/src/examples/cinematics/village.ts +88 -0
  101. package/starters/examples/src/examples/common/looks.ts +65 -0
  102. package/starters/examples/src/examples/common/units.ts +70 -0
  103. package/starters/examples/src/examples/construction/index.ts +329 -0
  104. package/starters/examples/src/examples/construction/looks.ts +275 -0
  105. package/starters/examples/src/examples/custom-animation/bones.ts +92 -0
  106. package/starters/examples/src/examples/custom-animation/index.ts +190 -0
  107. package/starters/examples/src/examples/custom-animation/looks.ts +523 -0
  108. package/starters/examples/src/examples/custom-animation/runes.ts +82 -0
  109. package/starters/examples/src/examples/custom-fx/glaive.look.ts +110 -0
  110. package/starters/examples/src/examples/custom-fx/glaive.ts +40 -0
  111. package/starters/examples/src/examples/custom-fx/gpu.look.ts +145 -0
  112. package/starters/examples/src/examples/custom-fx/gpu.ts +22 -0
  113. package/starters/examples/src/examples/custom-fx/index.ts +25 -0
  114. package/starters/examples/src/examples/custom-fx/lights.look.ts +144 -0
  115. package/starters/examples/src/examples/custom-fx/lights.ts +25 -0
  116. package/starters/examples/src/examples/custom-fx/looks.ts +36 -0
  117. package/starters/examples/src/examples/custom-fx/meteor.look.ts +133 -0
  118. package/starters/examples/src/examples/custom-fx/meteor.ts +26 -0
  119. package/starters/examples/src/examples/custom-fx/parts.look.ts +193 -0
  120. package/starters/examples/src/examples/custom-fx/parts.ts +21 -0
  121. package/starters/examples/src/examples/custom-fx/portal.look.ts +193 -0
  122. package/starters/examples/src/examples/custom-fx/portal.ts +48 -0
  123. package/starters/examples/src/examples/custom-fx/renderer.look.ts +139 -0
  124. package/starters/examples/src/examples/custom-fx/renderer.ts +18 -0
  125. package/starters/examples/src/examples/custom-fx/ribbons.look.ts +151 -0
  126. package/starters/examples/src/examples/custom-fx/ribbons.ts +22 -0
  127. package/starters/examples/src/examples/custom-fx/shader.ts +42 -0
  128. package/starters/examples/src/examples/custom-fx/shield.look.ts +226 -0
  129. package/starters/examples/src/examples/custom-fx/shield.ts +43 -0
  130. package/starters/examples/src/examples/define.ts +86 -0
  131. package/starters/examples/src/examples/destruction/index.ts +570 -0
  132. package/starters/examples/src/examples/destruction/looks.ts +447 -0
  133. package/starters/examples/src/examples/dialogs/index.ts +383 -0
  134. package/starters/examples/src/examples/dialogs/looks.ts +546 -0
  135. package/starters/examples/src/examples/dynamic-map/index.ts +603 -0
  136. package/starters/examples/src/examples/dynamic-map/looks.ts +301 -0
  137. package/starters/examples/src/examples/fx/blood.look.ts +62 -0
  138. package/starters/examples/src/examples/fx/blood.ts +59 -0
  139. package/starters/examples/src/examples/fx/feel.look.ts +86 -0
  140. package/starters/examples/src/examples/fx/feel.ts +57 -0
  141. package/starters/examples/src/examples/fx/fight.ts +28 -0
  142. package/starters/examples/src/examples/fx/floats.look.ts +108 -0
  143. package/starters/examples/src/examples/fx/floats.ts +24 -0
  144. package/starters/examples/src/examples/fx/gallery.look.ts +168 -0
  145. package/starters/examples/src/examples/fx/gallery.ts +23 -0
  146. package/starters/examples/src/examples/fx/index.ts +30 -0
  147. package/starters/examples/src/examples/fx/links.look.ts +83 -0
  148. package/starters/examples/src/examples/fx/links.ts +49 -0
  149. package/starters/examples/src/examples/fx/looks.ts +55 -0
  150. package/starters/examples/src/examples/fx/map.look.ts +93 -0
  151. package/starters/examples/src/examples/fx/map.ts +33 -0
  152. package/starters/examples/src/examples/fx/marks.look.ts +100 -0
  153. package/starters/examples/src/examples/fx/marks.ts +36 -0
  154. package/starters/examples/src/examples/fx/shots.look.ts +92 -0
  155. package/starters/examples/src/examples/fx/shots.ts +71 -0
  156. package/starters/examples/src/examples/fx/spells.look.ts +87 -0
  157. package/starters/examples/src/examples/fx/spells.ts +122 -0
  158. package/starters/examples/src/examples/fx/stage.ts +31 -0
  159. package/starters/examples/src/examples/gui/babylonScreen.ts +167 -0
  160. package/starters/examples/src/examples/gui/babylonWorld.ts +191 -0
  161. package/starters/examples/src/examples/gui/htmlPart.ts +95 -0
  162. package/starters/examples/src/examples/gui/index.ts +143 -0
  163. package/starters/examples/src/examples/gui/keys.ts +91 -0
  164. package/starters/examples/src/examples/gui/layout.ts +42 -0
  165. package/starters/examples/src/examples/gui/looks.ts +270 -0
  166. package/starters/examples/src/examples/gui/minimap.ts +127 -0
  167. package/starters/examples/src/examples/gui/ownInterface.ts +129 -0
  168. package/starters/examples/src/examples/gui/plates.ts +87 -0
  169. package/starters/examples/src/examples/gui/radial.ts +104 -0
  170. package/starters/examples/src/examples/gui/solidPart.ts +107 -0
  171. package/starters/examples/src/examples/looks.ts +45 -0
  172. package/starters/examples/src/examples/mechanics/battle.ts +51 -0
  173. package/starters/examples/src/examples/mechanics/damage.ts +80 -0
  174. package/starters/examples/src/examples/mechanics/flying.ts +93 -0
  175. package/starters/examples/src/examples/mechanics/formations.ts +58 -0
  176. package/starters/examples/src/examples/mechanics/highGround.ts +82 -0
  177. package/starters/examples/src/examples/mechanics/index.ts +44 -0
  178. package/starters/examples/src/examples/mechanics/looks.ts +289 -0
  179. package/starters/examples/src/examples/mechanics/maze.ts +119 -0
  180. package/starters/examples/src/examples/mechanics/orders.ts +68 -0
  181. package/starters/examples/src/examples/mechanics/selection.ts +30 -0
  182. package/starters/examples/src/examples/moba/index.ts +377 -0
  183. package/starters/examples/src/examples/moba/looks.ts +349 -0
  184. package/starters/examples/src/examples/moba/maps.ts +69 -0
  185. package/starters/examples/src/examples/moba/spells.ts +34 -0
  186. package/starters/examples/src/examples/moba/units.ts +196 -0
  187. package/starters/examples/src/examples/nature/camera.ts +15 -0
  188. package/starters/examples/src/examples/nature/grass.ts +64 -0
  189. package/starters/examples/src/examples/nature/index.ts +327 -0
  190. package/starters/examples/src/examples/nature/land.ts +173 -0
  191. package/starters/examples/src/examples/nature/looks.ts +577 -0
  192. package/starters/examples/src/examples/nature/maps.ts +370 -0
  193. package/starters/examples/src/examples/nature/skies.ts +324 -0
  194. package/starters/examples/src/examples/nature/weather.ts +80 -0
  195. package/starters/examples/src/examples/performance/index.ts +263 -0
  196. package/starters/examples/src/examples/performance/looks.ts +372 -0
  197. package/starters/examples/src/examples/performance/meter.ts +158 -0
  198. package/starters/examples/src/examples/post/grades.ts +86 -0
  199. package/starters/examples/src/examples/post/index.ts +202 -0
  200. package/starters/examples/src/examples/post/looks.ts +884 -0
  201. package/starters/examples/src/examples/post/passes.ts +213 -0
  202. package/starters/examples/src/examples/rts/ai.ts +49 -0
  203. package/starters/examples/src/examples/rts/airlift.ts +43 -0
  204. package/starters/examples/src/examples/rts/base.ts +129 -0
  205. package/starters/examples/src/examples/rts/build.ts +65 -0
  206. package/starters/examples/src/examples/rts/camps.ts +36 -0
  207. package/starters/examples/src/examples/rts/carry.ts +103 -0
  208. package/starters/examples/src/examples/rts/creeps.ts +61 -0
  209. package/starters/examples/src/examples/rts/data.ts +406 -0
  210. package/starters/examples/src/examples/rts/ferry.ts +41 -0
  211. package/starters/examples/src/examples/rts/garrison.ts +50 -0
  212. package/starters/examples/src/examples/rts/harvest.ts +29 -0
  213. package/starters/examples/src/examples/rts/hero.ts +67 -0
  214. package/starters/examples/src/examples/rts/index.ts +77 -0
  215. package/starters/examples/src/examples/rts/looks.ts +721 -0
  216. package/starters/examples/src/examples/rts/shop.ts +46 -0
  217. package/starters/examples/src/examples/rts/tech.ts +39 -0
  218. package/starters/examples/src/examples/rts/train.ts +48 -0
  219. package/starters/examples/src/examples/rts/waterside.ts +185 -0
  220. package/starters/examples/src/examples/showcase/index.ts +714 -0
  221. package/starters/examples/src/examples/showcase/looks.ts +238 -0
  222. package/starters/examples/src/game.ts +17 -0
  223. package/starters/examples/src/main.ts +82 -0
  224. package/starters/examples/src/rules.ts +31 -0
  225. package/starters/examples/src/shell/look.ts +79 -0
  226. package/starters/examples/src/shell/registry.ts +7 -0
  227. package/starters/examples/src/shell/scenes.ts +178 -0
  228. package/starters/examples/src/shell/shell.ts +290 -0
  229. package/starters/examples/src/shell/style.ts +202 -0
  230. package/starters/examples/src/view.ts +50 -0
  231. package/starters/melee/README.md +12 -3
  232. package/starters/melee/layers/tests/tests/heroes.test.ts +402 -0
  233. package/starters/melee/layers/tests/tests/rules.test.ts +10 -8
  234. package/starters/melee/src/game.ts +32 -2
  235. package/starters/melee/src/maps/bay.names.ts +2 -2
  236. package/starters/melee/src/maps/bay.vgmap.json +198 -1
  237. package/starters/melee/src/maps.ts +11 -9
  238. package/starters/melee/src/melee/abilities.ts +1 -194
  239. package/starters/melee/src/melee/blight.ts +7 -7
  240. package/starters/melee/src/melee/cellLayers.json +3 -3
  241. package/starters/melee/src/melee/data.json +1 -18
  242. package/starters/melee/src/melee/game.ts +6 -4
  243. package/starters/melee/src/melee/heroes.ts +1025 -0
  244. package/starters/melee/src/melee/index.ts +64 -61
  245. package/starters/melee/src/melee/items.ts +15 -362
  246. package/starters/melee/src/melee/languages/he.json +14 -2
  247. package/starters/melee/src/melee/pings.ts +5 -6
  248. package/starters/melee/src/melee/saves.ts +1 -1
  249. package/starters/melee/src/melee/undead.ts +15 -230
  250. package/starters/melee/src/melee/units.ts +37 -171
  251. package/starters/melee/src/melee/view/attacks.ts +19 -0
  252. package/starters/melee/src/melee/view/heroSpellLooks.ts +39 -8
  253. package/starters/melee/src/melee/view/index.ts +56 -52
  254. package/starters/melee/src/melee/view/looks.ts +86 -63
  255. package/starters/melee/src/melee/view/undeadLooks.ts +70 -31
  256. package/starters/melee/src/melee/view/walls.ts +1 -1
  257. package/starters.mjs +6 -0
  258. package/v12.mjs +14 -15
  259. package/starters/campaign/src/rules.ts +0 -20
  260. package/starters/empty/src/maps.ts +0 -44
  261. package/starters/empty/src/tiles.ts +0 -12
  262. package/starters/melee/src/melee/heroSpells.ts +0 -38
  263. package/starters/melee/src/rules.ts +0 -24
@@ -0,0 +1,444 @@
1
+ /**
2
+ * The animation examples on the client's side: each character with its model, gear and clips (`UnitLook`), and the
3
+ * cards that play clips on them (`client.playClip`), change how fast, blend one into the next, and swap what they hold
4
+ * (`client.looks.set`). A clip is played by its name in the model's file: the shared characters' are KayKit's
5
+ */
6
+ import type { GameClientApi, GameView, ModelDef, UnitLook } from '@v12-engine/client'
7
+ import { presetFx } from '@v12-engine/client'
8
+ import { button, type CategoryLooks, readout, select, slider, toggle } from '../../shell/look'
9
+ import { character, hold, ONE_HANDED, person, TWO_HANDED } from '../common/looks'
10
+ import { ANIM, type Character, CHARACTERS, characterType, raise, strike } from './index'
11
+
12
+ /** The clips every character of the shared art has (KayKit's medium rig), by what they are for */
13
+ export const CLIPS = [
14
+ 'Idle_A',
15
+ 'Idle_B',
16
+ 'Walking_A',
17
+ 'Running_A',
18
+ 'Running_B',
19
+ 'Jump_Full_Short',
20
+ 'Melee_1H_Attack_Chop',
21
+ 'Melee_1H_Attack_Slice_Diagonal',
22
+ 'Melee_1H_Attack_Stab',
23
+ 'Melee_2H_Attack_Chop',
24
+ 'Melee_2H_Attack_Slice',
25
+ 'Melee_Dualwield_Attack_Chop',
26
+ 'Melee_Unarmed_Attack_Punch_A',
27
+ 'Melee_Block',
28
+ 'Melee_Blocking',
29
+ 'Ranged_1H_Shoot',
30
+ 'Ranged_2H_Shoot',
31
+ 'Ranged_Bow_Release',
32
+ 'Ranged_Magic_Shoot',
33
+ 'Ranged_Magic_Spellcasting',
34
+ 'Ranged_Magic_Spellcasting_Long',
35
+ 'Throw',
36
+ 'Use_Item',
37
+ 'Interact',
38
+ 'PickUp',
39
+ 'Hit_A',
40
+ 'Death_A',
41
+ 'Death_B',
42
+ 'Spawn_Ground',
43
+ 'Chop',
44
+ 'Chopping',
45
+ 'Dig',
46
+ 'Digging',
47
+ 'Hammer',
48
+ 'Hammering',
49
+ 'Pickaxe',
50
+ 'Pickaxing'
51
+ ] as const
52
+ /** The skeletons' own, besides */
53
+ export const SKELETON_CLIPS = [
54
+ 'Skeletons_Awaken_Floor',
55
+ 'Skeletons_Awaken_Standing',
56
+ 'Skeletons_Taunt',
57
+ 'Skeletons_Idle',
58
+ 'Skeletons_Walking',
59
+ 'Skeletons_Death'
60
+ ] as const
61
+
62
+ /** What each character holds: its gear, from the shared art's `gear/` */
63
+ const GEAR: Record<Character, ModelDef['hold']> = {
64
+ knight: [hold('sword_1handed'), hold('shield_badge_color', 'l')],
65
+ barbarian: [hold('axe_2handed')],
66
+ mage: [hold('staff')],
67
+ ranger: [hold('bow_with_string', 'l')],
68
+ rogue: [hold('dagger'), hold('dagger', 'l')],
69
+ rogue_hooded: [hold('crossbow_1handed')],
70
+ skeleton_warrior: [hold('sword_1handed'), hold('shield_round', 'l')],
71
+ skeleton_mage: [hold('wand')],
72
+ skeleton_rogue: [hold('axe_1handed')],
73
+ skeleton_minion: []
74
+ }
75
+
76
+ /** A character's look: its model at the size of the shared units, holding its gear */
77
+ const characterLook = (name: Character, own: Partial<ModelDef> = {}): UnitLook => ({
78
+ height: 1.8,
79
+ model: character(name, 0.7, { hold: GEAR[name], ...own }),
80
+ shapes: person(1.8)
81
+ })
82
+
83
+ /** The knight of the examples here, a sword in its right hand and a shield in its left */
84
+ const knight = (own: Partial<ModelDef> = {}, look: Partial<UnitLook> = {}): UnitLook => ({
85
+ height: 1.8,
86
+ model: character('knight', 0.7, { hold: [hold('sword_1handed'), hold('shield_round', 'l')], ...own }),
87
+ shapes: person(1.8),
88
+ ...look
89
+ })
90
+
91
+ /** How far a model walks in a second with its walk clip at normal speed, at size 1 (`ModelDef.stride`): KayKit's walk and run */
92
+ const STRIDE = { walk: 5.5, run: 10 }
93
+
94
+ /** The looks of the category's units */
95
+ const looks: Record<string, UnitLook> = {
96
+ ...Object.fromEntries(CHARACTERS.map((name) => [characterType(name), characterLook(name)])),
97
+ // three swordsmen, each swinging another way (`AttackLook`): in turn, at random, two-handed
98
+ [ANIM.animationTurns]: knight(
99
+ {},
100
+ { attacks: [{ clip: ['Melee_1H_Attack_Chop', 'Melee_1H_Attack_Slice_Diagonal', 'Melee_1H_Attack_Stab'], order: 'turn' }] }
101
+ ),
102
+ [ANIM.animationRandom]: knight({}, { attacks: [ONE_HANDED] }),
103
+ [ANIM.animationTwoHanded]: { ...characterLook('barbarian', { hold: [hold('sword_2handed_color')] }), attacks: [TWO_HANDED] },
104
+ // the walkers: each clip's speed is matched to how fast the unit goes by its stride
105
+ [ANIM.animationSlow]: knight({ stride: STRIDE.walk }),
106
+ [ANIM.animationJog]: knight({ stride: STRIDE.walk }),
107
+ [ANIM.animationRun]: knight({ stride: STRIDE.run, animations: { walk: 'Running_A' } }),
108
+ [ANIM.animationWielder]: knight({}, { attacks: [ONE_HANDED] }),
109
+ // five deaths of the same skeleton, one each by where it stands in the row (`onDeath`)
110
+ [ANIM.animationVictim]: {
111
+ ...characterLook('skeleton_warrior'),
112
+ onDeath: (entity) => DEATHS[deathOf(entity.pos.x)]
113
+ },
114
+ [ANIM.animationWaver]: { height: 1.8, model: character('mage', 0.7, { hold: [hold('staff')], stride: STRIDE.walk }), shapes: person(1.8) }
115
+ }
116
+
117
+ /** The five deaths, in the row's order from the left */
118
+ const DEATHS: NonNullable<UnitLook['death']>[] = [
119
+ { clip: 'Death_A', corpse: { lie: 4, sink: 2 } },
120
+ { clip: 'Death_B', corpse: { lie: 4, fade: 1.5 } },
121
+ { clip: 'Death_B', keep: true },
122
+ { shatter: 14, fx: 'dust' },
123
+ { clip: 'Death_A', fx: 'smoke', corpse: { lie: 0.6, sink: 1.5 } }
124
+ ]
125
+ /** Which of the row a skeleton is, by where it stands (laid out 2.2 apart round x 24) */
126
+ const deathOf = (x: number) => Math.max(0, Math.min(4, Math.round((x - 24) / 2.2) + 2))
127
+ const DEATH_NAMES = ['falls, lies, sinks', 'falls, fades', 'falls, stays for good', 'shatters', 'falls in smoke, sinks']
128
+
129
+ // ---- what the cards do
130
+
131
+ /** The ids of the units of the given types the client knows */
132
+ const idsOf = (client: GameClientApi, types: (type: string) => boolean): number[] =>
133
+ [...client.state.entities.values()].filter((each) => each.health > 0 && types(each.def.id)).map((each) => each.id)
134
+
135
+ /** The ten characters of the clips example */
136
+ const LINEUP = new Set(CHARACTERS.map(characterType))
137
+ const lineup = (client: GameClientApi) => idsOf(client, (type) => LINEUP.has(type))
138
+
139
+ /** What the clips example's card has chosen, by client */
140
+ const chosen = new WeakMap<GameClientApi, { clip: string; loop: boolean; speed: number }>()
141
+ const choiceOf = (client: GameClientApi) => chosen.get(client) ?? { clip: 'Idle_A', loop: true, speed: 1 }
142
+
143
+ /**
144
+ * Plays the chosen clip on every character that has it, as fast as the slider says: its length at its own speed
145
+ * (`animator.seconds`) over the speed. A loop plays until another is chosen; once, it goes back to standing
146
+ */
147
+ function playChosen(client: GameClientApi): void {
148
+ const { clip, loop, speed } = choiceOf(client)
149
+ for (const id of lineup(client)) {
150
+ const length = client.renderer.kit.model(id)?.animator?.seconds(clip)
151
+ if (length === undefined) {
152
+ // a clip this character has not got (a skeleton's, on a knight): it stands
153
+ client.stopClip(id)
154
+ continue
155
+ }
156
+ client.playClip(id, clip, { loop, seconds: length / speed })
157
+ }
158
+ }
159
+
160
+ /** The clip shown next to the one chosen on the clips example's card, a step along the list */
161
+ function stepClip(client: GameClientApi, by: number): void {
162
+ const all = [...CLIPS, ...SKELETON_CLIPS] as string[]
163
+ const choice = choiceOf(client)
164
+ const next = all[(all.indexOf(choice.clip) + by + all.length) % all.length]!
165
+ chosen.set(client, { ...choice, clip: next })
166
+ playChosen(client)
167
+ }
168
+
169
+ /** Puts the camera before the middle of the field, `distance` back and as steep as `pitch`, a little to the south */
170
+ function stage(client: GameClientApi, at: { x: number; z: number }, distance: number, pitch = 0.45, yaw = 0): void {
171
+ client.camera.setPose({ x: at.x, z: at.z, distance, yaw, pitch })
172
+ }
173
+
174
+ // ---- the blending example: two knights playing the same clips in turn, one blending and the other snapping
175
+
176
+ /** The pairs of clips the blending example plays in turn */
177
+ const PAIRS: Record<string, [string, string]> = {
178
+ 'idle and a swing': ['Idle_A', 'Melee_2H_Attack_Chop'],
179
+ 'walk and run': ['Walking_A', 'Running_A'],
180
+ 'block and punch': ['Melee_Blocking', 'Melee_Unarmed_Attack_Punch_A'],
181
+ 'cast and throw': ['Ranged_Magic_Spellcasting', 'Throw']
182
+ }
183
+ const blending = new WeakMap<GameClientApi, { pair: string; seconds: number; timer?: ReturnType<typeof setInterval> }>()
184
+
185
+ /** Both knights' looks again with the blend time chosen: the knight's for every clip, the barbarian's none at all */
186
+ async function setBlend(client: GameClientApi, seconds: number): Promise<void> {
187
+ const state = blending.get(client) ?? { pair: 'idle and a swing', seconds }
188
+ state.seconds = seconds
189
+ blending.set(client, state)
190
+ const look = client.looks.get(characterType('knight'))
191
+ if (look?.model) await client.looks.set(characterType('knight'), { ...look, model: { ...look.model, blends: [{ seconds }] } })
192
+ }
193
+
194
+ /** Plays the pair's clips on both, one then the other, a second and a half each, over and over */
195
+ function playPair(client: GameClientApi): void {
196
+ const state = blending.get(client) ?? { pair: 'idle and a swing', seconds: 0.4 }
197
+ blending.set(client, state)
198
+ clearInterval(state.timer)
199
+ let turn = 0
200
+ const ids = idsOf(client, (type) => type === characterType('knight') || type === characterType('barbarian'))
201
+ const play = () => {
202
+ const clip = PAIRS[state.pair]![turn++ % 2]!
203
+ for (const id of ids) client.playClip(id, clip, { loop: true })
204
+ }
205
+ play()
206
+ state.timer = setInterval(play, 1500)
207
+ }
208
+
209
+ // ---- the weapons example: the knight's look again with other gear in its hands, and the swing that goes with it
210
+
211
+ const RIGHT = ['sword_1handed', 'axe_1handed', 'dagger', 'wand', 'sword_2handed', 'axe_2handed', 'staff', 'crossbow_1handed', 'nothing'] as const
212
+ const LEFT = [
213
+ 'shield_round',
214
+ 'shield_badge_color',
215
+ 'shield_spikes_color',
216
+ 'shield_square',
217
+ 'spellbook_open',
218
+ 'dagger',
219
+ 'mug_full',
220
+ 'nothing'
221
+ ] as const
222
+ const TWO_HANDS = new Set<string>(['sword_2handed', 'axe_2handed', 'staff'])
223
+ const wielding = new WeakMap<GameClientApi, { right: string; left: string }>()
224
+
225
+ /** The wielder's look with the gear chosen: what it holds, and its swings (two-handed, one-handed, or its fists) */
226
+ function wield(client: GameClientApi, change: Partial<{ right: string; left: string }>): void {
227
+ const now = { ...(wielding.get(client) ?? { right: 'sword_1handed', left: 'shield_round' }), ...change }
228
+ wielding.set(client, now)
229
+ const look = client.looks.get(ANIM.animationWielder)
230
+ if (!look?.model) return
231
+ const twoHanded = TWO_HANDS.has(now.right)
232
+ const gear = [
233
+ ...(now.right === 'nothing' ? [] : [hold(now.right)]),
234
+ // a two-handed weapon takes both hands: nothing in the left
235
+ ...(now.left === 'nothing' || twoHanded ? [] : [hold(now.left, 'l')])
236
+ ]
237
+ const attacks = now.right === 'nothing' ? [{ clip: 'Melee_Unarmed_Attack_Punch_A' }] : twoHanded ? [TWO_HANDED] : [ONE_HANDED]
238
+ void client.looks.set(ANIM.animationWielder, { ...look, model: { ...look.model, hold: gear }, attacks })
239
+ }
240
+
241
+ // ---- the layers example: a clip on the upper body while the legs walk on
242
+
243
+ const LAYERED = ['Ranged_Magic_Spellcasting', 'Throw', 'Interact', 'Ranged_Magic_Shoot', 'Use_Item', 'Melee_Block'] as const
244
+ const layering = new WeakMap<GameClientApi, string>()
245
+
246
+ function playLayered(client: GameClientApi, mask: 'upper' | null): void {
247
+ const clip = layering.get(client) ?? LAYERED[0]
248
+ for (const id of idsOf(client, (type) => type === ANIM.animationWaver)) client.playClip(id, clip, { mask })
249
+ }
250
+
251
+ export const category: CategoryLooks = {
252
+ view: (view: GameView<any>) => ({
253
+ ...view,
254
+ units: { ...view.units, looks: { ...view.units.looks, ...looks } },
255
+ fx: { ...view.fx, defs: { ...presetFx(['dust', 'smoke']), ...view.fx?.defs } }
256
+ }),
257
+ looks: {
258
+ 'animation-clips': {
259
+ controls: [
260
+ select(
261
+ 'Clip',
262
+ [...CLIPS, ...SKELETON_CLIPS],
263
+ 'Idle_A',
264
+ (client, clip) => {
265
+ chosen.set(client, { ...choiceOf(client), clip })
266
+ playChosen(client)
267
+ },
268
+ 'The clip every character plays, by its name in the model’s file; one a character has not got, it stands for'
269
+ ),
270
+ button('Next clip', (client) => stepClip(client, 1), 'Plays the clip after this one in the list'),
271
+ button('Clip before', (client) => stepClip(client, -1), 'Plays the clip before this one in the list'),
272
+ toggle(
273
+ 'Over and over',
274
+ true,
275
+ (client, loop) => {
276
+ chosen.set(client, { ...choiceOf(client), loop })
277
+ playChosen(client)
278
+ },
279
+ 'On, the clip loops; off, it plays once and they go back to standing'
280
+ ),
281
+ slider(
282
+ 'Speed',
283
+ { min: 0.1, max: 3, step: 0.05, value: 1 },
284
+ (client, speed) => {
285
+ chosen.set(client, { ...choiceOf(client), speed })
286
+ playChosen(client)
287
+ },
288
+ 'How fast the clip plays: 1 as it was made, 0.1 a tenth as fast'
289
+ ),
290
+ readout(
291
+ 'Playing',
292
+ (client) => {
293
+ const { clip } = choiceOf(client)
294
+ // its length on the first that has it: a skeleton's own clips only the skeletons have
295
+ const seconds = lineup(client)
296
+ .map((id) => client.renderer.kit.model(id)?.animator?.seconds(clip))
297
+ .find((each) => each !== undefined)
298
+ return seconds === undefined ? clip : `${clip} · ${seconds.toFixed(2)} s`
299
+ },
300
+ 'The clip played and how long it is at normal speed'
301
+ )
302
+ ],
303
+ ready: (client) => stage(client, { x: 26.5, z: 24.5 }, 25, 0.5)
304
+ },
305
+ 'animation-attacks': {
306
+ controls: [
307
+ readout('Left', () => 'chop, slice, stab, in turn', 'The left knight swings its three clips one after another (`order: turn`)'),
308
+ readout('Middle', () => 'the same three, at random', 'The middle knight picks one of the same three at random each blow'),
309
+ readout('Right', () => 'two-handed chop and slice', 'The barbarian on the right swings its two-handed clips')
310
+ ],
311
+ ready: (client) => stage(client, { x: 26.5, z: 24.5 }, 25, 0.5)
312
+ },
313
+ 'animation-blending': {
314
+ controls: [
315
+ select(
316
+ 'Clips',
317
+ Object.keys(PAIRS),
318
+ 'idle and a swing',
319
+ (client, pair) => {
320
+ blending.set(client, { ...(blending.get(client) ?? { seconds: 0.4 }), pair })
321
+ playPair(client)
322
+ },
323
+ 'The two clips both play in turn, a second and a half each: watch the change from one to the other'
324
+ ),
325
+ slider(
326
+ 'Blend (left knight)',
327
+ { min: 0, max: 1, step: 0.05, value: 0.4 },
328
+ (client, seconds) => void setBlend(client, seconds),
329
+ 'Seconds one clip takes to melt into the next (`ModelDef.blends`). The barbarian on the right snaps, with none'
330
+ ),
331
+ readout(
332
+ 'Left · right',
333
+ (client) => `blends over ${(blending.get(client)?.seconds ?? 0.4).toFixed(2)} s · snaps`,
334
+ 'How each changes clip: the knight melts over the blend time, the barbarian jumps at once'
335
+ )
336
+ ],
337
+ view: (view) => {
338
+ // the barbarian on the right snaps from clip to clip; the knight on the left blends as the slider says
339
+ const units = view.units.looks
340
+ const knightLook = units[characterType('knight')]!
341
+ const barbarian = units[characterType('barbarian')]!
342
+ return {
343
+ ...view,
344
+ units: {
345
+ ...view.units,
346
+ looks: {
347
+ ...units,
348
+ [characterType('knight')]: { ...knightLook, model: { ...knightLook.model!, blends: [{ seconds: 0.4 }] } },
349
+ [characterType('barbarian')]: { ...barbarian, model: { ...barbarian.model!, blends: [{ seconds: 0 }] } }
350
+ }
351
+ }
352
+ }
353
+ },
354
+ ready: (client) => {
355
+ stage(client, { x: 25, z: 23.5 }, 12, 0.5)
356
+ playPair(client)
357
+ return () => clearInterval(blending.get(client)?.timer)
358
+ }
359
+ },
360
+ 'animation-paces': {
361
+ controls: [
362
+ toggle(
363
+ 'Feet matched to the pace',
364
+ true,
365
+ (client, on) => {
366
+ // matched: the stride the clip covers at normal speed; not: a stride that plays each clip at its own speed whatever the pace
367
+ for (const [type, stride, speed] of [
368
+ [ANIM.animationSlow, STRIDE.walk, 2],
369
+ [ANIM.animationJog, STRIDE.walk, 3.2],
370
+ [ANIM.animationRun, STRIDE.run, 6.5]
371
+ ] as const) {
372
+ const look = client.looks.get(type)
373
+ if (look?.model) void client.looks.set(type, { ...look, model: { ...look.model, stride: on ? stride : speed / look.model.scale } })
374
+ }
375
+ },
376
+ 'On, each walk clip plays as fast as its unit goes, so the feet keep to the ground; off, every clip plays at one speed and the feet slide'
377
+ ),
378
+ readout('Paces', () => 'stroll 2 · walk 3.2 · run 6.5 cells a second', 'How fast the stroller, the walker and the runner each go')
379
+ ],
380
+ ready: (client) => stage(client, { x: 25, z: 24 }, 25, 0.55)
381
+ },
382
+ 'animation-weapons': {
383
+ controls: [
384
+ select(
385
+ 'Right hand',
386
+ RIGHT,
387
+ 'sword_1handed',
388
+ (client, right) => wield(client, { right }),
389
+ 'What the knight holds in its right hand: a two-handed one takes both hands, and swings two-handed'
390
+ ),
391
+ select(
392
+ 'Left hand',
393
+ LEFT,
394
+ 'shield_round',
395
+ (client, left) => wield(client, { left }),
396
+ 'What the knight holds in its left hand, unless the right holds a two-handed weapon'
397
+ ),
398
+ readout(
399
+ 'Swings',
400
+ (client) => {
401
+ const { right } = wielding.get(client) ?? { right: 'sword_1handed' }
402
+ return right === 'nothing' ? 'punches' : TWO_HANDS.has(right) ? 'two-handed' : 'one-handed, three swings at random'
403
+ },
404
+ 'The attack clips that go with what it holds'
405
+ )
406
+ ],
407
+ ready: (client) => stage(client, { x: 24.6, z: 24.3 }, 11, 0.45, -0.3)
408
+ },
409
+ 'animation-deaths': {
410
+ controls: [
411
+ button(
412
+ 'Strike one down',
413
+ (client) => client.session.send(strike.make({})),
414
+ 'Kills the next skeleton standing, from the left: each of the five dies its own way'
415
+ ),
416
+ button('Raise them again', (client) => client.session.send(raise.make({})), 'Takes the dead away and stands a new row of five'),
417
+ readout('Left to right', () => DEATH_NAMES.join(' · '), 'How each of the row dies, from the left')
418
+ ],
419
+ ready: (client) => stage(client, { x: 25.5, z: 23.5 }, 17, 0.6)
420
+ },
421
+ 'animation-layers': {
422
+ controls: [
423
+ select(
424
+ 'Clip',
425
+ LAYERED,
426
+ LAYERED[0],
427
+ (client, clip) => {
428
+ layering.set(client, clip)
429
+ playLayered(client, 'upper')
430
+ },
431
+ 'The clip the walking mage plays, on its upper body: its legs walk on'
432
+ ),
433
+ button(
434
+ 'On the upper body',
435
+ (client) => playLayered(client, 'upper'),
436
+ 'Plays the clip on the chest, arms and head alone, the legs still walking'
437
+ ),
438
+ button('On the whole body', (client) => playLayered(client, null), 'Plays the clip on the whole body: the legs stop walking and slide'),
439
+ readout('Legs', () => 'walking round the square all the while', 'The mage walks round a square the whole time: only the clip on top changes')
440
+ ],
441
+ ready: (client) => stage(client, { x: 24.5, z: 23.5 }, 19, 0.65)
442
+ }
443
+ }
444
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Every example on the rules' side, by category: what the list shows, and what each adds to the game's rules (`src/rules.ts`
3
+ * registers them). The engine alone, as the rules are. A category's examples are its folder's `index.ts`; their looks
4
+ * its `looks.ts` (`./looks.ts` lists those)
5
+ */
6
+ import { CATEGORIES, type CategoryId, type ExampleCategory, type ExampleDef } from './define'
7
+ import { category as mechanics } from './mechanics/index'
8
+ import { category as abilities } from './abilities/index'
9
+ import { category as moba } from './moba/index'
10
+ import { category as rts } from './rts/index'
11
+ import { category as fx } from './fx/index'
12
+ import { category as customFx } from './custom-fx/index'
13
+ import { category as animation } from './animation/index'
14
+ import { category as customAnimation } from './custom-animation/index'
15
+ import { category as destruction } from './destruction/index'
16
+ import { category as construction } from './construction/index'
17
+ import { category as nature } from './nature/index'
18
+ import { category as post } from './post/index'
19
+ import { category as dynamicMap } from './dynamic-map/index'
20
+ import { category as cinematics } from './cinematics/index'
21
+ import { category as dialogs } from './dialogs/index'
22
+ import { category as gui } from './gui/index'
23
+ import { category as showcase } from './showcase/index'
24
+ import { category as performance } from './performance/index'
25
+
26
+ /** The categories' rules and examples, by category id */
27
+ export const categories: Record<CategoryId, ExampleCategory> = {
28
+ mechanics,
29
+ abilities,
30
+ moba,
31
+ rts,
32
+ fx,
33
+ 'custom-fx': customFx,
34
+ animation,
35
+ 'custom-animation': customAnimation,
36
+ destruction,
37
+ construction,
38
+ nature,
39
+ post,
40
+ 'dynamic-map': dynamicMap,
41
+ cinematics,
42
+ dialogs,
43
+ gui,
44
+ showcase,
45
+ performance
46
+ }
47
+
48
+ /** Every example, in the list's order */
49
+ export const examples: readonly ExampleDef[] = CATEGORIES.flatMap((each) => categories[each.id].examples)
50
+
51
+ /** An example by its id */
52
+ export const exampleNamed = (id: string): ExampleDef | undefined => examples.find((each) => each.id === id)