@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,603 @@
1
+ /**
2
+ * The dynamic-map examples on the rules' side: the ground changed while the game runs. A part of it is taken
3
+ * (`terrainPatch`), its heights, levels, water and paint changed, and put back (`world.reshapeGround`): every screen
4
+ * draws it again, the fog sees by its new levels and the ways are cut again where it changed, so units walk round
5
+ * what was raised and over what was lowered. Grass is a layer of cells (`world.setCells`), trees are things put down
6
+ * and taken away, and a region of new land is made by the engine's map generator (`generateMap`) as the game goes
7
+ */
8
+ import {
9
+ defineCellLayer,
10
+ defineCommand,
11
+ defineComponent,
12
+ defineTimer,
13
+ type Entity,
14
+ type Game,
15
+ generateMap,
16
+ MAP_NEUTRAL,
17
+ mapTerrain,
18
+ type MapGenerator,
19
+ number,
20
+ oneOf,
21
+ presetFoliageStyles,
22
+ scatterPass,
23
+ terrainPass,
24
+ type TerrainPatch,
25
+ terrainPatch,
26
+ text,
27
+ tilesPass,
28
+ type UnitTypeDef,
29
+ type Vec2,
30
+ WATER_DEEP,
31
+ WATER_NONE,
32
+ WATER_SHALLOW,
33
+ type World,
34
+ exactMath
35
+ } from '@v12-engine/engine'
36
+ import { PLAYER, UNIT } from '../common/units'
37
+ import { type ExampleCategory, type ExampleDef, exampleField } from '../define'
38
+
39
+ /** The dynamic-map examples' own: a tree to plant and clear, and the grass they grow */
40
+ export const TREE = 'dynamicMapTree'
41
+ export const GRASS = 'dynamicMapGrass'
42
+
43
+ /** How much higher a cliff's top is than its foot: what the maps of the engine use (`GameMap.cliffHeight`) */
44
+ const CLIFF = 3
45
+
46
+ const tree: UnitTypeDef = {
47
+ id: TREE,
48
+ name: 'Tree',
49
+ // a building of nobody's: put down while the game runs, it grows (`construction: 'self'`) and blocks the way
50
+ kind: 'building',
51
+ radius: 0.5,
52
+ footprint: { w: 1, d: 1 },
53
+ stats: { health: 60 },
54
+ sight: 0,
55
+ ignored: true,
56
+ fellable: true,
57
+ construction: 'self',
58
+ buildTime: 3
59
+ }
60
+
61
+ /**
62
+ * How high the ground of the field is as the map was made (the field is not at 0): what every height here is over,
63
+ * read as an example starts
64
+ */
65
+ const groundBase = defineComponent<{ base: number }>({ id: 'dynamicMap.base', default: () => ({ base: 0 }) })
66
+ const baseOf = (world: World) => world.state(groundBase).base
67
+
68
+ /** The water's surface in the flooding example, over the field's ground, and where it is going */
69
+ const flood = defineComponent<{ level: number; to: number }>({ id: 'dynamicMap.flood', default: () => ({ level: -3, to: -3 }) })
70
+
71
+ // ---- the ground, a part at a time
72
+
73
+ /** The part of the ground a rectangle of cells covers, inside the map; none where it is all outside */
74
+ function rectOf(world: World, x0: number, z0: number, x1: number, z1: number) {
75
+ const { width, depth } = world.terrain
76
+ const x = Math.max(0, Math.floor(x0))
77
+ const z = Math.max(0, Math.floor(z0))
78
+ const w = Math.min(width, Math.ceil(x1)) - x
79
+ const d = Math.min(depth, Math.ceil(z1)) - z
80
+ return w > 0 && d > 0 ? { x, z, width: w, depth: d } : undefined
81
+ }
82
+
83
+ /**
84
+ * Changes a part of the ground: `change` is given the patch (`TerrainPatch`) and writes into it, then it is put back
85
+ * with `world.reshapeGround`, which tells every screen and cuts the ways again there
86
+ */
87
+ function reshape(world: World, x0: number, z0: number, x1: number, z1: number, change: (patch: TerrainPatch) => void): void {
88
+ const rect = rectOf(world, x0, z0, x1, z1)
89
+ if (!rect) return
90
+ const patch = terrainPatch(world.terrain, rect)
91
+ change(patch)
92
+ world.reshapeGround(patch)
93
+ }
94
+
95
+ /** Each vertex of a patch, with where it is on the map and its index in `heights` and `waterHeights` */
96
+ function eachVertex(patch: TerrainPatch, run: (x: number, z: number, i: number) => void): void {
97
+ for (let vz = 0; vz <= patch.depth; vz++) for (let vx = 0; vx <= patch.width; vx++) run(patch.x + vx, patch.z + vz, vz * (patch.width + 1) + vx)
98
+ }
99
+
100
+ /** Each cell of a patch, with where its middle is on the map and its index in the cells' layers */
101
+ function eachCell(patch: TerrainPatch, run: (x: number, z: number, i: number) => void): void {
102
+ for (let cz = 0; cz < patch.depth; cz++) for (let cx = 0; cx < patch.width; cx++) run(patch.x + cx + 0.5, patch.z + cz + 0.5, cz * patch.width + cx)
103
+ }
104
+
105
+ /** How much of a brush reaches a point: 1 at its middle, easing to 0 at its edge */
106
+ const falloff = (d: number, radius: number) => {
107
+ if (d >= radius) return 0
108
+ const edge = exactMath.cos((d / radius) * (Math.PI / 2))
109
+ return edge * edge
110
+ }
111
+
112
+ /** What the sculpting brush does */
113
+ export const BRUSHES = ['Raise', 'Dig', 'Flatten', 'Plateau', 'Water'] as const
114
+
115
+ /** The ground under a brush changed: raised or dug a little more each stroke, flattened, a plateau with cliffs, a pool */
116
+ function sculptAt(world: World, at: Vec2, radius: number, brush: (typeof BRUSHES)[number]): void {
117
+ const base = baseOf(world)
118
+ const tile = { rock: world.registry.tile('rock'), dirt: world.registry.tile('dirt'), sand: world.registry.tile('sand') }
119
+ reshape(world, at.x - radius - 1, at.z - radius - 1, at.x + radius + 1, at.z + radius + 1, (patch) => {
120
+ if (brush === 'Plateau') {
121
+ // a level up: the cells inside are of level 1 and their corners a cliff's height up; the client draws the walls
122
+ eachCell(patch, (x, z, i) => {
123
+ if (exactMath.hypot(x - at.x, z - at.z) > radius) return
124
+ patch.levels[i] = Math.max(patch.levels[i], 1)
125
+ patch.tiles[i] = tile.rock
126
+ })
127
+ eachVertex(patch, (x, z, i) => {
128
+ if (exactMath.hypot(x - at.x, z - at.z) <= radius + 0.5) patch.heights[i] = Math.max(patch.heights[i], base + CLIFF)
129
+ })
130
+ return
131
+ }
132
+ if (brush === 'Water') {
133
+ // a hollow with water in it: deep in the middle, shallow at its edge, sand round it
134
+ eachVertex(patch, (x, z, i) => {
135
+ const d = exactMath.hypot(x - at.x, z - at.z)
136
+ patch.heights[i] = Math.min(patch.heights[i], base - 2.2 * falloff(d, radius + 0.5))
137
+ patch.waterHeights[i] = base - 0.35
138
+ })
139
+ eachCell(patch, (x, z, i) => {
140
+ const d = exactMath.hypot(x - at.x, z - at.z)
141
+ if (d < radius) patch.water[i] = d < radius * 0.6 ? WATER_DEEP : WATER_SHALLOW
142
+ if (d < radius + 1) patch.tiles[i] = tile.sand
143
+ })
144
+ return
145
+ }
146
+ eachVertex(patch, (x, z, i) => {
147
+ const f = falloff(exactMath.hypot(x - at.x, z - at.z), radius)
148
+ if (brush === 'Raise') patch.heights[i] += 1.4 * f
149
+ else if (brush === 'Dig') patch.heights[i] -= 1.4 * f
150
+ else patch.heights[i] = base + (patch.heights[i] - base) * (1 - f)
151
+ })
152
+ // a pit is earth, a hill's top rock
153
+ if (brush === 'Dig') eachCell(patch, (x, z, i) => falloff(exactMath.hypot(x - at.x, z - at.z), radius) > 0.5 && (patch.tiles[i] = tile.dirt))
154
+ if (brush === 'Flatten')
155
+ eachCell(patch, (x, z, i) => {
156
+ if (exactMath.hypot(x - at.x, z - at.z) >= radius * 0.8) return
157
+ patch.levels[i] = 0
158
+ patch.water[i] = WATER_NONE
159
+ })
160
+ })
161
+ }
162
+
163
+ /** The ground of the whole map as it was made: flat, grass, dry, of level 0 */
164
+ function flatten(world: World): void {
165
+ const grass = world.registry.tile('grass')
166
+ const base = baseOf(world)
167
+ reshape(world, 0, 0, world.terrain.width, world.terrain.depth, (patch) => {
168
+ patch.heights.fill(base)
169
+ patch.waterHeights.fill(base)
170
+ patch.levels.fill(0)
171
+ patch.water.fill(WATER_NONE)
172
+ patch.tiles.fill(grass)
173
+ })
174
+ }
175
+
176
+ /** Footmen of the player's that walk back and forth between two points: what shows the ways changing */
177
+ function walkers(world: World, from: Vec2, to: Vec2, count = 6): void {
178
+ for (let i = 0; i < count; i++) {
179
+ const at = { x: from.x + (i % 3) * 1.6 - 1.6, z: from.z + Math.floor(i / 3) * 1.6 }
180
+ const unit = world.spawn(UNIT.footman, { at, owner: PLAYER })
181
+ unit.order('patrol', { x: to.x + (i % 3) * 1.6 - 1.6, z: to.z - Math.floor(i / 3) * 1.6 })
182
+ }
183
+ }
184
+
185
+ /** Takes away everything an example put down, and lays the ground flat */
186
+ function clear(world: World): void {
187
+ for (const each of [...world.entities.values()]) if (each.alive) world.kill(each, undefined, false, 'removed')
188
+ flatten(world)
189
+ if (world.terrain.cells[GRASS]) world.setCells(GRASS, { kind: 'rect', x: 0, z: 0, width: world.terrain.width, depth: world.terrain.depth }, 0)
190
+ }
191
+
192
+ // ---- the examples' layouts
193
+
194
+ /** A field with footmen walking across it, to sculpt under their feet */
195
+ function field(world: World): void {
196
+ walkers(world, { x: 24, z: 8 }, { x: 24, z: 40 })
197
+ sculptAt(world, { x: 15, z: 24 }, 5, 'Raise')
198
+ sculptAt(world, { x: 15, z: 24 }, 5, 'Raise')
199
+ sculptAt(world, { x: 34, z: 26 }, 4, 'Dig')
200
+ }
201
+
202
+ /** The river's valley: across the field, deep in the middle, shallow at its east end where it can be forded */
203
+ function valley(world: World): void {
204
+ const base = baseOf(world)
205
+ const sand = world.registry.tile('sand')
206
+ const dirt = world.registry.tile('dirt')
207
+ reshape(world, 0, 16, world.terrain.width, 33, (patch) => {
208
+ eachVertex(patch, (x, z, i) => {
209
+ const across = falloff(Math.abs(z - 24.5), 8)
210
+ // the floor rises towards the ford in the east
211
+ const floor = x > 36 ? -0.9 : x > 32 ? -0.9 - ((36 - x) / 4) * 1.6 : -2.5
212
+ patch.heights[i] = base + floor * Math.min(1, across * 1.6)
213
+ })
214
+ eachCell(patch, (_x, z, i) => {
215
+ const d = Math.abs(z - 24.5)
216
+ patch.tiles[i] = d < 4 ? dirt : d < 6.5 ? sand : patch.tiles[i]
217
+ })
218
+ })
219
+ walkers(world, { x: 16, z: 8 }, { x: 16, z: 40 })
220
+ world.setState(flood, { level: -3, to: -3 })
221
+ }
222
+
223
+ /** A field, footmen walking across it: where the cliff will rise */
224
+ function plain(world: World): void {
225
+ walkers(world, { x: 24, z: 9 }, { x: 24, z: 40 })
226
+ }
227
+
228
+ /** The west half grass, the east half sea: where new land is made */
229
+ function shore(world: World): void {
230
+ sea(world)
231
+ walkers(world, { x: 10, z: 10 }, { x: 10, z: 38 }, 3)
232
+ }
233
+
234
+ /** The sea east of x 24 */
235
+ function sea(world: World): void {
236
+ const base = baseOf(world)
237
+ const sand = world.registry.tile('sand')
238
+ reshape(world, 22, 0, world.terrain.width, world.terrain.depth, (patch) => {
239
+ eachVertex(patch, (x, _z, i) => {
240
+ patch.heights[i] = base - (x <= 23 ? 0 : 2.4 * Math.min(1, (x - 23) / 3))
241
+ patch.waterHeights[i] = base - 0.4
242
+ })
243
+ eachCell(patch, (x, _z, i) => {
244
+ patch.levels[i] = 0
245
+ patch.water[i] = x > 25 ? WATER_DEEP : x > 23.5 ? WATER_SHALLOW : WATER_NONE
246
+ patch.tiles[i] = sand
247
+ })
248
+ })
249
+ }
250
+
251
+ /** An empty meadow to plant: footmen at its west edge, to walk the road once it is cleared */
252
+ function meadow(world: World): void {
253
+ for (let i = 0; i < 4; i++) world.spawn(UNIT.footman, { at: { x: 5, z: 22 + i * 1.4 }, owner: PLAYER })
254
+ }
255
+
256
+ /** Each example's layout, by its id */
257
+ const LAYOUTS: Record<string, (world: World) => void> = {
258
+ 'dynamic-map-sculpt': field,
259
+ 'dynamic-map-water': valley,
260
+ 'dynamic-map-cliffs': plain,
261
+ 'dynamic-map-generate': shore,
262
+ 'dynamic-map-forest': meadow,
263
+ 'dynamic-map-paint': (world) => walkers(world, { x: 24, z: 10 }, { x: 24, z: 38 }, 3)
264
+ }
265
+
266
+ // ---- commands, sent by the examples' buttons and clicks
267
+
268
+ /** The ground sculpted where the player clicked, or where the camera looks */
269
+ export const sculpt = defineCommand(
270
+ 'dynamicMap.sculpt',
271
+ { x: number, z: number, radius: number, brush: oneOf(...BRUSHES) },
272
+ (world, _player, { x, z, radius, brush }) => sculptAt(world, { x, z }, Math.min(12, Math.max(1, radius)), brush)
273
+ )
274
+
275
+ /** The tiles the paint brush has */
276
+ export const PAINTS = ['grass', 'darkGrass', 'dirt', 'road', 'sand', 'rock', 'snow'] as const
277
+
278
+ /** The ground painted in a circle with a tile */
279
+ export const paint = defineCommand(
280
+ 'dynamicMap.paint',
281
+ { x: number, z: number, radius: number, tile: oneOf(...PAINTS) },
282
+ (world, _player, { x, z, radius, tile }) => {
283
+ const index = world.registry.tile(tile)
284
+ reshape(world, x - radius, z - radius, x + radius + 1, z + radius + 1, (patch) =>
285
+ eachCell(patch, (cx, cz, i) => exactMath.hypot(cx - x, cz - z) <= radius && (patch.tiles[i] = index))
286
+ )
287
+ }
288
+ )
289
+
290
+ /** Snow spreading out from a point, a ring a step (`snowfall`) */
291
+ const snowfall = defineTimer<{ x: number; z: number; ring: number }>({
292
+ id: 'dynamicMap.snowfall',
293
+ run(world, { x, z, ring }) {
294
+ const snow = world.registry.tile('snow')
295
+ const radius = ring * 1.5
296
+ reshape(world, x - radius - 1, z - radius - 1, x + radius + 1, z + radius + 1, (patch) =>
297
+ eachCell(patch, (cx, cz, i) => {
298
+ // a ragged edge: a cell near it is snow by a hash of where it is
299
+ const d = exactMath.hypot(cx - x, cz - z) + exactMath.sin(cx * 1.7 + cz * 2.3) * 0.8
300
+ if (d <= radius) patch.tiles[i] = snow
301
+ })
302
+ )
303
+ if (radius < 15) world.timers.after(0.25, snowfall, { x, z, ring: ring + 1 })
304
+ }
305
+ })
306
+
307
+ export const spreadSnow = defineCommand('dynamicMap.snow', { x: number, z: number }, (world, _player, at) =>
308
+ world.timers.after(0.05, snowfall, { ...at, ring: 1 })
309
+ )
310
+
311
+ /** A road painted from the west edge to the east, winding a little */
312
+ export const road = defineCommand('dynamicMap.road', {}, (world) => {
313
+ const tile = world.registry.tile('road')
314
+ reshape(world, 0, 14, world.terrain.width, 34, (patch) =>
315
+ eachCell(patch, (x, z, i) => Math.abs(z - (24 + exactMath.sin(x / 6) * 4)) < 1.6 && (patch.tiles[i] = tile))
316
+ )
317
+ })
318
+
319
+ /** The water of the valley raised or lowered a step at a time to where it is going (`flood`) */
320
+ const tide = defineTimer({
321
+ id: 'dynamicMap.tide',
322
+ run(world) {
323
+ const state = world.state(flood)
324
+ const step = Math.sign(state.to - state.level) * Math.min(0.15, Math.abs(state.to - state.level))
325
+ const level = state.level + step
326
+ world.setState(flood, { ...state, level })
327
+ reshape(world, 0, 15, world.terrain.width, 34, (patch) => {
328
+ const rowOf = patch.width + 1
329
+ const surface = baseOf(world) + level
330
+ eachVertex(patch, (_x, _z, i) => (patch.waterHeights[i] = surface))
331
+ // a cell is under water where its lowest corner is under the surface: deep past most of a unit's height
332
+ eachCell(patch, (_x, _z, i) => {
333
+ const cz = Math.floor(i / patch.width)
334
+ const cx = i % patch.width
335
+ const at = cz * rowOf + cx
336
+ const lowest = Math.min(patch.heights[at], patch.heights[at + 1], patch.heights[at + rowOf], patch.heights[at + rowOf + 1])
337
+ const depth = surface - lowest
338
+ patch.water[i] = depth > 1 ? WATER_DEEP : depth > 0.05 ? WATER_SHALLOW : WATER_NONE
339
+ })
340
+ })
341
+ if (Math.abs(state.to - level) > 1e-3) world.timers.after(0.2, tide)
342
+ }
343
+ })
344
+
345
+ /** The valley flooded up to a level, or drained (a level under its floor) */
346
+ export const setFlood = defineCommand('dynamicMap.flood', { level: number }, (world, _player, { level }) => {
347
+ world.setState(flood, { ...world.state(flood), to: level })
348
+ world.timers.after(0.05, tide)
349
+ })
350
+
351
+ /** Where the cliff wall goes: across the field, from west to east */
352
+ const WALL = { z0: 22, z1: 26, x0: 3, x1: 45 }
353
+
354
+ /** A cliff wall raised across the field: a level up, a band of cells, with only its ends to go round */
355
+ export const raiseWall = defineCommand('dynamicMap.wall', {}, (world) => {
356
+ const rock = world.registry.tile('rock')
357
+ const base = baseOf(world)
358
+ reshape(world, WALL.x0, WALL.z0, WALL.x1, WALL.z1, (patch) => {
359
+ patch.levels.fill(1)
360
+ patch.tiles.fill(rock)
361
+ patch.heights.fill(base + CLIFF)
362
+ })
363
+ })
364
+
365
+ /** A ramp cut through the wall in its middle: up the south side, over, down the north. The way across is open */
366
+ export const cutRamp = defineCommand('dynamicMap.ramp', {}, (world) => {
367
+ const dirt = world.registry.tile('dirt')
368
+ const { z0, z1 } = WALL
369
+ const base = baseOf(world)
370
+ reshape(world, 21, z0 - 5, 28, z1 + 5, (patch) => {
371
+ eachVertex(patch, (x, z, i) => {
372
+ if (x < 22 || x > 27) return
373
+ // up over four cells from either side: no step a cell is a cliff's rise
374
+ const up = z < z0 ? (z - (z0 - 4)) / 4 : z > z1 ? (z1 + 4 - z) / 4 : 1
375
+ patch.heights[i] = Math.max(patch.heights[i], base + CLIFF * Math.max(0, Math.min(1, up)))
376
+ })
377
+ eachCell(patch, (x, z, i) => {
378
+ if (x < 22 || x > 27) return
379
+ if (z < z0 || z > z1) patch.levels[i] = 0
380
+ patch.tiles[i] = dirt
381
+ })
382
+ })
383
+ })
384
+
385
+ /** The wall and the ramp gone: the field flat again */
386
+ export const lower = defineCommand('dynamicMap.lower', {}, (world) => flatten(world))
387
+
388
+ /** Trees put down by the generator's scatter pass, as objects of the map, put in the world as it is laid */
389
+ const SCATTER = 'dynamicMap.trees'
390
+
391
+ /**
392
+ * The generator of the land that rises out of the sea: hills and levels with ramps (`terrainPass`), painted by height
393
+ * (`tilesPass`), trees in patches (`scatterPass`). The engine's passes; the trees a type of this example's
394
+ */
395
+ const landMaker: MapGenerator = {
396
+ params: [],
397
+ tries: 1,
398
+ passes: [
399
+ terrainPass({ levels: 2, water: 0.08, roughness: 0.6, hills: 14 }),
400
+ tilesPass(),
401
+ scatterPass({ kind: SCATTER, density: 0.2, spacing: 1.6, avoid: 0, what: (ctx, at) => void ctx.addObject(TREE, MAP_NEUTRAL, at.x, at.z) })
402
+ ]
403
+ }
404
+
405
+ /** The new land's cells: the sea's part of the map, its edges eased down into it */
406
+ const LAND = { x: 24, z: 1, width: 23, depth: 46 }
407
+
408
+ /** New land made out of the sea with a seed: generated as a small map of its own, then laid into the world's ground */
409
+ export const raiseLand = defineCommand('dynamicMap.land', { seed: number }, (world, _player, { seed }) => {
410
+ // what stood on the old land goes with it
411
+ for (const each of world.query({ type: TREE })) if (each.pos.x > LAND.x) world.kill(each, undefined, false, 'removed')
412
+ const { map } = generateMap(landMaker, { seed, width: LAND.width, depth: LAND.depth, players: 1 }, world.registry)
413
+ const land = mapTerrain(map)
414
+ // its lowest dry ground laid at the field's height: the hills and plateaus over it, the ponds in it
415
+ const base = baseOf(world)
416
+ const landBase = lowestDry(land)
417
+ reshape(world, LAND.x, LAND.z, LAND.x + LAND.width, LAND.z + LAND.depth, (patch) => {
418
+ eachVertex(patch, (x, z, i) => {
419
+ const lx = x - LAND.x
420
+ const lz = z - LAND.z
421
+ // near its edge the land eases down into the sea round it
422
+ const edge = Math.min(lx, lz, LAND.width - lx, LAND.depth - lz)
423
+ const ease = Math.min(1, edge / 4)
424
+ const h = land.heights[lz * land.vertsX + lx]
425
+ patch.heights[i] = base - 2.4 + (h - landBase + 2.4) * ease
426
+ patch.waterHeights[i] = edge < 3 ? base - 0.4 : land.waterHeights[lz * land.vertsX + lx] - landBase + base
427
+ })
428
+ eachCell(patch, (x, z, i) => {
429
+ const lx = Math.floor(x - LAND.x)
430
+ const lz = Math.floor(z - LAND.z)
431
+ const c = lz * land.width + lx
432
+ const edge = Math.min(lx, lz, LAND.width - 1 - lx, LAND.depth - 1 - lz)
433
+ patch.levels[i] = edge < 4 ? 0 : land.levels[c]
434
+ patch.tiles[i] = land.tiles[c]
435
+ patch.water[i] = edge < 1 ? WATER_DEEP : edge < 2 ? WATER_SHALLOW : edge < 3 ? WATER_NONE : land.water[c]
436
+ })
437
+ })
438
+ // the generator's trees, where the land is dry and not too near its edge: growing up
439
+ for (const object of map.objects) {
440
+ if (object.type !== TREE) continue
441
+ const at = { x: LAND.x + object.x, z: LAND.z + object.z }
442
+ const edge = Math.min(object.x, object.z, LAND.width - object.x, LAND.depth - object.z)
443
+ if (edge < 4 || world.terrain.waterAt(at.x, at.z) !== WATER_NONE) continue
444
+ plant(world, at)
445
+ }
446
+ })
447
+
448
+ /** The lowest corner of a ground's dry cells of its lowest level: where the land meets the shore */
449
+ function lowestDry(land: ReturnType<typeof mapTerrain>): number {
450
+ let lowest = Infinity
451
+ for (let cz = 0; cz < land.depth; cz++)
452
+ for (let cx = 0; cx < land.width; cx++) {
453
+ const c = cz * land.width + cx
454
+ if (land.levels[c] !== 0 || land.water[c] !== WATER_NONE) continue
455
+ lowest = Math.min(
456
+ lowest,
457
+ land.vertexHeight(cx, cz),
458
+ land.vertexHeight(cx + 1, cz),
459
+ land.vertexHeight(cx, cz + 1),
460
+ land.vertexHeight(cx + 1, cz + 1)
461
+ )
462
+ }
463
+ return Number.isFinite(lowest) ? lowest : land.heights[0]
464
+ }
465
+
466
+ /** The sea back where the land was */
467
+ export const sink = defineCommand('dynamicMap.sink', {}, (world) => {
468
+ for (const each of world.query({ type: TREE })) if (each.pos.x > LAND.x) world.kill(each, undefined, false, 'removed')
469
+ sea(world)
470
+ })
471
+
472
+ /** A tree put down as a sapling that grows: nothing where something stands already */
473
+ function plant(world: World, at: Vec2): Entity | undefined {
474
+ try {
475
+ return world.spawn(TREE, { at, owner: MAP_NEUTRAL, underConstruction: true })
476
+ } catch {
477
+ return undefined
478
+ }
479
+ }
480
+
481
+ /** Where the wood is planted: a disc of trees on every other cell, scattered by where each is */
482
+ export const plantWood = defineCommand('dynamicMap.plant', {}, (world) => {
483
+ for (let z = 10; z < 40; z++)
484
+ for (let x = 10; x < 40; x++) {
485
+ if ((x + z) % 2 || exactMath.hypot(x + 0.5 - 25, z + 0.5 - 25) > 14) continue
486
+ if (Math.abs(exactMath.sin(x * 12.9898 + z * 78.233) * 43758.5453) % 1 > 0.55) continue
487
+ if (world.query({ near: { x: x + 0.5, z: z + 0.5 }, radius: 0.6, limit: 1 }).length) continue
488
+ plant(world, { x: x + 0.5, z: z + 0.5 })
489
+ }
490
+ })
491
+
492
+ /** Grass grown over the meadow, eased in on the screens over seconds (`setCells`' `over`) */
493
+ export const growGrass = defineCommand('dynamicMap.grass', {}, (world) => {
494
+ world.setCells(GRASS, { kind: 'circle', x: 25, z: 25, radius: 19 }, 1, { over: 5 })
495
+ })
496
+
497
+ /** The band of the road through the wood */
498
+ const inRoad = (x: number, z: number) => Math.abs(z - (25 + exactMath.sin(x / 5) * 2.5)) < 2
499
+
500
+ /** A road cleared through the wood: its trees felled, its grass gone, its ground painted road. The footmen walk it */
501
+ export const clearRoad = defineCommand('dynamicMap.clear', {}, (world) => {
502
+ for (const each of world.query({ type: TREE })) if (inRoad(each.pos.x, each.pos.z)) world.kill(each, undefined, false, 'felled')
503
+ const tile = world.registry.tile('road')
504
+ const cells: number[] = []
505
+ reshape(world, 0, 18, world.terrain.width, 32, (patch) =>
506
+ eachCell(patch, (x, z, i) => {
507
+ if (!inRoad(x, z)) return
508
+ patch.tiles[i] = tile
509
+ cells.push(Math.floor(z) * world.terrain.width + Math.floor(x))
510
+ })
511
+ )
512
+ if (world.terrain.cells[GRASS]) world.setCells(GRASS, cells, 0)
513
+ for (const each of world.query({ type: UNIT.footman, owner: PLAYER })) each.order('patrol', { x: 44, z: each.pos.z })
514
+ })
515
+
516
+ /** Takes everything away and lays the example out again */
517
+ export const relay = defineCommand('dynamicMap.relay', { example: text }, (world, _player, { example }) => {
518
+ const layout = LAYOUTS[example]
519
+ if (!layout) return
520
+ clear(world)
521
+ layout(world)
522
+ })
523
+
524
+ const example = (id: string, title: string, summary: string, tags: string[]): ExampleDef => ({
525
+ id,
526
+ category: 'dynamic-map',
527
+ title,
528
+ summary,
529
+ tags,
530
+ map: () => exampleField({ name: title }),
531
+ start: (world) => {
532
+ world.setState(groundBase, { base: world.terrain.heights[0] })
533
+ LAYOUTS[id]?.(world)
534
+ },
535
+ source: ['examples/dynamic-map/index.ts', 'examples/dynamic-map/looks.ts']
536
+ })
537
+
538
+ export const category: ExampleCategory = {
539
+ rules: (game: Game<any>) => {
540
+ game.unit(tree)
541
+ game.component(flood)
542
+ game.component(groundBase)
543
+ game.cellLayer(
544
+ defineCellLayer({ id: GRASS, name: 'Grass (dynamic map)', values: [{ value: 1, name: 'Meadow', foliage: presetFoliageStyles.meadow }] })
545
+ )
546
+ for (const command of [
547
+ sculpt,
548
+ paint,
549
+ spreadSnow,
550
+ road,
551
+ setFlood,
552
+ raiseWall,
553
+ cutRamp,
554
+ lower,
555
+ raiseLand,
556
+ sink,
557
+ plantWood,
558
+ growGrass,
559
+ clearRoad,
560
+ relay
561
+ ])
562
+ game.command(command)
563
+ for (const timer of [snowfall, tide]) game.timer(timer)
564
+ },
565
+ examples: [
566
+ example(
567
+ 'dynamic-map-sculpt',
568
+ 'Raise and dig',
569
+ 'Click the ground to raise it, dig it, flatten it, lift a plateau with cliffs or sink a pool. The footmen walking across go over the hills and round the cliffs and water, the ways cut again as it changes.',
570
+ ['terrain', 'sculpt', 'height', 'raise', 'dig', 'cliff', 'plateau', 'navigation', 'reshapeGround']
571
+ ),
572
+ example(
573
+ 'dynamic-map-paint',
574
+ 'Paint the ground',
575
+ 'Click to paint the ground with grass, dirt, road, sand, rock or snow; lay a winding road across; let snow spread out from the middle ring by ring.',
576
+ ['terrain', 'paint', 'tiles', 'texture', 'road', 'snow']
577
+ ),
578
+ example(
579
+ 'dynamic-map-water',
580
+ 'Flood and drain a valley',
581
+ 'Flood the valley and the water rises step by step: deep water cuts the way, so the footmen go round by the shallow ford in the east. Drain it and they walk straight across again.',
582
+ ['water', 'flood', 'drain', 'river', 'ford', 'navigation']
583
+ ),
584
+ example(
585
+ 'dynamic-map-cliffs',
586
+ 'A cliff, then a ramp',
587
+ 'Raise a cliff wall across the field and the footmen walking across go round its ends; cut a ramp through its middle and they take it, up and over.',
588
+ ['cliff', 'ramp', 'levels', 'high ground', 'navigation', 'reshapeGround']
589
+ ),
590
+ example(
591
+ 'dynamic-map-generate',
592
+ 'Land out of the sea',
593
+ "Raise new land out of the sea with the engine's map generator: hills, cliffs, ramps, ponds and a wood, made from a seed while the game runs. Another seed, other land.",
594
+ ['generate', 'procedural', 'generator', 'seed', 'terrain', 'trees']
595
+ ),
596
+ example(
597
+ 'dynamic-map-forest',
598
+ 'Plant, grow, clear',
599
+ 'Plant a wood and watch it grow, grow grass under it, then clear a road through it: the trees felled, the grass taken off, the ground paved, and the footmen walk the new way.',
600
+ ['trees', 'plant', 'grass', 'grow', 'clear', 'road', 'forest']
601
+ )
602
+ ]
603
+ }