@v12-engine/create 0.1.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +1 -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,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MOBA examples on the client's side: how their heroes, minions, towers and creeps look, their abilities' and hero
|
|
3
|
+
* spells' looks, and each example's card. Each example takes the HUD's half of the heroes (`heroesViewPack`: the hero's
|
|
4
|
+
* panel, its spells on D and F, the shop window) in its own look, so the other examples keep theirs
|
|
5
|
+
*/
|
|
6
|
+
import { type GameClientApi, type GameView, heroesViewPack, type UnitLook } from '@v12-engine/client'
|
|
7
|
+
import { button, type CategoryLooks, type ExampleControl, type ExampleLook, readout, toggle } from '../../shell/look'
|
|
8
|
+
import { character, hold, ONE_HANDED, person, sharedLooks, TWO_HANDED } from '../common/looks'
|
|
9
|
+
import { castButton, look, readyOn, resetButton, withLooks } from '../abilities/lookKit'
|
|
10
|
+
import { attack, buyback, buyItem, experience, fight, gold, learnIt, strafe, strike, trouble, wave } from './index'
|
|
11
|
+
import { HERO_SPELLS } from './spells'
|
|
12
|
+
import { abilities } from './units'
|
|
13
|
+
|
|
14
|
+
const units: Record<string, UnitLook> = {
|
|
15
|
+
mobaStormcaller: {
|
|
16
|
+
height: 2,
|
|
17
|
+
model: character('mage', 0.8, { hold: [hold('staff')] }),
|
|
18
|
+
shot: { color: [0.55, 0.7, 1], size: 0.32 },
|
|
19
|
+
shapes: person(2)
|
|
20
|
+
},
|
|
21
|
+
mobaDuskblade: {
|
|
22
|
+
height: 2.2,
|
|
23
|
+
model: character('rogue', 0.85, { hold: [hold('dagger'), hold('dagger', 'l')] }),
|
|
24
|
+
attacks: [ONE_HANDED],
|
|
25
|
+
shapes: person(2.2)
|
|
26
|
+
},
|
|
27
|
+
mobaMinion: {
|
|
28
|
+
height: 1.4,
|
|
29
|
+
model: character('knight', 0.55, { hold: [hold('sword_1handed'), hold('shield_round', 'l')] }),
|
|
30
|
+
attacks: [ONE_HANDED],
|
|
31
|
+
shapes: person(1.4)
|
|
32
|
+
},
|
|
33
|
+
mobaCasterMinion: {
|
|
34
|
+
height: 1.4,
|
|
35
|
+
model: character('mage', 0.55, { hold: [hold('wand')] }),
|
|
36
|
+
shot: { color: [0.6, 0.5, 1], size: 0.22 },
|
|
37
|
+
shapes: person(1.4)
|
|
38
|
+
},
|
|
39
|
+
mobaStrafer: { ...sharedLooks.knight, attacks: [TWO_HANDED] },
|
|
40
|
+
mobaTower: {
|
|
41
|
+
height: 5.6,
|
|
42
|
+
model: { src: 'rts/watch_tower.glb', scale: 1.28, points: { launch: { offset: [0, 4.6, 0] } } },
|
|
43
|
+
shot: { color: [1, 0.85, 0.4], size: 0.45 },
|
|
44
|
+
shapes: [
|
|
45
|
+
{ shape: 'cylinder', size: [1.1, 4.4, 1.1], offset: [0, 2.2, 0], color: [0.62, 0.62, 0.68] },
|
|
46
|
+
{ shape: 'cylinder', size: [1.6, 0.5, 1.6], offset: [0, 4.6, 0], color: 'team' }
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
mobaShop: {
|
|
50
|
+
height: 2,
|
|
51
|
+
model: { src: 'rts/market_stalls.glb', scale: 0.66 },
|
|
52
|
+
shapes: [{ shape: 'box', size: [3, 1.6, 3], offset: [0, 0.8, 0], color: [0.55, 0.38, 0.22] }]
|
|
53
|
+
},
|
|
54
|
+
// the jungle's creeps: the dead of old battles
|
|
55
|
+
mobaRaider: {
|
|
56
|
+
height: 1.6,
|
|
57
|
+
model: character('skeleton_minion', 0.7, { hold: [{ src: 'skeleton-gear/blade.glb', at: 'handslot.r' }] }),
|
|
58
|
+
attacks: [ONE_HANDED],
|
|
59
|
+
shapes: person(1.6, [0.85, 0.82, 0.72])
|
|
60
|
+
},
|
|
61
|
+
mobaBrute: {
|
|
62
|
+
height: 3,
|
|
63
|
+
model: character('skeleton_warrior', 1.3, {
|
|
64
|
+
hold: [
|
|
65
|
+
{ src: 'skeleton-gear/axe.glb', at: 'handslot.r' },
|
|
66
|
+
{ src: 'skeleton-gear/shield_large_a.glb', at: 'handslot.l' }
|
|
67
|
+
]
|
|
68
|
+
}),
|
|
69
|
+
attacks: [ONE_HANDED],
|
|
70
|
+
shapes: person(3, [0.85, 0.82, 0.72])
|
|
71
|
+
},
|
|
72
|
+
// what the engine's items bring: the Sight Ward's ward, a standing torch of the dungeon kit's, its parts showing
|
|
73
|
+
// while it loads; and the Wolfhide Charm's wolf, of parts in a ghostly blue, the shared art having no animals
|
|
74
|
+
ward: {
|
|
75
|
+
height: 1.1,
|
|
76
|
+
blood: false,
|
|
77
|
+
model: { src: 'dungeon-kit/torch.glb', scale: 0.8, raise: 0.55 },
|
|
78
|
+
shapes: [
|
|
79
|
+
{ shape: 'cylinder', size: [0.12, 1, 0.12], offset: [0, 0.5, 0], color: [0.35, 0.25, 0.15] },
|
|
80
|
+
{ shape: 'sphere', size: [0.35, 0.35, 0.35], offset: [0, 1, 0], color: [1, 0.85, 0.3] }
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
spiritWolf: {
|
|
84
|
+
height: 1.1,
|
|
85
|
+
blood: false,
|
|
86
|
+
shapes: [
|
|
87
|
+
{ shape: 'sphere', size: [0.65, 0.6, 1.3], offset: [0, 0.65, 0], color: [0.6, 0.8, 1] },
|
|
88
|
+
{ shape: 'sphere', size: [0.42, 0.42, 0.5], offset: [0, 0.9, 0.7], color: [0.65, 0.85, 1] },
|
|
89
|
+
{ shape: 'cone', size: [0.2, 0.35, 0.2], offset: [0, 0.9, 1.05], color: [0.55, 0.75, 0.95], tilt: Math.PI / 2 },
|
|
90
|
+
{ shape: 'cone', size: [0.2, 0.6, 0.2], offset: [0, 0.75, -0.8], color: [0.55, 0.75, 0.95], tilt: -Math.PI / 2 }
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** The abilities' and hero spells' looks (and their buffs', where a buff has the id of its spell) */
|
|
96
|
+
const abilityLooks = {
|
|
97
|
+
mobaArcaneBolt: look([0.55, 0.65, 1], 'missile', { options: { shape: 'ball' } }),
|
|
98
|
+
mobaChain: look([0.75, 0.8, 0.85], 'missile', { options: { shape: 'arrow' } }),
|
|
99
|
+
mobaShadowStep: look([0.55, 0.3, 0.9], 'slam'),
|
|
100
|
+
mobaStarLance: look([1, 0.9, 0.5], 'missile', { options: { shape: 'bird' } }),
|
|
101
|
+
mobaHeld: look([0.75, 0.8, 0.85], undefined, { aura: 'glow' }),
|
|
102
|
+
mobaFlash: look([1, 0.95, 0.6], 'burst'),
|
|
103
|
+
mobaSecondWind: look([0.45, 1, 0.5], 'sonar'),
|
|
104
|
+
mobaHealHaste: look([0.45, 1, 0.5], undefined, { aura: 'glow' }),
|
|
105
|
+
mobaBarrier: look([1, 0.85, 0.35], 'burst', { aura: 'glow', options: { bubble: true } }),
|
|
106
|
+
mobaBrand: look([1, 0.4, 0.1], 'burst', { aura: 'glow' }),
|
|
107
|
+
mobaWither: look([0.55, 0.45, 0.75], 'tether'),
|
|
108
|
+
mobaWithered: look([0.55, 0.45, 0.75], undefined, { aura: 'glow' }),
|
|
109
|
+
mobaPhantomStride: look([0.6, 0.9, 1], 'burst', { aura: 'glow' }),
|
|
110
|
+
mobaPurify: look([0.9, 1, 1], 'burst'),
|
|
111
|
+
mobaPurified: look([0.9, 1, 1], undefined, { aura: 'glow' }),
|
|
112
|
+
mobaWaystone: look([0.4, 0.75, 1], 'mark'),
|
|
113
|
+
mobaReap: look([1, 0.75, 0.25], 'smash')
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** The game's look with the heroes' HUD, the keys of a hero's abilities, spells and the shop, and the last hits shown */
|
|
117
|
+
const heroHud = (view: GameView<any>): GameView<any> => ({
|
|
118
|
+
...view,
|
|
119
|
+
packs: [...(view.packs ?? []), heroesViewPack()],
|
|
120
|
+
input: { ...view.input, keys: { ...view.input?.keys, abilities: ['Q', 'W', 'E', 'R'], spells: ['D', 'F'], shop: 'P' } }
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
/** The player's hero, as the snapshot has it */
|
|
124
|
+
const heroOn = (client: GameClientApi) => {
|
|
125
|
+
const named = ['mobaStormcaller', 'mobaDuskblade']
|
|
126
|
+
return client.session.snapshot().entities.find((each) => named.includes(each.type) && each.owner === client.session.playerId)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** What the rules report of the player's hero (`moba.report`) */
|
|
130
|
+
type HeroReport = {
|
|
131
|
+
level: number
|
|
132
|
+
xp: number
|
|
133
|
+
next: number
|
|
134
|
+
points: number
|
|
135
|
+
kills: number
|
|
136
|
+
deaths: number
|
|
137
|
+
assists: number
|
|
138
|
+
streak: number
|
|
139
|
+
back: number
|
|
140
|
+
items: string[]
|
|
141
|
+
}
|
|
142
|
+
const report = (client: GameClientApi) => (client.session.snapshot().report ?? {}) as { lastHits?: number; denies?: number; mobaHero?: HeroReport }
|
|
143
|
+
|
|
144
|
+
/** The camera on the hero wherever it goes, as a MOBA's camera lock (Y in League), or free */
|
|
145
|
+
const cameraLock = (on = false): ExampleControl =>
|
|
146
|
+
toggle(
|
|
147
|
+
'Camera locked on the hero',
|
|
148
|
+
on,
|
|
149
|
+
(client, locked) => {
|
|
150
|
+
const hero = heroOn(client)
|
|
151
|
+
client.follow(locked && hero ? hero.id : null, { smoothing: 0.15 })
|
|
152
|
+
},
|
|
153
|
+
'As a MOBA has it: the view follows the hero, the edges of the screen do not move it'
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
/** Selects the player's hero and frames the box, as every example here starts */
|
|
157
|
+
const readyHero =
|
|
158
|
+
(box: Parameters<typeof readyOn>[2], follow = false) =>
|
|
159
|
+
(client: GameClientApi) => {
|
|
160
|
+
readyOn(client, undefined, box)
|
|
161
|
+
const hero = heroOn(client)
|
|
162
|
+
if (hero) client.controller.select([hero.id])
|
|
163
|
+
if (follow && hero) client.follow(hero.id, { smoothing: 0.15 })
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const spells = HERO_SPELLS.abilities
|
|
167
|
+
|
|
168
|
+
const looks: Record<string, ExampleLook> = {
|
|
169
|
+
'moba-skillshots': {
|
|
170
|
+
view: heroHud,
|
|
171
|
+
ready: readyHero({ minX: 12, minZ: 10, maxX: 34, maxZ: 30 }),
|
|
172
|
+
controls: [
|
|
173
|
+
...abilities.map((ability) => castButton(ability, { by: 'hero' })),
|
|
174
|
+
toggle(
|
|
175
|
+
'Duelists step aside',
|
|
176
|
+
true,
|
|
177
|
+
(client, on) => client.session.send(strafe.make({ on })),
|
|
178
|
+
'They walk side to side, or stand still to be hit'
|
|
179
|
+
),
|
|
180
|
+
cameraLock(),
|
|
181
|
+
toggle(
|
|
182
|
+
'Quick cast',
|
|
183
|
+
false,
|
|
184
|
+
(client, on) => client.controller.setQuickCast(on),
|
|
185
|
+
'A key casts at once where the pointer is, without a click to aim: as most MOBA players play'
|
|
186
|
+
),
|
|
187
|
+
resetButton('moba-skillshots')
|
|
188
|
+
]
|
|
189
|
+
},
|
|
190
|
+
'moba-spells': {
|
|
191
|
+
view: heroHud,
|
|
192
|
+
ready: readyHero({ minX: 12, minZ: 10, maxX: 38, maxZ: 32 }),
|
|
193
|
+
controls: [
|
|
194
|
+
button('Hurt the hero', (client) => client.session.send(trouble.make({ stun: false })), 'Takes most of its health, for Second Wind or Barrier'),
|
|
195
|
+
button('Stun the hero', (client) => client.session.send(trouble.make({ stun: true })), 'Holds it for 2.5 seconds, for Purify'),
|
|
196
|
+
...spells.map((spell) =>
|
|
197
|
+
castButton(spell, {
|
|
198
|
+
by: 'hero',
|
|
199
|
+
...(spell.id === 'mobaWaystone' && { at: 'waystone' }),
|
|
200
|
+
...(spell.id === 'mobaReap' && { at: 'creep' }),
|
|
201
|
+
...((spell.id === 'mobaBrand' || spell.id === 'mobaWither') && { at: 'rival' })
|
|
202
|
+
})
|
|
203
|
+
),
|
|
204
|
+
resetButton('moba-spells')
|
|
205
|
+
]
|
|
206
|
+
},
|
|
207
|
+
'moba-lane': {
|
|
208
|
+
view: (view) => {
|
|
209
|
+
const own = heroHud(view)
|
|
210
|
+
return {
|
|
211
|
+
...own,
|
|
212
|
+
hud: {
|
|
213
|
+
...own.hud,
|
|
214
|
+
status: (state) => [
|
|
215
|
+
...(own.hud?.status?.(state) ?? []),
|
|
216
|
+
{ text: `Last hits ${Number(state.lastHits ?? 0)} · Denies ${Number(state.denies ?? 0)}`, title: 'Minions you gave the killing blow to' }
|
|
217
|
+
]
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
ready: readyHero({ minX: 10, minZ: 2, maxX: 62, maxZ: 26 }),
|
|
222
|
+
controls: [
|
|
223
|
+
button(
|
|
224
|
+
'Last-hit the weakest',
|
|
225
|
+
(client) => client.session.send(strike.make({ deny: false })),
|
|
226
|
+
"The hero strikes the enemy minion nearest death: the gold is the killing blow's"
|
|
227
|
+
),
|
|
228
|
+
button(
|
|
229
|
+
'Deny the weakest',
|
|
230
|
+
(client) => client.session.send(strike.make({ deny: true })),
|
|
231
|
+
'The hero strikes one of its own below half health: the enemy gets less for it'
|
|
232
|
+
),
|
|
233
|
+
button('A wave now', (client) => client.session.send(wave.make({})), 'Both ends send a wave at once, besides the one every 20 seconds'),
|
|
234
|
+
cameraLock(),
|
|
235
|
+
readout(
|
|
236
|
+
'Last hits · denies',
|
|
237
|
+
(client) => `${report(client).lastHits ?? 0} · ${report(client).denies ?? 0}`,
|
|
238
|
+
'Minions your hero dealt the killing blow to: enemy ones (last hits, which pay gold) and its own (denies)'
|
|
239
|
+
),
|
|
240
|
+
readout('Gold', (client) =>
|
|
241
|
+
String(Math.floor(client.session.snapshot().players?.find((each) => each.id === client.session.playerId)?.own?.resources.gold ?? 0))
|
|
242
|
+
),
|
|
243
|
+
resetButton('moba-lane')
|
|
244
|
+
]
|
|
245
|
+
},
|
|
246
|
+
'moba-shop': {
|
|
247
|
+
view: heroHud,
|
|
248
|
+
ready: readyHero({ minX: 12, minZ: 12, maxX: 30, maxZ: 28 }),
|
|
249
|
+
controls: [
|
|
250
|
+
button(
|
|
251
|
+
'Iron Sword',
|
|
252
|
+
(client) => client.session.send(buyItem.make({ item: 'ironSword' })),
|
|
253
|
+
'A basic: one of the two a Hunter’s Knife is built from'
|
|
254
|
+
),
|
|
255
|
+
button(
|
|
256
|
+
'Hunter’s Knife',
|
|
257
|
+
(client) => client.session.send(buyItem.make({ item: 'huntersKnife' })),
|
|
258
|
+
'The recipe: costs what it does less the swords carried, which it uses up'
|
|
259
|
+
),
|
|
260
|
+
button(
|
|
261
|
+
'Boots',
|
|
262
|
+
(client) => client.session.send(buyItem.make({ item: 'boots' })),
|
|
263
|
+
'A basic item, +25 move speed: one of the two Greaves of Haste are built from'
|
|
264
|
+
),
|
|
265
|
+
button(
|
|
266
|
+
'Greaves of Haste',
|
|
267
|
+
(client) => client.session.send(buyItem.make({ item: 'greavesOfHaste' })),
|
|
268
|
+
'Boots built into more (+45 move speed, attacks faster): with Boots carried it costs that much less'
|
|
269
|
+
),
|
|
270
|
+
button('Healing Draught', (client) => client.session.send(buyItem.make({ item: 'healingDraught' })), 'A consumable: drink it, it is gone'),
|
|
271
|
+
button('1000 gold', (client) => client.session.send(gold.make({ amount: 1000 })), 'Gives you 1000 gold to buy with'),
|
|
272
|
+
readout('Carried', (client) => report(client).mobaHero?.items.join(', ') || 'nothing', 'The items your hero carries now'),
|
|
273
|
+
readout('Gold', (client) =>
|
|
274
|
+
String(Math.floor(client.session.snapshot().players?.find((each) => each.id === client.session.playerId)?.own?.resources.gold ?? 0))
|
|
275
|
+
),
|
|
276
|
+
resetButton('moba-shop')
|
|
277
|
+
]
|
|
278
|
+
},
|
|
279
|
+
'moba-levels': {
|
|
280
|
+
view: heroHud,
|
|
281
|
+
ready: readyHero({ minX: 12, minZ: 12, maxX: 28, maxZ: 28 }),
|
|
282
|
+
controls: [
|
|
283
|
+
button('Gain 150 experience', (client) => client.session.send(experience.make({ amount: 150 })), 'What killing a few minions near it brings'),
|
|
284
|
+
button('Up a level', (client) => client.session.send(experience.make({ levels: 1 })), 'Straight to the next level, with its point'),
|
|
285
|
+
...abilities.map((ability) =>
|
|
286
|
+
button(
|
|
287
|
+
`Learn ${ability.name} (${ability.hotkey})`,
|
|
288
|
+
(client) => client.session.send(learnIt.make({ ability: ability.id })),
|
|
289
|
+
ability.ultimate ? 'The ultimate: from level 6' : 'Spends a point'
|
|
290
|
+
)
|
|
291
|
+
),
|
|
292
|
+
readout('Level · experience', (client) => {
|
|
293
|
+
const hero = report(client).mobaHero
|
|
294
|
+
return hero ? `${hero.level} · ${hero.xp} / ${hero.next}` : '—'
|
|
295
|
+
}),
|
|
296
|
+
readout('Points to spend', (client) => String(report(client).mobaHero?.points ?? 0)),
|
|
297
|
+
resetButton('moba-levels')
|
|
298
|
+
]
|
|
299
|
+
},
|
|
300
|
+
'moba-teamfight': {
|
|
301
|
+
view: heroHud,
|
|
302
|
+
ready: readyHero({ minX: 6, minZ: 4, maxX: 42, maxZ: 28 }),
|
|
303
|
+
controls: [
|
|
304
|
+
button('Fight', (client) => client.session.send(fight.make({})), 'Every hero goes for the nearest of the other side'),
|
|
305
|
+
button('Buy back', (client) => client.session.send(buyback.make({})), 'Your fallen hero back at once, at home, for gold'),
|
|
306
|
+
readout('Kills · deaths · assists', (client) => {
|
|
307
|
+
const hero = report(client).mobaHero
|
|
308
|
+
return hero ? `${hero.kills} · ${hero.deaths} · ${hero.assists}` : '—'
|
|
309
|
+
}),
|
|
310
|
+
readout(
|
|
311
|
+
'Streak',
|
|
312
|
+
(client) => String(report(client).mobaHero?.streak ?? 0),
|
|
313
|
+
'Kills in a row without dying: a hero on a streak is worth more to whoever ends it'
|
|
314
|
+
),
|
|
315
|
+
readout(
|
|
316
|
+
'Back in',
|
|
317
|
+
(client) => {
|
|
318
|
+
const back = report(client).mobaHero?.back ?? 0
|
|
319
|
+
return back > 0 ? `${back} s` : 'alive'
|
|
320
|
+
},
|
|
321
|
+
'Seconds until your fallen hero comes back at home by itself'
|
|
322
|
+
),
|
|
323
|
+
resetButton('moba-teamfight')
|
|
324
|
+
]
|
|
325
|
+
},
|
|
326
|
+
'moba-jungle': {
|
|
327
|
+
view: heroHud,
|
|
328
|
+
ready: readyHero({ minX: 10, minZ: 10, maxX: 36, maxZ: 32 }),
|
|
329
|
+
controls: [
|
|
330
|
+
button(
|
|
331
|
+
'Attack the raiders',
|
|
332
|
+
(client) => client.session.send(attack.make({ unit: 'mobaRaider' })),
|
|
333
|
+
'They answer together, chase a while, and walk back to their camp'
|
|
334
|
+
),
|
|
335
|
+
button('Attack the brute', (client) => client.session.send(attack.make({ unit: 'mobaBrute' })), 'A strong creep alone: Reap finishes it'),
|
|
336
|
+
castButton(
|
|
337
|
+
spells.find((each) => each.id === 'mobaReap')!,
|
|
338
|
+
{ by: 'hero', label: 'Reap the nearest creep' }
|
|
339
|
+
),
|
|
340
|
+
cameraLock(),
|
|
341
|
+
resetButton('moba-jungle')
|
|
342
|
+
]
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export const category: CategoryLooks = {
|
|
347
|
+
looks,
|
|
348
|
+
view: (view: GameView<any>) => withLooks(view, units, abilityLooks)
|
|
349
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MOBA examples' maps, made in code: a field of two sides (blue, the player, and red), and on it one lane with a
|
|
3
|
+
* spawner at each end, or camps of creeps in a jungle. The engine alone
|
|
4
|
+
*/
|
|
5
|
+
import { createMap, type GameMap, LANE_SPAWNERS, type Vec2 } from '@v12-engine/engine'
|
|
6
|
+
import { tiles } from '../define'
|
|
7
|
+
|
|
8
|
+
/** The two sides: the player's (blue) and the one it plays against (red), as the shared units say them */
|
|
9
|
+
export const BLUE = 1
|
|
10
|
+
export const RED = 2
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A field of two sides, painted grass, each side a team of its own with gold to spend and a home (`home.<team>`, where
|
|
14
|
+
* a hero comes back after it falls)
|
|
15
|
+
*/
|
|
16
|
+
export function mobaField(options: { name: string; width: number; depth: number; blue: Vec2; red: Vec2; gold?: number }): GameMap {
|
|
17
|
+
const { name, width, depth, blue, red, gold = 0 } = options
|
|
18
|
+
const map = createMap({ name, width, depth, players: 2, game: 'examples', tileset: tiles.map((each) => each.id) })
|
|
19
|
+
map.tiles.fill(map.tileset.indexOf('grass') + 1)
|
|
20
|
+
const sides = [
|
|
21
|
+
{ at: blue, name: 'Blue', color: [0.2, 0.45, 0.95] as [number, number, number] },
|
|
22
|
+
{ at: red, name: 'Red', color: [0.9, 0.2, 0.18] as [number, number, number] }
|
|
23
|
+
]
|
|
24
|
+
sides.forEach((side, i) => {
|
|
25
|
+
map.players[i] = { ...map.players[i], name: side.name, team: i + 1, color: side.color, start: side.at, resources: { gold } }
|
|
26
|
+
map.points.push({ id: 900 + i, name: `home.${i + 1}`, x: side.at.x, z: side.at.z, facing: 0 })
|
|
27
|
+
})
|
|
28
|
+
return map
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The lane: west (blue) to east (red), along the middle of a long field */
|
|
32
|
+
export const LANE = { width: 72, depth: 28, from: { x: 5, z: 14 }, to: { x: 67, z: 14 } }
|
|
33
|
+
|
|
34
|
+
/** Where the lane's towers stand, a side's first: two a side, beside the lane, the outer one nearer the middle */
|
|
35
|
+
export const TOWERS = {
|
|
36
|
+
blue: [
|
|
37
|
+
{ x: 16.5, z: 16.5 },
|
|
38
|
+
{ x: 27.5, z: 11.5 }
|
|
39
|
+
],
|
|
40
|
+
red: [
|
|
41
|
+
{ x: 55.5, z: 11.5 },
|
|
42
|
+
{ x: 44.5, z: 16.5 }
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* One lane between the two sides: a path painted as a road, a spawner at each end (`LANE_SPAWNERS`: whose waves, which
|
|
48
|
+
* lane), and the heroes' homes behind the spawners
|
|
49
|
+
*/
|
|
50
|
+
export function laneMap(): GameMap {
|
|
51
|
+
const map = mobaField({ name: 'One lane', width: LANE.width, depth: LANE.depth, blue: { x: 3, z: 14 }, red: { x: 69, z: 14 }, gold: 500 })
|
|
52
|
+
const lane = 100
|
|
53
|
+
map.paths.push({ id: lane, name: 'mid', points: [LANE.from, { x: 36, z: 14 }, LANE.to], width: 4, paint: 'road' })
|
|
54
|
+
map.data[LANE_SPAWNERS] = [
|
|
55
|
+
{ id: 200, x: LANE.from.x, z: LANE.from.z, data: { player: BLUE, lane } },
|
|
56
|
+
{ id: 201, x: LANE.to.x, z: LANE.to.z, data: { player: RED, lane } }
|
|
57
|
+
]
|
|
58
|
+
return map
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** A jungle between two lanes' worth of field: camps of creeps that come back a while after they are cleared */
|
|
62
|
+
export function jungleMap(): GameMap {
|
|
63
|
+
const map = mobaField({ name: 'Jungle', width: 48, depth: 48, blue: { x: 6, z: 6 }, red: { x: 42, z: 42 }, gold: 500 })
|
|
64
|
+
map.camps.push(
|
|
65
|
+
{ id: 400, x: 18, z: 24, units: ['mobaRaider', 'mobaRaider', 'mobaRaider'], name: 'Raiders', respawn: 20 },
|
|
66
|
+
{ id: 401, x: 30, z: 20, units: ['mobaBrute'], name: 'Brute', respawn: 30 }
|
|
67
|
+
)
|
|
68
|
+
return map
|
|
69
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The engine's nine hero spells (`defaultHeroSpells`: Flash, Second Wind, Barrier, Brand, Wither, Phantom Stride,
|
|
3
|
+
* Purify, Waystone, Reap) and their buffs, under ids of the MOBA examples' own (`flash` is `mobaFlash`): the game is one
|
|
4
|
+
* for every example, and their plain ids are words another example could use
|
|
5
|
+
*/
|
|
6
|
+
import { type AbilityDef, type BuffDef, defaultHeroSpellBuffs, defaultHeroSpells, type EffectDef } from '@v12-engine/engine'
|
|
7
|
+
|
|
8
|
+
/** `flash` as `mobaFlash` */
|
|
9
|
+
const own = (id: string) => `moba${id[0].toUpperCase()}${id.slice(1)}`
|
|
10
|
+
|
|
11
|
+
/** An effect with the buffs it names (its own and its effects', at any depth) under their new ids */
|
|
12
|
+
function renamed(effect: EffectDef): EffectDef {
|
|
13
|
+
const out: Record<string, unknown> = { ...effect }
|
|
14
|
+
if (typeof out.buff === 'string') out.buff = own(out.buff)
|
|
15
|
+
for (const key of ['effects', 'otherwise', 'onEnd', 'finished']) if (Array.isArray(out[key])) out[key] = (out[key] as EffectDef[]).map(renamed)
|
|
16
|
+
return out as unknown as EffectDef
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** The hero spells and their buffs, renamed: a new list each time, as the engine's are */
|
|
20
|
+
function heroSpells(): { abilities: AbilityDef[]; buffs: BuffDef[] } {
|
|
21
|
+
const abilities = defaultHeroSpells().map((spell) => ({
|
|
22
|
+
...spell,
|
|
23
|
+
id: own(spell.id),
|
|
24
|
+
effects: spell.effects.map(renamed),
|
|
25
|
+
...(spell.channel && { channel: { ...spell.channel, ...(spell.channel.finished && { finished: spell.channel.finished.map(renamed) }) } })
|
|
26
|
+
}))
|
|
27
|
+
const buffs = defaultHeroSpellBuffs().map((buff) => ({ ...buff, id: own(buff.id) }))
|
|
28
|
+
return { abilities, buffs }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const HERO_SPELLS = heroSpells()
|
|
32
|
+
|
|
33
|
+
/** The spells' ids, in the engine's order */
|
|
34
|
+
export const SPELL_IDS = HERO_SPELLS.abilities.map((each) => each.id)
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MOBA examples' units and abilities: two heroes (a caster of skillshots and a blade), lane minions, a tower, a
|
|
3
|
+
* shop, creeps of the jungle and a target that walks side to side. Data alone; how each looks is `./looks.ts`
|
|
4
|
+
*/
|
|
5
|
+
import { type AbilityDef, type BuffDef, defaultItems, itemTier, type UnitTypeDef } from '@v12-engine/engine'
|
|
6
|
+
import { HERO_SPELLS } from './spells'
|
|
7
|
+
|
|
8
|
+
/** The four abilities a hero learns, Q, W, E and R, the last its ultimate */
|
|
9
|
+
export const abilities: AbilityDef[] = [
|
|
10
|
+
{
|
|
11
|
+
// a skillshot: flies where it was aimed, not at a unit, and stops at the first enemy it meets (`pierce: false`)
|
|
12
|
+
id: 'mobaArcaneBolt',
|
|
13
|
+
name: 'Arcane Bolt',
|
|
14
|
+
description: 'A bolt along the aim: {damage} to the first enemy it meets. It hits nothing that stepped aside',
|
|
15
|
+
hotkey: 'Q',
|
|
16
|
+
target: 'direction',
|
|
17
|
+
range: 11,
|
|
18
|
+
manaCost: 30,
|
|
19
|
+
cooldown: 1.2,
|
|
20
|
+
castTime: 0.15,
|
|
21
|
+
effects: [
|
|
22
|
+
{ type: 'missile', speed: 20, distance: 11, radius: 0.5, pierce: false, effects: [{ type: 'damage', amount: [90, 130, 170, 210, 250] }] }
|
|
23
|
+
],
|
|
24
|
+
maxLevel: 5
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
// a hook: the first enemy met is dragged back to the caster, held, and hurt as it arrives (a `when` chain)
|
|
28
|
+
id: 'mobaChain',
|
|
29
|
+
name: 'Grasping Chain',
|
|
30
|
+
description: 'A chain along the aim: the first enemy it meets is dragged back and held, and takes {damage} as it arrives',
|
|
31
|
+
hotkey: 'W',
|
|
32
|
+
target: 'direction',
|
|
33
|
+
range: 9,
|
|
34
|
+
manaCost: 60,
|
|
35
|
+
cooldown: 4,
|
|
36
|
+
castTime: 0.25,
|
|
37
|
+
effects: [
|
|
38
|
+
{ type: 'missile', speed: 26, distance: 9, radius: 0.6, pierce: false, returns: true },
|
|
39
|
+
{ type: 'pull', seconds: 0.5, when: 'hit' },
|
|
40
|
+
{ type: 'buff', buff: 'mobaHeld', when: 'hit' },
|
|
41
|
+
{ type: 'damage', amount: [80, 120, 160, 200, 240], when: 'arrived' }
|
|
42
|
+
],
|
|
43
|
+
maxLevel: 5
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
// a dash: rushes along the aim, no target needed, and keeps walking after
|
|
47
|
+
id: 'mobaShadowStep',
|
|
48
|
+
name: 'Shadow Step',
|
|
49
|
+
description: 'Rushes {distance} along the aim, through enemies, hurting each on the way',
|
|
50
|
+
hotkey: 'E',
|
|
51
|
+
target: 'direction',
|
|
52
|
+
range: 5,
|
|
53
|
+
manaCost: 40,
|
|
54
|
+
cooldown: 3,
|
|
55
|
+
effects: [{ type: 'dash', distance: 5, speed: 24, collide: [{ type: 'damage', amount: [60, 90, 120, 150, 180] }] }],
|
|
56
|
+
maxLevel: 5
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
// the ultimate: learned at level 6, a wide lance that goes through all
|
|
60
|
+
id: 'mobaStarLance',
|
|
61
|
+
name: 'Star Lance',
|
|
62
|
+
description: 'A wide lance of light through everything along the aim: {damage} to each',
|
|
63
|
+
hotkey: 'R',
|
|
64
|
+
target: 'direction',
|
|
65
|
+
range: 14,
|
|
66
|
+
manaCost: 100,
|
|
67
|
+
cooldown: 12,
|
|
68
|
+
castTime: 0.4,
|
|
69
|
+
ultimate: true,
|
|
70
|
+
effects: [{ type: 'missile', speed: 30, distance: 14, radius: 1.4, pierce: true, effects: [{ type: 'damage', amount: [300, 450, 600] }] }]
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
export const buffs: BuffDef[] = [{ id: 'mobaHeld', name: 'Held', harmful: true, duration: 0.8, stun: true }]
|
|
75
|
+
|
|
76
|
+
/** What a hero learns: its four, Q W E R */
|
|
77
|
+
const learns = abilities.map((each) => each.id)
|
|
78
|
+
|
|
79
|
+
/** A hero's stats and what grows of them with each level */
|
|
80
|
+
const hero = (callings: Record<string, 1 | 2 | 3>, spells = ['mobaFlash', 'mobaBarrier']): UnitTypeDef['hero'] => ({
|
|
81
|
+
learns,
|
|
82
|
+
spells,
|
|
83
|
+
callings,
|
|
84
|
+
growth: { stats: { health: 90, mana: 40, attackDamage: 4, armor: 3 } },
|
|
85
|
+
maxLevel: 18
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
export const units: UnitTypeDef[] = [
|
|
89
|
+
{
|
|
90
|
+
id: 'mobaStormcaller',
|
|
91
|
+
name: 'Stormcaller',
|
|
92
|
+
kind: 'unit',
|
|
93
|
+
radius: 0.45,
|
|
94
|
+
stats: { health: 620, healthRegen: 3, mana: 420, manaRegen: 8, attackDamage: 34, attackSpeed: 0.9, attackRange: 6, armor: 18, moveSpeed: 4.6 },
|
|
95
|
+
sight: 13,
|
|
96
|
+
attacks: [{ damagePoint: 0.4, acquisitionRange: 7, projectileSpeed: 22 }],
|
|
97
|
+
hero: hero({ arcanist: 3, binder: 2 }),
|
|
98
|
+
inventory: 6,
|
|
99
|
+
level: 1,
|
|
100
|
+
bounty: [250, 300]
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: 'mobaDuskblade',
|
|
104
|
+
name: 'Duskblade',
|
|
105
|
+
kind: 'unit',
|
|
106
|
+
radius: 0.5,
|
|
107
|
+
stats: { health: 760, healthRegen: 4, mana: 300, manaRegen: 6, attackDamage: 48, attackSpeed: 1, attackRange: 0.6, armor: 26, moveSpeed: 4.6 },
|
|
108
|
+
sight: 12,
|
|
109
|
+
attacks: [{ damagePoint: 0.35, acquisitionRange: 7 }],
|
|
110
|
+
hero: hero({ stalker: 3, reaver: 2 }, ['mobaFlash', 'mobaBrand']),
|
|
111
|
+
inventory: 6,
|
|
112
|
+
level: 1,
|
|
113
|
+
bounty: [250, 300]
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'mobaMinion',
|
|
117
|
+
name: 'Melee minion',
|
|
118
|
+
kind: 'unit',
|
|
119
|
+
radius: 0.4,
|
|
120
|
+
stats: { health: 420, attackDamage: 14, attackSpeed: 1.2, attackRange: 0.5, armor: 5, moveSpeed: 3.4 },
|
|
121
|
+
sight: 8,
|
|
122
|
+
attacks: [{ damagePoint: 0.4, acquisitionRange: 6 }],
|
|
123
|
+
cost: { gold: 80 },
|
|
124
|
+
bounty: [20, 22]
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: 'mobaCasterMinion',
|
|
128
|
+
name: 'Caster minion',
|
|
129
|
+
kind: 'unit',
|
|
130
|
+
radius: 0.4,
|
|
131
|
+
stats: { health: 280, attackDamage: 22, attackSpeed: 1.4, attackRange: 5.5, moveSpeed: 3.4 },
|
|
132
|
+
sight: 9,
|
|
133
|
+
attacks: [{ damagePoint: 0.5, acquisitionRange: 6, projectileSpeed: 18 }],
|
|
134
|
+
cost: { gold: 60 },
|
|
135
|
+
bounty: [14, 16]
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
// a lane's tower: strikes minions first, and the hero who hits one of its side's heroes (`lanesPack`)
|
|
139
|
+
id: 'mobaTower',
|
|
140
|
+
name: 'Tower',
|
|
141
|
+
kind: 'building',
|
|
142
|
+
radius: 1,
|
|
143
|
+
footprint: { w: 2, d: 2 },
|
|
144
|
+
stats: { health: 3000, attackDamage: 140, attackSpeed: 1.2, attackRange: 7.5, armor: 40 },
|
|
145
|
+
sight: 11,
|
|
146
|
+
attacks: [{ damagePoint: 0.2, acquisitionRange: 7.5, projectileSpeed: 24 }],
|
|
147
|
+
bounty: [250, 250]
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
// sells the engine's whole tree of items but its relics, to a hero within reach (`SHOP_RANGE`)
|
|
151
|
+
id: 'mobaShop',
|
|
152
|
+
name: 'Shop',
|
|
153
|
+
kind: 'building',
|
|
154
|
+
radius: 1.5,
|
|
155
|
+
footprint: { w: 3, d: 3 },
|
|
156
|
+
stats: { health: 2000, armor: 50 },
|
|
157
|
+
invulnerable: true,
|
|
158
|
+
sight: 8,
|
|
159
|
+
sells: defaultItems.filter((item) => itemTier(item) !== 'relic').map((item) => item.id)
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
id: 'mobaRaider',
|
|
163
|
+
name: 'Grave raider',
|
|
164
|
+
kind: 'unit',
|
|
165
|
+
radius: 0.45,
|
|
166
|
+
stats: { health: 480, attackDamage: 16, attackSpeed: 1.2, attackRange: 0.5, armor: 8, moveSpeed: 4.2 },
|
|
167
|
+
sight: 8,
|
|
168
|
+
attacks: [{ damagePoint: 0.35, acquisitionRange: 5 }],
|
|
169
|
+
level: 2,
|
|
170
|
+
bounty: [30, 36]
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: 'mobaBrute',
|
|
174
|
+
name: 'Ancient brute',
|
|
175
|
+
kind: 'unit',
|
|
176
|
+
radius: 0.8,
|
|
177
|
+
stats: { health: 1800, healthRegen: 4, attackDamage: 55, attackSpeed: 0.7, attackRange: 0.8, armor: 30, moveSpeed: 3 },
|
|
178
|
+
sight: 8,
|
|
179
|
+
attacks: [{ damagePoint: 0.5, acquisitionRange: 5 }],
|
|
180
|
+
level: 5,
|
|
181
|
+
bounty: [100, 120]
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
// what a skillshot is tried on: an enemy who walks side to side and never strikes back
|
|
185
|
+
id: 'mobaStrafer',
|
|
186
|
+
name: 'Dodging duelist',
|
|
187
|
+
kind: 'unit',
|
|
188
|
+
radius: 0.5,
|
|
189
|
+
stats: { health: 1400, healthRegen: 25, moveSpeed: 3.6, armor: 10 },
|
|
190
|
+
sight: 8
|
|
191
|
+
}
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
/** Every ability of the MOBA examples, the hero spells among them */
|
|
195
|
+
export const allAbilities: AbilityDef[] = [...abilities, ...HERO_SPELLS.abilities]
|
|
196
|
+
export const allBuffs: BuffDef[] = [...buffs, ...HERO_SPELLS.buffs]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Where the camera stands in an example: the client's side of the nature and post-processing examples */
|
|
2
|
+
import type { GameClientApi } from '@v12-engine/client'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Puts the camera somewhere and keeps it there: the point looked at, how far back (cells), turned how far from
|
|
6
|
+
* looking north (degrees, clockwise) and looking down how steeply (degrees). The view rests at that turn, and keeps
|
|
7
|
+
* that steepness as the player zooms, steeper further out (`pitchCurve`, which the game's own would set again)
|
|
8
|
+
*/
|
|
9
|
+
export function stand(client: GameClientApi, at: { x: number; z: number }, distance: number, yaw: number, pitch: number): void {
|
|
10
|
+
const turn = (yaw * Math.PI) / 180
|
|
11
|
+
const { camera } = client
|
|
12
|
+
camera.restYaw = turn
|
|
13
|
+
camera.pitchCurve = (far) => Math.min(80, Math.max(15, pitch + (far - distance) * 0.7))
|
|
14
|
+
camera.setPose({ x: at.x, z: at.z, distance, yaw: turn, pitch: (pitch * Math.PI) / 180 })
|
|
15
|
+
}
|