@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,603 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dynamic-map examples on the rules' side: the ground changed while the game runs. A part of it is taken
|
|
3
|
+
* (`terrainPatch`), its heights, levels, water and paint changed, and put back (`world.reshapeGround`): every screen
|
|
4
|
+
* draws it again, the fog sees by its new levels and the ways are cut again where it changed, so units walk round
|
|
5
|
+
* what was raised and over what was lowered. Grass is a layer of cells (`world.setCells`), trees are things put down
|
|
6
|
+
* and taken away, and a region of new land is made by the engine's map generator (`generateMap`) as the game goes
|
|
7
|
+
*/
|
|
8
|
+
import {
|
|
9
|
+
defineCellLayer,
|
|
10
|
+
defineCommand,
|
|
11
|
+
defineComponent,
|
|
12
|
+
defineTimer,
|
|
13
|
+
type Entity,
|
|
14
|
+
type Game,
|
|
15
|
+
generateMap,
|
|
16
|
+
MAP_NEUTRAL,
|
|
17
|
+
mapTerrain,
|
|
18
|
+
type MapGenerator,
|
|
19
|
+
number,
|
|
20
|
+
oneOf,
|
|
21
|
+
presetFoliageStyles,
|
|
22
|
+
scatterPass,
|
|
23
|
+
terrainPass,
|
|
24
|
+
type TerrainPatch,
|
|
25
|
+
terrainPatch,
|
|
26
|
+
text,
|
|
27
|
+
tilesPass,
|
|
28
|
+
type UnitTypeDef,
|
|
29
|
+
type Vec2,
|
|
30
|
+
WATER_DEEP,
|
|
31
|
+
WATER_NONE,
|
|
32
|
+
WATER_SHALLOW,
|
|
33
|
+
type World,
|
|
34
|
+
exactMath
|
|
35
|
+
} from '@v12-engine/engine'
|
|
36
|
+
import { PLAYER, UNIT } from '../common/units'
|
|
37
|
+
import { type ExampleCategory, type ExampleDef, exampleField } from '../define'
|
|
38
|
+
|
|
39
|
+
/** The dynamic-map examples' own: a tree to plant and clear, and the grass they grow */
|
|
40
|
+
export const TREE = 'dynamicMapTree'
|
|
41
|
+
export const GRASS = 'dynamicMapGrass'
|
|
42
|
+
|
|
43
|
+
/** How much higher a cliff's top is than its foot: what the maps of the engine use (`GameMap.cliffHeight`) */
|
|
44
|
+
const CLIFF = 3
|
|
45
|
+
|
|
46
|
+
const tree: UnitTypeDef = {
|
|
47
|
+
id: TREE,
|
|
48
|
+
name: 'Tree',
|
|
49
|
+
// a building of nobody's: put down while the game runs, it grows (`construction: 'self'`) and blocks the way
|
|
50
|
+
kind: 'building',
|
|
51
|
+
radius: 0.5,
|
|
52
|
+
footprint: { w: 1, d: 1 },
|
|
53
|
+
stats: { health: 60 },
|
|
54
|
+
sight: 0,
|
|
55
|
+
ignored: true,
|
|
56
|
+
fellable: true,
|
|
57
|
+
construction: 'self',
|
|
58
|
+
buildTime: 3
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* How high the ground of the field is as the map was made (the field is not at 0): what every height here is over,
|
|
63
|
+
* read as an example starts
|
|
64
|
+
*/
|
|
65
|
+
const groundBase = defineComponent<{ base: number }>({ id: 'dynamicMap.base', default: () => ({ base: 0 }) })
|
|
66
|
+
const baseOf = (world: World) => world.state(groundBase).base
|
|
67
|
+
|
|
68
|
+
/** The water's surface in the flooding example, over the field's ground, and where it is going */
|
|
69
|
+
const flood = defineComponent<{ level: number; to: number }>({ id: 'dynamicMap.flood', default: () => ({ level: -3, to: -3 }) })
|
|
70
|
+
|
|
71
|
+
// ---- the ground, a part at a time
|
|
72
|
+
|
|
73
|
+
/** The part of the ground a rectangle of cells covers, inside the map; none where it is all outside */
|
|
74
|
+
function rectOf(world: World, x0: number, z0: number, x1: number, z1: number) {
|
|
75
|
+
const { width, depth } = world.terrain
|
|
76
|
+
const x = Math.max(0, Math.floor(x0))
|
|
77
|
+
const z = Math.max(0, Math.floor(z0))
|
|
78
|
+
const w = Math.min(width, Math.ceil(x1)) - x
|
|
79
|
+
const d = Math.min(depth, Math.ceil(z1)) - z
|
|
80
|
+
return w > 0 && d > 0 ? { x, z, width: w, depth: d } : undefined
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Changes a part of the ground: `change` is given the patch (`TerrainPatch`) and writes into it, then it is put back
|
|
85
|
+
* with `world.reshapeGround`, which tells every screen and cuts the ways again there
|
|
86
|
+
*/
|
|
87
|
+
function reshape(world: World, x0: number, z0: number, x1: number, z1: number, change: (patch: TerrainPatch) => void): void {
|
|
88
|
+
const rect = rectOf(world, x0, z0, x1, z1)
|
|
89
|
+
if (!rect) return
|
|
90
|
+
const patch = terrainPatch(world.terrain, rect)
|
|
91
|
+
change(patch)
|
|
92
|
+
world.reshapeGround(patch)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Each vertex of a patch, with where it is on the map and its index in `heights` and `waterHeights` */
|
|
96
|
+
function eachVertex(patch: TerrainPatch, run: (x: number, z: number, i: number) => void): void {
|
|
97
|
+
for (let vz = 0; vz <= patch.depth; vz++) for (let vx = 0; vx <= patch.width; vx++) run(patch.x + vx, patch.z + vz, vz * (patch.width + 1) + vx)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Each cell of a patch, with where its middle is on the map and its index in the cells' layers */
|
|
101
|
+
function eachCell(patch: TerrainPatch, run: (x: number, z: number, i: number) => void): void {
|
|
102
|
+
for (let cz = 0; cz < patch.depth; cz++) for (let cx = 0; cx < patch.width; cx++) run(patch.x + cx + 0.5, patch.z + cz + 0.5, cz * patch.width + cx)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** How much of a brush reaches a point: 1 at its middle, easing to 0 at its edge */
|
|
106
|
+
const falloff = (d: number, radius: number) => {
|
|
107
|
+
if (d >= radius) return 0
|
|
108
|
+
const edge = exactMath.cos((d / radius) * (Math.PI / 2))
|
|
109
|
+
return edge * edge
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** What the sculpting brush does */
|
|
113
|
+
export const BRUSHES = ['Raise', 'Dig', 'Flatten', 'Plateau', 'Water'] as const
|
|
114
|
+
|
|
115
|
+
/** The ground under a brush changed: raised or dug a little more each stroke, flattened, a plateau with cliffs, a pool */
|
|
116
|
+
function sculptAt(world: World, at: Vec2, radius: number, brush: (typeof BRUSHES)[number]): void {
|
|
117
|
+
const base = baseOf(world)
|
|
118
|
+
const tile = { rock: world.registry.tile('rock'), dirt: world.registry.tile('dirt'), sand: world.registry.tile('sand') }
|
|
119
|
+
reshape(world, at.x - radius - 1, at.z - radius - 1, at.x + radius + 1, at.z + radius + 1, (patch) => {
|
|
120
|
+
if (brush === 'Plateau') {
|
|
121
|
+
// a level up: the cells inside are of level 1 and their corners a cliff's height up; the client draws the walls
|
|
122
|
+
eachCell(patch, (x, z, i) => {
|
|
123
|
+
if (exactMath.hypot(x - at.x, z - at.z) > radius) return
|
|
124
|
+
patch.levels[i] = Math.max(patch.levels[i], 1)
|
|
125
|
+
patch.tiles[i] = tile.rock
|
|
126
|
+
})
|
|
127
|
+
eachVertex(patch, (x, z, i) => {
|
|
128
|
+
if (exactMath.hypot(x - at.x, z - at.z) <= radius + 0.5) patch.heights[i] = Math.max(patch.heights[i], base + CLIFF)
|
|
129
|
+
})
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
if (brush === 'Water') {
|
|
133
|
+
// a hollow with water in it: deep in the middle, shallow at its edge, sand round it
|
|
134
|
+
eachVertex(patch, (x, z, i) => {
|
|
135
|
+
const d = exactMath.hypot(x - at.x, z - at.z)
|
|
136
|
+
patch.heights[i] = Math.min(patch.heights[i], base - 2.2 * falloff(d, radius + 0.5))
|
|
137
|
+
patch.waterHeights[i] = base - 0.35
|
|
138
|
+
})
|
|
139
|
+
eachCell(patch, (x, z, i) => {
|
|
140
|
+
const d = exactMath.hypot(x - at.x, z - at.z)
|
|
141
|
+
if (d < radius) patch.water[i] = d < radius * 0.6 ? WATER_DEEP : WATER_SHALLOW
|
|
142
|
+
if (d < radius + 1) patch.tiles[i] = tile.sand
|
|
143
|
+
})
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
eachVertex(patch, (x, z, i) => {
|
|
147
|
+
const f = falloff(exactMath.hypot(x - at.x, z - at.z), radius)
|
|
148
|
+
if (brush === 'Raise') patch.heights[i] += 1.4 * f
|
|
149
|
+
else if (brush === 'Dig') patch.heights[i] -= 1.4 * f
|
|
150
|
+
else patch.heights[i] = base + (patch.heights[i] - base) * (1 - f)
|
|
151
|
+
})
|
|
152
|
+
// a pit is earth, a hill's top rock
|
|
153
|
+
if (brush === 'Dig') eachCell(patch, (x, z, i) => falloff(exactMath.hypot(x - at.x, z - at.z), radius) > 0.5 && (patch.tiles[i] = tile.dirt))
|
|
154
|
+
if (brush === 'Flatten')
|
|
155
|
+
eachCell(patch, (x, z, i) => {
|
|
156
|
+
if (exactMath.hypot(x - at.x, z - at.z) >= radius * 0.8) return
|
|
157
|
+
patch.levels[i] = 0
|
|
158
|
+
patch.water[i] = WATER_NONE
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** The ground of the whole map as it was made: flat, grass, dry, of level 0 */
|
|
164
|
+
function flatten(world: World): void {
|
|
165
|
+
const grass = world.registry.tile('grass')
|
|
166
|
+
const base = baseOf(world)
|
|
167
|
+
reshape(world, 0, 0, world.terrain.width, world.terrain.depth, (patch) => {
|
|
168
|
+
patch.heights.fill(base)
|
|
169
|
+
patch.waterHeights.fill(base)
|
|
170
|
+
patch.levels.fill(0)
|
|
171
|
+
patch.water.fill(WATER_NONE)
|
|
172
|
+
patch.tiles.fill(grass)
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Footmen of the player's that walk back and forth between two points: what shows the ways changing */
|
|
177
|
+
function walkers(world: World, from: Vec2, to: Vec2, count = 6): void {
|
|
178
|
+
for (let i = 0; i < count; i++) {
|
|
179
|
+
const at = { x: from.x + (i % 3) * 1.6 - 1.6, z: from.z + Math.floor(i / 3) * 1.6 }
|
|
180
|
+
const unit = world.spawn(UNIT.footman, { at, owner: PLAYER })
|
|
181
|
+
unit.order('patrol', { x: to.x + (i % 3) * 1.6 - 1.6, z: to.z - Math.floor(i / 3) * 1.6 })
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Takes away everything an example put down, and lays the ground flat */
|
|
186
|
+
function clear(world: World): void {
|
|
187
|
+
for (const each of [...world.entities.values()]) if (each.alive) world.kill(each, undefined, false, 'removed')
|
|
188
|
+
flatten(world)
|
|
189
|
+
if (world.terrain.cells[GRASS]) world.setCells(GRASS, { kind: 'rect', x: 0, z: 0, width: world.terrain.width, depth: world.terrain.depth }, 0)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ---- the examples' layouts
|
|
193
|
+
|
|
194
|
+
/** A field with footmen walking across it, to sculpt under their feet */
|
|
195
|
+
function field(world: World): void {
|
|
196
|
+
walkers(world, { x: 24, z: 8 }, { x: 24, z: 40 })
|
|
197
|
+
sculptAt(world, { x: 15, z: 24 }, 5, 'Raise')
|
|
198
|
+
sculptAt(world, { x: 15, z: 24 }, 5, 'Raise')
|
|
199
|
+
sculptAt(world, { x: 34, z: 26 }, 4, 'Dig')
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** The river's valley: across the field, deep in the middle, shallow at its east end where it can be forded */
|
|
203
|
+
function valley(world: World): void {
|
|
204
|
+
const base = baseOf(world)
|
|
205
|
+
const sand = world.registry.tile('sand')
|
|
206
|
+
const dirt = world.registry.tile('dirt')
|
|
207
|
+
reshape(world, 0, 16, world.terrain.width, 33, (patch) => {
|
|
208
|
+
eachVertex(patch, (x, z, i) => {
|
|
209
|
+
const across = falloff(Math.abs(z - 24.5), 8)
|
|
210
|
+
// the floor rises towards the ford in the east
|
|
211
|
+
const floor = x > 36 ? -0.9 : x > 32 ? -0.9 - ((36 - x) / 4) * 1.6 : -2.5
|
|
212
|
+
patch.heights[i] = base + floor * Math.min(1, across * 1.6)
|
|
213
|
+
})
|
|
214
|
+
eachCell(patch, (_x, z, i) => {
|
|
215
|
+
const d = Math.abs(z - 24.5)
|
|
216
|
+
patch.tiles[i] = d < 4 ? dirt : d < 6.5 ? sand : patch.tiles[i]
|
|
217
|
+
})
|
|
218
|
+
})
|
|
219
|
+
walkers(world, { x: 16, z: 8 }, { x: 16, z: 40 })
|
|
220
|
+
world.setState(flood, { level: -3, to: -3 })
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** A field, footmen walking across it: where the cliff will rise */
|
|
224
|
+
function plain(world: World): void {
|
|
225
|
+
walkers(world, { x: 24, z: 9 }, { x: 24, z: 40 })
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** The west half grass, the east half sea: where new land is made */
|
|
229
|
+
function shore(world: World): void {
|
|
230
|
+
sea(world)
|
|
231
|
+
walkers(world, { x: 10, z: 10 }, { x: 10, z: 38 }, 3)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** The sea east of x 24 */
|
|
235
|
+
function sea(world: World): void {
|
|
236
|
+
const base = baseOf(world)
|
|
237
|
+
const sand = world.registry.tile('sand')
|
|
238
|
+
reshape(world, 22, 0, world.terrain.width, world.terrain.depth, (patch) => {
|
|
239
|
+
eachVertex(patch, (x, _z, i) => {
|
|
240
|
+
patch.heights[i] = base - (x <= 23 ? 0 : 2.4 * Math.min(1, (x - 23) / 3))
|
|
241
|
+
patch.waterHeights[i] = base - 0.4
|
|
242
|
+
})
|
|
243
|
+
eachCell(patch, (x, _z, i) => {
|
|
244
|
+
patch.levels[i] = 0
|
|
245
|
+
patch.water[i] = x > 25 ? WATER_DEEP : x > 23.5 ? WATER_SHALLOW : WATER_NONE
|
|
246
|
+
patch.tiles[i] = sand
|
|
247
|
+
})
|
|
248
|
+
})
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** An empty meadow to plant: footmen at its west edge, to walk the road once it is cleared */
|
|
252
|
+
function meadow(world: World): void {
|
|
253
|
+
for (let i = 0; i < 4; i++) world.spawn(UNIT.footman, { at: { x: 5, z: 22 + i * 1.4 }, owner: PLAYER })
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Each example's layout, by its id */
|
|
257
|
+
const LAYOUTS: Record<string, (world: World) => void> = {
|
|
258
|
+
'dynamic-map-sculpt': field,
|
|
259
|
+
'dynamic-map-water': valley,
|
|
260
|
+
'dynamic-map-cliffs': plain,
|
|
261
|
+
'dynamic-map-generate': shore,
|
|
262
|
+
'dynamic-map-forest': meadow,
|
|
263
|
+
'dynamic-map-paint': (world) => walkers(world, { x: 24, z: 10 }, { x: 24, z: 38 }, 3)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ---- commands, sent by the examples' buttons and clicks
|
|
267
|
+
|
|
268
|
+
/** The ground sculpted where the player clicked, or where the camera looks */
|
|
269
|
+
export const sculpt = defineCommand(
|
|
270
|
+
'dynamicMap.sculpt',
|
|
271
|
+
{ x: number, z: number, radius: number, brush: oneOf(...BRUSHES) },
|
|
272
|
+
(world, _player, { x, z, radius, brush }) => sculptAt(world, { x, z }, Math.min(12, Math.max(1, radius)), brush)
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
/** The tiles the paint brush has */
|
|
276
|
+
export const PAINTS = ['grass', 'darkGrass', 'dirt', 'road', 'sand', 'rock', 'snow'] as const
|
|
277
|
+
|
|
278
|
+
/** The ground painted in a circle with a tile */
|
|
279
|
+
export const paint = defineCommand(
|
|
280
|
+
'dynamicMap.paint',
|
|
281
|
+
{ x: number, z: number, radius: number, tile: oneOf(...PAINTS) },
|
|
282
|
+
(world, _player, { x, z, radius, tile }) => {
|
|
283
|
+
const index = world.registry.tile(tile)
|
|
284
|
+
reshape(world, x - radius, z - radius, x + radius + 1, z + radius + 1, (patch) =>
|
|
285
|
+
eachCell(patch, (cx, cz, i) => exactMath.hypot(cx - x, cz - z) <= radius && (patch.tiles[i] = index))
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
/** Snow spreading out from a point, a ring a step (`snowfall`) */
|
|
291
|
+
const snowfall = defineTimer<{ x: number; z: number; ring: number }>({
|
|
292
|
+
id: 'dynamicMap.snowfall',
|
|
293
|
+
run(world, { x, z, ring }) {
|
|
294
|
+
const snow = world.registry.tile('snow')
|
|
295
|
+
const radius = ring * 1.5
|
|
296
|
+
reshape(world, x - radius - 1, z - radius - 1, x + radius + 1, z + radius + 1, (patch) =>
|
|
297
|
+
eachCell(patch, (cx, cz, i) => {
|
|
298
|
+
// a ragged edge: a cell near it is snow by a hash of where it is
|
|
299
|
+
const d = exactMath.hypot(cx - x, cz - z) + exactMath.sin(cx * 1.7 + cz * 2.3) * 0.8
|
|
300
|
+
if (d <= radius) patch.tiles[i] = snow
|
|
301
|
+
})
|
|
302
|
+
)
|
|
303
|
+
if (radius < 15) world.timers.after(0.25, snowfall, { x, z, ring: ring + 1 })
|
|
304
|
+
}
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
export const spreadSnow = defineCommand('dynamicMap.snow', { x: number, z: number }, (world, _player, at) =>
|
|
308
|
+
world.timers.after(0.05, snowfall, { ...at, ring: 1 })
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
/** A road painted from the west edge to the east, winding a little */
|
|
312
|
+
export const road = defineCommand('dynamicMap.road', {}, (world) => {
|
|
313
|
+
const tile = world.registry.tile('road')
|
|
314
|
+
reshape(world, 0, 14, world.terrain.width, 34, (patch) =>
|
|
315
|
+
eachCell(patch, (x, z, i) => Math.abs(z - (24 + exactMath.sin(x / 6) * 4)) < 1.6 && (patch.tiles[i] = tile))
|
|
316
|
+
)
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
/** The water of the valley raised or lowered a step at a time to where it is going (`flood`) */
|
|
320
|
+
const tide = defineTimer({
|
|
321
|
+
id: 'dynamicMap.tide',
|
|
322
|
+
run(world) {
|
|
323
|
+
const state = world.state(flood)
|
|
324
|
+
const step = Math.sign(state.to - state.level) * Math.min(0.15, Math.abs(state.to - state.level))
|
|
325
|
+
const level = state.level + step
|
|
326
|
+
world.setState(flood, { ...state, level })
|
|
327
|
+
reshape(world, 0, 15, world.terrain.width, 34, (patch) => {
|
|
328
|
+
const rowOf = patch.width + 1
|
|
329
|
+
const surface = baseOf(world) + level
|
|
330
|
+
eachVertex(patch, (_x, _z, i) => (patch.waterHeights[i] = surface))
|
|
331
|
+
// a cell is under water where its lowest corner is under the surface: deep past most of a unit's height
|
|
332
|
+
eachCell(patch, (_x, _z, i) => {
|
|
333
|
+
const cz = Math.floor(i / patch.width)
|
|
334
|
+
const cx = i % patch.width
|
|
335
|
+
const at = cz * rowOf + cx
|
|
336
|
+
const lowest = Math.min(patch.heights[at], patch.heights[at + 1], patch.heights[at + rowOf], patch.heights[at + rowOf + 1])
|
|
337
|
+
const depth = surface - lowest
|
|
338
|
+
patch.water[i] = depth > 1 ? WATER_DEEP : depth > 0.05 ? WATER_SHALLOW : WATER_NONE
|
|
339
|
+
})
|
|
340
|
+
})
|
|
341
|
+
if (Math.abs(state.to - level) > 1e-3) world.timers.after(0.2, tide)
|
|
342
|
+
}
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
/** The valley flooded up to a level, or drained (a level under its floor) */
|
|
346
|
+
export const setFlood = defineCommand('dynamicMap.flood', { level: number }, (world, _player, { level }) => {
|
|
347
|
+
world.setState(flood, { ...world.state(flood), to: level })
|
|
348
|
+
world.timers.after(0.05, tide)
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
/** Where the cliff wall goes: across the field, from west to east */
|
|
352
|
+
const WALL = { z0: 22, z1: 26, x0: 3, x1: 45 }
|
|
353
|
+
|
|
354
|
+
/** A cliff wall raised across the field: a level up, a band of cells, with only its ends to go round */
|
|
355
|
+
export const raiseWall = defineCommand('dynamicMap.wall', {}, (world) => {
|
|
356
|
+
const rock = world.registry.tile('rock')
|
|
357
|
+
const base = baseOf(world)
|
|
358
|
+
reshape(world, WALL.x0, WALL.z0, WALL.x1, WALL.z1, (patch) => {
|
|
359
|
+
patch.levels.fill(1)
|
|
360
|
+
patch.tiles.fill(rock)
|
|
361
|
+
patch.heights.fill(base + CLIFF)
|
|
362
|
+
})
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
/** A ramp cut through the wall in its middle: up the south side, over, down the north. The way across is open */
|
|
366
|
+
export const cutRamp = defineCommand('dynamicMap.ramp', {}, (world) => {
|
|
367
|
+
const dirt = world.registry.tile('dirt')
|
|
368
|
+
const { z0, z1 } = WALL
|
|
369
|
+
const base = baseOf(world)
|
|
370
|
+
reshape(world, 21, z0 - 5, 28, z1 + 5, (patch) => {
|
|
371
|
+
eachVertex(patch, (x, z, i) => {
|
|
372
|
+
if (x < 22 || x > 27) return
|
|
373
|
+
// up over four cells from either side: no step a cell is a cliff's rise
|
|
374
|
+
const up = z < z0 ? (z - (z0 - 4)) / 4 : z > z1 ? (z1 + 4 - z) / 4 : 1
|
|
375
|
+
patch.heights[i] = Math.max(patch.heights[i], base + CLIFF * Math.max(0, Math.min(1, up)))
|
|
376
|
+
})
|
|
377
|
+
eachCell(patch, (x, z, i) => {
|
|
378
|
+
if (x < 22 || x > 27) return
|
|
379
|
+
if (z < z0 || z > z1) patch.levels[i] = 0
|
|
380
|
+
patch.tiles[i] = dirt
|
|
381
|
+
})
|
|
382
|
+
})
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
/** The wall and the ramp gone: the field flat again */
|
|
386
|
+
export const lower = defineCommand('dynamicMap.lower', {}, (world) => flatten(world))
|
|
387
|
+
|
|
388
|
+
/** Trees put down by the generator's scatter pass, as objects of the map, put in the world as it is laid */
|
|
389
|
+
const SCATTER = 'dynamicMap.trees'
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* The generator of the land that rises out of the sea: hills and levels with ramps (`terrainPass`), painted by height
|
|
393
|
+
* (`tilesPass`), trees in patches (`scatterPass`). The engine's passes; the trees a type of this example's
|
|
394
|
+
*/
|
|
395
|
+
const landMaker: MapGenerator = {
|
|
396
|
+
params: [],
|
|
397
|
+
tries: 1,
|
|
398
|
+
passes: [
|
|
399
|
+
terrainPass({ levels: 2, water: 0.08, roughness: 0.6, hills: 14 }),
|
|
400
|
+
tilesPass(),
|
|
401
|
+
scatterPass({ kind: SCATTER, density: 0.2, spacing: 1.6, avoid: 0, what: (ctx, at) => void ctx.addObject(TREE, MAP_NEUTRAL, at.x, at.z) })
|
|
402
|
+
]
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/** The new land's cells: the sea's part of the map, its edges eased down into it */
|
|
406
|
+
const LAND = { x: 24, z: 1, width: 23, depth: 46 }
|
|
407
|
+
|
|
408
|
+
/** New land made out of the sea with a seed: generated as a small map of its own, then laid into the world's ground */
|
|
409
|
+
export const raiseLand = defineCommand('dynamicMap.land', { seed: number }, (world, _player, { seed }) => {
|
|
410
|
+
// what stood on the old land goes with it
|
|
411
|
+
for (const each of world.query({ type: TREE })) if (each.pos.x > LAND.x) world.kill(each, undefined, false, 'removed')
|
|
412
|
+
const { map } = generateMap(landMaker, { seed, width: LAND.width, depth: LAND.depth, players: 1 }, world.registry)
|
|
413
|
+
const land = mapTerrain(map)
|
|
414
|
+
// its lowest dry ground laid at the field's height: the hills and plateaus over it, the ponds in it
|
|
415
|
+
const base = baseOf(world)
|
|
416
|
+
const landBase = lowestDry(land)
|
|
417
|
+
reshape(world, LAND.x, LAND.z, LAND.x + LAND.width, LAND.z + LAND.depth, (patch) => {
|
|
418
|
+
eachVertex(patch, (x, z, i) => {
|
|
419
|
+
const lx = x - LAND.x
|
|
420
|
+
const lz = z - LAND.z
|
|
421
|
+
// near its edge the land eases down into the sea round it
|
|
422
|
+
const edge = Math.min(lx, lz, LAND.width - lx, LAND.depth - lz)
|
|
423
|
+
const ease = Math.min(1, edge / 4)
|
|
424
|
+
const h = land.heights[lz * land.vertsX + lx]
|
|
425
|
+
patch.heights[i] = base - 2.4 + (h - landBase + 2.4) * ease
|
|
426
|
+
patch.waterHeights[i] = edge < 3 ? base - 0.4 : land.waterHeights[lz * land.vertsX + lx] - landBase + base
|
|
427
|
+
})
|
|
428
|
+
eachCell(patch, (x, z, i) => {
|
|
429
|
+
const lx = Math.floor(x - LAND.x)
|
|
430
|
+
const lz = Math.floor(z - LAND.z)
|
|
431
|
+
const c = lz * land.width + lx
|
|
432
|
+
const edge = Math.min(lx, lz, LAND.width - 1 - lx, LAND.depth - 1 - lz)
|
|
433
|
+
patch.levels[i] = edge < 4 ? 0 : land.levels[c]
|
|
434
|
+
patch.tiles[i] = land.tiles[c]
|
|
435
|
+
patch.water[i] = edge < 1 ? WATER_DEEP : edge < 2 ? WATER_SHALLOW : edge < 3 ? WATER_NONE : land.water[c]
|
|
436
|
+
})
|
|
437
|
+
})
|
|
438
|
+
// the generator's trees, where the land is dry and not too near its edge: growing up
|
|
439
|
+
for (const object of map.objects) {
|
|
440
|
+
if (object.type !== TREE) continue
|
|
441
|
+
const at = { x: LAND.x + object.x, z: LAND.z + object.z }
|
|
442
|
+
const edge = Math.min(object.x, object.z, LAND.width - object.x, LAND.depth - object.z)
|
|
443
|
+
if (edge < 4 || world.terrain.waterAt(at.x, at.z) !== WATER_NONE) continue
|
|
444
|
+
plant(world, at)
|
|
445
|
+
}
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
/** The lowest corner of a ground's dry cells of its lowest level: where the land meets the shore */
|
|
449
|
+
function lowestDry(land: ReturnType<typeof mapTerrain>): number {
|
|
450
|
+
let lowest = Infinity
|
|
451
|
+
for (let cz = 0; cz < land.depth; cz++)
|
|
452
|
+
for (let cx = 0; cx < land.width; cx++) {
|
|
453
|
+
const c = cz * land.width + cx
|
|
454
|
+
if (land.levels[c] !== 0 || land.water[c] !== WATER_NONE) continue
|
|
455
|
+
lowest = Math.min(
|
|
456
|
+
lowest,
|
|
457
|
+
land.vertexHeight(cx, cz),
|
|
458
|
+
land.vertexHeight(cx + 1, cz),
|
|
459
|
+
land.vertexHeight(cx, cz + 1),
|
|
460
|
+
land.vertexHeight(cx + 1, cz + 1)
|
|
461
|
+
)
|
|
462
|
+
}
|
|
463
|
+
return Number.isFinite(lowest) ? lowest : land.heights[0]
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/** The sea back where the land was */
|
|
467
|
+
export const sink = defineCommand('dynamicMap.sink', {}, (world) => {
|
|
468
|
+
for (const each of world.query({ type: TREE })) if (each.pos.x > LAND.x) world.kill(each, undefined, false, 'removed')
|
|
469
|
+
sea(world)
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
/** A tree put down as a sapling that grows: nothing where something stands already */
|
|
473
|
+
function plant(world: World, at: Vec2): Entity | undefined {
|
|
474
|
+
try {
|
|
475
|
+
return world.spawn(TREE, { at, owner: MAP_NEUTRAL, underConstruction: true })
|
|
476
|
+
} catch {
|
|
477
|
+
return undefined
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/** Where the wood is planted: a disc of trees on every other cell, scattered by where each is */
|
|
482
|
+
export const plantWood = defineCommand('dynamicMap.plant', {}, (world) => {
|
|
483
|
+
for (let z = 10; z < 40; z++)
|
|
484
|
+
for (let x = 10; x < 40; x++) {
|
|
485
|
+
if ((x + z) % 2 || exactMath.hypot(x + 0.5 - 25, z + 0.5 - 25) > 14) continue
|
|
486
|
+
if (Math.abs(exactMath.sin(x * 12.9898 + z * 78.233) * 43758.5453) % 1 > 0.55) continue
|
|
487
|
+
if (world.query({ near: { x: x + 0.5, z: z + 0.5 }, radius: 0.6, limit: 1 }).length) continue
|
|
488
|
+
plant(world, { x: x + 0.5, z: z + 0.5 })
|
|
489
|
+
}
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
/** Grass grown over the meadow, eased in on the screens over seconds (`setCells`' `over`) */
|
|
493
|
+
export const growGrass = defineCommand('dynamicMap.grass', {}, (world) => {
|
|
494
|
+
world.setCells(GRASS, { kind: 'circle', x: 25, z: 25, radius: 19 }, 1, { over: 5 })
|
|
495
|
+
})
|
|
496
|
+
|
|
497
|
+
/** The band of the road through the wood */
|
|
498
|
+
const inRoad = (x: number, z: number) => Math.abs(z - (25 + exactMath.sin(x / 5) * 2.5)) < 2
|
|
499
|
+
|
|
500
|
+
/** A road cleared through the wood: its trees felled, its grass gone, its ground painted road. The footmen walk it */
|
|
501
|
+
export const clearRoad = defineCommand('dynamicMap.clear', {}, (world) => {
|
|
502
|
+
for (const each of world.query({ type: TREE })) if (inRoad(each.pos.x, each.pos.z)) world.kill(each, undefined, false, 'felled')
|
|
503
|
+
const tile = world.registry.tile('road')
|
|
504
|
+
const cells: number[] = []
|
|
505
|
+
reshape(world, 0, 18, world.terrain.width, 32, (patch) =>
|
|
506
|
+
eachCell(patch, (x, z, i) => {
|
|
507
|
+
if (!inRoad(x, z)) return
|
|
508
|
+
patch.tiles[i] = tile
|
|
509
|
+
cells.push(Math.floor(z) * world.terrain.width + Math.floor(x))
|
|
510
|
+
})
|
|
511
|
+
)
|
|
512
|
+
if (world.terrain.cells[GRASS]) world.setCells(GRASS, cells, 0)
|
|
513
|
+
for (const each of world.query({ type: UNIT.footman, owner: PLAYER })) each.order('patrol', { x: 44, z: each.pos.z })
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
/** Takes everything away and lays the example out again */
|
|
517
|
+
export const relay = defineCommand('dynamicMap.relay', { example: text }, (world, _player, { example }) => {
|
|
518
|
+
const layout = LAYOUTS[example]
|
|
519
|
+
if (!layout) return
|
|
520
|
+
clear(world)
|
|
521
|
+
layout(world)
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
const example = (id: string, title: string, summary: string, tags: string[]): ExampleDef => ({
|
|
525
|
+
id,
|
|
526
|
+
category: 'dynamic-map',
|
|
527
|
+
title,
|
|
528
|
+
summary,
|
|
529
|
+
tags,
|
|
530
|
+
map: () => exampleField({ name: title }),
|
|
531
|
+
start: (world) => {
|
|
532
|
+
world.setState(groundBase, { base: world.terrain.heights[0] })
|
|
533
|
+
LAYOUTS[id]?.(world)
|
|
534
|
+
},
|
|
535
|
+
source: ['examples/dynamic-map/index.ts', 'examples/dynamic-map/looks.ts']
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
export const category: ExampleCategory = {
|
|
539
|
+
rules: (game: Game<any>) => {
|
|
540
|
+
game.unit(tree)
|
|
541
|
+
game.component(flood)
|
|
542
|
+
game.component(groundBase)
|
|
543
|
+
game.cellLayer(
|
|
544
|
+
defineCellLayer({ id: GRASS, name: 'Grass (dynamic map)', values: [{ value: 1, name: 'Meadow', foliage: presetFoliageStyles.meadow }] })
|
|
545
|
+
)
|
|
546
|
+
for (const command of [
|
|
547
|
+
sculpt,
|
|
548
|
+
paint,
|
|
549
|
+
spreadSnow,
|
|
550
|
+
road,
|
|
551
|
+
setFlood,
|
|
552
|
+
raiseWall,
|
|
553
|
+
cutRamp,
|
|
554
|
+
lower,
|
|
555
|
+
raiseLand,
|
|
556
|
+
sink,
|
|
557
|
+
plantWood,
|
|
558
|
+
growGrass,
|
|
559
|
+
clearRoad,
|
|
560
|
+
relay
|
|
561
|
+
])
|
|
562
|
+
game.command(command)
|
|
563
|
+
for (const timer of [snowfall, tide]) game.timer(timer)
|
|
564
|
+
},
|
|
565
|
+
examples: [
|
|
566
|
+
example(
|
|
567
|
+
'dynamic-map-sculpt',
|
|
568
|
+
'Raise and dig',
|
|
569
|
+
'Click the ground to raise it, dig it, flatten it, lift a plateau with cliffs or sink a pool. The footmen walking across go over the hills and round the cliffs and water, the ways cut again as it changes.',
|
|
570
|
+
['terrain', 'sculpt', 'height', 'raise', 'dig', 'cliff', 'plateau', 'navigation', 'reshapeGround']
|
|
571
|
+
),
|
|
572
|
+
example(
|
|
573
|
+
'dynamic-map-paint',
|
|
574
|
+
'Paint the ground',
|
|
575
|
+
'Click to paint the ground with grass, dirt, road, sand, rock or snow; lay a winding road across; let snow spread out from the middle ring by ring.',
|
|
576
|
+
['terrain', 'paint', 'tiles', 'texture', 'road', 'snow']
|
|
577
|
+
),
|
|
578
|
+
example(
|
|
579
|
+
'dynamic-map-water',
|
|
580
|
+
'Flood and drain a valley',
|
|
581
|
+
'Flood the valley and the water rises step by step: deep water cuts the way, so the footmen go round by the shallow ford in the east. Drain it and they walk straight across again.',
|
|
582
|
+
['water', 'flood', 'drain', 'river', 'ford', 'navigation']
|
|
583
|
+
),
|
|
584
|
+
example(
|
|
585
|
+
'dynamic-map-cliffs',
|
|
586
|
+
'A cliff, then a ramp',
|
|
587
|
+
'Raise a cliff wall across the field and the footmen walking across go round its ends; cut a ramp through its middle and they take it, up and over.',
|
|
588
|
+
['cliff', 'ramp', 'levels', 'high ground', 'navigation', 'reshapeGround']
|
|
589
|
+
),
|
|
590
|
+
example(
|
|
591
|
+
'dynamic-map-generate',
|
|
592
|
+
'Land out of the sea',
|
|
593
|
+
"Raise new land out of the sea with the engine's map generator: hills, cliffs, ramps, ponds and a wood, made from a seed while the game runs. Another seed, other land.",
|
|
594
|
+
['generate', 'procedural', 'generator', 'seed', 'terrain', 'trees']
|
|
595
|
+
),
|
|
596
|
+
example(
|
|
597
|
+
'dynamic-map-forest',
|
|
598
|
+
'Plant, grow, clear',
|
|
599
|
+
'Plant a wood and watch it grow, grow grass under it, then clear a road through it: the trees felled, the grass taken off, the ground paved, and the footmen walk the new way.',
|
|
600
|
+
['trees', 'plant', 'grass', 'grow', 'clear', 'road', 'forest']
|
|
601
|
+
)
|
|
602
|
+
]
|
|
603
|
+
}
|