@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,570 @@
1
+ /**
2
+ * The destruction examples on the rules' side: buildings, trees, rocks, fences and grass to break, and what breaks
3
+ * them (a meteor, a shockwave, kegs of powder, catapults). The rules only say what dies, and how (`DamageOptions.how`);
4
+ * how it comes apart on the screen is the look's (`./looks.ts`: `UnitLook.death`), with the client's physics
5
+ */
6
+ import {
7
+ type AreaShape,
8
+ createMap,
9
+ defineCellLayer,
10
+ defineCommand,
11
+ defineComponent,
12
+ defineTimer,
13
+ entityDied,
14
+ type Entity,
15
+ FOLIAGE_STATE,
16
+ foliageStateLayer,
17
+ type Game,
18
+ MAP_NEUTRAL,
19
+ mapTerrain,
20
+ number,
21
+ oneOf,
22
+ presetFoliageStyles,
23
+ terrainPatch,
24
+ text,
25
+ type UnitTypeDef,
26
+ type Vec2,
27
+ type World,
28
+ exactMath
29
+ } from '@v12-engine/engine'
30
+ import { ENEMY, PLAYER, UNIT } from '../common/units'
31
+ import { type ExampleCategory, type ExampleDef, exampleField } from '../define'
32
+
33
+ /** The destruction examples' own unit types: what stands to be broken, and what breaks it */
34
+ export const D = {
35
+ house: 'destructionHouse',
36
+ hut: 'destructionHut',
37
+ tower: 'destructionTower',
38
+ wall: 'destructionWall',
39
+ fence: 'destructionFence',
40
+ tree: 'destructionTree',
41
+ rock: 'destructionRock',
42
+ keg: 'destructionKeg',
43
+ catapult: 'destructionCatapult',
44
+ blast: 'destructionBlast'
45
+ } as const
46
+
47
+ /** The words for how something died (`DamageOptions.how`): each look says what it does for each (`death.how`) */
48
+ export const HOW = {
49
+ crumble: 'destructionCrumble',
50
+ explode: 'destructionExplode',
51
+ topple: 'destructionTopple',
52
+ sink: 'destructionSink',
53
+ shatter: 'destructionShatter',
54
+ planks: 'destructionPlanks',
55
+ // a meteor, a keg going off: blown apart, away from the blast
56
+ blasted: 'destructionBlasted',
57
+ // a shockwave: flung far, in many pieces
58
+ swept: 'destructionSwept',
59
+ // a catapult's boulder
60
+ crushed: 'destructionCrushed'
61
+ } as const
62
+
63
+ /** The layer of grass the examples lay and burn: their own, drawn by `./looks.ts`, no pack of the game's needed */
64
+ export const GRASS = 'destructionGrass'
65
+
66
+ /** Whether what falls in this world grows back by itself, after how many seconds: the regrowing example's alone */
67
+ const regrowing = defineComponent<{ after: number } | null>({ id: 'destruction.regrowing', default: () => null })
68
+
69
+ /** A number from 0 to 1 for a whole number: the same everywhere, for scattering things the same on every machine */
70
+ const hash = (n: number): number => {
71
+ let h = Math.imul(n ^ 0x9e3779b9, 0x85ebca6b)
72
+ h = Math.imul(h ^ (h >>> 13), 0xc2b2ae35)
73
+ return ((h ^ (h >>> 16)) >>> 0) / 4294967296
74
+ }
75
+
76
+ /**
77
+ * A tree, a rock: scenery to break. A building of nobody's rather than a resource, so that each is drawn as a model of
78
+ * its own, whose meshes the client can split into pieces as it dies (`death.shatter`); trees as resources are drawn
79
+ * many to a batch, and fall or throw solids (`death.debris`) instead
80
+ */
81
+ const scenery = (id: string, name: string, own: Partial<UnitTypeDef>): UnitTypeDef => ({
82
+ id,
83
+ name,
84
+ kind: 'building',
85
+ radius: 0.5,
86
+ footprint: { w: 1, d: 1 },
87
+ stats: { health: 60 },
88
+ sight: 0,
89
+ // nobody hits it on their own: only what the examples throw at it
90
+ ignored: true,
91
+ // what falls may grow back: put down as a site that builds itself (`construction: 'self'`), its look growing with it
92
+ construction: 'self',
93
+ buildTime: 5,
94
+ ...own
95
+ })
96
+
97
+ const building = (id: string, name: string, size: number, health: number, own: Partial<UnitTypeDef> = {}): UnitTypeDef => ({
98
+ id,
99
+ name,
100
+ kind: 'building',
101
+ radius: size / 2,
102
+ footprint: { w: size, d: size },
103
+ stats: { health, armor: 2 },
104
+ sight: 4,
105
+ construction: 'self',
106
+ buildTime: 4,
107
+ ...own
108
+ })
109
+
110
+ const types: UnitTypeDef[] = [
111
+ building(D.house, 'House', 3, 900),
112
+ building(D.hut, 'Hut', 2, 500),
113
+ building(D.tower, 'Watch tower', 2, 700),
114
+ // a length of wall or fence: three cells along x, one deep
115
+ { ...building(D.wall, 'Stone wall', 3, 600), footprint: { w: 3, d: 1 }, radius: 0.5 },
116
+ { ...building(D.fence, 'Fence', 3, 120), footprint: { w: 3, d: 1 }, radius: 0.5 },
117
+ building(D.keg, 'Keg of powder', 1, 40, { stats: { health: 40 } }),
118
+ scenery(D.tree, 'Tree', { fellable: true }),
119
+ scenery(D.rock, 'Rock', { footprint: { w: 2, d: 2 }, radius: 1, stats: { health: 300 }, buildTime: 4 }),
120
+ {
121
+ id: D.catapult,
122
+ name: 'Catapult',
123
+ kind: 'unit',
124
+ radius: 0.8,
125
+ stats: { health: 500, attackDamage: 160, attackSpeed: 0.5, attackRange: 16, moveSpeed: 2.5, armor: 10 },
126
+ sight: 18,
127
+ // a boulder thrown in an arc, landing where it was aimed: all near it hurt, trees brought down
128
+ attacks: [
129
+ {
130
+ damagePoint: 0.6,
131
+ acquisitionRange: 16,
132
+ projectileSpeed: 14,
133
+ splash: 2,
134
+ ground: true,
135
+ fells: true,
136
+ how: HOW.crushed,
137
+ targets: ['building', 'unit']
138
+ }
139
+ ]
140
+ },
141
+ {
142
+ // where a blast comes from: what flung pieces fly away from. Here for a second, then gone; nothing sees or hits it
143
+ id: D.blast,
144
+ name: 'Blast',
145
+ kind: 'unit',
146
+ radius: 0.1,
147
+ stats: { health: 1, moveSpeed: 0 },
148
+ sight: 0,
149
+ invulnerable: true,
150
+ targetableBy: [],
151
+ lifetime: 1
152
+ }
153
+ ]
154
+
155
+ // ---- what breaks things
156
+
157
+ /**
158
+ * Hurts everything within `radius` of a point as a blast does: in full at its heart, less to the edge, killing what it
159
+ * kills `how` it says, from a blast at the point (what the pieces fly away from). The grass under it burns black, and
160
+ * what is left of the ground is dug into a crater `depth` deep. What it hit, nearest first
161
+ */
162
+ export function blast(world: World, at: Vec2, options: { radius: number; damage: number; how: string; crater?: number; except?: Entity }): Entity[] {
163
+ const from = world.spawn(D.blast, { at, owner: MAP_NEUTRAL })
164
+ const hit = world.query({ near: at, radius: options.radius, sort: 'nearest', ...(options.except && { except: [options.except, from] }) })
165
+ for (const each of hit) {
166
+ if (each === from || each.def.id === D.blast) continue
167
+ const away = exactMath.hypot(each.pos.x - at.x, each.pos.z - at.z)
168
+ const share = 1 - 0.6 * Math.min(1, away / options.radius)
169
+ world.damage(each, options.damage * share, from, { how: options.how, tags: ['blast'] })
170
+ }
171
+ burnGrass(world, { kind: 'circle', x: at.x, z: at.z, radius: options.radius * 0.8 })
172
+ if (options.crater) crater(world, at, options.radius * 0.6, options.crater)
173
+ return hit
174
+ }
175
+
176
+ /** The grass in an area burnt black: its state layer set (`FOLIAGE_STATE.burnt`), drawn as stubble over ash */
177
+ export function burnGrass(world: World, where: AreaShape): void {
178
+ const layer = world.terrain.cells[GRASS]
179
+ if (!layer) return
180
+ world.setCells(foliageStateLayer(GRASS), where, FOLIAGE_STATE.burnt)
181
+ }
182
+
183
+ /** The ground dug into a bowl round a point (`World.reshapeGround`): deepest at the middle, `depth` down, painted dirt */
184
+ export function crater(world: World, at: Vec2, radius: number, depth: number): void {
185
+ const { terrain } = world
186
+ const x = Math.max(0, Math.floor(at.x - radius - 1))
187
+ const z = Math.max(0, Math.floor(at.z - radius - 1))
188
+ const width = Math.min(terrain.width - x, Math.ceil(radius * 2 + 2))
189
+ const deep = Math.min(terrain.depth - z, Math.ceil(radius * 2 + 2))
190
+ const patch = terrainPatch(terrain, { x, z, width, depth: deep })
191
+ const dirt = world.registry.tile('dirt')
192
+ for (let vz = 0; vz <= deep; vz++)
193
+ for (let vx = 0; vx <= width; vx++) {
194
+ const d = exactMath.hypot(x + vx - at.x, z + vz - at.z) / radius
195
+ if (d >= 1) continue
196
+ // a bowl with a lip: down in the middle, a little up at its rim
197
+ const bowl = exactMath.cos((d * Math.PI) / 2)
198
+ const down = bowl * bowl * depth - exactMath.sin(d * Math.PI) * depth * 0.15
199
+ patch.heights[vz * (width + 1) + vx] -= down
200
+ }
201
+ for (let cz = 0; cz < deep; cz++)
202
+ for (let cx = 0; cx < width; cx++)
203
+ if (exactMath.hypot(x + cx + 0.5 - at.x, z + cz + 0.5 - at.z) < radius * 0.85) patch.tiles[cz * width + cx] = dirt
204
+ world.reshapeGround(patch)
205
+ }
206
+
207
+ /** Grass laid over an area */
208
+ const grass = (world: World, where: AreaShape) => world.setCells(GRASS, where, 1)
209
+
210
+ /** Trees round a point in a disc, a cell apart at least, scattered the same every time; none within `clear` of the middle */
211
+ function grove(world: World, at: Vec2, radius: number, clear = 0, every = 0.45): void {
212
+ for (let z = Math.floor(at.z - radius); z <= at.z + radius; z++)
213
+ for (let x = Math.floor(at.x - radius); x <= at.x + radius; x++) {
214
+ const d = exactMath.hypot(x + 0.5 - at.x, z + 0.5 - at.z)
215
+ if (d > radius || d < clear || hash(x * 131 + z * 7919) > every) continue
216
+ // every other cell at most, so they stand apart
217
+ if ((x + z) % 2) continue
218
+ world.spawn(D.tree, { at: { x: x + 0.5, z: z + 0.5 }, owner: MAP_NEUTRAL })
219
+ }
220
+ }
221
+
222
+ /** Takes away everything an example put down: what it lays out again starts on an empty field */
223
+ function clear(world: World): void {
224
+ for (const each of [...world.entities.values()]) if (each.alive) world.kill(each, undefined, false, 'removed')
225
+ }
226
+
227
+ // ---- the examples' layouts
228
+
229
+ /** Five buildings in a row, each to be brought down its own way */
230
+ function row(world: World): void {
231
+ const kinds = [D.house, D.hut, D.tower, D.house, D.hut, D.house]
232
+ kinds.forEach((type, i) => world.spawn(type, { at: { x: 8.5 + i * 6.5, z: 24.5 }, owner: ENEMY }))
233
+ grass(world, { kind: 'rect', x: 4, z: 18, width: 40, depth: 12 })
234
+ }
235
+
236
+ /** A village: houses and huts round a square, fences, trees about it, rocks, grass, and its people */
237
+ function village(world: World): void {
238
+ const houses: [string, number, number][] = [
239
+ [D.house, 18.5, 28.5],
240
+ [D.house, 29.5, 28.5],
241
+ [D.hut, 24, 32],
242
+ [D.hut, 16, 21],
243
+ [D.hut, 32, 21],
244
+ [D.tower, 24, 15]
245
+ ]
246
+ for (const [type, x, z] of houses) world.spawn(type, { at: { x, z }, owner: ENEMY })
247
+ for (let i = 0; i < 3; i++) world.spawn(D.fence, { at: { x: 14.5 + i * 3, z: 16.5 }, owner: ENEMY })
248
+ for (let i = 0; i < 3; i++) world.spawn(D.fence, { at: { x: 27.5 + i * 3, z: 16.5 }, owner: ENEMY })
249
+ for (const [x, z] of [
250
+ [11, 26],
251
+ [37, 26],
252
+ [20, 37],
253
+ [30, 37]
254
+ ])
255
+ world.spawn(D.rock, { at: { x, z }, owner: MAP_NEUTRAL })
256
+ grove(world, { x: 24, z: 24 }, 18, 10, 0.6)
257
+ grass(world, { kind: 'circle', x: 24, z: 24, radius: 16 })
258
+ for (let i = 0; i < 6; i++) world.spawn(UNIT.footman, { at: { x: 21 + (i % 3) * 1.5, z: 22 + Math.floor(i / 3) * 1.5 }, owner: ENEMY })
259
+ }
260
+
261
+ /** A hero in a clearing in a wood: what the shockwave goes out from */
262
+ function clearing(world: World): void {
263
+ world.spawn(UNIT.knight, { at: { x: 24, z: 24 }, owner: PLAYER })
264
+ grove(world, { x: 24, z: 24 }, 16, 4, 0.4)
265
+ for (const [x, z] of [
266
+ [18, 18],
267
+ [30, 19],
268
+ [17, 30],
269
+ [31, 31],
270
+ [24, 33]
271
+ ])
272
+ world.spawn(D.rock, { at: { x, z }, owner: MAP_NEUTRAL })
273
+ for (let i = 0; i < 3; i++) world.spawn(D.fence, { at: { x: 20.5 + i * 3, z: 37.5 }, owner: MAP_NEUTRAL })
274
+ grass(world, { kind: 'circle', x: 24, z: 24, radius: 20 })
275
+ }
276
+
277
+ /** A fort of walls and towers with kegs of powder along a winding line through it: one lit, the rest go in turn */
278
+ function fort(world: World): void {
279
+ for (let i = 0; i < 6; i++) {
280
+ world.spawn(D.wall, { at: { x: 12.5 + i * 3, z: 34.5 }, owner: ENEMY })
281
+ world.spawn(D.wall, { at: { x: 12.5 + i * 3, z: 14.5 }, owner: ENEMY })
282
+ }
283
+ for (const [x, z] of [
284
+ [10, 14],
285
+ [30, 14],
286
+ [10, 35],
287
+ [30, 35]
288
+ ])
289
+ world.spawn(D.tower, { at: { x, z }, owner: ENEMY })
290
+ world.spawn(D.house, { at: { x: 16.5, z: 24.5 }, owner: ENEMY })
291
+ world.spawn(D.house, { at: { x: 25.5, z: 27.5 }, owner: ENEMY })
292
+ world.spawn(D.hut, { at: { x: 25, z: 20 }, owner: ENEMY })
293
+ // the kegs: a line that winds from the gate through the fort and past every building
294
+ for (const [x, z] of KEGS) world.spawn(D.keg, { at: { x: x + 0.5, z: z + 0.5 }, owner: ENEMY })
295
+ grass(world, { kind: 'rect', x: 6, z: 10, width: 30, depth: 30 })
296
+ grove(world, { x: 40, z: 24 }, 6, 0, 0.7)
297
+ }
298
+
299
+ /** Where the kegs stand, in the order the fire takes them */
300
+ const KEGS: [number, number][] = [
301
+ [34, 24],
302
+ [32, 24],
303
+ [30, 23],
304
+ [28, 22],
305
+ [28, 20],
306
+ [27, 17],
307
+ [25, 16],
308
+ [22, 17],
309
+ [20, 18],
310
+ [19, 20],
311
+ [20, 22],
312
+ [22, 23],
313
+ [22, 25],
314
+ [21, 27],
315
+ [19, 28],
316
+ [16, 28],
317
+ [13, 27],
318
+ [12, 25],
319
+ [12, 22],
320
+ [13, 19],
321
+ [12, 16],
322
+ [13, 31],
323
+ [16, 32],
324
+ [20, 32],
325
+ [24, 31],
326
+ [28, 31],
327
+ [29, 33]
328
+ ]
329
+
330
+ /** A grove, rocks and grass to smash: all of it grows back, a few seconds after it fell */
331
+ function meadow(world: World): void {
332
+ world.setState(regrowing, { after: 4 })
333
+ grove(world, { x: 24, z: 26 }, 11, 0, 0.7)
334
+ for (const [x, z] of [
335
+ [18, 20],
336
+ [30, 21],
337
+ [24, 32]
338
+ ])
339
+ world.spawn(D.rock, { at: { x, z }, owner: MAP_NEUTRAL })
340
+ grass(world, { kind: 'circle', x: 24, z: 26, radius: 14 })
341
+ }
342
+
343
+ /** A stone fort on the far side of the field, and catapults on the near side to knock it down */
344
+ function siege(world: World): void {
345
+ for (let i = 0; i < 5; i++) world.spawn(D.wall, { at: { x: 16.5 + i * 3, z: 30.5 }, owner: ENEMY })
346
+ world.spawn(D.tower, { at: { x: 14, z: 31 }, owner: ENEMY })
347
+ world.spawn(D.tower, { at: { x: 34, z: 31 }, owner: ENEMY })
348
+ world.spawn(D.house, { at: { x: 20.5, z: 35.5 }, owner: ENEMY })
349
+ world.spawn(D.house, { at: { x: 28.5, z: 35.5 }, owner: ENEMY })
350
+ world.spawn(D.hut, { at: { x: 24, z: 40 }, owner: ENEMY })
351
+ grove(world, { x: 24, z: 37 }, 12, 6, 0.4)
352
+ for (let i = 0; i < 4; i++) world.spawn(D.catapult, { at: { x: 15 + i * 6, z: 12 }, owner: PLAYER, facing: Math.PI / 2 })
353
+ grass(world, { kind: 'rect', x: 4, z: 26, width: 40, depth: 18 })
354
+ }
355
+
356
+ /** Each example's layout, by its id: what it starts with, and what Lay it out again lays */
357
+ const LAYOUTS: Record<string, (world: World) => void> = {
358
+ 'destruction-collapse': row,
359
+ 'destruction-meteor': village,
360
+ 'destruction-shockwave': clearing,
361
+ 'destruction-chain': fort,
362
+ 'destruction-regrow': meadow,
363
+ 'destruction-siege': siege
364
+ }
365
+
366
+ // ---- commands, sent by the examples' buttons
367
+
368
+ /** Brings the first building of the row still standing down, `how` the button says */
369
+ export const raze = defineCommand('destruction.raze', { how: oneOf(...Object.values(HOW)) }, (world, _player, { how }) => {
370
+ const standing = world.query({ kinds: ['building'], owner: ENEMY }).sort((a, b) => a.pos.x - b.pos.x)[0]
371
+ if (!standing) return
372
+ // hit from in front, where the camera is: a toppled one falls away from it
373
+ const from = world.spawn(D.blast, { at: { x: standing.pos.x - 1, z: standing.pos.z - 6 }, owner: MAP_NEUTRAL })
374
+ // more than it has, past its armour
375
+ world.damage(standing, standing.maxHealth * 10, from, { how })
376
+ })
377
+
378
+ /** Hurts every building to a share of its health: what its look shows under a half, a quarter */
379
+ export const hurt = defineCommand('destruction.hurt', { share: number }, (world, _player, { share }) => {
380
+ for (const each of world.query({ kinds: ['building'], owner: ENEMY })) each.health = Math.max(1, Math.min(each.health, each.maxHealth * share))
381
+ })
382
+
383
+ /** A meteor comes down at a point: a big blast, a crater, everything round it broken and flung away */
384
+ export const meteor = defineCommand('destruction.meteor', { x: number, z: number, size: number }, (world, _player, { x, z, size }) => {
385
+ world.fx('explosion', { at: { x, z }, params: { size: 2.6 * size, shake: 1.4 } })
386
+ world.fx('ground-crack', { at: { x, z }, params: { size: 2.2 * size } })
387
+ world.fx('shockwave', { at: { x, z }, params: { size: 3 * size } })
388
+ blast(world, { x, z }, { radius: 9.5 * size, damage: 2500, how: HOW.blasted, crater: 1.6 * size })
389
+ })
390
+
391
+ /** How fast the shockwave goes out, in cells a second, and how far */
392
+ const WAVE = { speed: 8, reach: 20 }
393
+
394
+ /** A ring that goes out from the hero, fells what it passes */
395
+ const waveTimer = defineTimer<{ x: number; z: number; step: number; hero: number }>({
396
+ id: 'destruction.wave',
397
+ run(world, { x, z, step, hero }) {
398
+ const reach = Math.min(WAVE.reach, (step + 1) * WAVE.speed * 0.1)
399
+ const inner = step * WAVE.speed * 0.1
400
+ const from = world.entities.get(hero)
401
+ const ring = world.query({ near: { x, z }, radius: reach, where: (each) => exactMath.hypot(each.pos.x - x, each.pos.z - z) >= inner - 0.5 })
402
+ for (const each of ring) if (each !== from && each.def.id !== D.blast) world.damage(each, 5000, from, { how: HOW.swept })
403
+ // the grass in the ring laid flat, away from the hero
404
+ const cells = []
405
+ for (let cz = Math.floor(z - reach); cz <= z + reach; cz++)
406
+ for (let cx = Math.floor(x - reach); cx <= x + reach; cx++) {
407
+ const d = exactMath.hypot(cx + 0.5 - x, cz + 0.5 - z)
408
+ if (d < reach && d >= inner - 1 && cx >= 0 && cz >= 0 && cx < world.terrain.width && cz < world.terrain.depth)
409
+ cells.push(cz * world.terrain.width + cx)
410
+ }
411
+ if (world.terrain.cells[GRASS]) world.setCells(foliageStateLayer(GRASS), cells, FOLIAGE_STATE.flattened)
412
+ if (reach < WAVE.reach) world.timers.after(0.1, waveTimer, { x, z, step: step + 1, hero })
413
+ }
414
+ })
415
+
416
+ /** The hero sends a shockwave out round it */
417
+ export const shockwave = defineCommand('destruction.shockwave', {}, (world) => {
418
+ const hero = world.query({ type: UNIT.knight, limit: 1 })[0]
419
+ if (!hero) return
420
+ const { x, z } = hero.pos
421
+ world.fx('shockwave', { at: { x, z }, params: { size: 6 } })
422
+ world.fx('impact-ring', { at: { x, z }, params: { size: 4 } })
423
+ world.fx('ground-crack', { at: { x, z }, params: { size: 1.5 } })
424
+ world.timers.after(0.05, waveTimer, { x, z, step: 0, hero: hero.id })
425
+ })
426
+
427
+ /** A keg that went off: the blast it makes a moment after, which sets off the kegs near it */
428
+ const kegBlast = defineTimer<{ x: number; z: number }>({
429
+ id: 'destruction.kegBlast',
430
+ run(world, at) {
431
+ world.fx('explosion', { at, params: { size: 1.3, shake: 0.5 } })
432
+ blast(world, at, { radius: 4.3, damage: 900, how: HOW.blasted, crater: 0.35 })
433
+ }
434
+ })
435
+
436
+ /** Lights the first keg: the rest go one after another */
437
+ export const fuse = defineCommand('destruction.fuse', {}, (world) => {
438
+ const first = world.query({ type: D.keg }).sort((a, b) => b.pos.x - a.pos.x)[0]
439
+ if (first) world.damage(first, 1000, undefined, { how: HOW.blasted })
440
+ })
441
+
442
+ /** Everything in the meadow smashed at once: it grows back by itself (`regrowing`) */
443
+ export const smash = defineCommand('destruction.smash', {}, (world) => {
444
+ world.fx('shockwave', { at: { x: 24, z: 26 }, params: { size: 5 } })
445
+ blast(world, { x: 24, z: 26 }, { radius: 16, damage: 5000, how: HOW.shatter })
446
+ world.timers.after(6, grassBack, { x: 24, z: 26, radius: 16 })
447
+ })
448
+
449
+ /** Burnt grass grows back: whole again, eased in over a few seconds on the screens (`setCells`' `over`) */
450
+ const grassBack = defineTimer<{ x: number; z: number; radius: number }>({
451
+ id: 'destruction.grassBack',
452
+ run(world, { x, z, radius }) {
453
+ world.setCells(foliageStateLayer(GRASS), { kind: 'circle', x, z, radius }, FOLIAGE_STATE.whole, { over: 4 })
454
+ }
455
+ })
456
+
457
+ /** A tree, a rock that fell grows back where it stood (`regrowing`): put down as a site that builds itself */
458
+ const regrow = defineTimer<{ type: string; x: number; z: number }>({
459
+ id: 'destruction.regrow',
460
+ run(world, { type, x, z }) {
461
+ try {
462
+ world.spawn(type, { at: { x, z }, owner: MAP_NEUTRAL, underConstruction: true })
463
+ } catch {
464
+ // something stands there now: it does not come back
465
+ }
466
+ }
467
+ })
468
+
469
+ /** A catapult told to throw at what of the fort is nearest it */
470
+ function aim(world: World, catapult: Entity): void {
471
+ const target = world.query({ near: catapult, kinds: ['building'], owner: ENEMY, sort: 'nearest', limit: 1 })[0]
472
+ if (target) catapult.order('attack', target)
473
+ }
474
+
475
+ /** The catapults told to throw at the fort: each at what of it is nearest, and at the next as that falls */
476
+ export const bombard = defineCommand('destruction.bombard', {}, (world) => {
477
+ for (const catapult of world.query({ type: D.catapult })) aim(world, catapult)
478
+ })
479
+
480
+ /** Takes everything away and lays the example out again, as it started */
481
+ export const relay = defineCommand('destruction.relay', { example: text }, (world, _player, { example }) => {
482
+ const layout = LAYOUTS[example]
483
+ if (!layout) return
484
+ clear(world)
485
+ // the ground and the grass as they were: flat, grass whole again
486
+ const { terrain } = world
487
+ const patch = terrainPatch(terrain, { x: 0, z: 0, width: terrain.width, depth: terrain.depth })
488
+ const grassTile = world.registry.tile('grass')
489
+ // as high as a new map's ground stands, which the field is
490
+ patch.heights.fill(mapTerrain(createMap({ name: 'ground', width: 1, depth: 1, players: 1, tileset: world.registry.tileIds })).heights[0])
491
+ patch.tiles.fill(grassTile)
492
+ world.reshapeGround(patch)
493
+ if (terrain.cells[GRASS]) {
494
+ world.setCells(GRASS, { kind: 'rect', x: 0, z: 0, width: terrain.width, depth: terrain.depth }, 0)
495
+ world.setCells(foliageStateLayer(GRASS), { kind: 'rect', x: 0, z: 0, width: terrain.width, depth: terrain.depth }, FOLIAGE_STATE.whole)
496
+ }
497
+ layout(world)
498
+ })
499
+
500
+ const example = (id: string, title: string, summary: string, tags: string[], own: Partial<ExampleDef> = {}): ExampleDef => ({
501
+ id,
502
+ category: 'destruction',
503
+ title,
504
+ summary,
505
+ tags,
506
+ map: () => exampleField({ name: title, players: 2 }),
507
+ start: (world) => LAYOUTS[id]?.(world),
508
+ source: ['examples/destruction/index.ts', 'examples/destruction/looks.ts'],
509
+ ...own
510
+ })
511
+
512
+ export const category: ExampleCategory = {
513
+ rules: (game: Game<any>) => {
514
+ for (const type of types) game.unit(type)
515
+ game.component(regrowing)
516
+ game.cellLayer(
517
+ defineCellLayer({ id: GRASS, name: 'Grass (destruction)', values: [{ value: 1, name: 'Meadow', foliage: presetFoliageStyles.meadow }] })
518
+ )
519
+ for (const command of [raze, hurt, meteor, shockwave, fuse, smash, bombard, relay]) game.command(command)
520
+ for (const timer of [waveTimer, kegBlast, regrow, grassBack]) game.timer(timer)
521
+ game.on(entityDied, (world, { entity, killer, reason }) => {
522
+ // a catapult that brought something down throws at the next
523
+ if (killer?.def.id === D.catapult && killer.alive) aim(world, killer)
524
+ // a keg that is hit goes off a moment later: that is what sets the next one off
525
+ if (entity.def.id === D.keg && reason !== 'removed') world.timers.after(0.18, kegBlast, { x: entity.pos.x, z: entity.pos.z })
526
+ // in the regrowing example, scenery that fell comes back
527
+ const regrows = world.state(regrowing)
528
+ if (regrows && reason !== 'removed' && (entity.def.id === D.tree || entity.def.id === D.rock))
529
+ world.timers.after(regrows.after + hash(entity.id) * 3, regrow, { type: entity.def.id, x: entity.pos.x, z: entity.pos.z })
530
+ })
531
+ },
532
+ examples: [
533
+ example(
534
+ 'destruction-collapse',
535
+ 'Six ways to come down',
536
+ 'A row of buildings, and a button for each way one breaks: crumbling into pieces of itself, blown apart, toppled, sunk, shattered, thrown down as planks. Hurt them first to see smoke and fire.',
537
+ ['building', 'collapse', 'crumble', 'explode', 'topple', 'shatter', 'debris', 'physics', 'damaged']
538
+ ),
539
+ example(
540
+ 'destruction-meteor',
541
+ 'Meteor on a village',
542
+ 'Call a meteor down on a village: it digs a crater, burns the grass, and blows the houses, fences, trees, rocks and people round it apart, every piece flung away from the blast.',
543
+ ['meteor', 'blast', 'crater', 'explosion', 'trees', 'rocks', 'grass', 'physics', 'shatter']
544
+ ),
545
+ example(
546
+ 'destruction-shockwave',
547
+ 'Shockwave through a forest',
548
+ 'The knight sends a shockwave out: a ring that goes out through the wood, breaking every tree, rock and fence it passes and laying the grass flat, all of it flung away from him.',
549
+ ['shockwave', 'trees', 'forest', 'rocks', 'grass', 'ring', 'physics']
550
+ ),
551
+ example(
552
+ 'destruction-chain',
553
+ 'Chain reaction',
554
+ 'Kegs of powder wind through a fort. Light the first: each keg that goes off sets off the next, and the walls, towers and houses beside them come down with it.',
555
+ ['chain', 'explosion', 'kegs', 'barrels', 'fort', 'walls', 'timers']
556
+ ),
557
+ example(
558
+ 'destruction-regrow',
559
+ 'Smashed, then grown back',
560
+ 'Smash a grove, its rocks and its grass: a few seconds later each tree and rock grows back where it stood, and the grass after it.',
561
+ ['regrow', 'respawn', 'trees', 'rocks', 'grass', 'construction']
562
+ ),
563
+ example(
564
+ 'destruction-siege',
565
+ 'Catapults on a fort',
566
+ 'Four catapults throw boulders at a stone fort: walls smoke and burn as they are hurt and crumble when they fall, and the trees round the fort are knocked down where boulders land.',
567
+ ['siege', 'catapult', 'splash', 'walls', 'damaged', 'fells', 'trees']
568
+ )
569
+ ]
570
+ }