@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,377 @@
1
+ /**
2
+ * The MOBA examples on the rules' side: heroes with levels, skill points and callings (`heroesPack`), skillshots and
3
+ * the hero spells, a lane of minions and towers (`lanesPack`), last hits and denies (`lastHitsPack`), a shop of the
4
+ * engine's items (`defaultItemSet`), kill streaks and buyback, camps of the jungle, heroes that come back after they fall
5
+ * (`respawnPack`). Their units are `./units.ts`, their maps `./maps.ts`
6
+ */
7
+ import {
8
+ applyBuff,
9
+ bool,
10
+ buy,
11
+ buyBack,
12
+ defaultItemAbilities,
13
+ defaultItemBuffs,
14
+ defaultItems,
15
+ defaultItemsPack,
16
+ defaultItemUnits,
17
+ defineCommand,
18
+ type Entity,
19
+ type Game,
20
+ type GamePack,
21
+ grantAbility,
22
+ grantXp,
23
+ heroesPack,
24
+ heroOf,
25
+ heroState,
26
+ heroTally,
27
+ laneMinion,
28
+ lanesOf,
29
+ lanesPack,
30
+ type LanesOptions,
31
+ lastHitsOf,
32
+ lastHitsPack,
33
+ learn,
34
+ mobaStatsPack,
35
+ number,
36
+ optional,
37
+ respawnIn,
38
+ respawnPack,
39
+ sendWave,
40
+ setHeroLevel,
41
+ slotsOf,
42
+ string,
43
+ type Vec2,
44
+ type World,
45
+ exactMath
46
+ } from '@v12-engine/engine'
47
+ import { nearestEnemy, packOnce, put, starts } from '../abilities/kit'
48
+ import type { ExampleCategory, ExampleDef } from '../define'
49
+ import { BLUE, jungleMap, laneMap, mobaField, RED, TOWERS } from './maps'
50
+ import { SPELL_IDS } from './spells'
51
+ import { abilities, allAbilities, allBuffs, units } from './units'
52
+
53
+ // ---- heroes
54
+
55
+ /**
56
+ * Puts down a hero of `type`, raised to `level` with its points spent: the ultimate first where it may, then its
57
+ * other abilities in turn, as a player levels a hero
58
+ */
59
+ export function putHero(world: World, type: string, owner: number, at: Vec2, level: number, name?: string): Entity {
60
+ const hero = put(world, type, owner, at, name, owner === BLUE ? Math.PI / 2 : -Math.PI / 2)
61
+ if (level > 1) setHeroLevel(world, hero, level)
62
+ const order = [abilities[3], abilities[0], abilities[1], abilities[2]].map((each) => each.id)
63
+ for (let spent = true; spent && (heroOf(hero)?.points ?? 0) > 0;) {
64
+ spent = false
65
+ for (const id of order) if ((heroOf(hero)?.points ?? 0) > 0 && learn(world, hero, id)) spent = true
66
+ }
67
+ return hero
68
+ }
69
+
70
+ /** The player's hero: the one named `hero`, alive or fallen (a fallen one is out of the world, waiting to come back) */
71
+ function heroOfPlayer(world: World, player: number): Entity | undefined {
72
+ const named = world.named('hero')
73
+ if (named && named.owner === player) return named
74
+ for (const entity of world.index.with(heroState, player)) if (entity.def.id.startsWith('moba')) return entity
75
+ return undefined
76
+ }
77
+
78
+ /** Gives a player gold */
79
+ function giveGold(world: World, player: number, amount: number): void {
80
+ const purse = world.player(player).resources
81
+ purse.gold = (purse.gold ?? 0) + amount
82
+ }
83
+
84
+ // ---- the lane
85
+
86
+ /** The lane's waves: three minions that close in and two casters, the first a few seconds in */
87
+ const laneOptions: LanesOptions = {
88
+ firstWave: 4,
89
+ every: 20,
90
+ minions: { melee: 'mobaMinion', ranged: 'mobaCasterMinion' },
91
+ wave: () => ['mobaMinion', 'mobaMinion', 'mobaMinion', 'mobaCasterMinion', 'mobaCasterMinion'],
92
+ // the game's own way of winning (none: an example plays until it is left)
93
+ cores: false,
94
+ towers: (_world, entity) => entity.def.id === 'mobaTower'
95
+ }
96
+
97
+ /** A minion of a lane, of this game's lanes */
98
+ const isMinion = (_world: World, entity: Entity) => entity.has(laneMinion)
99
+
100
+ // ---- commands the cards send
101
+
102
+ /** Strafers walk side to side, or stand still */
103
+ export const strafe = defineCommand('moba.strafe', { on: bool }, (world, _player, { on }) => {
104
+ for (const unit of world.units.values()) {
105
+ if (!unit.alive || unit.def.id !== 'mobaStrafer') continue
106
+ if (on) unit.order('patrol', { x: unit.pos.x, z: unit.pos.z < 22 ? unit.pos.z + 6 : unit.pos.z - 6 })
107
+ else unit.order('hold')
108
+ }
109
+ })
110
+
111
+ /** Experience for the player's hero: as much as `amount`, or the rest of the way to its next level (`levels`) */
112
+ export const experience = defineCommand('moba.xp', { amount: optional(number), levels: optional(number) }, (world, player, { amount, levels }) => {
113
+ const hero = heroOfPlayer(world, player)
114
+ const state = hero && heroOf(hero)
115
+ if (!hero || !state) return
116
+ if (levels) setHeroLevel(world, hero, Math.min(state.level + levels, 18))
117
+ else grantXp(world, hero, amount ?? 100)
118
+ })
119
+
120
+ /** The player's hero learns an ability, spending a point (`learn`): refused as a player's would be */
121
+ export const learnIt = defineCommand('moba.learn', { ability: string }, (world, player, { ability }) => {
122
+ const hero = heroOfPlayer(world, player)
123
+ if (hero && !learn(world, hero, ability)) world.message(player, 'cannotDoThat')
124
+ })
125
+
126
+ /** Gold for the player */
127
+ export const gold = defineCommand('moba.gold', { amount: number }, (world, player, { amount }) => giveGold(world, player, Math.min(amount, 10000)))
128
+
129
+ /** The player's hero buys an item at the shop named `shop`, as the shop window would (`buy`) */
130
+ export const buyItem = defineCommand('moba.buy', { item: string }, (world, player, { item }) => {
131
+ const shop = world.named('shop')
132
+ const hero = heroOfPlayer(world, player)
133
+ if (shop && hero) buy(world, shop, item, player, hero)
134
+ })
135
+
136
+ /** The player's fallen hero bought back, there and then (`buyBack`) */
137
+ export const buyback = defineCommand('moba.buyback', {}, (world, player) => {
138
+ const hero = heroOfPlayer(world, player)
139
+ if (hero) buyBack(world, hero)
140
+ })
141
+
142
+ /** Every hero of both sides goes for the other side, fighting what it meets */
143
+ export const fight = defineCommand('moba.fight', {}, (world) => {
144
+ for (const unit of world.index.with(heroState)) {
145
+ if (!unit.alive) continue
146
+ const enemy = nearestEnemy(world, unit)
147
+ if (enemy) unit.order('attackMove', { x: enemy.pos.x, z: enemy.pos.z })
148
+ }
149
+ })
150
+
151
+ /** A wave from both ends of the lane now, besides those that come by themselves */
152
+ export const wave = defineCommand('moba.wave', {}, (world) => {
153
+ for (const lane of lanesOf(world)) for (const end of lane.ends) if (end) sendWave(world, end.spawner.id, laneOptions)
154
+ })
155
+
156
+ /**
157
+ * The player's hero strikes the weakest minion near it: an enemy's to last-hit it, or (`deny`) one of its own side's
158
+ * that is below half its health, to deny it
159
+ */
160
+ export const strike = defineCommand('moba.strike', { deny: bool }, (world, player, { deny }) => {
161
+ const hero = heroOfPlayer(world, player)
162
+ if (!hero?.alive) return
163
+ let best: Entity | undefined
164
+ for (const unit of world.spatial.query(hero.pos.x, hero.pos.z, 9)) {
165
+ if (!unit.alive || !unit.has(laneMinion)) continue
166
+ const ours = !world.areEnemies(player, unit.owner)
167
+ if (ours !== deny || (deny && unit.health >= unit.maxHealth * 0.5)) continue
168
+ if (!best || unit.health < best.health) best = unit
169
+ }
170
+ if (best) hero.order('attack', best)
171
+ })
172
+
173
+ /** The player's hero attacks the nearest unit of a type */
174
+ export const attack = defineCommand('moba.attack', { unit: string }, (world, player, { unit: type }) => {
175
+ const hero = heroOfPlayer(world, player)
176
+ if (!hero?.alive) return
177
+ let best: Entity | undefined
178
+ for (const unit of world.units.values())
179
+ if (
180
+ unit.alive &&
181
+ unit.def.id === type &&
182
+ (!best || exactMath.hypot(unit.pos.x - hero.pos.x, unit.pos.z - hero.pos.z) < exactMath.hypot(best.pos.x - hero.pos.x, best.pos.z - hero.pos.z))
183
+ )
184
+ best = unit
185
+ if (best) hero.order('attack', best)
186
+ })
187
+
188
+ /** Hurts or stuns the player's own hero, for a hero spell that answers it (Second Wind, Barrier, Purify) */
189
+ export const trouble = defineCommand('moba.trouble', { stun: bool }, (world, player, { stun }) => {
190
+ const hero = heroOfPlayer(world, player)
191
+ if (!hero?.alive) return
192
+ if (stun) applyBuff(world, hero, 'mobaHeld', 2.5)
193
+ else world.damage(hero, hero.health * 0.6, undefined, { damageType: 'true' })
194
+ })
195
+
196
+ const commands = [attack, strafe, experience, learnIt, gold, buyItem, buyback, fight, wave, strike, trouble]
197
+
198
+ /** What the HUD reads of the player: last hits and denies, and its hero's level, points, tally and wait to come back */
199
+ const report: GamePack = {
200
+ id: 'moba.report',
201
+ report(world, player) {
202
+ const hero = heroOfPlayer(world, player)
203
+ const state = hero && heroOf(hero)
204
+ const tally = hero?.get(heroTally)
205
+ return {
206
+ ...lastHitsOf(world, player),
207
+ mobaHero: state
208
+ ? {
209
+ level: state.level,
210
+ xp: Math.floor(state.xp),
211
+ next: world.settings.heroes.xpForLevel(state.level + 1),
212
+ points: state.points,
213
+ kills: tally?.kills ?? 0,
214
+ deaths: tally?.deaths ?? 0,
215
+ assists: tally?.assists ?? 0,
216
+ streak: tally?.streak ?? 0,
217
+ back: hero.alive ? 0 : Math.ceil(respawnIn(world, hero) ?? 0),
218
+ items: (slotsOf(hero) ?? []).flatMap((slot) =>
219
+ slot ? [world.registry.items.has(slot.item) ? world.registry.item(slot.item).name : slot.item] : []
220
+ )
221
+ }
222
+ : undefined
223
+ }
224
+ }
225
+ }
226
+
227
+ // ---- the examples
228
+
229
+ /** The examples' starts by id, kept for their Start again button (`starts`) */
230
+ function example(def: ExampleDef): ExampleDef {
231
+ if (def.start) starts[def.id] = def.start as (world: World) => void
232
+ return def
233
+ }
234
+
235
+ const SOURCE = ['examples/moba/index.ts', 'examples/moba/units.ts', 'examples/moba/maps.ts']
236
+
237
+ export const examples: ExampleDef[] = [
238
+ example({
239
+ id: 'moba-skillshots',
240
+ category: 'moba',
241
+ title: 'Skillshots and a locked camera',
242
+ summary:
243
+ 'Aimed abilities that hit the first enemy in their way: a bolt, a chain that drags back, a dash, a lance. The duelists step side to side, so aim ahead of them; the camera can be locked on the hero.',
244
+ tags: ['skillshot', 'hook', 'dash', 'direction', 'camera', 'quick cast', 'league'],
245
+ source: SOURCE,
246
+ map: () => mobaField({ name: 'Skillshots', width: 48, depth: 40, blue: { x: 8, z: 20 }, red: { x: 40, z: 20 } }),
247
+ start: (world) => {
248
+ putHero(world, 'mobaStormcaller', BLUE, { x: 16, z: 20 }, 6, 'hero')
249
+ const duelists: Vec2[] = [
250
+ { x: 27, z: 15 },
251
+ { x: 30, z: 22 },
252
+ { x: 26, z: 26 }
253
+ ]
254
+ duelists.forEach((at, i) =>
255
+ put(world, 'mobaStrafer', RED, at, `enemy${i + 1}`, -Math.PI / 2).order('patrol', { x: at.x, z: at.z + (i % 2 ? -5 : 5) })
256
+ )
257
+ }
258
+ }),
259
+ example({
260
+ id: 'moba-spells',
261
+ category: 'moba',
262
+ title: 'Hero spells: Flash and the rest',
263
+ summary:
264
+ "The engine's nine hero spells, two of them on D and F: Flash, Barrier, Second Wind, Brand, Wither, Phantom Stride, Purify, Waystone and Reap. Each button casts one; hurt or stun the hero first to see what answers it.",
265
+ tags: ['flash', 'barrier', 'ignite', 'exhaust', 'ghost', 'cleanse', 'teleport', 'smite', 'summoner spells', 'D', 'F'],
266
+ source: [...SOURCE, 'examples/moba/spells.ts'],
267
+ map: () => mobaField({ name: 'Hero spells', width: 48, depth: 40, blue: { x: 8, z: 20 }, red: { x: 40, z: 20 } }),
268
+ start: (world) => {
269
+ const hero = putHero(world, 'mobaStormcaller', BLUE, { x: 18, z: 20 }, 6, 'hero')
270
+ // the other seven, besides its two: given for good (`grantAbility`)
271
+ for (const id of SPELL_IDS) grantAbility(world, hero, id, { level: 1 })
272
+ put(world, 'mobaDuskblade', RED, { x: 26, z: 21 }, 'rival', -Math.PI / 2)
273
+ // what Reap is for (no hero), and what Waystone goes to (an ally of the hero's far off)
274
+ put(world, 'mobaBrute', RED, { x: 25, z: 15 }, 'creep', -Math.PI / 2)
275
+ put(world, 'mobaMinion', BLUE, { x: 36, z: 31 }, 'waystone')
276
+ }
277
+ }),
278
+ example({
279
+ id: 'moba-lane',
280
+ category: 'moba',
281
+ title: 'A lane: waves, towers, last hits',
282
+ summary:
283
+ 'Waves of minions walk the lane from both ends and fight; two towers a side guard it. Strike the blow that kills an enemy minion for its gold (a last hit), or kill one of your own below half health so the enemy gets less (a deny).',
284
+ tags: ['lane', 'minions', 'waves', 'towers', 'last hit', 'deny', 'creep score', 'farm'],
285
+ source: [...SOURCE, 'examples/moba/maps.ts'],
286
+ map: laneMap,
287
+ start: (world) => {
288
+ for (const at of TOWERS.blue) put(world, 'mobaTower', BLUE, at)
289
+ for (const at of TOWERS.red) put(world, 'mobaTower', RED, at)
290
+ putHero(world, 'mobaStormcaller', BLUE, { x: 30, z: 14 }, 3, 'hero')
291
+ }
292
+ }),
293
+ example({
294
+ id: 'moba-shop',
295
+ category: 'moba',
296
+ title: 'A shop and build paths',
297
+ summary:
298
+ "The engine's whole tree of items in a shop: starters, basics, what they build into, legendaries. Open the shop window (P, or click the shop), or buy a path from the card: the parts first, then the recipe that uses them up.",
299
+ tags: ['shop', 'items', 'recipe', 'build path', 'gold', 'inventory', 'default items'],
300
+ source: SOURCE,
301
+ map: () => mobaField({ name: 'Shop', width: 40, depth: 40, blue: { x: 20, z: 14 }, red: { x: 20, z: 36 }, gold: 4000 }),
302
+ start: (world) => {
303
+ put(world, 'mobaShop', BLUE, { x: 20.5, z: 22.5 }, 'shop')
304
+ putHero(world, 'mobaDuskblade', BLUE, { x: 20, z: 18 }, 1, 'hero')
305
+ }
306
+ }),
307
+ example({
308
+ id: 'moba-levels',
309
+ category: 'moba',
310
+ title: 'Levels, skill points and callings',
311
+ summary:
312
+ 'A hero gains experience, levels up and gets a point a level to learn an ability with: Q, W and E from level 1, the ultimate R from level 6. Its callings (Arcanist, Binder) are on its panel; learn with the + on it, or the card.',
313
+ tags: ['hero', 'level', 'experience', 'skill points', 'ultimate', 'callings', 'roles'],
314
+ source: SOURCE,
315
+ map: () => mobaField({ name: 'Levels', width: 40, depth: 40, blue: { x: 14, z: 20 }, red: { x: 32, z: 20 } }),
316
+ start: (world) => {
317
+ put(world, 'mobaStormcaller', BLUE, { x: 18, z: 20 }, 'hero', Math.PI / 2)
318
+ for (let i = 0; i < 3; i++) put(world, 'mobaMinion', RED, { x: 24, z: 17 + i * 3 }, undefined, -Math.PI / 2).order('hold')
319
+ }
320
+ }),
321
+ example({
322
+ id: 'moba-teamfight',
323
+ category: 'moba',
324
+ title: 'Kill streaks, assists and buyback',
325
+ summary:
326
+ 'Two heroes a side. A hero that kills is on a streak until it falls, a kill helped is an assist, a fallen hero comes back after a while, or at once for gold (buyback). Fight sends them at each other.',
327
+ tags: ['kills', 'deaths', 'assists', 'streak', 'shutdown', 'buyback', 'respawn', 'KDA'],
328
+ source: SOURCE,
329
+ map: () => mobaField({ name: 'Teamfight', width: 48, depth: 32, blue: { x: 6, z: 16 }, red: { x: 42, z: 16 }, gold: 1500 }),
330
+ start: (world) => {
331
+ putHero(world, 'mobaStormcaller', BLUE, { x: 18, z: 14 }, 8, 'hero')
332
+ putHero(world, 'mobaDuskblade', BLUE, { x: 19, z: 18 }, 8)
333
+ putHero(world, 'mobaDuskblade', RED, { x: 30, z: 14 }, 5)
334
+ putHero(world, 'mobaStormcaller', RED, { x: 31, z: 18 }, 5)
335
+ }
336
+ }),
337
+ example({
338
+ id: 'moba-jungle',
339
+ category: 'moba',
340
+ title: 'Camps of the jungle',
341
+ summary:
342
+ 'Creeps of the jungle keep to their camp: they turn on whoever strikes one, chase a while and walk back. Cleared, a camp comes back 20 or 30 seconds later. Reap (F) finishes a creep off with true damage.',
343
+ tags: ['jungle', 'camps', 'creeps', 'neutral', 'respawn', 'smite', 'reap', 'leash'],
344
+ source: [...SOURCE, 'examples/moba/maps.ts'],
345
+ map: jungleMap,
346
+ start: (world) => {
347
+ const hero = putHero(world, 'mobaDuskblade', BLUE, { x: 18, z: 16 }, 6, 'hero')
348
+ grantAbility(world, hero, 'mobaReap', { level: 1 })
349
+ }
350
+ })
351
+ ]
352
+
353
+ export const category: ExampleCategory = {
354
+ rules: (game: Game<any>) => {
355
+ // the gold a shop sells for and a kill pays: none to start with, so it shows nothing in the other examples
356
+ if (!(game.toRules().resources ?? []).some((each) => each.id === 'gold')) game.resource({ id: 'gold', name: 'Gold', start: 0 })
357
+ packOnce(game, heroesPack())
358
+ packOnce(game, mobaStatsPack())
359
+ packOnce(game, defaultItemsPack())
360
+ packOnce(game, lanesPack(laneOptions))
361
+ // last hits and denies of lane minions alone
362
+ packOnce(game, lastHitsPack({ deny: { below: 0.5, denies: (world, _attacker, target) => isMinion(world, target) }, counts: isMinion }))
363
+ // the MOBA's heroes come back by themselves, 6 seconds after they fall; another example's hero is left as it was
364
+ packOnce(game, respawnPack({ time: () => 6, heroes: (_world, hero) => hero.def.id.startsWith('moba') }))
365
+ packOnce(game, report)
366
+ // the engine's items, with what they use: their abilities, buffs and units (a ward, a wolf)
367
+ for (const item of defaultItems) game.item(item)
368
+ for (const ability of defaultItemAbilities) game.ability(ability)
369
+ for (const buff of defaultItemBuffs) game.buff(buff)
370
+ for (const unit of defaultItemUnits) game.unit(unit)
371
+ for (const unit of units) game.unit(unit)
372
+ for (const ability of allAbilities) game.ability(ability)
373
+ for (const buff of allBuffs) game.buff(buff)
374
+ for (const command of commands) game.command(command)
375
+ },
376
+ examples
377
+ }