@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,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crowd control: buffs with a flag, put on by the `buff` effect. Stun, root, silence, disarm, knocked up, knocked
|
|
3
|
+
* down, fear, charm, taunt, sleep, polymorph, suppression and a slow; then cleanse and dispel to take them off. The
|
|
4
|
+
* footmen walk up and down so that what holds them, slows them or turns them shows
|
|
5
|
+
*/
|
|
6
|
+
import { type AbilityDef, type BuffDef, defineCommand, type World } from '@v12-engine/engine'
|
|
7
|
+
import { exampleField } from '../define'
|
|
8
|
+
import { ENEMY } from '../common/units'
|
|
9
|
+
import type { Family } from './family'
|
|
10
|
+
import { caster, lineUp, put, sparring } from './kit'
|
|
11
|
+
|
|
12
|
+
/** A crowd control buff of `seconds`, harmful, with its flags */
|
|
13
|
+
const held = (id: string, name: string, duration: number, flags: Partial<BuffDef>): BuffDef => ({ id, name, harmful: true, duration, ...flags })
|
|
14
|
+
|
|
15
|
+
const buffs: BuffDef[] = [
|
|
16
|
+
held('abilitiesStunned', 'Stunned', 2, { stun: true }),
|
|
17
|
+
held('abilitiesRooted', 'Rooted', 3, { root: true }),
|
|
18
|
+
held('abilitiesSilenced', 'Silenced', 3, { silence: true }),
|
|
19
|
+
held('abilitiesDisarmed', 'Disarmed', 3, { disarm: true }),
|
|
20
|
+
// a stun that throws it up, and one that lays it down: stuns with a look
|
|
21
|
+
held('abilitiesKnockedUp', 'Airborne', 1, { stun: true, airborne: true }),
|
|
22
|
+
held('abilitiesKnockedDown', 'Knocked down', 2, { stun: true, down: true }),
|
|
23
|
+
// controls: who carries one walks away from (fear), toward (charm) or attacks (taunt) whoever put it on
|
|
24
|
+
held('abilitiesFeared', 'Terrified', 2.5, { control: 'fear' }),
|
|
25
|
+
held('abilitiesCharmed', 'Charmed', 2.5, { control: 'charm' }),
|
|
26
|
+
held('abilitiesTaunted', 'Taunted', 3, { control: 'taunt' }),
|
|
27
|
+
// a stun broken by the first damage
|
|
28
|
+
held('abilitiesAsleep', 'Asleep', 6, { stun: true, breaksOn: ['damage'] }),
|
|
29
|
+
// a polymorph: can't attack or cast, slower, and drawn as a sheep (`drawnAs` on its look)
|
|
30
|
+
held('abilitiesHexed', 'Hexed', 4, { disarm: true, silence: true, percent: { moveSpeed: -0.4 } }),
|
|
31
|
+
// held like a stun, but tenacity doesn't shorten it nor cleanse take it off
|
|
32
|
+
held('abilitiesSuppressed', 'Suppressed', 2.5, { suppress: true }),
|
|
33
|
+
held('abilitiesFrost', 'Frost', 4, { percent: { moveSpeed: -0.6, attackSpeed: -0.3 } })
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
/** An ability putting a buff on the enemy it is cast on, or on every enemy within `radius` of the spot */
|
|
37
|
+
const putOn = (id: string, name: string, hotkey: string, buff: string, description: string, radius?: number): AbilityDef => ({
|
|
38
|
+
id,
|
|
39
|
+
name,
|
|
40
|
+
description,
|
|
41
|
+
hotkey,
|
|
42
|
+
target: radius ? 'point' : 'unit',
|
|
43
|
+
...(!radius && { targets: { enemies: true } }),
|
|
44
|
+
range: 12,
|
|
45
|
+
cooldown: 0.5,
|
|
46
|
+
effects: [{ type: 'buff', buff, ...(radius && { radius }) }]
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const abilities: AbilityDef[] = [
|
|
50
|
+
putOn('abilitiesThunderBolt', 'Stun', 'Q', 'abilitiesStunned', 'Can not move, attack or cast for 2 seconds; what it was told waits'),
|
|
51
|
+
putOn('abilitiesEntangle', 'Root', 'W', 'abilitiesRooted', 'Can not move for 3 seconds, but still attacks and casts'),
|
|
52
|
+
putOn('abilitiesSilence', 'Silence', 'E', 'abilitiesSilenced', 'Can not cast for 3 seconds, everyone within 3 of the spot', 3),
|
|
53
|
+
putOn('abilitiesDisarm', 'Disarm', 'R', 'abilitiesDisarmed', 'Can not attack for 3 seconds: the brute stops swinging'),
|
|
54
|
+
putOn('abilitiesKnockUp', 'Knock up', 'T', 'abilitiesKnockedUp', 'Thrown in the air for a second, everyone within 2.5 of the spot', 2.5),
|
|
55
|
+
putOn('abilitiesKnockDown', 'Knock down', 'D', 'abilitiesKnockedDown', 'Laid on the ground for 2 seconds'),
|
|
56
|
+
{
|
|
57
|
+
// on everyone near the caster: they run from it
|
|
58
|
+
id: 'abilitiesTerrify',
|
|
59
|
+
name: 'Fear',
|
|
60
|
+
description: 'Every enemy within 12 runs away from the caster for 2.5 seconds',
|
|
61
|
+
hotkey: 'F',
|
|
62
|
+
target: 'none',
|
|
63
|
+
cooldown: 0.5,
|
|
64
|
+
effects: [{ type: 'buff', buff: 'abilitiesFeared', radius: 12 }]
|
|
65
|
+
},
|
|
66
|
+
putOn('abilitiesCharm', 'Charm', 'G', 'abilitiesCharmed', 'Walks helplessly toward the caster for 2.5 seconds'),
|
|
67
|
+
putOn('abilitiesTaunt', 'Taunt', 'Z', 'abilitiesTaunted', 'Must attack the caster for 3 seconds: cast on the brute, it leaves the footman'),
|
|
68
|
+
putOn('abilitiesSleep', 'Sleep', 'X', 'abilitiesAsleep', 'Asleep for 6 seconds, until something hurts it'),
|
|
69
|
+
putOn('abilitiesHex', 'Polymorph', 'C', 'abilitiesHexed', 'A sheep for 4 seconds: can not attack or cast, and slow'),
|
|
70
|
+
putOn('abilitiesNetherGrasp', 'Suppress', 'V', 'abilitiesSuppressed', 'Held for 2.5 seconds; cleanse does not take it off'),
|
|
71
|
+
putOn('abilitiesFrostNova', 'Slow', 'B', 'abilitiesFrost', 'Everyone within 3 of the spot is 60% slower for 4 seconds', 3),
|
|
72
|
+
{
|
|
73
|
+
// cleanse takes off stuns, roots, silences, disarms, controls and slows; allies when not said, the enemies here
|
|
74
|
+
id: 'abilitiesUnbind',
|
|
75
|
+
name: 'Cleanse',
|
|
76
|
+
description: 'Takes stuns, roots, silences, fears and slows off every enemy within 12 (not a suppression)',
|
|
77
|
+
hotkey: 'N',
|
|
78
|
+
target: 'none',
|
|
79
|
+
cooldown: 0.5,
|
|
80
|
+
effects: [{ type: 'cleanse', radius: 12, affects: 'enemies' }]
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
// dispel takes off whatever buff may be dispelled, the good and the bad
|
|
84
|
+
id: 'abilitiesPurge',
|
|
85
|
+
name: 'Dispel',
|
|
86
|
+
description: 'Takes every buff that may be dispelled off every unit within 12, a suppression too',
|
|
87
|
+
hotkey: 'Y',
|
|
88
|
+
target: 'none',
|
|
89
|
+
cooldown: 0.5,
|
|
90
|
+
effects: [{ type: 'dispel', radius: 12, affects: 'all' }]
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Each footman walks up and down its own line, so that what stops, slows or turns it shows; a sentry (the vision
|
|
96
|
+
* example's) across the north. The ranger who hides stays where it is
|
|
97
|
+
*/
|
|
98
|
+
function patrolAll(world: World): void {
|
|
99
|
+
const ranger = world.named('ranger')
|
|
100
|
+
for (const unit of world.units.values()) {
|
|
101
|
+
if (!unit.alive || unit.owner !== ENEMY || unit === ranger) continue
|
|
102
|
+
if (unit.def.id === 'abilitiesTarget') unit.order('patrol', { x: unit.pos.x, z: unit.pos.z + 6 })
|
|
103
|
+
if (unit.def.id === 'abilitiesSentry') unit.order('patrol', { x: unit.pos.x + 8, z: unit.pos.z + 2 })
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Sends the footmen walking again: what a control or a stun took away from them */
|
|
108
|
+
export const walkAgain = defineCommand('abilities.walk', {}, (world) => patrolAll(world))
|
|
109
|
+
|
|
110
|
+
export const control: Family = {
|
|
111
|
+
abilities,
|
|
112
|
+
buffs,
|
|
113
|
+
commands: [walkAgain],
|
|
114
|
+
units: [
|
|
115
|
+
caster(
|
|
116
|
+
'abilitiesBinder',
|
|
117
|
+
'Binder',
|
|
118
|
+
abilities.map((each) => each.id),
|
|
119
|
+
{
|
|
120
|
+
stats: {
|
|
121
|
+
health: 4000,
|
|
122
|
+
healthRegen: 40,
|
|
123
|
+
mana: 1000,
|
|
124
|
+
manaRegen: 60,
|
|
125
|
+
attackDamage: 30,
|
|
126
|
+
attackSpeed: 1,
|
|
127
|
+
attackRange: 6,
|
|
128
|
+
moveSpeed: 4.2,
|
|
129
|
+
armor: 20
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
),
|
|
133
|
+
// a brute that fights what is next to it, for disarm and taunt
|
|
134
|
+
sparring('abilitiesBrute', 'Brute', {
|
|
135
|
+
radius: 0.55,
|
|
136
|
+
stats: { health: 1500, healthRegen: 30, attackDamage: 12, attackSpeed: 1, attackRange: 0.6, moveSpeed: 3.6, armor: 5 },
|
|
137
|
+
attacks: [{ damagePoint: 0.4, acquisitionRange: 2 }]
|
|
138
|
+
}),
|
|
139
|
+
// what the polymorph draws its target as (`drawnAs`): a type of its own, never put down
|
|
140
|
+
{ id: 'abilitiesSheep', name: 'Sheep', kind: 'unit', radius: 0.4, stats: { health: 10, moveSpeed: 2 }, sight: 2 }
|
|
141
|
+
],
|
|
142
|
+
example: {
|
|
143
|
+
id: 'abilities-control',
|
|
144
|
+
category: 'abilities',
|
|
145
|
+
title: 'Crowd control',
|
|
146
|
+
summary:
|
|
147
|
+
'Stun, root, silence, disarm, knock up and down, fear, charm, taunt, sleep, polymorph, suppress and slow, then cleanse and dispel to take them off. The footmen walk up and down so each shows; the brute on the right fights a footman of yours.',
|
|
148
|
+
tags: ['crowd control', 'cc', 'stun', 'root', 'silence', 'fear', 'charm', 'taunt', 'polymorph', 'sleep', 'slow', 'cleanse', 'dispel', 'knock up'],
|
|
149
|
+
source: ['examples/abilities/control.ts', 'examples/abilities/kit.ts'],
|
|
150
|
+
map: () => exampleField({ name: 'Crowd control', players: 2 }),
|
|
151
|
+
start: (world) => {
|
|
152
|
+
lineUp(world, 'abilitiesBinder', 'abilitiesTarget', 4)
|
|
153
|
+
// the brute, and a footman of yours by it to fight
|
|
154
|
+
put(world, 'abilitiesBrute', ENEMY, { x: 31, z: 22 }, 'brute', -Math.PI / 2)
|
|
155
|
+
put(world, 'abilitiesWounded', 1, { x: 29.8, z: 22 }, 'ally', Math.PI / 2)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** How the crowd control example looks, and its card: a button for each hold, and one to send the footmen walking */
|
|
2
|
+
import { button } from '../../shell/look'
|
|
3
|
+
import { character, hold, person, sharedLooks, TWO_HANDED } from '../common/looks'
|
|
4
|
+
import { control, walkAgain } from './control'
|
|
5
|
+
import type { FamilyLooks } from './looks'
|
|
6
|
+
import { castButton, look, readyOn, resetButton } from './lookKit'
|
|
7
|
+
|
|
8
|
+
/** A buff's look on its carrier: sparks of `shape` in its colour (`glow`) */
|
|
9
|
+
const glow = (color: [number, number, number], shape: string, more: Record<string, unknown> = {}) =>
|
|
10
|
+
look(color, undefined, { aura: 'glow', options: { shape, ...more } })
|
|
11
|
+
|
|
12
|
+
/** The abilities cast on the brute rather than on the nearest footman */
|
|
13
|
+
const onBrute = new Set(['abilitiesDisarm', 'abilitiesTaunt'])
|
|
14
|
+
/** The abilities cast on a footman, where the brute is as near */
|
|
15
|
+
const onFootman = new Set([
|
|
16
|
+
'abilitiesHex',
|
|
17
|
+
'abilitiesThunderBolt',
|
|
18
|
+
'abilitiesEntangle',
|
|
19
|
+
'abilitiesKnockDown',
|
|
20
|
+
'abilitiesCharm',
|
|
21
|
+
'abilitiesSleep',
|
|
22
|
+
'abilitiesNetherGrasp'
|
|
23
|
+
])
|
|
24
|
+
|
|
25
|
+
export const controlLooks: FamilyLooks = {
|
|
26
|
+
units: {
|
|
27
|
+
abilitiesBinder: sharedLooks.mage,
|
|
28
|
+
abilitiesBrute: { height: 2.3, model: character('barbarian', 0.95, { hold: [hold('axe_2handed')] }), attacks: [TWO_HANDED], shapes: person(2.3) },
|
|
29
|
+
// the shared art has no animals: a sheep of parts, a woolly body and a dark head
|
|
30
|
+
abilitiesSheep: {
|
|
31
|
+
height: 0.9,
|
|
32
|
+
shapes: [
|
|
33
|
+
{ shape: 'sphere', size: [0.9, 0.7, 1.1], offset: [0, 0.6, 0], color: [0.95, 0.95, 0.9] },
|
|
34
|
+
{ shape: 'sphere', size: [0.4, 0.4, 0.45], offset: [0, 0.85, 0.55], color: [0.15, 0.15, 0.15] }
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
abilities: {
|
|
39
|
+
abilitiesThunderBolt: look([1, 0.95, 0.5], 'burst'),
|
|
40
|
+
abilitiesEntangle: look([0.35, 0.8, 0.25], 'burst'),
|
|
41
|
+
abilitiesSilence: look([0.6, 0.4, 1], 'sonar'),
|
|
42
|
+
abilitiesKnockUp: look([0.8, 0.7, 0.5], 'smash'),
|
|
43
|
+
abilitiesTerrify: look([0.55, 0.15, 0.7], 'sonar'),
|
|
44
|
+
abilitiesHex: look([0.4, 1, 0.4], 'burst'),
|
|
45
|
+
abilitiesFrostNova: look([0.6, 0.85, 1], 'slam'),
|
|
46
|
+
abilitiesUnbind: look([1, 1, 1], 'sonar'),
|
|
47
|
+
abilitiesPurge: look([0.5, 1, 0.9], 'sonar'),
|
|
48
|
+
abilitiesStunned: glow([1, 0.95, 0.5], 'ring', { at: 'head' }),
|
|
49
|
+
abilitiesRooted: glow([0.35, 0.8, 0.25], 'column'),
|
|
50
|
+
abilitiesSilenced: glow([0.6, 0.4, 1], 'ring', { at: 'head' }),
|
|
51
|
+
abilitiesDisarmed: glow([0.8, 0.8, 0.8], 'ring', { at: 'head' }),
|
|
52
|
+
abilitiesAsleep: glow([0.5, 0.6, 1], 'rise'),
|
|
53
|
+
// the hexed footman drawn as a sheep
|
|
54
|
+
abilitiesHexed: look([0.4, 1, 0.4], undefined, { drawnAs: 'abilitiesSheep' }),
|
|
55
|
+
abilitiesSuppressed: glow([0.5, 0.1, 0.6], 'shell'),
|
|
56
|
+
abilitiesFrost: glow([0.6, 0.85, 1], 'fall'),
|
|
57
|
+
// the mark over a feared, charmed or taunted unit, in its colour
|
|
58
|
+
abilitiesFeared: look([0.55, 0.15, 0.7], undefined, { marker: true }),
|
|
59
|
+
abilitiesCharmed: look([1, 0.45, 0.75], undefined, { marker: true }),
|
|
60
|
+
abilitiesTaunted: look([1, 0.3, 0.2], undefined, { marker: true })
|
|
61
|
+
},
|
|
62
|
+
examples: {
|
|
63
|
+
'abilities-control': {
|
|
64
|
+
ready: (client) => {
|
|
65
|
+
readyOn(client, 'abilitiesBinder', { minX: 17, minZ: 14, maxX: 33, maxZ: 31 })
|
|
66
|
+
// the footmen set walking, once the game runs
|
|
67
|
+
client.session.send(walkAgain.make({}))
|
|
68
|
+
},
|
|
69
|
+
controls: [
|
|
70
|
+
...control.abilities.map((ability) =>
|
|
71
|
+
castButton(ability, onBrute.has(ability.id) ? { at: 'brute' } : onFootman.has(ability.id) ? { at: 'enemy2' } : {})
|
|
72
|
+
),
|
|
73
|
+
button('Walk again', (client) => client.session.send(walkAgain.make({})), 'Sends the footmen up and down their lines again'),
|
|
74
|
+
resetButton('abilities-control')
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Damage and heal: the `damage`, `heal` and `mana` effects, how much they do (by level, by the target's health, by
|
|
3
|
+
* chance), and the conditions that pick one or the other (`if`). A mage casts each on a row of sparring footmen and on a
|
|
4
|
+
* wounded ally
|
|
5
|
+
*/
|
|
6
|
+
import type { AbilityDef } from '@v12-engine/engine'
|
|
7
|
+
import { exampleField } from '../define'
|
|
8
|
+
import type { Family } from './family'
|
|
9
|
+
import { caster, lineUp, sparring } from './kit'
|
|
10
|
+
|
|
11
|
+
const abilities: AbilityDef[] = [
|
|
12
|
+
{
|
|
13
|
+
// the plainest: magic damage to the unit it is cast on
|
|
14
|
+
id: 'abilitiesFireBolt',
|
|
15
|
+
name: 'Fire Bolt',
|
|
16
|
+
description: 'Burns an enemy for {damage}',
|
|
17
|
+
hotkey: 'Q',
|
|
18
|
+
target: 'unit',
|
|
19
|
+
targets: { enemies: true },
|
|
20
|
+
range: 8,
|
|
21
|
+
manaCost: 40,
|
|
22
|
+
cooldown: 1,
|
|
23
|
+
effects: [{ type: 'damage', amount: 140, damageType: 'magic' }]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
// a radius makes it an area: every enemy within 2.5 cells of the spot
|
|
27
|
+
id: 'abilitiesShockBlast',
|
|
28
|
+
name: 'Shock Blast',
|
|
29
|
+
description: 'Blasts the ground: {damage} to every enemy within {radius}',
|
|
30
|
+
hotkey: 'W',
|
|
31
|
+
target: 'point',
|
|
32
|
+
range: 9,
|
|
33
|
+
manaCost: 60,
|
|
34
|
+
cooldown: 1.5,
|
|
35
|
+
effects: [{ type: 'damage', amount: 90, radius: 2.5 }]
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
// an `if` asks about the target: below half its health it takes true damage that ignores armor, more the more it lacks
|
|
39
|
+
id: 'abilitiesExecute',
|
|
40
|
+
name: 'Execute',
|
|
41
|
+
description: 'Below half health, a blow of true damage that grows with the health it lacks; above, a plain cut',
|
|
42
|
+
hotkey: 'E',
|
|
43
|
+
target: 'unit',
|
|
44
|
+
targets: { enemies: true },
|
|
45
|
+
range: 8,
|
|
46
|
+
manaCost: 50,
|
|
47
|
+
cooldown: 1,
|
|
48
|
+
effects: [
|
|
49
|
+
{
|
|
50
|
+
type: 'if',
|
|
51
|
+
target: { healthBelow: 0.5 },
|
|
52
|
+
effects: [{ type: 'damage', amount: 150, byMissingHealth: 0.5, damageType: 'true' }],
|
|
53
|
+
otherwise: [{ type: 'damage', amount: 60, damageType: 'physical' }]
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
// heal reaches allies: the wounded footman beside the mage
|
|
59
|
+
id: 'abilitiesHolyLight',
|
|
60
|
+
name: 'Holy Light',
|
|
61
|
+
description: 'Heals an ally for {heal}',
|
|
62
|
+
hotkey: 'R',
|
|
63
|
+
target: 'unit',
|
|
64
|
+
targets: { allies: true, self: true },
|
|
65
|
+
range: 8,
|
|
66
|
+
manaCost: 50,
|
|
67
|
+
cooldown: 1,
|
|
68
|
+
effects: [{ type: 'heal', amount: 200 }]
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
// hurt one, heal the caster: two effects, one with `on: 'caster'`
|
|
72
|
+
id: 'abilitiesDrain',
|
|
73
|
+
name: 'Drain',
|
|
74
|
+
description: 'Takes {damage} from an enemy and heals the caster as much',
|
|
75
|
+
hotkey: 'T',
|
|
76
|
+
target: 'unit',
|
|
77
|
+
targets: { enemies: true },
|
|
78
|
+
range: 8,
|
|
79
|
+
manaCost: 40,
|
|
80
|
+
cooldown: 1,
|
|
81
|
+
effects: [
|
|
82
|
+
{ type: 'damage', amount: 80 },
|
|
83
|
+
{ type: 'heal', amount: 80, on: 'caster' }
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
// the three most hurt allies around the caster, picked by a condition, sorted and limited
|
|
88
|
+
id: 'abilitiesMend',
|
|
89
|
+
name: 'Mend',
|
|
90
|
+
description: 'Heals the three most hurt allies near for {heal}, and gives them back mana',
|
|
91
|
+
hotkey: 'D',
|
|
92
|
+
target: 'none',
|
|
93
|
+
manaCost: 60,
|
|
94
|
+
cooldown: 2,
|
|
95
|
+
effects: [
|
|
96
|
+
{ type: 'heal', amount: 120, radius: 8, affects: 'allies', sort: 'lowestHealthShare', limit: 3 },
|
|
97
|
+
{ type: 'mana', amount: 50, radius: 8 }
|
|
98
|
+
]
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
// a zone hurts those inside four times a second: a share of their most health, so it bites big and small alike
|
|
102
|
+
id: 'abilitiesDecay',
|
|
103
|
+
name: 'Decay',
|
|
104
|
+
description: 'Rots the ground for 4 seconds: each second, 4% of the most health of every enemy on it',
|
|
105
|
+
hotkey: 'F',
|
|
106
|
+
target: 'point',
|
|
107
|
+
range: 9,
|
|
108
|
+
manaCost: 70,
|
|
109
|
+
cooldown: 3,
|
|
110
|
+
effects: [{ type: 'zone', radius: 2.5, duration: 4, effects: [{ type: 'damage', amount: 0, byMaxHealth: 0.01, damageType: 'true' }] }]
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
// a chance on any effect: here a third of the time it strikes twice
|
|
114
|
+
id: 'abilitiesLuckyStrike',
|
|
115
|
+
name: 'Lucky Strike',
|
|
116
|
+
description: 'Strikes for 70, and a third of the time for 140 more',
|
|
117
|
+
hotkey: 'G',
|
|
118
|
+
target: 'unit',
|
|
119
|
+
targets: { enemies: true },
|
|
120
|
+
range: 8,
|
|
121
|
+
cooldown: 0.5,
|
|
122
|
+
effects: [
|
|
123
|
+
{ type: 'damage', amount: 70, damageType: 'physical' },
|
|
124
|
+
{ type: 'damage', amount: 140, damageType: 'physical', chance: 0.33 }
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
export const damage: Family = {
|
|
130
|
+
abilities,
|
|
131
|
+
units: [
|
|
132
|
+
caster(
|
|
133
|
+
'abilitiesHealer',
|
|
134
|
+
'Battle mage',
|
|
135
|
+
abilities.map((each) => each.id)
|
|
136
|
+
),
|
|
137
|
+
sparring('abilitiesTarget', 'Sparring footman'),
|
|
138
|
+
// an ally to heal: it comes in wounded, and does not heal by itself
|
|
139
|
+
sparring('abilitiesWounded', 'Wounded footman', { stats: { health: 900, moveSpeed: 3.2, armor: 5 } })
|
|
140
|
+
],
|
|
141
|
+
example: {
|
|
142
|
+
id: 'abilities-damage',
|
|
143
|
+
category: 'abilities',
|
|
144
|
+
title: 'Damage and heal',
|
|
145
|
+
summary:
|
|
146
|
+
'Damage, heal and mana: plain amounts, areas, a blow that grows with the health the target lacks, a condition, a chance. Press a button to cast it; the wounded footman on the left is the one healed.',
|
|
147
|
+
tags: ['damage', 'heal', 'mana', 'execute', 'if', 'chance', 'zone', 'true damage'],
|
|
148
|
+
source: ['examples/abilities/damage.ts', 'examples/abilities/kit.ts'],
|
|
149
|
+
map: () => exampleField({ name: 'Damage and heal', players: 2 }),
|
|
150
|
+
start: (world) => {
|
|
151
|
+
lineUp(world, 'abilitiesHealer', 'abilitiesTarget', 3, 'abilitiesWounded')
|
|
152
|
+
const wounded = world.named('ally')
|
|
153
|
+
if (wounded) wounded.health = wounded.maxHealth * 0.3
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** How the damage and heal example looks, and its card: a button for each ability (`./damage.ts`) */
|
|
2
|
+
import { readout } from '../../shell/look'
|
|
3
|
+
import { sharedLooks } from '../common/looks'
|
|
4
|
+
import { damage } from './damage'
|
|
5
|
+
import type { FamilyLooks } from './looks'
|
|
6
|
+
import { castButton, look, readyOn, resetButton } from './lookKit'
|
|
7
|
+
|
|
8
|
+
export const damageLooks: FamilyLooks = {
|
|
9
|
+
units: {
|
|
10
|
+
abilitiesHealer: sharedLooks.mage,
|
|
11
|
+
abilitiesTarget: sharedLooks.footman,
|
|
12
|
+
abilitiesWounded: sharedLooks.footman
|
|
13
|
+
},
|
|
14
|
+
abilities: {
|
|
15
|
+
abilitiesFireBolt: look([1, 0.45, 0.1], 'burst'),
|
|
16
|
+
abilitiesShockBlast: look([0.55, 0.75, 1], 'smash'),
|
|
17
|
+
abilitiesExecute: look([0.85, 0.1, 0.1], 'burst'),
|
|
18
|
+
abilitiesHolyLight: look([1, 0.9, 0.45], 'burst'),
|
|
19
|
+
abilitiesDrain: look([0.6, 0.1, 0.5], 'tether'),
|
|
20
|
+
abilitiesMend: look([0.45, 1, 0.55], 'sonar'),
|
|
21
|
+
abilitiesDecay: look([0.4, 0.75, 0.2], 'mark'),
|
|
22
|
+
abilitiesLuckyStrike: look([1, 0.8, 0.2], 'burst')
|
|
23
|
+
},
|
|
24
|
+
examples: {
|
|
25
|
+
'abilities-damage': {
|
|
26
|
+
ready: (client) => readyOn(client, 'abilitiesHealer'),
|
|
27
|
+
controls: [
|
|
28
|
+
...damage.abilities.map((ability) => castButton(ability)),
|
|
29
|
+
resetButton('abilities-damage'),
|
|
30
|
+
readout('Wounded ally', (client) => {
|
|
31
|
+
const ally = client.session.snapshot().entities.find((each) => each.type === 'abilitiesWounded')
|
|
32
|
+
return ally ? `${Math.round(ally.health)} health` : '—'
|
|
33
|
+
})
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A family of abilities, as an example shows it: the abilities, the buffs and units they use, and the example that
|
|
3
|
+
* casts them. Each family's file gives one; `./index.ts` registers them all
|
|
4
|
+
*/
|
|
5
|
+
import type { AbilityDef, BuffDef, CommandDef, Game, UnitTypeDef } from '@v12-engine/engine'
|
|
6
|
+
import type { ExampleDef } from '../define'
|
|
7
|
+
import { starts } from './kit'
|
|
8
|
+
|
|
9
|
+
export interface Family {
|
|
10
|
+
/** The example that shows them */
|
|
11
|
+
example: ExampleDef
|
|
12
|
+
/** The abilities, in the order the card's buttons cast them */
|
|
13
|
+
abilities: AbilityDef[]
|
|
14
|
+
/** Abilities used but not cast from a button: what a trap or a barrel sets off, a passive of another's */
|
|
15
|
+
more?: AbilityDef[]
|
|
16
|
+
buffs?: BuffDef[]
|
|
17
|
+
units: UnitTypeDef[]
|
|
18
|
+
/** Commands of its own its card sends */
|
|
19
|
+
commands?: CommandDef<any>[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Registers a family's definitions on the game, and its start for the reset command */
|
|
23
|
+
export function registerFamily(game: Game<any>, family: Family): void {
|
|
24
|
+
for (const unit of family.units) game.unit(unit)
|
|
25
|
+
for (const ability of [...family.abilities, ...(family.more ?? [])]) game.ability(ability)
|
|
26
|
+
for (const buff of family.buffs ?? []) game.buff(buff)
|
|
27
|
+
for (const command of family.commands ?? []) game.command(command)
|
|
28
|
+
if (family.example.start) starts[family.example.id] = family.example.start as (world: Parameters<(typeof starts)[string]>[0]) => void
|
|
29
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The abilities examples on the rules' side: a family of the engine's abilities each (`./family.ts`), every one cast
|
|
3
|
+
* from a button of the card by the command of `./kit.ts`. Their packs, abilities, buffs and units are registered here
|
|
4
|
+
*/
|
|
5
|
+
import type { Game } from '@v12-engine/engine'
|
|
6
|
+
import type { ExampleCategory } from '../define'
|
|
7
|
+
import { areas } from './areas'
|
|
8
|
+
import { buffsFamily } from './buffs'
|
|
9
|
+
import { changing } from './changing'
|
|
10
|
+
import { control } from './control'
|
|
11
|
+
import { damage } from './damage'
|
|
12
|
+
import { type Family, registerFamily } from './family'
|
|
13
|
+
import { abilityPacks, kitRules } from './kit'
|
|
14
|
+
import { missiles } from './missiles'
|
|
15
|
+
import { moves } from './moves'
|
|
16
|
+
import { recasts } from './recasts'
|
|
17
|
+
import { resources } from './resources'
|
|
18
|
+
import { summons } from './summons'
|
|
19
|
+
import { triggers } from './triggers'
|
|
20
|
+
import { vision } from './vision'
|
|
21
|
+
|
|
22
|
+
/** The families, in the list's order */
|
|
23
|
+
export const families: Family[] = [damage, missiles, areas, control, buffsFamily, moves, summons, triggers, recasts, resources, vision, changing]
|
|
24
|
+
|
|
25
|
+
export const category: ExampleCategory = {
|
|
26
|
+
rules: (game: Game<any>) => {
|
|
27
|
+
abilityPacks(game)
|
|
28
|
+
kitRules(game)
|
|
29
|
+
for (const family of families) registerFamily(game, family)
|
|
30
|
+
},
|
|
31
|
+
examples: families.map((family) => family.example)
|
|
32
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the abilities examples share on the rules' side (the MOBA ones too): the packs their abilities need, a caster
|
|
3
|
+
* made of a list of abilities, sparring partners to cast on, and the command a card button sends to cast one. The
|
|
4
|
+
* engine alone
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
abilityChangesPack,
|
|
8
|
+
type AbilityDef,
|
|
9
|
+
aimKind,
|
|
10
|
+
defineCommand,
|
|
11
|
+
type Entity,
|
|
12
|
+
type Game,
|
|
13
|
+
type GamePack,
|
|
14
|
+
groundObjectsPack,
|
|
15
|
+
holdsPack,
|
|
16
|
+
manaBurnPack,
|
|
17
|
+
movesPack,
|
|
18
|
+
optional,
|
|
19
|
+
point,
|
|
20
|
+
releaseHold,
|
|
21
|
+
resourcesPack,
|
|
22
|
+
string,
|
|
23
|
+
type UnitTypeDef,
|
|
24
|
+
type Vec2,
|
|
25
|
+
visionPack,
|
|
26
|
+
type World,
|
|
27
|
+
exactMath
|
|
28
|
+
} from '@v12-engine/engine'
|
|
29
|
+
import { ENEMY, PLAYER } from '../common/units'
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Adds a pack to the game unless it has one of that id already: the game is one for every example, and two of a pack
|
|
33
|
+
* would run its rules twice
|
|
34
|
+
*/
|
|
35
|
+
export function packOnce(game: Game<any>, pack: GamePack): void {
|
|
36
|
+
if ((game.toRules().packs ?? []).some((each) => each.id === pack.id)) return
|
|
37
|
+
game.pack(pack)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The packs the engine's abilities need beyond its core: moves (dash to a unit, swap, attach), ground objects (traps,
|
|
42
|
+
* walls, barrels), holds (hold to charge, aimed channels), pools (energy, fury, heat, ammo), vision (marks, camouflage,
|
|
43
|
+
* true sight), changing abilities (grant, swap, steal) and mana burn. Each does nothing to a unit that uses none of it
|
|
44
|
+
*/
|
|
45
|
+
export function abilityPacks(game: Game<any>): void {
|
|
46
|
+
for (const pack of [movesPack(), groundObjectsPack(), holdsPack(), resourcesPack(), visionPack(), abilityChangesPack(), manaBurnPack()])
|
|
47
|
+
packOnce(game, pack)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* A caster for an example: a mage of plenty of mana, quick to get it back, who knows `abilities` (each at level 1). It
|
|
52
|
+
* strikes nothing of its own (no reach to find a fight), so what it does is what it is told
|
|
53
|
+
*/
|
|
54
|
+
export const caster = (id: string, name: string, abilities: readonly string[], own: Partial<UnitTypeDef> = {}): UnitTypeDef => ({
|
|
55
|
+
id,
|
|
56
|
+
name,
|
|
57
|
+
kind: 'unit',
|
|
58
|
+
radius: 0.45,
|
|
59
|
+
stats: { health: 1200, healthRegen: 4, mana: 1000, manaRegen: 60, attackDamage: 30, attackSpeed: 1, attackRange: 6, moveSpeed: 4.2 },
|
|
60
|
+
sight: 12,
|
|
61
|
+
attacks: [{ damagePoint: 0.5, acquisitionRange: 0, projectileSpeed: 20 }],
|
|
62
|
+
abilities: [...abilities],
|
|
63
|
+
...own
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Something to cast on: a footman of a side who never strikes back (no attack), holds its ground until moved, and heals
|
|
68
|
+
* fast, so a cast can be tried again and again
|
|
69
|
+
*/
|
|
70
|
+
export const sparring = (id: string, name: string, own: Partial<UnitTypeDef> = {}): UnitTypeDef => ({
|
|
71
|
+
id,
|
|
72
|
+
name,
|
|
73
|
+
kind: 'unit',
|
|
74
|
+
radius: 0.45,
|
|
75
|
+
stats: { health: 900, healthRegen: 30, mana: 300, manaRegen: 5, moveSpeed: 3.2, armor: 5 },
|
|
76
|
+
sight: 8,
|
|
77
|
+
...own
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
/** The point `distance` from `from` toward `to` */
|
|
81
|
+
export function toward(from: Vec2, to: Vec2, distance: number): Vec2 {
|
|
82
|
+
const dx = to.x - from.x
|
|
83
|
+
const dz = to.z - from.z
|
|
84
|
+
const length = exactMath.hypot(dx, dz) || 1
|
|
85
|
+
return { x: from.x + (dx / length) * distance, z: from.z + (dz / length) * distance }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The living enemy of `of` nearest to it, of the other side */
|
|
89
|
+
export function nearestEnemy(world: World, of: Entity): Entity | undefined {
|
|
90
|
+
let best: Entity | undefined
|
|
91
|
+
let far = Infinity
|
|
92
|
+
for (const unit of world.units.values()) {
|
|
93
|
+
if (!unit.alive || unit.hidden || !world.areEnemies(of.owner, unit.owner)) continue
|
|
94
|
+
const d = exactMath.hypot(unit.pos.x - of.pos.x, unit.pos.z - of.pos.z)
|
|
95
|
+
if (d < far) {
|
|
96
|
+
far = d
|
|
97
|
+
best = unit
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return best
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Casts an ability as a player would, aimed for them: the unit named `by` (`caster` when not said) casts `ability` on
|
|
105
|
+
* the unit named `at`, or at `point`. With neither, it aims as the ability asks: an enemy's ability at the nearest
|
|
106
|
+
* enemy, an ally's at the unit named `ally` (itself where there is none), a point or a direction at the nearest enemy,
|
|
107
|
+
* nothing at nothing. Cast again while its window is open, it is the recast
|
|
108
|
+
*/
|
|
109
|
+
export function castFor(world: World, player: number, ability: string, options: { by?: string; at?: string; point?: Vec2 } = {}): void {
|
|
110
|
+
const unit = world.named(options.by ?? 'caster')
|
|
111
|
+
if (!unit || unit.owner !== player || !world.registry.abilities.has(ability)) return
|
|
112
|
+
const def: AbilityDef = world.registry.ability(ability)
|
|
113
|
+
const kind = aimKind(world.registry, def)
|
|
114
|
+
const named = options.at ? world.named(options.at) : undefined
|
|
115
|
+
const friendly = def.targets && !def.targets.enemies && (def.targets.allies || def.targets.self)
|
|
116
|
+
const target = named ?? (friendly ? (world.named('ally') ?? unit) : nearestEnemy(world, unit))
|
|
117
|
+
const at = options.point ?? target?.pos
|
|
118
|
+
switch (kind) {
|
|
119
|
+
case 'none':
|
|
120
|
+
return unit.order('cast', { ability })
|
|
121
|
+
case 'unit':
|
|
122
|
+
return target ? unit.order('cast', { ability, target: target.id }) : undefined
|
|
123
|
+
case 'point':
|
|
124
|
+
case 'direction':
|
|
125
|
+
return at ? unit.order('cast', { ability, point: { x: at.x, z: at.z } }) : undefined
|
|
126
|
+
case 'vector': {
|
|
127
|
+
// from beside the target, across it
|
|
128
|
+
if (!at) return
|
|
129
|
+
const side = { x: at.x - (at.z - unit.pos.z) * 0.6, z: at.z + (at.x - unit.pos.x) * 0.6 }
|
|
130
|
+
return unit.order('cast', { ability, point: side, to: { x: at.x, z: at.z } })
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** The command a card button sends: casts an ability for its player (`castFor`) */
|
|
136
|
+
export const cast = defineCommand(
|
|
137
|
+
'abilities.cast',
|
|
138
|
+
{ ability: string, by: optional(string), at: optional(string), point: optional(point) },
|
|
139
|
+
(world, player, { ability, by, at, point }) => castFor(world, player, ability, { by, at, point })
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
/** Lets go of a held ability (hold to charge, release to fire) of the unit named `by`, toward the nearest enemy */
|
|
143
|
+
export const release = defineCommand('abilities.release', { by: optional(string) }, (world, player, { by }) => {
|
|
144
|
+
const unit = world.named(by ?? 'caster')
|
|
145
|
+
if (!unit || unit.owner !== player) return
|
|
146
|
+
const enemy = nearestEnemy(world, unit)
|
|
147
|
+
releaseHold(world, unit, enemy ? { x: enemy.pos.x, z: enemy.pos.z } : undefined)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
/** Each example's start by its id, for the reset command to lay one out again */
|
|
151
|
+
export const starts: Record<string, (world: World) => void> = {}
|
|
152
|
+
|
|
153
|
+
/** Takes every unit away and lays the example out again as it started (`starts`) */
|
|
154
|
+
export const reset = defineCommand('abilities.reset', { example: string }, (world, _player, { example }) => {
|
|
155
|
+
const start = starts[example]
|
|
156
|
+
if (!start) return
|
|
157
|
+
for (const unit of [...world.units.values()]) if (unit.alive) world.kill(unit, undefined, false, 'removed')
|
|
158
|
+
start(world)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
/** Puts a unit down for a side, named when it is to be found again, facing `facing` (radians; toward +z is 0) */
|
|
162
|
+
export function put(world: World, type: string, owner: number, at: Vec2, name?: string, facing = 0): Entity {
|
|
163
|
+
return world.spawn(type, { at, owner, facing, ...(name && { name }) })
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** The usual lay-out of a family: the caster at the south, an ally by it, enemies in a row to the north */
|
|
167
|
+
export function lineUp(world: World, casterType: string, enemyType: string, count = 3, ally?: string): void {
|
|
168
|
+
put(world, casterType, PLAYER, { x: 24, z: 18 }, 'caster')
|
|
169
|
+
if (ally) put(world, ally, PLAYER, { x: 21.5, z: 17 }, 'ally')
|
|
170
|
+
for (let i = 0; i < count; i++)
|
|
171
|
+
put(world, enemyType, ENEMY, { x: 24 + (i - (count - 1) / 2) * 2.5, z: 26 + (i % 2) * 1.2 }, `enemy${i + 1}`, Math.PI)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Registers the shared commands once on the game */
|
|
175
|
+
export function kitRules(game: Game<any>): void {
|
|
176
|
+
game.command(cast)
|
|
177
|
+
game.command(release)
|
|
178
|
+
game.command(reset)
|
|
179
|
+
}
|