@v12-engine/create 0.1.0 → 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 +2 -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,447 @@
1
+ /**
2
+ * The destruction examples on the client's side: how what they break looks, and how it comes apart (`UnitLook.death`:
3
+ * a building crumbling, blown apart or toppled, trees and rocks shattered into pieces of their own models, flung with
4
+ * the client's physics), the grass they burn, and the buttons. The meteor's fall is drawn here; where it lands is the
5
+ * rules' (`destruction.meteor`)
6
+ */
7
+ import type { DeathDef, DebrisPiece, GameClientApi, GameView, ModelDef, UnitLook } from '@v12-engine/client'
8
+ import { foliage, presetFoliage, presetFx } from '@v12-engine/client'
9
+ import { button, type CategoryLooks, type ExampleLook, readout, slider, toggle } from '../../shell/look'
10
+ import { bombard, D, fuse, GRASS, HOW, hurt, meteor, raze, relay, shockwave, smash } from './index'
11
+
12
+ /** A model of the shared art made `width` across: its size at scale 1 (across, up, deep) is in the assets' `manifest.json` */
13
+ const sized = (src: string, size: [number, number, number], width: number, own: Partial<ModelDef> = {}): ModelDef => ({
14
+ src,
15
+ scale: width / Math.max(size[0], size[2]),
16
+ ...own
17
+ })
18
+
19
+ const STONE: [number, number, number] = [0.6, 0.58, 0.55]
20
+ const WOOD: [number, number, number] = [0.45, 0.31, 0.18]
21
+ const LEAF: [number, number, number] = [0.22, 0.45, 0.18]
22
+
23
+ /** Planks, stones and straw: what a house throws down when it is told to (`death.debris`) */
24
+ const PLANKS: DebrisPiece[] = [
25
+ { shape: 'box', size: [1.1, 0.12, 0.28], color: [0.45, 0.3, 0.18], count: 14 },
26
+ { shape: 'box', size: [0.4, 0.3, 0.35], color: [0.55, 0.53, 0.5], count: 10 },
27
+ { shape: 'box', size: [0.35, 0.05, 0.35], color: [0.85, 0.72, 0.38], count: 8 }
28
+ ]
29
+
30
+ /** Blown apart: pieces of itself flung hard, an explosion, smoke after, smoke trailing a third of the pieces */
31
+ const EXPLODE: DeathDef = {
32
+ collapse: { look: 'explode', pieces: 36, dust: true },
33
+ strength: 3.2,
34
+ fx: [
35
+ { fx: 'explosion', params: { size: 1.4 } },
36
+ { fx: 'smoke', delay: 0.4 }
37
+ ],
38
+ pieces: { fx: 'smoke-trail', share: 0.3, landed: 'dust', params: { size: 0.5 } }
39
+ }
40
+
41
+ /** How a building comes down, by how the rules said it died */
42
+ const BUILDING_DEATH: DeathDef = {
43
+ collapse: { look: 'crumble', pieces: 26, dust: true },
44
+ fx: 'rubble-fall',
45
+ how: {
46
+ [HOW.crumble]: { collapse: { look: 'crumble', pieces: 30, dust: true }, fx: 'rubble-fall', pieces: { landed: 'dust', share: 0.2 } },
47
+ [HOW.explode]: EXPLODE,
48
+ [HOW.blasted]: { ...EXPLODE, strength: 4 },
49
+ [HOW.swept]: { ...EXPLODE, fx: 'dust' },
50
+ [HOW.topple]: { collapse: { look: 'topple', time: 2.4, dust: true }, fx: 'rubble-fall' },
51
+ [HOW.sink]: {
52
+ collapse: { look: 'sink', time: 3, dust: true },
53
+ fx: [
54
+ { fx: 'dust', params: { size: 2 } },
55
+ { fx: 'ground-crack', params: { size: 1.4 } }
56
+ ]
57
+ },
58
+ [HOW.shatter]: { shatter: 40, strength: 1.4, fx: 'dust', pieces: { landed: 'dust', share: 0.15, params: { size: 0.4 } } },
59
+ [HOW.planks]: { debris: PLANKS, strength: 1.6, fx: 'rubble-fall' },
60
+ [HOW.crushed]: { collapse: { look: 'crumble', pieces: 30, dust: true }, strength: 1.2, fx: 'rubble-fall' }
61
+ }
62
+ }
63
+
64
+ /** Smoke under half its health, fire under a quarter; both go as it is mended */
65
+ const HURT: UnitLook['damaged'] = [
66
+ { below: 0.5, fx: 'smoke', params: { size: 0.9 } },
67
+ { below: 0.25, fx: 'fire', params: { size: 0.9 } }
68
+ ]
69
+
70
+ /** A building of the shared art: its model, the box drawn while it loads, how it comes down and how it looks hurt */
71
+ const house = (height: number, model: ModelDef, box: [number, number, number], own: Partial<UnitLook> = {}): UnitLook => ({
72
+ height,
73
+ model,
74
+ shapes: [{ shape: 'box', size: box, offset: [0, box[1] / 2, 0], color: WOOD }],
75
+ death: BUILDING_DEATH,
76
+ damaged: HURT,
77
+ // put down again (Lay it out again), it rises out of the ground
78
+ construction: { motion: 'rise', done: { fx: 'dust' } },
79
+ ...own
80
+ })
81
+
82
+ /** Trees of the shared art's forest a tree is drawn as, one picked for each */
83
+ const TREES = ['common_tree_1', 'common_tree_2', 'common_tree_3', 'common_tree_5', 'pine_1', 'pine_2', 'pine_3', 'pine_5']
84
+ const ROCKS = ['rock_medium_1', 'rock_medium_2', 'rock_medium_3']
85
+
86
+ /** Scenery broken: into pieces of its own model, flung away from what broke it */
87
+ const shattered = (pieces: number, strength: number, fx: string): DeathDef => ({
88
+ shatter: pieces,
89
+ strength,
90
+ fx,
91
+ pieces: { landed: 'dust', share: 0.1, params: { size: 0.3 } }
92
+ })
93
+
94
+ const looks: Record<string, UnitLook> = {
95
+ [D.house]: house(2.6, sized('rts/house.glb', [2.6, 2.3, 3.5], 3), [2.6, 2, 2.6], {
96
+ // one of three houses for each, picked by its id
97
+ looks: {
98
+ house: sized('rts/house.glb', [2.6, 2.3, 3.5], 3),
99
+ house_3: sized('rts/house_3.glb', [2.7, 2.8, 2.4], 3),
100
+ house_4: sized('rts/house_4.glb', [2.7, 2.3, 2.6], 3)
101
+ },
102
+ mix: ['house', 'house_3', 'house_4']
103
+ }),
104
+ [D.hut]: house(2.2, sized('rts/hut.glb', [2.6, 2.3, 2.3], 2.1), [1.8, 1.6, 1.8]),
105
+ [D.tower]: house(4.6, sized('rts/watch_tower.glb', [1.5, 4.4, 1.6], 1.9), [1.4, 4, 1.4], {
106
+ // a tower falls over rather than in on itself, when nothing else is said
107
+ death: { ...BUILDING_DEATH, collapse: { look: 'topple', time: 2, dust: true } }
108
+ }),
109
+ [D.wall]: house(1.4, sized('rts/stone_wall.glb', [4.7, 1.3, 0.7], 3.1), [3, 1.2, 0.7], {
110
+ shapes: [{ shape: 'box', size: [3, 1.2, 0.7], offset: [0, 0.6, 0], color: STONE }],
111
+ death: { ...BUILDING_DEATH, collapse: { look: 'crumble', pieces: 16, dust: true } }
112
+ }),
113
+ [D.fence]: house(1.4, sized('rts/wooden_wall.glb', [4.1, 1.6, 0.2], 3.1), [3, 1.1, 0.2], {
114
+ death: { shatter: 10, strength: 2, fx: 'dust', how: { [HOW.planks]: { debris: PLANKS.slice(0, 1), strength: 1.5 } } },
115
+ damaged: []
116
+ }),
117
+ [D.keg]: {
118
+ height: 1,
119
+ model: sized('dungeon/barrel.glb', [1, 1.3, 1], 0.8),
120
+ shapes: [{ shape: 'cylinder', size: [0.7, 0.9, 0.7], offset: [0, 0.45, 0], color: [0.5, 0.33, 0.18] }],
121
+ // the blast is the rules' (an explosion where it stood): the keg itself goes to splinters
122
+ death: { shatter: 10, strength: 3.5, pieces: { fx: 'fire-trail', share: 0.4, params: { size: 0.3 } } }
123
+ },
124
+ [D.tree]: {
125
+ height: 3,
126
+ looks: Object.fromEntries(TREES.map((name) => [name, { src: `forest/${name}.glb`, scale: 0.8 }])),
127
+ mix: TREES,
128
+ vary: { scale: 0.25, lean: 0.06 },
129
+ shapes: [
130
+ { shape: 'cylinder', size: [0.3, 1, 0.3], offset: [0, 0.5, 0], color: WOOD },
131
+ { shape: 'cone', size: [1.5, 1.8, 1.5], offset: [0, 1.7, 0], color: LEAF }
132
+ ],
133
+ death: {
134
+ ...shattered(12, 1.6, 'leaves'),
135
+ how: {
136
+ [HOW.blasted]: shattered(14, 3.4, 'leaves'),
137
+ // a wood broken at once: fewer pieces each, so the physics keeps up
138
+ [HOW.swept]: shattered(6, 4, 'leaves'),
139
+ [HOW.crushed]: { clip: undefined, fx: 'leaves', shatter: 10, strength: 1.4 }
140
+ }
141
+ },
142
+ // grown back: up out of the ground, leaves shaken off it as it is whole
143
+ construction: { motion: 'grow', done: { fx: 'leaves' } }
144
+ },
145
+ [D.rock]: {
146
+ height: 1.3,
147
+ looks: Object.fromEntries(ROCKS.map((name) => [name, { src: `forest/${name}.glb`, scale: 1.25 }])),
148
+ mix: ROCKS,
149
+ vary: { scale: 0.2 },
150
+ shapes: [{ shape: 'sphere', size: [1.8, 1.2, 1.8], offset: [0, 0.5, 0], color: STONE }],
151
+ death: { ...shattered(18, 1.6, 'dust'), how: { [HOW.blasted]: shattered(20, 3.2, 'dust'), [HOW.swept]: shattered(18, 2.8, 'dust') } },
152
+ construction: { motion: 'rise', done: { fx: 'dust' } }
153
+ },
154
+ // the shared art has no catapult: a cart of the village's, its shafts to the back, a boulder of the RTS kit's loaded
155
+ // on it, throwing boulders like it. The parts below are drawn while it loads
156
+ [D.catapult]: {
157
+ height: 2,
158
+ model: {
159
+ ...sized('village/cart.glb', [0.693, 1.194, 1.391], 2.1, { turn: Math.PI }),
160
+ points: { load: { offset: [0, 0.75, -0.2] } },
161
+ hold: [{ src: 'rts/rock.glb', at: 'load' }]
162
+ },
163
+ shapes: [
164
+ { shape: 'box', size: [1.4, 0.4, 2], offset: [0, 0.5, 0], color: WOOD },
165
+ { shape: 'cylinder', size: [0.7, 0.25, 0.7], offset: [-0.8, 0.35, 0.6], color: [0.3, 0.2, 0.12] },
166
+ { shape: 'cylinder', size: [0.7, 0.25, 0.7], offset: [0.8, 0.35, 0.6], color: [0.3, 0.2, 0.12] },
167
+ { shape: 'cylinder', size: [0.7, 0.25, 0.7], offset: [-0.8, 0.35, -0.6], color: [0.3, 0.2, 0.12] },
168
+ { shape: 'cylinder', size: [0.7, 0.25, 0.7], offset: [0.8, 0.35, -0.6], color: [0.3, 0.2, 0.12] },
169
+ { shape: 'box', size: [0.2, 1.8, 0.2], offset: [0, 1.4, -0.3], color: [0.55, 0.38, 0.22] },
170
+ { shape: 'box', size: [0.6, 0.25, 0.6], offset: [0, 2.3, -0.3], color: 'team' }
171
+ ],
172
+ shot: { model: 'rts/rock.glb', color: [0.5, 0.48, 0.45], size: 0.55 },
173
+ death: { debris: PLANKS.slice(0, 1), strength: 1.2, fx: 'dust' }
174
+ },
175
+ // nothing to see: where a blast comes from
176
+ [D.blast]: { height: 0.1, shapes: [], death: 'none' }
177
+ }
178
+
179
+ /** The ground's grass of these examples: a meadow, drawn black and short where it burnt, flat where it was laid down */
180
+ const grass = foliage({ ...presetFoliage.meadow, ruined: { stubble: 0.12 } })
181
+
182
+ // ---- the meteor's fall, drawn
183
+
184
+ /** How long a meteor takes to come down, in seconds */
185
+ const FALL = 0.9
186
+
187
+ /**
188
+ * A meteor falls on a point: a fireball from high in the sky, its trail behind it, and when it reaches the ground the
189
+ * rules are told (`destruction.meteor`): what breaks is theirs. The fall is the screen's alone
190
+ */
191
+ function callMeteor(client: GameClientApi, at: { x: number; z: number }, size = 1): void {
192
+ const from = { x: at.x - 14, y: 34, z: at.z + 10 }
193
+ const ball = client.fx.play('fireball', { at: from, params: { size: 2.4 * size, light: 4, impact: '' } })
194
+ client.timers.tween(
195
+ FALL,
196
+ (t) => {
197
+ const ease = t * t
198
+ ball.move({ at: { x: from.x + (at.x - from.x) * ease, y: from.y * (1 - ease) + 0.5, z: from.z + (at.z - from.z) * ease } })
199
+ },
200
+ {
201
+ onEnd: () => {
202
+ ball.stop()
203
+ client.session.send(meteor.make({ x: at.x, z: at.z, size }))
204
+ }
205
+ }
206
+ )
207
+ }
208
+
209
+ /** Where on the ground a click of the pointer is, or nothing over the sky */
210
+ function groundUnder(client: GameClientApi, event: PointerEvent): { x: number; z: number } | null {
211
+ const canvas = client.scene.getEngine().getRenderingCanvas()
212
+ if (!canvas) return null
213
+ const rect = canvas.getBoundingClientRect()
214
+ // the client's own (`GameClient.groundAt`): what a click picks; not in `GameClientApi`, so asked for by name
215
+ const picker = client as GameClientApi & { groundAt?: (x: number, y: number) => { x: number; z: number } | null }
216
+ return picker.groundAt?.(event.clientX - rect.left, event.clientY - rect.top) ?? null
217
+ }
218
+
219
+ /**
220
+ * The camera over a point: how far back and how steeply, turned as it is. Farther than the player may zoom out (48
221
+ * cells), the limit is let out for this example: what it gives back puts it back
222
+ */
223
+ function look(client: GameClientApi, x: number, z: number, distance: number, pitch = 50): () => void {
224
+ if (distance > client.camera.farthest) client.camera.setLimits({ max: distance })
225
+ client.camera.setPose({ x, z, distance, yaw: client.camera.pose().yaw, pitch: (pitch * Math.PI) / 180 })
226
+ return () => client.camera.setLimits({ max: 48 })
227
+ }
228
+
229
+ /** A count of what still stands of some types */
230
+ const standing = (client: GameClientApi, types: readonly string[]) =>
231
+ client.session.snapshot().entities.filter((each) => types.includes(each.type) && each.health > 0).length
232
+
233
+ const again = (id: string) =>
234
+ button(
235
+ 'Lay it out again',
236
+ (client) => client.session.send(relay.make({ example: id })),
237
+ 'Clears what is left and puts the scene back as it started: buildings rise out of the ground, the grass whole again.'
238
+ )
239
+
240
+ /** Whether a click calls a meteor down where it lands: the meteor example's, off as it is left */
241
+ let clickToStrike = false
242
+
243
+ const examples: Record<string, ExampleLook> = {
244
+ 'destruction-collapse': {
245
+ controls: [
246
+ button(
247
+ 'Crumble',
248
+ (client) => client.session.send(raze.make({ how: HOW.crumble })),
249
+ 'Brings down the first building of the row still standing: pieces of its own model slump where it stood, in a cloud of dust.'
250
+ ),
251
+ button(
252
+ 'Blow apart',
253
+ (client) => client.session.send(raze.make({ how: HOW.explode })),
254
+ 'Blows up the next building: an explosion flings pieces of its model away, a third of them trailing smoke.'
255
+ ),
256
+ button(
257
+ 'Topple',
258
+ (client) => client.session.send(raze.make({ how: HOW.topple })),
259
+ 'The next building tips over, away from the camera, in one piece and sinks into the ground.'
260
+ ),
261
+ button(
262
+ 'Sink',
263
+ (client) => client.session.send(raze.make({ how: HOW.sink })),
264
+ 'The next building sinks straight down into the ground in one piece, the ground cracking round it.'
265
+ ),
266
+ button(
267
+ 'Shatter',
268
+ (client) => client.session.send(raze.make({ how: HOW.shatter })),
269
+ 'The next building is split into 40 pieces of its own model that tumble with physics and settle on the ground.'
270
+ ),
271
+ button(
272
+ 'Planks',
273
+ (client) => client.session.send(raze.make({ how: HOW.planks })),
274
+ 'The next building falls as pieces described in code, planks, stones and straw, rather than pieces of its model.'
275
+ ),
276
+ button(
277
+ 'Hurt them',
278
+ (client) => client.session.send(hurt.make({ share: 0.2 })),
279
+ 'Takes every building down to a fifth of its health: each smokes under half and burns under a quarter.'
280
+ ),
281
+ again('destruction-collapse'),
282
+ readout('Standing', (client) => `${standing(client, [D.house, D.hut, D.tower])} of 6`, 'How many of the six buildings of the row still stand.')
283
+ ],
284
+ // the row a little left of the middle: the card stands over the right of the picture
285
+ ready: (client) => look(client, 31, 22, 60, 44)
286
+ },
287
+ 'destruction-meteor': {
288
+ controls: [
289
+ button(
290
+ 'Meteor',
291
+ (client) => callMeteor(client, { x: 24, z: 24 }, 1),
292
+ 'Drops a meteor on the middle of the village: it digs a crater and blows apart what stands near.'
293
+ ),
294
+ button(
295
+ 'Meteor shower',
296
+ (client) => {
297
+ const spots = [
298
+ [17, 29],
299
+ [31, 27],
300
+ [24, 17],
301
+ [14, 18],
302
+ [34, 18],
303
+ [24, 34]
304
+ ]
305
+ spots.forEach(([x, z], i) => client.timers.after(i * 0.45, () => callMeteor(client, { x, z }, 0.6)))
306
+ },
307
+ 'Six smaller meteors, one after another round the village: each digs its own crater and burns the grass.'
308
+ ),
309
+ toggle(
310
+ 'Click to strike',
311
+ false,
312
+ (_client, on) => (clickToStrike = on),
313
+ 'While on, a click on the ground calls a meteor down where you clicked, instead of selecting.'
314
+ ),
315
+ again('destruction-meteor'),
316
+ readout('Still standing', (client) => `${standing(client, [D.house, D.hut, D.tower, D.fence])} buildings · ${standing(client, [D.tree])} trees`)
317
+ ],
318
+ ready: (client) => {
319
+ const camera = look(client, 24, 22, 58, 48)
320
+ const canvas = client.scene.getEngine().getRenderingCanvas()
321
+ const strike = (event: PointerEvent) => {
322
+ if (!clickToStrike || event.button !== 0) return
323
+ const at = groundUnder(client, event)
324
+ if (!at) return
325
+ // the click is the meteor's: nothing is selected by it
326
+ event.stopImmediatePropagation()
327
+ callMeteor(client, at, 0.8)
328
+ }
329
+ canvas?.addEventListener('pointerdown', strike, { capture: true })
330
+ return () => {
331
+ clickToStrike = false
332
+ canvas?.removeEventListener('pointerdown', strike, { capture: true })
333
+ camera()
334
+ }
335
+ }
336
+ },
337
+ 'destruction-shockwave': {
338
+ controls: [
339
+ button(
340
+ 'Shockwave',
341
+ (client) => client.session.send(shockwave.make({})),
342
+ 'The knight sends a ring out round him: every tree, rock and fence it passes is broken and flung away, the grass laid flat.'
343
+ ),
344
+ again('destruction-shockwave'),
345
+ readout('Trees standing', (client) => String(standing(client, [D.tree])))
346
+ ],
347
+ ready: (client) => {
348
+ const camera = look(client, 24, 24, 60, 55)
349
+ // a whole wood broken at once: room for many more pieces than the player's Debris setting keeps
350
+ const was = client.debrisMost
351
+ client.debrisMost = 1500
352
+ return () => {
353
+ client.debrisMost = was
354
+ camera()
355
+ }
356
+ }
357
+ },
358
+ 'destruction-chain': {
359
+ controls: [
360
+ button(
361
+ 'Light the fuse',
362
+ (client) => client.session.send(fuse.make({})),
363
+ 'Sets off the first keg of powder: its blast sets off the kegs near it, and the fort goes up one keg after another.'
364
+ ),
365
+ again('destruction-chain'),
366
+ readout('Kegs left', (client) => String(standing(client, [D.keg])))
367
+ ],
368
+ ready: (client) => look(client, 23, 24, 62, 55)
369
+ },
370
+ 'destruction-regrow': {
371
+ controls: [
372
+ button(
373
+ 'Smash it all',
374
+ (client) => client.session.send(smash.make({})),
375
+ 'Breaks every tree and rock of the meadow at once: a few seconds later each grows back out of the ground where it stood, and the grass comes back.'
376
+ ),
377
+ readout('Standing', (client) => `${standing(client, [D.tree])} trees · ${standing(client, [D.rock])} rocks`)
378
+ ],
379
+ ready: (client) => look(client, 24, 25, 52, 48)
380
+ },
381
+ 'destruction-siege': {
382
+ controls: [
383
+ button(
384
+ 'Fire!',
385
+ (client) => client.session.send(bombard.make({})),
386
+ 'Each catapult throws boulders at what of the fort is nearest it, and at the next as that falls.'
387
+ ),
388
+ slider(
389
+ 'Debris pieces',
390
+ { min: 0, max: 600, step: 20, value: 240 },
391
+ (client, value) => (client.debrisMost = value),
392
+ 'The most broken pieces drawn at once (the Debris setting): at 0 nothing breaks apart into pieces, buildings fall without them; fire again to see the difference.'
393
+ ),
394
+ again('destruction-siege'),
395
+ readout('Fort standing', (client) => `${standing(client, [D.wall, D.tower, D.house, D.hut])} of 10`)
396
+ ],
397
+ ready: (client) => look(client, 24, 23, 56, 48)
398
+ }
399
+ }
400
+
401
+ /** The ready-made effects the destruction examples play (`presetFx`): a view plays none it did not take */
402
+ const FX = [
403
+ 'explosion',
404
+ 'fireball',
405
+ 'smoke',
406
+ 'fire',
407
+ 'dust',
408
+ 'rubble-fall',
409
+ 'ground-crack',
410
+ 'shockwave',
411
+ 'impact-ring',
412
+ 'leaves',
413
+ 'smoke-trail',
414
+ 'fire-trail',
415
+ 'blood-burst',
416
+ 'spark-burst'
417
+ ] as const
418
+
419
+ /** The meteor and the shockwave kill people too: blown off their feet (`deathFor`, for every type of the game) */
420
+ const blownApart = (view: GameView): GameView['units'] => ({
421
+ ...view.units,
422
+ looks: { ...view.units?.looks, ...looks },
423
+ deathFor: (entity, death) => {
424
+ if (entity.def.kind === 'unit' && (death.how === HOW.blasted || death.how === HOW.swept))
425
+ return { fx: [{ fx: 'blood-burst' }, { fx: 'dust' }], strength: 2 }
426
+ return view.units?.deathFor?.(entity, death)
427
+ },
428
+ // no bars over scenery, nor over a tree growing back: only over what is fought over
429
+ hud: {
430
+ ...view.units?.hud,
431
+ bars: (entity) => (SCENERY.has(entity.def.id) ? { hide: true } : view.units?.hud?.bars?.(entity))
432
+ }
433
+ })
434
+
435
+ /** What stands about to be broken: no health or progress bars over it */
436
+ const SCENERY = new Set<string>([D.tree, D.rock, D.keg, D.fence, D.blast])
437
+
438
+ export const category: CategoryLooks = {
439
+ looks: examples,
440
+ view: (view) => ({
441
+ ...view,
442
+ units: blownApart(view),
443
+ // the ready-made effects these examples play, under what the game's view says of them
444
+ fx: { ...view.fx, defs: { ...presetFx(FX), ...view.fx?.defs } },
445
+ ground: { ...view.ground, cellLayers: { ...view.ground?.cellLayers, [GRASS]: grass } }
446
+ })
447
+ }