@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,151 @@
1
+ /**
2
+ * Ribbons and orbits, on the client's side: effects whose parts go round where the effect is (`orbit`: how far out,
3
+ * turns a second, how high, a lean, a figure, a climb) or along a way of their own (`path`), each leaving a ribbon
4
+ * (`trail`) and sparkles. They are played on the mage and follow him
5
+ */
6
+ import type { FxDef, FxHandle, FxPart, GameClientApi, ParticleDef } from '@v12-engine/client'
7
+ import { button, type ExampleLook, select } from '../../shell/look'
8
+ import { UNIT } from '../common/units'
9
+ import { aim, idsOf } from '../fx/stage'
10
+ import { RIBBONS_AT } from './ribbons'
11
+
12
+ /** Sparkles left by a part as it goes: a few soft dots, drifting a little */
13
+ const sparkles = (color: string): ParticleDef => ({
14
+ rate: 40,
15
+ lifetime: [0.4, 0.9],
16
+ speed: [0, 0.3],
17
+ size: [0.18, 0],
18
+ color: [color, '#ffffff'],
19
+ texture: 'flare'
20
+ })
21
+
22
+ /** A braid: three strands in a figure of eight, a third of a turn apart, climbing from the feet over the head and again */
23
+ const braid: FxDef = {
24
+ length: 6,
25
+ parts: ['#6ff2ff', '#c48bff', '#ff8fd8'].flatMap((color, i): FxPart[] => {
26
+ const orbit = { radius: 0.7, figure: 2, depth: 0.45, phase: i / 3, turns: 1.1, climb: 1.6, height: [0.1, 2.3] as const }
27
+ return [
28
+ { orbit, trail: { color, width: 0.22, soft: true, blend: 'add', time: 1.2, gap: 0.9, linger: 0.8 } },
29
+ { orbit, particles: sparkles(color) }
30
+ ]
31
+ })
32
+ }
33
+
34
+ /** A spiral: two strands climbing round and round, closing in as they rise */
35
+ const spiral: FxDef = {
36
+ length: 4,
37
+ parts: ['#ffd76a', '#ff7a2a'].flatMap((color, i): FxPart[] => {
38
+ const orbit = {
39
+ radius: [
40
+ [0, 1.3],
41
+ [1, 0.2]
42
+ ] as const,
43
+ turns: 1.4,
44
+ phase: i / 2,
45
+ height: [
46
+ [0, 0.1],
47
+ [1, 3]
48
+ ] as const
49
+ }
50
+ return [
51
+ { orbit, trail: { color, width: 0.3, soft: true, blend: 'add', time: 0.8, strength: [1, 0] } },
52
+ { orbit, particles: sparkles(color) }
53
+ ]
54
+ })
55
+ }
56
+
57
+ /** A figure of eight lying flat round the feet, turning as a whole */
58
+ const eight: FxDef = {
59
+ length: 5,
60
+ parts: [
61
+ {
62
+ orbit: { radius: 1.6, figure: 2, depth: 0.6, turns: 0.7, spin: 0.3, height: 0.4 },
63
+ trail: { color: '#7dff9a', width: 0.35, soft: true, blend: 'add', time: 1.4 }
64
+ },
65
+ {
66
+ orbit: { radius: 1.6, figure: 2, depth: 0.6, turns: 0.7, spin: 0.3, height: 0.4, phase: 0.5 },
67
+ trail: { color: '#d8ffe0', width: 0.15, soft: true, blend: 'add', time: 1.4 }
68
+ }
69
+ ]
70
+ }
71
+
72
+ /** A halo: six motes round the head, leaning, bobbing, each with its ribbon */
73
+ const halo: FxDef = {
74
+ length: 5,
75
+ parts: Array.from({ length: 6 }, (_, i): FxPart => ({
76
+ orbit: { radius: 0.55, turns: 0.9, phase: i / 6, height: 2.2, wobble: 0.25, bob: 0.08, bobs: 3 },
77
+ trail: { color: '#fff2b0', width: 0.08, blend: 'add', soft: true, time: 0.5 }
78
+ }))
79
+ }
80
+
81
+ /** Comets on a way of their own (`path`): out from the chest, over in an arc, down at a point ahead, four of them in turn */
82
+ const comets: FxDef = {
83
+ length: 3.5,
84
+ parts: [0, 1, 2, 3].map((i): FxPart => ({
85
+ at: i * 0.35,
86
+ for: 1.6,
87
+ path: (age, life) => {
88
+ const t = Math.min(age / life, 1)
89
+ const side = (i % 2 ? 1 : -1) * (1 + i * 0.4)
90
+ // up and over in an arc to a spot five cells ahead, swinging out to one side
91
+ return [Math.sin(t * Math.PI) * side, 1.3 + Math.sin(t * Math.PI) * 2.5 - t * 1.2, t * 5]
92
+ },
93
+ trail: { color: ['#ff9a3a', '#ffd23c', '#ff5aa0', '#8fd8ff'][i], width: 0.28, blend: 'add', soft: true, time: 0.6, taper: true },
94
+ particles: { rate: 60, lifetime: [0.3, 0.6], size: [0.25, 0], speed: [0.2, 0.8], color: ['#ffffff', '#ffb050'], texture: 'spark' }
95
+ }))
96
+ }
97
+
98
+ /** The ribbons of this example, taken into the game's view */
99
+ export const ribbonsFx: Record<string, FxDef> = {
100
+ 'customFx.braid': braid,
101
+ 'customFx.spiral': spiral,
102
+ 'customFx.eight': eight,
103
+ 'customFx.halo': halo,
104
+ 'customFx.comets': comets
105
+ }
106
+
107
+ const NAMES: Record<string, string> = {
108
+ 'A braid': 'customFx.braid',
109
+ 'A spiral': 'customFx.spiral',
110
+ 'A figure of eight': 'customFx.eight',
111
+ 'A halo': 'customFx.halo',
112
+ 'Comets on a path': 'customFx.comets'
113
+ }
114
+
115
+ let chosen = 'customFx.braid'
116
+ let playing: FxHandle | undefined
117
+
118
+ /** Plays the chosen ribbons on the mage, the last let finish */
119
+ function play(client: GameClientApi): void {
120
+ const [mage] = idsOf(client, UNIT.mage)
121
+ if (mage === undefined) return
122
+ playing?.stop()
123
+ playing = client.fx.play(chosen, { on: mage })
124
+ }
125
+
126
+ export const ribbonsLook: ExampleLook = {
127
+ controls: [
128
+ select(
129
+ 'Ribbons',
130
+ Object.keys(NAMES),
131
+ 'A braid',
132
+ (client, name) => {
133
+ chosen = NAMES[name] ?? chosen
134
+ play(client)
135
+ },
136
+ 'Which ribbons play round the mage: each a few lines of data'
137
+ ),
138
+ button('Play', (client) => play(client), 'Plays the chosen ribbons on the mage again')
139
+ ],
140
+ ready: (client) => {
141
+ chosen = 'customFx.braid'
142
+ aim(client, { x: RIBBONS_AT.x, z: RIBBONS_AT.z + 1 }, 14, 0.5)
143
+ const first = window.setTimeout(() => play(client), 300)
144
+ const again = window.setInterval(() => playing?.alive || play(client), 1000)
145
+ return () => {
146
+ window.clearTimeout(first)
147
+ window.clearInterval(again)
148
+ playing?.stop({ mode: 'at-once' })
149
+ }
150
+ }
151
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Ribbons and orbits, on the rules' side: a mage to wind them round. The ribbons are the view's effects
3
+ * (`./ribbons.look.ts`)
4
+ */
5
+ import { PLAYER, UNIT } from '../common/units'
6
+ import type { ExampleDef } from '../define'
7
+
8
+ export const RIBBONS_AT = { x: 24, z: 24 }
9
+
10
+ export const ribbons: ExampleDef = {
11
+ id: 'custom-fx-ribbons',
12
+ category: 'custom-fx',
13
+ title: 'Ribbons and orbits',
14
+ summary:
15
+ 'Parts that go round where the effect is (`orbit`) leave a ribbon behind them (`trail`): a braid of three strands, a spiral climbing, a figure of eight, a halo, comets on their own path. Pick one; each is a few lines of data.',
16
+ tags: ['trail', 'ribbon', 'orbit', 'path', 'FxTrail', 'FxOrbit', 'spiral', 'braid'],
17
+ environment: 'dusk',
18
+ start: (world) => {
19
+ world.spawn(UNIT.mage, { at: RIBBONS_AT, owner: PLAYER })
20
+ },
21
+ source: ['examples/custom-fx/ribbons.ts', 'examples/custom-fx/ribbons.look.ts']
22
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * A shader material of your own, for either renderer: Babylon's `ShaderMaterial` with its vertex and fragment shaders
3
+ * written twice, in GLSL for WebGL and in WGSL for WebGPU, the one the engine draws with taken. Client code: only looks
4
+ * files import it
5
+ */
6
+ import { ShaderStore } from '@babylonjs/core/Engines/shaderStore.js'
7
+ import { ShaderLanguage } from '@babylonjs/core/Materials/shaderLanguage.js'
8
+ import { ShaderMaterial } from '@babylonjs/core/Materials/shaderMaterial.js'
9
+ import type { Scene } from '@babylonjs/core/scene.js'
10
+
11
+ /** A shader in both languages: its vertex and fragment code in each */
12
+ export interface TwoShaders {
13
+ glsl: { vertex: string; fragment: string }
14
+ wgsl: { vertex: string; fragment: string }
15
+ }
16
+
17
+ /**
18
+ * A material drawn by the shaders of `name`, kept in Babylon's store the first time: `uniforms` are what the code
19
+ * sets (`setFloat`, `setVector3`...) besides the matrices Babylon sets itself (`world`, `worldViewProjection`)
20
+ */
21
+ export function shaderMaterial(
22
+ name: string,
23
+ scene: Scene,
24
+ shaders: TwoShaders,
25
+ uniforms: readonly string[],
26
+ attributes = ['position', 'normal', 'uv']
27
+ ): ShaderMaterial {
28
+ const webgpu = scene.getEngine().isWebGPU
29
+ const language = webgpu ? ShaderLanguage.WGSL : ShaderLanguage.GLSL
30
+ const store = ShaderStore.GetShadersStore(language)
31
+ const code = webgpu ? shaders.wgsl : shaders.glsl
32
+ if (!store[`${name}VertexShader`]) {
33
+ store[`${name}VertexShader`] = code.vertex
34
+ store[`${name}FragmentShader`] = code.fragment
35
+ }
36
+ return new ShaderMaterial(
37
+ name,
38
+ scene,
39
+ { vertex: name, fragment: name },
40
+ { attributes, uniforms: ['world', 'worldViewProjection', ...uniforms], needAlphaBlending: true, shaderLanguage: language }
41
+ )
42
+ }
@@ -0,0 +1,226 @@
1
+ /**
2
+ * An energy shield, on the client's side: a renderer of the game's own (`GameView.renderers`, a `RendererFactory`),
3
+ * given the render kit: it puts a sphere on every warden, drawn by a shader of its own (`./shader.ts`), places it where
4
+ * the warden is drawn every frame (`kit.drawn`), and hears every blow the screen is told of (`sync`, its `events`): a
5
+ * blow on a warden starts a ripple on the bubble, from the side it came from
6
+ */
7
+ import { Constants } from '@babylonjs/core/Engines/constants.js'
8
+ import type { ShaderMaterial } from '@babylonjs/core/Materials/shaderMaterial.js'
9
+ import { Color3 } from '@babylonjs/core/Maths/math.color.js'
10
+ import { Vector3, Vector4 } from '@babylonjs/core/Maths/math.vector.js'
11
+ import { CreateSphere } from '@babylonjs/core/Meshes/Builders/sphereBuilder.js'
12
+ import type { Mesh } from '@babylonjs/core/Meshes/mesh.js'
13
+ import type { RendererFactory, UnitLook } from '@v12-engine/client'
14
+ import { type ExampleLook, select } from '../../shell/look'
15
+ import { character, hold, person } from '../common/looks'
16
+ import { aim } from '../fx/stage'
17
+ import { type TwoShaders, shaderMaterial } from './shader'
18
+ import { WARDEN, WARDEN_AT } from './shield'
19
+
20
+ /** The warden: the knight, shield and sword */
21
+ export const wardenLook: UnitLook = {
22
+ height: 2,
23
+ model: character('knight', 0.8, { hold: [hold('sword_1handed'), hold('shield_badge_color', 'l')] }),
24
+ shapes: person(2, [0.4, 0.6, 1])
25
+ }
26
+
27
+ /** How many ripples at once: the oldest gives way */
28
+ const RIPPLES = 4
29
+
30
+ /**
31
+ * The bubble's shaders. Bright where it is seen edge on (a fresnel), a grid of lines crawling up it, and up to four
32
+ * ripples: each a ring spreading from where a blow struck (a direction from the middle, `xyz`), as old as `w` (0 to 1,
33
+ * none below 0)
34
+ */
35
+ const SHIELD: TwoShaders = {
36
+ glsl: {
37
+ vertex: `precision highp float;
38
+ attribute vec3 position;
39
+ attribute vec3 normal;
40
+ uniform mat4 world;
41
+ uniform mat4 worldViewProjection;
42
+ varying vec3 vPositionW;
43
+ varying vec3 vNormalW;
44
+ varying vec3 vLocal;
45
+ void main() {
46
+ vec4 p = vec4(position, 1.0);
47
+ gl_Position = worldViewProjection * p;
48
+ vPositionW = (world * p).xyz;
49
+ vNormalW = normalize((world * vec4(normal, 0.0)).xyz);
50
+ vLocal = normalize(position);
51
+ }`,
52
+ fragment: `precision highp float;
53
+ uniform vec3 eye;
54
+ uniform vec3 color;
55
+ uniform float time;
56
+ uniform vec4 hit0;
57
+ uniform vec4 hit1;
58
+ uniform vec4 hit2;
59
+ uniform vec4 hit3;
60
+ varying vec3 vPositionW;
61
+ varying vec3 vNormalW;
62
+ varying vec3 vLocal;
63
+ float ripple(vec4 hit, vec3 at) {
64
+ if (hit.w < 0.0) return 0.0;
65
+ float away = acos(clamp(dot(at, hit.xyz), -1.0, 1.0));
66
+ float ring = away - hit.w * 2.6;
67
+ return exp(-ring * ring * 60.0) * (1.0 - hit.w) * 1.6 + exp(-away * away * 12.0) * (1.0 - hit.w) * 0.8 * step(hit.w, 0.25);
68
+ }
69
+ void main() {
70
+ vec3 toEye = normalize(eye - vPositionW);
71
+ float rim = pow(1.0 - abs(dot(normalize(vNormalW), toEye)), 2.4);
72
+ float around = atan(vLocal.z, vLocal.x);
73
+ float up = vLocal.y;
74
+ float grid = max(step(0.93, abs(sin(around * 9.0))), step(0.9, abs(sin(up * 12.0 - time * 1.5))));
75
+ float waves = ripple(hit0, vLocal) + ripple(hit1, vLocal) + ripple(hit2, vLocal) + ripple(hit3, vLocal);
76
+ float shine = 0.05 + rim * 0.85 + grid * (0.08 + rim * 0.25) + waves;
77
+ vec3 lit = color * shine + vec3(1.0) * waves * 0.5;
78
+ gl_FragColor = vec4(lit, clamp(shine, 0.0, 1.0));
79
+ }`
80
+ },
81
+ wgsl: {
82
+ vertex: `attribute position: vec3f;
83
+ attribute normal: vec3f;
84
+ uniform world: mat4x4f;
85
+ uniform worldViewProjection: mat4x4f;
86
+ varying vPositionW: vec3f;
87
+ varying vNormalW: vec3f;
88
+ varying vLocal: vec3f;
89
+ @vertex
90
+ fn main(input: VertexInputs) -> FragmentInputs {
91
+ let p = vec4f(vertexInputs.position, 1.0);
92
+ vertexOutputs.position = uniforms.worldViewProjection * p;
93
+ vertexOutputs.vPositionW = (uniforms.world * p).xyz;
94
+ vertexOutputs.vNormalW = normalize((uniforms.world * vec4f(vertexInputs.normal, 0.0)).xyz);
95
+ vertexOutputs.vLocal = normalize(vertexInputs.position);
96
+ }`,
97
+ fragment: `uniform eye: vec3f;
98
+ uniform color: vec3f;
99
+ uniform time: f32;
100
+ uniform hit0: vec4f;
101
+ uniform hit1: vec4f;
102
+ uniform hit2: vec4f;
103
+ uniform hit3: vec4f;
104
+ varying vPositionW: vec3f;
105
+ varying vNormalW: vec3f;
106
+ varying vLocal: vec3f;
107
+ fn ripple(hit: vec4f, at: vec3f) -> f32 {
108
+ if (hit.w < 0.0) { return 0.0; }
109
+ let away = acos(clamp(dot(at, hit.xyz), -1.0, 1.0));
110
+ let ring = away - hit.w * 2.6;
111
+ return exp(-ring * ring * 60.0) * (1.0 - hit.w) * 1.6 + exp(-away * away * 12.0) * (1.0 - hit.w) * 0.8 * step(hit.w, 0.25);
112
+ }
113
+ @fragment
114
+ fn main(input: FragmentInputs) -> FragmentOutputs {
115
+ let toEye = normalize(uniforms.eye - fragmentInputs.vPositionW);
116
+ let rim = pow(1.0 - abs(dot(normalize(fragmentInputs.vNormalW), toEye)), 2.4);
117
+ let local = fragmentInputs.vLocal;
118
+ let around = atan2(local.z, local.x);
119
+ let grid = max(step(0.93, abs(sin(around * 9.0))), step(0.9, abs(sin(local.y * 12.0 - uniforms.time * 1.5))));
120
+ let waves = ripple(uniforms.hit0, local) + ripple(uniforms.hit1, local) + ripple(uniforms.hit2, local) + ripple(uniforms.hit3, local);
121
+ let shine = 0.05 + rim * 0.85 + grid * (0.08 + rim * 0.25) + waves;
122
+ let lit = uniforms.color * shine + vec3f(1.0) * waves * 0.5;
123
+ fragmentOutputs.color = vec4f(lit, clamp(shine, 0.0, 1.0));
124
+ }`
125
+ }
126
+ }
127
+
128
+ /** The bubble's colour, as the card sets it */
129
+ const shieldColor = new Color3(0.35, 0.75, 1)
130
+
131
+ /** The renderer: a bubble on each warden, its ripples, told of every blow */
132
+ const shieldRenderer: RendererFactory = (kit) => {
133
+ const bubbles = new Map<number, { mesh: Mesh; material: ShaderMaterial; hits: Vector4[]; next: number }>()
134
+ let time = 0
135
+ const bubbleOf = (id: number) => {
136
+ let bubble = bubbles.get(id)
137
+ if (bubble) return bubble
138
+ const mesh = CreateSphere('customFx.shield', { diameter: 2.6, segments: 48 }, kit.scene)
139
+ const material = shaderMaterial(
140
+ 'customFxShield',
141
+ kit.scene,
142
+ SHIELD,
143
+ ['eye', 'color', 'time', 'hit0', 'hit1', 'hit2', 'hit3'],
144
+ ['position', 'normal']
145
+ )
146
+ // light added, not painted: what is behind shows through
147
+ material.alphaMode = Constants.ALPHA_ADD
148
+ material.backFaceCulling = false
149
+ mesh.material = material
150
+ mesh.isPickable = false
151
+ bubble = { mesh, material, hits: Array.from({ length: RIPPLES }, () => new Vector4(0, 1, 0, -1)), next: 0 }
152
+ bubbles.set(id, bubble)
153
+ return bubble
154
+ }
155
+ return {
156
+ sync(changes) {
157
+ for (const event of changes.events) {
158
+ if (event.type !== 'damage') continue
159
+ const bubble = bubbles.get(event.id)
160
+ const struck = kit.drawn(event.id)
161
+ const from = event.source !== undefined ? kit.drawn(event.source) : undefined
162
+ if (!bubble || !struck) continue
163
+ // the side the blow came from, from the bubble's middle; straight at the front where the shooter is not seen
164
+ const way = from ? from.position.subtract(struck.position).addInPlaceFromFloats(0, 0.6, 0) : new Vector3(0, 0.3, 1)
165
+ way.normalize()
166
+ bubble.hits[bubble.next]!.set(way.x, way.y, way.z, 0)
167
+ bubble.next = (bubble.next + 1) % RIPPLES
168
+ }
169
+ },
170
+ frame(_alpha, dt) {
171
+ time += dt
172
+ for (const entity of kit.state.entities.values()) if (entity.def.id === WARDEN && entity.health > 0) bubbleOf(entity.id)
173
+ const eye = kit.scene.activeCamera?.globalPosition ?? Vector3.Zero()
174
+ for (const [id, bubble] of bubbles) {
175
+ const drawn = kit.drawn(id)
176
+ if (!drawn || !kit.state.entities.has(id)) {
177
+ bubble.mesh.dispose()
178
+ bubble.material.dispose()
179
+ bubbles.delete(id)
180
+ continue
181
+ }
182
+ bubble.mesh.setEnabled(drawn.visible)
183
+ bubble.mesh.position.set(drawn.position.x, drawn.position.y + 1, drawn.position.z)
184
+ const { material, hits } = bubble
185
+ for (const hit of hits) if (hit.w >= 0) hit.w = hit.w + dt / 0.9 > 1 ? -1 : hit.w + dt / 0.9
186
+ material.setVector3('eye', eye)
187
+ material.setColor3('color', shieldColor)
188
+ material.setFloat('time', time)
189
+ hits.forEach((hit, i) => material.setVector4(`hit${i}`, hit))
190
+ }
191
+ },
192
+ dispose() {
193
+ for (const bubble of bubbles.values()) {
194
+ bubble.mesh.dispose()
195
+ bubble.material.dispose()
196
+ }
197
+ bubbles.clear()
198
+ }
199
+ }
200
+ }
201
+
202
+ const COLORS: Record<string, [number, number, number]> = {
203
+ Azure: [0.35, 0.75, 1],
204
+ Gold: [1, 0.75, 0.25],
205
+ Venom: [0.4, 1, 0.35],
206
+ Void: [0.75, 0.3, 1],
207
+ Blood: [1, 0.2, 0.25]
208
+ }
209
+
210
+ export const shieldLook: ExampleLook = {
211
+ // the renderer is this example's alone: laid over the game's renderers as it opens
212
+ view: (view) => ({ ...view, renderers: [...(view.renderers ?? []), shieldRenderer] }),
213
+ controls: [
214
+ select(
215
+ 'Colour',
216
+ Object.keys(COLORS),
217
+ 'Azure',
218
+ (_client, name) => shieldColor.set(...(COLORS[name] ?? COLORS.Azure!)),
219
+ 'The colour of the bubble round the warden: the shader is handed it every frame'
220
+ )
221
+ ],
222
+ ready: (client) => {
223
+ shieldColor.set(...COLORS.Azure!)
224
+ aim(client, WARDEN_AT, 19, 0.7)
225
+ }
226
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * An energy shield, on the rules' side: a warden who takes the blows and heals them as fast, archers round him who
3
+ * shoot at him of themselves. The shield is the screen's: a renderer of the game's own that draws it with a shader of
4
+ * its own and is told of every blow (`./shield.look.ts`)
5
+ */
6
+ import { exactMath } from '@v12-engine/engine'
7
+ import type { Game } from '@v12-engine/engine'
8
+ import { ENEMY, PLAYER, UNIT } from '../common/units'
9
+ import { type ExampleDef, exampleField } from '../define'
10
+
11
+ export const WARDEN = 'customFxWarden'
12
+ export const WARDEN_AT = { x: 24, z: 24 }
13
+
14
+ export const shield: ExampleDef = {
15
+ id: 'custom-fx-shield',
16
+ category: 'custom-fx',
17
+ title: 'An energy shield (a shader)',
18
+ summary:
19
+ "A bubble drawn by a shader of your own, GLSL and WGSL: bright at its rim, a grid crawling over it, and a ripple spreading from every arrow that strikes it. A renderer of the game's own places it on the warden and hears each blow. Change its colour; walk him about.",
20
+ tags: ['shader', 'ShaderMaterial', 'GLSL', 'WGSL', 'renderer', 'RendererFactory', 'shield', 'fresnel', 'ripple'],
21
+ environment: 'dusk',
22
+ map: () => exampleField({ name: 'Ring', players: 2 }),
23
+ start: (world) => {
24
+ world.spawn(WARDEN, { at: WARDEN_AT, owner: PLAYER })
25
+ for (let i = 0; i < 4; i++) {
26
+ const angle = (i / 4) * Math.PI * 2 + 0.4
27
+ world.spawn(UNIT.archer, { at: { x: WARDEN_AT.x + exactMath.cos(angle) * 6.5, z: WARDEN_AT.z + exactMath.sin(angle) * 6.5 }, owner: ENEMY })
28
+ }
29
+ },
30
+ source: ['examples/custom-fx/shield.ts', 'examples/custom-fx/shield.look.ts', 'examples/custom-fx/shader.ts']
31
+ }
32
+
33
+ /** Registers the warden: he cannot fall, nor strike back */
34
+ export function shieldRules(game: Game<any>): void {
35
+ game.unit({
36
+ id: WARDEN,
37
+ name: 'Warden',
38
+ kind: 'unit',
39
+ radius: 0.5,
40
+ stats: { health: 3000, healthRegen: 200, armor: 40, moveSpeed: 3.5 },
41
+ sight: 12
42
+ })
43
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * What an example is, on the rules' side: its place in the list, its words, its map and what it starts on it. The engine
3
+ * alone (`@v12-engine/engine`), so the page, the server, a worker and the tests read it with nothing of the client. How
4
+ * an example looks and the controls beside it are its category's `looks.ts` (`ExampleLook`, `src/shell/look.ts`),
5
+ * fetched with the game's look
6
+ */
7
+ import { createMap, exactMath, type Game, type GameMap, type GameStart, type MapEnvironment, presetTiles } from '@v12-engine/engine'
8
+
9
+ /** The categories of the list, in its order: an example names one by its id */
10
+ export const CATEGORIES = [
11
+ { id: 'mechanics', title: 'Core mechanics', summary: 'Selection, orders, paths, combat, sight and fog: what every game here is made of' },
12
+ { id: 'abilities', title: 'Abilities', summary: "The engine's own abilities and effects, each cast on something to see what it does" },
13
+ { id: 'moba', title: 'Like a MOBA', summary: 'Heroes, skillshots, lanes, a shop and items, as a game like League of Legends has them' },
14
+ { id: 'rts', title: 'Like an RTS', summary: 'Bases, workers, building and training, armies and heroes, as a game like Warcraft III has them' },
15
+ { id: 'fx', title: 'Effects', summary: 'Every effect the client draws: missiles, impacts, auras, beams, fields and more' },
16
+ { id: 'custom-fx', title: 'Your own effects', summary: 'Effects made in code: particles, meshes, shaders and lights of your own' },
17
+ { id: 'animation', title: 'Animation', summary: "Models' clips: walking, attacking, blending, timed to what happens" },
18
+ { id: 'custom-animation', title: 'Your own animation', summary: 'Moves made in code: poses, springs, tweens and bones driven by hand' },
19
+ { id: 'destruction', title: 'Destruction', summary: 'Buildings, trees, rocks and grass broken apart, split into pieces that fall' },
20
+ { id: 'construction', title: 'Construction', summary: 'Buildings rising as they are built, stage by stage' },
21
+ { id: 'nature', title: 'Nature and weather', summary: 'Trees, grass, water and waterfalls, clouds, mist, rain and snow, the time of day, fire' },
22
+ { id: 'post', title: 'Post-processing', summary: 'The picture after it is drawn: bloom, colour, depth of field, outlines and more' },
23
+ { id: 'dynamic-map', title: 'A changing map', summary: 'Ground raised, dug, painted and made while the game runs' },
24
+ { id: 'cinematics', title: 'Cinematics', summary: 'Cameras, timelines, letterboxing and scenes played in the game' },
25
+ { id: 'dialogs', title: 'Dialogue', summary: 'Characters talking, choices, portraits and messages' },
26
+ { id: 'gui', title: 'Interfaces', summary: "The HUD's parts, and interfaces of your own in Babylon GUI, Solid and plain HTML" },
27
+ { id: 'showcase', title: 'Showcase', summary: 'Larger scenes that put many parts together' },
28
+ { id: 'performance', title: 'Performance', summary: 'Thousands of units, crowds, and what the engine keeps fast' }
29
+ ] as const
30
+
31
+ /** A category's id */
32
+ export type CategoryId = (typeof CATEGORIES)[number]['id']
33
+
34
+ /**
35
+ * An example on the rules' side: what the list shows of it, and the world it plays. Its scene's id is its `id`, so the
36
+ * page's address opens it straight (`/?fx-fireball`)
37
+ */
38
+ export interface ExampleDef {
39
+ /** Its id, unique among them all, starting with its category's (`fx-fireball`): the scene's id and the address */
40
+ id: string
41
+ /** Which category it is listed under */
42
+ category: CategoryId
43
+ /** Its name in the list */
44
+ title: string
45
+ /** One or two sentences: what it shows, and what to do to see it */
46
+ summary: string
47
+ /** Words the list's search finds it by besides its name and summary */
48
+ tags?: readonly string[]
49
+ /** The map it plays: a flat field of 48 cells a side with one player when left out (`exampleField`) */
50
+ map?: () => GameMap | Promise<GameMap>
51
+ /** What it puts in the world once it is made, before anyone plays: units, buildings, scenery, a timer */
52
+ start?: GameStart
53
+ /** A look of the world over the map's own: sky, light, mist, weather. One of the view's by name, or given whole */
54
+ environment?: string | MapEnvironment
55
+ /** Whether the player sees the whole map, no fog of war: yes when left out */
56
+ reveal?: boolean
57
+ /** The files of the rules' side that make it, shown in the list's Source (paths in `src/`) */
58
+ source?: readonly string[]
59
+ }
60
+
61
+ /** What a category's `index.ts` gives: its examples, and what it adds to the game's rules (units, abilities, commands) */
62
+ export interface ExampleCategory {
63
+ examples: readonly ExampleDef[]
64
+ /** Registers what its examples need on the game: called once, as the rules are put together (`src/rules.ts`) */
65
+ rules?: (game: Game) => void
66
+ }
67
+
68
+ /** The ids of the tiles, the engine's of an RTS (grass, dark grass, dirt, road, sand, rock, snow) */
69
+ export const tiles = [...presetTiles]
70
+
71
+ /**
72
+ * A field to play an example on: flat, of `size` cells a side, `players` players with a start each (the first in the
73
+ * middle, the others round it), painted grass. What an example plays when it says no map
74
+ */
75
+ export function exampleField(options: { name?: string; size?: number; players?: number; tile?: string } = {}): GameMap {
76
+ const { name = 'Field', size = 48, players = 1, tile = 'grass' } = options
77
+ const map = createMap({ name, width: size, depth: size, players, game: 'examples', tileset: tiles.map((each) => each.id) })
78
+ const paint = map.tileset.indexOf(tile) + 1
79
+ if (paint > 0) map.tiles.fill(paint)
80
+ map.players.forEach((player, index) => {
81
+ const angle = (index / Math.max(1, players)) * Math.PI * 2
82
+ const away = index === 0 ? 0 : size / 3
83
+ player.start = { x: size / 2 + exactMath.cos(angle) * away, z: size / 2 + exactMath.sin(angle) * away }
84
+ })
85
+ return map
86
+ }