@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.
- package/README.md +4 -1
- package/base/_gitignore +5 -0
- package/base/package.json +3 -3
- package/base/src/main.ts +36 -10
- package/base/src/navigation.worker.ts +7 -0
- package/base/vite.config.ts +24 -2
- package/cli.mjs +21 -6
- package/index.mjs +27 -7
- package/layers/desktop/electron/app.mjs +237 -0
- package/layers/desktop/electron/config.mjs +16 -0
- package/layers/desktop/electron/dev.mjs +63 -0
- package/layers/desktop/electron/files.mjs +86 -0
- package/layers/desktop/electron/icons/icon.icns +0 -0
- package/layers/desktop/electron/icons/icon.png +0 -0
- package/layers/desktop/electron/main.mjs +12 -0
- package/layers/desktop/electron/package.mjs +75 -0
- package/layers/desktop/electron/preload.cjs +41 -0
- package/layers/desktop/electron/presentation.m +87 -0
- package/layers/desktop/electron/serve.mjs +76 -0
- package/layers/desktop/electron/tools.mjs +63 -0
- package/layers/desktop/electron/updates.mjs +71 -0
- package/layers/desktop/package.json +13 -0
- package/layers/desktop/src/desktop.ts +175 -0
- package/layers/desktop/src/shell.ts +279 -0
- package/layers/desktop/tests/main.test.ts +189 -0
- package/layers/desktop/tests/shell.test.ts +152 -0
- package/layers/rooms/server.ts +13 -10
- package/layers/server/server.ts +4 -5
- package/layers/source/vite.config.ts +21 -1
- package/package.json +2 -1
- package/starters/campaign/README.md +9 -2
- package/starters/campaign/layers/rooms/server.ts +3 -3
- package/starters/campaign/layers/server/server.ts +3 -5
- package/starters/campaign/layers/tests/tests/rules.test.ts +40 -21
- package/starters/campaign/src/campaign/game.ts +26 -5
- package/starters/campaign/src/campaign/index.ts +104 -68
- package/starters/campaign/src/campaign/kills.ts +1 -1
- package/starters/campaign/src/campaign/missions/homecoming.ts +7 -7
- package/starters/campaign/src/campaign/missions/siege.ts +6 -6
- package/starters/campaign/src/campaign/missions/warlord.ts +13 -14
- package/starters/campaign/src/campaign/scenes.ts +8 -7
- package/starters/campaign/src/campaign/units.ts +59 -20
- package/starters/campaign/src/campaign/view/attacks.ts +19 -0
- package/starters/campaign/src/campaign/view/fire.ts +2 -2
- package/starters/campaign/src/campaign/view/index.ts +54 -43
- package/starters/campaign/src/campaign/waves.ts +18 -17
- package/starters/campaign/src/game.ts +8 -1
- package/starters/campaign/src/main.ts +38 -19
- package/starters/campaign/src/maps/fort.names.ts +2 -2
- package/starters/campaign/src/maps/fort.vgmap.json +102 -2
- package/starters/campaign/src/maps/homestead.names.ts +2 -2
- package/starters/campaign/src/maps/homestead.vgmap.json +85 -1
- package/starters/campaign/src/maps/pass.names.ts +2 -2
- package/starters/campaign/src/maps/pass.vgmap.json +69 -1
- package/starters/campaign/src/waves-panel.ts +2 -1
- package/starters/empty/README.md +18 -10
- package/starters/empty/layers/tests/tests/rules.test.ts +16 -17
- package/starters/empty/src/game.ts +11 -75
- package/starters/empty/src/generate.worker.ts +5 -5
- package/starters/empty/src/rules.ts +83 -25
- package/starters/empty/src/view.ts +82 -0
- package/starters/examples/README.md +83 -0
- package/starters/examples/layers/tests/tests/examples.test.ts +30 -0
- package/starters/examples/package.json +5 -0
- package/starters/examples/src/examples/abilities/areas.ts +165 -0
- package/starters/examples/src/examples/abilities/areasLooks.ts +30 -0
- package/starters/examples/src/examples/abilities/buffs.ts +287 -0
- package/starters/examples/src/examples/abilities/buffsLooks.ts +46 -0
- package/starters/examples/src/examples/abilities/changing.ts +228 -0
- package/starters/examples/src/examples/abilities/changingLooks.ts +61 -0
- package/starters/examples/src/examples/abilities/control.ts +158 -0
- package/starters/examples/src/examples/abilities/controlLooks.ts +78 -0
- package/starters/examples/src/examples/abilities/damage.ts +156 -0
- package/starters/examples/src/examples/abilities/damageLooks.ts +37 -0
- package/starters/examples/src/examples/abilities/family.ts +29 -0
- package/starters/examples/src/examples/abilities/index.ts +32 -0
- package/starters/examples/src/examples/abilities/kit.ts +179 -0
- package/starters/examples/src/examples/abilities/lookKit.ts +71 -0
- package/starters/examples/src/examples/abilities/looks.ts +47 -0
- package/starters/examples/src/examples/abilities/missiles.ts +203 -0
- package/starters/examples/src/examples/abilities/missilesLooks.ts +38 -0
- package/starters/examples/src/examples/abilities/moves.ts +206 -0
- package/starters/examples/src/examples/abilities/movesLooks.ts +52 -0
- package/starters/examples/src/examples/abilities/recasts.ts +181 -0
- package/starters/examples/src/examples/abilities/recastsLooks.ts +41 -0
- package/starters/examples/src/examples/abilities/resources.ts +166 -0
- package/starters/examples/src/examples/abilities/resourcesLooks.ts +42 -0
- package/starters/examples/src/examples/abilities/summons.ts +296 -0
- package/starters/examples/src/examples/abilities/summonsLooks.ts +115 -0
- package/starters/examples/src/examples/abilities/triggers.ts +204 -0
- package/starters/examples/src/examples/abilities/triggersLooks.ts +53 -0
- package/starters/examples/src/examples/abilities/vision.ts +137 -0
- package/starters/examples/src/examples/abilities/visionLooks.ts +50 -0
- package/starters/examples/src/examples/animation/index.ts +235 -0
- package/starters/examples/src/examples/animation/looks.ts +444 -0
- package/starters/examples/src/examples/catalog.ts +52 -0
- package/starters/examples/src/examples/cinematics/index.ts +217 -0
- package/starters/examples/src/examples/cinematics/looks.ts +476 -0
- package/starters/examples/src/examples/cinematics/tour.json +43 -0
- package/starters/examples/src/examples/cinematics/village.ts +88 -0
- package/starters/examples/src/examples/common/looks.ts +65 -0
- package/starters/examples/src/examples/common/units.ts +70 -0
- package/starters/examples/src/examples/construction/index.ts +329 -0
- package/starters/examples/src/examples/construction/looks.ts +275 -0
- package/starters/examples/src/examples/custom-animation/bones.ts +92 -0
- package/starters/examples/src/examples/custom-animation/index.ts +190 -0
- package/starters/examples/src/examples/custom-animation/looks.ts +523 -0
- package/starters/examples/src/examples/custom-animation/runes.ts +82 -0
- package/starters/examples/src/examples/custom-fx/glaive.look.ts +110 -0
- package/starters/examples/src/examples/custom-fx/glaive.ts +40 -0
- package/starters/examples/src/examples/custom-fx/gpu.look.ts +145 -0
- package/starters/examples/src/examples/custom-fx/gpu.ts +22 -0
- package/starters/examples/src/examples/custom-fx/index.ts +25 -0
- package/starters/examples/src/examples/custom-fx/lights.look.ts +144 -0
- package/starters/examples/src/examples/custom-fx/lights.ts +25 -0
- package/starters/examples/src/examples/custom-fx/looks.ts +36 -0
- package/starters/examples/src/examples/custom-fx/meteor.look.ts +133 -0
- package/starters/examples/src/examples/custom-fx/meteor.ts +26 -0
- package/starters/examples/src/examples/custom-fx/parts.look.ts +193 -0
- package/starters/examples/src/examples/custom-fx/parts.ts +21 -0
- package/starters/examples/src/examples/custom-fx/portal.look.ts +193 -0
- package/starters/examples/src/examples/custom-fx/portal.ts +48 -0
- package/starters/examples/src/examples/custom-fx/renderer.look.ts +139 -0
- package/starters/examples/src/examples/custom-fx/renderer.ts +18 -0
- package/starters/examples/src/examples/custom-fx/ribbons.look.ts +151 -0
- package/starters/examples/src/examples/custom-fx/ribbons.ts +22 -0
- package/starters/examples/src/examples/custom-fx/shader.ts +42 -0
- package/starters/examples/src/examples/custom-fx/shield.look.ts +226 -0
- package/starters/examples/src/examples/custom-fx/shield.ts +43 -0
- package/starters/examples/src/examples/define.ts +86 -0
- package/starters/examples/src/examples/destruction/index.ts +570 -0
- package/starters/examples/src/examples/destruction/looks.ts +447 -0
- package/starters/examples/src/examples/dialogs/index.ts +383 -0
- package/starters/examples/src/examples/dialogs/looks.ts +546 -0
- package/starters/examples/src/examples/dynamic-map/index.ts +603 -0
- package/starters/examples/src/examples/dynamic-map/looks.ts +301 -0
- package/starters/examples/src/examples/fx/blood.look.ts +62 -0
- package/starters/examples/src/examples/fx/blood.ts +59 -0
- package/starters/examples/src/examples/fx/feel.look.ts +86 -0
- package/starters/examples/src/examples/fx/feel.ts +57 -0
- package/starters/examples/src/examples/fx/fight.ts +28 -0
- package/starters/examples/src/examples/fx/floats.look.ts +108 -0
- package/starters/examples/src/examples/fx/floats.ts +24 -0
- package/starters/examples/src/examples/fx/gallery.look.ts +168 -0
- package/starters/examples/src/examples/fx/gallery.ts +23 -0
- package/starters/examples/src/examples/fx/index.ts +30 -0
- package/starters/examples/src/examples/fx/links.look.ts +83 -0
- package/starters/examples/src/examples/fx/links.ts +49 -0
- package/starters/examples/src/examples/fx/looks.ts +55 -0
- package/starters/examples/src/examples/fx/map.look.ts +93 -0
- package/starters/examples/src/examples/fx/map.ts +33 -0
- package/starters/examples/src/examples/fx/marks.look.ts +100 -0
- package/starters/examples/src/examples/fx/marks.ts +36 -0
- package/starters/examples/src/examples/fx/shots.look.ts +92 -0
- package/starters/examples/src/examples/fx/shots.ts +71 -0
- package/starters/examples/src/examples/fx/spells.look.ts +87 -0
- package/starters/examples/src/examples/fx/spells.ts +122 -0
- package/starters/examples/src/examples/fx/stage.ts +31 -0
- package/starters/examples/src/examples/gui/babylonScreen.ts +167 -0
- package/starters/examples/src/examples/gui/babylonWorld.ts +191 -0
- package/starters/examples/src/examples/gui/htmlPart.ts +95 -0
- package/starters/examples/src/examples/gui/index.ts +143 -0
- package/starters/examples/src/examples/gui/keys.ts +91 -0
- package/starters/examples/src/examples/gui/layout.ts +42 -0
- package/starters/examples/src/examples/gui/looks.ts +270 -0
- package/starters/examples/src/examples/gui/minimap.ts +127 -0
- package/starters/examples/src/examples/gui/ownInterface.ts +129 -0
- package/starters/examples/src/examples/gui/plates.ts +87 -0
- package/starters/examples/src/examples/gui/radial.ts +104 -0
- package/starters/examples/src/examples/gui/solidPart.ts +107 -0
- package/starters/examples/src/examples/looks.ts +45 -0
- package/starters/examples/src/examples/mechanics/battle.ts +51 -0
- package/starters/examples/src/examples/mechanics/damage.ts +80 -0
- package/starters/examples/src/examples/mechanics/flying.ts +93 -0
- package/starters/examples/src/examples/mechanics/formations.ts +58 -0
- package/starters/examples/src/examples/mechanics/highGround.ts +82 -0
- package/starters/examples/src/examples/mechanics/index.ts +44 -0
- package/starters/examples/src/examples/mechanics/looks.ts +289 -0
- package/starters/examples/src/examples/mechanics/maze.ts +119 -0
- package/starters/examples/src/examples/mechanics/orders.ts +68 -0
- package/starters/examples/src/examples/mechanics/selection.ts +30 -0
- package/starters/examples/src/examples/moba/index.ts +377 -0
- package/starters/examples/src/examples/moba/looks.ts +349 -0
- package/starters/examples/src/examples/moba/maps.ts +69 -0
- package/starters/examples/src/examples/moba/spells.ts +34 -0
- package/starters/examples/src/examples/moba/units.ts +196 -0
- package/starters/examples/src/examples/nature/camera.ts +15 -0
- package/starters/examples/src/examples/nature/grass.ts +64 -0
- package/starters/examples/src/examples/nature/index.ts +327 -0
- package/starters/examples/src/examples/nature/land.ts +173 -0
- package/starters/examples/src/examples/nature/looks.ts +577 -0
- package/starters/examples/src/examples/nature/maps.ts +370 -0
- package/starters/examples/src/examples/nature/skies.ts +324 -0
- package/starters/examples/src/examples/nature/weather.ts +80 -0
- package/starters/examples/src/examples/performance/index.ts +263 -0
- package/starters/examples/src/examples/performance/looks.ts +372 -0
- package/starters/examples/src/examples/performance/meter.ts +158 -0
- package/starters/examples/src/examples/post/grades.ts +86 -0
- package/starters/examples/src/examples/post/index.ts +202 -0
- package/starters/examples/src/examples/post/looks.ts +884 -0
- package/starters/examples/src/examples/post/passes.ts +213 -0
- package/starters/examples/src/examples/rts/ai.ts +49 -0
- package/starters/examples/src/examples/rts/airlift.ts +43 -0
- package/starters/examples/src/examples/rts/base.ts +129 -0
- package/starters/examples/src/examples/rts/build.ts +65 -0
- package/starters/examples/src/examples/rts/camps.ts +36 -0
- package/starters/examples/src/examples/rts/carry.ts +103 -0
- package/starters/examples/src/examples/rts/creeps.ts +61 -0
- package/starters/examples/src/examples/rts/data.ts +406 -0
- package/starters/examples/src/examples/rts/ferry.ts +41 -0
- package/starters/examples/src/examples/rts/garrison.ts +50 -0
- package/starters/examples/src/examples/rts/harvest.ts +29 -0
- package/starters/examples/src/examples/rts/hero.ts +67 -0
- package/starters/examples/src/examples/rts/index.ts +77 -0
- package/starters/examples/src/examples/rts/looks.ts +721 -0
- package/starters/examples/src/examples/rts/shop.ts +46 -0
- package/starters/examples/src/examples/rts/tech.ts +39 -0
- package/starters/examples/src/examples/rts/train.ts +48 -0
- package/starters/examples/src/examples/rts/waterside.ts +185 -0
- package/starters/examples/src/examples/showcase/index.ts +714 -0
- package/starters/examples/src/examples/showcase/looks.ts +238 -0
- package/starters/examples/src/game.ts +17 -0
- package/starters/examples/src/main.ts +82 -0
- package/starters/examples/src/rules.ts +31 -0
- package/starters/examples/src/shell/look.ts +79 -0
- package/starters/examples/src/shell/registry.ts +7 -0
- package/starters/examples/src/shell/scenes.ts +178 -0
- package/starters/examples/src/shell/shell.ts +290 -0
- package/starters/examples/src/shell/style.ts +202 -0
- package/starters/examples/src/view.ts +50 -0
- package/starters/melee/README.md +12 -3
- package/starters/melee/layers/tests/tests/heroes.test.ts +402 -0
- package/starters/melee/layers/tests/tests/rules.test.ts +10 -8
- package/starters/melee/src/game.ts +32 -2
- package/starters/melee/src/maps/bay.names.ts +2 -2
- package/starters/melee/src/maps/bay.vgmap.json +198 -1
- package/starters/melee/src/maps.ts +11 -9
- package/starters/melee/src/melee/abilities.ts +1 -194
- package/starters/melee/src/melee/blight.ts +7 -7
- package/starters/melee/src/melee/cellLayers.json +3 -3
- package/starters/melee/src/melee/data.json +1 -18
- package/starters/melee/src/melee/game.ts +6 -4
- package/starters/melee/src/melee/heroes.ts +1025 -0
- package/starters/melee/src/melee/index.ts +64 -61
- package/starters/melee/src/melee/items.ts +15 -362
- package/starters/melee/src/melee/languages/he.json +14 -2
- package/starters/melee/src/melee/pings.ts +5 -6
- package/starters/melee/src/melee/saves.ts +1 -1
- package/starters/melee/src/melee/undead.ts +15 -230
- package/starters/melee/src/melee/units.ts +37 -171
- package/starters/melee/src/melee/view/attacks.ts +19 -0
- package/starters/melee/src/melee/view/heroSpellLooks.ts +39 -8
- package/starters/melee/src/melee/view/index.ts +56 -52
- package/starters/melee/src/melee/view/looks.ts +86 -63
- package/starters/melee/src/melee/view/undeadLooks.ts +70 -31
- package/starters/melee/src/melee/view/walls.ts +1 -1
- package/starters.mjs +6 -0
- package/v12.mjs +14 -15
- package/starters/campaign/src/rules.ts +0 -20
- package/starters/empty/src/maps.ts +0 -44
- package/starters/empty/src/tiles.ts +0 -12
- package/starters/melee/src/melee/heroSpells.ts +0 -38
- package/starters/melee/src/rules.ts +0 -24
|
@@ -1,4 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
defaultHeroSpellSet,
|
|
3
|
+
defineCellLayer,
|
|
4
|
+
foliagePack,
|
|
5
|
+
type GameMap,
|
|
6
|
+
presetFoliageStyles,
|
|
7
|
+
type Rules,
|
|
8
|
+
hotRules,
|
|
9
|
+
objectiveVictory,
|
|
10
|
+
prefabsFrom,
|
|
11
|
+
layData,
|
|
12
|
+
heroesPack,
|
|
13
|
+
useDefaults,
|
|
14
|
+
type World
|
|
15
|
+
} from '@v12-engine/engine'
|
|
2
16
|
import { ENEMY, PLAYER } from './ids'
|
|
3
17
|
import { mapKeyOf, maps } from './maps'
|
|
4
18
|
import { homecoming, homecomingListeners, homecomingSequences, outpost, redHand, reinforcements } from './missions/homecoming'
|
|
@@ -6,7 +20,7 @@ import { siege, siegeListeners, siegeSequences } from './missions/siege'
|
|
|
6
20
|
import { parley, vargasMen, warlord, warlordListeners, warlordSequences } from './missions/warlord'
|
|
7
21
|
import { countKills, kills } from './kills'
|
|
8
22
|
import { tiles } from './tiles'
|
|
9
|
-
import { abilities, buffs, effects, items, units } from './units'
|
|
23
|
+
import { abilities, buffs, effects, items, itemTags, units } from './units'
|
|
10
24
|
import { nextWave, reportWaves, siegeWaves, waveBeaten, waveComing, waveListeners, waves, wavesFile, waveTimers } from './waves'
|
|
11
25
|
import dataFile from './data.json'
|
|
12
26
|
import watchpost from './prefabs/watchpost.prefab.json'
|
|
@@ -31,75 +45,97 @@ export { builtWaves, readWaves, siegeWaves, type Wave, WAVE_UNITS, wavesFile, wa
|
|
|
31
45
|
* over the tower
|
|
32
46
|
* - missions: objectives, sequences of dialog and cinematics, areas of the map the rules watch, timers, prefabs, and
|
|
33
47
|
* computer players that are mission brains (`@v12-engine/engine/ai`), each listed so that a save finds it by its id
|
|
48
|
+
* - two of the engine's hero spells for the knight (`useDefaults`: of the set, only those he brings; `all` would take
|
|
49
|
+
* the nine, to give heroes in the Data panel), and the engine's item tags its items say
|
|
34
50
|
*/
|
|
35
|
-
export const campaign:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
players: [
|
|
67
|
-
{ id: PLAYER, name: 'Aldric', team: 1, color: [0.2, 0.45, 0.95] },
|
|
68
|
-
{ id: ENEMY, name: 'The Red Hand', team: 2, color: [0.9, 0.2, 0.18], controller: 'computer' }
|
|
51
|
+
export const campaign: Rules = layData(
|
|
52
|
+
useDefaults(
|
|
53
|
+
{
|
|
54
|
+
// the maps of this game say they are its own by this id: change it, and change theirs (`"game"`) with it
|
|
55
|
+
id: 'my-game',
|
|
56
|
+
name: 'My game',
|
|
57
|
+
units,
|
|
58
|
+
abilities,
|
|
59
|
+
buffs,
|
|
60
|
+
items,
|
|
61
|
+
// what its items' tags are called and look like (the shop's filters and search)
|
|
62
|
+
tags: itemTags,
|
|
63
|
+
effects,
|
|
64
|
+
resources: [{ id: 'gold', name: 'Gold', start: 300 }],
|
|
65
|
+
currency: 'gold',
|
|
66
|
+
// its hero, who earns levels and is brought back at an altar, and the creeps' camps (`heroesPack`); the grass of
|
|
67
|
+
// its maps, which hides who stands in the tall of it and burns (`foliagePack`)
|
|
68
|
+
packs: [heroesPack(), foliagePack({ layer: 'grass' })],
|
|
69
|
+
tiles,
|
|
70
|
+
// grass painted on the maps, each value a style of it: a meadow, and tall grass that hides who stands in it.
|
|
71
|
+
// How each looks is the view's (`ground.cellLayers.grass`), by the same names (`presetFoliage`)
|
|
72
|
+
cellLayers: [
|
|
73
|
+
defineCellLayer({
|
|
74
|
+
id: 'grass',
|
|
75
|
+
name: 'Grass',
|
|
76
|
+
color: [0.4, 0.72, 0.28],
|
|
77
|
+
values: [
|
|
78
|
+
{ value: 1, name: 'Meadow', foliage: presetFoliageStyles.meadow },
|
|
79
|
+
{ value: 2, name: 'Tall grass', color: [0.25, 0.5, 0.18], foliage: presetFoliageStyles.tallGrass }
|
|
80
|
+
]
|
|
81
|
+
})
|
|
69
82
|
],
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
// the maps it ships: a save of a game on one keeps only what changed of its ground
|
|
84
|
+
maps: Object.values(maps),
|
|
85
|
+
// the outpost in code, and the designers' prefabs (`prefabs/*.prefab.json`), as the rules may spawn them too
|
|
86
|
+
prefabs: [outpost, ...prefabsFrom({ 'watchpost.prefab.json': watchpost }, { prefix: 'campaign.' })],
|
|
87
|
+
brains: [redHand, vargasMen],
|
|
88
|
+
commands: [nextWave],
|
|
89
|
+
listeners: [...waveListeners, countKills, ...homecomingListeners, ...siegeListeners, ...warlordListeners],
|
|
90
|
+
timers: [...waveTimers, reinforcements],
|
|
91
|
+
files: [wavesFile],
|
|
92
|
+
sequences: [...homecomingSequences, ...siegeSequences, ...warlordSequences],
|
|
93
|
+
// what it keeps of its own: the waves' state, each tower's kills (sent to everyone, pinned over it by the view), and
|
|
94
|
+
// the answer at Varga's gate
|
|
95
|
+
components: [waves, kills, parley],
|
|
96
|
+
events: [waveBeaten, waveComing],
|
|
97
|
+
editor: {
|
|
98
|
+
// the player and the Red Hand, always: the editor sets where they start, not who they are
|
|
99
|
+
players: [
|
|
100
|
+
{ id: PLAYER, name: 'Aldric', team: 1, color: [0.2, 0.45, 0.95] },
|
|
101
|
+
{ id: ENEMY, name: 'The Red Hand', team: 2, color: [0.9, 0.2, 0.18], controller: 'computer' }
|
|
102
|
+
],
|
|
103
|
+
playersNote: 'Aldric is you. The Red Hand is the computer, as each mission says.',
|
|
104
|
+
// where the game names a unit type beyond the map: the waves that send it (Used by, Find references)
|
|
105
|
+
references: (ref, _map, registry) =>
|
|
106
|
+
'type' in ref
|
|
107
|
+
? siegeWaves({ registry }).flatMap((wave, i) =>
|
|
108
|
+
wave.units.some((unit) => unit.type === ref.type) ? [{ what: `wave ${i + 1}`, where: 'waves.json' }] : []
|
|
109
|
+
)
|
|
110
|
+
: []
|
|
111
|
+
},
|
|
112
|
+
victory: objectiveVictory,
|
|
113
|
+
report: (world) => reportWaves(world),
|
|
114
|
+
// a save made before a patch is loaded after it (`saves.migrate` takes the step)
|
|
115
|
+
saves: { version: 1 },
|
|
116
|
+
// what the editor's Data panel may change, kept in `data.json`: numbers and prices, never what a unit is
|
|
117
|
+
editable: {
|
|
118
|
+
units: (def) =>
|
|
119
|
+
def.kind === 'building'
|
|
120
|
+
? ['name', { path: 'stats.health', min: 1 }, 'stats.armor', { path: 'cost.gold', kind: 'integer', min: 0 }, 'buildTime']
|
|
121
|
+
: def.kind === 'unit'
|
|
122
|
+
? [
|
|
123
|
+
'name',
|
|
124
|
+
{ path: 'stats.health', min: 1 },
|
|
125
|
+
'stats.attackDamage',
|
|
126
|
+
'stats.attackSpeed',
|
|
127
|
+
'stats.armor',
|
|
128
|
+
'stats.moveSpeed',
|
|
129
|
+
{ path: 'cost.gold', kind: 'integer', min: 0 }
|
|
130
|
+
]
|
|
131
|
+
: null,
|
|
132
|
+
abilities: [{ path: 'cooldown', min: 0 }, { path: 'manaCost', min: 0 }, 'description'],
|
|
133
|
+
items: [{ path: 'cost.gold', kind: 'integer', min: 0 }, 'description']
|
|
134
|
+
}
|
|
78
135
|
},
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
saves: { version: 1 },
|
|
83
|
-
// what the editor's Data panel may change, kept in `data.json`: numbers and prices, never what a unit is
|
|
84
|
-
editable: {
|
|
85
|
-
units: (def) =>
|
|
86
|
-
def.kind === 'building'
|
|
87
|
-
? ['name', { path: 'stats.health', min: 1 }, 'stats.armor', { path: 'cost.gold', kind: 'integer', min: 0 }, 'buildTime']
|
|
88
|
-
: def.kind === 'unit'
|
|
89
|
-
? [
|
|
90
|
-
'name',
|
|
91
|
-
{ path: 'stats.health', min: 1 },
|
|
92
|
-
'stats.attackDamage',
|
|
93
|
-
'stats.attackSpeed',
|
|
94
|
-
'stats.armor',
|
|
95
|
-
'stats.moveSpeed',
|
|
96
|
-
{ path: 'cost.gold', kind: 'integer', min: 0 }
|
|
97
|
-
]
|
|
98
|
-
: null,
|
|
99
|
-
abilities: [{ path: 'cooldown', min: 0 }, { path: 'manaCost', min: 0 }, 'description'],
|
|
100
|
-
items: [{ path: 'cost.gold', kind: 'integer', min: 0 }, 'description']
|
|
101
|
-
}
|
|
102
|
-
},
|
|
136
|
+
[defaultHeroSpellSet()],
|
|
137
|
+
{ heroSpells: { only: ['barrier', 'purify'] } }
|
|
138
|
+
),
|
|
103
139
|
dataFile
|
|
104
140
|
)
|
|
105
141
|
|
|
@@ -3,7 +3,7 @@ import { defineComponent, defineListener } from '@v12-engine/engine'
|
|
|
3
3
|
import { ENEMY, PLAYER } from './ids'
|
|
4
4
|
|
|
5
5
|
/** How many of the Red Hand a tower of the fort has finished off. Everyone sees it: the view puts it over the tower */
|
|
6
|
-
export const kills = defineComponent<number>('campaign.kills',
|
|
6
|
+
export const kills = defineComponent<number>({ id: 'campaign.kills', replicate: 'everyone' })
|
|
7
7
|
|
|
8
8
|
/** One of the Red Hand that dies counts for the building of the player's that killed it */
|
|
9
9
|
export const countKills = defineListener({
|
|
@@ -65,18 +65,18 @@ export const redHand = missionBrain({
|
|
|
65
65
|
})
|
|
66
66
|
|
|
67
67
|
/** Every so often, two raiders more at the camp: the brain sends them */
|
|
68
|
-
export const reinforcements = defineTimer('campaign.reinforcements', (world) => {
|
|
68
|
+
export const reinforcements = defineTimer({ id: 'campaign.reinforcements', run: (world) => {
|
|
69
69
|
if (world.winner !== null) return
|
|
70
70
|
const camp = world.map.point('camp')
|
|
71
71
|
if (!enemyBuildings(world).length) return
|
|
72
72
|
for (const dx of [-1.5, 1.5]) world.spawn(UNIT.raider, ENEMY, { x: camp.x + dx, z: camp.z + 1 })
|
|
73
|
-
})
|
|
73
|
+
} })
|
|
74
74
|
|
|
75
75
|
/** The Red Hand's buildings, standing */
|
|
76
76
|
export const enemyBuildings = (world: World) => [...world.index.ownedBy(ENEMY)].filter((entity) => entity.alive && entity.def.kind === 'building')
|
|
77
77
|
|
|
78
78
|
/** The intro, then Brann's briefing: a sequence, skippable a line at a time, saved halfway like everything else */
|
|
79
|
-
export const briefing = defineSequence('campaign.briefing', [
|
|
79
|
+
export const briefing = defineSequence({ id: 'campaign.briefing', steps: [
|
|
80
80
|
// the screen lets the first tick pass before it draws: the intro is the first thing seen, the game waiting
|
|
81
81
|
wait(0),
|
|
82
82
|
cinematic('campaign.intro', { hold: true, wait: 'ended' }),
|
|
@@ -85,17 +85,17 @@ export const briefing = defineSequence('campaign.briefing', [
|
|
|
85
85
|
{ speaker: BRANN, text: MSG.homeStart, seconds: 5 },
|
|
86
86
|
{ speaker: ALDRIC, text: MSG.homeCamp, seconds: 5 }
|
|
87
87
|
])
|
|
88
|
-
])
|
|
88
|
+
] })
|
|
89
89
|
|
|
90
90
|
/** At the ford, a word, and the camp shown for a moment: whoever of the player's walks in first */
|
|
91
|
-
export const atTheFord = defineSequence('campaign.atTheFord', [
|
|
91
|
+
export const atTheFord = defineSequence({ id: 'campaign.atTheFord', steps: [
|
|
92
92
|
waitFor(enteredArea, (_world, { area, entity }) => area === FORD && entity.owner === PLAYER),
|
|
93
93
|
(world) => {
|
|
94
94
|
const camp = world.map.point('camp')
|
|
95
95
|
world.reveal(world.player(PLAYER).team, camp, 12, 10)
|
|
96
96
|
},
|
|
97
97
|
...talk(PLAYER, [{ speaker: BRANN, text: MSG.homeFord, seconds: 5 }])
|
|
98
|
-
])
|
|
98
|
+
] })
|
|
99
99
|
|
|
100
100
|
/** The caravan found: its gold, and the optional objective done */
|
|
101
101
|
export const caravanFound = defineListener({
|
|
@@ -130,7 +130,7 @@ export const buildingFell = defineListener({
|
|
|
130
130
|
}
|
|
131
131
|
})
|
|
132
132
|
|
|
133
|
-
const wellDone = defineSequence('campaign.homeDone', talk(PLAYER, [{ speaker: ALDRIC, text: MSG.homeDone, seconds: 4 }]))
|
|
133
|
+
const wellDone = defineSequence({ id: 'campaign.homeDone', steps: talk(PLAYER, [{ speaker: ALDRIC, text: MSG.homeDone, seconds: 4 }]) })
|
|
134
134
|
|
|
135
135
|
export const homecomingSequences = [briefing, atTheFord, wellDone]
|
|
136
136
|
export const homecomingListeners = [caravanFound, buildingFell]
|
|
@@ -18,13 +18,13 @@ export const keepTowers = defineObjective({ id: 'campaign.towers', text: MSG.obj
|
|
|
18
18
|
export const GATE = 'gate'
|
|
19
19
|
|
|
20
20
|
/** The briefing: what is coming, and what the gate does */
|
|
21
|
-
export const siegeBriefing = defineSequence('campaign.siegeBriefing', [
|
|
21
|
+
export const siegeBriefing = defineSequence({ id: 'campaign.siegeBriefing', steps: [
|
|
22
22
|
wait(0.5),
|
|
23
23
|
...talk(PLAYER, [
|
|
24
24
|
{ speaker: BRANN, text: MSG.siegeStart, seconds: 5 },
|
|
25
25
|
{ speaker: ALDRIC, text: MSG.siegeGate, seconds: 5 }
|
|
26
26
|
])
|
|
27
|
-
])
|
|
27
|
+
] })
|
|
28
28
|
|
|
29
29
|
/** A wave sets out: the gate shuts, the camp flares (an effect fired by the rules, seen by every screen) */
|
|
30
30
|
export const waveSetsOut = defineListener({
|
|
@@ -32,13 +32,13 @@ export const waveSetsOut = defineListener({
|
|
|
32
32
|
id: 'campaign.waveSetsOut',
|
|
33
33
|
run(world, { x, z }) {
|
|
34
34
|
if (!world.objectives.has(PLAYER, holdOut.id)) return
|
|
35
|
-
world.
|
|
35
|
+
world.fx('spark-burst', { at: { x, z }, params: { size: 2 } })
|
|
36
36
|
if (setGate(world, GATE, false)) world.message(PLAYER, MSG.gateShut, 'info')
|
|
37
37
|
}
|
|
38
38
|
})
|
|
39
39
|
|
|
40
|
-
const halfway = defineSequence('campaign.siegeHalfway', talk(PLAYER, [{ speaker: BRANN, text: MSG.siegeHalfway, seconds: 4 }]))
|
|
41
|
-
const beforeTheLast = defineSequence('campaign.siegeLast', talk(PLAYER, [{ speaker: ALDRIC, text: MSG.siegeLast, seconds: 5 }]))
|
|
40
|
+
const halfway = defineSequence({ id: 'campaign.siegeHalfway', steps: talk(PLAYER, [{ speaker: BRANN, text: MSG.siegeHalfway, seconds: 4 }]) })
|
|
41
|
+
const beforeTheLast = defineSequence({ id: 'campaign.siegeLast', steps: talk(PLAYER, [{ speaker: ALDRIC, text: MSG.siegeLast, seconds: 5 }]) })
|
|
42
42
|
|
|
43
43
|
/** A wave beaten: the gate opens, the objective moves along; after the last, the mission is won */
|
|
44
44
|
export const waveHeld = defineListener({
|
|
@@ -66,7 +66,7 @@ export const fortBuildingFell = defineListener({
|
|
|
66
66
|
id: 'campaign.fortBuildingFell',
|
|
67
67
|
run(world, { entity }) {
|
|
68
68
|
if (entity.owner !== PLAYER || entity.def.kind !== 'building' || !world.objectives.has(PLAYER, holdOut.id)) return
|
|
69
|
-
world.
|
|
69
|
+
world.fx('explosion', { at: { x: entity.pos.x, z: entity.pos.z }, params: { size: entity.def.id === UNIT.wall ? 0.8 : 1.5 } })
|
|
70
70
|
if (entity.def.id === UNIT.tower) world.objectives.fail(PLAYER, keepTowers.id)
|
|
71
71
|
if (entity.def.id === UNIT.hall) world.objectives.fail(PLAYER, keepTheHall.id, MSG.hallFell)
|
|
72
72
|
}
|
|
@@ -44,7 +44,7 @@ export const PARLEY_CHOICE = 'parley'
|
|
|
44
44
|
* What the player answered at the gate, kept in the world (so a save keeps it): what the campaign carries on as the
|
|
45
45
|
* mission is won (`carry.ts`). Nothing for a talk skipped, or not had yet
|
|
46
46
|
*/
|
|
47
|
-
export const parley = defineComponent<{ choice?: 'duel' | 'charge' }>('campaign.parley',
|
|
47
|
+
export const parley = defineComponent<{ choice?: 'duel' | 'charge' }>({ id: 'campaign.parley', default: () => ({}) })
|
|
48
48
|
|
|
49
49
|
/** Varga's men: they hold the hold, and his guard stays by him */
|
|
50
50
|
export const vargasMen = missionBrain({
|
|
@@ -53,27 +53,27 @@ export const vargasMen = missionBrain({
|
|
|
53
53
|
})
|
|
54
54
|
|
|
55
55
|
/** The briefing, and the trader pointed out */
|
|
56
|
-
export const passBriefing = defineSequence('campaign.passBriefing', [
|
|
56
|
+
export const passBriefing = defineSequence({ id: 'campaign.passBriefing', steps: [
|
|
57
57
|
wait(0.5),
|
|
58
58
|
...talk(PLAYER, [
|
|
59
59
|
{ speaker: BRANN, text: MSG.passStart, seconds: 5 },
|
|
60
60
|
{ speaker: BRANN, text: MSG.passShop, seconds: 5 }
|
|
61
61
|
])
|
|
62
|
-
])
|
|
62
|
+
] })
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
65
|
* At the gate: the talk, as a cinematic the rules play for the player (`playCinematic`, handed who talks), the game
|
|
66
66
|
* held while it plays. The choice in it comes back as a command of the player's (`cinematicChoice`), which
|
|
67
67
|
* `theChoice` hears; a cinematic with no data to hand over is the `cinematic` step, waited for or not
|
|
68
68
|
*/
|
|
69
|
-
export const atTheGate = defineSequence('campaign.atTheGate', [
|
|
69
|
+
export const atTheGate = defineSequence({ id: 'campaign.atTheGate', steps: [
|
|
70
70
|
waitFor(enteredArea, (_world, { area, entity }) => area === GATE && entity.owner === PLAYER),
|
|
71
71
|
(world) => {
|
|
72
72
|
const hero = world.named('hero')
|
|
73
73
|
const warlord = world.named('warlord')
|
|
74
74
|
playCinematic(world, PARLEY, { to: PLAYER, hold: true, params: { hero: hero?.id, warlord: warlord?.id } })
|
|
75
75
|
}
|
|
76
|
-
])
|
|
76
|
+
] })
|
|
77
77
|
|
|
78
78
|
/** Varga's guards, by the names the map gives them */
|
|
79
79
|
const guards = (world: World) => ['guard1', 'guard2', 'guard3'].flatMap((name) => world.named(name) ?? [])
|
|
@@ -95,15 +95,14 @@ export const theChoice = defineListener({
|
|
|
95
95
|
if (warlord?.alive) applyBuff(world, warlord, 'rallied', 60)
|
|
96
96
|
world.sequences.start(duel)
|
|
97
97
|
} else {
|
|
98
|
-
if (hero?.alive)
|
|
99
|
-
for (const man of guards(world)) world.issue({ type: 'order', player: ENEMY, entities: [man.id], order: { type: 'attack', target: hero.id } })
|
|
98
|
+
if (hero?.alive) for (const man of guards(world)) man.order('attack', hero)
|
|
100
99
|
world.sequences.start(charge)
|
|
101
100
|
}
|
|
102
101
|
}
|
|
103
102
|
})
|
|
104
103
|
|
|
105
|
-
const duel = defineSequence('campaign.duel', talk(PLAYER, [{ speaker: ALDRIC, text: MSG.passDuel, seconds: 4 }]))
|
|
106
|
-
const charge = defineSequence('campaign.charge', talk(PLAYER, [{ speaker: BRANN, text: MSG.passCharge, seconds: 4 }]))
|
|
104
|
+
const duel = defineSequence({ id: 'campaign.duel', steps: talk(PLAYER, [{ speaker: ALDRIC, text: MSG.passDuel, seconds: 4 }]) })
|
|
105
|
+
const charge = defineSequence({ id: 'campaign.charge', steps: talk(PLAYER, [{ speaker: BRANN, text: MSG.passCharge, seconds: 4 }]) })
|
|
107
106
|
|
|
108
107
|
/** The prisoners' cells reached: they are the player's from then on */
|
|
109
108
|
export const cellsOpened = defineListener({
|
|
@@ -119,13 +118,13 @@ export const cellsOpened = defineListener({
|
|
|
119
118
|
}
|
|
120
119
|
})
|
|
121
120
|
|
|
122
|
-
const freed = defineSequence('campaign.freed', talk(PLAYER, [{ speaker: BRANN, text: MSG.passPrisoners, seconds: 4 }]))
|
|
121
|
+
const freed = defineSequence({ id: 'campaign.freed', steps: talk(PLAYER, [{ speaker: BRANN, text: MSG.passPrisoners, seconds: 4 }]) })
|
|
123
122
|
|
|
124
123
|
/**
|
|
125
124
|
* Varga falls: the outro plays, the game held while it does, and then it is won (the end screen comes once the
|
|
126
125
|
* outro is over, as the game stands still under it). Aldric falls: it is lost
|
|
127
126
|
*/
|
|
128
|
-
export const theEnd = defineSequence('campaign.theEnd', [
|
|
127
|
+
export const theEnd = defineSequence({ id: 'campaign.theEnd', steps: [
|
|
129
128
|
waitFor(entityDied, (_world, { entity }) => entity.def.id === UNIT.warlord),
|
|
130
129
|
wait(1.5),
|
|
131
130
|
cinematic('campaign.outro', { hold: true }),
|
|
@@ -136,7 +135,7 @@ export const theEnd = defineSequence('campaign.theEnd', [
|
|
|
136
135
|
objectives.complete(PLAYER, keepHero.id)
|
|
137
136
|
if (objectives.get(PLAYER, freePrisoners.id).status === 'open') objectives.fail(PLAYER, freePrisoners.id)
|
|
138
137
|
}
|
|
139
|
-
])
|
|
138
|
+
] })
|
|
140
139
|
|
|
141
140
|
export const heroFell = defineListener({
|
|
142
141
|
event: entityDied,
|
|
@@ -168,8 +167,8 @@ export function warlord(world: World, fresh: boolean, difficulty = 'normal'): vo
|
|
|
168
167
|
const purse = world.player(ENEMY).resources
|
|
169
168
|
for (const key of Object.keys(purse)) purse[key] = 0
|
|
170
169
|
const varga = world.named('warlord')
|
|
171
|
-
if (varga && difficulty === 'easy') varga.
|
|
172
|
-
if (varga && difficulty === 'hard') world.spawn(UNIT.brute,
|
|
170
|
+
if (varga && difficulty === 'easy') varga.health = Math.round(varga.maxHealth * 0.75)
|
|
171
|
+
if (varga && difficulty === 'hard') world.spawn(UNIT.brute, { at: { x: varga.pos.x + 2, z: varga.pos.z + 1 }, owner: ENEMY, name: 'guard4' })
|
|
173
172
|
world.computers.seat(ENEMY, { brain: vargasMen })
|
|
174
173
|
world.sequences.start(passBriefing)
|
|
175
174
|
world.sequences.start(atTheGate)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineCampaign } from '@v12-engine/client/app'
|
|
2
|
+
import { campaignGame } from './game'
|
|
2
3
|
import campaignFile from './campaign.json'
|
|
3
|
-
import {
|
|
4
|
+
import { campaignMap, homecoming, homecomingCarry, type RoadData, siege, siegeCarriedIn, warlord, warlordCarry } from './index'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* The campaign of three missions, Aldric carried from one to the next: `defineCampaign` gives the menu (where the
|
|
@@ -13,9 +14,9 @@ import { campaign, campaignMap, homecoming, homecomingCarry, type RoadData, sieg
|
|
|
13
14
|
*/
|
|
14
15
|
export const theLongRoad = defineCampaign<RoadData>({
|
|
15
16
|
file: campaignFile,
|
|
16
|
-
game: campaign
|
|
17
|
-
//
|
|
18
|
-
|
|
17
|
+
// the game (`./game.ts`): its rules, and its look (and the client that draws it) fetched as the campaign's menu
|
|
18
|
+
// opens, not part of the start screen's code
|
|
19
|
+
game: campaignGame,
|
|
19
20
|
// a mission's map by the name the file gives it: `homestead` is `src/maps/homestead.vgmap.json`
|
|
20
21
|
maps: campaignMap,
|
|
21
22
|
missions: [
|
|
@@ -23,13 +24,13 @@ export const theLongRoad = defineCampaign<RoadData>({
|
|
|
23
24
|
id: 'homecoming',
|
|
24
25
|
// its debrief's words are the file's; its rating, by how quick it was, is code
|
|
25
26
|
debrief: { rating: ({ seconds }) => (seconds < 600 ? 3 : seconds < 1200 ? 2 : 1) },
|
|
26
|
-
|
|
27
|
+
onStart: (world, { fresh }) => homecoming(world, fresh),
|
|
27
28
|
// the footmen who live through it, and whether the caravan was found
|
|
28
29
|
carry: (world) => homecomingCarry(world)
|
|
29
30
|
},
|
|
30
31
|
{
|
|
31
32
|
id: 'siege',
|
|
32
|
-
|
|
33
|
+
onStart: (world, { fresh, map, progress }) => {
|
|
33
34
|
siege(world, fresh)
|
|
34
35
|
// the footmen of Homecoming man the fort; a game loaded from a save has them already
|
|
35
36
|
if (fresh) siegeCarriedIn(world, map, progress.data)
|
|
@@ -38,7 +39,7 @@ export const theLongRoad = defineCampaign<RoadData>({
|
|
|
38
39
|
{
|
|
39
40
|
id: 'warlord',
|
|
40
41
|
// harder or easier with the campaign's difficulty, chosen in its menu
|
|
41
|
-
|
|
42
|
+
onStart: (world, { fresh, difficulty }) => warlord(world, fresh, difficulty),
|
|
42
43
|
// the answer at the gate: the last mission, so the campaign keeps it for whatever comes after
|
|
43
44
|
carry: (world) => warlordCarry(world)
|
|
44
45
|
}
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import type { EffectFields } from '@v12-engine/engine'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type AbilityDef,
|
|
4
|
+
type BuffDef,
|
|
5
|
+
defaultItemTags,
|
|
6
|
+
defineEffect,
|
|
7
|
+
effectAmount,
|
|
8
|
+
type EffectType,
|
|
9
|
+
type ItemDef,
|
|
10
|
+
type TagDef,
|
|
11
|
+
type UnitTypeDef
|
|
12
|
+
} from '@v12-engine/engine'
|
|
3
13
|
import { UNIT } from './ids'
|
|
4
14
|
|
|
5
15
|
// the game's own effects, each with the fields it reads (`EffectTypes`)
|
|
@@ -62,9 +72,9 @@ export const archer: UnitTypeDef = {
|
|
|
62
72
|
}
|
|
63
73
|
|
|
64
74
|
/**
|
|
65
|
-
* The hero: Aldric, the map names him `hero`. He grows with every level (`hero.growth`),
|
|
66
|
-
* his skill points, carries six items, and goes from one mission to
|
|
67
|
-
* him: `carryOut`, `carryIn`)
|
|
75
|
+
* The hero: Aldric, the map names him `hero`. He grows with every level (`hero.growth`), is a Vanguard and a Keeper
|
|
76
|
+
* (`hero.callings`), learns three abilities with his skill points, carries six items, and goes from one mission to
|
|
77
|
+
* the next with all of it (the campaign carries him: `carryOut`, `carryIn`)
|
|
68
78
|
*/
|
|
69
79
|
export const knight: UnitTypeDef = {
|
|
70
80
|
id: UNIT.knight,
|
|
@@ -86,7 +96,12 @@ export const knight: UnitTypeDef = {
|
|
|
86
96
|
attacks: [{ damagePoint: 0.4, acquisitionRange: 8 }],
|
|
87
97
|
hero: {
|
|
88
98
|
growth: { stats: { health: 80, mana: 30, attackDamage: 3, armor: 3 } },
|
|
99
|
+
// what he is good at (the engine's callings, `defaultCallings`): in front, taking the blows; keeping his men up
|
|
100
|
+
callings: { vanguard: 3, keeper: 2 },
|
|
89
101
|
learns: ['holyLight', 'warCry', 'judgement'],
|
|
102
|
+
// two of the engine's hero spells, on D and F (the set taken in `index.ts`, `defaultHeroSpellSet`): a shield, and
|
|
103
|
+
// a way out of a stun
|
|
104
|
+
spells: ['barrier', 'purify'],
|
|
90
105
|
maxLevel: 10
|
|
91
106
|
},
|
|
92
107
|
inventory: 6,
|
|
@@ -274,7 +289,7 @@ export const goldmine: UnitTypeDef = {
|
|
|
274
289
|
stats: { health: 1 },
|
|
275
290
|
sight: 0,
|
|
276
291
|
invulnerable: true,
|
|
277
|
-
resource: {
|
|
292
|
+
resource: { id: 'gold', amount: 8000, slots: 1, entered: true }
|
|
278
293
|
}
|
|
279
294
|
|
|
280
295
|
export const units = [
|
|
@@ -304,23 +319,24 @@ export const buffs: BuffDef[] = [
|
|
|
304
319
|
{ id: 'rallied', name: 'Rallied', description: 'Hits 20% harder and 15% faster', percent: { attackDamage: 0.2, attackSpeed: 0.15 } }
|
|
305
320
|
]
|
|
306
321
|
|
|
307
|
-
/** A number a level (`Scaled`): the level's, or the last for a level past the list */
|
|
308
|
-
const atLevel = (amount: Scaled | undefined, level: number): number =>
|
|
309
|
-
Array.isArray(amount) ? amount[Math.min(level, amount.length) - 1] : (amount ?? 0)
|
|
310
|
-
|
|
311
322
|
/**
|
|
312
323
|
* Effects the engine does not have, by the type an ability's effect names (`effects` of the module): a function given
|
|
313
324
|
* the caster, the target and the level, with the world's own damage and healing, so that armour, death, bounty and
|
|
314
325
|
* experience happen as for any hit
|
|
315
326
|
*/
|
|
316
|
-
export const effects:
|
|
327
|
+
export const effects: EffectType[] = [
|
|
317
328
|
/** Judgement: the enemy struck, and the caster healed for what it took */
|
|
318
|
-
defineEffect(
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
329
|
+
defineEffect({
|
|
330
|
+
type: 'campaign.judgement',
|
|
331
|
+
run: (context, effect) => {
|
|
332
|
+
const { world, caster, target } = context
|
|
333
|
+
if (!target?.alive) return
|
|
334
|
+
// its amount at its level, with what the caster's stats add, as the engine's damage works it out
|
|
335
|
+
const amount = effectAmount(context, effect, target)
|
|
336
|
+
const before = target.health
|
|
337
|
+
world.damage(target, amount, caster, { damageType: 'magic' })
|
|
338
|
+
world.heal(caster, Math.max(0, before - Math.max(target.health, 0)), caster)
|
|
339
|
+
}
|
|
324
340
|
})
|
|
325
341
|
]
|
|
326
342
|
|
|
@@ -375,7 +391,10 @@ export const abilities: AbilityDef[] = [
|
|
|
375
391
|
}
|
|
376
392
|
]
|
|
377
393
|
|
|
378
|
-
/**
|
|
394
|
+
/**
|
|
395
|
+
* What the trader sells (`trader.sells`): each with its tags (what the shop's search and filters find it by) and the
|
|
396
|
+
* callings it suits (what the shop puts first for the knight)
|
|
397
|
+
*/
|
|
379
398
|
export const items: ItemDef[] = [
|
|
380
399
|
{
|
|
381
400
|
id: 'healingPotion',
|
|
@@ -383,8 +402,28 @@ export const items: ItemDef[] = [
|
|
|
383
402
|
description: 'Drunk, heals 200. Three in the bottle',
|
|
384
403
|
cost: { gold: 60 },
|
|
385
404
|
charges: 3,
|
|
386
|
-
ability: 'drinkPotion'
|
|
405
|
+
ability: 'drinkPotion',
|
|
406
|
+
tier: 'consumable',
|
|
407
|
+
tags: ['consumable', 'health']
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
id: 'steelSword',
|
|
411
|
+
name: 'Steel Sword',
|
|
412
|
+
description: '+12 attack damage',
|
|
413
|
+
cost: { gold: 180 },
|
|
414
|
+
bonus: { stats: { attackDamage: 12 } },
|
|
415
|
+
tags: ['attack'],
|
|
416
|
+
suits: ['vanguard']
|
|
387
417
|
},
|
|
388
|
-
{
|
|
389
|
-
|
|
418
|
+
{
|
|
419
|
+
id: 'swiftBoots',
|
|
420
|
+
name: 'Swift Boots',
|
|
421
|
+
description: '+0.8 move speed',
|
|
422
|
+
cost: { gold: 120 },
|
|
423
|
+
bonus: { stats: { moveSpeed: 0.8 } },
|
|
424
|
+
tags: ['movement']
|
|
425
|
+
}
|
|
390
426
|
]
|
|
427
|
+
|
|
428
|
+
/** The tags its items say, named and coloured: the engine's own (`defaultItemTags`), those its items use */
|
|
429
|
+
export const itemTags: TagDef[] = defaultItemTags.filter((tag) => items.some((item) => item.tags?.includes(tag.id)))
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AttackLook } from '@v12-engine/client'
|
|
2
|
+
|
|
3
|
+
// The swings of the shared characters' rig (`UnitLook.attacks`): several each, so a fight does not play the same blow
|
|
4
|
+
// over and over. At random, never the same twice running; a critical strike ends in a big two-handed slice
|
|
5
|
+
|
|
6
|
+
/** A one-handed weapon's swings: a chop, a slice, a stab */
|
|
7
|
+
export const ONE_HANDED: AttackLook = {
|
|
8
|
+
clip: ['Melee_1H_Attack_Chop', 'Melee_1H_Attack_Slice_Diagonal', 'Melee_1H_Attack_Stab'],
|
|
9
|
+
order: 'random',
|
|
10
|
+
crit: 'Melee_2H_Attack_Slice'
|
|
11
|
+
}
|
|
12
|
+
/** A two-handed weapon's: a chop and a slice, in turn */
|
|
13
|
+
export const TWO_HANDED: AttackLook = { clip: ['Melee_2H_Attack_Chop', 'Melee_2H_Attack_Slice'], crit: 'Melee_2H_Attack_Chop' }
|
|
14
|
+
/** Two weapons': both together, and each alone */
|
|
15
|
+
export const DUAL_WIELD: AttackLook = {
|
|
16
|
+
clip: ['Melee_Dualwield_Attack_Chop', 'Melee_1H_Attack_Slice_Diagonal', 'Melee_1H_Attack_Stab'],
|
|
17
|
+
order: 'random',
|
|
18
|
+
crit: 'Melee_Dualwield_Attack_Chop'
|
|
19
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Vector3 } from '@babylonjs/core/Maths/math.vector'
|
|
2
|
-
import { type
|
|
2
|
+
import { type FxHandle, type Rgb, Sparks, type SpellStyleFactory } from '@v12-engine/client'
|
|
3
3
|
|
|
4
4
|
/*
|
|
5
5
|
* A spell style of the campaign's own (`styles` in the view, `style: 'campaign.holyFire'` on Judgement's look), as an
|
|
@@ -24,7 +24,7 @@ export const holyFire: SpellStyleFactory = (context) => {
|
|
|
24
24
|
const sparks = new Sparks(context.fx)
|
|
25
25
|
/** What is still to happen, by the time it happens at (`context.time`) */
|
|
26
26
|
const later: { at: number; run: () => void }[] = []
|
|
27
|
-
const burning: { until: number; stream:
|
|
27
|
+
const burning: { until: number; stream: FxHandle }[] = []
|
|
28
28
|
|
|
29
29
|
const groundAt = (x: number, z: number) => context.terrain.heightAt(x, z)
|
|
30
30
|
|