@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
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The heroes, tested with no page on a small flat map: their callings and innates, what each of their abilities does,
|
|
3
|
+
* the altars that train them and the tavern that hires them
|
|
4
|
+
*/
|
|
5
|
+
import {
|
|
6
|
+
applyBuff,
|
|
7
|
+
callingsOf,
|
|
8
|
+
checkRules,
|
|
9
|
+
damaged,
|
|
10
|
+
type Entity,
|
|
11
|
+
fighting,
|
|
12
|
+
hasBuff,
|
|
13
|
+
heroOf,
|
|
14
|
+
hire,
|
|
15
|
+
isPhasing,
|
|
16
|
+
learn,
|
|
17
|
+
MAP_CREEPS,
|
|
18
|
+
setHeroLevel,
|
|
19
|
+
shieldOf,
|
|
20
|
+
statsOf,
|
|
21
|
+
type World
|
|
22
|
+
} from '@v12-engine/engine'
|
|
23
|
+
import { run, TEST_PLAYERS, testWorld } from '@v12-engine/engine/testing'
|
|
24
|
+
import { describe, expect, it } from 'vitest'
|
|
25
|
+
import { game as melee } from '../src/game'
|
|
26
|
+
import { harvested, heroes, HOLLOW_HEROES, KINGDOM_HEROES } from '../src/melee/heroes'
|
|
27
|
+
|
|
28
|
+
const rules = melee.toRules()
|
|
29
|
+
|
|
30
|
+
const ME = 1
|
|
31
|
+
const FOE = 2
|
|
32
|
+
const NEUTRAL = 0
|
|
33
|
+
/** The camps' creeps: everyone's enemy */
|
|
34
|
+
const CREEPS = MAP_CREEPS
|
|
35
|
+
|
|
36
|
+
/** What each hero is, as the plan has it: its callings and its innate */
|
|
37
|
+
const PLANNED: Record<string, { callings: Record<string, number>; innate: string }> = {
|
|
38
|
+
oathbreaker: { callings: { vanguard: 3, keeper: 1 }, innate: 'bulwark' },
|
|
39
|
+
dawnPriest: { callings: { keeper: 3, arcanist: 1 }, innate: 'mercysEcho' },
|
|
40
|
+
stormcaller: { callings: { arcanist: 3, binder: 2 }, innate: 'staticCharge' },
|
|
41
|
+
galeHunter: { callings: { ranger: 3, roamer: 1 }, innate: 'steadyAim' },
|
|
42
|
+
gravelord: { callings: { reaver: 3, vanguard: 1, keeper: 1 }, innate: 'bloodTithe' },
|
|
43
|
+
rimeWitch: { callings: { binder: 3, arcanist: 2 }, innate: 'shatter' },
|
|
44
|
+
duskblade: { callings: { stalker: 3, roamer: 2 }, innate: 'shadowStep' },
|
|
45
|
+
boneReaver: { callings: { roamer: 3, reaver: 2 }, innate: 'harvest' }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function arena(): Promise<World> {
|
|
49
|
+
const world = await testWorld(rules, {
|
|
50
|
+
size: 64,
|
|
51
|
+
players: [...TEST_PLAYERS.map((player) => ({ ...player })), { id: CREEPS, name: 'Creeps', team: 1000, color: [0.42, 0.36, 0.3], creeps: true }]
|
|
52
|
+
})
|
|
53
|
+
for (const player of [ME, FOE]) world.player(player).resources.gold = 10000
|
|
54
|
+
return world
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Picks no fight of its own: what it does is the test's */
|
|
58
|
+
function calm(unit: Entity): Entity {
|
|
59
|
+
unit.get(fighting)!.passive = true
|
|
60
|
+
return unit
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** A hero of a type at a level, every point it has put into what it learns, in order, full of mana; picking no fights */
|
|
64
|
+
function hero(world: World, type: string, owner = ME, at = { x: 30, z: 30 }, level = 10): Entity {
|
|
65
|
+
const made = calm(world.spawn(type, owner, at))
|
|
66
|
+
setHeroLevel(world, made, level)
|
|
67
|
+
for (let round = 0; round < 3; round++) for (const id of made.def.hero!.learns) learn(world, made, id)
|
|
68
|
+
made.mana = made.maxMana
|
|
69
|
+
return made
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Units of the enemy's that stand where they are put and pick no fights: what the abilities are tried on */
|
|
73
|
+
const target = (world: World, at: { x: number; z: number }, type = 'footman', owner = FOE): Entity => calm(world.spawn(type, owner, at))
|
|
74
|
+
|
|
75
|
+
/** A tick on, so that a player knows of what was just made and may aim at it */
|
|
76
|
+
const seen = (world: World) => run(world, 0.1)
|
|
77
|
+
|
|
78
|
+
const cast = (world: World, caster: Entity, ability: string, aim: { target?: Entity; point?: { x: number; z: number } } = {}) =>
|
|
79
|
+
world.issue({
|
|
80
|
+
type: 'cast',
|
|
81
|
+
player: caster.owner,
|
|
82
|
+
entities: [caster.id],
|
|
83
|
+
ability,
|
|
84
|
+
...(aim.target && { target: aim.target.id }),
|
|
85
|
+
...(aim.point && { point: { x: aim.point.x, z: aim.point.z } })
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const hurt = (unit: Entity) => unit.health < unit.maxHealth
|
|
89
|
+
|
|
90
|
+
describe('the heroes', () => {
|
|
91
|
+
it('are eight, each with its callings and innate as planned, of the game’s callings, the rules whole', () => {
|
|
92
|
+
expect(checkRules(rules).filter((problem) => problem.level === 'error')).toEqual([])
|
|
93
|
+
expect(heroes.map((def) => def.id).sort()).toEqual(Object.keys(PLANNED).sort())
|
|
94
|
+
const known = new Set(callingsOf(rules).map((calling) => calling.id))
|
|
95
|
+
for (const def of heroes) {
|
|
96
|
+
expect(def.hero!.callings, def.id).toEqual(PLANNED[def.id].callings)
|
|
97
|
+
expect(def.hero!.innate, def.id).toBe(PLANNED[def.id].innate)
|
|
98
|
+
for (const calling of Object.keys(def.hero!.callings!)) expect(known.has(calling), calling).toBe(true)
|
|
99
|
+
// three abilities and an ultimate, one of them at least a passive or a toggle, and two of the hero spells
|
|
100
|
+
const learns = def.hero!.learns.map((id) => rules.abilities!.find((ability) => ability.id === id)!)
|
|
101
|
+
expect(learns, def.id).toHaveLength(4)
|
|
102
|
+
expect(learns.filter((ability) => ability.ultimate), def.id).toHaveLength(1)
|
|
103
|
+
expect(
|
|
104
|
+
learns.some((ability) => ability.target === 'passive' || ability.effects.some((effect) => effect.type === 'toggle')),
|
|
105
|
+
def.id
|
|
106
|
+
).toBe(true)
|
|
107
|
+
expect(def.hero!.spells, def.id).toHaveLength(2)
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('of the Kingdom are trained at its altar, of the Restless at theirs, and the tavern hires any', async () => {
|
|
112
|
+
const unit = (id: string) => rules.units.find((def) => def.id === id)!
|
|
113
|
+
expect(unit('altar').trains).toEqual(KINGDOM_HEROES)
|
|
114
|
+
expect(unit('darkAltar').trains).toEqual(HOLLOW_HEROES)
|
|
115
|
+
expect([...unit('tavern').hires!].sort()).toEqual([...KINGDOM_HEROES, ...HOLLOW_HEROES].sort())
|
|
116
|
+
const world = await arena()
|
|
117
|
+
const tavern = world.spawn('tavern', NEUTRAL, { x: 30, z: 30 })
|
|
118
|
+
world.spawn('footman', ME, { x: 27, z: 30 })
|
|
119
|
+
const gold = world.player(ME).resources.gold
|
|
120
|
+
// a hero of the other side, hired once
|
|
121
|
+
const hired = hire(world, tavern, 'duskblade', ME)!
|
|
122
|
+
expect(hired.def.id).toBe('duskblade')
|
|
123
|
+
expect(heroOf(hired)?.level).toBe(1)
|
|
124
|
+
expect(world.player(ME).resources.gold).toBe(gold - 425)
|
|
125
|
+
expect(hire(world, tavern, 'duskblade', ME)).toBeUndefined()
|
|
126
|
+
expect(hire(world, tavern, 'oathbreaker', ME)?.def.id).toBe('oathbreaker')
|
|
127
|
+
expect(hire(world, tavern, 'stormcaller', ME)?.def.id).toBe('stormcaller')
|
|
128
|
+
// three heroes at most, however they come
|
|
129
|
+
expect(hire(world, tavern, 'gravelord', ME)).toBeUndefined()
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
describe('what their abilities do', () => {
|
|
134
|
+
it('Oathbreaker: charges in and taunts, shields allies, makes a last stand', async () => {
|
|
135
|
+
const world = await arena()
|
|
136
|
+
const me = hero(world, 'oathbreaker')
|
|
137
|
+
const ally = hero(world, 'dawnPriest', ME, { x: 28, z: 32 })
|
|
138
|
+
const foe = target(world, { x: 36, z: 30 })
|
|
139
|
+
seen(world)
|
|
140
|
+
cast(world, me, 'oathCharge', { point: foe.pos })
|
|
141
|
+
run(world, 1.5)
|
|
142
|
+
expect(hurt(foe)).toBe(true)
|
|
143
|
+
expect(hasBuff(foe, 'taunted')).toBe(true)
|
|
144
|
+
cast(world, me, 'shieldwall')
|
|
145
|
+
run(world, 0.2)
|
|
146
|
+
expect(shieldOf(me)).toBeGreaterThan(0)
|
|
147
|
+
run(world, 1)
|
|
148
|
+
cast(world, me, 'lastStand')
|
|
149
|
+
run(world, 0.5)
|
|
150
|
+
expect(hasBuff(me, 'lastStand')).toBe(true)
|
|
151
|
+
expect(hasBuff(foe, 'taunted')).toBe(true)
|
|
152
|
+
void ally
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('Oathbreaker’s Bulwark: he takes a fifth of what an allied hero near him takes; Iron Resolve strikes back', async () => {
|
|
156
|
+
const world = await arena()
|
|
157
|
+
const me = hero(world, 'oathbreaker')
|
|
158
|
+
const ally = hero(world, 'galeHunter', ME, { x: 33, z: 30 })
|
|
159
|
+
run(world, 0.5)
|
|
160
|
+
expect(hasBuff(ally, 'bulwarkWard')).toBe(true)
|
|
161
|
+
const [mine, theirs] = [me.health, ally.health]
|
|
162
|
+
world.damage(ally, 100, undefined, { damageType: 'true' })
|
|
163
|
+
expect(theirs - ally.health).toBeCloseTo(80, 0)
|
|
164
|
+
expect(mine - me.health).toBeCloseTo(20, 0)
|
|
165
|
+
// far from him, none of it
|
|
166
|
+
world.place(ally, { x: 55, z: 55 })
|
|
167
|
+
run(world, 2)
|
|
168
|
+
const far = ally.health
|
|
169
|
+
world.damage(ally, 100, undefined, { damageType: 'true' })
|
|
170
|
+
expect(far - ally.health).toBeCloseTo(100, 0)
|
|
171
|
+
// a footman striking him is struck back
|
|
172
|
+
const footman = world.spawn('footman', FOE, { x: 31, z: 30 })
|
|
173
|
+
run(world, 4)
|
|
174
|
+
expect(hurt(footman)).toBe(true)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('Dawn Priest: heals, burns and cleanses, and Sanctuary saves a life; Mercy’s Echo shields what overflows', async () => {
|
|
178
|
+
const world = await arena()
|
|
179
|
+
const me = hero(world, 'dawnPriest')
|
|
180
|
+
const ally = hero(world, 'oathbreaker', ME, { x: 33, z: 30 })
|
|
181
|
+
const foe = target(world, { x: 34, z: 32 })
|
|
182
|
+
run(world, 0.5)
|
|
183
|
+
// the aura
|
|
184
|
+
expect(hasBuff(ally, 'dawnlight')).toBe(true)
|
|
185
|
+
// healing a whole ally: what spills over is a shield
|
|
186
|
+
cast(world, me, 'holyLight', { target: ally })
|
|
187
|
+
run(world, 1)
|
|
188
|
+
expect(shieldOf(ally)).toBeGreaterThan(0)
|
|
189
|
+
applyBuff(world, ally, 'iceRooted', 5, foe)
|
|
190
|
+
cast(world, me, 'cleansingFlame', { point: { x: 34, z: 31 } })
|
|
191
|
+
run(world, 1)
|
|
192
|
+
expect(hasBuff(ally, 'iceRooted')).toBe(false)
|
|
193
|
+
expect(hurt(foe)).toBe(true)
|
|
194
|
+
cast(world, me, 'sanctuary', { target: ally })
|
|
195
|
+
run(world, 1)
|
|
196
|
+
expect(hasBuff(ally, 'sanctuary')).toBe(true)
|
|
197
|
+
world.damage(ally, 100000, foe, { damageType: 'true' })
|
|
198
|
+
expect(ally.alive).toBe(true)
|
|
199
|
+
expect(ally.health).toBeCloseTo(ally.maxHealth * 0.3, -1)
|
|
200
|
+
expect(hasBuff(ally, 'hallowed')).toBe(true)
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it('Stormcaller: a tornado, lightning that leaps, a storm that throws up who crosses it; every fourth spell free and arcing', async () => {
|
|
204
|
+
const world = await arena()
|
|
205
|
+
const me = hero(world, 'stormcaller')
|
|
206
|
+
const foes = [target(world, { x: 35, z: 30 }), target(world, { x: 36, z: 32 }), target(world, { x: 37, z: 29 })]
|
|
207
|
+
seen(world)
|
|
208
|
+
cast(world, me, 'tornado', { point: foes[0].pos })
|
|
209
|
+
run(world, 2.5)
|
|
210
|
+
expect(hurt(foes[0])).toBe(true)
|
|
211
|
+
expect(me.buffs.find((buff) => buff.id === 'staticCharge')?.stacks).toBe(1)
|
|
212
|
+
for (const foe of foes) foe.health = foe.maxHealth
|
|
213
|
+
cast(world, me, 'chainLightning', { target: foes[0] })
|
|
214
|
+
run(world, 1.5)
|
|
215
|
+
expect(foes.filter(hurt).length).toBeGreaterThanOrEqual(2)
|
|
216
|
+
// Gale Ward: what his abilities hurt is slowed
|
|
217
|
+
expect(foes.some((foe) => hasBuff(foe, 'buffeted'))).toBe(true)
|
|
218
|
+
cast(world, me, 'eyeOfTheStorm', { point: { x: 36, z: 30 } })
|
|
219
|
+
run(world, 1.5)
|
|
220
|
+
expect(hasBuff(foes[0], 'stormSlow')).toBe(true)
|
|
221
|
+
// that was the third: the fourth costs nothing, and arcs to one more
|
|
222
|
+
expect(me.buffs.find((buff) => buff.id === 'staticCharge')?.stacks).toBe(3)
|
|
223
|
+
// once Chain Lightning is ready again
|
|
224
|
+
run(world, 9)
|
|
225
|
+
const outside = target(world, { x: 30, z: 36 })
|
|
226
|
+
const near = target(world, { x: 33, z: 37 })
|
|
227
|
+
seen(world)
|
|
228
|
+
me.mana = 100
|
|
229
|
+
const struck: number[] = []
|
|
230
|
+
world.events.on(damaged, ({ target: hit, ability }) => {
|
|
231
|
+
if (ability === 'staticCharge') struck.push(hit.id)
|
|
232
|
+
})
|
|
233
|
+
cast(world, me, 'chainLightning', { target: outside })
|
|
234
|
+
run(world, 1.5)
|
|
235
|
+
expect(me.mana).toBeGreaterThanOrEqual(100)
|
|
236
|
+
expect(struck).toEqual([near.id])
|
|
237
|
+
expect(hasBuff(me, 'staticCharge')).toBe(false)
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it('Gale Hunter: a bolt through a line, a roll and a harder shot, a storm of arrows; Steady Aim standing still', async () => {
|
|
241
|
+
const world = await arena()
|
|
242
|
+
const me = hero(world, 'galeHunter')
|
|
243
|
+
const foes = [target(world, { x: 35, z: 30 }), target(world, { x: 39, z: 30 })]
|
|
244
|
+
seen(world)
|
|
245
|
+
const range = statsOf(world, me).attackRange
|
|
246
|
+
run(world, 2.7)
|
|
247
|
+
expect(me.buffs.find((buff) => buff.id === 'steadied')?.stacks).toBe(4)
|
|
248
|
+
expect(statsOf(world, me).attackRange).toBeCloseTo(range + 2, 3)
|
|
249
|
+
cast(world, me, 'piercingBolt', { point: { x: 45, z: 30 } })
|
|
250
|
+
run(world, 1.5)
|
|
251
|
+
expect(foes.every(hurt)).toBe(true)
|
|
252
|
+
cast(world, me, 'tumble', { point: { x: 30, z: 26 } })
|
|
253
|
+
run(world, 0.3)
|
|
254
|
+
expect(me.pos.z).toBeLessThan(28)
|
|
255
|
+
expect(hasBuff(me, 'tumbleShot')).toBe(true)
|
|
256
|
+
// standing, the aim steadies again; walking loses it (an order made him pick fights again: not now)
|
|
257
|
+
calm(me)
|
|
258
|
+
world.issue({ type: 'order', player: ME, entities: [me.id], order: { type: 'stop' } })
|
|
259
|
+
run(world, 2)
|
|
260
|
+
expect(hasBuff(me, 'steadied')).toBe(true)
|
|
261
|
+
world.issue({ type: 'order', player: ME, entities: [me.id], order: { type: 'move', point: { x: 10, z: 10 } } })
|
|
262
|
+
run(world, 1)
|
|
263
|
+
expect(hasBuff(me, 'steadied')).toBe(false)
|
|
264
|
+
// back into range, and the arrows fall
|
|
265
|
+
cast(world, me, 'arrowstorm', { point: foes[1].pos })
|
|
266
|
+
expect(run(world, 4, () => hasBuff(foes[1], 'pinned'))).toBeLessThan(4)
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
it('Gravelord: pays with health, drains, and raises the dead; his aura for his allies', async () => {
|
|
270
|
+
const world = await arena()
|
|
271
|
+
const me = hero(world, 'gravelord')
|
|
272
|
+
const ally = world.spawn('ghoul', ME, { x: 32, z: 32 })
|
|
273
|
+
const foe = target(world, { x: 34, z: 30 })
|
|
274
|
+
run(world, 0.5)
|
|
275
|
+
expect(me.maxMana).toBe(0)
|
|
276
|
+
expect(hasBuff(ally, 'deathless')).toBe(true)
|
|
277
|
+
const health = me.health
|
|
278
|
+
cast(world, me, 'graveLash', { target: foe })
|
|
279
|
+
run(world, 0.8)
|
|
280
|
+
expect(hurt(foe)).toBe(true)
|
|
281
|
+
expect(hasBuff(foe, 'graveChill')).toBe(true)
|
|
282
|
+
// 60 of health paid for it
|
|
283
|
+
expect(me.health).toBeLessThan(health)
|
|
284
|
+
foe.health = foe.maxHealth
|
|
285
|
+
me.health = 300
|
|
286
|
+
cast(world, me, 'siphon', { target: foe })
|
|
287
|
+
run(world, 3.5)
|
|
288
|
+
expect(hurt(foe)).toBe(true)
|
|
289
|
+
// the dead around him rise
|
|
290
|
+
for (let i = 0; i < 4; i++) world.damage(target(world, { x: 28 + i, z: 27 }), 100000, me, { damageType: 'true' })
|
|
291
|
+
run(world, 0.5)
|
|
292
|
+
cast(world, me, 'armyOfTheGrave')
|
|
293
|
+
run(world, 1)
|
|
294
|
+
expect(world.query({ type: 'skeleton', owner: ME }).length).toBeGreaterThan(0)
|
|
295
|
+
expect(hasBuff(ally, 'graveRallied')).toBe(true)
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
it('Rime Witch: freezes, roots, a blizzard that freezes who stays; Shatter for her allies’ blows', async () => {
|
|
299
|
+
const world = await arena()
|
|
300
|
+
const me = hero(world, 'rimeWitch')
|
|
301
|
+
const ally = hero(world, 'oathbreaker', ME, { x: 31, z: 32 })
|
|
302
|
+
const foes = [target(world, { x: 35, z: 30 }), target(world, { x: 35, z: 33 })]
|
|
303
|
+
seen(world)
|
|
304
|
+
cast(world, me, 'rimeBolt', { target: foes[0] })
|
|
305
|
+
run(world, 0.6)
|
|
306
|
+
expect(hasBuff(foes[0], 'frozen')).toBe(true)
|
|
307
|
+
// her own blow does not shatter it; an ally's does, harder
|
|
308
|
+
world.damage(foes[0], 10, me, { damageType: 'true' })
|
|
309
|
+
expect(hasBuff(foes[0], 'frozen')).toBe(true)
|
|
310
|
+
const before = foes[0].health
|
|
311
|
+
world.damage(foes[0], 100, ally, { damageType: 'true' })
|
|
312
|
+
expect(before - foes[0].health).toBeCloseTo(135, 0)
|
|
313
|
+
expect(hasBuff(foes[0], 'frozen')).toBe(false)
|
|
314
|
+
cast(world, me, 'grasp', { point: foes[1].pos })
|
|
315
|
+
run(world, 0.8)
|
|
316
|
+
expect(hasBuff(foes[1], 'iceRooted')).toBe(true)
|
|
317
|
+
cast(world, me, 'whiteout', { point: { x: 35, z: 31 } })
|
|
318
|
+
run(world, 3)
|
|
319
|
+
expect(foes.some((foe) => hasBuff(foe, 'frozen'))).toBe(true)
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
it('Duskblade: in the shadows after three quiet seconds, a mark that bursts, a fade through a crowd, an eclipse', async () => {
|
|
323
|
+
const world = await arena()
|
|
324
|
+
const me = hero(world, 'duskblade')
|
|
325
|
+
const foe = target(world, { x: 36, z: 30 })
|
|
326
|
+
seen(world)
|
|
327
|
+
run(world, 3.5)
|
|
328
|
+
expect(hasBuff(me, 'shadowed')).toBe(true)
|
|
329
|
+
// hurt, out of them
|
|
330
|
+
world.damage(me, 10, foe)
|
|
331
|
+
expect(hasBuff(me, 'shadowed')).toBe(false)
|
|
332
|
+
cast(world, me, 'duskMark', { target: foe })
|
|
333
|
+
run(world, 1)
|
|
334
|
+
expect(hasBuff(foe, 'duskMark')).toBe(true)
|
|
335
|
+
const marked = foe.health
|
|
336
|
+
run(world, 4)
|
|
337
|
+
// it burst as it ran out
|
|
338
|
+
expect(foe.health).toBeLessThan(marked)
|
|
339
|
+
cast(world, me, 'fade')
|
|
340
|
+
run(world, 0.3)
|
|
341
|
+
expect(isPhasing(world, me)).toBe(true)
|
|
342
|
+
run(world, 3)
|
|
343
|
+
const other = target(world, { x: 34, z: 34 })
|
|
344
|
+
seen(world)
|
|
345
|
+
cast(world, me, 'eclipse', { target: other })
|
|
346
|
+
run(world, 0.5)
|
|
347
|
+
expect(hasBuff(other, 'eclipsed')).toBe(true)
|
|
348
|
+
expect(Math.hypot(me.pos.x - other.pos.x, me.pos.z - other.pos.z)).toBeLessThan(3)
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
it('Duskblade: her first strike from the shadows deals more and silences', async () => {
|
|
352
|
+
const world = await arena()
|
|
353
|
+
const me = hero(world, 'duskblade')
|
|
354
|
+
run(world, 3.5)
|
|
355
|
+
expect(hasBuff(me, 'shadowed')).toBe(true)
|
|
356
|
+
me.get(fighting)!.passive = false
|
|
357
|
+
const foe = target(world, { x: 31.5, z: 30 })
|
|
358
|
+
const blows: number[] = []
|
|
359
|
+
world.events.on(damaged, ({ target: hit, source, amount }) => {
|
|
360
|
+
if (hit === foe && source === me) blows.push(amount)
|
|
361
|
+
})
|
|
362
|
+
// her first strike: out of the shadows, the foe silenced
|
|
363
|
+
run(world, 3, () => !hasBuff(me, 'shadowed'))
|
|
364
|
+
run(world, 0.1)
|
|
365
|
+
expect(hasBuff(foe, 'hushed')).toBe(true)
|
|
366
|
+
run(world, 3)
|
|
367
|
+
expect(blows.length).toBeGreaterThanOrEqual(3)
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
it('Bone Reaver: cleaves, gorges on a creep, a pit of bones; Harvest grows with kills and halves on death', async () => {
|
|
371
|
+
const world = await arena()
|
|
372
|
+
const me = hero(world, 'boneReaver')
|
|
373
|
+
const foe = target(world, { x: 32, z: 30 })
|
|
374
|
+
const creep = calm(world.spawn('ogre', CREEPS, { x: 29, z: 32 }))
|
|
375
|
+
// what is nobody's, not a camp's: left alone
|
|
376
|
+
const nobodys = calm(world.spawn('ogre', NEUTRAL, { x: 31, z: 32 }))
|
|
377
|
+
seen(world)
|
|
378
|
+
cast(world, me, 'boneCleaver')
|
|
379
|
+
run(world, 0.8)
|
|
380
|
+
expect(hurt(foe)).toBe(true)
|
|
381
|
+
expect(hurt(creep)).toBe(true)
|
|
382
|
+
expect(hurt(nobodys)).toBe(false)
|
|
383
|
+
const health = statsOf(world, me).health
|
|
384
|
+
// what is left of it, Gorge's to finish
|
|
385
|
+
creep.health = 300
|
|
386
|
+
cast(world, me, 'gorge', { target: creep })
|
|
387
|
+
run(world, 1.5)
|
|
388
|
+
expect(creep.alive).toBe(false)
|
|
389
|
+
expect(me.get(harvested)?.count).toBe(1)
|
|
390
|
+
expect(statsOf(world, me).health).toBe(health + 6)
|
|
391
|
+
// a hero is five
|
|
392
|
+
const rival = hero(world, 'galeHunter', FOE, { x: 40, z: 40 }, 1)
|
|
393
|
+
world.damage(rival, 100000, me, { damageType: 'true' })
|
|
394
|
+
expect(me.get(harvested)?.count).toBe(6)
|
|
395
|
+
expect(me.buffs.find((buff) => buff.id === 'harvest')?.stacks).toBe(6)
|
|
396
|
+
cast(world, me, 'bonePit', { point: me.pos })
|
|
397
|
+
run(world, 0.5)
|
|
398
|
+
expect(hasBuff(me, 'bonePitFrenzy')).toBe(true)
|
|
399
|
+
world.damage(me, 100000, foe, { damageType: 'true' })
|
|
400
|
+
expect(me.get(harvested)?.count).toBe(3)
|
|
401
|
+
})
|
|
402
|
+
})
|
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
import { checkRules, createRegistry, createWorldFromMap, generateMap, type World } from '@v12-engine/engine'
|
|
3
3
|
import { describe, expect, it } from 'vitest'
|
|
4
4
|
import { DEFAULT_MAP, mapNamed, maps } from '../src/maps'
|
|
5
|
-
import {
|
|
5
|
+
import { game as melee } from '../src/game'
|
|
6
6
|
import { checkMeleeMap, HALLS } from '../src/melee/maps'
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const rules = melee.toRules()
|
|
9
|
+
|
|
10
|
+
/** A game on a map, started as the page starts it (`game.onStart`): you are the first player, the computer the others */
|
|
9
11
|
async function game(name = DEFAULT_MAP): Promise<World> {
|
|
10
|
-
const map = mapNamed(name)
|
|
11
|
-
const world = await createWorldFromMap({ map,
|
|
12
|
-
|
|
12
|
+
const map = await mapNamed(name)
|
|
13
|
+
const world = await createWorldFromMap({ map, rules, seed: 1 })
|
|
14
|
+
await melee.toStart()?.(world, { map, player: map.players[0].id })
|
|
13
15
|
return world
|
|
14
16
|
}
|
|
15
17
|
|
|
@@ -20,7 +22,7 @@ describe('the rules', () => {
|
|
|
20
22
|
|
|
21
23
|
it('give every player with a start a hall and workers, and seat the computer at all but you', async () => {
|
|
22
24
|
const world = await game()
|
|
23
|
-
const starts = mapNamed().players.filter((player) => player.start)
|
|
25
|
+
const starts = (await mapNamed()).players.filter((player) => player.start)
|
|
24
26
|
for (const player of starts) {
|
|
25
27
|
const own = [...world.index.ownedBy(player.id)]
|
|
26
28
|
expect(
|
|
@@ -42,10 +44,10 @@ describe('the rules', () => {
|
|
|
42
44
|
world.dispose()
|
|
43
45
|
}, 30_000)
|
|
44
46
|
|
|
45
|
-
it('read every map, each one melee can be played on', () => {
|
|
47
|
+
it('read every map, each one melee can be played on', async () => {
|
|
46
48
|
for (const [name, map] of Object.entries(maps))
|
|
47
49
|
expect(
|
|
48
|
-
checkMeleeMap(map).filter((problem) => problem.error),
|
|
50
|
+
checkMeleeMap(await map()).filter((problem) => problem.error),
|
|
49
51
|
name
|
|
50
52
|
).toEqual([])
|
|
51
53
|
})
|
|
@@ -1,2 +1,32 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The game, whole, as every part of the project takes it (`createGame`): melee's rules (`src/melee`), its look
|
|
3
|
+
* (`src/melee/view`, fetched as the page first plays it), its maps (`src/maps.ts`) and what it does as a game starts.
|
|
4
|
+
* The page, the server, the editor and the tests all take this
|
|
5
|
+
*/
|
|
6
|
+
import { maps } from './maps'
|
|
7
|
+
import { meleeGame } from './melee/game'
|
|
8
|
+
|
|
9
|
+
export const game = meleeGame.maps(maps)
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* What the game does once a world is made from a map, before anyone plays. Every player with a start is given a hall
|
|
13
|
+
* and five workers by the rules themselves; in the page, the computer plays everyone but you, each against the other
|
|
14
|
+
* teams' starts. A server seats its computers itself (`server.ts`), so there is no one to seat here
|
|
15
|
+
*/
|
|
16
|
+
game.onStart((world, { map, player }) => {
|
|
17
|
+
if (!map || player === undefined) return
|
|
18
|
+
const players = map.players.filter((other) => other.start)
|
|
19
|
+
for (const other of players) {
|
|
20
|
+
if (other.id === player) continue
|
|
21
|
+
const enemyBases = players.filter((rival) => rival.team !== other.team).map((rival) => rival.start!)
|
|
22
|
+
// remembered, so that a game loaded from a save of this one plays on against them
|
|
23
|
+
world.computers.seat(other.id, { enemyBases, settings: { army: { firstAttack: 360, waveSize: 6 } } })
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// @if desktop
|
|
28
|
+
// its look with what the desktop app adds to it: a Windowed setting, Quit on the pause menu
|
|
29
|
+
game.view(() => Promise.all([import('./melee/view'), import('./desktop')]).then(([{ meleeView }, { desktopView }]) => desktopView(meleeView)))
|
|
30
|
+
// @end
|
|
31
|
+
// (saved while it runs, melee's rules and its look take their new versions with no new page: `src/melee/index.ts`
|
|
32
|
+
// and `src/melee/view/index.ts` say so. A change here loads the page again)
|
|
@@ -21,8 +21,8 @@ export type BayPaths = never
|
|
|
21
21
|
/** The lights of the map that are named, for `client.lights.named(...)` */
|
|
22
22
|
export type BayLights = never
|
|
23
23
|
|
|
24
|
-
/** The effects of the map that are named, for `client.
|
|
25
|
-
export type
|
|
24
|
+
/** The effects of the map that are named, for `client.mapFx.named(...)` */
|
|
25
|
+
export type BayFx = never
|
|
26
26
|
|
|
27
27
|
/** The ids of the regions, points and paths by name: what a field that names one keeps (it is handed the name) */
|
|
28
28
|
export const BayIds = { regions: { }, points: { }, paths: { } } as const
|