@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,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Floating combat text, on the client's side. The view's `hud.floats` says what each kind of event floats: the
|
|
3
|
+
* ready-made ones (`presetFloats`), one changed (crits bigger and gold), and one of the game's own (`fxCombo`). The
|
|
4
|
+
* card puts words up in code with `client.floats.show(def, { on, amount })`: a kind's def, words alone, or a def made
|
|
5
|
+
* there, and a handle to count a number up as a channel ticks (`handle.update`)
|
|
6
|
+
*/
|
|
7
|
+
import { type FloatDef, type FloatSet, type GameClientApi, presetFloats } from '@v12-engine/client'
|
|
8
|
+
import { button, type ExampleLook, toggle } from '../../shell/look'
|
|
9
|
+
import { UNIT } from '../common/units'
|
|
10
|
+
import { BRUTE } from './feel'
|
|
11
|
+
import { fight } from './fight'
|
|
12
|
+
import { FLOATS_AT } from './floats'
|
|
13
|
+
import { aim, idsOf } from './stage'
|
|
14
|
+
|
|
15
|
+
/** Every ready-made kind, a critical strike bigger and golden, and a combo of the game's own that bounces */
|
|
16
|
+
const FLOATS: FloatSet = {
|
|
17
|
+
...presetFloats(),
|
|
18
|
+
crit: { ...presetFloats(['crit']).crit, style: { color: '#ffcf3a', size: 28, stroke: { color: '#5a1a00', width: 4 } } },
|
|
19
|
+
fxCombo: {
|
|
20
|
+
text: 'Combo ×{amount}',
|
|
21
|
+
style: { color: '#ff8aff', size: 20, background: { color: 'rgba(30, 0, 30, 0.75)', padding: 5, radius: 6 } },
|
|
22
|
+
motion: [{ bounce: { height: 0.7, times: 2, seconds: 0.8 } }, { pop: { from: 0.4, seconds: 0.25, ease: 'outBack' } }],
|
|
23
|
+
seconds: 1.6
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A kind's def as the view has it now */
|
|
28
|
+
const kind = (client: GameClientApi, name: string): FloatDef | undefined => {
|
|
29
|
+
const rule = client.floats.def(name)
|
|
30
|
+
return rule && typeof rule === 'object' ? rule : undefined
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Floats a kind over a unit, as the client floats it for an event of its kind */
|
|
34
|
+
function float(client: GameClientApi, name: string, on: 'brute' | 'dummy', amount?: number, params?: Record<string, unknown>): void {
|
|
35
|
+
const [id] = idsOf(client, on === 'brute' ? BRUTE : UNIT.dummy)
|
|
36
|
+
const def = kind(client, name)
|
|
37
|
+
if (id === undefined || !def) return
|
|
38
|
+
client.floats.show(def, { kind: name, on: id, ...(amount !== undefined && { amount }), ...(params && { params }) })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const random = (least: number, most: number) => Math.round(least + Math.random() * (most - least))
|
|
42
|
+
|
|
43
|
+
let combo = 0
|
|
44
|
+
|
|
45
|
+
export const floatsLook: ExampleLook = {
|
|
46
|
+
view: (view) => ({ ...view, hud: { ...view.hud, floats: FLOATS } }),
|
|
47
|
+
controls: [
|
|
48
|
+
button(
|
|
49
|
+
'Damage',
|
|
50
|
+
(client) => float(client, 'damage', 'dummy', random(20, 60)),
|
|
51
|
+
'A damage number over the dummy: press fast and hits within 0.8 s merge into one number that counts up'
|
|
52
|
+
),
|
|
53
|
+
button(
|
|
54
|
+
'Critical strike',
|
|
55
|
+
(client) => float(client, 'crit', 'dummy', random(150, 320)),
|
|
56
|
+
'A critical strike over the dummy: this game made it bigger and gold'
|
|
57
|
+
),
|
|
58
|
+
button('Miss', (client) => float(client, 'miss', 'dummy'), 'The word a dodged blow floats over the dummy'),
|
|
59
|
+
button('Heal', (client) => float(client, 'heal', 'brute', random(40, 120)), 'A green heal number over the brute'),
|
|
60
|
+
button(
|
|
61
|
+
'Heal over time',
|
|
62
|
+
(client) => {
|
|
63
|
+
// one number told more as the channel ticks, counted up, then let go
|
|
64
|
+
const [brute] = idsOf(client, BRUTE)
|
|
65
|
+
if (brute === undefined) return
|
|
66
|
+
const channel = client.floats.show({ ...kind(client, 'heal'), merge: { window: 2, countUp: 0.3 } }, { kind: 'heal', on: brute, amount: 15 })
|
|
67
|
+
let total = 15
|
|
68
|
+
let ticks = 0
|
|
69
|
+
const tick = window.setInterval(() => {
|
|
70
|
+
total += 15
|
|
71
|
+
channel?.update(total)
|
|
72
|
+
if (++ticks >= 7) {
|
|
73
|
+
window.clearInterval(tick)
|
|
74
|
+
channel?.end()
|
|
75
|
+
}
|
|
76
|
+
}, 300)
|
|
77
|
+
},
|
|
78
|
+
'A heal that ticks for two seconds: one number over the brute counts up as it goes, rather than eight numbers (FloatHandle.update)'
|
|
79
|
+
),
|
|
80
|
+
button('Experience', (client) => float(client, 'xp', 'brute', random(20, 90)), 'The experience a kill brings, over the brute'),
|
|
81
|
+
button('Mana', (client) => float(client, 'mana', 'brute', -random(30, 80)), 'Mana lost, in blue, over the brute'),
|
|
82
|
+
button('Gold', (client) => float(client, 'bounty', 'dummy', random(5, 40)), 'The gold a kill pays, over the dummy'),
|
|
83
|
+
button('Level up', (client) => float(client, 'levelUp', 'brute', random(2, 10)), 'The level-up banner over the brute'),
|
|
84
|
+
button(
|
|
85
|
+
'Combo (your own)',
|
|
86
|
+
(client) => float(client, 'fxCombo', 'brute', ++combo),
|
|
87
|
+
'A float this game made itself, by its id: it bounces, and counts up each press'
|
|
88
|
+
),
|
|
89
|
+
button(
|
|
90
|
+
'Words',
|
|
91
|
+
(client) => {
|
|
92
|
+
const [brute] = idsOf(client, BRUTE)
|
|
93
|
+
if (brute !== undefined) client.floats.show(['Rally!', 'For the king!', 'Hold the line!'][random(0, 2)]!, { on: brute })
|
|
94
|
+
},
|
|
95
|
+
"Plain words over the brute, in the overlay's plain look"
|
|
96
|
+
),
|
|
97
|
+
toggle(
|
|
98
|
+
'The brute fights',
|
|
99
|
+
false,
|
|
100
|
+
(client, on) => client.session.send(fight.make({ on })),
|
|
101
|
+
'The brute attacks the dummy for real: every hit floats its damage by itself'
|
|
102
|
+
)
|
|
103
|
+
],
|
|
104
|
+
ready: (client) => {
|
|
105
|
+
combo = 0
|
|
106
|
+
aim(client, { x: FLOATS_AT.x + 0.8, z: FLOATS_AT.z + 1.5 }, 18, 0.8)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Floating combat text, on the rules' side: the brute and a dummy for him to hit. The words themselves are the
|
|
3
|
+
* screen's: what each kind of event floats (`./floats.look.ts`), and words put up in code
|
|
4
|
+
*/
|
|
5
|
+
import { ENEMY, PLAYER, UNIT } from '../common/units'
|
|
6
|
+
import { type ExampleDef, exampleField } from '../define'
|
|
7
|
+
import { BRUTE } from './feel'
|
|
8
|
+
|
|
9
|
+
export const FLOATS_AT = { x: 24, z: 24 }
|
|
10
|
+
|
|
11
|
+
export const floats: ExampleDef = {
|
|
12
|
+
id: 'fx-floats',
|
|
13
|
+
category: 'fx',
|
|
14
|
+
title: 'Floating combat text',
|
|
15
|
+
summary:
|
|
16
|
+
'Numbers and words over units: damage, critical strikes, heals, misses, experience, gold, a level up, a heal that counts up as it ticks, and words of your own. Press one to float it, or let the brute fight to see the damage floats merge.',
|
|
17
|
+
tags: ['floats', 'floating text', 'damage numbers', 'combat text', 'presetFloats', 'FloatDef'],
|
|
18
|
+
map: () => exampleField({ name: 'Yard', players: 2 }),
|
|
19
|
+
start: (world) => {
|
|
20
|
+
world.spawn(BRUTE, { at: FLOATS_AT, owner: PLAYER })
|
|
21
|
+
world.spawn(UNIT.dummy, { at: { x: FLOATS_AT.x + 1.6, z: FLOATS_AT.z + 0.8 }, owner: ENEMY })
|
|
22
|
+
},
|
|
23
|
+
source: ['examples/fx/floats.ts', 'examples/fx/floats.look.ts']
|
|
24
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every ready-made effect, on the client's side: the presets (`PRESET_FX`) and the links (`LINK_FX`) in a list, each
|
|
3
|
+
* played on this screen alone with `client.fx.play`. How each is played follows what it is: a missile is flown at the
|
|
4
|
+
* footman and lands with its `impact`, a trail is carried round him, a link reaches from him to the archer, the rest
|
|
5
|
+
* play on him and follow him
|
|
6
|
+
*/
|
|
7
|
+
import { type FxHandle, type GameClientApi, LINK_FX, PRESET_FX } from '@v12-engine/client'
|
|
8
|
+
import { button, type ExampleLook, readout, select, slider, toggle } from '../../shell/look'
|
|
9
|
+
import { STAGE } from './gallery'
|
|
10
|
+
import { aim, everyFrame, idsOf } from './stage'
|
|
11
|
+
import { UNIT } from '../common/units'
|
|
12
|
+
|
|
13
|
+
/** Every effect there is to play, in the order the docs list them: fire, smoke, explosions, earth, magic, water, blood, links */
|
|
14
|
+
const NAMES = [...Object.keys(PRESET_FX), ...Object.keys(LINK_FX)]
|
|
15
|
+
|
|
16
|
+
/** What the card has chosen, and what plays */
|
|
17
|
+
const chosen = { name: 'explosion', scale: 1, oneAtATime: true }
|
|
18
|
+
let playing: FxHandle[] = []
|
|
19
|
+
/** Missiles on their way: the effect, where it is, where it goes */
|
|
20
|
+
let flights: { handle: FxHandle; at: { x: number; y: number; z: number }; to: { x: number; y: number; z: number } }[] = []
|
|
21
|
+
/** Trails carried round the footman: the effect, and how long it has gone round */
|
|
22
|
+
let rounds: { handle: FxHandle; age: number }[] = []
|
|
23
|
+
|
|
24
|
+
/** Plays an effect the way it is made to be seen */
|
|
25
|
+
function play(client: GameClientApi, name: string): void {
|
|
26
|
+
const [footman] = idsOf(client, UNIT.footman)
|
|
27
|
+
const [archer] = idsOf(client, UNIT.archer)
|
|
28
|
+
if (footman === undefined) return
|
|
29
|
+
if (chosen.oneAtATime) stopAll()
|
|
30
|
+
const def = client.fx.get(name)
|
|
31
|
+
const tags = def?.tags ?? []
|
|
32
|
+
const scale = chosen.scale
|
|
33
|
+
const ground = (x: number, z: number) => client.session.terrain.heightAt(x, z)
|
|
34
|
+
if (name in LINK_FX) {
|
|
35
|
+
// drawn between two: from the footman toward the archer, both ends followed as they move
|
|
36
|
+
// a link of a chain lasts its `time` (0.4 s): longer here, to be seen
|
|
37
|
+
playing.push(client.fx.play(name, { on: footman, toward: archer, scale, ...(name === 'chain' && { params: { time: 1.5 } }) }))
|
|
38
|
+
} else if (tags.includes('projectile')) {
|
|
39
|
+
// a missile: flown from the thrower's spot at the footman, landing with what its `impact` knob names
|
|
40
|
+
const { thrower, target } = STAGE
|
|
41
|
+
const at = { x: thrower.x, y: ground(thrower.x, thrower.z) + 1.4, z: thrower.z }
|
|
42
|
+
const handle = client.fx.play(name, { at, scale })
|
|
43
|
+
flights.push({ handle, at, to: { x: target.x, y: ground(target.x, target.z) + 1.1, z: target.z } })
|
|
44
|
+
playing.push(handle)
|
|
45
|
+
} else if (tags.includes('trail')) {
|
|
46
|
+
// left behind what moves: carried round the footman, so what it leaves shows
|
|
47
|
+
const handle = client.fx.play(name, { at: { x: STAGE.target.x + 3, z: STAGE.target.z }, scale })
|
|
48
|
+
rounds.push({ handle, age: 0 })
|
|
49
|
+
playing.push(handle)
|
|
50
|
+
} else {
|
|
51
|
+
playing.push(client.fx.play(name, { on: footman, scale }))
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Stops every effect played here, letting what is alive of each fade */
|
|
56
|
+
function stopAll(): void {
|
|
57
|
+
for (const handle of playing) handle.stop({ mode: 'fade', fade: 0.4 })
|
|
58
|
+
playing = []
|
|
59
|
+
flights = []
|
|
60
|
+
rounds = []
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Moves what flies and what goes round, a frame's worth */
|
|
64
|
+
function step(client: GameClientApi, dt: number): void {
|
|
65
|
+
flights = flights.filter((flight) => {
|
|
66
|
+
const { at, to, handle } = flight
|
|
67
|
+
if (!handle.alive) return false
|
|
68
|
+
const left = Math.hypot(to.x - at.x, to.y - at.y, to.z - at.z)
|
|
69
|
+
const move = 14 * dt
|
|
70
|
+
if (left <= move) {
|
|
71
|
+
// landed: what it was carrying lets its trail finish, and its impact goes off where it struck
|
|
72
|
+
handle.stop()
|
|
73
|
+
const impact = handle.params.impact
|
|
74
|
+
if (typeof impact === 'string' && impact)
|
|
75
|
+
playing.push(
|
|
76
|
+
client.fx.play(impact, { at: { x: to.x, z: to.z }, params: { size: Number(handle.params.impactSize ?? 1) }, scale: chosen.scale })
|
|
77
|
+
)
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
at.x += ((to.x - at.x) / left) * move
|
|
81
|
+
at.y += ((to.y - at.y) / left) * move
|
|
82
|
+
at.z += ((to.z - at.z) / left) * move
|
|
83
|
+
handle.move({ at: { ...at } })
|
|
84
|
+
return true
|
|
85
|
+
})
|
|
86
|
+
rounds = rounds.filter((round) => {
|
|
87
|
+
round.age += dt
|
|
88
|
+
if (!round.handle.alive) return false
|
|
89
|
+
if (round.age > 5) {
|
|
90
|
+
round.handle.stop()
|
|
91
|
+
return false
|
|
92
|
+
}
|
|
93
|
+
const angle = round.age * 1.4
|
|
94
|
+
round.handle.move({ at: { x: STAGE.target.x + Math.cos(angle) * 3, z: STAGE.target.z + Math.sin(angle) * 3 } })
|
|
95
|
+
return true
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Plays every effect in turn, one each `seconds`, until stopped */
|
|
100
|
+
let tour: number | undefined
|
|
101
|
+
function playAll(client: GameClientApi, seconds = 3): void {
|
|
102
|
+
if (tour !== undefined) window.clearInterval(tour)
|
|
103
|
+
let next = 0
|
|
104
|
+
const one = () => {
|
|
105
|
+
if (next >= NAMES.length) {
|
|
106
|
+
window.clearInterval(tour)
|
|
107
|
+
tour = undefined
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
chosen.name = NAMES[next++]!
|
|
111
|
+
play(client, chosen.name)
|
|
112
|
+
}
|
|
113
|
+
one()
|
|
114
|
+
tour = window.setInterval(one, seconds * 1000)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export const galleryLook: ExampleLook = {
|
|
118
|
+
controls: [
|
|
119
|
+
select(
|
|
120
|
+
'Effect',
|
|
121
|
+
NAMES,
|
|
122
|
+
chosen.name,
|
|
123
|
+
(client, name) => {
|
|
124
|
+
chosen.name = name
|
|
125
|
+
play(client, name)
|
|
126
|
+
},
|
|
127
|
+
'One of the ready-made effects: choosing it plays it in the middle of the stage'
|
|
128
|
+
),
|
|
129
|
+
button('Play', (client) => play(client, chosen.name), 'Plays the chosen effect again'),
|
|
130
|
+
button('Play them all', (client) => playAll(client), 'Every effect in turn, three seconds each'),
|
|
131
|
+
button(
|
|
132
|
+
'Stop',
|
|
133
|
+
() => {
|
|
134
|
+
if (tour !== undefined) window.clearInterval(tour)
|
|
135
|
+
tour = undefined
|
|
136
|
+
stopAll()
|
|
137
|
+
},
|
|
138
|
+
'Stops what plays: what is alive of it fades'
|
|
139
|
+
),
|
|
140
|
+
slider(
|
|
141
|
+
'Scale',
|
|
142
|
+
{ min: 0.25, max: 3, step: 0.05, value: chosen.scale },
|
|
143
|
+
(_client, value) => (chosen.scale = value),
|
|
144
|
+
'How big the next one plays (`FxPlay.scale`): press Play to see it'
|
|
145
|
+
),
|
|
146
|
+
toggle('One at a time', chosen.oneAtATime, (_client, on) => (chosen.oneAtATime = on), 'On: each play stops the one before; off, they pile up'),
|
|
147
|
+
readout(
|
|
148
|
+
'Playing',
|
|
149
|
+
(client) => {
|
|
150
|
+
const stats = client.fx.stats()
|
|
151
|
+
return `${tour !== undefined ? chosen.name : playing.some((each) => each.alive) ? chosen.name : '–'} · ${stats.particles} particles, ${stats.lights} lights`
|
|
152
|
+
},
|
|
153
|
+
'What plays now, and how many particles and lights every effect on screen holds'
|
|
154
|
+
)
|
|
155
|
+
],
|
|
156
|
+
ready: (client) => {
|
|
157
|
+
aim(client, { x: 25, z: 24.5 }, 20, 0.62)
|
|
158
|
+
const stop = everyFrame(client, (dt) => step(client, dt))
|
|
159
|
+
// a first one, so the stage is not empty
|
|
160
|
+
window.setTimeout(() => play(client, chosen.name), 300)
|
|
161
|
+
return () => {
|
|
162
|
+
stop()
|
|
163
|
+
if (tour !== undefined) window.clearInterval(tour)
|
|
164
|
+
tour = undefined
|
|
165
|
+
stopAll()
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every ready-made effect, on the rules' side: a footman to play them on, and an archer for those drawn between two.
|
|
3
|
+
* The effects themselves are the client's alone (`./gallery.look.ts`): nothing here knows of them
|
|
4
|
+
*/
|
|
5
|
+
import { PLAYER, UNIT } from '../common/units'
|
|
6
|
+
import type { ExampleDef } from '../define'
|
|
7
|
+
|
|
8
|
+
/** Where the gallery's three places are: the footman effects play on, the archer links reach, where missiles leave from */
|
|
9
|
+
export const STAGE = { target: { x: 24, z: 24 }, other: { x: 29, z: 26 }, thrower: { x: 16, z: 21 } }
|
|
10
|
+
|
|
11
|
+
export const gallery: ExampleDef = {
|
|
12
|
+
id: 'fx-gallery',
|
|
13
|
+
category: 'fx',
|
|
14
|
+
title: 'Every ready-made effect',
|
|
15
|
+
summary:
|
|
16
|
+
'All the effects the client ships, from fire to blood. Pick one and press Play: it plays on the footman, flies at him if it is a missile, or reaches from him to the archer if it is drawn between two. Play them all runs through every one.',
|
|
17
|
+
tags: ['presetFx', 'linkFx', 'particles', 'fire', 'explosion', 'magic', 'smoke', 'water', 'blood', 'gallery'],
|
|
18
|
+
start: (world) => {
|
|
19
|
+
world.spawn(UNIT.footman, { at: STAGE.target, owner: PLAYER, facing: Math.PI })
|
|
20
|
+
world.spawn(UNIT.archer, { at: STAGE.other, owner: PLAYER, facing: Math.PI })
|
|
21
|
+
},
|
|
22
|
+
source: ['examples/fx/gallery.ts', 'examples/fx/gallery.look.ts']
|
|
23
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The effects' examples on the rules' side (`ExampleCategory`): their list entries, maps and starts, and the units,
|
|
3
|
+
* abilities and commands they add to the game. Each example is a file of its own here, its look beside it
|
|
4
|
+
* (`<name>.look.ts`, gathered in `./looks.ts`)
|
|
5
|
+
*/
|
|
6
|
+
import type { Game } from '@v12-engine/engine'
|
|
7
|
+
import type { ExampleCategory } from '../define'
|
|
8
|
+
import { blood, bloodRules } from './blood'
|
|
9
|
+
import { feel, feelRules } from './feel'
|
|
10
|
+
import { fight } from './fight'
|
|
11
|
+
import { floats } from './floats'
|
|
12
|
+
import { gallery } from './gallery'
|
|
13
|
+
import { links, linksRules } from './links'
|
|
14
|
+
import { map } from './map'
|
|
15
|
+
import { marks, marksRules } from './marks'
|
|
16
|
+
import { shots, shotsRules } from './shots'
|
|
17
|
+
import { spells, spellsRules } from './spells'
|
|
18
|
+
|
|
19
|
+
export const category: ExampleCategory = {
|
|
20
|
+
rules: (game: Game<any>) => {
|
|
21
|
+
game.command(fight)
|
|
22
|
+
shotsRules(game)
|
|
23
|
+
spellsRules(game)
|
|
24
|
+
feelRules(game)
|
|
25
|
+
bloodRules(game)
|
|
26
|
+
marksRules(game)
|
|
27
|
+
linksRules(game)
|
|
28
|
+
},
|
|
29
|
+
examples: [gallery, shots, spells, links, feel, floats, blood, marks, map]
|
|
30
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Beams, tethers and chains, on the client's side: the link effects (`linkFx`: `ray`, `tether`, `chain`) played on one
|
|
3
|
+
* unit `toward` another, which the effect asks where it is every frame, so both ends follow. A ray and a rope play
|
|
4
|
+
* until stopped (`time: 0`); a link of a chain lasts its `time`. Chain lightning is links played one after another,
|
|
5
|
+
* each from the last struck to the next, with a spark where it lands
|
|
6
|
+
*/
|
|
7
|
+
import type { FxHandle, GameClientApi } from '@v12-engine/client'
|
|
8
|
+
import { button, type ExampleLook, toggle } from '../../shell/look'
|
|
9
|
+
import { UNIT } from '../common/units'
|
|
10
|
+
import { LINK_MAGE_AT, ray } from './links'
|
|
11
|
+
import { aim, idsOf } from './stage'
|
|
12
|
+
|
|
13
|
+
/** The beam and the rope while they are on */
|
|
14
|
+
const held: { beam?: FxHandle; rope?: FxHandle } = {}
|
|
15
|
+
/** The leaps of a chain still to come */
|
|
16
|
+
let leaps: number[] = []
|
|
17
|
+
|
|
18
|
+
/** Lightning from the mage to the first dummy, and on from each to the next, 0.18 s apart */
|
|
19
|
+
function chainLightning(client: GameClientApi): void {
|
|
20
|
+
for (const leap of leaps) window.clearTimeout(leap)
|
|
21
|
+
const [mage] = idsOf(client, UNIT.mage)
|
|
22
|
+
const dummies = idsOf(client, UNIT.dummy)
|
|
23
|
+
if (mage === undefined) return
|
|
24
|
+
const hops = [mage, ...dummies]
|
|
25
|
+
leaps = hops.slice(1).map((to, i) =>
|
|
26
|
+
window.setTimeout(() => {
|
|
27
|
+
client.fx.play('chain', { on: hops[i], toward: to, params: { time: 0.5, width: 0.12 } })
|
|
28
|
+
client.fx.play('spark-burst', { on: to, point: 'chest', params: { count: 16, color: '#bfe0ff', size: 0.6 } })
|
|
29
|
+
// each struck flashes as the bolt lands
|
|
30
|
+
client.renderer.kit.flash(to, [0.6, 0.8, 1], 0.2)
|
|
31
|
+
}, i * 180)
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const linksLook: ExampleLook = {
|
|
36
|
+
controls: [
|
|
37
|
+
toggle(
|
|
38
|
+
'Beam',
|
|
39
|
+
true,
|
|
40
|
+
(client, on) => {
|
|
41
|
+
held.beam?.stop({ mode: 'fade', fade: 0.3 })
|
|
42
|
+
held.beam = undefined
|
|
43
|
+
const [mage] = idsOf(client, UNIT.mage)
|
|
44
|
+
const [footman] = idsOf(client, UNIT.footman)
|
|
45
|
+
if (on && mage !== undefined) held.beam = client.fx.play('ray', { on: mage, toward: footman })
|
|
46
|
+
},
|
|
47
|
+
'A ray from the mage to the first footman, held as he walks'
|
|
48
|
+
),
|
|
49
|
+
toggle(
|
|
50
|
+
'Tether',
|
|
51
|
+
true,
|
|
52
|
+
(client, on) => {
|
|
53
|
+
held.rope?.stop({ mode: 'fade', fade: 0.3 })
|
|
54
|
+
held.rope = undefined
|
|
55
|
+
const [mage] = idsOf(client, UNIT.mage)
|
|
56
|
+
const footmen = idsOf(client, UNIT.footman)
|
|
57
|
+
if (on && mage !== undefined) held.rope = client.fx.play('tether', { on: mage, toward: footmen[1], params: { color: '#ff7a8a' } })
|
|
58
|
+
},
|
|
59
|
+
'A rope that sags as he comes near and pulls tight as he goes'
|
|
60
|
+
),
|
|
61
|
+
button('Chain lightning', (client) => chainLightning(client), 'Links one after another, along the dummies'),
|
|
62
|
+
button(
|
|
63
|
+
'A ray from the rules',
|
|
64
|
+
(client) => client.session.send(ray.make({})),
|
|
65
|
+
'A ray played by the rules (`world.fx`) rather than the screen: every player sees it'
|
|
66
|
+
)
|
|
67
|
+
],
|
|
68
|
+
ready: (client) => {
|
|
69
|
+
aim(client, { x: LINK_MAGE_AT.x, z: LINK_MAGE_AT.z + 4 }, 32, 0.8)
|
|
70
|
+
const [mage] = idsOf(client, UNIT.mage)
|
|
71
|
+
const footmen = idsOf(client, UNIT.footman)
|
|
72
|
+
if (mage !== undefined) {
|
|
73
|
+
held.beam = client.fx.play('ray', { on: mage, toward: footmen[0] })
|
|
74
|
+
held.rope = client.fx.play('tether', { on: mage, toward: footmen[1], params: { color: '#ff7a8a' } })
|
|
75
|
+
}
|
|
76
|
+
return () => {
|
|
77
|
+
held.beam?.stop({ mode: 'at-once' })
|
|
78
|
+
held.rope?.stop({ mode: 'at-once' })
|
|
79
|
+
held.beam = held.rope = undefined
|
|
80
|
+
for (const leap of leaps) window.clearTimeout(leap)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Beams, tethers and chains, on the rules' side: a mage in the middle, two footmen walking back and forth past him,
|
|
3
|
+
* and a row of dummies for lightning to leap along. The links are the screen's (`./links.look.ts`); here, the rules
|
|
4
|
+
* play one too, every player seeing it (`world.fx` with `toward`), from the command `fx.ray`
|
|
5
|
+
*/
|
|
6
|
+
import { defineCommand, defineTimer, type Game } from '@v12-engine/engine'
|
|
7
|
+
import { ENEMY, PLAYER, UNIT } from '../common/units'
|
|
8
|
+
import { type ExampleDef, exampleField } from '../define'
|
|
9
|
+
|
|
10
|
+
export const LINK_MAGE_AT = { x: 24, z: 22 }
|
|
11
|
+
|
|
12
|
+
/** What puts the rules' ray out, set to go off as it is played: a timer, so a save and a replay keep it */
|
|
13
|
+
const rayEnds = defineTimer<{ fx: number }>({ id: 'fx.rayEnds', run: (world, { fx }) => world.stopFx(fx, { fade: 0.4 }) })
|
|
14
|
+
|
|
15
|
+
/** The rules play a ray from the mage to the farthest dummy: an effect drawn between two, sent to every screen */
|
|
16
|
+
export const ray = defineCommand('fx.ray', {}, (world) => {
|
|
17
|
+
const mage = [...world.units.values()].find((unit) => unit.def.id === UNIT.mage && unit.alive)
|
|
18
|
+
const dummies = [...world.units.values()].filter((unit) => unit.def.id === UNIT.dummy && unit.alive)
|
|
19
|
+
const far = dummies.sort((a, b) => b.pos.x - a.pos.x)[0]
|
|
20
|
+
if (!mage || !far) return
|
|
21
|
+
const id = world.fx('ray', { on: mage, toward: far, params: { color: '#ffcf6a' } })
|
|
22
|
+
// it lasts as long as the rules say: two seconds
|
|
23
|
+
world.timers.after(2, rayEnds, { fx: id })
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
export const links: ExampleDef = {
|
|
27
|
+
id: 'fx-links',
|
|
28
|
+
category: 'fx',
|
|
29
|
+
title: 'Beams, tethers and chains',
|
|
30
|
+
summary:
|
|
31
|
+
'Effects drawn between two, each end followed as it moves: a beam, a rope that sags, lightning that leaps from one to the next. The footmen walk; the links keep hold. One ray is played by the rules, for every player.',
|
|
32
|
+
tags: ['linkFx', 'ray', 'beam', 'tether', 'chain', 'lightning', 'toward', 'link'],
|
|
33
|
+
map: () => exampleField({ name: 'Field', players: 2 }),
|
|
34
|
+
start: (world) => {
|
|
35
|
+
world.spawn(UNIT.mage, { at: LINK_MAGE_AT, owner: PLAYER })
|
|
36
|
+
// two footmen walking to and fro past the mage
|
|
37
|
+
world.spawn(UNIT.footman, { at: { x: 17, z: 24 }, owner: PLAYER }).order('patrol', { x: 31, z: 24 })
|
|
38
|
+
world.spawn(UNIT.footman, { at: { x: 31, z: 18.5 }, owner: PLAYER }).order('patrol', { x: 17, z: 18.5 })
|
|
39
|
+
// dummies for the chain, in a line
|
|
40
|
+
for (let i = 0; i < 6; i++) world.spawn(UNIT.dummy, { at: { x: 16 + i * 3.2, z: 33 + (i % 2) * 1.2 }, owner: ENEMY })
|
|
41
|
+
},
|
|
42
|
+
source: ['examples/fx/links.ts', 'examples/fx/links.look.ts']
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Registers the command */
|
|
46
|
+
export function linksRules(game: Game<any>): void {
|
|
47
|
+
game.command(ray)
|
|
48
|
+
game.timer(rayEnds)
|
|
49
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The effects' examples on the client's side (`CategoryLooks`): each example's look and controls by its id, and what
|
|
3
|
+
* they all need of the game's look: every ready-made effect (`presetFx`), the links (`linkFx`) and the marks laid on
|
|
4
|
+
* the ground (`presetDecals`), taken into the game's effects for any example to play, and the looks of the units and
|
|
5
|
+
* spells the examples add
|
|
6
|
+
*/
|
|
7
|
+
import { linkFx, presetDecals, presetFx } from '@v12-engine/client'
|
|
8
|
+
import type { CategoryLooks } from '../../shell/look'
|
|
9
|
+
import { SKELETON } from './blood'
|
|
10
|
+
import { bloodLook, skeletonLook } from './blood.look'
|
|
11
|
+
import { BRUTE } from './feel'
|
|
12
|
+
import { bruteLook, feelLook } from './feel.look'
|
|
13
|
+
import { floatsLook } from './floats.look'
|
|
14
|
+
import { galleryLook } from './gallery.look'
|
|
15
|
+
import { linksLook } from './links.look'
|
|
16
|
+
import { mapLook } from './map.look'
|
|
17
|
+
import { IDLE_SKELETON } from './marks'
|
|
18
|
+
import { marksLook } from './marks.look'
|
|
19
|
+
import { shooterLooks, shotsLook } from './shots.look'
|
|
20
|
+
import { CASTER } from './spells'
|
|
21
|
+
import { casterLook, spellLooks, spellsLook } from './spells.look'
|
|
22
|
+
|
|
23
|
+
export const category: CategoryLooks = {
|
|
24
|
+
view: (view) => ({
|
|
25
|
+
...view,
|
|
26
|
+
units: {
|
|
27
|
+
...view.units,
|
|
28
|
+
looks: {
|
|
29
|
+
...view.units.looks,
|
|
30
|
+
...shooterLooks,
|
|
31
|
+
[CASTER]: casterLook,
|
|
32
|
+
[BRUTE]: bruteLook,
|
|
33
|
+
[SKELETON]: skeletonLook,
|
|
34
|
+
[IDLE_SKELETON]: skeletonLook
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
fx: {
|
|
38
|
+
...view.fx,
|
|
39
|
+
defs: { ...presetFx(), ...linkFx(), ...view.fx?.defs },
|
|
40
|
+
abilities: { ...view.fx?.abilities, looks: { ...view.fx?.abilities?.looks, ...spellLooks } }
|
|
41
|
+
},
|
|
42
|
+
ground: { ...view.ground, decals: { ...presetDecals(), ...view.ground?.decals } }
|
|
43
|
+
}),
|
|
44
|
+
looks: {
|
|
45
|
+
'fx-gallery': galleryLook,
|
|
46
|
+
'fx-shots': shotsLook,
|
|
47
|
+
'fx-spell-styles': spellsLook,
|
|
48
|
+
'fx-links': linksLook,
|
|
49
|
+
'fx-hit-feel': feelLook,
|
|
50
|
+
'fx-floats': floatsLook,
|
|
51
|
+
'fx-blood': bloodLook,
|
|
52
|
+
'fx-marks': marksLook,
|
|
53
|
+
'fx-map': mapLook
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Effects laid on the map, on the client's side. A map's file keeps its own effects (`MapEnvironment.fx`: an effect of
|
|
3
|
+
* the game's by name, where, its knobs, when it plays), its lights (`lights`) and the marks on its ground (`decals`, by
|
|
4
|
+
* the game's decal looks, `presetDecals`); the editor's effect tool (X) places them. Here they are laid in code with
|
|
5
|
+
* `client.setEnvironment`, which a map's file does as it loads, and found again by name with `client.mapFx.named`
|
|
6
|
+
*/
|
|
7
|
+
import type { GameClientApi, MapDecal, MapFx, MapLightSource } from '@v12-engine/client'
|
|
8
|
+
import type { MapEnvironment } from '@v12-engine/engine'
|
|
9
|
+
import { type ExampleLook, slider, toggle } from '../../shell/look'
|
|
10
|
+
import { CAMP } from './map'
|
|
11
|
+
import { aim } from './stage'
|
|
12
|
+
|
|
13
|
+
const { x, z } = CAMP
|
|
14
|
+
|
|
15
|
+
/** The effects of the map: each plays for as long as the map does, or at night alone (`when`) */
|
|
16
|
+
const FX: MapFx[] = [
|
|
17
|
+
{ id: 1, name: 'campfire', fx: 'campfire', x, z, params: { size: 0.9 } },
|
|
18
|
+
// four torches round the camp, lit at night
|
|
19
|
+
...[0, 1, 2, 3].map((i): MapFx => {
|
|
20
|
+
const angle = (i / 4) * Math.PI * 2 + Math.PI / 4
|
|
21
|
+
return { id: 2 + i, name: `torch ${i + 1}`, fx: 'torch', x: x + Math.cos(angle) * 6, z: z + Math.sin(angle) * 6, height: 1.6, when: 'night' }
|
|
22
|
+
}),
|
|
23
|
+
{ id: 6, name: 'smoke', fx: 'chimney', x: x - 7, z: z + 5, height: 2.5 },
|
|
24
|
+
{ id: 7, name: 'mist', fx: 'mist', x: x + 8, z: z + 8, params: { radius: 5 } },
|
|
25
|
+
{ id: 8, name: 'portal', fx: 'portal', x: x + 7, z: z - 6, params: { size: 0.9 } },
|
|
26
|
+
{ id: 9, name: 'wisp', fx: 'glow', x: x - 6, z: z - 5, height: 1.4, params: { color: '#7fe0ff' }, when: 'night' },
|
|
27
|
+
{ id: 10, name: 'wisp 2', fx: 'glow', x: x - 8, z: z - 2, height: 2, params: { color: '#b48cff', size: 0.7 }, when: 'night' },
|
|
28
|
+
{ id: 11, name: 'sparkle', fx: 'sparkle', x: x - 6, z: z - 5, params: { color: '#a8e6ff' } },
|
|
29
|
+
{ id: 12, name: 'embers', fx: 'embers', x: x + 3.5, z: z + 6.5, params: { radius: 1 } }
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
/** The map's own lights besides what the effects bring: a cold one over the runes, always on */
|
|
33
|
+
const LIGHTS: MapLightSource[] = [
|
|
34
|
+
{ id: 1, name: 'rune light', x: x - 6, z: z - 5, height: 2, color: [0.45, 0.75, 1], intensity: 1.4, range: 7, when: 'always' }
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
/** The marks on the ground: runes that glow, a scorch round the fire, cracks, cart ruts */
|
|
38
|
+
const DECALS: MapDecal[] = [
|
|
39
|
+
{ id: 1, look: 'scorch', x, z, size: 3.4 },
|
|
40
|
+
{ id: 2, look: 'rune', x: x - 6, z: z - 5, size: 3.5 },
|
|
41
|
+
{ id: 3, look: 'glyph', x: x + 7, z: z - 6, size: 4.2, tint: [0.75, 0.5, 1] },
|
|
42
|
+
{ id: 4, look: 'crack', x: x + 5, z: z + 3, size: 3, facing: 0.6 },
|
|
43
|
+
{ id: 5, look: 'ruts', x: x - 3, z: z + 7, size: 4, facing: 1.2 },
|
|
44
|
+
{ id: 6, look: 'blood', x: x + 2.5, z: z - 3.2, size: 1.2, fade: 0.3 }
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
/** The night the example opens on (the game's look named `night`, `src/view.ts`), and the day turned to */
|
|
48
|
+
const NIGHT: Partial<MapEnvironment> = {
|
|
49
|
+
sky: [0.05, 0.07, 0.14],
|
|
50
|
+
light: { sunUp: 35, sunAround: 200, sunColor: [0.55, 0.65, 1], sun: 0.35, ambient: 0.25, ambientColor: [0.35, 0.4, 0.7] },
|
|
51
|
+
fog: { start: 40, end: 160 }
|
|
52
|
+
}
|
|
53
|
+
const DAY: Partial<MapEnvironment> = {
|
|
54
|
+
sky: [0.55, 0.72, 0.95],
|
|
55
|
+
light: { sunUp: 50, sunAround: 210, sun: 1.1, ambient: 0.6, sunColor: [1, 0.97, 0.9], ambientColor: [0.85, 0.9, 1] },
|
|
56
|
+
fog: { start: 80, end: 260 }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The fire's size, through what plays of it on the map */
|
|
60
|
+
const fire = (client: GameClientApi, size: number) => client.mapFx?.named('campfire')?.set({ size })
|
|
61
|
+
|
|
62
|
+
export const mapLook: ExampleLook = {
|
|
63
|
+
controls: [
|
|
64
|
+
toggle(
|
|
65
|
+
'Day',
|
|
66
|
+
false,
|
|
67
|
+
(client, on) => client.setEnvironment(on ? DAY : NIGHT, { over: 1.5 }),
|
|
68
|
+
'Day or night, over a second and a half: the torches and wisps play by night alone (`when: night`)'
|
|
69
|
+
),
|
|
70
|
+
slider(
|
|
71
|
+
'Fire',
|
|
72
|
+
{ min: 0.2, max: 2.5, step: 0.05, value: 0.9 },
|
|
73
|
+
(client, size) => fire(client, size),
|
|
74
|
+
"How big the campfire burns: the map's effect changed while it plays (`client.mapFx.named('campfire').set`)"
|
|
75
|
+
),
|
|
76
|
+
toggle(
|
|
77
|
+
'Torches',
|
|
78
|
+
true,
|
|
79
|
+
(client, on) => client.setEnvironment({ fx: FX.map((each) => (each.fx === 'torch' ? { ...each, on } : each)) }),
|
|
80
|
+
'Puts the torches round the camp out or lights them again (`on: false` on the map effect)'
|
|
81
|
+
),
|
|
82
|
+
toggle(
|
|
83
|
+
'Marks on the ground',
|
|
84
|
+
true,
|
|
85
|
+
(client, on) => client.setEnvironment({ decals: on ? DECALS : [] }),
|
|
86
|
+
'The marks painted on the ground round the camp: a scorch, runes, a crack, ruts and blood (`decals`)'
|
|
87
|
+
)
|
|
88
|
+
],
|
|
89
|
+
ready: (client) => {
|
|
90
|
+
client.setEnvironment({ fx: FX, lights: LIGHTS, decals: DECALS })
|
|
91
|
+
aim(client, { x: x - 0.5, z: z - 0.5 }, 24, 0.68)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Effects laid on the map, on the rules' side: a night, and footmen round where the fire will be. The fire, the
|
|
3
|
+
* torches, the lights and the marks on the ground are the map's look (`MapEnvironment.fx`, `lights`, `decals`), which
|
|
4
|
+
* the rules never see: `./map.look.ts` lays them as the map's file would
|
|
5
|
+
*/
|
|
6
|
+
import { exactMath } from '@v12-engine/engine'
|
|
7
|
+
import { PLAYER, UNIT } from '../common/units'
|
|
8
|
+
import type { ExampleDef } from '../define'
|
|
9
|
+
|
|
10
|
+
/** The middle of the camp: where the fire burns */
|
|
11
|
+
export const CAMP = { x: 24, z: 24 }
|
|
12
|
+
|
|
13
|
+
export const map: ExampleDef = {
|
|
14
|
+
id: 'fx-map',
|
|
15
|
+
category: 'fx',
|
|
16
|
+
title: 'A camp at night',
|
|
17
|
+
summary:
|
|
18
|
+
"Effects that play for as long as the map does: a campfire, torches that light at night, smoke, mist, a portal, wisps; the map's own lights; runes, scorches and cracks laid on the ground. Turn day on to see what is lit only at night; turn the fire up.",
|
|
19
|
+
tags: ['map fx', 'MapFx', 'decals', 'lights', 'night', 'campfire', 'torch', 'environment', 'placed'],
|
|
20
|
+
environment: 'night',
|
|
21
|
+
start: (world) => {
|
|
22
|
+
for (let i = 0; i < 5; i++) {
|
|
23
|
+
const angle = (i / 5) * Math.PI * 2 + 0.3
|
|
24
|
+
world.spawn(UNIT.footman, {
|
|
25
|
+
at: { x: CAMP.x + exactMath.cos(angle) * 2.6, z: CAMP.z + exactMath.sin(angle) * 2.6 },
|
|
26
|
+
owner: PLAYER,
|
|
27
|
+
// each facing the fire
|
|
28
|
+
facing: exactMath.atan2(-exactMath.cos(angle), -exactMath.sin(angle))
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
source: ['examples/fx/map.ts', 'examples/fx/map.look.ts']
|
|
33
|
+
}
|