@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,577 @@
1
+ /**
2
+ * The nature examples on the client's side: how their grass and trees look (the category's `view`), and each
3
+ * example's camera and controls (`CategoryLooks`). Controls that change the world for every player send a command
4
+ * (`./index.ts`); those that change only how this screen draws it call the client (`client.setEnvironment`,
5
+ * `client.foliage(layer).setLook`)
6
+ */
7
+ import {
8
+ foliage,
9
+ type FoliageLook,
10
+ type GameClient,
11
+ type GameClientApi,
12
+ type ModelDef,
13
+ presetFoliage,
14
+ presetFx,
15
+ type UnitLook
16
+ } from '@v12-engine/client'
17
+ import type { MapEnvironment } from '@v12-engine/engine'
18
+ import { button, type CategoryLooks, readout, select, slider, toggle } from '../../shell/look'
19
+ import { DEAD_TREE, GRASS, STYLE, TREE } from './grass'
20
+ import { blowGust, douse, FIRE_START, GLADE, growBack, lightFire, lightWood, setWeather, setWind, SQUARE, stroll } from './index'
21
+ import { MEADOW_WEST } from './maps'
22
+ import { stand } from './camera'
23
+ import { clock, LOOKS, lookAtHour } from './skies'
24
+
25
+ // ---- how the grass and the trees look
26
+
27
+ /**
28
+ * The grass, style by style: the client's ready-made looks by the rules' names (`presetFoliage`), a meadow in flower
29
+ * of its own, the thicket as the forest's flowering bushes. All of it lies down under feet and stands again in 14 s,
30
+ * leaving a trail behind whoever walks through
31
+ */
32
+ const grassLook: FoliageLook = {
33
+ walked: { flatten: 0.65, recover: 14, trail: true },
34
+ styles: {
35
+ [STYLE.meadow]: presetFoliage.meadow,
36
+ [STYLE.tallGrass]: presetFoliage.tallGrass,
37
+ [STYLE.dryGrass]: presetFoliage.dryGrass,
38
+ [STYLE.wheat]: presetFoliage.wheat,
39
+ [STYLE.thicket]: { draw: 'models', models: [{ src: 'forest/bush_common_flowers.glb' }], density: 1, height: 1.2 },
40
+ [STYLE.reeds]: presetFoliage.reeds,
41
+ [STYLE.snowGrass]: presetFoliage.snowGrass,
42
+ // a meadow thick with flowers: yellow, white, pink and blue among shorter blades
43
+ [STYLE.flowers]: {
44
+ ...presetFoliage.meadow,
45
+ height: 0.45,
46
+ colors: { foot: [0.2, 0.42, 0.12], tip: [0.55, 0.78, 0.3], vary: 0.15, hue: 0.03 },
47
+ scatter: [
48
+ {
49
+ density: 0.9,
50
+ height: 0.42,
51
+ colors: [
52
+ [0.98, 0.86, 0.25],
53
+ [0.96, 0.96, 0.98],
54
+ [0.92, 0.42, 0.65],
55
+ [0.55, 0.6, 0.98],
56
+ [0.95, 0.5, 0.3]
57
+ ]
58
+ }
59
+ ]
60
+ }
61
+ }
62
+ }
63
+
64
+ /** The trees a tree is drawn as, one picked for each: the shared art's oaks, pines, columnar and common trees */
65
+ const TREES: Record<string, ModelDef> = {
66
+ oak: { src: 'trees/oak_5.glb', scale: 0.62 },
67
+ pine: { src: 'trees/pine_5.glb', scale: 1.05 },
68
+ pineTall: { src: 'trees/pine_4.glb', scale: 1 },
69
+ columnar: { src: 'trees/columnar_1.glb', scale: 1 },
70
+ columnar2: { src: 'trees/columnar_2.glb', scale: 1 },
71
+ common: { src: 'forest/common_tree_1.glb', scale: 1.05 },
72
+ common2: { src: 'forest/common_tree_2.glb', scale: 1.05 },
73
+ common5: { src: 'forest/common_tree_5.glb', scale: 1 }
74
+ }
75
+
76
+ /** A tree as parts while its model loads: a trunk and two cones */
77
+ const treeShapes: UnitLook['shapes'] = [
78
+ { shape: 'cylinder', size: [0.3, 1, 0.3], offset: [0, 0.5, 0], color: [0.36, 0.25, 0.15] },
79
+ { shape: 'cone', size: [1.5, 1.5, 1.5], offset: [0, 1.5, 0], color: [0.14, 0.36, 0.17] },
80
+ { shape: 'cone', size: [1.1, 1.3, 1.1], offset: [0, 2.4, 0], color: [0.17, 0.42, 0.2] }
81
+ ]
82
+
83
+ const treeLook: UnitLook = {
84
+ height: 3.6,
85
+ looks: TREES,
86
+ vary: { scale: 0.25, lean: 0.06 },
87
+ shapes: treeShapes,
88
+ // nothing over a tree: no health bar for scenery
89
+ unitHud: { plates: [] }
90
+ }
91
+
92
+ const deadTreeLook: UnitLook = {
93
+ ...treeLook,
94
+ looks: Object.fromEntries([1, 2, 3, 4, 5].map((n) => [`dead${n}`, { src: `forest/dead_tree_${n}.glb`, scale: 1 }])),
95
+ shapes: [{ shape: 'cylinder', size: [0.3, 2.4, 0.3], offset: [0, 1.2, 0], color: [0.4, 0.36, 0.32] }]
96
+ }
97
+
98
+ // ---- small helpers of the controls
99
+
100
+ /** The client as a game's code has it, with the grass's handle (`client.foliage`), which `GameClientApi` leaves out */
101
+ const grassOf = (client: GameClientApi) => (client as GameClient).foliage(GRASS)
102
+
103
+ /** Changes the look of this screen's map by one of its groups, over what that group is now: the water, the cloud layer, the mist */
104
+ function change<K extends keyof MapEnvironment>(client: GameClientApi, key: K, value: Partial<NonNullable<MapEnvironment[K]>>, over = 0.6): void {
105
+ const now = (client as GameClient).mapEnvironment[key]
106
+ client.setEnvironment({ [key]: { ...(now as object), ...value } } as Partial<MapEnvironment>, { over })
107
+ }
108
+
109
+ /** How many trees of the nature examples stand */
110
+ const treesStanding = (client: GameClientApi) =>
111
+ client.session.snapshot().entities.filter((each) => (each.type === TREE || each.type === DEAD_TREE) && each.health > 0).length
112
+
113
+ // ---- the wind, as the forest's controls keep it
114
+
115
+ const wind = { around: 70, strength: 0.45 }
116
+ const sendWind = (client: GameClientApi) => client.session.send(setWind.make({ ...wind }))
117
+
118
+ // ---- the weather's choice
119
+
120
+ const weather = { kind: 'rain' as 'clear' | 'rain' | 'storm' | 'snow', amount: 0.6 }
121
+ const sendWeather = (client: GameClientApi) => client.session.send(setWeather.make({ ...weather }))
122
+
123
+ // ---- the time of day
124
+
125
+ const day = { hour: 9, running: false, timer: 0 as ReturnType<typeof setInterval> | 0 }
126
+ const showHour = (client: GameClientApi, over = 0.3) => client.setEnvironment(lookAtHour(day.hour), { over })
127
+ /** A button that goes to an hour, eased over two seconds */
128
+ const toHour = (label: string, hour: number, hint: string) =>
129
+ button(
130
+ label,
131
+ (client) => {
132
+ day.hour = hour
133
+ showHour(client, 2)
134
+ },
135
+ hint
136
+ )
137
+
138
+ /** The looks example's cross-fade, in seconds */
139
+ const looks = { over: 4 }
140
+
141
+ // ---- the water's colours
142
+
143
+ const WATERS: Record<string, MapEnvironment['water']> = {
144
+ 'Mountain lake': { shallow: [0.3, 0.62, 0.6], deep: [0.05, 0.2, 0.3], opacity: 0.78 },
145
+ 'Tropical lagoon': { shallow: [0.3, 0.85, 0.8], deep: [0.02, 0.35, 0.55], opacity: 0.6 },
146
+ 'Glacier melt': { shallow: [0.62, 0.85, 0.9], deep: [0.25, 0.5, 0.62], opacity: 0.85 },
147
+ 'Peat bog': { shallow: [0.35, 0.33, 0.18], deep: [0.1, 0.09, 0.04], opacity: 0.95 },
148
+ 'Night sea': { shallow: [0.1, 0.18, 0.3], deep: [0.01, 0.03, 0.08], opacity: 0.9 }
149
+ }
150
+
151
+ // ---- the mist's colours
152
+
153
+ const MISTS: Record<string, [number, number, number]> = {
154
+ 'Dawn rose': [0.9, 0.84, 0.84],
155
+ 'Pale white': [0.92, 0.94, 0.96],
156
+ 'Cold blue': [0.62, 0.7, 0.85],
157
+ 'Swamp green': [0.55, 0.66, 0.5],
158
+ 'Smoke grey': [0.45, 0.45, 0.48]
159
+ }
160
+
161
+ export const category: CategoryLooks = {
162
+ // the grass layer drawn, the trees' looks, and the effects the village's fire and torches play
163
+ view: (view) => ({
164
+ ...view,
165
+ units: { ...view.units, looks: { ...view.units.looks, [TREE]: treeLook, [DEAD_TREE]: deadTreeLook } },
166
+ ground: { ...view.ground, cellLayers: { ...view.ground?.cellLayers, [GRASS]: foliage(grassLook) } },
167
+ fx: { ...view.fx, defs: { ...presetFx(['campfire', 'torch']), ...view.fx?.defs } }
168
+ }),
169
+ looks: {
170
+ 'nature-forest': {
171
+ ready: (client) => {
172
+ Object.assign(wind, { around: 70, strength: 0.45 })
173
+ stand(client, { x: GLADE.x, z: GLADE.z - 2 }, 30, 190, 34)
174
+ },
175
+ controls: [
176
+ slider(
177
+ 'Wind',
178
+ { min: 0, max: 1, step: 0.05, value: 0.45 },
179
+ (client, value) => {
180
+ wind.strength = value
181
+ sendWind(client)
182
+ },
183
+ 'How hard the wind blows, for every player: the trees sway and the grass leans further, eased in over two seconds.'
184
+ ),
185
+ slider(
186
+ 'Blowing towards (°)',
187
+ { min: 0, max: 360, step: 5, value: 70 },
188
+ (client, value) => {
189
+ wind.around = value
190
+ sendWind(client)
191
+ },
192
+ 'Which way the wind blows, in degrees from north: the grass and the treetops turn to lean that way.'
193
+ ),
194
+ button(
195
+ 'Gust through the glade',
196
+ (client) => client.session.send(blowGust.make({ x: GLADE.x, z: GLADE.z })),
197
+ 'A gust bursts out from the middle of the glade: the flowers round it bend away in a ring, then stand again.'
198
+ ),
199
+ button(
200
+ 'Look from above',
201
+ (client) => stand(client, GLADE, 58, 190, 62),
202
+ 'Moves the camera high over the forest, to see the wind run over the treetops.'
203
+ ),
204
+ button(
205
+ 'Look into the glade',
206
+ (client) => stand(client, { x: GLADE.x, z: GLADE.z - 2 }, 30, 190, 34),
207
+ 'Moves the camera back down, close over the glade in flower.'
208
+ ),
209
+ readout(
210
+ 'Wind now',
211
+ (client) => `${Math.round(client.wind.strength * 100)}%`,
212
+ 'How hard the wind blows on this screen now, as it eases towards what was asked.'
213
+ )
214
+ ]
215
+ },
216
+ 'nature-meadow': {
217
+ ready: (client) => {
218
+ stand(client, { x: 28, z: 27 }, 42, 195, 44)
219
+ // they set off at once: the trails are the point
220
+ const off = setTimeout(() => client.session.send(stroll.make({})), 1500)
221
+ return () => clearTimeout(off)
222
+ },
223
+ controls: [
224
+ button(
225
+ 'Walk across',
226
+ (client) => client.session.send(stroll.make({})),
227
+ 'Sends the walkers across the meadow to the other side: the grass lies down under their feet and leaves a trail behind them.'
228
+ ),
229
+ slider(
230
+ 'Lies down underfoot',
231
+ { min: 0, max: 1, step: 0.05, value: 0.65 },
232
+ (client, value) => grassOf(client)?.setLook({ walked: { flatten: value } }),
233
+ 'How far the grass lies down where someone walks: at 0 it stays standing, at 1 it lies flat. Walk across to see it.'
234
+ ),
235
+ slider(
236
+ 'Stands again in (s)',
237
+ { min: 1, max: 40, step: 1, value: 14 },
238
+ (client, value) => grassOf(client)?.setLook({ walked: { recover: value } }),
239
+ 'How many seconds trodden grass takes to stand up again: short and the trail fades behind the walkers, long and it stays.'
240
+ ),
241
+ toggle(
242
+ 'Leave a trail',
243
+ true,
244
+ (client, on) => grassOf(client)?.setLook({ walked: { trail: on } }),
245
+ 'Whether the grass stays down along the path walked, for the time set above; off, it only bends round each foot as they pass.'
246
+ ),
247
+ button(
248
+ 'Lay a ring flat',
249
+ (client) => grassOf(client)?.flatten({ kind: 'circle', x: 28, z: 28, radius: 4 }, 1),
250
+ 'Lays the grass flat in a ring in the middle of the meadow, as a dragon landing would (on this screen only): it stands again over the time set above.'
251
+ ),
252
+ button(
253
+ 'Gust',
254
+ (client) => client.session.send(blowGust.make({ x: 30, z: 30 })),
255
+ 'A gust bursts out from the middle of the meadow: the grass bends away from it in a ring, then stands again.'
256
+ ),
257
+ readout(
258
+ 'Walkers',
259
+ (client) => {
260
+ const mine = client.session.snapshot().entities.filter((each) => each.owner === 1)
261
+ const x = mine.reduce((sum, each) => sum + each.x, 0) / Math.max(1, mine.length)
262
+ return x < MEADOW_WEST + 6 ? 'on the west side' : x > 40 ? 'on the east side' : 'crossing'
263
+ },
264
+ 'Where the walkers are: Walk across sends them to the other side.'
265
+ )
266
+ ]
267
+ },
268
+ 'nature-lake': {
269
+ ready: (client) => stand(client, { x: 32, z: 34 }, 36, 180, 32),
270
+ controls: [
271
+ select(
272
+ 'Water',
273
+ Object.keys(WATERS),
274
+ 'Mountain lake',
275
+ (client, name) => change(client, 'water', WATERS[name] ?? {}, 2),
276
+ 'The water’s colours, shallow and deep, and how clear it is: eased over two seconds.'
277
+ ),
278
+ slider(
279
+ 'Waves',
280
+ { min: 0, max: 1, step: 0.05, value: 0.7 },
281
+ (client, value) => change(client, 'water', { waves: { strength: value } }),
282
+ 'How high the waves on the lake are: at 0 it lies still as glass.'
283
+ ),
284
+ slider(
285
+ 'Wave length',
286
+ { min: 1, max: 8, step: 0.5, value: 3 },
287
+ (client, value) => change(client, 'water', { waves: { ...(client as GameClient).mapEnvironment.water?.waves, length: value } }),
288
+ 'How far apart the waves are, in cells: short choppy ripples or long slow swells.'
289
+ ),
290
+ slider(
291
+ 'Flow',
292
+ { min: 0, max: 1.5, step: 0.05, value: 0.25 },
293
+ (client, value) => change(client, 'water', { flow: value }),
294
+ 'How fast the water’s surface drifts along: up high the lake runs like a river.'
295
+ ),
296
+ slider(
297
+ 'Mist at the falls',
298
+ { min: 0, max: 1, step: 0.05, value: 0.8 },
299
+ (client, value) => change(client, 'water', { falls: { ...(client as GameClient).mapEnvironment.water?.falls, mist: value } }),
300
+ 'How much spray rises where the falls meet the lake: best seen with Look at the falls.'
301
+ ),
302
+ toggle(
303
+ 'Waterfalls',
304
+ true,
305
+ (client, on) => change(client, 'water', { falls: { ...(client as GameClient).mapEnvironment.water?.falls, on } }),
306
+ 'Whether water falls where the ground drops from the high pool to the lake, with its spray: off, the drop is bare. Best seen with Look at the falls.'
307
+ ),
308
+ button(
309
+ 'Look at the falls',
310
+ (client) => stand(client, { x: 32, z: 25 }, 16, 180, 24),
311
+ 'Moves the camera close to the waterfalls and their spray.'
312
+ ),
313
+ button('Look over the lake', (client) => stand(client, { x: 32, z: 34 }, 36, 180, 32), 'Moves the camera back out over the whole lake.'),
314
+ readout('Waterfalls', (client) => `${client.waterfalls.list.length}`, 'How many waterfalls the client found where the water drops.')
315
+ ]
316
+ },
317
+ 'nature-clouds': {
318
+ ready: (client) => stand(client, { x: 48, z: 52 }, 52, 20, 50),
319
+ controls: [
320
+ slider(
321
+ 'Cover',
322
+ { min: 0, max: 1, step: 0.05, value: 0.5 },
323
+ (client, value) => change(client, 'cloudLayer', { cover: value }),
324
+ 'How much of the sky the low clouds cover: from a few scattered puffs to a closed deck, their shadows with them.'
325
+ ),
326
+ slider(
327
+ 'Shadows',
328
+ { min: 0, max: 1, step: 0.05, value: 0.65 },
329
+ (client, value) => change(client, 'cloudLayer', { shadows: value }),
330
+ 'How dark the shadows the clouds cast on the ground are, drifting over the hills.'
331
+ ),
332
+ slider(
333
+ 'Height',
334
+ { min: 8, max: 40, step: 1, value: 20 },
335
+ (client, value) => change(client, 'cloudLayer', { height: value }),
336
+ 'How high over the ground the clouds float, in cells: low, they pass between the camera and the land.'
337
+ ),
338
+ slider(
339
+ 'Size',
340
+ { min: 8, max: 60, step: 1, value: 28 },
341
+ (client, value) => change(client, 'cloudLayer', { scale: value }),
342
+ 'How big each cloud is, in cells: many small ones or a few big banks.'
343
+ ),
344
+ slider(
345
+ 'Opacity',
346
+ { min: 0, max: 1, step: 0.05, value: 0.65 },
347
+ (client, value) => change(client, 'cloudLayer', { opacity: value }),
348
+ 'How solid the clouds look from above: low, the land shows through them.'
349
+ ),
350
+ slider(
351
+ 'Drift (wind)',
352
+ { min: 0, max: 1, step: 0.05, value: 0.5 },
353
+ (client, value) => client.setEnvironment({ wind: { around: 110, strength: value, gusts: 0.3 } }, { over: 1 }),
354
+ 'How fast the wind carries the clouds and their shadows over the land; at 0 they hang still.'
355
+ ),
356
+ toggle(
357
+ 'A volume (high effects)',
358
+ false,
359
+ (client, on) => change(client, 'cloudLayer', { volumetric: on, thickness: 6 }),
360
+ 'Draws the clouds as a ray-marched volume with depth instead of a flat layer: needs the effects quality set to high, otherwise nothing changes.'
361
+ ),
362
+ slider(
363
+ 'Sky clouds',
364
+ { min: 0, max: 1, step: 0.05, value: 0.45 },
365
+ (client, value) => change(client, 'clouds', { cover: value }),
366
+ 'How cloudy the far sky is, painted on the sky round the horizon (not the layer over the land): tilt up to see it.'
367
+ )
368
+ ]
369
+ },
370
+ 'nature-mist': {
371
+ ready: (client) => stand(client, { x: 40, z: 48 }, 44, 180, 30),
372
+ controls: [
373
+ slider(
374
+ 'Lies up to',
375
+ { min: 0.5, max: 9, step: 0.25, value: 3.2 },
376
+ (client, value) => change(client, 'mist', { ground: { ...(client as GameClient).mapEnvironment.mist?.ground!, top: value } }, 0.4),
377
+ 'How high up the ground mist reaches, in cells: low, it pools in the valley floor; high, it swallows the hills.'
378
+ ),
379
+ slider(
380
+ 'Thickness',
381
+ { min: 0, max: 1, step: 0.05, value: 0.55 },
382
+ (client, value) => change(client, 'mist', { ground: { ...(client as GameClient).mapEnvironment.mist?.ground!, density: value } }, 0.4),
383
+ 'How thick the ground mist is: at 0 it is gone, at 1 nothing under it shows.'
384
+ ),
385
+ select(
386
+ 'Colour',
387
+ Object.keys(MISTS),
388
+ 'Dawn rose',
389
+ (client, name) => {
390
+ const color = MISTS[name]
391
+ const mist = (client as GameClient).mapEnvironment.mist
392
+ change(client, 'mist', { ground: { ...mist?.ground!, color }, areas: mist?.areas?.map((area) => ({ ...area, color })) }, 2)
393
+ },
394
+ 'The colour of the mist and of the wisps by the stream, eased over two seconds.'
395
+ ),
396
+ toggle(
397
+ 'Wisps by the stream',
398
+ true,
399
+ (client, on) =>
400
+ change(
401
+ client,
402
+ 'mist',
403
+ { areas: (client as GameClient).mapEnvironment.mist?.areas?.map((area) => ({ ...area, density: on ? 0.6 : 0 })) },
404
+ 2
405
+ ),
406
+ 'Whether patches of thicker mist hang along the stream, over the ground mist: they fade in and out over two seconds.'
407
+ ),
408
+ button(
409
+ 'Look down the valley',
410
+ (client) => stand(client, { x: 40, z: 48 }, 44, 180, 30),
411
+ 'Moves the camera low, looking along the misty valley.'
412
+ ),
413
+ button(
414
+ 'Look from above',
415
+ (client) => stand(client, { x: 40, z: 40 }, 70, 180, 65),
416
+ 'Moves the camera high over the valley, to see where the mist lies.'
417
+ )
418
+ ]
419
+ },
420
+ 'nature-weather': {
421
+ ready: (client) => {
422
+ Object.assign(weather, { kind: 'rain', amount: 0.6 })
423
+ stand(client, { x: SQUARE.x, z: SQUARE.z }, 26, 200, 30)
424
+ },
425
+ controls: [
426
+ select(
427
+ 'Weather',
428
+ ['clear', 'rain', 'storm', 'snow'],
429
+ 'rain',
430
+ (client, kind) => {
431
+ weather.kind = kind as typeof weather.kind
432
+ sendWeather(client)
433
+ },
434
+ 'What falls on the village, for every player: rain splashes on the ground, a storm drives it slanting in a gale under low racing cloud, snow drifts down; it comes in over four seconds.'
435
+ ),
436
+ slider(
437
+ 'How much',
438
+ { min: 0, max: 1, step: 0.05, value: 0.6 },
439
+ (client, value) => {
440
+ weather.amount = value
441
+ sendWeather(client)
442
+ },
443
+ 'How much falls, from a few drops to a downpour: the sky and the light darken with it.'
444
+ ),
445
+ button('Look from above', (client) => stand(client, SQUARE, 50, 200, 60), 'Moves the camera high over the village.'),
446
+ button(
447
+ 'Look into the square',
448
+ (client) => stand(client, { x: SQUARE.x, z: SQUARE.z }, 26, 200, 30),
449
+ 'Moves the camera back down into the square, close to where it falls.'
450
+ ),
451
+ readout(
452
+ 'Falling',
453
+ (client) => {
454
+ const now = (client as GameClient).mapEnvironment.weather
455
+ return now && now.amount > 0.01 ? `${now.kind}, ${Math.round(now.amount * 100)}%` : 'nothing'
456
+ },
457
+ 'What falls on this screen now, as it eases towards what was asked.'
458
+ )
459
+ ]
460
+ },
461
+ 'nature-time': {
462
+ ready: (client) => {
463
+ Object.assign(day, { hour: 9, running: false })
464
+ showHour(client, 0)
465
+ stand(client, { x: SQUARE.x, z: SQUARE.z }, 28, 205, 32)
466
+ // lamps glow at night: a little bloom where it is bright
467
+ const before = client.picture.look
468
+ client.picture.look = { ...before, bloom: 0.5, bloomThreshold: 0.75, tone: 'aces', exposure: 1.1 }
469
+ return () => {
470
+ if (day.timer) clearInterval(day.timer)
471
+ day.timer = 0
472
+ client.picture.look = before
473
+ }
474
+ },
475
+ controls: [
476
+ slider(
477
+ 'Hour',
478
+ { min: 0, max: 24, step: 0.25, value: 9 },
479
+ (client, value) => {
480
+ day.hour = value
481
+ showHour(client)
482
+ },
483
+ 'The hour of the day: the sun moves across the sky, the light and the shadows change with it, and at night the lamps glow.'
484
+ ),
485
+ toggle(
486
+ 'Let the day run',
487
+ false,
488
+ (client, on) => {
489
+ if (day.timer) clearInterval(day.timer)
490
+ day.timer = 0
491
+ // a day in a minute: the look eased from one step to the next
492
+ if (on)
493
+ day.timer = setInterval(() => {
494
+ day.hour = (day.hour + 0.04) % 24
495
+ showHour(client, 0.1)
496
+ }, 100)
497
+ },
498
+ 'Runs the clock on its own, a whole day in a minute: watch the shadows turn and the sky change.'
499
+ ),
500
+ toHour('Dawn', 6.2, 'Goes to dawn over two seconds: a low warm sun, long shadows.'),
501
+ toHour('Noon', 12, 'Goes to noon over two seconds: the sun high, short shadows.'),
502
+ toHour('Dusk', 18.8, 'Goes to dusk over two seconds: an orange sky, the lamps coming on.'),
503
+ toHour('Night', 22.5, 'Goes to night over two seconds: dark and blue, lit by the village’s lamps.'),
504
+ readout('Time', () => clock(day.hour), 'The hour shown, on a 24-hour clock.')
505
+ ]
506
+ },
507
+ 'nature-fire': {
508
+ ready: (client) => stand(client, { x: 30, z: 34 }, 40, -15, 44),
509
+ controls: [
510
+ button(
511
+ 'Light the field',
512
+ (client) => client.session.send(lightFire.make(FIRE_START)),
513
+ 'Sets the dry grass alight at the west end of the field: the fire runs downwind, east, through the grass and into the trees.'
514
+ ),
515
+ button(
516
+ 'Set the wood alight',
517
+ (client) => client.session.send(lightWood.make({ x: 50, z: 26 })),
518
+ 'Sets three trees of the wood alight: a burning tree lights the trees beside it and the grass round it.'
519
+ ),
520
+ button(
521
+ 'Put it out',
522
+ (client) => client.session.send(douse.make({})),
523
+ 'Puts every fire out: the grass stays black where it burnt, the trees stand charred as far as they burnt.'
524
+ ),
525
+ button(
526
+ 'Rain on it',
527
+ (client) => client.session.send(setWeather.make({ kind: 'rain', amount: 0.9 })),
528
+ 'Rain falls hard on the field: wet grass catches less, so the fire slows.'
529
+ ),
530
+ button(
531
+ 'Clear the sky',
532
+ (client) => client.session.send(setWeather.make({ kind: 'clear', amount: 0.2 })),
533
+ 'Stops the rain: the sky clears over four seconds and the grass burns as before.'
534
+ ),
535
+ button(
536
+ 'Grow it all back',
537
+ (client) => client.session.send(growBack.make({})),
538
+ 'Puts out any fire and grows it all back: the burnt grass over eight seconds, new trees where the burnt ones stood.'
539
+ ),
540
+ slider(
541
+ 'Wind (east)',
542
+ { min: 0, max: 1, step: 0.05, value: 0.55 },
543
+ (client, value) => client.session.send(setWind.make({ around: 90, strength: value })),
544
+ 'How hard the wind blows east: the harder, the faster the fire runs that way and the further it jumps.'
545
+ ),
546
+ readout(
547
+ 'Trees standing',
548
+ (client) => String(treesStanding(client)),
549
+ 'How many trees still stand, burnt or not: a tree that burns through falls.'
550
+ )
551
+ ]
552
+ },
553
+ 'nature-looks': {
554
+ ready: (client) => {
555
+ client.setEnvironment(LOOKS['Summer noon'], { over: 0 })
556
+ stand(client, { x: 32, z: 34 }, 40, 170, 30)
557
+ },
558
+ controls: [
559
+ select(
560
+ 'Look',
561
+ Object.keys(LOOKS),
562
+ 'Summer noon',
563
+ (client, name) => client.setEnvironment(LOOKS[name], { over: looks.over }),
564
+ 'A whole look of the map at once, sky, light, fog, water and weather: it cross-fades from the one before over the time below.'
565
+ ),
566
+ slider(
567
+ 'Cross-fade (s)',
568
+ { min: 0, max: 10, step: 0.5, value: 4 },
569
+ (_client, value) => (looks.over = value),
570
+ 'How many seconds the next look takes to fade in: changes nothing by itself, pick a look after it.'
571
+ ),
572
+ button('Look at the falls', (client) => stand(client, { x: 32, z: 25 }, 16, 180, 24), 'Moves the camera close to the waterfalls.'),
573
+ button('Look over the lake', (client) => stand(client, { x: 32, z: 34 }, 40, 170, 30), 'Moves the camera back out over the lake.')
574
+ ]
575
+ }
576
+ }
577
+ }