@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,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The nature examples' grass and trees, to the rules: a layer of cells of their own (`natureGrass`) whose values are
|
|
3
|
+
* styles of grass (`FoliageStyleDef`: what hides, burns, is crushed and grows back), and a tree that burns. How they
|
|
4
|
+
* look is `./looks.ts`. A layer no map paints costs nothing, so the other categories' examples never pay for it
|
|
5
|
+
*/
|
|
6
|
+
import { type CellLayerDef, defineCellLayer, type FoliageStyleDef, presetFoliageStyles, type UnitTypeDef } from '@v12-engine/engine'
|
|
7
|
+
|
|
8
|
+
/** The layer of grass: its id, which the look (`ground.cellLayers`) and the examples' maps name */
|
|
9
|
+
export const GRASS = 'natureGrass'
|
|
10
|
+
|
|
11
|
+
/** The styles of grass, by their value in the layer */
|
|
12
|
+
export const STYLE = { meadow: 1, tallGrass: 2, dryGrass: 3, wheat: 4, thicket: 5, reeds: 6, snowGrass: 7, flowers: 8 } as const
|
|
13
|
+
|
|
14
|
+
/** A style without its `speed`: a style that slows gives every map of the game the ground's speeds to weigh */
|
|
15
|
+
const noSlowing = ({ speed: _slows, ...rest }: FoliageStyleDef): FoliageStyleDef => rest
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The layer: a short meadow, tall grass that hides who stands in it, dry grass that burns fast, wheat crushed
|
|
19
|
+
* underfoot, a thicket of bushes, reeds by the water, grass of the snow, and a meadow in flower. Each grows back a
|
|
20
|
+
* while after it burns or is crushed
|
|
21
|
+
*/
|
|
22
|
+
export const grassLayer: CellLayerDef = defineCellLayer({
|
|
23
|
+
id: GRASS,
|
|
24
|
+
name: 'Nature grass',
|
|
25
|
+
description: "The nature examples' grass and bushes: they sway, part, hide, burn and grow back",
|
|
26
|
+
color: [0.4, 0.75, 0.3],
|
|
27
|
+
values: [
|
|
28
|
+
{ value: STYLE.meadow, name: 'Meadow', color: [0.45, 0.72, 0.28], foliage: { ...presetFoliageStyles.meadow, regrows: 30, grows: 12 } },
|
|
29
|
+
{ value: STYLE.tallGrass, name: 'Tall grass', color: [0.3, 0.55, 0.2], foliage: { ...presetFoliageStyles.tallGrass, regrows: 30, grows: 15 } },
|
|
30
|
+
// catches from anything and burns out in three seconds, hot: what a fire runs through
|
|
31
|
+
{
|
|
32
|
+
value: STYLE.dryGrass,
|
|
33
|
+
name: 'Dry grass',
|
|
34
|
+
color: [0.8, 0.7, 0.4],
|
|
35
|
+
foliage: { ...presetFoliageStyles.dryGrass, burns: 3, fuel: 2, regrows: 40 }
|
|
36
|
+
},
|
|
37
|
+
{ value: STYLE.wheat, name: 'Wheat', color: [0.9, 0.8, 0.42], foliage: { ...presetFoliageStyles.wheat, regrows: 20, grows: 10 } },
|
|
38
|
+
{ value: STYLE.thicket, name: 'Thicket', color: [0.15, 0.35, 0.12], foliage: noSlowing(presetFoliageStyles.thicket) },
|
|
39
|
+
{ value: STYLE.reeds, name: 'Reeds', color: [0.4, 0.55, 0.3], foliage: noSlowing(presetFoliageStyles.reeds) },
|
|
40
|
+
{ value: STYLE.snowGrass, name: 'Snow grass', color: [0.8, 0.85, 0.85], foliage: presetFoliageStyles.snowGrass },
|
|
41
|
+
{ value: STYLE.flowers, name: 'Flowers', color: [0.8, 0.5, 0.7], foliage: { ...presetFoliageStyles.meadow, regrows: 30, grows: 12 } }
|
|
42
|
+
]
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
/** The nature examples' own unit types: a tree and a dead tree */
|
|
46
|
+
export const TREE = 'natureTree'
|
|
47
|
+
export const DEAD_TREE = 'natureDeadTree'
|
|
48
|
+
|
|
49
|
+
/** A tree: scenery that stands in the way, burns for 20 seconds when set alight, and comes down burnt */
|
|
50
|
+
export const tree: UnitTypeDef = {
|
|
51
|
+
id: TREE,
|
|
52
|
+
name: 'Tree',
|
|
53
|
+
kind: 'resource',
|
|
54
|
+
radius: 0.5,
|
|
55
|
+
footprint: { w: 1, d: 1 },
|
|
56
|
+
stats: { health: 1 },
|
|
57
|
+
sight: 0,
|
|
58
|
+
invulnerable: true,
|
|
59
|
+
fellable: true,
|
|
60
|
+
burns: { seconds: 20 }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** A dead tree: grey and bare, it catches as easily and burns quicker */
|
|
64
|
+
export const deadTree: UnitTypeDef = { ...tree, id: DEAD_TREE, name: 'Dead tree', burns: { seconds: 8 } }
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The nature examples on the rules' side: their list entries, maps and starts, and what they add to the game
|
|
3
|
+
* (`ExampleCategory`): a layer of grass (`./grass.ts`) with the foliage pack (it hides, burns, is crushed and grows
|
|
4
|
+
* back), trees that burn (the burning pack), and commands that change the wind and the weather for every player and
|
|
5
|
+
* light, put out and grow back a fire. The maps are made in code (`./maps.ts`)
|
|
6
|
+
*/
|
|
7
|
+
import {
|
|
8
|
+
burningOf,
|
|
9
|
+
burningPack,
|
|
10
|
+
defineCommand,
|
|
11
|
+
defineComponent,
|
|
12
|
+
type Entity,
|
|
13
|
+
foliagePack,
|
|
14
|
+
type Game,
|
|
15
|
+
gust,
|
|
16
|
+
ignite,
|
|
17
|
+
isBurning,
|
|
18
|
+
number,
|
|
19
|
+
oneOf,
|
|
20
|
+
putOut,
|
|
21
|
+
regrow,
|
|
22
|
+
setAlight,
|
|
23
|
+
stopFire,
|
|
24
|
+
type World,
|
|
25
|
+
exactMath
|
|
26
|
+
} from '@v12-engine/engine'
|
|
27
|
+
import { PLAYER, UNIT } from '../common/units'
|
|
28
|
+
import type { ExampleCategory } from '../define'
|
|
29
|
+
import { deadTree, GRASS, grassLayer, tree, TREE, DEAD_TREE } from './grass'
|
|
30
|
+
import { type At, plant } from './land'
|
|
31
|
+
import {
|
|
32
|
+
FIRE_START,
|
|
33
|
+
fireMap,
|
|
34
|
+
fireTrees,
|
|
35
|
+
forestMap,
|
|
36
|
+
forestTrees,
|
|
37
|
+
GLADE,
|
|
38
|
+
hillsMap,
|
|
39
|
+
hillsTrees,
|
|
40
|
+
lakeMap,
|
|
41
|
+
lakeTrees,
|
|
42
|
+
MEADOW_EAST,
|
|
43
|
+
MEADOW_WEST,
|
|
44
|
+
meadowMap,
|
|
45
|
+
meadowTrees,
|
|
46
|
+
valleyAt,
|
|
47
|
+
valleyMap,
|
|
48
|
+
valleyTrees,
|
|
49
|
+
SQUARE,
|
|
50
|
+
villageMap,
|
|
51
|
+
villageTrees
|
|
52
|
+
} from './maps'
|
|
53
|
+
import { WEATHER, type WeatherKind } from './weather'
|
|
54
|
+
|
|
55
|
+
/** Whether a thing is one of the nature examples' trees */
|
|
56
|
+
const isTree = (entity: Entity) => entity.alive && (entity.def.id === TREE || entity.def.id === DEAD_TREE)
|
|
57
|
+
|
|
58
|
+
/** Every tree standing in the world */
|
|
59
|
+
const treesOf = (world: World): Entity[] => [...world.units.values()].filter(isTree)
|
|
60
|
+
|
|
61
|
+
// ---- commands: what a control of the card asks of the rules, for every player to see
|
|
62
|
+
|
|
63
|
+
/** The wind for everyone: which way it blows (degrees from north, clockwise) and how hard (0 to 1), eased in over 2 s */
|
|
64
|
+
export const setWind = defineCommand('nature.wind', { around: number, strength: number }, (world, _player, { around, strength }) => {
|
|
65
|
+
world.setEnvironment({ wind: { around, strength: Math.max(0, Math.min(1, strength)), gusts: 0.5 } }, { over: 2 })
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
/** A gust out from a point: the grass and the trees' grass round it bend away, then stand again */
|
|
69
|
+
export const blowGust = defineCommand('nature.gust', { x: number, z: number }, (world, _player, { x, z }) => {
|
|
70
|
+
gust(world, { x, z }, { radius: 9, strength: 1.6, lasts: 1.2 })
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
/** The weather for everyone, eased in over 4 s: clear, rain, a storm or snow, and how much falls */
|
|
74
|
+
export const setWeather = defineCommand(
|
|
75
|
+
'nature.weather',
|
|
76
|
+
{ kind: oneOf('clear', 'rain', 'storm', 'snow'), amount: number },
|
|
77
|
+
(world, _player, { kind, amount }) => {
|
|
78
|
+
world.setEnvironment(WEATHER[kind as WeatherKind](Math.max(0, Math.min(1, amount))), { over: 4 })
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
/** Sets the grass alight round a point, and the nearest tree to it */
|
|
83
|
+
export const lightFire = defineCommand('nature.ignite', { x: number, z: number }, (world, _player, { x, z }) => {
|
|
84
|
+
ignite(world, GRASS, { kind: 'circle', x, z, radius: 1.8 })
|
|
85
|
+
const nearest = treesOf(world).sort((a, b) => exactMath.hypot(a.pos.x - x, a.pos.z - z) - exactMath.hypot(b.pos.x - x, b.pos.z - z))[0]
|
|
86
|
+
if (nearest && exactMath.hypot(nearest.pos.x - x, nearest.pos.z - z) < 4) setAlight(world, nearest)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
/** Sets the wood alight: the trees nearest a point, a few of them */
|
|
90
|
+
export const lightWood = defineCommand('nature.lightWood', { x: number, z: number }, (world, _player, { x, z }) => {
|
|
91
|
+
const near = treesOf(world).sort((a, b) => exactMath.hypot(a.pos.x - x, a.pos.z - z) - exactMath.hypot(b.pos.x - x, b.pos.z - z))
|
|
92
|
+
for (const each of near.slice(0, 3)) setAlight(world, each)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
/** Puts every fire out: the grass stops burning where it is, the trees stand charred as far as they burnt */
|
|
96
|
+
export const douse = defineCommand('nature.douse', {}, (world) => {
|
|
97
|
+
stopFire(world, GRASS)
|
|
98
|
+
for (const each of treesOf(world)) if (isBurning(each)) putOut(world, each)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Grows it all back over 8 seconds: the grass burnt, cut or crushed rises again (`regrow`), and trees stand again
|
|
103
|
+
* where the map had them, the charred ones new. Each example that burns says where its trees stand (`replant`)
|
|
104
|
+
*/
|
|
105
|
+
export const growBack = defineCommand('nature.regrow', {}, (world) => {
|
|
106
|
+
stopFire(world, GRASS)
|
|
107
|
+
const { width, depth } = world.terrain
|
|
108
|
+
regrow(world, GRASS, { kind: 'rect', x: 0, z: 0, width, depth }, { over: 8 })
|
|
109
|
+
for (const each of treesOf(world)) {
|
|
110
|
+
if (isBurning(each)) putOut(world, each)
|
|
111
|
+
if ((burningOf(world, each)?.share ?? 0) > 0) world.kill(each, undefined, false, 'removed')
|
|
112
|
+
}
|
|
113
|
+
const standing = new Set(treesOf(world).map((each) => Math.floor(each.pos.z) * width + Math.floor(each.pos.x)))
|
|
114
|
+
const places = world.state(replant).places.filter((at) => !standing.has(Math.floor(at.z) * width + Math.floor(at.x)))
|
|
115
|
+
plant(world, places)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
/** Where the trees of a world that burns stood as it started: what `growBack` plants again. Kept in the world, so a save keeps it */
|
|
119
|
+
const replant = defineComponent<{ places: At[] }>({ id: 'nature.replant', default: () => ({ places: [] }) })
|
|
120
|
+
|
|
121
|
+
/** Sends the walkers of the meadow across it, to whichever side they are not on */
|
|
122
|
+
export const stroll = defineCommand('nature.stroll', {}, (world) => {
|
|
123
|
+
const walkers = [...world.units.values()].filter((each) => each.alive && each.owner === PLAYER)
|
|
124
|
+
const middle = walkers.reduce((sum, each) => sum + each.pos.x, 0) / Math.max(1, walkers.length)
|
|
125
|
+
const to = middle < (MEADOW_WEST + MEADOW_EAST) / 2 ? MEADOW_EAST : MEADOW_WEST
|
|
126
|
+
for (const each of walkers) each.order('move', { x: to + (each.pos.x % 1) * 2, z: each.pos.z })
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
/** Walkers in a column on the meadow's west side */
|
|
130
|
+
function walkers(world: World): void {
|
|
131
|
+
const types = [UNIT.knight, UNIT.footman, UNIT.footman, UNIT.archer, UNIT.mage, UNIT.footman, UNIT.archer]
|
|
132
|
+
types.forEach((type, i) => world.spawn(type, { at: { x: MEADOW_WEST + (i % 2) * 1.2, z: 17 + i * 3.4 }, owner: PLAYER }))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export const category: ExampleCategory = {
|
|
136
|
+
rules: (game: Game<any>) => {
|
|
137
|
+
game.unit(tree)
|
|
138
|
+
game.unit(deadTree)
|
|
139
|
+
game.component(replant)
|
|
140
|
+
// the grass: hidden in, burning (passing it to the trees standing in it), crushed, growing back
|
|
141
|
+
game.cellLayer(grassLayer)
|
|
142
|
+
game.pack(foliagePack({ layer: GRASS, fire: { lights: true, wind: 1.6, jump: 0.5 } }))
|
|
143
|
+
// trees burn: a burning tree lights those beside it (likelier downwind) and the grass round it
|
|
144
|
+
game.pack(burningPack({ spread: { chance: 0.08, reach: 1.8 }, lightsFoliage: true }))
|
|
145
|
+
for (const command of [setWind, blowGust, setWeather, lightFire, lightWood, douse, growBack, stroll]) game.command(command)
|
|
146
|
+
},
|
|
147
|
+
examples: [
|
|
148
|
+
{
|
|
149
|
+
id: 'nature-forest',
|
|
150
|
+
category: 'nature',
|
|
151
|
+
title: 'A forest in the wind',
|
|
152
|
+
summary: "A forest of the shared art's trees round a glade in flower. Turn the wind round and up, and send a gust through the glade.",
|
|
153
|
+
tags: ['trees', 'wind', 'grass', 'forest', 'gust', 'scenery'],
|
|
154
|
+
map: forestMap,
|
|
155
|
+
start: (world) => plant(world, forestTrees(forestMap()), 0.04),
|
|
156
|
+
environment: {
|
|
157
|
+
sky: [0.5, 0.68, 0.9],
|
|
158
|
+
clouds: { cover: 0.35, speed: 0.6 },
|
|
159
|
+
light: { sunUp: 40, sunAround: 150, sun: 1.35, sunColor: [1, 0.93, 0.8], ambient: 0.55, ambientColor: [0.85, 0.92, 1] },
|
|
160
|
+
wind: { around: 70, strength: 0.45, gusts: 0.5 },
|
|
161
|
+
fog: { start: 55, end: 170 },
|
|
162
|
+
mist: { ground: { base: 0, top: 1.2, density: 0.1, color: [0.85, 0.9, 0.92] } }
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: 'nature-meadow',
|
|
167
|
+
category: 'nature',
|
|
168
|
+
title: 'A meadow of every grass',
|
|
169
|
+
summary:
|
|
170
|
+
'Wheat, tall grass, flowers, dry grass, a thicket and reeds by a pond. Send the walkers across: the grass parts round them and lies down in a trail behind.',
|
|
171
|
+
tags: ['grass', 'meadow', 'trails', 'wheat', 'reeds', 'flowers', 'foliage', 'trample'],
|
|
172
|
+
map: meadowMap,
|
|
173
|
+
start: (world) => {
|
|
174
|
+
plant(world, meadowTrees(meadowMap()), 0)
|
|
175
|
+
walkers(world)
|
|
176
|
+
},
|
|
177
|
+
environment: {
|
|
178
|
+
sky: [0.55, 0.72, 0.92],
|
|
179
|
+
clouds: { cover: 0.3 },
|
|
180
|
+
light: { sunUp: 48, sunAround: 200, sun: 1.4, sunColor: [1, 0.95, 0.84], ambient: 0.6 },
|
|
181
|
+
wind: { around: 100, strength: 0.3, gusts: 0.4 },
|
|
182
|
+
fog: { start: 60, end: 170 }
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: 'nature-lake',
|
|
187
|
+
category: 'nature',
|
|
188
|
+
title: 'A lake and a waterfall',
|
|
189
|
+
summary:
|
|
190
|
+
'A river spills off a cliff into a lake, which runs out east as another. Waves roll to the shore and the falls mist at their foot; change the water and its waves.',
|
|
191
|
+
tags: ['water', 'waterfall', 'river', 'lake', 'waves', 'shore'],
|
|
192
|
+
map: lakeMap,
|
|
193
|
+
start: (world) => plant(world, lakeTrees(lakeMap()), 0.03),
|
|
194
|
+
environment: {
|
|
195
|
+
sky: [0.52, 0.7, 0.9],
|
|
196
|
+
clouds: { cover: 0.4 },
|
|
197
|
+
light: { sunUp: 34, sunAround: 120, sun: 1.3, sunColor: [1, 0.92, 0.8], ambient: 0.55 },
|
|
198
|
+
wind: { around: 160, strength: 0.3, gusts: 0.3 },
|
|
199
|
+
water: { shallow: [0.3, 0.62, 0.6], deep: [0.05, 0.2, 0.3], waves: { strength: 0.7 }, falls: { mist: 0.8 } },
|
|
200
|
+
fog: { start: 60, end: 180 }
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
id: 'nature-clouds',
|
|
205
|
+
category: 'nature',
|
|
206
|
+
title: 'Clouds and their shadows',
|
|
207
|
+
summary:
|
|
208
|
+
'Clouds drift over rolling hills, their shadows running over the woods. Change how much of the sky they cover, how high they float and how dark they shade.',
|
|
209
|
+
tags: ['clouds', 'shadows', 'sky', 'cloud layer', 'volumetric'],
|
|
210
|
+
map: hillsMap,
|
|
211
|
+
start: (world) => plant(world, hillsTrees(hillsMap())),
|
|
212
|
+
environment: {
|
|
213
|
+
sky: [0.48, 0.66, 0.9],
|
|
214
|
+
clouds: { cover: 0.45 },
|
|
215
|
+
cloudLayer: { height: 20, scale: 28, cover: 0.5, opacity: 0.65, shadows: 0.65 },
|
|
216
|
+
light: { sunUp: 52, sunAround: 160, sun: 1.45, ambient: 0.5 },
|
|
217
|
+
wind: { around: 110, strength: 0.5, gusts: 0.3 },
|
|
218
|
+
fog: { start: 70, end: 220 }
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: 'nature-mist',
|
|
223
|
+
category: 'nature',
|
|
224
|
+
title: 'Mist in a valley',
|
|
225
|
+
summary:
|
|
226
|
+
'Dawn in a deep valley: mist lies along its floor, the hills standing out of it, and drifts in wisps by the stream. Raise it, thicken it, colour it.',
|
|
227
|
+
tags: ['mist', 'fog', 'valley', 'dawn', 'atmosphere'],
|
|
228
|
+
map: valleyMap,
|
|
229
|
+
start: (world) => plant(world, valleyTrees(valleyMap()), 0.08),
|
|
230
|
+
environment: {
|
|
231
|
+
sky: [0.86, 0.72, 0.66],
|
|
232
|
+
clouds: { cover: 0.5, color: [1, 0.86, 0.8] },
|
|
233
|
+
light: { sunUp: 10, sunAround: 75, sun: 1.1, sunColor: [1, 0.72, 0.5], ambient: 0.5, ambientColor: [0.8, 0.78, 0.95] },
|
|
234
|
+
wind: { around: 20, strength: 0.15, gusts: 0.2 },
|
|
235
|
+
fog: { start: 40, end: 150, color: [0.85, 0.76, 0.74] },
|
|
236
|
+
mist: {
|
|
237
|
+
ground: { base: 0, top: 3.2, density: 0.55, color: [0.9, 0.84, 0.84] },
|
|
238
|
+
areas: [
|
|
239
|
+
{
|
|
240
|
+
id: 1,
|
|
241
|
+
name: 'by the stream',
|
|
242
|
+
shape: { kind: 'circle', ...valleyAt(0.45), radius: 12 },
|
|
243
|
+
base: 0,
|
|
244
|
+
top: 4.5,
|
|
245
|
+
density: 0.6,
|
|
246
|
+
softness: 4,
|
|
247
|
+
drift: { x: 0.4, z: 0.6 }
|
|
248
|
+
}
|
|
249
|
+
]
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
id: 'nature-weather',
|
|
255
|
+
category: 'nature',
|
|
256
|
+
title: 'Rain, storm and snow',
|
|
257
|
+
summary:
|
|
258
|
+
'A village under changing weather. Pick clear, rain, a storm or snow and how much falls: every player sees it come in over a few seconds, the wind with it.',
|
|
259
|
+
tags: ['weather', 'rain', 'snow', 'storm', 'wind', 'splashes'],
|
|
260
|
+
map: villageMap,
|
|
261
|
+
start: (world) => {
|
|
262
|
+
plant(world, villageTrees(villageMap()))
|
|
263
|
+
villagers(world)
|
|
264
|
+
},
|
|
265
|
+
environment: WEATHER.rain(0.6)
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
id: 'nature-time',
|
|
269
|
+
category: 'nature',
|
|
270
|
+
title: 'The time of day',
|
|
271
|
+
summary:
|
|
272
|
+
'A village from dawn to night: the sun goes round and down, the sky and mist turn, and the torches come up. Slide the hour, or let the day run.',
|
|
273
|
+
tags: ['time of day', 'day', 'night', 'dawn', 'dusk', 'lights', 'torches', 'sun'],
|
|
274
|
+
map: villageMap,
|
|
275
|
+
start: (world) => {
|
|
276
|
+
plant(world, villageTrees(villageMap()))
|
|
277
|
+
villagers(world)
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
id: 'nature-fire',
|
|
282
|
+
category: 'nature',
|
|
283
|
+
title: 'Fire, and growing back',
|
|
284
|
+
summary:
|
|
285
|
+
'A dry field by a wood, the wind blowing east. Light it and the fire runs downwind through the grass into the trees; put it out, let it rain, or grow it all back.',
|
|
286
|
+
tags: ['fire', 'burning', 'grass', 'trees', 'regrow', 'wind', 'restore'],
|
|
287
|
+
map: fireMap,
|
|
288
|
+
start: (world) => {
|
|
289
|
+
const places = fireTrees(fireMap())
|
|
290
|
+
world.setState(replant, { places })
|
|
291
|
+
plant(world, places, 0.1)
|
|
292
|
+
},
|
|
293
|
+
environment: {
|
|
294
|
+
sky: [0.62, 0.72, 0.86],
|
|
295
|
+
clouds: { cover: 0.25 },
|
|
296
|
+
light: { sunUp: 44, sunAround: 210, sun: 1.45, sunColor: [1, 0.92, 0.78], ambient: 0.55 },
|
|
297
|
+
wind: { around: 90, strength: 0.55, gusts: 0.4 },
|
|
298
|
+
fog: { start: 60, end: 180 }
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
id: 'nature-looks',
|
|
303
|
+
category: 'nature',
|
|
304
|
+
title: 'Looks of the world',
|
|
305
|
+
summary:
|
|
306
|
+
'One lake under many skies: a summer noon, golden hour, a blue night, a grey storm, a winter morning, an autumn haze. Pick one and watch the world cross-fade into it.',
|
|
307
|
+
tags: ['environment', 'presets', 'cross-fade', 'sky', 'light', 'mood', 'setEnvironment'],
|
|
308
|
+
map: lakeMap,
|
|
309
|
+
start: (world) => plant(world, lakeTrees(lakeMap()), 0.03)
|
|
310
|
+
}
|
|
311
|
+
]
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** A few villagers about the square: who the weather falls on */
|
|
315
|
+
function villagers(world: World): void {
|
|
316
|
+
const at: At[] = [
|
|
317
|
+
{ x: 31, z: 30 },
|
|
318
|
+
{ x: 33.5, z: 33 },
|
|
319
|
+
{ x: 29.5, z: 32.5 },
|
|
320
|
+
{ x: 35, z: 29 },
|
|
321
|
+
{ x: 34, z: 36.5 }
|
|
322
|
+
]
|
|
323
|
+
const types = [UNIT.footman, UNIT.archer, UNIT.mage, UNIT.footman, UNIT.knight]
|
|
324
|
+
at.forEach((place, i) => world.spawn(types[i], { at: place, owner: PLAYER }))
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export { FIRE_START, GLADE, SQUARE }
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Land made in code for the nature examples' maps: the ground shaped by a function of where, cliffs raised, water
|
|
3
|
+
* poured, tiles and grass painted, scenery put down, and trees planted as the world starts. The engine alone: a map
|
|
4
|
+
* is data the rules read (`GameMap`), and the same seed lays the same land on every machine
|
|
5
|
+
*/
|
|
6
|
+
import { exactMath } from '@v12-engine/engine'
|
|
7
|
+
import type { GameMap, MapDoodad, World } from '@v12-engine/engine'
|
|
8
|
+
import { exampleField } from '../define'
|
|
9
|
+
import { DEAD_TREE, GRASS, TREE } from './grass'
|
|
10
|
+
|
|
11
|
+
/** A point of the map, in cells */
|
|
12
|
+
export interface At {
|
|
13
|
+
x: number
|
|
14
|
+
z: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** A test of a cell (its middle) or a vertex: whether something goes there */
|
|
18
|
+
export type Where = (x: number, z: number) => boolean
|
|
19
|
+
|
|
20
|
+
/** Numbers from a seed, the same each time: what lays scenery out the same on every machine */
|
|
21
|
+
export function seeded(seed: number): () => number {
|
|
22
|
+
let s = seed >>> 0
|
|
23
|
+
return () => {
|
|
24
|
+
s = (s + 0x6d2b79f5) >>> 0
|
|
25
|
+
let t = s
|
|
26
|
+
t = Math.imul(t ^ (t >>> 15), t | 1)
|
|
27
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
|
|
28
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** A hash of a whole point, 0 to 1 */
|
|
33
|
+
const hash = (x: number, z: number, seed: number) => {
|
|
34
|
+
const n = exactMath.sin(x * 127.1 + z * 311.7 + seed * 74.7) * 43758.5453
|
|
35
|
+
return n - Math.floor(n)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Smooth noise, 0 to 1, about `scale` cells from one hump to the next: what makes edges and hills look grown, not drawn */
|
|
39
|
+
export function noise(x: number, z: number, scale: number, seed = 1): number {
|
|
40
|
+
const fx = x / scale
|
|
41
|
+
const fz = z / scale
|
|
42
|
+
const ix = Math.floor(fx)
|
|
43
|
+
const iz = Math.floor(fz)
|
|
44
|
+
const tx = fx - ix
|
|
45
|
+
const tz = fz - iz
|
|
46
|
+
const sx = tx * tx * (3 - 2 * tx)
|
|
47
|
+
const sz = tz * tz * (3 - 2 * tz)
|
|
48
|
+
const a = hash(ix, iz, seed)
|
|
49
|
+
const b = hash(ix + 1, iz, seed)
|
|
50
|
+
const c = hash(ix, iz + 1, seed)
|
|
51
|
+
const d = hash(ix + 1, iz + 1, seed)
|
|
52
|
+
return a + (b - a) * sx + (c - a) * sz + (a - b - c + d) * sx * sz
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** 0 below `from`, 1 above `to`, smooth between */
|
|
56
|
+
export const smooth = (from: number, to: number, value: number): number => {
|
|
57
|
+
const t = Math.min(1, Math.max(0, (value - from) / (to - from)))
|
|
58
|
+
return t * t * (3 - 2 * t)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** A flat field of `size` cells a side, one player in the middle, painted grass (`exampleField`) */
|
|
62
|
+
export const field = (name: string, size: number): GameMap => exampleField({ name, size })
|
|
63
|
+
|
|
64
|
+
/** The ground's height at every vertex, from a function of where it is (the field's is 1) */
|
|
65
|
+
export function sculpt(map: GameMap, height: (x: number, z: number) => number): void {
|
|
66
|
+
const row = map.width + 1
|
|
67
|
+
for (let z = 0; z <= map.depth; z++) for (let x = 0; x <= map.width; x++) map.ground[z * row + x] = height(x, z)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Every cell `where` says, set to a value: the cell's middle is asked */
|
|
71
|
+
function eachCell(map: GameMap, where: Where, set: (cell: number) => void): void {
|
|
72
|
+
for (let z = 0; z < map.depth; z++) for (let x = 0; x < map.width; x++) if (where(x + 0.5, z + 0.5)) set(z * map.width + x)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Paints a tile of the game's (`grass`, `darkGrass`, `dirt`, `road`, `sand`, `rock`, `snow`) where it says */
|
|
76
|
+
export function paint(map: GameMap, tile: string, where: Where): void {
|
|
77
|
+
const value = map.tileset.indexOf(tile) + 1
|
|
78
|
+
if (value > 0) eachCell(map, where, (cell) => (map.tiles[cell] = value))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Grows a style of the nature grass (`STYLE`) where it says, over what grew there */
|
|
82
|
+
export function grow(map: GameMap, style: number, where: Where): void {
|
|
83
|
+
const cells = (map.cells ??= {})
|
|
84
|
+
const layer = (cells[GRASS] ??= new Uint8Array(map.width * map.depth))
|
|
85
|
+
eachCell(map, where, (cell) => (layer[cell] = style))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Takes the grass away where it says: under water, on a road */
|
|
89
|
+
export function mow(map: GameMap, where: Where): void {
|
|
90
|
+
const layer = map.cells?.[GRASS]
|
|
91
|
+
if (layer) eachCell(map, where, (cell) => (layer[cell] = 0))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Water where it says: deep where `deep` says too (nothing on foot crosses it), shallow (waded through) elsewhere */
|
|
95
|
+
export function pour(map: GameMap, where: Where, deep: Where = () => false): void {
|
|
96
|
+
eachCell(map, where, (cell) => {
|
|
97
|
+
const x = (cell % map.width) + 0.5
|
|
98
|
+
const z = Math.floor(cell / map.width) + 0.5
|
|
99
|
+
map.water[cell] = deep(x, z) ? 2 : 1
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Raises the cells it says to a cliff level (each `map.cliffHeight` over the one below) */
|
|
104
|
+
export function raise(map: GameMap, level: number, where: Where): void {
|
|
105
|
+
eachCell(map, where, (cell) => (map.levels[cell] = level))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Whether a cell is dry and on the map's ground level: where a tree, a house or a walker may go */
|
|
109
|
+
export const dryAt = (map: GameMap, x: number, z: number): boolean => {
|
|
110
|
+
if (x < 0 || z < 0 || x >= map.width || z >= map.depth) return false
|
|
111
|
+
return map.water[Math.floor(z) * map.width + Math.floor(x)] === 0
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** A ring round a middle, `radius` out, with a wobble of `rough` cells so it looks grown */
|
|
115
|
+
export const blob =
|
|
116
|
+
(middle: At, radius: number, rough = 2, seed = 1): Where =>
|
|
117
|
+
(x, z) =>
|
|
118
|
+
exactMath.hypot(x - middle.x, z - middle.z) < radius + (noise(x, z, 6, seed) - 0.5) * 2 * rough
|
|
119
|
+
|
|
120
|
+
/** A band along a line of points, `width` across, wobbling by `rough` */
|
|
121
|
+
export function band(points: readonly At[], width: number, rough = 0.6, seed = 2): Where {
|
|
122
|
+
return (x, z) => distanceToLine(points, x, z) < width / 2 + (noise(x, z, 5, seed) - 0.5) * 2 * rough
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** How far a point is from a line of points, in cells */
|
|
126
|
+
export function distanceToLine(points: readonly At[], x: number, z: number): number {
|
|
127
|
+
let best = Infinity
|
|
128
|
+
for (let i = 0; i + 1 < points.length; i++) {
|
|
129
|
+
const a = points[i]
|
|
130
|
+
const b = points[i + 1]
|
|
131
|
+
const dx = b.x - a.x
|
|
132
|
+
const dz = b.z - a.z
|
|
133
|
+
const t = Math.max(0, Math.min(1, ((x - a.x) * dx + (z - a.z) * dz) / (dx * dx + dz * dz || 1)))
|
|
134
|
+
best = Math.min(best, exactMath.hypot(x - (a.x + dx * t), z - (a.z + dz * t)))
|
|
135
|
+
}
|
|
136
|
+
return best
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Places for trees in an area: as many as fit, `spacing` apart at least, the same for a seed */
|
|
140
|
+
export function scatter(map: GameMap, where: Where, spacing: number, seed: number, most = 2000): At[] {
|
|
141
|
+
const random = seeded(seed)
|
|
142
|
+
const out: At[] = []
|
|
143
|
+
const tries = Math.min(most * 12, map.width * map.depth * 4)
|
|
144
|
+
for (let i = 0; i < tries && out.length < most; i++) {
|
|
145
|
+
const x = 1 + random() * (map.width - 2)
|
|
146
|
+
const z = 1 + random() * (map.depth - 2)
|
|
147
|
+
if (!where(x, z) || !dryAt(map, x, z)) continue
|
|
148
|
+
if (out.some((other) => exactMath.hypot(other.x - x, other.z - z) < spacing)) continue
|
|
149
|
+
out.push({ x, z })
|
|
150
|
+
}
|
|
151
|
+
return out
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Plants trees at places, standing on cells' middles (a tree takes a cell): `dead` of them dead, at random */
|
|
155
|
+
export function plant(world: World, places: readonly At[], dead = 0, seed = 5): void {
|
|
156
|
+
const random = seeded(seed)
|
|
157
|
+
const taken = new Set<number>()
|
|
158
|
+
for (const { x, z } of places) {
|
|
159
|
+
const cell = Math.floor(z) * world.terrain.width + Math.floor(x)
|
|
160
|
+
if (taken.has(cell)) continue
|
|
161
|
+
taken.add(cell)
|
|
162
|
+
world.spawn(random() < dead ? DEAD_TREE : TREE, { at: { x: Math.floor(x) + 0.5, z: Math.floor(z) + 0.5 }, owner: 0 })
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Scenery put down on the map (`MapDoodad`): a house, a rock, a lamp. Drawn by the screen alone; the rules never see it */
|
|
167
|
+
export function place(map: GameMap, model: string, at: At, options: { yaw?: number; scale?: number; blocks?: boolean; name?: string } = {}): void {
|
|
168
|
+
const { yaw = 0, scale = 1, blocks = false, name } = options
|
|
169
|
+
const doodad: MapDoodad = { id: map.doodads.length + 1, model, x: at.x, z: at.z, yaw, scale: [scale, scale, scale] }
|
|
170
|
+
if (blocks) Object.assign(doodad, { blocks: 'walk', radius: scale * 1.2 })
|
|
171
|
+
if (name) doodad.name = name
|
|
172
|
+
map.doodads.push(doodad)
|
|
173
|
+
}
|