@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,570 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The destruction examples on the rules' side: buildings, trees, rocks, fences and grass to break, and what breaks
|
|
3
|
+
* them (a meteor, a shockwave, kegs of powder, catapults). The rules only say what dies, and how (`DamageOptions.how`);
|
|
4
|
+
* how it comes apart on the screen is the look's (`./looks.ts`: `UnitLook.death`), with the client's physics
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
type AreaShape,
|
|
8
|
+
createMap,
|
|
9
|
+
defineCellLayer,
|
|
10
|
+
defineCommand,
|
|
11
|
+
defineComponent,
|
|
12
|
+
defineTimer,
|
|
13
|
+
entityDied,
|
|
14
|
+
type Entity,
|
|
15
|
+
FOLIAGE_STATE,
|
|
16
|
+
foliageStateLayer,
|
|
17
|
+
type Game,
|
|
18
|
+
MAP_NEUTRAL,
|
|
19
|
+
mapTerrain,
|
|
20
|
+
number,
|
|
21
|
+
oneOf,
|
|
22
|
+
presetFoliageStyles,
|
|
23
|
+
terrainPatch,
|
|
24
|
+
text,
|
|
25
|
+
type UnitTypeDef,
|
|
26
|
+
type Vec2,
|
|
27
|
+
type World,
|
|
28
|
+
exactMath
|
|
29
|
+
} from '@v12-engine/engine'
|
|
30
|
+
import { ENEMY, PLAYER, UNIT } from '../common/units'
|
|
31
|
+
import { type ExampleCategory, type ExampleDef, exampleField } from '../define'
|
|
32
|
+
|
|
33
|
+
/** The destruction examples' own unit types: what stands to be broken, and what breaks it */
|
|
34
|
+
export const D = {
|
|
35
|
+
house: 'destructionHouse',
|
|
36
|
+
hut: 'destructionHut',
|
|
37
|
+
tower: 'destructionTower',
|
|
38
|
+
wall: 'destructionWall',
|
|
39
|
+
fence: 'destructionFence',
|
|
40
|
+
tree: 'destructionTree',
|
|
41
|
+
rock: 'destructionRock',
|
|
42
|
+
keg: 'destructionKeg',
|
|
43
|
+
catapult: 'destructionCatapult',
|
|
44
|
+
blast: 'destructionBlast'
|
|
45
|
+
} as const
|
|
46
|
+
|
|
47
|
+
/** The words for how something died (`DamageOptions.how`): each look says what it does for each (`death.how`) */
|
|
48
|
+
export const HOW = {
|
|
49
|
+
crumble: 'destructionCrumble',
|
|
50
|
+
explode: 'destructionExplode',
|
|
51
|
+
topple: 'destructionTopple',
|
|
52
|
+
sink: 'destructionSink',
|
|
53
|
+
shatter: 'destructionShatter',
|
|
54
|
+
planks: 'destructionPlanks',
|
|
55
|
+
// a meteor, a keg going off: blown apart, away from the blast
|
|
56
|
+
blasted: 'destructionBlasted',
|
|
57
|
+
// a shockwave: flung far, in many pieces
|
|
58
|
+
swept: 'destructionSwept',
|
|
59
|
+
// a catapult's boulder
|
|
60
|
+
crushed: 'destructionCrushed'
|
|
61
|
+
} as const
|
|
62
|
+
|
|
63
|
+
/** The layer of grass the examples lay and burn: their own, drawn by `./looks.ts`, no pack of the game's needed */
|
|
64
|
+
export const GRASS = 'destructionGrass'
|
|
65
|
+
|
|
66
|
+
/** Whether what falls in this world grows back by itself, after how many seconds: the regrowing example's alone */
|
|
67
|
+
const regrowing = defineComponent<{ after: number } | null>({ id: 'destruction.regrowing', default: () => null })
|
|
68
|
+
|
|
69
|
+
/** A number from 0 to 1 for a whole number: the same everywhere, for scattering things the same on every machine */
|
|
70
|
+
const hash = (n: number): number => {
|
|
71
|
+
let h = Math.imul(n ^ 0x9e3779b9, 0x85ebca6b)
|
|
72
|
+
h = Math.imul(h ^ (h >>> 13), 0xc2b2ae35)
|
|
73
|
+
return ((h ^ (h >>> 16)) >>> 0) / 4294967296
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* A tree, a rock: scenery to break. A building of nobody's rather than a resource, so that each is drawn as a model of
|
|
78
|
+
* its own, whose meshes the client can split into pieces as it dies (`death.shatter`); trees as resources are drawn
|
|
79
|
+
* many to a batch, and fall or throw solids (`death.debris`) instead
|
|
80
|
+
*/
|
|
81
|
+
const scenery = (id: string, name: string, own: Partial<UnitTypeDef>): UnitTypeDef => ({
|
|
82
|
+
id,
|
|
83
|
+
name,
|
|
84
|
+
kind: 'building',
|
|
85
|
+
radius: 0.5,
|
|
86
|
+
footprint: { w: 1, d: 1 },
|
|
87
|
+
stats: { health: 60 },
|
|
88
|
+
sight: 0,
|
|
89
|
+
// nobody hits it on their own: only what the examples throw at it
|
|
90
|
+
ignored: true,
|
|
91
|
+
// what falls may grow back: put down as a site that builds itself (`construction: 'self'`), its look growing with it
|
|
92
|
+
construction: 'self',
|
|
93
|
+
buildTime: 5,
|
|
94
|
+
...own
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
const building = (id: string, name: string, size: number, health: number, own: Partial<UnitTypeDef> = {}): UnitTypeDef => ({
|
|
98
|
+
id,
|
|
99
|
+
name,
|
|
100
|
+
kind: 'building',
|
|
101
|
+
radius: size / 2,
|
|
102
|
+
footprint: { w: size, d: size },
|
|
103
|
+
stats: { health, armor: 2 },
|
|
104
|
+
sight: 4,
|
|
105
|
+
construction: 'self',
|
|
106
|
+
buildTime: 4,
|
|
107
|
+
...own
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const types: UnitTypeDef[] = [
|
|
111
|
+
building(D.house, 'House', 3, 900),
|
|
112
|
+
building(D.hut, 'Hut', 2, 500),
|
|
113
|
+
building(D.tower, 'Watch tower', 2, 700),
|
|
114
|
+
// a length of wall or fence: three cells along x, one deep
|
|
115
|
+
{ ...building(D.wall, 'Stone wall', 3, 600), footprint: { w: 3, d: 1 }, radius: 0.5 },
|
|
116
|
+
{ ...building(D.fence, 'Fence', 3, 120), footprint: { w: 3, d: 1 }, radius: 0.5 },
|
|
117
|
+
building(D.keg, 'Keg of powder', 1, 40, { stats: { health: 40 } }),
|
|
118
|
+
scenery(D.tree, 'Tree', { fellable: true }),
|
|
119
|
+
scenery(D.rock, 'Rock', { footprint: { w: 2, d: 2 }, radius: 1, stats: { health: 300 }, buildTime: 4 }),
|
|
120
|
+
{
|
|
121
|
+
id: D.catapult,
|
|
122
|
+
name: 'Catapult',
|
|
123
|
+
kind: 'unit',
|
|
124
|
+
radius: 0.8,
|
|
125
|
+
stats: { health: 500, attackDamage: 160, attackSpeed: 0.5, attackRange: 16, moveSpeed: 2.5, armor: 10 },
|
|
126
|
+
sight: 18,
|
|
127
|
+
// a boulder thrown in an arc, landing where it was aimed: all near it hurt, trees brought down
|
|
128
|
+
attacks: [
|
|
129
|
+
{
|
|
130
|
+
damagePoint: 0.6,
|
|
131
|
+
acquisitionRange: 16,
|
|
132
|
+
projectileSpeed: 14,
|
|
133
|
+
splash: 2,
|
|
134
|
+
ground: true,
|
|
135
|
+
fells: true,
|
|
136
|
+
how: HOW.crushed,
|
|
137
|
+
targets: ['building', 'unit']
|
|
138
|
+
}
|
|
139
|
+
]
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
// where a blast comes from: what flung pieces fly away from. Here for a second, then gone; nothing sees or hits it
|
|
143
|
+
id: D.blast,
|
|
144
|
+
name: 'Blast',
|
|
145
|
+
kind: 'unit',
|
|
146
|
+
radius: 0.1,
|
|
147
|
+
stats: { health: 1, moveSpeed: 0 },
|
|
148
|
+
sight: 0,
|
|
149
|
+
invulnerable: true,
|
|
150
|
+
targetableBy: [],
|
|
151
|
+
lifetime: 1
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
// ---- what breaks things
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Hurts everything within `radius` of a point as a blast does: in full at its heart, less to the edge, killing what it
|
|
159
|
+
* kills `how` it says, from a blast at the point (what the pieces fly away from). The grass under it burns black, and
|
|
160
|
+
* what is left of the ground is dug into a crater `depth` deep. What it hit, nearest first
|
|
161
|
+
*/
|
|
162
|
+
export function blast(world: World, at: Vec2, options: { radius: number; damage: number; how: string; crater?: number; except?: Entity }): Entity[] {
|
|
163
|
+
const from = world.spawn(D.blast, { at, owner: MAP_NEUTRAL })
|
|
164
|
+
const hit = world.query({ near: at, radius: options.radius, sort: 'nearest', ...(options.except && { except: [options.except, from] }) })
|
|
165
|
+
for (const each of hit) {
|
|
166
|
+
if (each === from || each.def.id === D.blast) continue
|
|
167
|
+
const away = exactMath.hypot(each.pos.x - at.x, each.pos.z - at.z)
|
|
168
|
+
const share = 1 - 0.6 * Math.min(1, away / options.radius)
|
|
169
|
+
world.damage(each, options.damage * share, from, { how: options.how, tags: ['blast'] })
|
|
170
|
+
}
|
|
171
|
+
burnGrass(world, { kind: 'circle', x: at.x, z: at.z, radius: options.radius * 0.8 })
|
|
172
|
+
if (options.crater) crater(world, at, options.radius * 0.6, options.crater)
|
|
173
|
+
return hit
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** The grass in an area burnt black: its state layer set (`FOLIAGE_STATE.burnt`), drawn as stubble over ash */
|
|
177
|
+
export function burnGrass(world: World, where: AreaShape): void {
|
|
178
|
+
const layer = world.terrain.cells[GRASS]
|
|
179
|
+
if (!layer) return
|
|
180
|
+
world.setCells(foliageStateLayer(GRASS), where, FOLIAGE_STATE.burnt)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** The ground dug into a bowl round a point (`World.reshapeGround`): deepest at the middle, `depth` down, painted dirt */
|
|
184
|
+
export function crater(world: World, at: Vec2, radius: number, depth: number): void {
|
|
185
|
+
const { terrain } = world
|
|
186
|
+
const x = Math.max(0, Math.floor(at.x - radius - 1))
|
|
187
|
+
const z = Math.max(0, Math.floor(at.z - radius - 1))
|
|
188
|
+
const width = Math.min(terrain.width - x, Math.ceil(radius * 2 + 2))
|
|
189
|
+
const deep = Math.min(terrain.depth - z, Math.ceil(radius * 2 + 2))
|
|
190
|
+
const patch = terrainPatch(terrain, { x, z, width, depth: deep })
|
|
191
|
+
const dirt = world.registry.tile('dirt')
|
|
192
|
+
for (let vz = 0; vz <= deep; vz++)
|
|
193
|
+
for (let vx = 0; vx <= width; vx++) {
|
|
194
|
+
const d = exactMath.hypot(x + vx - at.x, z + vz - at.z) / radius
|
|
195
|
+
if (d >= 1) continue
|
|
196
|
+
// a bowl with a lip: down in the middle, a little up at its rim
|
|
197
|
+
const bowl = exactMath.cos((d * Math.PI) / 2)
|
|
198
|
+
const down = bowl * bowl * depth - exactMath.sin(d * Math.PI) * depth * 0.15
|
|
199
|
+
patch.heights[vz * (width + 1) + vx] -= down
|
|
200
|
+
}
|
|
201
|
+
for (let cz = 0; cz < deep; cz++)
|
|
202
|
+
for (let cx = 0; cx < width; cx++)
|
|
203
|
+
if (exactMath.hypot(x + cx + 0.5 - at.x, z + cz + 0.5 - at.z) < radius * 0.85) patch.tiles[cz * width + cx] = dirt
|
|
204
|
+
world.reshapeGround(patch)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Grass laid over an area */
|
|
208
|
+
const grass = (world: World, where: AreaShape) => world.setCells(GRASS, where, 1)
|
|
209
|
+
|
|
210
|
+
/** Trees round a point in a disc, a cell apart at least, scattered the same every time; none within `clear` of the middle */
|
|
211
|
+
function grove(world: World, at: Vec2, radius: number, clear = 0, every = 0.45): void {
|
|
212
|
+
for (let z = Math.floor(at.z - radius); z <= at.z + radius; z++)
|
|
213
|
+
for (let x = Math.floor(at.x - radius); x <= at.x + radius; x++) {
|
|
214
|
+
const d = exactMath.hypot(x + 0.5 - at.x, z + 0.5 - at.z)
|
|
215
|
+
if (d > radius || d < clear || hash(x * 131 + z * 7919) > every) continue
|
|
216
|
+
// every other cell at most, so they stand apart
|
|
217
|
+
if ((x + z) % 2) continue
|
|
218
|
+
world.spawn(D.tree, { at: { x: x + 0.5, z: z + 0.5 }, owner: MAP_NEUTRAL })
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Takes away everything an example put down: what it lays out again starts on an empty field */
|
|
223
|
+
function clear(world: World): void {
|
|
224
|
+
for (const each of [...world.entities.values()]) if (each.alive) world.kill(each, undefined, false, 'removed')
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ---- the examples' layouts
|
|
228
|
+
|
|
229
|
+
/** Five buildings in a row, each to be brought down its own way */
|
|
230
|
+
function row(world: World): void {
|
|
231
|
+
const kinds = [D.house, D.hut, D.tower, D.house, D.hut, D.house]
|
|
232
|
+
kinds.forEach((type, i) => world.spawn(type, { at: { x: 8.5 + i * 6.5, z: 24.5 }, owner: ENEMY }))
|
|
233
|
+
grass(world, { kind: 'rect', x: 4, z: 18, width: 40, depth: 12 })
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** A village: houses and huts round a square, fences, trees about it, rocks, grass, and its people */
|
|
237
|
+
function village(world: World): void {
|
|
238
|
+
const houses: [string, number, number][] = [
|
|
239
|
+
[D.house, 18.5, 28.5],
|
|
240
|
+
[D.house, 29.5, 28.5],
|
|
241
|
+
[D.hut, 24, 32],
|
|
242
|
+
[D.hut, 16, 21],
|
|
243
|
+
[D.hut, 32, 21],
|
|
244
|
+
[D.tower, 24, 15]
|
|
245
|
+
]
|
|
246
|
+
for (const [type, x, z] of houses) world.spawn(type, { at: { x, z }, owner: ENEMY })
|
|
247
|
+
for (let i = 0; i < 3; i++) world.spawn(D.fence, { at: { x: 14.5 + i * 3, z: 16.5 }, owner: ENEMY })
|
|
248
|
+
for (let i = 0; i < 3; i++) world.spawn(D.fence, { at: { x: 27.5 + i * 3, z: 16.5 }, owner: ENEMY })
|
|
249
|
+
for (const [x, z] of [
|
|
250
|
+
[11, 26],
|
|
251
|
+
[37, 26],
|
|
252
|
+
[20, 37],
|
|
253
|
+
[30, 37]
|
|
254
|
+
])
|
|
255
|
+
world.spawn(D.rock, { at: { x, z }, owner: MAP_NEUTRAL })
|
|
256
|
+
grove(world, { x: 24, z: 24 }, 18, 10, 0.6)
|
|
257
|
+
grass(world, { kind: 'circle', x: 24, z: 24, radius: 16 })
|
|
258
|
+
for (let i = 0; i < 6; i++) world.spawn(UNIT.footman, { at: { x: 21 + (i % 3) * 1.5, z: 22 + Math.floor(i / 3) * 1.5 }, owner: ENEMY })
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** A hero in a clearing in a wood: what the shockwave goes out from */
|
|
262
|
+
function clearing(world: World): void {
|
|
263
|
+
world.spawn(UNIT.knight, { at: { x: 24, z: 24 }, owner: PLAYER })
|
|
264
|
+
grove(world, { x: 24, z: 24 }, 16, 4, 0.4)
|
|
265
|
+
for (const [x, z] of [
|
|
266
|
+
[18, 18],
|
|
267
|
+
[30, 19],
|
|
268
|
+
[17, 30],
|
|
269
|
+
[31, 31],
|
|
270
|
+
[24, 33]
|
|
271
|
+
])
|
|
272
|
+
world.spawn(D.rock, { at: { x, z }, owner: MAP_NEUTRAL })
|
|
273
|
+
for (let i = 0; i < 3; i++) world.spawn(D.fence, { at: { x: 20.5 + i * 3, z: 37.5 }, owner: MAP_NEUTRAL })
|
|
274
|
+
grass(world, { kind: 'circle', x: 24, z: 24, radius: 20 })
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** A fort of walls and towers with kegs of powder along a winding line through it: one lit, the rest go in turn */
|
|
278
|
+
function fort(world: World): void {
|
|
279
|
+
for (let i = 0; i < 6; i++) {
|
|
280
|
+
world.spawn(D.wall, { at: { x: 12.5 + i * 3, z: 34.5 }, owner: ENEMY })
|
|
281
|
+
world.spawn(D.wall, { at: { x: 12.5 + i * 3, z: 14.5 }, owner: ENEMY })
|
|
282
|
+
}
|
|
283
|
+
for (const [x, z] of [
|
|
284
|
+
[10, 14],
|
|
285
|
+
[30, 14],
|
|
286
|
+
[10, 35],
|
|
287
|
+
[30, 35]
|
|
288
|
+
])
|
|
289
|
+
world.spawn(D.tower, { at: { x, z }, owner: ENEMY })
|
|
290
|
+
world.spawn(D.house, { at: { x: 16.5, z: 24.5 }, owner: ENEMY })
|
|
291
|
+
world.spawn(D.house, { at: { x: 25.5, z: 27.5 }, owner: ENEMY })
|
|
292
|
+
world.spawn(D.hut, { at: { x: 25, z: 20 }, owner: ENEMY })
|
|
293
|
+
// the kegs: a line that winds from the gate through the fort and past every building
|
|
294
|
+
for (const [x, z] of KEGS) world.spawn(D.keg, { at: { x: x + 0.5, z: z + 0.5 }, owner: ENEMY })
|
|
295
|
+
grass(world, { kind: 'rect', x: 6, z: 10, width: 30, depth: 30 })
|
|
296
|
+
grove(world, { x: 40, z: 24 }, 6, 0, 0.7)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Where the kegs stand, in the order the fire takes them */
|
|
300
|
+
const KEGS: [number, number][] = [
|
|
301
|
+
[34, 24],
|
|
302
|
+
[32, 24],
|
|
303
|
+
[30, 23],
|
|
304
|
+
[28, 22],
|
|
305
|
+
[28, 20],
|
|
306
|
+
[27, 17],
|
|
307
|
+
[25, 16],
|
|
308
|
+
[22, 17],
|
|
309
|
+
[20, 18],
|
|
310
|
+
[19, 20],
|
|
311
|
+
[20, 22],
|
|
312
|
+
[22, 23],
|
|
313
|
+
[22, 25],
|
|
314
|
+
[21, 27],
|
|
315
|
+
[19, 28],
|
|
316
|
+
[16, 28],
|
|
317
|
+
[13, 27],
|
|
318
|
+
[12, 25],
|
|
319
|
+
[12, 22],
|
|
320
|
+
[13, 19],
|
|
321
|
+
[12, 16],
|
|
322
|
+
[13, 31],
|
|
323
|
+
[16, 32],
|
|
324
|
+
[20, 32],
|
|
325
|
+
[24, 31],
|
|
326
|
+
[28, 31],
|
|
327
|
+
[29, 33]
|
|
328
|
+
]
|
|
329
|
+
|
|
330
|
+
/** A grove, rocks and grass to smash: all of it grows back, a few seconds after it fell */
|
|
331
|
+
function meadow(world: World): void {
|
|
332
|
+
world.setState(regrowing, { after: 4 })
|
|
333
|
+
grove(world, { x: 24, z: 26 }, 11, 0, 0.7)
|
|
334
|
+
for (const [x, z] of [
|
|
335
|
+
[18, 20],
|
|
336
|
+
[30, 21],
|
|
337
|
+
[24, 32]
|
|
338
|
+
])
|
|
339
|
+
world.spawn(D.rock, { at: { x, z }, owner: MAP_NEUTRAL })
|
|
340
|
+
grass(world, { kind: 'circle', x: 24, z: 26, radius: 14 })
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** A stone fort on the far side of the field, and catapults on the near side to knock it down */
|
|
344
|
+
function siege(world: World): void {
|
|
345
|
+
for (let i = 0; i < 5; i++) world.spawn(D.wall, { at: { x: 16.5 + i * 3, z: 30.5 }, owner: ENEMY })
|
|
346
|
+
world.spawn(D.tower, { at: { x: 14, z: 31 }, owner: ENEMY })
|
|
347
|
+
world.spawn(D.tower, { at: { x: 34, z: 31 }, owner: ENEMY })
|
|
348
|
+
world.spawn(D.house, { at: { x: 20.5, z: 35.5 }, owner: ENEMY })
|
|
349
|
+
world.spawn(D.house, { at: { x: 28.5, z: 35.5 }, owner: ENEMY })
|
|
350
|
+
world.spawn(D.hut, { at: { x: 24, z: 40 }, owner: ENEMY })
|
|
351
|
+
grove(world, { x: 24, z: 37 }, 12, 6, 0.4)
|
|
352
|
+
for (let i = 0; i < 4; i++) world.spawn(D.catapult, { at: { x: 15 + i * 6, z: 12 }, owner: PLAYER, facing: Math.PI / 2 })
|
|
353
|
+
grass(world, { kind: 'rect', x: 4, z: 26, width: 40, depth: 18 })
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** Each example's layout, by its id: what it starts with, and what Lay it out again lays */
|
|
357
|
+
const LAYOUTS: Record<string, (world: World) => void> = {
|
|
358
|
+
'destruction-collapse': row,
|
|
359
|
+
'destruction-meteor': village,
|
|
360
|
+
'destruction-shockwave': clearing,
|
|
361
|
+
'destruction-chain': fort,
|
|
362
|
+
'destruction-regrow': meadow,
|
|
363
|
+
'destruction-siege': siege
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// ---- commands, sent by the examples' buttons
|
|
367
|
+
|
|
368
|
+
/** Brings the first building of the row still standing down, `how` the button says */
|
|
369
|
+
export const raze = defineCommand('destruction.raze', { how: oneOf(...Object.values(HOW)) }, (world, _player, { how }) => {
|
|
370
|
+
const standing = world.query({ kinds: ['building'], owner: ENEMY }).sort((a, b) => a.pos.x - b.pos.x)[0]
|
|
371
|
+
if (!standing) return
|
|
372
|
+
// hit from in front, where the camera is: a toppled one falls away from it
|
|
373
|
+
const from = world.spawn(D.blast, { at: { x: standing.pos.x - 1, z: standing.pos.z - 6 }, owner: MAP_NEUTRAL })
|
|
374
|
+
// more than it has, past its armour
|
|
375
|
+
world.damage(standing, standing.maxHealth * 10, from, { how })
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
/** Hurts every building to a share of its health: what its look shows under a half, a quarter */
|
|
379
|
+
export const hurt = defineCommand('destruction.hurt', { share: number }, (world, _player, { share }) => {
|
|
380
|
+
for (const each of world.query({ kinds: ['building'], owner: ENEMY })) each.health = Math.max(1, Math.min(each.health, each.maxHealth * share))
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
/** A meteor comes down at a point: a big blast, a crater, everything round it broken and flung away */
|
|
384
|
+
export const meteor = defineCommand('destruction.meteor', { x: number, z: number, size: number }, (world, _player, { x, z, size }) => {
|
|
385
|
+
world.fx('explosion', { at: { x, z }, params: { size: 2.6 * size, shake: 1.4 } })
|
|
386
|
+
world.fx('ground-crack', { at: { x, z }, params: { size: 2.2 * size } })
|
|
387
|
+
world.fx('shockwave', { at: { x, z }, params: { size: 3 * size } })
|
|
388
|
+
blast(world, { x, z }, { radius: 9.5 * size, damage: 2500, how: HOW.blasted, crater: 1.6 * size })
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
/** How fast the shockwave goes out, in cells a second, and how far */
|
|
392
|
+
const WAVE = { speed: 8, reach: 20 }
|
|
393
|
+
|
|
394
|
+
/** A ring that goes out from the hero, fells what it passes */
|
|
395
|
+
const waveTimer = defineTimer<{ x: number; z: number; step: number; hero: number }>({
|
|
396
|
+
id: 'destruction.wave',
|
|
397
|
+
run(world, { x, z, step, hero }) {
|
|
398
|
+
const reach = Math.min(WAVE.reach, (step + 1) * WAVE.speed * 0.1)
|
|
399
|
+
const inner = step * WAVE.speed * 0.1
|
|
400
|
+
const from = world.entities.get(hero)
|
|
401
|
+
const ring = world.query({ near: { x, z }, radius: reach, where: (each) => exactMath.hypot(each.pos.x - x, each.pos.z - z) >= inner - 0.5 })
|
|
402
|
+
for (const each of ring) if (each !== from && each.def.id !== D.blast) world.damage(each, 5000, from, { how: HOW.swept })
|
|
403
|
+
// the grass in the ring laid flat, away from the hero
|
|
404
|
+
const cells = []
|
|
405
|
+
for (let cz = Math.floor(z - reach); cz <= z + reach; cz++)
|
|
406
|
+
for (let cx = Math.floor(x - reach); cx <= x + reach; cx++) {
|
|
407
|
+
const d = exactMath.hypot(cx + 0.5 - x, cz + 0.5 - z)
|
|
408
|
+
if (d < reach && d >= inner - 1 && cx >= 0 && cz >= 0 && cx < world.terrain.width && cz < world.terrain.depth)
|
|
409
|
+
cells.push(cz * world.terrain.width + cx)
|
|
410
|
+
}
|
|
411
|
+
if (world.terrain.cells[GRASS]) world.setCells(foliageStateLayer(GRASS), cells, FOLIAGE_STATE.flattened)
|
|
412
|
+
if (reach < WAVE.reach) world.timers.after(0.1, waveTimer, { x, z, step: step + 1, hero })
|
|
413
|
+
}
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
/** The hero sends a shockwave out round it */
|
|
417
|
+
export const shockwave = defineCommand('destruction.shockwave', {}, (world) => {
|
|
418
|
+
const hero = world.query({ type: UNIT.knight, limit: 1 })[0]
|
|
419
|
+
if (!hero) return
|
|
420
|
+
const { x, z } = hero.pos
|
|
421
|
+
world.fx('shockwave', { at: { x, z }, params: { size: 6 } })
|
|
422
|
+
world.fx('impact-ring', { at: { x, z }, params: { size: 4 } })
|
|
423
|
+
world.fx('ground-crack', { at: { x, z }, params: { size: 1.5 } })
|
|
424
|
+
world.timers.after(0.05, waveTimer, { x, z, step: 0, hero: hero.id })
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
/** A keg that went off: the blast it makes a moment after, which sets off the kegs near it */
|
|
428
|
+
const kegBlast = defineTimer<{ x: number; z: number }>({
|
|
429
|
+
id: 'destruction.kegBlast',
|
|
430
|
+
run(world, at) {
|
|
431
|
+
world.fx('explosion', { at, params: { size: 1.3, shake: 0.5 } })
|
|
432
|
+
blast(world, at, { radius: 4.3, damage: 900, how: HOW.blasted, crater: 0.35 })
|
|
433
|
+
}
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
/** Lights the first keg: the rest go one after another */
|
|
437
|
+
export const fuse = defineCommand('destruction.fuse', {}, (world) => {
|
|
438
|
+
const first = world.query({ type: D.keg }).sort((a, b) => b.pos.x - a.pos.x)[0]
|
|
439
|
+
if (first) world.damage(first, 1000, undefined, { how: HOW.blasted })
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
/** Everything in the meadow smashed at once: it grows back by itself (`regrowing`) */
|
|
443
|
+
export const smash = defineCommand('destruction.smash', {}, (world) => {
|
|
444
|
+
world.fx('shockwave', { at: { x: 24, z: 26 }, params: { size: 5 } })
|
|
445
|
+
blast(world, { x: 24, z: 26 }, { radius: 16, damage: 5000, how: HOW.shatter })
|
|
446
|
+
world.timers.after(6, grassBack, { x: 24, z: 26, radius: 16 })
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
/** Burnt grass grows back: whole again, eased in over a few seconds on the screens (`setCells`' `over`) */
|
|
450
|
+
const grassBack = defineTimer<{ x: number; z: number; radius: number }>({
|
|
451
|
+
id: 'destruction.grassBack',
|
|
452
|
+
run(world, { x, z, radius }) {
|
|
453
|
+
world.setCells(foliageStateLayer(GRASS), { kind: 'circle', x, z, radius }, FOLIAGE_STATE.whole, { over: 4 })
|
|
454
|
+
}
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
/** A tree, a rock that fell grows back where it stood (`regrowing`): put down as a site that builds itself */
|
|
458
|
+
const regrow = defineTimer<{ type: string; x: number; z: number }>({
|
|
459
|
+
id: 'destruction.regrow',
|
|
460
|
+
run(world, { type, x, z }) {
|
|
461
|
+
try {
|
|
462
|
+
world.spawn(type, { at: { x, z }, owner: MAP_NEUTRAL, underConstruction: true })
|
|
463
|
+
} catch {
|
|
464
|
+
// something stands there now: it does not come back
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
/** A catapult told to throw at what of the fort is nearest it */
|
|
470
|
+
function aim(world: World, catapult: Entity): void {
|
|
471
|
+
const target = world.query({ near: catapult, kinds: ['building'], owner: ENEMY, sort: 'nearest', limit: 1 })[0]
|
|
472
|
+
if (target) catapult.order('attack', target)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/** The catapults told to throw at the fort: each at what of it is nearest, and at the next as that falls */
|
|
476
|
+
export const bombard = defineCommand('destruction.bombard', {}, (world) => {
|
|
477
|
+
for (const catapult of world.query({ type: D.catapult })) aim(world, catapult)
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
/** Takes everything away and lays the example out again, as it started */
|
|
481
|
+
export const relay = defineCommand('destruction.relay', { example: text }, (world, _player, { example }) => {
|
|
482
|
+
const layout = LAYOUTS[example]
|
|
483
|
+
if (!layout) return
|
|
484
|
+
clear(world)
|
|
485
|
+
// the ground and the grass as they were: flat, grass whole again
|
|
486
|
+
const { terrain } = world
|
|
487
|
+
const patch = terrainPatch(terrain, { x: 0, z: 0, width: terrain.width, depth: terrain.depth })
|
|
488
|
+
const grassTile = world.registry.tile('grass')
|
|
489
|
+
// as high as a new map's ground stands, which the field is
|
|
490
|
+
patch.heights.fill(mapTerrain(createMap({ name: 'ground', width: 1, depth: 1, players: 1, tileset: world.registry.tileIds })).heights[0])
|
|
491
|
+
patch.tiles.fill(grassTile)
|
|
492
|
+
world.reshapeGround(patch)
|
|
493
|
+
if (terrain.cells[GRASS]) {
|
|
494
|
+
world.setCells(GRASS, { kind: 'rect', x: 0, z: 0, width: terrain.width, depth: terrain.depth }, 0)
|
|
495
|
+
world.setCells(foliageStateLayer(GRASS), { kind: 'rect', x: 0, z: 0, width: terrain.width, depth: terrain.depth }, FOLIAGE_STATE.whole)
|
|
496
|
+
}
|
|
497
|
+
layout(world)
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
const example = (id: string, title: string, summary: string, tags: string[], own: Partial<ExampleDef> = {}): ExampleDef => ({
|
|
501
|
+
id,
|
|
502
|
+
category: 'destruction',
|
|
503
|
+
title,
|
|
504
|
+
summary,
|
|
505
|
+
tags,
|
|
506
|
+
map: () => exampleField({ name: title, players: 2 }),
|
|
507
|
+
start: (world) => LAYOUTS[id]?.(world),
|
|
508
|
+
source: ['examples/destruction/index.ts', 'examples/destruction/looks.ts'],
|
|
509
|
+
...own
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
export const category: ExampleCategory = {
|
|
513
|
+
rules: (game: Game<any>) => {
|
|
514
|
+
for (const type of types) game.unit(type)
|
|
515
|
+
game.component(regrowing)
|
|
516
|
+
game.cellLayer(
|
|
517
|
+
defineCellLayer({ id: GRASS, name: 'Grass (destruction)', values: [{ value: 1, name: 'Meadow', foliage: presetFoliageStyles.meadow }] })
|
|
518
|
+
)
|
|
519
|
+
for (const command of [raze, hurt, meteor, shockwave, fuse, smash, bombard, relay]) game.command(command)
|
|
520
|
+
for (const timer of [waveTimer, kegBlast, regrow, grassBack]) game.timer(timer)
|
|
521
|
+
game.on(entityDied, (world, { entity, killer, reason }) => {
|
|
522
|
+
// a catapult that brought something down throws at the next
|
|
523
|
+
if (killer?.def.id === D.catapult && killer.alive) aim(world, killer)
|
|
524
|
+
// a keg that is hit goes off a moment later: that is what sets the next one off
|
|
525
|
+
if (entity.def.id === D.keg && reason !== 'removed') world.timers.after(0.18, kegBlast, { x: entity.pos.x, z: entity.pos.z })
|
|
526
|
+
// in the regrowing example, scenery that fell comes back
|
|
527
|
+
const regrows = world.state(regrowing)
|
|
528
|
+
if (regrows && reason !== 'removed' && (entity.def.id === D.tree || entity.def.id === D.rock))
|
|
529
|
+
world.timers.after(regrows.after + hash(entity.id) * 3, regrow, { type: entity.def.id, x: entity.pos.x, z: entity.pos.z })
|
|
530
|
+
})
|
|
531
|
+
},
|
|
532
|
+
examples: [
|
|
533
|
+
example(
|
|
534
|
+
'destruction-collapse',
|
|
535
|
+
'Six ways to come down',
|
|
536
|
+
'A row of buildings, and a button for each way one breaks: crumbling into pieces of itself, blown apart, toppled, sunk, shattered, thrown down as planks. Hurt them first to see smoke and fire.',
|
|
537
|
+
['building', 'collapse', 'crumble', 'explode', 'topple', 'shatter', 'debris', 'physics', 'damaged']
|
|
538
|
+
),
|
|
539
|
+
example(
|
|
540
|
+
'destruction-meteor',
|
|
541
|
+
'Meteor on a village',
|
|
542
|
+
'Call a meteor down on a village: it digs a crater, burns the grass, and blows the houses, fences, trees, rocks and people round it apart, every piece flung away from the blast.',
|
|
543
|
+
['meteor', 'blast', 'crater', 'explosion', 'trees', 'rocks', 'grass', 'physics', 'shatter']
|
|
544
|
+
),
|
|
545
|
+
example(
|
|
546
|
+
'destruction-shockwave',
|
|
547
|
+
'Shockwave through a forest',
|
|
548
|
+
'The knight sends a shockwave out: a ring that goes out through the wood, breaking every tree, rock and fence it passes and laying the grass flat, all of it flung away from him.',
|
|
549
|
+
['shockwave', 'trees', 'forest', 'rocks', 'grass', 'ring', 'physics']
|
|
550
|
+
),
|
|
551
|
+
example(
|
|
552
|
+
'destruction-chain',
|
|
553
|
+
'Chain reaction',
|
|
554
|
+
'Kegs of powder wind through a fort. Light the first: each keg that goes off sets off the next, and the walls, towers and houses beside them come down with it.',
|
|
555
|
+
['chain', 'explosion', 'kegs', 'barrels', 'fort', 'walls', 'timers']
|
|
556
|
+
),
|
|
557
|
+
example(
|
|
558
|
+
'destruction-regrow',
|
|
559
|
+
'Smashed, then grown back',
|
|
560
|
+
'Smash a grove, its rocks and its grass: a few seconds later each tree and rock grows back where it stood, and the grass after it.',
|
|
561
|
+
['regrow', 'respawn', 'trees', 'rocks', 'grass', 'construction']
|
|
562
|
+
),
|
|
563
|
+
example(
|
|
564
|
+
'destruction-siege',
|
|
565
|
+
'Catapults on a fort',
|
|
566
|
+
'Four catapults throw boulders at a stone fort: walls smoke and burn as they are hurt and crumble when they fall, and the trees round the fort are knocked down where boulders land.',
|
|
567
|
+
['siege', 'catapult', 'splash', 'walls', 'damaged', 'fells', 'trees']
|
|
568
|
+
)
|
|
569
|
+
]
|
|
570
|
+
}
|