@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,383 @@
1
+ /**
2
+ * The dialogue examples on the rules' side: their list entries, maps and starts, and what the rules say. A line the
3
+ * rules say (`world.dialog.say`, `talk` in a sequence) is heard by every screen at the same moment and kept in a save;
4
+ * its words are a message key the view has words for (`hud.messages`, in `./looks.ts`). Objectives (`world.objectives`)
5
+ * are the quest log; messages (`world.message`) the line of news over the console. A choice asked in a cinematic comes
6
+ * back to the rules as a command of the player's (`cinematicChoice`), so the story branches the same everywhere
7
+ */
8
+ import {
9
+ cinematicChoice,
10
+ defineCommand,
11
+ defineComponent,
12
+ defineIds,
13
+ defineSequence,
14
+ entityDied,
15
+ exactMath,
16
+ type Game,
17
+ grantXp,
18
+ type LineDef,
19
+ oneOf,
20
+ talk,
21
+ type UnitTypeDef,
22
+ wait,
23
+ type World
24
+ } from '@v12-engine/engine'
25
+ import { CINE } from '../cinematics/index'
26
+ import { ROAD_END, SQUARE, villageMap } from '../cinematics/village'
27
+ import { ENEMY, PLAYER, UNIT } from '../common/units'
28
+ import type { ExampleCategory } from '../define'
29
+
30
+ /** The dialogue examples' own unit types: the village's elder, and a villager who has things to say */
31
+ export const TALK = defineIds('dialogs.elder', 'dialogs.villager')
32
+
33
+ const units: UnitTypeDef[] = [
34
+ {
35
+ id: TALK['dialogs.elder'],
36
+ name: 'Elder Maren',
37
+ kind: 'unit',
38
+ radius: 0.42,
39
+ stats: { health: 300, moveSpeed: 2.4 },
40
+ sight: 9
41
+ },
42
+ {
43
+ id: TALK['dialogs.villager'],
44
+ name: 'Villager',
45
+ kind: 'unit',
46
+ radius: 0.4,
47
+ stats: { health: 150, moveSpeed: 2.8 },
48
+ sight: 8
49
+ }
50
+ ]
51
+
52
+ /** What each example's map is called: its rules tell their own world by it (`world.map.name`) */
53
+ export const MAPS = { conversation: 'Hearth', choice: 'Toll', quest: 'Quest', barks: 'Market', messages: 'News', ownBox: 'Parchment' } as const
54
+
55
+ /** The speakers, by the ids the rules say lines with; their names and faces are the view's (`cinematics.speakers`) */
56
+ export const SPEAKER = { knight: 'dialogs.knight', elder: 'dialogs.elder', bandit: 'dialogs.bandit' } as const
57
+
58
+ /** The cinematic of the toll (the view's, by this id), and what its question is called */
59
+ export const TOLL_CINEMATIC = 'dialogs.toll'
60
+ export const TOLL_QUESTION = 'toll'
61
+
62
+ /** Where the elder stands, by the fire */
63
+ export const ELDER_AT = { x: SQUARE.x + 1.6, z: SQUARE.z + 0.8 }
64
+
65
+ // ---- Two talking
66
+
67
+ /** The conversation at the fire: a line each in turn, each said by a unit (its face in the box, talking) */
68
+ const conversationLines: LineDef[] = [
69
+ { speaker: SPEAKER.elder, unit: 'elder', text: 'dialogsTalk1', seconds: 4.5 },
70
+ { speaker: SPEAKER.knight, unit: 'hero', text: 'dialogsTalk2', seconds: 3.5 },
71
+ { speaker: SPEAKER.elder, unit: 'elder', text: 'dialogsTalk3', seconds: 5, emotion: 'worried' },
72
+ { speaker: SPEAKER.knight, unit: 'hero', text: 'dialogsTalk4', seconds: 3.5 },
73
+ { speaker: SPEAKER.elder, unit: 'elder', text: 'dialogsTalk5', seconds: 4 }
74
+ ]
75
+ const conversation = defineSequence({ id: 'dialogs.conversation', steps: [...talk(PLAYER, conversationLines)] })
76
+
77
+ /** Starts the conversation: what the Talk button sends */
78
+ export const converse = defineCommand('dialogs.converse', {}, (world) => void world.sequences.start(conversation))
79
+
80
+ // ---- A choice that matters
81
+
82
+ /**
83
+ * What the player chose at the toll, heard by the rules as the command the cinematic sent: they pay (the bandits
84
+ * take the gold and go), fight (the bandits attack), or talk them round (the bandits join the player)
85
+ */
86
+ function answerToll(world: World, choice: string): void {
87
+ const bandits = world.find({ owner: ENEMY }).filter((unit) => unit.alive)
88
+ const hero = world.named('hero')
89
+ if (choice === 'pay') {
90
+ world.message(PLAYER, 'dialogsPaid', 'info')
91
+ for (const bandit of bandits) bandit.order('move', { x: bandit.pos.x + (bandit.pos.x < 24 ? -14 : 14), z: bandit.pos.z + 10 })
92
+ world.sequences.start(banditsLeave)
93
+ } else if (choice === 'fight') {
94
+ world.message(PLAYER, 'dialogsFight', 'error')
95
+ if (hero) for (const bandit of bandits) bandit.order('attack', hero)
96
+ if (hero && bandits[0]) hero.order('attack', bandits[0])
97
+ } else {
98
+ world.message(PLAYER, 'dialogsJoined', 'hint')
99
+ for (const bandit of bandits) {
100
+ world.setOwner(bandit, PLAYER)
101
+ if (hero) bandit.order('move', { x: hero.pos.x + (bandit.pos.x - 24) * 0.5, z: hero.pos.z - 1.5 })
102
+ }
103
+ }
104
+ }
105
+
106
+ /** The paid bandits gone into the woods a few seconds later */
107
+ const banditsLeave = defineSequence({
108
+ id: 'dialogs.banditsLeave',
109
+ steps: [
110
+ wait(4),
111
+ (world) => {
112
+ for (const bandit of world.find({ owner: ENEMY })) if (bandit.alive) world.kill(bandit, undefined, false, 'removed')
113
+ }
114
+ ]
115
+ })
116
+
117
+ /** The toll laid out again: the knight on the road, three bandits across it, out of reach of each other's eyes (9 cells) */
118
+ function toll(world: World): void {
119
+ world.spawn(UNIT.knight, { at: { x: ROAD_END.x, z: ROAD_END.z + 5 }, owner: PLAYER, facing: 0, name: 'hero' })
120
+ const across = [-2, 0, 2]
121
+ across.forEach((dx, i) =>
122
+ world.spawn(CINE['cinematics.raider'], {
123
+ at: { x: ROAD_END.x + dx, z: ROAD_END.z + 14.5 + Math.abs(dx) * 0.4 },
124
+ owner: ENEMY,
125
+ facing: Math.PI,
126
+ name: `bandit${i + 1}`
127
+ })
128
+ )
129
+ }
130
+
131
+ /** Takes every unit away and lays the toll out again */
132
+ export const resetToll = defineCommand('dialogs.resetToll', {}, (world) => {
133
+ for (const unit of [...world.units.values()]) if (unit.alive) world.kill(unit, undefined, false, 'removed')
134
+ toll(world)
135
+ })
136
+
137
+ // ---- A quest giver
138
+
139
+ /** The quest's objectives (the quest log) */
140
+ const QUEST = {
141
+ bandits: { id: 'dialogs.bandits', text: 'dialogsQuestBandits', detail: 'dialogsQuestBanditsDetail' },
142
+ back: { id: 'dialogs.back', text: 'dialogsQuestBack' }
143
+ }
144
+ const QUEST_BANDITS = 3
145
+ /** How near the elder the knight stands to talk to her */
146
+ const TALK_RANGE = 3.2
147
+
148
+ /** Where the quest stands: not given, given (the bandits to kill), done (back to the elder), rewarded */
149
+ const questState = defineComponent<{ stage: 'none' | 'given' | 'killed' | 'rewarded'; killed: number }>({
150
+ id: 'dialogs.quest',
151
+ default: () => ({ stage: 'none', killed: 0 })
152
+ })
153
+
154
+ /** The elder gives the quest: lines, then the objective in the log */
155
+ const giveQuest = defineSequence({
156
+ id: 'dialogs.giveQuest',
157
+ steps: [
158
+ ...talk(PLAYER, [
159
+ { speaker: SPEAKER.elder, unit: 'elder', text: 'dialogsQuest1', seconds: 4.5 },
160
+ { speaker: SPEAKER.knight, unit: 'hero', text: 'dialogsQuest2', seconds: 3 },
161
+ { speaker: SPEAKER.elder, unit: 'elder', text: 'dialogsQuest3', seconds: 4 }
162
+ ]),
163
+ (world) => {
164
+ // the client says "New objective" by itself as one is given
165
+ world.objectives.add(PLAYER, { ...QUEST.bandits, required: false }, { progress: { value: 0, max: QUEST_BANDITS } })
166
+ }
167
+ ]
168
+ })
169
+
170
+ /** The elder thanks the knight and rewards him: experience, and his health back */
171
+ const rewardQuest = defineSequence({
172
+ id: 'dialogs.rewardQuest',
173
+ steps: [
174
+ ...talk(PLAYER, [
175
+ { speaker: SPEAKER.elder, unit: 'elder', text: 'dialogsQuest4', seconds: 4 },
176
+ { speaker: SPEAKER.knight, unit: 'hero', text: 'dialogsQuest5', seconds: 3 }
177
+ ]),
178
+ (world) => {
179
+ world.objectives.complete(PLAYER, QUEST.back.id)
180
+ const hero = world.named('hero')
181
+ if (hero?.alive) {
182
+ grantXp(world, hero, 500)
183
+ hero.health = hero.maxHealth
184
+ }
185
+ world.message(PLAYER, 'dialogsQuestReward', 'info')
186
+ }
187
+ ]
188
+ })
189
+
190
+ /** The quest's turns: the knight comes to the elder to be given it, and back to be rewarded */
191
+ const questGiver = {
192
+ name: 'dialogs.questGiver',
193
+ every: 3,
194
+ update(world: World) {
195
+ if (world.map.name !== MAPS.quest) return
196
+ const hero = world.named('hero')
197
+ const elder = world.named('elder')
198
+ if (!hero?.alive || !elder?.alive) return
199
+ const near = exactMath.hypot(hero.pos.x - elder.pos.x, hero.pos.z - elder.pos.z) <= TALK_RANGE
200
+ const state = world.state(questState)
201
+ if (near && state.stage === 'none') {
202
+ world.setState(questState, { ...state, stage: 'given' })
203
+ hero.order('stop')
204
+ world.sequences.start(giveQuest)
205
+ } else if (near && state.stage === 'killed') {
206
+ world.setState(questState, { ...state, stage: 'rewarded' })
207
+ hero.order('stop')
208
+ world.sequences.start(rewardQuest)
209
+ }
210
+ }
211
+ }
212
+
213
+ /** A bandit of the quest fell: one more on the objective, and when all are down, back to the elder */
214
+ function banditFell(world: World, unit: { owner: number; def: { id: string } }): void {
215
+ if (world.map.name !== MAPS.quest || unit.owner !== ENEMY) return
216
+ const state = world.state(questState)
217
+ if (state.stage !== 'given') return
218
+ const killed = state.killed + 1
219
+ world.objectives.progress(PLAYER, QUEST.bandits.id, killed)
220
+ if (killed < QUEST_BANDITS) return world.setState(questState, { ...state, killed })
221
+ world.setState(questState, { stage: 'killed', killed })
222
+ world.objectives.complete(PLAYER, QUEST.bandits.id)
223
+ world.objectives.add(PLAYER, { ...QUEST.back, required: false })
224
+ world.dialog.say(PLAYER, { speaker: SPEAKER.knight, unit: 'hero', text: 'dialogsQuestBackLine', seconds: 3 })
225
+ }
226
+
227
+ /** The quest's village: the elder at the fire, the knight on the road, three bandits in the east woods */
228
+ function questVillage(world: World): void {
229
+ world.spawn(TALK['dialogs.elder'], { at: ELDER_AT, owner: PLAYER, facing: Math.PI, name: 'elder' })
230
+ world.spawn(UNIT.knight, { at: { x: ROAD_END.x, z: ROAD_END.z + 8 }, owner: PLAYER, facing: 0, name: 'hero' })
231
+ const camp = [
232
+ [38, 29],
233
+ [39.5, 31],
234
+ [37.5, 32.5]
235
+ ]
236
+ for (const [x, z] of camp) world.spawn(CINE['cinematics.raider'], { at: { x, z }, owner: ENEMY, facing: -Math.PI / 2 })
237
+ }
238
+
239
+ // ---- Barks
240
+
241
+ /** The market's people, milling about: the client has them say things over their heads */
242
+ function market(world: World): void {
243
+ const spots = [
244
+ [20, 26.5],
245
+ [21.5, 29],
246
+ [27, 24],
247
+ [28.5, 27.5],
248
+ [24, 30.5],
249
+ [25.5, 22]
250
+ ]
251
+ for (const [x, z] of spots)
252
+ world.spawn(TALK['dialogs.villager'], { at: { x, z }, owner: PLAYER, facing: exactMath.atan2(SQUARE.x - x, SQUARE.z - z) })
253
+ world.spawn(UNIT.footman, { at: { x: 22, z: 21 }, owner: PLAYER, facing: 0, name: 'guard' })
254
+ world.spawn(UNIT.footman, { at: { x: 26, z: 21 }, owner: PLAYER, facing: 0 })
255
+ }
256
+
257
+ /** Sends the villagers wandering a little: a few steps each, somewhere round the square */
258
+ export const wander = defineCommand('dialogs.wander', {}, (world) => {
259
+ let n = world.tick
260
+ for (const unit of world.find({ type: TALK['dialogs.villager'] })) {
261
+ if (!unit.alive) continue
262
+ n = (n * 1103515245 + 12345) % 2147483648
263
+ const angle = (n / 2147483648) * Math.PI * 2
264
+ const away = 2.5 + (n % 5)
265
+ unit.order('move', { x: SQUARE.x + exactMath.cos(angle) * away, z: SQUARE.z + exactMath.sin(angle) * away })
266
+ }
267
+ })
268
+
269
+ // ---- Messages
270
+
271
+ /** Each kind of news the rules can tell the player, sent from the example's buttons */
272
+ export const news = defineCommand(
273
+ 'dialogs.news',
274
+ { kind: oneOf('info', 'hint', 'error', 'objective', 'done', 'line') },
275
+ (world, _player, { kind }) => {
276
+ if (kind === 'info' || kind === 'hint' || kind === 'error') return world.message(PLAYER, `dialogsNews_${kind}`, kind)
277
+ if (kind === 'objective') {
278
+ if (!world.objectives.has(PLAYER, 'dialogs.news'))
279
+ world.objectives.add(PLAYER, { id: 'dialogs.news', text: 'dialogsNewsObjective', required: false })
280
+ return
281
+ }
282
+ if (kind === 'done') {
283
+ if (world.objectives.has(PLAYER, 'dialogs.news')) world.objectives.complete(PLAYER, 'dialogs.news')
284
+ return
285
+ }
286
+ world.dialog.say(PLAYER, { speaker: SPEAKER.elder, unit: 'elder', text: 'dialogsNewsLine', seconds: 4 })
287
+ }
288
+ )
289
+
290
+ /** The elder and the knight at the fire, facing each other */
291
+ function hearth(world: World): void {
292
+ world.spawn(TALK['dialogs.elder'], { at: ELDER_AT, owner: PLAYER, facing: -Math.PI / 2, name: 'elder' })
293
+ world.spawn(UNIT.knight, { at: { x: SQUARE.x - 1.6, z: SQUARE.z + 0.8 }, owner: PLAYER, facing: Math.PI / 2, name: 'hero' })
294
+ }
295
+
296
+ export const category: ExampleCategory = {
297
+ rules: (game: Game<any>) => {
298
+ for (const unit of units) game.unit(unit)
299
+ game.component(questState)
300
+ game.sequence(conversation)
301
+ game.sequence(banditsLeave)
302
+ game.sequence(giveQuest)
303
+ game.sequence(rewardQuest)
304
+ game.system(questGiver)
305
+ game.command(converse)
306
+ game.command(resetToll)
307
+ game.command(wander)
308
+ game.command(news)
309
+ // the toll's answer, as the cinematic sent it: only the toll's question of the toll's cinematic
310
+ game.on(cinematicChoice, (world, { cinematic, question, choice }) => {
311
+ if (cinematic === TOLL_CINEMATIC && question === TOLL_QUESTION) answerToll(world, choice)
312
+ })
313
+ game.on(entityDied, (world, { entity, reason }) => reason !== 'removed' && banditFell(world, entity))
314
+ },
315
+ examples: [
316
+ {
317
+ id: 'dialogs-conversation',
318
+ category: 'dialogs',
319
+ title: 'Two talking',
320
+ summary:
321
+ 'The knight and the elder talk at the fire, each line with the speaker’s face. Press Talk, then move the box, switch to subtitles or a typewriter, and talk again.',
322
+ tags: ['dialog', 'portrait', 'face', 'talk', 'lines', 'speaker', 'subtitles', 'typewriter', 'setDialogLook'],
323
+ map: () => villageMap(MAPS.conversation, 1),
324
+ start: (world) => hearth(world),
325
+ source: ['examples/dialogs/index.ts', 'examples/dialogs/looks.ts']
326
+ },
327
+ {
328
+ id: 'dialogs-choice',
329
+ category: 'dialogs',
330
+ title: 'A choice that matters',
331
+ summary:
332
+ 'Bandits block the road and ask a toll. Pay, fight or talk them round: the answer goes to the rules, and the bandits leave, attack or join you.',
333
+ tags: ['choice', 'branch', 'choose', 'cinematicChoice', 'story', 'options', 'conversation'],
334
+ map: () => villageMap(MAPS.choice),
335
+ start: (world) => toll(world),
336
+ source: ['examples/dialogs/index.ts', 'examples/dialogs/looks.ts']
337
+ },
338
+ {
339
+ id: 'dialogs-quest',
340
+ category: 'dialogs',
341
+ title: 'A quest giver',
342
+ summary:
343
+ 'Walk the knight to the elder (the ! by the fire): she asks him to clear the bandits in the east woods. The quest log counts them; go back to her for the reward.',
344
+ tags: ['quest', 'objective', 'reward', 'npc', 'quest log', 'progress', 'experience', 'talk'],
345
+ map: () => villageMap(MAPS.quest),
346
+ start: (world) => questVillage(world),
347
+ source: ['examples/dialogs/index.ts', 'examples/dialogs/looks.ts']
348
+ },
349
+ {
350
+ id: 'dialogs-barks',
351
+ category: 'dialogs',
352
+ title: 'Barks and speech bubbles',
353
+ summary:
354
+ 'The market chatters: people say things in bubbles over their heads. Click one and it answers; order the guard about and he acknowledges. Bubbles or floating words.',
355
+ tags: ['bark', 'speech bubble', 'chatter', 'npc', 'acknowledge', 'floating text', 'attach'],
356
+ map: () => villageMap(MAPS.barks, 1),
357
+ start: (world) => market(world),
358
+ source: ['examples/dialogs/index.ts', 'examples/dialogs/looks.ts']
359
+ },
360
+ {
361
+ id: 'dialogs-messages',
362
+ category: 'dialogs',
363
+ title: 'Messages, alerts and pings',
364
+ summary:
365
+ 'Every way the game tells the player something: messages of three kinds, an objective given and done, a line, an alert, a ping on the map and words floating up.',
366
+ tags: ['message', 'alert', 'notification', 'ping', 'objective', 'floating text', 'toast', 'news'],
367
+ map: () => villageMap(MAPS.messages, 1),
368
+ start: (world) => hearth(world),
369
+ source: ['examples/dialogs/index.ts', 'examples/dialogs/looks.ts']
370
+ },
371
+ {
372
+ id: 'dialogs-own-box',
373
+ category: 'dialogs',
374
+ title: 'A dialog box of your own',
375
+ summary:
376
+ 'The same conversation in a box and a choice menu drawn by the game itself: a parchment scroll that types the words out, and wax-seal answers.',
377
+ tags: ['dialog box', 'custom', 'mount', 'DialogLook', 'ChoiceLook', 'typewriter', 'style'],
378
+ map: () => villageMap(MAPS.ownBox, 1),
379
+ start: (world) => hearth(world),
380
+ source: ['examples/dialogs/index.ts', 'examples/dialogs/looks.ts']
381
+ }
382
+ ]
383
+ }