@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,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MOBA examples on the rules' side: heroes with levels, skill points and callings (`heroesPack`), skillshots and
|
|
3
|
+
* the hero spells, a lane of minions and towers (`lanesPack`), last hits and denies (`lastHitsPack`), a shop of the
|
|
4
|
+
* engine's items (`defaultItemSet`), kill streaks and buyback, camps of the jungle, heroes that come back after they fall
|
|
5
|
+
* (`respawnPack`). Their units are `./units.ts`, their maps `./maps.ts`
|
|
6
|
+
*/
|
|
7
|
+
import {
|
|
8
|
+
applyBuff,
|
|
9
|
+
bool,
|
|
10
|
+
buy,
|
|
11
|
+
buyBack,
|
|
12
|
+
defaultItemAbilities,
|
|
13
|
+
defaultItemBuffs,
|
|
14
|
+
defaultItems,
|
|
15
|
+
defaultItemsPack,
|
|
16
|
+
defaultItemUnits,
|
|
17
|
+
defineCommand,
|
|
18
|
+
type Entity,
|
|
19
|
+
type Game,
|
|
20
|
+
type GamePack,
|
|
21
|
+
grantAbility,
|
|
22
|
+
grantXp,
|
|
23
|
+
heroesPack,
|
|
24
|
+
heroOf,
|
|
25
|
+
heroState,
|
|
26
|
+
heroTally,
|
|
27
|
+
laneMinion,
|
|
28
|
+
lanesOf,
|
|
29
|
+
lanesPack,
|
|
30
|
+
type LanesOptions,
|
|
31
|
+
lastHitsOf,
|
|
32
|
+
lastHitsPack,
|
|
33
|
+
learn,
|
|
34
|
+
mobaStatsPack,
|
|
35
|
+
number,
|
|
36
|
+
optional,
|
|
37
|
+
respawnIn,
|
|
38
|
+
respawnPack,
|
|
39
|
+
sendWave,
|
|
40
|
+
setHeroLevel,
|
|
41
|
+
slotsOf,
|
|
42
|
+
string,
|
|
43
|
+
type Vec2,
|
|
44
|
+
type World,
|
|
45
|
+
exactMath
|
|
46
|
+
} from '@v12-engine/engine'
|
|
47
|
+
import { nearestEnemy, packOnce, put, starts } from '../abilities/kit'
|
|
48
|
+
import type { ExampleCategory, ExampleDef } from '../define'
|
|
49
|
+
import { BLUE, jungleMap, laneMap, mobaField, RED, TOWERS } from './maps'
|
|
50
|
+
import { SPELL_IDS } from './spells'
|
|
51
|
+
import { abilities, allAbilities, allBuffs, units } from './units'
|
|
52
|
+
|
|
53
|
+
// ---- heroes
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Puts down a hero of `type`, raised to `level` with its points spent: the ultimate first where it may, then its
|
|
57
|
+
* other abilities in turn, as a player levels a hero
|
|
58
|
+
*/
|
|
59
|
+
export function putHero(world: World, type: string, owner: number, at: Vec2, level: number, name?: string): Entity {
|
|
60
|
+
const hero = put(world, type, owner, at, name, owner === BLUE ? Math.PI / 2 : -Math.PI / 2)
|
|
61
|
+
if (level > 1) setHeroLevel(world, hero, level)
|
|
62
|
+
const order = [abilities[3], abilities[0], abilities[1], abilities[2]].map((each) => each.id)
|
|
63
|
+
for (let spent = true; spent && (heroOf(hero)?.points ?? 0) > 0;) {
|
|
64
|
+
spent = false
|
|
65
|
+
for (const id of order) if ((heroOf(hero)?.points ?? 0) > 0 && learn(world, hero, id)) spent = true
|
|
66
|
+
}
|
|
67
|
+
return hero
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The player's hero: the one named `hero`, alive or fallen (a fallen one is out of the world, waiting to come back) */
|
|
71
|
+
function heroOfPlayer(world: World, player: number): Entity | undefined {
|
|
72
|
+
const named = world.named('hero')
|
|
73
|
+
if (named && named.owner === player) return named
|
|
74
|
+
for (const entity of world.index.with(heroState, player)) if (entity.def.id.startsWith('moba')) return entity
|
|
75
|
+
return undefined
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Gives a player gold */
|
|
79
|
+
function giveGold(world: World, player: number, amount: number): void {
|
|
80
|
+
const purse = world.player(player).resources
|
|
81
|
+
purse.gold = (purse.gold ?? 0) + amount
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---- the lane
|
|
85
|
+
|
|
86
|
+
/** The lane's waves: three minions that close in and two casters, the first a few seconds in */
|
|
87
|
+
const laneOptions: LanesOptions = {
|
|
88
|
+
firstWave: 4,
|
|
89
|
+
every: 20,
|
|
90
|
+
minions: { melee: 'mobaMinion', ranged: 'mobaCasterMinion' },
|
|
91
|
+
wave: () => ['mobaMinion', 'mobaMinion', 'mobaMinion', 'mobaCasterMinion', 'mobaCasterMinion'],
|
|
92
|
+
// the game's own way of winning (none: an example plays until it is left)
|
|
93
|
+
cores: false,
|
|
94
|
+
towers: (_world, entity) => entity.def.id === 'mobaTower'
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** A minion of a lane, of this game's lanes */
|
|
98
|
+
const isMinion = (_world: World, entity: Entity) => entity.has(laneMinion)
|
|
99
|
+
|
|
100
|
+
// ---- commands the cards send
|
|
101
|
+
|
|
102
|
+
/** Strafers walk side to side, or stand still */
|
|
103
|
+
export const strafe = defineCommand('moba.strafe', { on: bool }, (world, _player, { on }) => {
|
|
104
|
+
for (const unit of world.units.values()) {
|
|
105
|
+
if (!unit.alive || unit.def.id !== 'mobaStrafer') continue
|
|
106
|
+
if (on) unit.order('patrol', { x: unit.pos.x, z: unit.pos.z < 22 ? unit.pos.z + 6 : unit.pos.z - 6 })
|
|
107
|
+
else unit.order('hold')
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
/** Experience for the player's hero: as much as `amount`, or the rest of the way to its next level (`levels`) */
|
|
112
|
+
export const experience = defineCommand('moba.xp', { amount: optional(number), levels: optional(number) }, (world, player, { amount, levels }) => {
|
|
113
|
+
const hero = heroOfPlayer(world, player)
|
|
114
|
+
const state = hero && heroOf(hero)
|
|
115
|
+
if (!hero || !state) return
|
|
116
|
+
if (levels) setHeroLevel(world, hero, Math.min(state.level + levels, 18))
|
|
117
|
+
else grantXp(world, hero, amount ?? 100)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
/** The player's hero learns an ability, spending a point (`learn`): refused as a player's would be */
|
|
121
|
+
export const learnIt = defineCommand('moba.learn', { ability: string }, (world, player, { ability }) => {
|
|
122
|
+
const hero = heroOfPlayer(world, player)
|
|
123
|
+
if (hero && !learn(world, hero, ability)) world.message(player, 'cannotDoThat')
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
/** Gold for the player */
|
|
127
|
+
export const gold = defineCommand('moba.gold', { amount: number }, (world, player, { amount }) => giveGold(world, player, Math.min(amount, 10000)))
|
|
128
|
+
|
|
129
|
+
/** The player's hero buys an item at the shop named `shop`, as the shop window would (`buy`) */
|
|
130
|
+
export const buyItem = defineCommand('moba.buy', { item: string }, (world, player, { item }) => {
|
|
131
|
+
const shop = world.named('shop')
|
|
132
|
+
const hero = heroOfPlayer(world, player)
|
|
133
|
+
if (shop && hero) buy(world, shop, item, player, hero)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
/** The player's fallen hero bought back, there and then (`buyBack`) */
|
|
137
|
+
export const buyback = defineCommand('moba.buyback', {}, (world, player) => {
|
|
138
|
+
const hero = heroOfPlayer(world, player)
|
|
139
|
+
if (hero) buyBack(world, hero)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
/** Every hero of both sides goes for the other side, fighting what it meets */
|
|
143
|
+
export const fight = defineCommand('moba.fight', {}, (world) => {
|
|
144
|
+
for (const unit of world.index.with(heroState)) {
|
|
145
|
+
if (!unit.alive) continue
|
|
146
|
+
const enemy = nearestEnemy(world, unit)
|
|
147
|
+
if (enemy) unit.order('attackMove', { x: enemy.pos.x, z: enemy.pos.z })
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
/** A wave from both ends of the lane now, besides those that come by themselves */
|
|
152
|
+
export const wave = defineCommand('moba.wave', {}, (world) => {
|
|
153
|
+
for (const lane of lanesOf(world)) for (const end of lane.ends) if (end) sendWave(world, end.spawner.id, laneOptions)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The player's hero strikes the weakest minion near it: an enemy's to last-hit it, or (`deny`) one of its own side's
|
|
158
|
+
* that is below half its health, to deny it
|
|
159
|
+
*/
|
|
160
|
+
export const strike = defineCommand('moba.strike', { deny: bool }, (world, player, { deny }) => {
|
|
161
|
+
const hero = heroOfPlayer(world, player)
|
|
162
|
+
if (!hero?.alive) return
|
|
163
|
+
let best: Entity | undefined
|
|
164
|
+
for (const unit of world.spatial.query(hero.pos.x, hero.pos.z, 9)) {
|
|
165
|
+
if (!unit.alive || !unit.has(laneMinion)) continue
|
|
166
|
+
const ours = !world.areEnemies(player, unit.owner)
|
|
167
|
+
if (ours !== deny || (deny && unit.health >= unit.maxHealth * 0.5)) continue
|
|
168
|
+
if (!best || unit.health < best.health) best = unit
|
|
169
|
+
}
|
|
170
|
+
if (best) hero.order('attack', best)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
/** The player's hero attacks the nearest unit of a type */
|
|
174
|
+
export const attack = defineCommand('moba.attack', { unit: string }, (world, player, { unit: type }) => {
|
|
175
|
+
const hero = heroOfPlayer(world, player)
|
|
176
|
+
if (!hero?.alive) return
|
|
177
|
+
let best: Entity | undefined
|
|
178
|
+
for (const unit of world.units.values())
|
|
179
|
+
if (
|
|
180
|
+
unit.alive &&
|
|
181
|
+
unit.def.id === type &&
|
|
182
|
+
(!best || exactMath.hypot(unit.pos.x - hero.pos.x, unit.pos.z - hero.pos.z) < exactMath.hypot(best.pos.x - hero.pos.x, best.pos.z - hero.pos.z))
|
|
183
|
+
)
|
|
184
|
+
best = unit
|
|
185
|
+
if (best) hero.order('attack', best)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
/** Hurts or stuns the player's own hero, for a hero spell that answers it (Second Wind, Barrier, Purify) */
|
|
189
|
+
export const trouble = defineCommand('moba.trouble', { stun: bool }, (world, player, { stun }) => {
|
|
190
|
+
const hero = heroOfPlayer(world, player)
|
|
191
|
+
if (!hero?.alive) return
|
|
192
|
+
if (stun) applyBuff(world, hero, 'mobaHeld', 2.5)
|
|
193
|
+
else world.damage(hero, hero.health * 0.6, undefined, { damageType: 'true' })
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
const commands = [attack, strafe, experience, learnIt, gold, buyItem, buyback, fight, wave, strike, trouble]
|
|
197
|
+
|
|
198
|
+
/** What the HUD reads of the player: last hits and denies, and its hero's level, points, tally and wait to come back */
|
|
199
|
+
const report: GamePack = {
|
|
200
|
+
id: 'moba.report',
|
|
201
|
+
report(world, player) {
|
|
202
|
+
const hero = heroOfPlayer(world, player)
|
|
203
|
+
const state = hero && heroOf(hero)
|
|
204
|
+
const tally = hero?.get(heroTally)
|
|
205
|
+
return {
|
|
206
|
+
...lastHitsOf(world, player),
|
|
207
|
+
mobaHero: state
|
|
208
|
+
? {
|
|
209
|
+
level: state.level,
|
|
210
|
+
xp: Math.floor(state.xp),
|
|
211
|
+
next: world.settings.heroes.xpForLevel(state.level + 1),
|
|
212
|
+
points: state.points,
|
|
213
|
+
kills: tally?.kills ?? 0,
|
|
214
|
+
deaths: tally?.deaths ?? 0,
|
|
215
|
+
assists: tally?.assists ?? 0,
|
|
216
|
+
streak: tally?.streak ?? 0,
|
|
217
|
+
back: hero.alive ? 0 : Math.ceil(respawnIn(world, hero) ?? 0),
|
|
218
|
+
items: (slotsOf(hero) ?? []).flatMap((slot) =>
|
|
219
|
+
slot ? [world.registry.items.has(slot.item) ? world.registry.item(slot.item).name : slot.item] : []
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
: undefined
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ---- the examples
|
|
228
|
+
|
|
229
|
+
/** The examples' starts by id, kept for their Start again button (`starts`) */
|
|
230
|
+
function example(def: ExampleDef): ExampleDef {
|
|
231
|
+
if (def.start) starts[def.id] = def.start as (world: World) => void
|
|
232
|
+
return def
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const SOURCE = ['examples/moba/index.ts', 'examples/moba/units.ts', 'examples/moba/maps.ts']
|
|
236
|
+
|
|
237
|
+
export const examples: ExampleDef[] = [
|
|
238
|
+
example({
|
|
239
|
+
id: 'moba-skillshots',
|
|
240
|
+
category: 'moba',
|
|
241
|
+
title: 'Skillshots and a locked camera',
|
|
242
|
+
summary:
|
|
243
|
+
'Aimed abilities that hit the first enemy in their way: a bolt, a chain that drags back, a dash, a lance. The duelists step side to side, so aim ahead of them; the camera can be locked on the hero.',
|
|
244
|
+
tags: ['skillshot', 'hook', 'dash', 'direction', 'camera', 'quick cast', 'league'],
|
|
245
|
+
source: SOURCE,
|
|
246
|
+
map: () => mobaField({ name: 'Skillshots', width: 48, depth: 40, blue: { x: 8, z: 20 }, red: { x: 40, z: 20 } }),
|
|
247
|
+
start: (world) => {
|
|
248
|
+
putHero(world, 'mobaStormcaller', BLUE, { x: 16, z: 20 }, 6, 'hero')
|
|
249
|
+
const duelists: Vec2[] = [
|
|
250
|
+
{ x: 27, z: 15 },
|
|
251
|
+
{ x: 30, z: 22 },
|
|
252
|
+
{ x: 26, z: 26 }
|
|
253
|
+
]
|
|
254
|
+
duelists.forEach((at, i) =>
|
|
255
|
+
put(world, 'mobaStrafer', RED, at, `enemy${i + 1}`, -Math.PI / 2).order('patrol', { x: at.x, z: at.z + (i % 2 ? -5 : 5) })
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
}),
|
|
259
|
+
example({
|
|
260
|
+
id: 'moba-spells',
|
|
261
|
+
category: 'moba',
|
|
262
|
+
title: 'Hero spells: Flash and the rest',
|
|
263
|
+
summary:
|
|
264
|
+
"The engine's nine hero spells, two of them on D and F: Flash, Barrier, Second Wind, Brand, Wither, Phantom Stride, Purify, Waystone and Reap. Each button casts one; hurt or stun the hero first to see what answers it.",
|
|
265
|
+
tags: ['flash', 'barrier', 'ignite', 'exhaust', 'ghost', 'cleanse', 'teleport', 'smite', 'summoner spells', 'D', 'F'],
|
|
266
|
+
source: [...SOURCE, 'examples/moba/spells.ts'],
|
|
267
|
+
map: () => mobaField({ name: 'Hero spells', width: 48, depth: 40, blue: { x: 8, z: 20 }, red: { x: 40, z: 20 } }),
|
|
268
|
+
start: (world) => {
|
|
269
|
+
const hero = putHero(world, 'mobaStormcaller', BLUE, { x: 18, z: 20 }, 6, 'hero')
|
|
270
|
+
// the other seven, besides its two: given for good (`grantAbility`)
|
|
271
|
+
for (const id of SPELL_IDS) grantAbility(world, hero, id, { level: 1 })
|
|
272
|
+
put(world, 'mobaDuskblade', RED, { x: 26, z: 21 }, 'rival', -Math.PI / 2)
|
|
273
|
+
// what Reap is for (no hero), and what Waystone goes to (an ally of the hero's far off)
|
|
274
|
+
put(world, 'mobaBrute', RED, { x: 25, z: 15 }, 'creep', -Math.PI / 2)
|
|
275
|
+
put(world, 'mobaMinion', BLUE, { x: 36, z: 31 }, 'waystone')
|
|
276
|
+
}
|
|
277
|
+
}),
|
|
278
|
+
example({
|
|
279
|
+
id: 'moba-lane',
|
|
280
|
+
category: 'moba',
|
|
281
|
+
title: 'A lane: waves, towers, last hits',
|
|
282
|
+
summary:
|
|
283
|
+
'Waves of minions walk the lane from both ends and fight; two towers a side guard it. Strike the blow that kills an enemy minion for its gold (a last hit), or kill one of your own below half health so the enemy gets less (a deny).',
|
|
284
|
+
tags: ['lane', 'minions', 'waves', 'towers', 'last hit', 'deny', 'creep score', 'farm'],
|
|
285
|
+
source: [...SOURCE, 'examples/moba/maps.ts'],
|
|
286
|
+
map: laneMap,
|
|
287
|
+
start: (world) => {
|
|
288
|
+
for (const at of TOWERS.blue) put(world, 'mobaTower', BLUE, at)
|
|
289
|
+
for (const at of TOWERS.red) put(world, 'mobaTower', RED, at)
|
|
290
|
+
putHero(world, 'mobaStormcaller', BLUE, { x: 30, z: 14 }, 3, 'hero')
|
|
291
|
+
}
|
|
292
|
+
}),
|
|
293
|
+
example({
|
|
294
|
+
id: 'moba-shop',
|
|
295
|
+
category: 'moba',
|
|
296
|
+
title: 'A shop and build paths',
|
|
297
|
+
summary:
|
|
298
|
+
"The engine's whole tree of items in a shop: starters, basics, what they build into, legendaries. Open the shop window (P, or click the shop), or buy a path from the card: the parts first, then the recipe that uses them up.",
|
|
299
|
+
tags: ['shop', 'items', 'recipe', 'build path', 'gold', 'inventory', 'default items'],
|
|
300
|
+
source: SOURCE,
|
|
301
|
+
map: () => mobaField({ name: 'Shop', width: 40, depth: 40, blue: { x: 20, z: 14 }, red: { x: 20, z: 36 }, gold: 4000 }),
|
|
302
|
+
start: (world) => {
|
|
303
|
+
put(world, 'mobaShop', BLUE, { x: 20.5, z: 22.5 }, 'shop')
|
|
304
|
+
putHero(world, 'mobaDuskblade', BLUE, { x: 20, z: 18 }, 1, 'hero')
|
|
305
|
+
}
|
|
306
|
+
}),
|
|
307
|
+
example({
|
|
308
|
+
id: 'moba-levels',
|
|
309
|
+
category: 'moba',
|
|
310
|
+
title: 'Levels, skill points and callings',
|
|
311
|
+
summary:
|
|
312
|
+
'A hero gains experience, levels up and gets a point a level to learn an ability with: Q, W and E from level 1, the ultimate R from level 6. Its callings (Arcanist, Binder) are on its panel; learn with the + on it, or the card.',
|
|
313
|
+
tags: ['hero', 'level', 'experience', 'skill points', 'ultimate', 'callings', 'roles'],
|
|
314
|
+
source: SOURCE,
|
|
315
|
+
map: () => mobaField({ name: 'Levels', width: 40, depth: 40, blue: { x: 14, z: 20 }, red: { x: 32, z: 20 } }),
|
|
316
|
+
start: (world) => {
|
|
317
|
+
put(world, 'mobaStormcaller', BLUE, { x: 18, z: 20 }, 'hero', Math.PI / 2)
|
|
318
|
+
for (let i = 0; i < 3; i++) put(world, 'mobaMinion', RED, { x: 24, z: 17 + i * 3 }, undefined, -Math.PI / 2).order('hold')
|
|
319
|
+
}
|
|
320
|
+
}),
|
|
321
|
+
example({
|
|
322
|
+
id: 'moba-teamfight',
|
|
323
|
+
category: 'moba',
|
|
324
|
+
title: 'Kill streaks, assists and buyback',
|
|
325
|
+
summary:
|
|
326
|
+
'Two heroes a side. A hero that kills is on a streak until it falls, a kill helped is an assist, a fallen hero comes back after a while, or at once for gold (buyback). Fight sends them at each other.',
|
|
327
|
+
tags: ['kills', 'deaths', 'assists', 'streak', 'shutdown', 'buyback', 'respawn', 'KDA'],
|
|
328
|
+
source: SOURCE,
|
|
329
|
+
map: () => mobaField({ name: 'Teamfight', width: 48, depth: 32, blue: { x: 6, z: 16 }, red: { x: 42, z: 16 }, gold: 1500 }),
|
|
330
|
+
start: (world) => {
|
|
331
|
+
putHero(world, 'mobaStormcaller', BLUE, { x: 18, z: 14 }, 8, 'hero')
|
|
332
|
+
putHero(world, 'mobaDuskblade', BLUE, { x: 19, z: 18 }, 8)
|
|
333
|
+
putHero(world, 'mobaDuskblade', RED, { x: 30, z: 14 }, 5)
|
|
334
|
+
putHero(world, 'mobaStormcaller', RED, { x: 31, z: 18 }, 5)
|
|
335
|
+
}
|
|
336
|
+
}),
|
|
337
|
+
example({
|
|
338
|
+
id: 'moba-jungle',
|
|
339
|
+
category: 'moba',
|
|
340
|
+
title: 'Camps of the jungle',
|
|
341
|
+
summary:
|
|
342
|
+
'Creeps of the jungle keep to their camp: they turn on whoever strikes one, chase a while and walk back. Cleared, a camp comes back 20 or 30 seconds later. Reap (F) finishes a creep off with true damage.',
|
|
343
|
+
tags: ['jungle', 'camps', 'creeps', 'neutral', 'respawn', 'smite', 'reap', 'leash'],
|
|
344
|
+
source: [...SOURCE, 'examples/moba/maps.ts'],
|
|
345
|
+
map: jungleMap,
|
|
346
|
+
start: (world) => {
|
|
347
|
+
const hero = putHero(world, 'mobaDuskblade', BLUE, { x: 18, z: 16 }, 6, 'hero')
|
|
348
|
+
grantAbility(world, hero, 'mobaReap', { level: 1 })
|
|
349
|
+
}
|
|
350
|
+
})
|
|
351
|
+
]
|
|
352
|
+
|
|
353
|
+
export const category: ExampleCategory = {
|
|
354
|
+
rules: (game: Game<any>) => {
|
|
355
|
+
// the gold a shop sells for and a kill pays: none to start with, so it shows nothing in the other examples
|
|
356
|
+
if (!(game.toRules().resources ?? []).some((each) => each.id === 'gold')) game.resource({ id: 'gold', name: 'Gold', start: 0 })
|
|
357
|
+
packOnce(game, heroesPack())
|
|
358
|
+
packOnce(game, mobaStatsPack())
|
|
359
|
+
packOnce(game, defaultItemsPack())
|
|
360
|
+
packOnce(game, lanesPack(laneOptions))
|
|
361
|
+
// last hits and denies of lane minions alone
|
|
362
|
+
packOnce(game, lastHitsPack({ deny: { below: 0.5, denies: (world, _attacker, target) => isMinion(world, target) }, counts: isMinion }))
|
|
363
|
+
// the MOBA's heroes come back by themselves, 6 seconds after they fall; another example's hero is left as it was
|
|
364
|
+
packOnce(game, respawnPack({ time: () => 6, heroes: (_world, hero) => hero.def.id.startsWith('moba') }))
|
|
365
|
+
packOnce(game, report)
|
|
366
|
+
// the engine's items, with what they use: their abilities, buffs and units (a ward, a wolf)
|
|
367
|
+
for (const item of defaultItems) game.item(item)
|
|
368
|
+
for (const ability of defaultItemAbilities) game.ability(ability)
|
|
369
|
+
for (const buff of defaultItemBuffs) game.buff(buff)
|
|
370
|
+
for (const unit of defaultItemUnits) game.unit(unit)
|
|
371
|
+
for (const unit of units) game.unit(unit)
|
|
372
|
+
for (const ability of allAbilities) game.ability(ability)
|
|
373
|
+
for (const buff of allBuffs) game.buff(buff)
|
|
374
|
+
for (const command of commands) game.command(command)
|
|
375
|
+
},
|
|
376
|
+
examples
|
|
377
|
+
}
|