@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,1025 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AbilityDef,
|
|
3
|
+
type AttackDef,
|
|
4
|
+
applyBuff,
|
|
5
|
+
type BuffDef,
|
|
6
|
+
type BuffInstance,
|
|
7
|
+
defineComponent,
|
|
8
|
+
defineEffect,
|
|
9
|
+
defineHook,
|
|
10
|
+
type EffectDef,
|
|
11
|
+
type EffectType,
|
|
12
|
+
type EffectValueContext,
|
|
13
|
+
type Entity,
|
|
14
|
+
hasBuff,
|
|
15
|
+
heroOf,
|
|
16
|
+
holdsUp,
|
|
17
|
+
type HookDef,
|
|
18
|
+
invalidateStats,
|
|
19
|
+
removeBuff,
|
|
20
|
+
type UnitTypeDef,
|
|
21
|
+
type World
|
|
22
|
+
} from '@v12-engine/engine'
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* The heroes: four of the Kingdom, trained at its altar, and four of the Restless at theirs; a tavern hires any of them
|
|
26
|
+
* (items-and-spells plan, phase 4). Each has callings (what it is good at, 1 to 3), an innate it has from level 1 that
|
|
27
|
+
* changes how it is played, three abilities and an ultimate that changes the shape of a fight, and two of the engine's
|
|
28
|
+
* hero spells (`defaultHeroSpells`). What they do is said in general words (heroes, creeps, buildings, allies, kills),
|
|
29
|
+
* so a hero fits a game of bases, a campaign and one of lanes alike. What the engine's fields do not say is done here in
|
|
30
|
+
* code: the hooks and the effect at the end of this file
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
// the game's own effects, each with the fields it reads (`EffectTypes`)
|
|
34
|
+
declare module '@v12-engine/engine' {
|
|
35
|
+
interface EffectTypes {
|
|
36
|
+
/** Lightning that leaps from where a spell went off to the nearest other enemy within `radius`: Static Charge */
|
|
37
|
+
staticArc: { amount: number; perLevel: number; radius: number }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** What every hero grows by, a level: the stats of a fighter up close, of one from afar, of a caster */
|
|
42
|
+
const GROWTH = {
|
|
43
|
+
melee: { stats: { health: 99, healthRegen: 0.1, mana: 55, manaRegen: 0.2, attackDamage: 3.5, armor: 4.3, magicResist: 2.05 }, percent: { attackSpeed: 0.02 } },
|
|
44
|
+
ranged: { stats: { health: 88, healthRegen: 0.09, mana: 40, manaRegen: 0.15, attackDamage: 3.4, armor: 3.8, magicResist: 1.5 }, percent: { attackSpeed: 0.03 } },
|
|
45
|
+
caster: { stats: { health: 85, healthRegen: 0.1, mana: 64, manaRegen: 0.2, attackDamage: 3, armor: 4, magicResist: 1.6 }, percent: { attackSpeed: 0.02 } }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** What a hero of each side costs and takes to train or hire */
|
|
49
|
+
const HERO = { kind: 'unit', radius: 0.5, sight: 12, inventory: 6, cost: { gold: 425, lumber: 100 }, supply: 5, buildTime: 45, threat: 3 } as const
|
|
50
|
+
|
|
51
|
+
/** A hero's ranged attack of magic, a bolt that flies */
|
|
52
|
+
const BOLT: AttackDef = { damageType: 'magic', damagePoint: 0.4, acquisitionRange: 8, projectileSpeed: 18, targets: ['ground', 'air'] }
|
|
53
|
+
|
|
54
|
+
// ---- the Kingdom
|
|
55
|
+
|
|
56
|
+
export const oathbreaker: UnitTypeDef = {
|
|
57
|
+
...HERO,
|
|
58
|
+
id: 'oathbreaker',
|
|
59
|
+
race: 'human',
|
|
60
|
+
name: 'Oathbreaker',
|
|
61
|
+
stats: { health: 660, healthRegen: 1.3, mana: 280, manaRegen: 1.6, attackDamage: 54, attackSpeed: 0.625, attackRange: 0.6, armor: 42, magicResist: 32, moveSpeed: 4.25 },
|
|
62
|
+
attacks: [{ damagePoint: 0.45, acquisitionRange: 8 }],
|
|
63
|
+
hero: {
|
|
64
|
+
growth: GROWTH.melee,
|
|
65
|
+
callings: { vanguard: 3, keeper: 1 },
|
|
66
|
+
innate: 'bulwark',
|
|
67
|
+
learns: ['oathCharge', 'shieldwall', 'ironResolve', 'lastStand'],
|
|
68
|
+
spells: ['flash', 'waystone']
|
|
69
|
+
},
|
|
70
|
+
hotkey: 'O'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const dawnPriest: UnitTypeDef = {
|
|
74
|
+
...HERO,
|
|
75
|
+
id: 'dawnPriest',
|
|
76
|
+
race: 'human',
|
|
77
|
+
name: 'Dawn Priest',
|
|
78
|
+
stats: { health: 560, healthRegen: 1.1, mana: 480, manaRegen: 1.9, attackDamage: 46, attackSpeed: 0.65, attackRange: 6.5, armor: 22, magicResist: 32, moveSpeed: 4.1 },
|
|
79
|
+
attacks: [{ ...BOLT }],
|
|
80
|
+
hero: {
|
|
81
|
+
growth: GROWTH.caster,
|
|
82
|
+
callings: { keeper: 3, arcanist: 1 },
|
|
83
|
+
innate: 'mercysEcho',
|
|
84
|
+
innateLevels: [1, 4, 7, 10],
|
|
85
|
+
learns: ['holyLight', 'cleansingFlame', 'dawnlightAura', 'sanctuary'],
|
|
86
|
+
spells: ['secondWind', 'purify']
|
|
87
|
+
},
|
|
88
|
+
hotkey: 'D'
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const stormcaller: UnitTypeDef = {
|
|
92
|
+
...HERO,
|
|
93
|
+
id: 'stormcaller',
|
|
94
|
+
race: 'human',
|
|
95
|
+
name: 'Stormcaller',
|
|
96
|
+
stats: { health: 570, healthRegen: 1.1, mana: 400, manaRegen: 2.1, attackDamage: 52, attackSpeed: 0.625, attackRange: 6.9, armor: 26, magicResist: 30, moveSpeed: 4.1 },
|
|
97
|
+
attacks: [{ ...BOLT }],
|
|
98
|
+
hero: {
|
|
99
|
+
growth: GROWTH.caster,
|
|
100
|
+
callings: { arcanist: 3, binder: 2 },
|
|
101
|
+
innate: 'staticCharge',
|
|
102
|
+
learns: ['tornado', 'chainLightning', 'galeWard', 'eyeOfTheStorm'],
|
|
103
|
+
spells: ['flash', 'purify']
|
|
104
|
+
},
|
|
105
|
+
hotkey: 'S'
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export const galeHunter: UnitTypeDef = {
|
|
109
|
+
...HERO,
|
|
110
|
+
id: 'galeHunter',
|
|
111
|
+
race: 'human',
|
|
112
|
+
name: 'Gale Hunter',
|
|
113
|
+
stats: { health: 540, healthRegen: 1, mana: 300, manaRegen: 1.4, attackDamage: 56, attackSpeed: 0.7, attackRange: 7.5, armor: 24, magicResist: 28, moveSpeed: 4.3 },
|
|
114
|
+
attacks: [{ damagePoint: 0.3, acquisitionRange: 9, projectileSpeed: 24, targets: ['ground', 'air'] }],
|
|
115
|
+
hero: {
|
|
116
|
+
growth: GROWTH.ranged,
|
|
117
|
+
callings: { ranger: 3, roamer: 1 },
|
|
118
|
+
innate: 'steadyAim',
|
|
119
|
+
learns: ['piercingBolt', 'tumble', 'huntsman', 'arrowstorm'],
|
|
120
|
+
spells: ['flash', 'barrier']
|
|
121
|
+
},
|
|
122
|
+
hotkey: 'G'
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ---- the Restless
|
|
126
|
+
|
|
127
|
+
export const gravelord: UnitTypeDef = {
|
|
128
|
+
...HERO,
|
|
129
|
+
id: 'gravelord',
|
|
130
|
+
race: 'undead',
|
|
131
|
+
name: 'Gravelord',
|
|
132
|
+
// no mana: what he casts costs him health (Blood Tithe)
|
|
133
|
+
stats: { health: 700, healthRegen: 1.6, attackDamage: 54, attackSpeed: 0.65, attackRange: 0.6, armor: 38, magicResist: 32, moveSpeed: 4.4 },
|
|
134
|
+
attacks: [{ damagePoint: 0.45, acquisitionRange: 8 }],
|
|
135
|
+
hero: {
|
|
136
|
+
growth: { ...GROWTH.melee, stats: { ...GROWTH.melee.stats, mana: 0, manaRegen: 0, health: 108, healthRegen: 0.14 } },
|
|
137
|
+
callings: { reaver: 3, vanguard: 1, keeper: 1 },
|
|
138
|
+
innate: 'bloodTithe',
|
|
139
|
+
innateLevels: [1, 4, 7, 10],
|
|
140
|
+
learns: ['graveLash', 'siphon', 'deathlessAura', 'armyOfTheGrave'],
|
|
141
|
+
spells: ['phantomStride', 'brand']
|
|
142
|
+
},
|
|
143
|
+
hotkey: 'G'
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const rimeWitch: UnitTypeDef = {
|
|
147
|
+
...HERO,
|
|
148
|
+
id: 'rimeWitch',
|
|
149
|
+
race: 'undead',
|
|
150
|
+
name: 'Rime Witch',
|
|
151
|
+
stats: { health: 550, healthRegen: 0.9, mana: 420, manaRegen: 2, attackDamage: 44, attackSpeed: 0.6, attackRange: 6.5, armor: 24, magicResist: 30, moveSpeed: 4.1 },
|
|
152
|
+
attacks: [{ ...BOLT }],
|
|
153
|
+
hero: {
|
|
154
|
+
growth: GROWTH.caster,
|
|
155
|
+
callings: { binder: 3, arcanist: 2 },
|
|
156
|
+
innate: 'shatter',
|
|
157
|
+
innateLevels: [1, 5, 9],
|
|
158
|
+
learns: ['rimeBolt', 'grasp', 'hoarfrost', 'whiteout'],
|
|
159
|
+
spells: ['flash', 'barrier']
|
|
160
|
+
},
|
|
161
|
+
hotkey: 'R'
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export const duskblade: UnitTypeDef = {
|
|
165
|
+
...HERO,
|
|
166
|
+
id: 'duskblade',
|
|
167
|
+
race: 'undead',
|
|
168
|
+
name: 'Duskblade',
|
|
169
|
+
stats: { health: 560, healthRegen: 1.1, mana: 300, manaRegen: 1.5, attackDamage: 50, attackSpeed: 0.8, attackRange: 0.6, armor: 30, magicResist: 28, moveSpeed: 4.5 },
|
|
170
|
+
attacks: [{ damagePoint: 0.3, acquisitionRange: 8 }],
|
|
171
|
+
hero: {
|
|
172
|
+
growth: { ...GROWTH.melee, percent: { attackSpeed: 0.03 } },
|
|
173
|
+
callings: { stalker: 3, roamer: 2 },
|
|
174
|
+
innate: 'shadowStep',
|
|
175
|
+
innateLevels: [1, 5, 9],
|
|
176
|
+
learns: ['duskMark', 'fade', 'twinFangs', 'eclipse'],
|
|
177
|
+
spells: ['flash', 'brand']
|
|
178
|
+
},
|
|
179
|
+
hotkey: 'D'
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export const boneReaver: UnitTypeDef = {
|
|
183
|
+
...HERO,
|
|
184
|
+
id: 'boneReaver',
|
|
185
|
+
race: 'undead',
|
|
186
|
+
name: 'Bone Reaver',
|
|
187
|
+
stats: { health: 640, healthRegen: 1.4, mana: 260, manaRegen: 1.5, attackDamage: 55, attackSpeed: 0.65, attackRange: 0.6, armor: 36, magicResist: 30, moveSpeed: 4.35 },
|
|
188
|
+
attacks: [{ damagePoint: 0.45, acquisitionRange: 8 }],
|
|
189
|
+
hero: {
|
|
190
|
+
growth: GROWTH.melee,
|
|
191
|
+
callings: { roamer: 3, reaver: 2 },
|
|
192
|
+
innate: 'harvest',
|
|
193
|
+
meter: 'harvest',
|
|
194
|
+
learns: ['boneCleaver', 'gorge', 'carrionHunger', 'bonePit'],
|
|
195
|
+
spells: ['reap', 'phantomStride']
|
|
196
|
+
},
|
|
197
|
+
hotkey: 'B'
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** The Kingdom's four, trained at its altar */
|
|
201
|
+
export const KINGDOM_HEROES = [oathbreaker, dawnPriest, stormcaller, galeHunter].map((hero) => hero.id)
|
|
202
|
+
/** The Restless' four, raised at theirs */
|
|
203
|
+
export const HOLLOW_HEROES = [gravelord, rimeWitch, duskblade, boneReaver].map((hero) => hero.id)
|
|
204
|
+
|
|
205
|
+
export const heroes: UnitTypeDef[] = [oathbreaker, dawnPriest, stormcaller, galeHunter, gravelord, rimeWitch, duskblade, boneReaver]
|
|
206
|
+
|
|
207
|
+
// ---- what they do
|
|
208
|
+
|
|
209
|
+
/** Stormcaller's every fourth spell, as the stacks of Static Charge are full: free, and leaping to one more */
|
|
210
|
+
const CHARGED: EffectDef = { type: 'charged', effects: [{ type: 'staticArc', amount: 40, perLevel: 10, radius: 8 }] }
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Whom a hero's areas strike: its enemies, the camps' creeps among them (the creeps' player is everyone's enemy), and
|
|
214
|
+
* not what is nobody's (a shop, a critter). Said outright: a buff's area goes to allies when it says nothing
|
|
215
|
+
*/
|
|
216
|
+
const FOES = { affects: 'enemies' } as const
|
|
217
|
+
/** An area's effect on foes that are not heroes */
|
|
218
|
+
const FOES_NOT_HEROES = { affects: 'enemies', where: (_context: EffectValueContext, unit: Entity) => !heroOf(unit) } as const
|
|
219
|
+
|
|
220
|
+
/** How many kills a hero's death counts as, to Harvest */
|
|
221
|
+
const HERO_HARVEST = 5
|
|
222
|
+
/** The most Harvest holds */
|
|
223
|
+
const MOST_HARVEST = 50
|
|
224
|
+
|
|
225
|
+
export const heroBuffs: BuffDef[] = [
|
|
226
|
+
// Oathbreaker
|
|
227
|
+
{ id: 'bulwarkWard', name: 'Bulwark', description: 'An Oathbreaker near takes a fifth of the damage this hero takes', tags: ['guard'] },
|
|
228
|
+
{ id: 'taunted', name: 'Taunted', description: 'Made to attack whoever taunted it', harmful: true, control: 'taunt', tags: ['control'] },
|
|
229
|
+
{ id: 'shieldwall', name: 'Shieldwall', description: 'A shield takes the damage', duration: 4, shield: [80, 140, 200], shieldScaling: { abilityPower: 0.4 }, tags: ['shield'] },
|
|
230
|
+
{ id: 'lastStand', name: 'Last Stand', description: 'Takes 30% less damage', stats: { incomingDamage: -0.3 }, tags: ['defence'] },
|
|
231
|
+
// Dawn Priest
|
|
232
|
+
{ id: 'mercysEcho', name: 'Mercy’s Echo', description: 'Healing past full health becomes a shield', overheal: { buff: 'echoShield', part: [0.4, 0.6, 0.8, 1], most: 0.25 } },
|
|
233
|
+
{ id: 'echoShield', name: 'Echo', description: 'Healing past full health, held as a shield', duration: 5, tags: ['shield'] },
|
|
234
|
+
{ id: 'dawnlight', name: 'Dawnlight', description: 'Heals over time, and healing it gets is stronger', stats: { healthRegen: [1, 2, 3], incomingHealing: [0.1, 0.15, 0.2] } },
|
|
235
|
+
{
|
|
236
|
+
id: 'sanctuary',
|
|
237
|
+
name: 'Sanctuary',
|
|
238
|
+
description: 'A blow that would kill it leaves it at 30% health and untouchable for 2 seconds instead',
|
|
239
|
+
duration: 6,
|
|
240
|
+
passive: [{ type: 'onFatal', health: 0.3, cooldown: 6, effects: [{ type: 'buff', buff: 'hallowed', on: 'caster' }, { type: 'removeBuff', buff: 'sanctuary', on: 'caster' }] }],
|
|
241
|
+
tags: ['save']
|
|
242
|
+
},
|
|
243
|
+
{ id: 'hallowed', name: 'Hallowed', description: 'Takes no damage', duration: 2, invulnerable: true, dispellable: false },
|
|
244
|
+
// Stormcaller
|
|
245
|
+
{ id: 'staticCharge', name: 'Static Charge', description: 'Every fourth spell costs no mana and leaps to one more enemy', maxStacks: 3 },
|
|
246
|
+
{ id: 'buffeted', name: 'Buffeted', description: 'Moves slower', harmful: true, duration: 1.5, percent: { moveSpeed: [-0.2, -0.3, -0.4] }, tags: ['slow'] },
|
|
247
|
+
{ id: 'stormSlow', name: 'In the Storm', description: 'Moves 35% slower', harmful: true, percent: { moveSpeed: -0.35 }, tags: ['slow'] },
|
|
248
|
+
// Gale Hunter
|
|
249
|
+
{
|
|
250
|
+
id: 'steadyAim',
|
|
251
|
+
name: 'Steady Aim',
|
|
252
|
+
description: 'Standing still steadies the aim: more range and critical chance, lost as soon as it moves',
|
|
253
|
+
// half a second still at a time adds a stack, moving takes them off (`steadyTick`)
|
|
254
|
+
onTick: { every: 0.5, run: steadyTick }
|
|
255
|
+
},
|
|
256
|
+
{ id: 'steadied', name: 'Steadied', description: '+0.5 range and +6% critical chance for every half second still', maxStacks: 4, perStack: true, stats: { attackRange: 0.5, critChance: 0.06 } },
|
|
257
|
+
{
|
|
258
|
+
id: 'tumbleShot',
|
|
259
|
+
name: 'Tumble',
|
|
260
|
+
description: 'The next attack deals more',
|
|
261
|
+
duration: 4,
|
|
262
|
+
passive: [{ type: 'onHit', effects: [{ type: 'damage', amount: [40, 70, 100], damageType: 'physical' }] }],
|
|
263
|
+
breaksOn: ['attack']
|
|
264
|
+
},
|
|
265
|
+
{ id: 'pinned', name: 'Pinned', description: 'Moves 50% slower', harmful: true, percent: { moveSpeed: -0.5 }, tags: ['slow'] },
|
|
266
|
+
// Gravelord
|
|
267
|
+
{ id: 'bloodTithe', name: 'Blood Tithe', description: 'Abilities cost health, not mana, and damage dealt heals', paysWithHealth: 1, stats: { omnivamp: [0.08, 0.1, 0.12, 0.14] } },
|
|
268
|
+
{ id: 'graveChill', name: 'Grave Chill', description: 'Moves 30% slower', harmful: true, duration: 1.5, percent: { moveSpeed: -0.3 }, tags: ['slow'] },
|
|
269
|
+
{ id: 'deathless', name: 'Deathless Aura', description: 'Moves faster and heals near a Gravelord', percent: { moveSpeed: [0.1, 0.15, 0.2] }, stats: { healthRegen: [1, 2, 3] } },
|
|
270
|
+
{ id: 'graveRallied', name: 'Grave Rally', description: 'Its attacks heal it', duration: 8, stats: { lifeSteal: 0.2 } },
|
|
271
|
+
// Rime Witch
|
|
272
|
+
{ id: 'shatterSense', name: 'Shatter', description: 'Who she holds takes more from the next blow of her allies, which ends the hold' },
|
|
273
|
+
{ id: 'frozen', name: 'Frozen', description: 'Frozen solid: cannot do anything', harmful: true, stun: true, tags: ['hold', 'frost'] },
|
|
274
|
+
{ id: 'iceRooted', name: 'Grasping Ice', description: 'Held by the ice: cannot move', harmful: true, root: true, tags: ['hold', 'frost'] },
|
|
275
|
+
{ id: 'frostbitten', name: 'Frostbite', description: 'Moves slower', harmful: true, duration: 1.5, percent: { moveSpeed: [-0.2, -0.25, -0.3] }, tags: ['slow', 'frost'] },
|
|
276
|
+
{ id: 'whiteoutChill', name: 'Whiteout', description: 'Moves 30% slower', harmful: true, percent: { moveSpeed: -0.3 }, tags: ['slow', 'frost'] },
|
|
277
|
+
{
|
|
278
|
+
id: 'frostbite',
|
|
279
|
+
name: 'Freezing',
|
|
280
|
+
description: 'Two seconds in the storm freezes it',
|
|
281
|
+
harmful: true,
|
|
282
|
+
duration: 0.5,
|
|
283
|
+
maxStacks: 8,
|
|
284
|
+
onMaxStacks: [{ type: 'buff', buff: 'frozen', duration: 1.5 }, { type: 'removeBuff', buff: 'frostbite' }],
|
|
285
|
+
tags: ['frost']
|
|
286
|
+
},
|
|
287
|
+
// Duskblade
|
|
288
|
+
{
|
|
289
|
+
id: 'shadowWatch',
|
|
290
|
+
name: 'Shadow Step',
|
|
291
|
+
description: 'Three seconds out of a fight hides it in the shadows',
|
|
292
|
+
onTick: { every: 0.25, run: (world, carrier, instance) => shadowTick(world, carrier, instance) }
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
id: 'shadowed',
|
|
296
|
+
name: 'In the Shadows',
|
|
297
|
+
description: 'Seen only from up close; its first strike from the shadows is a critical one and silences',
|
|
298
|
+
camouflage: 2.5,
|
|
299
|
+
passive: [
|
|
300
|
+
{
|
|
301
|
+
type: 'onHit',
|
|
302
|
+
effects: [
|
|
303
|
+
{ type: 'damage', amount: 0, scaling: { attackDamage: [0.75, 1, 1.25] }, damageType: 'physical' },
|
|
304
|
+
{ type: 'buff', buff: 'hushed', duration: 1.5 }
|
|
305
|
+
]
|
|
306
|
+
}
|
|
307
|
+
],
|
|
308
|
+
breaksOn: ['attack', 'cast']
|
|
309
|
+
},
|
|
310
|
+
{ id: 'hushed', name: 'Silenced', description: 'Cannot use its abilities', harmful: true, silence: true, tags: ['hold'] },
|
|
311
|
+
{
|
|
312
|
+
id: 'duskMark',
|
|
313
|
+
name: 'Dusk Mark',
|
|
314
|
+
description: 'Takes more damage from everyone, is seen wherever it goes, and bursts when the mark runs out',
|
|
315
|
+
harmful: true,
|
|
316
|
+
duration: 4,
|
|
317
|
+
revealed: true,
|
|
318
|
+
stats: { incomingDamage: [0.1, 0.15, 0.2] },
|
|
319
|
+
onEnd: [{ type: 'damage', amount: [60, 100, 140], damageType: 'magic', radius: 0.5 }],
|
|
320
|
+
tags: ['mark']
|
|
321
|
+
},
|
|
322
|
+
{ id: 'faded', name: 'Fade', description: 'Walks through units, faster, and seen only from up close', duration: 2, phasing: true, camouflage: 1.5, percent: { moveSpeed: 0.4 }, breaksOn: ['attack'] },
|
|
323
|
+
{ id: 'eclipsed', name: 'Eclipse', description: 'Silenced, cannot be healed, and seen wherever it goes', harmful: true, duration: 3, silence: true, revealed: true, stats: { incomingHealing: -1 }, tags: ['hold', 'anti-heal'] },
|
|
324
|
+
// Bone Reaver
|
|
325
|
+
{
|
|
326
|
+
id: 'harvest',
|
|
327
|
+
name: 'Harvest',
|
|
328
|
+
description: 'More health and damage for every kill; a hero counts as five, and half of it is lost on death',
|
|
329
|
+
maxStacks: MOST_HARVEST,
|
|
330
|
+
// what it reaped is kept on the hero (`harvested`), through deaths and forms: the stacks and the stats are of it
|
|
331
|
+
onApply: (_world, carrier, instance) => void (instance.stacks = carrier.get(harvested)?.count ?? 0),
|
|
332
|
+
change: (_world, carrier) => {
|
|
333
|
+
const count = carrier.get(harvested)?.count ?? 0
|
|
334
|
+
return count ? { stats: { health: 6 * count, attackDamage: 1 * count } } : undefined
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
{ id: 'carrionHunger', name: 'Carrion Hunger', description: 'Blows from up close strike those around the target too', cleave: { part: [0.3, 0.45, 0.6], radius: 2.5, angle: 1.6 } },
|
|
338
|
+
{ id: 'bonePitFrenzy', name: 'Bone Pit', description: 'Attacks faster, and its attacks heal it', percent: { attackSpeed: 0.3 }, stats: { lifeSteal: 0.25 } }
|
|
339
|
+
]
|
|
340
|
+
|
|
341
|
+
export const heroAbilities: AbilityDef[] = [
|
|
342
|
+
// ---- Oathbreaker: charges in, taunts, shields who stands behind him
|
|
343
|
+
{
|
|
344
|
+
id: 'bulwark',
|
|
345
|
+
name: 'Bulwark',
|
|
346
|
+
description: 'Takes a fifth of the damage allied heroes near him take, before their armor',
|
|
347
|
+
target: 'passive',
|
|
348
|
+
maxLevel: 1,
|
|
349
|
+
effects: [{ type: 'aura', buff: 'bulwarkWard', radius: 8, only: 'heroes' }]
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
id: 'oathCharge',
|
|
353
|
+
name: 'Oath Charge',
|
|
354
|
+
description: 'Charges to a spot: enemies around where he lands take 60, 100 or 140 (+50% attack damage) and are made to attack him for 1.25 to 1.75 seconds',
|
|
355
|
+
hotkey: 'Q',
|
|
356
|
+
target: 'point',
|
|
357
|
+
range: 8,
|
|
358
|
+
manaCost: 80,
|
|
359
|
+
cooldown: [14, 12, 10],
|
|
360
|
+
effects: [
|
|
361
|
+
{
|
|
362
|
+
type: 'dash',
|
|
363
|
+
distance: 8,
|
|
364
|
+
effects: [
|
|
365
|
+
{ type: 'damage', amount: [60, 100, 140], scaling: { attackDamage: 0.5 }, damageType: 'physical', radius: 2.5, ...FOES },
|
|
366
|
+
{ type: 'buff', buff: 'taunted', duration: [1.25, 1.5, 1.75], radius: 2.5, ...FOES }
|
|
367
|
+
]
|
|
368
|
+
}
|
|
369
|
+
]
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
id: 'shieldwall',
|
|
373
|
+
name: 'Shieldwall',
|
|
374
|
+
description: 'He and the allies around him get a shield of 80, 140 or 200 (+40% ability power) for 4 seconds',
|
|
375
|
+
hotkey: 'W',
|
|
376
|
+
target: 'none',
|
|
377
|
+
manaCost: 90,
|
|
378
|
+
cooldown: 16,
|
|
379
|
+
effects: [{ type: 'buff', buff: 'shieldwall', radius: 7, affects: 'allies' }]
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
id: 'ironResolve',
|
|
383
|
+
name: 'Iron Resolve',
|
|
384
|
+
description: 'Whoever strikes him from up close takes 12, 22 or 32 magic damage back',
|
|
385
|
+
target: 'passive',
|
|
386
|
+
effects: [{ type: 'onDamaged', melee: true, attack: true, effects: [{ type: 'damage', amount: [12, 22, 32], damageType: 'magic' }] }]
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
id: 'lastStand',
|
|
390
|
+
name: 'Last Stand',
|
|
391
|
+
description: 'Enemies around him are made to attack him for 2 seconds, and for 6 seconds allies who stay near him take 30% less damage',
|
|
392
|
+
hotkey: 'R',
|
|
393
|
+
target: 'none',
|
|
394
|
+
manaCost: 150,
|
|
395
|
+
cooldown: 100,
|
|
396
|
+
maxLevel: 1,
|
|
397
|
+
ultimate: true,
|
|
398
|
+
effects: [
|
|
399
|
+
{ type: 'buff', buff: 'taunted', duration: 2, radius: 6, ...FOES },
|
|
400
|
+
{ type: 'zone', follow: 'caster', duration: 6, radius: 7, effects: [{ type: 'buff', buff: 'lastStand', duration: 0.5, radius: 7, affects: 'allies' }] }
|
|
401
|
+
]
|
|
402
|
+
},
|
|
403
|
+
|
|
404
|
+
// ---- Dawn Priest: heals, cleanses, and an ultimate that saves a life
|
|
405
|
+
{
|
|
406
|
+
id: 'mercysEcho',
|
|
407
|
+
name: 'Mercy’s Echo',
|
|
408
|
+
description: 'Healing she gives past full health becomes a shield on whoever she healed: 40% of it, up to all of it as she grows, at most a quarter of their health',
|
|
409
|
+
target: 'passive',
|
|
410
|
+
maxLevel: 4,
|
|
411
|
+
effects: [{ type: 'buff', buff: 'mercysEcho' }]
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
id: 'holyLight',
|
|
415
|
+
name: 'Holy Light',
|
|
416
|
+
description: 'Heals an ally for 200, 350 or 500 (+80% ability power)',
|
|
417
|
+
hotkey: 'Q',
|
|
418
|
+
target: 'unit',
|
|
419
|
+
targets: { allies: true, self: false },
|
|
420
|
+
manaCost: 65,
|
|
421
|
+
cooldown: 5,
|
|
422
|
+
range: 8,
|
|
423
|
+
castTime: 0.4,
|
|
424
|
+
effects: [{ type: 'heal', amount: [200, 350, 500], scaling: { abilityPower: 0.8 } }]
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
id: 'cleansingFlame',
|
|
428
|
+
name: 'Cleansing Flame',
|
|
429
|
+
description: 'A flame in an area: allies in it are freed of what holds them and healed 60, 100 or 140, enemies burn for as much (+50% ability power)',
|
|
430
|
+
hotkey: 'W',
|
|
431
|
+
target: 'point',
|
|
432
|
+
range: 8,
|
|
433
|
+
manaCost: 90,
|
|
434
|
+
cooldown: 10,
|
|
435
|
+
castTime: 0.3,
|
|
436
|
+
effects: [
|
|
437
|
+
{ type: 'cleanse', radius: 3 },
|
|
438
|
+
{ type: 'heal', amount: [60, 100, 140], scaling: { abilityPower: 0.4 }, radius: 3, affects: 'allies' },
|
|
439
|
+
{ type: 'damage', amount: [60, 100, 140], scaling: { abilityPower: 0.5 }, damageType: 'magic', radius: 3, ...FOES }
|
|
440
|
+
]
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
id: 'dawnlightAura',
|
|
444
|
+
name: 'Dawnlight Aura',
|
|
445
|
+
description: 'Allies near her heal 1, 2 or 3 a second, and healing they get is 10, 15 or 20% stronger',
|
|
446
|
+
target: 'passive',
|
|
447
|
+
effects: [{ type: 'aura', buff: 'dawnlight', radius: 9 }]
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
id: 'sanctuary',
|
|
451
|
+
name: 'Sanctuary',
|
|
452
|
+
description: 'Heals an ally 200 (+60% ability power) and for 6 seconds a blow that would kill it leaves it at 30% health and untouchable for 2 seconds',
|
|
453
|
+
hotkey: 'R',
|
|
454
|
+
target: 'unit',
|
|
455
|
+
targets: { allies: true, self: true },
|
|
456
|
+
range: 8,
|
|
457
|
+
manaCost: 150,
|
|
458
|
+
cooldown: 110,
|
|
459
|
+
maxLevel: 1,
|
|
460
|
+
ultimate: true,
|
|
461
|
+
effects: [
|
|
462
|
+
{ type: 'heal', amount: 200, scaling: { abilityPower: 0.6 } },
|
|
463
|
+
{ type: 'buff', buff: 'sanctuary' }
|
|
464
|
+
]
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
// ---- Stormcaller: lightning and wind; every fourth spell free
|
|
468
|
+
{
|
|
469
|
+
id: 'staticCharge',
|
|
470
|
+
name: 'Static Charge',
|
|
471
|
+
description: 'Every fourth spell costs no mana, and lightning leaps from it to one more enemy for 40 (+10 a level)',
|
|
472
|
+
target: 'passive',
|
|
473
|
+
maxLevel: 1,
|
|
474
|
+
effects: [{ type: 'onCast', effects: [{ type: 'buff', buff: 'staticCharge', stack: true, on: 'caster' }] }]
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
id: 'tornado',
|
|
478
|
+
name: 'Tornado',
|
|
479
|
+
description:
|
|
480
|
+
'Whips up a tornado that sets off in a line. Enemies it passes, on the ground and in the air, take 60, 85 or 110 (+35% ability power) and are thrown up for 1 second',
|
|
481
|
+
hotkey: 'Q',
|
|
482
|
+
target: 'direction',
|
|
483
|
+
manaCost: [60, 70, 80],
|
|
484
|
+
cooldown: [12, 11, 10],
|
|
485
|
+
charge: 'staticCharge',
|
|
486
|
+
effects: [
|
|
487
|
+
{
|
|
488
|
+
type: 'missile',
|
|
489
|
+
delay: 0.75,
|
|
490
|
+
speed: 11,
|
|
491
|
+
distance: [11, 12.5, 14],
|
|
492
|
+
radius: 1.4,
|
|
493
|
+
effects: [
|
|
494
|
+
{ type: 'damage', amount: [60, 85, 110], scaling: { abilityPower: 0.35 } },
|
|
495
|
+
{ type: 'buff', buff: 'knockedUp', duration: 1 }
|
|
496
|
+
]
|
|
497
|
+
},
|
|
498
|
+
CHARGED
|
|
499
|
+
]
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
id: 'chainLightning',
|
|
503
|
+
name: 'Chain Lightning',
|
|
504
|
+
description: 'Lightning strikes an enemy for 90, 130 or 170 (+50% ability power), then leaps to two more near it, a quarter less each time',
|
|
505
|
+
hotkey: 'W',
|
|
506
|
+
target: 'unit',
|
|
507
|
+
targets: { enemies: true },
|
|
508
|
+
range: 8,
|
|
509
|
+
manaCost: 90,
|
|
510
|
+
cooldown: 9,
|
|
511
|
+
castTime: 0.3,
|
|
512
|
+
charge: 'staticCharge',
|
|
513
|
+
effects: [
|
|
514
|
+
{ type: 'missile', homing: true, speed: 30, bounce: { count: 2, range: 5, falloff: 0.25 }, effects: [{ type: 'damage', amount: [90, 130, 170], scaling: { abilityPower: 0.5 } }] },
|
|
515
|
+
CHARGED
|
|
516
|
+
]
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
id: 'galeWard',
|
|
520
|
+
name: 'Gale Ward',
|
|
521
|
+
description: 'Enemies his abilities hurt are slowed 20, 30 or 40% for 1.5 seconds',
|
|
522
|
+
target: 'passive',
|
|
523
|
+
effects: [{ type: 'onAbilityHit', effects: [{ type: 'buff', buff: 'buffeted' }] }]
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
id: 'eyeOfTheStorm',
|
|
527
|
+
name: 'Eye of the Storm',
|
|
528
|
+
description:
|
|
529
|
+
'A storm over an area for 5 seconds: enemies in it are slowed 35% and take 40 a second (+16% ability power), and whoever crosses its edge is thrown up and takes 60',
|
|
530
|
+
hotkey: 'R',
|
|
531
|
+
target: 'point',
|
|
532
|
+
range: 9,
|
|
533
|
+
manaCost: 175,
|
|
534
|
+
cooldown: 100,
|
|
535
|
+
castTime: 0.5,
|
|
536
|
+
maxLevel: 1,
|
|
537
|
+
ultimate: true,
|
|
538
|
+
charge: 'staticCharge',
|
|
539
|
+
effects: [
|
|
540
|
+
{
|
|
541
|
+
type: 'zone',
|
|
542
|
+
duration: 5,
|
|
543
|
+
radius: 4,
|
|
544
|
+
effects: [
|
|
545
|
+
{ type: 'damage', amount: 10, scaling: { abilityPower: 0.04 }, radius: 4, ...FOES },
|
|
546
|
+
{ type: 'buff', buff: 'stormSlow', duration: 0.5, radius: 4, ...FOES }
|
|
547
|
+
],
|
|
548
|
+
edge: [
|
|
549
|
+
{ type: 'buff', buff: 'knockedUp', duration: 0.75, ...FOES },
|
|
550
|
+
{ type: 'damage', amount: 60, ...FOES }
|
|
551
|
+
]
|
|
552
|
+
},
|
|
553
|
+
CHARGED
|
|
554
|
+
]
|
|
555
|
+
},
|
|
556
|
+
|
|
557
|
+
// ---- Gale Hunter: long shots, a dash, a volley that pins an area
|
|
558
|
+
{
|
|
559
|
+
id: 'steadyAim',
|
|
560
|
+
name: 'Steady Aim',
|
|
561
|
+
description: 'Each half second standing still adds 0.5 range and 6% critical chance, four times at most; moving loses all of it',
|
|
562
|
+
target: 'passive',
|
|
563
|
+
maxLevel: 1,
|
|
564
|
+
effects: [{ type: 'buff', buff: 'steadyAim' }]
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
id: 'piercingBolt',
|
|
568
|
+
name: 'Piercing Bolt',
|
|
569
|
+
description: 'A bolt that flies far in a line, through everyone in its way: 80, 130 or 180 (+80% attack damage) to each',
|
|
570
|
+
hotkey: 'Q',
|
|
571
|
+
target: 'direction',
|
|
572
|
+
manaCost: 70,
|
|
573
|
+
cooldown: [10, 9, 8],
|
|
574
|
+
castTime: 0.25,
|
|
575
|
+
effects: [
|
|
576
|
+
{ type: 'missile', speed: 24, distance: 14, radius: 0.8, pierce: true, effects: [{ type: 'damage', amount: [80, 130, 180], scaling: { attackDamage: 0.8 }, damageType: 'physical' }] }
|
|
577
|
+
]
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
id: 'tumble',
|
|
581
|
+
name: 'Tumble',
|
|
582
|
+
description: 'Rolls a short way, and the next attack within 4 seconds deals 40, 70 or 100 more',
|
|
583
|
+
hotkey: 'W',
|
|
584
|
+
target: 'point',
|
|
585
|
+
range: 5,
|
|
586
|
+
manaCost: 40,
|
|
587
|
+
cooldown: [9, 8, 7],
|
|
588
|
+
effects: [
|
|
589
|
+
{ type: 'dash', distance: 5 },
|
|
590
|
+
{ type: 'buff', buff: 'tumbleShot', on: 'caster' }
|
|
591
|
+
]
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
id: 'huntsman',
|
|
595
|
+
name: 'Huntsman',
|
|
596
|
+
description: 'Attacks on creeps and anything that is not a hero deal 15, 25 or 35 more',
|
|
597
|
+
target: 'passive',
|
|
598
|
+
effects: [{ type: 'onHit', effects: [{ type: 'damage', amount: [15, 25, 35], damageType: 'physical', where: { hero: false } }] }]
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
id: 'arrowstorm',
|
|
602
|
+
name: 'Arrowstorm',
|
|
603
|
+
description: 'Arrows fall over an area for 4 seconds: enemies in it are pinned, 50% slower, and take 48 a second (+20% attack damage)',
|
|
604
|
+
hotkey: 'R',
|
|
605
|
+
target: 'point',
|
|
606
|
+
range: 10,
|
|
607
|
+
manaCost: 150,
|
|
608
|
+
cooldown: 90,
|
|
609
|
+
castTime: 0.4,
|
|
610
|
+
maxLevel: 1,
|
|
611
|
+
ultimate: true,
|
|
612
|
+
effects: [
|
|
613
|
+
{
|
|
614
|
+
type: 'zone',
|
|
615
|
+
duration: 4,
|
|
616
|
+
radius: 5,
|
|
617
|
+
effects: [
|
|
618
|
+
{ type: 'damage', amount: 12, scaling: { attackDamage: 0.05 }, damageType: 'physical', radius: 5, ...FOES },
|
|
619
|
+
{ type: 'buff', buff: 'pinned', duration: 0.5, radius: 5, ...FOES }
|
|
620
|
+
]
|
|
621
|
+
}
|
|
622
|
+
]
|
|
623
|
+
},
|
|
624
|
+
|
|
625
|
+
// ---- Gravelord: drains life, an aura for his allies, raises the dead
|
|
626
|
+
{
|
|
627
|
+
id: 'bloodTithe',
|
|
628
|
+
name: 'Blood Tithe',
|
|
629
|
+
description: 'He has no mana: his abilities cost health instead, and 8 to 14% of the damage he deals heals him',
|
|
630
|
+
target: 'passive',
|
|
631
|
+
maxLevel: 4,
|
|
632
|
+
effects: [{ type: 'buff', buff: 'bloodTithe' }]
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
id: 'graveLash',
|
|
636
|
+
name: 'Grave Lash',
|
|
637
|
+
description: 'Lashes an enemy for 90, 150 or 210 (+60% ability power) and slows it 30% for 1.5 seconds',
|
|
638
|
+
hotkey: 'Q',
|
|
639
|
+
target: 'unit',
|
|
640
|
+
targets: { enemies: true },
|
|
641
|
+
range: 7,
|
|
642
|
+
manaCost: 60,
|
|
643
|
+
cooldown: 7,
|
|
644
|
+
castTime: 0.3,
|
|
645
|
+
effects: [
|
|
646
|
+
{ type: 'damage', amount: [90, 150, 210], scaling: { abilityPower: 0.6 } },
|
|
647
|
+
{ type: 'buff', buff: 'graveChill' }
|
|
648
|
+
]
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
id: 'siphon',
|
|
652
|
+
name: 'Siphon',
|
|
653
|
+
description: 'For 3 seconds, drains an enemy of 40, 60 or 80 a second (+20% ability power) and heals as much. Broken off as it gets away',
|
|
654
|
+
hotkey: 'W',
|
|
655
|
+
target: 'unit',
|
|
656
|
+
targets: { enemies: true },
|
|
657
|
+
range: 6,
|
|
658
|
+
manaCost: 70,
|
|
659
|
+
cooldown: 14,
|
|
660
|
+
effects: [],
|
|
661
|
+
channel: {
|
|
662
|
+
duration: 3,
|
|
663
|
+
every: 0.5,
|
|
664
|
+
leash: 8,
|
|
665
|
+
effects: [
|
|
666
|
+
{ type: 'damage', amount: [20, 30, 40], scaling: { abilityPower: 0.1 } },
|
|
667
|
+
{ type: 'heal', amount: [20, 30, 40], scaling: { abilityPower: 0.1 }, on: 'caster' }
|
|
668
|
+
]
|
|
669
|
+
}
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
id: 'deathlessAura',
|
|
673
|
+
name: 'Deathless Aura',
|
|
674
|
+
description: 'Allies near him move 10, 15 or 20% faster and heal 1, 2 or 3 a second',
|
|
675
|
+
target: 'passive',
|
|
676
|
+
effects: [{ type: 'aura', buff: 'deathless', radius: 9 }]
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
id: 'armyOfTheGrave',
|
|
680
|
+
name: 'Army of the Grave',
|
|
681
|
+
description: 'Six of the dead around him rise as skeletons for 40 seconds, and the attacks of his allies near heal them for 8 seconds',
|
|
682
|
+
hotkey: 'R',
|
|
683
|
+
target: 'none',
|
|
684
|
+
manaCost: 150,
|
|
685
|
+
cooldown: 120,
|
|
686
|
+
maxLevel: 1,
|
|
687
|
+
ultimate: true,
|
|
688
|
+
effects: [
|
|
689
|
+
{ type: 'raiseDead', unit: 'skeleton', count: 6, radius: 9, duration: 40 },
|
|
690
|
+
{ type: 'buff', buff: 'graveRallied', radius: 9, affects: 'allies' }
|
|
691
|
+
]
|
|
692
|
+
},
|
|
693
|
+
|
|
694
|
+
// ---- Rime Witch: roots and freezes; her team follows her holds up
|
|
695
|
+
{
|
|
696
|
+
id: 'shatter',
|
|
697
|
+
name: 'Shatter',
|
|
698
|
+
description: 'An enemy she holds (frozen, rooted) takes 25 to 35% more from the next blow of an ally of hers, which ends the hold',
|
|
699
|
+
target: 'passive',
|
|
700
|
+
maxLevel: 3,
|
|
701
|
+
effects: [{ type: 'buff', buff: 'shatterSense' }]
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
id: 'rimeBolt',
|
|
705
|
+
name: 'Rime Bolt',
|
|
706
|
+
description: 'Hits an enemy for 80, 130 or 180 (+60% ability power) and freezes it for 1, 1.25 or 1.5 seconds',
|
|
707
|
+
hotkey: 'Q',
|
|
708
|
+
target: 'unit',
|
|
709
|
+
targets: { enemies: true },
|
|
710
|
+
range: 8,
|
|
711
|
+
manaCost: 80,
|
|
712
|
+
cooldown: 8,
|
|
713
|
+
castTime: 0.3,
|
|
714
|
+
effects: [
|
|
715
|
+
{ type: 'damage', amount: [80, 130, 180], scaling: { abilityPower: 0.6 } },
|
|
716
|
+
{ type: 'buff', buff: 'frozen', duration: [1, 1.25, 1.5] }
|
|
717
|
+
]
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
id: 'grasp',
|
|
721
|
+
name: 'Grasping Ice',
|
|
722
|
+
description: 'Ice bursts from the ground in an area: enemies there take 70, 110 or 150 (+50% ability power) and are held where they stand for 1.5 to 2 seconds',
|
|
723
|
+
hotkey: 'W',
|
|
724
|
+
tags: ['ground'],
|
|
725
|
+
target: 'point',
|
|
726
|
+
range: 8,
|
|
727
|
+
manaCost: 90,
|
|
728
|
+
cooldown: 12,
|
|
729
|
+
castTime: 0.4,
|
|
730
|
+
effects: [
|
|
731
|
+
{ type: 'damage', amount: [70, 110, 150], scaling: { abilityPower: 0.5 }, radius: 2.5, layers: ['ground'], ...FOES },
|
|
732
|
+
{ type: 'buff', buff: 'iceRooted', duration: [1.5, 1.75, 2], radius: 2.5, layers: ['ground'], ...FOES }
|
|
733
|
+
]
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
id: 'hoarfrost',
|
|
737
|
+
name: 'Hoarfrost',
|
|
738
|
+
description: 'Her attacks slow 20, 25 or 30% for 1.5 seconds',
|
|
739
|
+
target: 'passive',
|
|
740
|
+
effects: [{ type: 'onHit', effects: [{ type: 'buff', buff: 'frostbitten' }] }]
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
id: 'whiteout',
|
|
744
|
+
name: 'Whiteout',
|
|
745
|
+
description:
|
|
746
|
+
'A blizzard over an area for 5 seconds: enemies in it are slowed 30% and take 32 a second (+12% ability power), and one that stays 2 seconds is frozen for 1.5',
|
|
747
|
+
hotkey: 'R',
|
|
748
|
+
target: 'point',
|
|
749
|
+
range: 9,
|
|
750
|
+
manaCost: 175,
|
|
751
|
+
cooldown: 100,
|
|
752
|
+
castTime: 0.5,
|
|
753
|
+
maxLevel: 1,
|
|
754
|
+
ultimate: true,
|
|
755
|
+
effects: [
|
|
756
|
+
{
|
|
757
|
+
type: 'zone',
|
|
758
|
+
duration: 5,
|
|
759
|
+
radius: 4.5,
|
|
760
|
+
effects: [
|
|
761
|
+
{ type: 'damage', amount: 8, scaling: { abilityPower: 0.03 }, radius: 4.5, ...FOES },
|
|
762
|
+
{ type: 'buff', buff: 'whiteoutChill', duration: 0.5, radius: 4.5, ...FOES },
|
|
763
|
+
{ type: 'buff', buff: 'frostbite', stack: true, radius: 4.5, ...FOES }
|
|
764
|
+
]
|
|
765
|
+
}
|
|
766
|
+
]
|
|
767
|
+
},
|
|
768
|
+
|
|
769
|
+
// ---- Duskblade: waits in the shadows, marks one and bursts it, slips away through a crowd
|
|
770
|
+
{
|
|
771
|
+
id: 'shadowStep',
|
|
772
|
+
name: 'Shadow Step',
|
|
773
|
+
description:
|
|
774
|
+
'Three seconds out of a fight (neither hurting nor hurt) hide her in the shadows, seen only from up close: her first strike from them deals 75 to 125% more and silences for 1.5 seconds',
|
|
775
|
+
target: 'passive',
|
|
776
|
+
maxLevel: 3,
|
|
777
|
+
effects: [{ type: 'buff', buff: 'shadowWatch' }]
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
id: 'duskMark',
|
|
781
|
+
name: 'Dusk Mark',
|
|
782
|
+
description:
|
|
783
|
+
'Cuts an enemy for 60, 100 or 140 (+50% attack damage) and marks it for 4 seconds: it takes 10 to 20% more from everyone, is seen wherever it goes, and the mark bursts for 60, 100 or 140 at its end',
|
|
784
|
+
hotkey: 'Q',
|
|
785
|
+
target: 'unit',
|
|
786
|
+
targets: { enemies: true },
|
|
787
|
+
range: 7,
|
|
788
|
+
manaCost: 70,
|
|
789
|
+
cooldown: 10,
|
|
790
|
+
effects: [
|
|
791
|
+
{ type: 'damage', amount: [60, 100, 140], scaling: { attackDamage: 0.5 }, damageType: 'physical' },
|
|
792
|
+
{ type: 'buff', buff: 'duskMark' }
|
|
793
|
+
]
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
id: 'fade',
|
|
797
|
+
name: 'Fade',
|
|
798
|
+
description: 'For 2 seconds she walks through units, 40% faster, seen only from up close. Attacking ends it',
|
|
799
|
+
hotkey: 'W',
|
|
800
|
+
target: 'none',
|
|
801
|
+
manaCost: 60,
|
|
802
|
+
cooldown: [16, 14, 12],
|
|
803
|
+
effects: [{ type: 'buff', buff: 'faded', on: 'caster' }]
|
|
804
|
+
},
|
|
805
|
+
{
|
|
806
|
+
id: 'twinFangs',
|
|
807
|
+
name: 'Twin Fangs',
|
|
808
|
+
description: 'Every third attack strikes again for 20, 35 or 50 (+50% attack damage)',
|
|
809
|
+
target: 'passive',
|
|
810
|
+
effects: [{ type: 'onHit', every: 3, effects: [{ type: 'damage', amount: [20, 35, 50], scaling: { attackDamage: 0.5 }, damageType: 'physical' }] }]
|
|
811
|
+
},
|
|
812
|
+
{
|
|
813
|
+
id: 'eclipse',
|
|
814
|
+
name: 'Eclipse',
|
|
815
|
+
description: 'Steps to an enemy and cuts it off for 3 seconds: it cannot use its abilities, cannot be healed, and is seen wherever it goes',
|
|
816
|
+
hotkey: 'R',
|
|
817
|
+
target: 'unit',
|
|
818
|
+
targets: { enemies: true },
|
|
819
|
+
range: 6,
|
|
820
|
+
manaCost: 120,
|
|
821
|
+
cooldown: 90,
|
|
822
|
+
maxLevel: 1,
|
|
823
|
+
ultimate: true,
|
|
824
|
+
effects: [
|
|
825
|
+
{ type: 'blink', distance: 6 },
|
|
826
|
+
{ type: 'buff', buff: 'eclipsed' }
|
|
827
|
+
]
|
|
828
|
+
},
|
|
829
|
+
|
|
830
|
+
// ---- Bone Reaver: clears camps fast, grows with every kill
|
|
831
|
+
{
|
|
832
|
+
id: 'harvest',
|
|
833
|
+
name: 'Harvest',
|
|
834
|
+
description: `Every kill adds 6 health and 1 attack damage for good, a hero's ${HERO_HARVEST} times as much, up to ${MOST_HARVEST}; he loses half of it when he dies`,
|
|
835
|
+
target: 'passive',
|
|
836
|
+
maxLevel: 1,
|
|
837
|
+
effects: [{ type: 'buff', buff: 'harvest' }]
|
|
838
|
+
},
|
|
839
|
+
{
|
|
840
|
+
id: 'boneCleaver',
|
|
841
|
+
name: 'Bone Cleaver',
|
|
842
|
+
description: 'Cleaves all around him for 60, 100 or 140 (+80% attack damage), twice as much to creeps and anything that is not a hero',
|
|
843
|
+
hotkey: 'Q',
|
|
844
|
+
target: 'none',
|
|
845
|
+
manaCost: 70,
|
|
846
|
+
cooldown: [8, 7, 6],
|
|
847
|
+
castTime: 0.3,
|
|
848
|
+
effects: [
|
|
849
|
+
{ type: 'damage', amount: [60, 100, 140], scaling: { attackDamage: 0.8 }, damageType: 'physical', radius: 3, ...FOES },
|
|
850
|
+
{ type: 'damage', amount: [60, 100, 140], scaling: { attackDamage: 0.8 }, damageType: 'physical', radius: 3, ...FOES_NOT_HEROES }
|
|
851
|
+
]
|
|
852
|
+
},
|
|
853
|
+
{
|
|
854
|
+
id: 'gorge',
|
|
855
|
+
name: 'Gorge',
|
|
856
|
+
description: 'Tears into a creep or another unit that is not a hero for 250, 400 or 550 true damage, and heals 60, 100 or 140',
|
|
857
|
+
hotkey: 'W',
|
|
858
|
+
target: 'unit',
|
|
859
|
+
targets: { enemies: true, heroes: false },
|
|
860
|
+
range: 2,
|
|
861
|
+
manaCost: 90,
|
|
862
|
+
cooldown: [16, 14, 12],
|
|
863
|
+
castTime: 0.25,
|
|
864
|
+
effects: [
|
|
865
|
+
{ type: 'damage', amount: [250, 400, 550], damageType: 'true' },
|
|
866
|
+
{ type: 'heal', amount: [60, 100, 140], on: 'caster' }
|
|
867
|
+
]
|
|
868
|
+
},
|
|
869
|
+
{
|
|
870
|
+
id: 'carrionHunger',
|
|
871
|
+
name: 'Carrion Hunger',
|
|
872
|
+
description: 'His blows strike the enemies around his target too, for 30, 45 or 60% of their damage',
|
|
873
|
+
target: 'passive',
|
|
874
|
+
effects: [{ type: 'buff', buff: 'carrionHunger' }]
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
id: 'bonePit',
|
|
878
|
+
name: 'Bone Pit',
|
|
879
|
+
description: 'A ring of bones for 4 seconds: whoever crosses it is stunned for 1 second, and allies inside attack 30% faster and heal by their attacks',
|
|
880
|
+
hotkey: 'R',
|
|
881
|
+
target: 'point',
|
|
882
|
+
range: 7,
|
|
883
|
+
manaCost: 150,
|
|
884
|
+
cooldown: 100,
|
|
885
|
+
maxLevel: 1,
|
|
886
|
+
ultimate: true,
|
|
887
|
+
effects: [
|
|
888
|
+
{
|
|
889
|
+
type: 'zone',
|
|
890
|
+
duration: 4,
|
|
891
|
+
radius: 4.5,
|
|
892
|
+
effects: [{ type: 'buff', buff: 'bonePitFrenzy', duration: 0.5, radius: 4.5, affects: 'allies' }],
|
|
893
|
+
edge: [{ type: 'buff', buff: 'stunned', duration: 1, ...FOES }]
|
|
894
|
+
}
|
|
895
|
+
]
|
|
896
|
+
}
|
|
897
|
+
]
|
|
898
|
+
|
|
899
|
+
// ---- in code: what the fields above do not say
|
|
900
|
+
|
|
901
|
+
/** What Bone Reaver reaped, kept on him through deaths, saved with him: the count of Harvest */
|
|
902
|
+
export const harvested = defineComponent<{ count: number }>({ id: 'melee.harvested' })
|
|
903
|
+
|
|
904
|
+
/** Gale Hunter, every half second: where it stood the last time, a stack more of Steadied; moved, none */
|
|
905
|
+
function steadyTick(world: World, carrier: Entity, instance: BuffInstance): void {
|
|
906
|
+
const was = instance.data as { x: number; z: number } | undefined
|
|
907
|
+
instance.data = { x: carrier.pos.x, z: carrier.pos.z }
|
|
908
|
+
const [dx, dz] = [carrier.pos.x - (was?.x ?? 0), carrier.pos.z - (was?.z ?? 0)]
|
|
909
|
+
if (!was || dx * dx + dz * dz > STILL * STILL) {
|
|
910
|
+
if (hasBuff(carrier, 'steadied')) removeBuff(world, carrier, 'steadied')
|
|
911
|
+
return
|
|
912
|
+
}
|
|
913
|
+
applyBuff(world, carrier, 'steadied', undefined, carrier)
|
|
914
|
+
const steadied = carrier.buffs.find((each) => each.id === 'steadied')
|
|
915
|
+
if (steadied) steadied.stacks = Math.min((steadied.stacks ?? 0) + 1, world.registry.buff('steadied').maxStacks ?? 1)
|
|
916
|
+
invalidateStats(world, carrier)
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/** How far a unit may be nudged in half a second and still stand still, to Steady Aim */
|
|
920
|
+
const STILL = 0.2
|
|
921
|
+
|
|
922
|
+
/** Seconds out of a fight before Duskblade is in the shadows */
|
|
923
|
+
const QUIET = 3
|
|
924
|
+
|
|
925
|
+
/** Duskblade, every quarter second: three seconds since she last hurt anyone or was hurt, in the shadows */
|
|
926
|
+
function shadowTick(world: World, carrier: Entity, instance: BuffInstance): void {
|
|
927
|
+
const struck = (instance.data?.struckAt as number | undefined) ?? -Infinity
|
|
928
|
+
if (hasBuff(carrier, 'shadowed') || world.time - Math.max(carrier.hurtAt, struck) < QUIET) return
|
|
929
|
+
applyBuff(world, carrier, 'shadowed', undefined, carrier, instance.level)
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/** Bone Reaver's Harvest put to a new count: the stacks shown and the stats */
|
|
933
|
+
function setHarvest(world: World, hero: Entity, count: number): void {
|
|
934
|
+
hero.set(harvested, { count })
|
|
935
|
+
const buff = hero.buffs.find((instance) => instance.id === 'harvest')
|
|
936
|
+
if (buff) buff.stacks = count
|
|
937
|
+
invalidateStats(world, hero)
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/** The ally of a hero's who stands guard over it (Bulwark): the Oathbreaker whose aura it carries, alive and not itself */
|
|
941
|
+
function guardOf(world: World, hero: Entity): Entity | undefined {
|
|
942
|
+
const ward = hero.buffs.find((instance) => instance.id === 'bulwarkWard')
|
|
943
|
+
const guard = ward && world.get(ward.source)
|
|
944
|
+
return guard?.alive && guard !== hero && !guard.hidden ? guard : undefined
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/** The share of a blow Bulwark takes */
|
|
948
|
+
const BULWARK = 0.2
|
|
949
|
+
/** How much more a shattering blow deals, by the level of Shatter */
|
|
950
|
+
const SHATTER = [0.25, 0.3, 0.35]
|
|
951
|
+
|
|
952
|
+
export const heroHooks: HookDef[] = [
|
|
953
|
+
defineHook({
|
|
954
|
+
hook: 'beforeDamage',
|
|
955
|
+
id: 'melee.heroes.damage',
|
|
956
|
+
run(world, event) {
|
|
957
|
+
const { target, source } = event
|
|
958
|
+
if (!target.buffs.length) return
|
|
959
|
+
// Shatter: a hold of a Rime Witch's, struck by an ally of hers, deals more and ends
|
|
960
|
+
if (source && !event.tags?.includes('shatter'))
|
|
961
|
+
for (const instance of target.buffs) {
|
|
962
|
+
const def = world.registry.buff(instance.id)
|
|
963
|
+
if (!def.harmful || !holdsUp(def)) continue
|
|
964
|
+
const witch = world.get(instance.source)
|
|
965
|
+
const sense = witch?.buffs.find((each) => each.id === 'shatterSense')
|
|
966
|
+
if (!witch || !sense || witch === source || world.areEnemies(witch.owner, source.owner)) continue
|
|
967
|
+
event.amount *= 1 + SHATTER[Math.min(sense.level, SHATTER.length) - 1]
|
|
968
|
+
for (const hold of target.buffs.filter((each) => each.source === witch.id && holdsUp(world.registry.buff(each.id))))
|
|
969
|
+
removeBuff(world, target, hold.id, witch)
|
|
970
|
+
break
|
|
971
|
+
}
|
|
972
|
+
// Bulwark: an Oathbreaker near takes a fifth of what an allied hero takes, before its armor
|
|
973
|
+
const guard = event.tags?.includes('bulwark') ? undefined : guardOf(world, target)
|
|
974
|
+
if (guard && event.amount > 0) {
|
|
975
|
+
const share = event.amount * BULWARK
|
|
976
|
+
event.amount -= share
|
|
977
|
+
world.damage(guard, share, source, { damageType: event.type, tags: ['bulwark'] })
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}),
|
|
981
|
+
defineHook({
|
|
982
|
+
hook: 'afterDamage',
|
|
983
|
+
id: 'melee.heroes.shadows',
|
|
984
|
+
run(_world, event) {
|
|
985
|
+
// Duskblade out of the shadows as she strikes; and the time of it, for Shadow Step
|
|
986
|
+
const watch = event.source?.buffs.find((instance) => instance.id === 'shadowWatch')
|
|
987
|
+
if (watch) watch.data = { ...watch.data, struckAt: _world.time }
|
|
988
|
+
if (event.dealt > 0 && hasBuff(event.target, 'shadowed')) removeBuff(_world, event.target, 'shadowed')
|
|
989
|
+
}
|
|
990
|
+
}),
|
|
991
|
+
defineHook({
|
|
992
|
+
hook: 'beforeCast',
|
|
993
|
+
id: 'melee.heroes.staticCharge',
|
|
994
|
+
run(world, event) {
|
|
995
|
+
// Static Charge full: the spell costs nothing
|
|
996
|
+
const charge = event.ability.charge
|
|
997
|
+
if (charge !== 'staticCharge') return
|
|
998
|
+
const stacks = event.caster.buffs.find((instance) => instance.id === charge)?.stacks ?? 0
|
|
999
|
+
if (stacks >= (world.registry.buff(charge).maxStacks ?? 1)) event.manaCost = 0
|
|
1000
|
+
}
|
|
1001
|
+
}),
|
|
1002
|
+
defineHook({
|
|
1003
|
+
hook: 'death',
|
|
1004
|
+
id: 'melee.heroes.harvest',
|
|
1005
|
+
run(world, { entity, killer }) {
|
|
1006
|
+
// Harvest: a kill adds to it, a hero's five times; dying, Bone Reaver loses half
|
|
1007
|
+
if (entity.has(harvested)) setHarvest(world, entity, Math.floor(entity.get(harvested)!.count / 2))
|
|
1008
|
+
if (!killer || killer === entity || !killer.alive || killer.def.hero?.innate !== 'harvest' || !entity.isUnit) return
|
|
1009
|
+
if (!world.areEnemies(killer.owner, entity.owner)) return
|
|
1010
|
+
const gain = heroOf(entity) ? HERO_HARVEST : 1
|
|
1011
|
+
setHarvest(world, killer, Math.min((killer.get(harvested)?.count ?? 0) + gain, MOST_HARVEST))
|
|
1012
|
+
}
|
|
1013
|
+
})
|
|
1014
|
+
]
|
|
1015
|
+
|
|
1016
|
+
export const heroEffects: EffectType[] = [
|
|
1017
|
+
/** Lightning leaping from where a spell went off to the nearest other enemy: Static Charge */
|
|
1018
|
+
defineEffect({ type: 'staticArc', run: ({ world, caster, target }, effect) => {
|
|
1019
|
+
const from = target ?? caster
|
|
1020
|
+
const struck = world.query({ near: from, radius: effect.radius, is: 'enemy', of: caster.owner, kinds: ['unit'], except: [caster, ...(target ? [target] : [])], sort: 'nearest', limit: 1 })[0]
|
|
1021
|
+
if (!struck) return
|
|
1022
|
+
const level = heroOf(caster)?.level ?? 1
|
|
1023
|
+
world.damage(struck, effect.amount + effect.perLevel * (level - 1), caster, { damageType: 'magic', ability: 'staticCharge' })
|
|
1024
|
+
} })
|
|
1025
|
+
]
|