@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,100 @@
1
+ /**
2
+ * Outlines, rings and pings, on the client's side. The view says how the selected (`units.select`), what the pointer is
3
+ * over (`units.hover`) and what a spell is aimed at (`units.target`) are outlined, and the pings the team may send
4
+ * (`pings.kinds`). In code: `client.outline(id, look)` puts an outline of its own on a unit until its handle takes it
5
+ * off; `client.ping` and `kit.ring` draw rings on the ground; `client.pings.send` pings for the team, `client.minimapPing`
6
+ * on the minimap alone
7
+ */
8
+ import { type GameClientApi, type OutlineHandle, type OutlineLook, presetPings } from '@v12-engine/client'
9
+ import { button, type ExampleLook, select } from '../../shell/look'
10
+ import { BRUTE } from './feel'
11
+ import { IDLE_SKELETON, MARKS_AT } from './marks'
12
+ import { aim, idsOf, where } from './stage'
13
+
14
+ /** The outlines the card puts on the nearer skeleton, by name */
15
+ const OUTLINES: Record<string, OutlineLook | undefined> = {
16
+ None: undefined,
17
+ 'A red line': { color: [1, 0.25, 0.2], width: 3 },
18
+ 'A soft glow': { color: [0.4, 0.85, 1], width: 4, style: 'glow' },
19
+ 'A pulsing glow': { color: [1, 0.8, 0.3], width: 5, style: 'glow', pulse: 1.2 },
20
+ 'Seen through walls': { color: [0.7, 0.4, 1], width: 3, throughWalls: true, pulse: true }
21
+ }
22
+
23
+ let outline: OutlineHandle | undefined
24
+
25
+ /** Puts the chosen outline on the nearer skeleton, the last one off */
26
+ function mark(client: GameClientApi, name: string): void {
27
+ outline?.remove()
28
+ outline = undefined
29
+ const look = OUTLINES[name]
30
+ const [skeleton] = idsOf(client, IDLE_SKELETON)
31
+ if (look && skeleton !== undefined) outline = client.outline(skeleton, look)
32
+ }
33
+
34
+ /** A spot near the middle, a little to one side at random */
35
+ const near = () => ({ x: MARKS_AT.x + (Math.random() - 0.5) * 8, z: MARKS_AT.z + (Math.random() - 0.5) * 6 })
36
+
37
+ export const marksLook: ExampleLook = {
38
+ view: (view) => ({
39
+ ...view,
40
+ units: {
41
+ ...view.units,
42
+ // the selected in green, what the pointer is over in gold, what a spell is aimed at in red
43
+ select: { color: [0.45, 1, 0.55], width: 2.5 },
44
+ hover: { color: [1, 0.85, 0.35], width: 2 },
45
+ target: { color: [1, 0.3, 0.25], width: 3, pulse: true }
46
+ },
47
+ pings: { ...view.pings, kinds: presetPings() }
48
+ }),
49
+ controls: [
50
+ select(
51
+ 'Outline the skeleton',
52
+ Object.keys(OUTLINES),
53
+ 'A pulsing glow',
54
+ (client, name) => mark(client, name),
55
+ 'An outline drawn round the skeleton on the left, in the look chosen (`client.outline`)'
56
+ ),
57
+ button(
58
+ 'Ring on the ground',
59
+ (client) => {
60
+ const at = near()
61
+ client.ping(at.x, at.z, [0.4, 0.9, 1])
62
+ },
63
+ 'The ring a move order leaves on the ground, at a spot near the middle (`client.ping`)'
64
+ ),
65
+ button(
66
+ 'Ring going out',
67
+ (client) => {
68
+ const [brute] = idsOf(client, BRUTE)
69
+ const at = (brute !== undefined && where(client, brute)) || MARKS_AT
70
+ client.renderer.kit.ring(at.x, at.z, [1, 0.75, 0.3], 0.5, 5)
71
+ },
72
+ 'A ring round the brute that grows and fades (`kit.ring`)'
73
+ ),
74
+ button(
75
+ 'Danger ping',
76
+ (client) => void client.pings.send('danger', near()),
77
+ 'A danger ping for the team near the middle: a mark, a sound and a line in the chat'
78
+ ),
79
+ button(
80
+ 'On my way',
81
+ (client) => void client.pings.send('onMyWay', near()),
82
+ 'Another kind of ping for the team, near the middle: its own mark and words'
83
+ ),
84
+ button(
85
+ 'Minimap ping',
86
+ (client) => client.minimapPing(MARKS_AT.x, MARKS_AT.z, '#ff4a3a', 4),
87
+ 'A red ping on the minimap alone, in the corner of the screen'
88
+ )
89
+ ],
90
+ ready: (client) => {
91
+ aim(client, { x: MARKS_AT.x, z: MARKS_AT.z + 1 }, 19, 0.8)
92
+ const [brute] = idsOf(client, BRUTE)
93
+ if (brute !== undefined) client.controller.select([brute])
94
+ mark(client, 'A pulsing glow')
95
+ return () => {
96
+ outline?.remove()
97
+ outline = undefined
98
+ }
99
+ }
100
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Outlines, rings and pings, on the rules' side: the brute of the player's, a bowman beside him, and two skeletons
3
+ * across from them to mark. Everything marked is the screen's alone (`./marks.look.ts`)
4
+ */
5
+ import type { Game } from '@v12-engine/engine'
6
+ import { ENEMY, PLAYER } from '../common/units'
7
+ import { type ExampleDef, exampleField } from '../define'
8
+ import { BRUTE } from './feel'
9
+
10
+ /** A skeleton that stands and takes what it is given: something of the enemy's to mark */
11
+ export const IDLE_SKELETON = 'fxIdleSkeleton'
12
+
13
+ export const MARKS_AT = { x: 24, z: 24 }
14
+
15
+ export const marks: ExampleDef = {
16
+ id: 'fx-marks',
17
+ category: 'fx',
18
+ title: 'Outlines, rings and pings',
19
+ summary:
20
+ 'How the screen marks things: the outline of what is selected and what the pointer is over, an outline of your own on a unit (a line, a glow, a pulse), rings on the ground, pings for the team and on the minimap. Hover and click the units; press the buttons.',
21
+ tags: ['outline', 'selection', 'hover', 'glow', 'ring', 'ping', 'minimap', 'indicators', 'markers'],
22
+ map: () => exampleField({ name: 'Field', players: 2 }),
23
+ start: (world) => {
24
+ // the brute and the bowman strike only when told: nothing here fights
25
+ world.spawn(BRUTE, { at: { x: MARKS_AT.x - 2, z: MARKS_AT.z - 1.5 }, owner: PLAYER })
26
+ world.spawn('fxBowman', { at: { x: MARKS_AT.x - 3.5, z: MARKS_AT.z - 2.5 }, owner: PLAYER })
27
+ world.spawn(IDLE_SKELETON, { at: { x: MARKS_AT.x + 2.5, z: MARKS_AT.z + 2 }, owner: ENEMY })
28
+ world.spawn(IDLE_SKELETON, { at: { x: MARKS_AT.x + 4, z: MARKS_AT.z + 0.5 }, owner: ENEMY })
29
+ },
30
+ source: ['examples/fx/marks.ts', 'examples/fx/marks.look.ts']
31
+ }
32
+
33
+ /** Registers the skeleton that stands */
34
+ export function marksRules(game: Game<any>): void {
35
+ game.unit({ id: IDLE_SKELETON, name: 'Skeleton', kind: 'unit', radius: 0.45, stats: { health: 380, moveSpeed: 3 }, sight: 8 })
36
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Missiles and impacts, on the client's side: each shooter's look with its `shot` (`ShotDef`). A shot is a ball in a
3
+ * colour, a model (`model`), or an effect carried as it flies (`fx`), with sparks behind it (`trail`), an arc, and an
4
+ * effect where it lands (`impact`). Every effect named is a ready-made one (`presetFx`), tuned by its knobs
5
+ */
6
+ import type { UnitLook } from '@v12-engine/client'
7
+ import { button, type ExampleLook, select } from '../../shell/look'
8
+ import { character, hold, person } from '../common/looks'
9
+ import { SHOOTERS, shoot, SHOOTER_Z, DUMMY_Z } from './shots'
10
+ import { aim } from './stage'
11
+
12
+ /** Each shooter's look: who it is, and what it shoots */
13
+ export const shooterLooks: Record<(typeof SHOOTERS)[number], UnitLook> = {
14
+ // an arrow, the gear's own model, on a low arc, with a few sparks where it strikes
15
+ fxBowman: {
16
+ height: 1.7,
17
+ model: character('ranger', 0.68, { hold: [hold('bow')], animations: { attack: 'Ranged_Bow_Release' } }),
18
+ shot: { model: 'gear/arrow_bow.glb', size: 0.9, arc: 0.18, impact: { fx: 'spark-burst', params: { count: 10, size: 0.5 } } },
19
+ shapes: person(1.7)
20
+ },
21
+ // the ready-made fireball carried as it flies, in the place of a ball; a small explosion where it lands
22
+ fxFireMage: {
23
+ height: 1.8,
24
+ model: character('mage', 0.72, { hold: [hold('staff')], animations: { attack: 'Ranged_Magic_Shoot' } }),
25
+ shot: { fx: { fx: 'fireball', params: { size: 0.55 } }, impact: { fx: 'explosion', params: { size: 0.6, shake: 0.12, light: 3 } } },
26
+ shapes: person(1.8, [0.8, 0.25, 0.1])
27
+ },
28
+ // a pale ball with a trail of sparks, the burst of cold where it lands
29
+ fxFrostMage: {
30
+ height: 1.8,
31
+ model: character('mage', 0.72, { hold: [hold('wand')], animations: { attack: 'Spell_Simple_Shoot' } }),
32
+ shot: {
33
+ color: [0.6, 0.9, 1],
34
+ size: 0.35,
35
+ trail: { color: [0.7, 0.95, 1], tint: [1, 1, 1], count: 60 },
36
+ impact: { fx: 'frost', params: { size: 0.7 } }
37
+ },
38
+ shapes: person(1.8, [0.4, 0.7, 1])
39
+ },
40
+ // a quick spark, and lightning called down on what it strikes
41
+ fxStormMage: {
42
+ height: 1.8,
43
+ model: character('mage', 0.72, { hold: [hold('staff')], animations: { attack: 'Ranged_Magic_Shoot' } }),
44
+ shot: {
45
+ color: [0.75, 0.85, 1],
46
+ size: 0.22,
47
+ trail: { color: [0.6, 0.75, 1], tint: [1, 1, 1], count: 90 },
48
+ impact: { fx: 'lightning', params: { size: 0.8, shake: 0.12 } }
49
+ },
50
+ shapes: person(1.8, [0.5, 0.5, 0.9])
51
+ },
52
+ // a bomb lobbed high, trailing smoke, a ring blown over the ground where it lands
53
+ fxBombardier: {
54
+ height: 1.8,
55
+ model: character('barbarian', 0.72, { animations: { attack: 'Ranged_1H_Shoot' } }),
56
+ shot: {
57
+ model: 'gear/smokebomb.glb',
58
+ size: 0.6,
59
+ arc: 0.55,
60
+ fx: { fx: 'smoke-trail', params: { size: 0.5, rate: 40 } },
61
+ impact: { fx: 'impact-ring', params: { size: 0.7, shake: 0.15 } }
62
+ },
63
+ shapes: person(1.8, [0.5, 0.4, 0.3])
64
+ }
65
+ }
66
+
67
+ /** What the card's choice of shooter names, and the id `fx.shoot` is told */
68
+ const WHO: Record<string, string> = {
69
+ 'All of them': 'all',
70
+ 'Bowman: an arrow': 'fxBowman',
71
+ 'Fire mage: a fireball': 'fxFireMage',
72
+ 'Frost mage: a frost bolt': 'fxFrostMage',
73
+ 'Storm mage: lightning': 'fxStormMage',
74
+ 'Bombardier: a bomb': 'fxBombardier'
75
+ }
76
+
77
+ export const shotsLook: ExampleLook = {
78
+ controls: [
79
+ select(
80
+ 'Who shoots',
81
+ Object.keys(WHO),
82
+ 'All of them',
83
+ (client, choice) => client.session.send(shoot.make({ who: WHO[choice] ?? 'all' })),
84
+ 'Which shooter fires at its dummy, over and over: each missile has its own trail and impact'
85
+ ),
86
+ button('Hold fire', (client) => client.session.send(shoot.make({ who: 'none' })), 'Every shooter stops; choose one above to start again')
87
+ ],
88
+ ready: (client) => {
89
+ aim(client, { x: 24, z: (SHOOTER_Z + DUMMY_Z) / 2 }, 31, 0.68)
90
+ client.session.send(shoot.make({ who: 'all' }))
91
+ }
92
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Missiles and impacts, on the rules' side: five shooters, each across from a dummy of its own. To the rules they are
3
+ * five ranged units alike; what each shot looks like, as it flies and where it lands, is all the client's
4
+ * (`./shots.look.ts`). They shoot only when told (`fx.shoot`): they look for nothing to shoot of themselves
5
+ */
6
+ import { defineCommand, type Game, text, type UnitTypeDef } from '@v12-engine/engine'
7
+ import { ENEMY, PLAYER, UNIT } from '../common/units'
8
+ import { type ExampleDef, exampleField } from '../define'
9
+
10
+ /** The shooters' ids, in their lanes from left to right */
11
+ export const SHOOTERS = ['fxBowman', 'fxFireMage', 'fxFrostMage', 'fxStormMage', 'fxBombardier'] as const
12
+
13
+ /** A ranged unit that shoots `speed` cells a second at what it is told to, from `range` away */
14
+ const shooter = (id: string, name: string, speed: number, cooldown = 1.2): UnitTypeDef => ({
15
+ id,
16
+ name,
17
+ kind: 'unit',
18
+ radius: 0.42,
19
+ stats: { health: 400, attackDamage: 20, attackSpeed: 1 / cooldown, attackRange: 12, moveSpeed: 0 },
20
+ sight: 14,
21
+ // it looks for nothing to shoot: what it shoots is what `fx.shoot` says
22
+ attacks: [{ damagePoint: 0.45, acquisitionRange: 0, projectileSpeed: speed }]
23
+ })
24
+
25
+ const units: UnitTypeDef[] = [
26
+ shooter(SHOOTERS[0], 'Bowman', 24, 0.9),
27
+ shooter(SHOOTERS[1], 'Fire mage', 13),
28
+ shooter(SHOOTERS[2], 'Frost mage', 16),
29
+ shooter(SHOOTERS[3], 'Storm mage', 30, 1.4),
30
+ shooter(SHOOTERS[4], 'Bombardier', 11, 1.8)
31
+ ]
32
+
33
+ /** Where each lane is across, and where its shooter and its dummy stand */
34
+ export const laneX = (lane: number) => 16 + lane * 4
35
+ export const SHOOTER_Z = 20
36
+ export const DUMMY_Z = 29
37
+
38
+ /** Sets one shooter or all of them (`all`) on their dummies, or (`none`) stops them all */
39
+ export const shoot = defineCommand('fx.shoot', { who: text }, (world, _player, { who }) => {
40
+ const dummies = [...world.units.values()].filter((unit) => unit.def.id === UNIT.dummy && unit.alive)
41
+ for (const unit of world.units.values()) {
42
+ if (!unit.alive || !(SHOOTERS as readonly string[]).includes(unit.def.id)) continue
43
+ const dummy = dummies.sort((a, b) => Math.abs(a.pos.x - unit.pos.x) - Math.abs(b.pos.x - unit.pos.x))[0]
44
+ if (dummy && (who === 'all' || who === unit.def.id)) unit.order('attack', dummy)
45
+ else unit.order('stop')
46
+ }
47
+ })
48
+
49
+ export const shots: ExampleDef = {
50
+ id: 'fx-shots',
51
+ category: 'fx',
52
+ title: 'Missiles and impacts',
53
+ summary:
54
+ "Five shooters, five looks of a shot: an arrow on an arc, a fireball that explodes, a frost bolt, a storm bolt that calls lightning down, a bomb trailing smoke. Pick who shoots; each shot is the unit look's `shot`, with its effect as it flies and its impact.",
55
+ tags: ['projectile', 'missile', 'shot', 'impact', 'fireball', 'arrow', 'trail', 'ShotDef'],
56
+ // two players: the shooters' and the dummies' they shoot
57
+ map: () => exampleField({ name: 'Range', players: 2 }),
58
+ start: (world) => {
59
+ SHOOTERS.forEach((type, lane) => {
60
+ world.spawn(type, { at: { x: laneX(lane), z: SHOOTER_Z }, owner: PLAYER, facing: 0 })
61
+ world.spawn(UNIT.dummy, { at: { x: laneX(lane), z: DUMMY_Z }, owner: ENEMY })
62
+ })
63
+ },
64
+ source: ['examples/fx/shots.ts', 'examples/fx/shots.look.ts']
65
+ }
66
+
67
+ /** Registers the shooters and their command */
68
+ export function shotsRules(game: Game<any>): void {
69
+ for (const unit of units) game.unit(unit)
70
+ game.command(shoot)
71
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Spell styles, on the client's side: a look for each spell (`AbilityLook`), keyed by the ability's id, naming the style
3
+ * that draws it and its colour, and the style's own options. Buffs have looks too: `aura` is the style drawn on their
4
+ * carrier for as long as it carries them, `counter` a pip a stack, `until` the buff a tether lasts as long as. The
5
+ * styles are the client's (`presetSpellStyles`, which the game's `presetView` takes in)
6
+ */
7
+ import type { AbilityLooks, UnitLook } from '@v12-engine/client'
8
+ import { button, type ExampleLook, select } from '../../shell/look'
9
+ import { character, hold, person } from '../common/looks'
10
+ import { aura, CASTER_AT, cast, GLOWS, glowBuff, MARK_AT, SPELLS } from './spells'
11
+ import { aim } from './stage'
12
+
13
+ const GOLD: [number, number, number] = [1, 0.82, 0.35]
14
+ const ARCANE: [number, number, number] = [0.7, 0.45, 1]
15
+ const FROST: [number, number, number] = [0.55, 0.85, 1]
16
+
17
+ /** Each spell's look, by its id: the style that draws it, its colour, and what the style is told besides */
18
+ export const spellLooks: AbilityLooks = {
19
+ fxRing: { color: FROST, style: 'ring' },
20
+ fxBurst: { color: [0.45, 1, 0.55], style: 'burst', options: { count: 70, rise: 2.5, ring: 2 } },
21
+ fxSlam: { color: [0.85, 0.7, 0.45], style: 'slam' },
22
+ fxSmash: { color: [1, 0.55, 0.2], style: 'smash' },
23
+ fxSonar: { color: [0.5, 1, 0.8], style: 'sonar' },
24
+ // a star falls from 14 cells up onto the ring, sparks rising from it as it waits
25
+ fxStar: { color: GOLD, style: 'mark', options: { falls: 14, sparks: 30, fill: true, lasts: 1.2 } },
26
+ // a field for the zone's life; its ground drawn too (`ground`)
27
+ fxField: { color: ARCANE, style: 'mark', options: { lasts: 4, sparks: 40, fill: true } },
28
+ // rockets from the sky, one for each `after`, the last a great blast
29
+ fxBarrage: { color: [1, 0.45, 0.15], style: 'barrage', options: { hits: [1, 1.5, 2, 2.6], power: [1, 1, 1, 2], scatter: 0.6 } },
30
+ fxArrow: { color: [0.9, 0.85, 0.7], style: 'missile', options: { shape: 'arrow', trail: 30 } },
31
+ fxHawk: { color: [0.6, 0.85, 1], style: 'missile', options: { shape: 'hawk', trail: 60 } },
32
+ fxRock: { color: [0.6, 0.5, 0.4], style: 'missile', options: { shape: 'rock', arc: 2 } },
33
+ fxOrb: { color: ARCANE, style: 'missile', options: { shape: 'ball', size: 1.5, trail: 80 } },
34
+ fxTornado: { color: [0.8, 0.88, 0.95], style: 'tornado' },
35
+ // a rope for as long as the target is bound
36
+ fxTether: { color: [0.95, 0.3, 0.35], style: 'tether', until: 'fxBound' },
37
+ // the buffs: a pip a stack over the bleeding one, a glow of each shape on the caster
38
+ fxBleeding: { color: [0.9, 0.1, 0.12], counter: { max: 5 }, aura: 'glow', options: { shape: 'fall' } },
39
+ fxBound: { color: [0.95, 0.3, 0.35] },
40
+ ...Object.fromEntries(
41
+ GLOWS.map((shape) => [
42
+ glowBuff(shape),
43
+ { color: shape === 'dust' ? ([0.7, 0.6, 0.45] as const) : GOLD, aura: 'glow', options: { shape, streaks: shape === 'rise' } }
44
+ ])
45
+ )
46
+ }
47
+
48
+ /** The caster's look: the mage, a staff in hand */
49
+ export const casterLook: UnitLook = {
50
+ height: 1.8,
51
+ model: character('mage', 0.75, { hold: [hold('staff')] }),
52
+ shapes: person(1.8, ARCANE)
53
+ }
54
+
55
+ /** The card's names of the spells, and the spell each casts */
56
+ const NAMES = Object.fromEntries(SPELLS.map((each) => [each.name, each.id]))
57
+ /** The spell the card has chosen */
58
+ let chosen = 'fxStar'
59
+
60
+ export const spellsLook: ExampleLook = {
61
+ controls: [
62
+ select(
63
+ 'Spell',
64
+ Object.keys(NAMES),
65
+ 'Falling star',
66
+ (client, name) => {
67
+ chosen = NAMES[name] ?? chosen
68
+ client.session.send(cast.make({ ability: chosen }))
69
+ },
70
+ 'A spell, cast at once at the middle dummy: each its own look'
71
+ ),
72
+ button('Cast', (client) => client.session.send(cast.make({ ability: chosen })), 'Casts the spell at the middle dummy again'),
73
+ select(
74
+ 'Wear an aura',
75
+ ['none', ...GLOWS],
76
+ 'none',
77
+ (client, shape) => client.session.send(aura.make({ shape })),
78
+ "A buff on the caster, drawn round it in one of the `glow` style's shapes"
79
+ )
80
+ ],
81
+ ready: (client) => {
82
+ aim(client, { x: (CASTER_AT.x + MARK_AT.x) / 2, z: (CASTER_AT.z + MARK_AT.z) / 2 + 1 }, 19, 0.8)
83
+ client.session.send(aura.make({ shape: 'rise' }))
84
+ chosen = 'fxStar'
85
+ client.session.send(cast.make({ ability: chosen }))
86
+ }
87
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Spell styles, on the rules' side: a caster with an ability for each way the client draws one, and dummies to cast
3
+ * them at. The abilities do little (a little damage, a buff); what each looks like is its look's `style`
4
+ * (`./spells.look.ts`). The card casts them through `fx.cast`, and puts a buff of each glow on the caster with `fx.aura`
5
+ */
6
+ import { type AbilityDef, applyBuff, type BuffDef, defineCommand, type EffectDef, type Game, removeBuff, text } from '@v12-engine/engine'
7
+ import { ENEMY, PLAYER, UNIT } from '../common/units'
8
+ import { type ExampleDef, exampleField } from '../define'
9
+
10
+ export const CASTER = 'fxCaster'
11
+
12
+ /** Where the caster stands, and the dummy it casts at */
13
+ export const CASTER_AT = { x: 24, z: 19 }
14
+ export const MARK_AT = { x: 24, z: 27 }
15
+
16
+ /** A bit of damage over a radius: what most of the spells do, for something to happen */
17
+ const hurt = (radius?: number, amount = 30): EffectDef => ({ type: 'damage', amount, ...(radius && { radius }) })
18
+
19
+ /** A spell with no cost and no wait, aimed as `target` says */
20
+ const spell = (id: string, name: string, target: AbilityDef['target'], effects: EffectDef[], own: Partial<AbilityDef> = {}): AbilityDef => ({
21
+ id,
22
+ name,
23
+ target,
24
+ range: 14,
25
+ manaCost: 0,
26
+ cooldown: 0,
27
+ effects,
28
+ ...(target === 'unit' && { targets: { enemies: true } }),
29
+ ...own
30
+ })
31
+
32
+ /** A missile flying 14 cells the way it is aimed, hurting what it passes */
33
+ const missile = (own: Partial<EffectDef> = {}): EffectDef =>
34
+ ({ type: 'missile', speed: 13, distance: 14, radius: 0.8, effects: [hurt()], ...own }) as EffectDef
35
+
36
+ /** Every spell, by the style that draws it: its id, its name on the card */
37
+ export const SPELLS: readonly AbilityDef[] = [
38
+ spell('fxRing', 'Ring', 'point', [hurt(2)]),
39
+ spell('fxBurst', 'Burst', 'none', [{ type: 'heal', amount: 40 }]),
40
+ spell('fxSlam', 'Slam', 'none', [hurt(3)]),
41
+ // a second's windup: its circle shown on the ground for everyone until it lands (the telegraph every ability gets)
42
+ spell('fxSmash', 'Smash (telegraphed)', 'point', [hurt(2.5)], { castTime: 1 }),
43
+ spell('fxSonar', 'Sonar', 'point', [{ type: 'reveal', duration: 3, radius: 6 }]),
44
+ spell('fxStar', 'Falling star', 'point', [{ type: 'after', delay: 1.2, effects: [hurt(2.5, 80)] }]),
45
+ spell('fxField', 'Field', 'point', [{ type: 'zone', duration: 4, radius: 3, effects: [hurt(3, 6)] }]),
46
+ spell(
47
+ 'fxBarrage',
48
+ 'Barrage',
49
+ 'point',
50
+ [1, 1.5, 2, 2.6].map((delay): EffectDef => ({ type: 'after', delay, effects: [hurt(3, 40)] }))
51
+ ),
52
+ spell('fxArrow', 'Arrow', 'direction', [missile()]),
53
+ spell('fxHawk', 'Hawk', 'direction', [missile({ speed: 10 })]),
54
+ spell('fxRock', 'Rock', 'direction', [missile({ speed: 9 })]),
55
+ spell('fxOrb', 'Orb', 'direction', [missile({ speed: 11 })]),
56
+ spell('fxTornado', 'Tornado', 'direction', [missile({ delay: 0.75, speed: 8, radius: 1.4 })]),
57
+ spell('fxTether', 'Tether', 'unit', [{ type: 'buff', buff: 'fxBound', duration: 3 }]),
58
+ spell('fxBleed', 'Bleed (stacks)', 'unit', [{ type: 'buff', buff: 'fxBleeding', duration: 6, stack: true }])
59
+ ]
60
+
61
+ /** The glows a buff can wear on its carrier (the `glow` style's shapes), one buff each */
62
+ export const GLOWS = ['rise', 'fall', 'dust', 'about', 'column', 'ring', 'shell'] as const
63
+ export const glowBuff = (shape: string) => `fxGlow${shape[0]!.toUpperCase()}${shape.slice(1)}`
64
+
65
+ const buffs: BuffDef[] = [
66
+ { id: 'fxBound', name: 'Bound', duration: 3, harmful: true },
67
+ { id: 'fxBleeding', name: 'Bleeding', duration: 6, harmful: true, maxStacks: 5 },
68
+ ...GLOWS.map((shape): BuffDef => ({ id: glowBuff(shape), name: `Glow (${shape})`, duration: 60 }))
69
+ ]
70
+
71
+ /** The caster casts a spell by its id: at the dummy, at its spot, toward it, or with no aim, as the spell is aimed */
72
+ export const cast = defineCommand('fx.cast', { ability: text }, (world, _player, { ability }) => {
73
+ const caster = [...world.units.values()].find((unit) => unit.def.id === CASTER && unit.alive)
74
+ const dummy = [...world.units.values()].find((unit) => unit.def.id === UNIT.dummy && unit.alive)
75
+ const def = SPELLS.find((each) => each.id === ability)
76
+ if (!caster || !dummy || !def) return
77
+ if (def.target === 'none') caster.order('cast', { ability })
78
+ else if (def.target === 'unit') caster.order('cast', { ability, target: dummy.id })
79
+ else caster.order('cast', { ability, point: { x: dummy.pos.x, z: dummy.pos.z } })
80
+ })
81
+
82
+ /** Puts the glow of a shape on the caster, the others off; `none` takes them all off */
83
+ export const aura = defineCommand('fx.aura', { shape: text }, (world, _player, { shape }) => {
84
+ const caster = [...world.units.values()].find((unit) => unit.def.id === CASTER && unit.alive)
85
+ if (!caster) return
86
+ for (const each of GLOWS) removeBuff(world, caster, glowBuff(each))
87
+ if ((GLOWS as readonly string[]).includes(shape)) applyBuff(world, caster, glowBuff(shape), 60, caster)
88
+ })
89
+
90
+ export const spells: ExampleDef = {
91
+ id: 'fx-spell-styles',
92
+ category: 'fx',
93
+ title: 'How spells are drawn',
94
+ summary:
95
+ 'A caster with a spell for each style the client draws abilities in: rings, bursts, slams, a smash telegraphed on the ground as it winds up, a falling star, a barrage, missiles of four shapes, a tornado, a tether, stacks. Pick a spell to cast it at the dummy, or an aura to wear.',
96
+ tags: ['spell styles', 'abilities', 'AbilityLook', 'presetSpellStyles', 'aura', 'glow', 'buff', 'missile', 'tornado', 'tether', 'barrage'],
97
+ // two players: the caster's and the dummies'
98
+ map: () => exampleField({ name: 'Spells', players: 2 }),
99
+ start: (world) => {
100
+ world.spawn(CASTER, { at: CASTER_AT, owner: PLAYER, facing: 0 })
101
+ world.spawn(UNIT.dummy, { at: MARK_AT, owner: ENEMY })
102
+ for (const dx of [-4, 4]) world.spawn(UNIT.dummy, { at: { x: MARK_AT.x + dx, z: MARK_AT.z + 2 }, owner: ENEMY })
103
+ },
104
+ source: ['examples/fx/spells.ts', 'examples/fx/spells.look.ts']
105
+ }
106
+
107
+ /** Registers the caster, its spells, the buffs and the commands */
108
+ export function spellsRules(game: Game<any>): void {
109
+ for (const buff of buffs) game.buff(buff)
110
+ for (const ability of SPELLS) game.ability(ability)
111
+ game.unit({
112
+ id: CASTER,
113
+ name: 'Spellweaver',
114
+ kind: 'unit',
115
+ radius: 0.45,
116
+ stats: { health: 800, mana: 500, manaRegen: 50, attackDamage: 10, attackRange: 6, moveSpeed: 0 },
117
+ sight: 16,
118
+ abilities: SPELLS.map((each) => each.id)
119
+ })
120
+ game.command(cast)
121
+ game.command(aura)
122
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * What the effects' examples share on the client's side: the camera put close to where an effect plays, and the units
3
+ * of a type found as the screen knows them. Client code: only looks files import it
4
+ */
5
+ import type { GameClientApi } from '@v12-engine/client'
6
+
7
+ /** Looks at a spot from `distance` cells away, `pitch` radians down from level (0.75 a little low, 1.1 steep), turned `yaw` */
8
+ export function aim(client: GameClientApi, at: { x: number; z: number }, distance: number, pitch = 0.8, yaw?: number): void {
9
+ client.camera.setPose({ x: at.x, z: at.z, distance, pitch, yaw: yaw ?? client.camera.pose().yaw })
10
+ }
11
+
12
+ /** The ids of the units of a type the screen knows of, alive, in the order they came into the world */
13
+ export function idsOf(client: GameClientApi, type: string): number[] {
14
+ const ids: number[] = []
15
+ for (const entity of client.state.entities.values()) if (entity.def.id === type && entity.health > 0) ids.push(entity.id)
16
+ return ids.sort((a, b) => a - b)
17
+ }
18
+
19
+ /** Where a unit is drawn now, on the ground: where it is when it is not drawn */
20
+ export function where(client: GameClientApi, id: number): { x: number; z: number } | undefined {
21
+ const drawn = client.renderer.kit.drawn(id)
22
+ if (drawn) return { x: drawn.position.x, z: drawn.position.z }
23
+ const entity = client.state.entities.get(id)
24
+ return entity && { x: entity.pos.x, z: entity.pos.z }
25
+ }
26
+
27
+ /** Runs `step` every frame with the seconds since the last, until what it gives back is called */
28
+ export function everyFrame(client: GameClientApi, step: (dt: number) => void): () => void {
29
+ const observer = client.scene.onBeforeRenderObservable.add(() => step(Math.min(client.scene.getEngine().getDeltaTime() / 1000, 0.1)))
30
+ return () => client.scene.onBeforeRenderObservable.remove(observer)
31
+ }