@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,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The examples' frame, as three.js's examples page has it: the list down the left (searched, by category, the one open
|
|
3
|
+
* marked), and the example on the right with a card over it (its name, what it shows, its controls, where its code is).
|
|
4
|
+
* Plain HTML, so the list's page carries nothing of the client: the game is drawn into `content`
|
|
5
|
+
*/
|
|
6
|
+
import { CATEGORIES, type ExampleDef } from '../examples/define'
|
|
7
|
+
import { examples } from '../examples/catalog'
|
|
8
|
+
import type { GameClientApi } from '@v12-engine/client'
|
|
9
|
+
import { errorFields } from '@v12-engine/engine'
|
|
10
|
+
import type { ExampleControl, ExampleLook } from './look'
|
|
11
|
+
import { STYLE } from './style'
|
|
12
|
+
|
|
13
|
+
/** What the frame is told: the example open (none on the list's own page), and how to open another */
|
|
14
|
+
export interface ShellOptions {
|
|
15
|
+
current?: ExampleDef
|
|
16
|
+
open: (id: string) => void
|
|
17
|
+
/** The start screen, from the frame's top */
|
|
18
|
+
home: () => void
|
|
19
|
+
/** Fetches an example's code and files ahead, as the pointer comes over its link */
|
|
20
|
+
warm?: (id: string) => void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The frame put up in an element: where the example is drawn, and what fills in its card once its game is drawn */
|
|
24
|
+
export interface Shell {
|
|
25
|
+
/** Where the example (or the list's page) is drawn */
|
|
26
|
+
content: HTMLElement
|
|
27
|
+
/** The example's controls, run with its client once its game is drawn */
|
|
28
|
+
controls(look: ExampleLook | undefined, client: GameClientApi): void
|
|
29
|
+
dispose(): void
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Where the list was scrolled to and what was searched, kept from one example to the next */
|
|
33
|
+
const kept = { scroll: 0, search: '', collapsed: false }
|
|
34
|
+
|
|
35
|
+
/** Puts the frame up in `element`; `dispose` takes it away */
|
|
36
|
+
export function mountShell(element: HTMLElement, options: ShellOptions): Shell {
|
|
37
|
+
addStyle()
|
|
38
|
+
const root = make('div', 'vx-root')
|
|
39
|
+
root.classList.toggle('vx-collapsed', kept.collapsed)
|
|
40
|
+
const side = make('aside', 'vx-side')
|
|
41
|
+
const content = make('main', 'vx-content')
|
|
42
|
+
root.append(side, content)
|
|
43
|
+
element.append(root)
|
|
44
|
+
|
|
45
|
+
// the top: the name, the start screen, the list folded away
|
|
46
|
+
const top = make('header', 'vx-top')
|
|
47
|
+
const brand = make('button', 'vx-brand')
|
|
48
|
+
brand.innerHTML = 'V12 <span>examples</span>'
|
|
49
|
+
brand.title = 'All the examples'
|
|
50
|
+
brand.onclick = () => options.open('')
|
|
51
|
+
const home = make('button', 'vx-icon')
|
|
52
|
+
home.textContent = '⌂'
|
|
53
|
+
home.title = 'The start screen'
|
|
54
|
+
home.onclick = options.home
|
|
55
|
+
const fold = make('button', 'vx-icon vx-fold')
|
|
56
|
+
fold.textContent = '⟨'
|
|
57
|
+
fold.title = 'Fold the list away'
|
|
58
|
+
fold.onclick = () => {
|
|
59
|
+
kept.collapsed = !kept.collapsed
|
|
60
|
+
root.classList.toggle('vx-collapsed', kept.collapsed)
|
|
61
|
+
fold.textContent = kept.collapsed ? '⟩' : '⟨'
|
|
62
|
+
// the game's canvas is sized by its element: told the element changed
|
|
63
|
+
window.dispatchEvent(new Event('resize'))
|
|
64
|
+
}
|
|
65
|
+
fold.textContent = kept.collapsed ? '⟩' : '⟨'
|
|
66
|
+
top.append(brand, home, fold)
|
|
67
|
+
for (const each of [brand, home, fold]) keepFocusOff(each)
|
|
68
|
+
|
|
69
|
+
const search = make('input', 'vx-search') as HTMLInputElement
|
|
70
|
+
search.type = 'search'
|
|
71
|
+
search.placeholder = `Search ${examples.length} examples ( / )`
|
|
72
|
+
search.value = kept.search
|
|
73
|
+
search.spellcheck = false
|
|
74
|
+
|
|
75
|
+
const list = make('nav', 'vx-list')
|
|
76
|
+
side.append(top, search, list)
|
|
77
|
+
|
|
78
|
+
const links: { def: ExampleDef; link: HTMLElement; words: string }[] = []
|
|
79
|
+
for (const category of CATEGORIES) {
|
|
80
|
+
const own = examples.filter((each) => each.category === category.id)
|
|
81
|
+
if (!own.length) continue
|
|
82
|
+
const section = make('section', 'vx-section')
|
|
83
|
+
const title = make('h3', '')
|
|
84
|
+
title.textContent = category.title
|
|
85
|
+
title.title = category.summary
|
|
86
|
+
section.append(title)
|
|
87
|
+
for (const def of own) {
|
|
88
|
+
const link = make('a', 'vx-link') as HTMLAnchorElement
|
|
89
|
+
link.href = `?${def.id}`
|
|
90
|
+
link.textContent = def.title
|
|
91
|
+
link.title = def.summary
|
|
92
|
+
if (def.id === options.current?.id) link.classList.add('vx-on')
|
|
93
|
+
keepFocusOff(link)
|
|
94
|
+
// its code and files fetched while the pointer is on its way to press it
|
|
95
|
+
link.onpointerenter = () => def.id !== options.current?.id && options.warm?.(def.id)
|
|
96
|
+
link.onclick = (event) => {
|
|
97
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.button !== 0) return
|
|
98
|
+
event.preventDefault()
|
|
99
|
+
if (def.id !== options.current?.id) options.open(def.id)
|
|
100
|
+
}
|
|
101
|
+
section.append(link)
|
|
102
|
+
links.push({ def, link, words: [def.title, def.summary, def.id, category.title, ...(def.tags ?? [])].join(' ').toLowerCase() })
|
|
103
|
+
}
|
|
104
|
+
list.append(section)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const filter = () => {
|
|
108
|
+
kept.search = search.value
|
|
109
|
+
const words = search.value.toLowerCase().split(/\s+/).filter(Boolean)
|
|
110
|
+
for (const each of links) each.link.hidden = !words.every((word) => each.words.includes(word))
|
|
111
|
+
for (const section of list.querySelectorAll('section'))
|
|
112
|
+
(section as HTMLElement).hidden = ![...section.querySelectorAll('a')].some((a) => !a.hidden)
|
|
113
|
+
}
|
|
114
|
+
search.oninput = filter
|
|
115
|
+
search.onkeydown = (event) => {
|
|
116
|
+
if (event.key === 'Enter') links.find((each) => !each.link.hidden)?.link.click()
|
|
117
|
+
if (event.key === 'Escape') {
|
|
118
|
+
search.value = ''
|
|
119
|
+
filter()
|
|
120
|
+
search.blur()
|
|
121
|
+
}
|
|
122
|
+
// the game's keys are not for what is typed here
|
|
123
|
+
event.stopPropagation()
|
|
124
|
+
}
|
|
125
|
+
filter()
|
|
126
|
+
list.onscroll = () => (kept.scroll = list.scrollTop)
|
|
127
|
+
requestAnimationFrame(() => {
|
|
128
|
+
list.scrollTop = kept.scroll
|
|
129
|
+
const on = list.querySelector<HTMLElement>('.vx-on')
|
|
130
|
+
if (on && (on.offsetTop < list.scrollTop || on.offsetTop > list.scrollTop + list.clientHeight - 40)) on.scrollIntoView({ block: 'center' })
|
|
131
|
+
})
|
|
132
|
+
const slash = (event: KeyboardEvent) => {
|
|
133
|
+
if (event.key !== '/' || document.activeElement === search) return
|
|
134
|
+
event.preventDefault()
|
|
135
|
+
event.stopPropagation()
|
|
136
|
+
search.focus()
|
|
137
|
+
search.select()
|
|
138
|
+
}
|
|
139
|
+
window.addEventListener('keydown', slash, true)
|
|
140
|
+
|
|
141
|
+
// the card over the example: its name, what it shows, its controls, where its code is
|
|
142
|
+
let card: HTMLElement | undefined
|
|
143
|
+
let controlsBox: HTMLElement | undefined
|
|
144
|
+
let hintLine: HTMLElement | undefined
|
|
145
|
+
if (options.current) {
|
|
146
|
+
const def = options.current
|
|
147
|
+
const category = CATEGORIES.find((each) => each.id === def.category)
|
|
148
|
+
card = make('div', 'vx-card')
|
|
149
|
+
const head = make('div', 'vx-card-head')
|
|
150
|
+
const name = make('h2', '')
|
|
151
|
+
name.textContent = def.title
|
|
152
|
+
const shut = make('button', 'vx-icon')
|
|
153
|
+
shut.textContent = '–'
|
|
154
|
+
shut.title = 'Fold the card'
|
|
155
|
+
shut.onclick = () => card!.classList.toggle('vx-card-folded')
|
|
156
|
+
keepFocusOff(shut)
|
|
157
|
+
head.append(name, shut)
|
|
158
|
+
const about = make('p', 'vx-summary')
|
|
159
|
+
about.textContent = def.summary
|
|
160
|
+
controlsBox = make('div', 'vx-controls')
|
|
161
|
+
controlsBox.innerHTML = '<div class="vx-wait">Loading…</div>'
|
|
162
|
+
hintLine = make('p', 'vx-hint')
|
|
163
|
+
hintLine.textContent = POINT_AT
|
|
164
|
+
const where = make('p', 'vx-where')
|
|
165
|
+
const folder = `src/examples/${def.category}/`
|
|
166
|
+
where.innerHTML = `<span>${category?.title ?? ''}</span> · <code>${folder}</code>`
|
|
167
|
+
where.title = `Its rules in ${folder}index.ts, its look and controls in ${folder}looks.ts${def.source?.length ? `, and ${def.source.join(', ')}` : ''}`
|
|
168
|
+
card.append(head, about, controlsBox, hintLine, where)
|
|
169
|
+
content.append(card)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const timers: number[] = []
|
|
173
|
+
return {
|
|
174
|
+
content,
|
|
175
|
+
controls(look, client) {
|
|
176
|
+
if (!controlsBox) return
|
|
177
|
+
const shown = (hint?: string) => hintLine && (hintLine.textContent = hint ?? POINT_AT)
|
|
178
|
+
controlsBox.replaceChildren(...(look?.controls ?? []).map((control) => controlOf(control, client, timers, shown)))
|
|
179
|
+
if (hintLine) hintLine.hidden = !look?.controls?.some((control) => control.hint)
|
|
180
|
+
},
|
|
181
|
+
dispose() {
|
|
182
|
+
window.removeEventListener('keydown', slash, true)
|
|
183
|
+
for (const timer of timers) clearInterval(timer)
|
|
184
|
+
root.remove()
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** A control as an element, run with the client */
|
|
190
|
+
function controlOf(control: ExampleControl, client: GameClientApi, timers: number[], shown: (hint?: string) => void): HTMLElement {
|
|
191
|
+
const row = make('div', `vx-control vx-${control.kind}`)
|
|
192
|
+
if (control.hint) {
|
|
193
|
+
row.title = control.hint
|
|
194
|
+
row.onpointerenter = () => shown(control.hint)
|
|
195
|
+
row.onpointerleave = () => shown()
|
|
196
|
+
}
|
|
197
|
+
const label = make('label', '')
|
|
198
|
+
label.textContent = control.label
|
|
199
|
+
const run = (action: () => void) => {
|
|
200
|
+
try {
|
|
201
|
+
action()
|
|
202
|
+
} catch (error) {
|
|
203
|
+
client.log.error('examples', `The control "${control.label}" failed`, errorFields(error))
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
switch (control.kind) {
|
|
207
|
+
case 'button': {
|
|
208
|
+
const button = make('button', 'vx-button')
|
|
209
|
+
button.textContent = control.label
|
|
210
|
+
button.onclick = () => run(() => control.run(client))
|
|
211
|
+
keepFocusOff(button)
|
|
212
|
+
row.append(button)
|
|
213
|
+
break
|
|
214
|
+
}
|
|
215
|
+
case 'toggle': {
|
|
216
|
+
const box = make('input', '') as HTMLInputElement
|
|
217
|
+
box.type = 'checkbox'
|
|
218
|
+
box.checked = control.value
|
|
219
|
+
box.onchange = () => {
|
|
220
|
+
run(() => control.set(client, box.checked))
|
|
221
|
+
box.blur()
|
|
222
|
+
}
|
|
223
|
+
label.prepend(box)
|
|
224
|
+
row.append(label)
|
|
225
|
+
break
|
|
226
|
+
}
|
|
227
|
+
case 'slider': {
|
|
228
|
+
const value = make('span', 'vx-value')
|
|
229
|
+
const range = make('input', '') as HTMLInputElement
|
|
230
|
+
range.type = 'range'
|
|
231
|
+
range.min = String(control.min)
|
|
232
|
+
range.max = String(control.max)
|
|
233
|
+
range.step = String(control.step ?? (control.max - control.min) / 100)
|
|
234
|
+
range.value = String(control.value)
|
|
235
|
+
value.textContent = String(control.value)
|
|
236
|
+
range.oninput = () => {
|
|
237
|
+
value.textContent = range.value
|
|
238
|
+
run(() => control.set(client, Number(range.value)))
|
|
239
|
+
}
|
|
240
|
+
// let go, the arrow keys are the camera's again
|
|
241
|
+
range.onpointerup = () => range.blur()
|
|
242
|
+
label.append(value)
|
|
243
|
+
row.append(label, range)
|
|
244
|
+
break
|
|
245
|
+
}
|
|
246
|
+
case 'select': {
|
|
247
|
+
const choice = make('select', '') as HTMLSelectElement
|
|
248
|
+
for (const option of control.options) choice.append(new Option(option, option, false, option === control.value))
|
|
249
|
+
choice.onchange = () => {
|
|
250
|
+
run(() => control.set(client, choice.value))
|
|
251
|
+
choice.blur()
|
|
252
|
+
}
|
|
253
|
+
row.append(label, choice)
|
|
254
|
+
break
|
|
255
|
+
}
|
|
256
|
+
case 'text': {
|
|
257
|
+
const value = make('span', 'vx-value')
|
|
258
|
+
const update = () => run(() => (value.textContent = control.text(client)))
|
|
259
|
+
update()
|
|
260
|
+
timers.push(window.setInterval(update, 500))
|
|
261
|
+
label.append(value)
|
|
262
|
+
row.append(label)
|
|
263
|
+
break
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return row
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** What the hint line says while the pointer is over no control */
|
|
270
|
+
const POINT_AT = 'Point at a control to see what it does.'
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* A button or link pressed with the mouse keeps the focus where it was: the game's keys (the arrows, space) stay the
|
|
274
|
+
* game's, not this element's. The keyboard still reaches it with Tab
|
|
275
|
+
*/
|
|
276
|
+
const keepFocusOff = (element: HTMLElement) => element.addEventListener('mousedown', (event) => event.preventDefault())
|
|
277
|
+
|
|
278
|
+
const make = (tag: string, className: string): HTMLElement => {
|
|
279
|
+
const element = document.createElement(tag)
|
|
280
|
+
if (className) element.className = className
|
|
281
|
+
return element
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function addStyle(): void {
|
|
285
|
+
if (document.getElementById('vx-style')) return
|
|
286
|
+
const style = document.createElement('style')
|
|
287
|
+
style.id = 'vx-style'
|
|
288
|
+
style.textContent = STYLE
|
|
289
|
+
document.head.append(style)
|
|
290
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/** The frame's styles (`./shell.ts`), put on the page once */
|
|
2
|
+
export const STYLE = `
|
|
3
|
+
.vx-root {
|
|
4
|
+
--vx-bg: #101114;
|
|
5
|
+
--vx-panel: #16181d;
|
|
6
|
+
--vx-line: #262a33;
|
|
7
|
+
--vx-text: #d7dbe3;
|
|
8
|
+
--vx-dim: #8a91a0;
|
|
9
|
+
--vx-accent: #f2b84b;
|
|
10
|
+
--vx-accent-dim: rgba(242, 184, 75, 0.14);
|
|
11
|
+
--vx-side: 280px;
|
|
12
|
+
position: absolute;
|
|
13
|
+
inset: 0;
|
|
14
|
+
display: flex;
|
|
15
|
+
background: var(--vx-bg);
|
|
16
|
+
color: var(--vx-text);
|
|
17
|
+
font: 13px/1.45 system-ui, -apple-system, 'Segoe UI', sans-serif;
|
|
18
|
+
}
|
|
19
|
+
.vx-side *, .vx-card, .vx-card *, .vx-index * { box-sizing: border-box; }
|
|
20
|
+
.vx-side {
|
|
21
|
+
width: var(--vx-side);
|
|
22
|
+
flex: none;
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
border-right: 1px solid var(--vx-line);
|
|
26
|
+
background: var(--vx-panel);
|
|
27
|
+
transition: margin-left 160ms ease;
|
|
28
|
+
z-index: 2;
|
|
29
|
+
}
|
|
30
|
+
.vx-collapsed .vx-side { margin-left: calc(-1 * var(--vx-side) + 34px); }
|
|
31
|
+
.vx-collapsed .vx-side > :not(.vx-top) { visibility: hidden; }
|
|
32
|
+
.vx-collapsed .vx-top > :not(.vx-fold) { visibility: hidden; }
|
|
33
|
+
.vx-top { display: flex; align-items: center; gap: 4px; padding: 10px 8px 8px 14px; }
|
|
34
|
+
.vx-brand {
|
|
35
|
+
flex: 1;
|
|
36
|
+
text-align: left;
|
|
37
|
+
background: none;
|
|
38
|
+
border: 0;
|
|
39
|
+
color: var(--vx-text);
|
|
40
|
+
font: 700 16px/1 system-ui, sans-serif;
|
|
41
|
+
letter-spacing: 0.02em;
|
|
42
|
+
cursor: pointer;
|
|
43
|
+
padding: 4px 0;
|
|
44
|
+
}
|
|
45
|
+
.vx-brand span { color: var(--vx-accent); font-weight: 500; }
|
|
46
|
+
.vx-icon {
|
|
47
|
+
width: 26px;
|
|
48
|
+
height: 26px;
|
|
49
|
+
border-radius: 6px;
|
|
50
|
+
border: 1px solid var(--vx-line);
|
|
51
|
+
background: transparent;
|
|
52
|
+
color: var(--vx-dim);
|
|
53
|
+
cursor: pointer;
|
|
54
|
+
font-size: 14px;
|
|
55
|
+
line-height: 1;
|
|
56
|
+
}
|
|
57
|
+
.vx-icon:hover { color: var(--vx-text); border-color: var(--vx-dim); }
|
|
58
|
+
.vx-search {
|
|
59
|
+
margin: 0 12px 8px;
|
|
60
|
+
padding: 7px 10px;
|
|
61
|
+
border-radius: 6px;
|
|
62
|
+
border: 1px solid var(--vx-line);
|
|
63
|
+
background: var(--vx-bg);
|
|
64
|
+
color: var(--vx-text);
|
|
65
|
+
font: inherit;
|
|
66
|
+
outline: none;
|
|
67
|
+
}
|
|
68
|
+
.vx-search:focus { border-color: var(--vx-accent); }
|
|
69
|
+
.vx-list { flex: 1; overflow-y: auto; padding: 0 6px 24px; scrollbar-width: thin; }
|
|
70
|
+
.vx-section h3 {
|
|
71
|
+
margin: 14px 8px 4px;
|
|
72
|
+
font-size: 11px;
|
|
73
|
+
font-weight: 600;
|
|
74
|
+
text-transform: uppercase;
|
|
75
|
+
letter-spacing: 0.08em;
|
|
76
|
+
color: var(--vx-dim);
|
|
77
|
+
}
|
|
78
|
+
.vx-link {
|
|
79
|
+
display: block;
|
|
80
|
+
padding: 4px 8px;
|
|
81
|
+
border-radius: 5px;
|
|
82
|
+
color: var(--vx-text);
|
|
83
|
+
text-decoration: none;
|
|
84
|
+
white-space: nowrap;
|
|
85
|
+
overflow: hidden;
|
|
86
|
+
text-overflow: ellipsis;
|
|
87
|
+
}
|
|
88
|
+
.vx-link:hover { background: rgba(255, 255, 255, 0.05); }
|
|
89
|
+
.vx-link.vx-on { background: var(--vx-accent-dim); color: var(--vx-accent); }
|
|
90
|
+
.vx-content { position: relative; flex: 1; min-width: 0; overflow: hidden; }
|
|
91
|
+
.vx-game { position: absolute; inset: 0; }
|
|
92
|
+
.vx-card {
|
|
93
|
+
position: absolute;
|
|
94
|
+
top: 84px;
|
|
95
|
+
right: 12px;
|
|
96
|
+
width: 290px;
|
|
97
|
+
max-height: calc(100% - 300px);
|
|
98
|
+
min-height: 120px;
|
|
99
|
+
overflow-y: auto;
|
|
100
|
+
z-index: 30;
|
|
101
|
+
padding: 12px 14px;
|
|
102
|
+
border-radius: 10px;
|
|
103
|
+
border: 1px solid var(--vx-line);
|
|
104
|
+
background: rgba(16, 17, 20, 0.88);
|
|
105
|
+
backdrop-filter: blur(6px);
|
|
106
|
+
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.35);
|
|
107
|
+
scrollbar-width: thin;
|
|
108
|
+
}
|
|
109
|
+
.vx-card-head { display: flex; align-items: center; gap: 8px; }
|
|
110
|
+
.vx-card h2 { flex: 1; margin: 0; font-size: 15px; font-weight: 650; }
|
|
111
|
+
.vx-card-folded > :not(.vx-card-head) { display: none; }
|
|
112
|
+
.vx-card-folded { min-height: 0; }
|
|
113
|
+
.vx-summary { margin: 6px 0 10px; color: var(--vx-dim); }
|
|
114
|
+
.vx-hint {
|
|
115
|
+
margin: 8px 0 0;
|
|
116
|
+
padding: 6px 8px;
|
|
117
|
+
min-height: 2.9em;
|
|
118
|
+
border-radius: 6px;
|
|
119
|
+
background: rgba(255, 255, 255, 0.04);
|
|
120
|
+
color: var(--vx-text);
|
|
121
|
+
font-size: 12px;
|
|
122
|
+
}
|
|
123
|
+
.vx-root :focus:not(:focus-visible) { outline: none; }
|
|
124
|
+
.vx-root :focus-visible { outline: 2px solid var(--vx-accent); outline-offset: 1px; }
|
|
125
|
+
.vx-loading {
|
|
126
|
+
position: absolute;
|
|
127
|
+
inset: 0;
|
|
128
|
+
display: flex;
|
|
129
|
+
flex-direction: column;
|
|
130
|
+
align-items: center;
|
|
131
|
+
justify-content: center;
|
|
132
|
+
gap: 14px;
|
|
133
|
+
background: var(--vx-bg);
|
|
134
|
+
color: var(--vx-dim);
|
|
135
|
+
}
|
|
136
|
+
.vx-loading strong { color: var(--vx-text); font-size: 15px; font-weight: 600; }
|
|
137
|
+
.vx-spinner {
|
|
138
|
+
width: 28px;
|
|
139
|
+
height: 28px;
|
|
140
|
+
border-radius: 50%;
|
|
141
|
+
border: 3px solid var(--vx-line);
|
|
142
|
+
border-top-color: var(--vx-accent);
|
|
143
|
+
animation: vx-spin 0.8s linear infinite;
|
|
144
|
+
}
|
|
145
|
+
@keyframes vx-spin { to { transform: rotate(360deg); } }
|
|
146
|
+
.vx-bar { width: 220px; height: 4px; border-radius: 2px; background: var(--vx-line); overflow: hidden; }
|
|
147
|
+
.vx-bar > div { height: 100%; width: 0; background: var(--vx-accent); transition: width 120ms linear; }
|
|
148
|
+
.vx-where { margin: 10px 0 0; font-size: 11px; color: var(--vx-dim); }
|
|
149
|
+
.vx-where code { color: var(--vx-text); }
|
|
150
|
+
.vx-controls { display: flex; flex-direction: column; gap: 6px; }
|
|
151
|
+
.vx-controls:empty { display: none; }
|
|
152
|
+
.vx-wait { color: var(--vx-dim); font-style: italic; }
|
|
153
|
+
.vx-control label { display: flex; align-items: center; gap: 6px; }
|
|
154
|
+
.vx-value { margin-left: auto; color: var(--vx-accent); font-variant-numeric: tabular-nums; }
|
|
155
|
+
.vx-slider input, .vx-select select { width: 100%; }
|
|
156
|
+
.vx-select select {
|
|
157
|
+
margin-top: 2px;
|
|
158
|
+
padding: 4px;
|
|
159
|
+
border-radius: 5px;
|
|
160
|
+
border: 1px solid var(--vx-line);
|
|
161
|
+
background: var(--vx-bg);
|
|
162
|
+
color: var(--vx-text);
|
|
163
|
+
}
|
|
164
|
+
.vx-slider input { accent-color: var(--vx-accent); }
|
|
165
|
+
.vx-toggle input { accent-color: var(--vx-accent); }
|
|
166
|
+
.vx-button {
|
|
167
|
+
width: 100%;
|
|
168
|
+
padding: 6px 10px;
|
|
169
|
+
border-radius: 6px;
|
|
170
|
+
border: 1px solid #4a3d22;
|
|
171
|
+
background: #2a2416;
|
|
172
|
+
color: #f1e4c3;
|
|
173
|
+
font: 600 12.5px system-ui, sans-serif;
|
|
174
|
+
cursor: pointer;
|
|
175
|
+
text-align: left;
|
|
176
|
+
}
|
|
177
|
+
.vx-button:hover { border-color: var(--vx-accent); }
|
|
178
|
+
.vx-button:active { transform: translateY(1px); }
|
|
179
|
+
.vx-index { position: absolute; inset: 0; overflow-y: auto; padding: 32px 40px 60px; }
|
|
180
|
+
.vx-index h1 { margin: 0 0 4px; font-size: 26px; }
|
|
181
|
+
.vx-index > p { margin: 0 0 22px; color: var(--vx-dim); max-width: 760px; }
|
|
182
|
+
.vx-index h2 { margin: 26px 0 2px; font-size: 16px; }
|
|
183
|
+
.vx-index h2 + p { margin: 0 0 10px; color: var(--vx-dim); }
|
|
184
|
+
.vx-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px; }
|
|
185
|
+
.vx-tile {
|
|
186
|
+
display: block;
|
|
187
|
+
padding: 12px;
|
|
188
|
+
border-radius: 8px;
|
|
189
|
+
border: 1px solid var(--vx-line);
|
|
190
|
+
background: var(--vx-panel);
|
|
191
|
+
color: inherit;
|
|
192
|
+
text-decoration: none;
|
|
193
|
+
}
|
|
194
|
+
.vx-tile:hover { border-color: var(--vx-accent); }
|
|
195
|
+
.vx-tile strong { display: block; margin-bottom: 3px; }
|
|
196
|
+
.vx-tile span { color: var(--vx-dim); font-size: 12px; }
|
|
197
|
+
@media (max-width: 760px) {
|
|
198
|
+
.vx-root { --vx-side: 220px; }
|
|
199
|
+
.vx-card { width: auto; left: 12px; top: 56px; max-height: 40%; }
|
|
200
|
+
.vx-index { padding: 20px 16px 40px; }
|
|
201
|
+
}
|
|
202
|
+
`
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How the game looks (`GameView`): the client's, fetched as the page first plays an example. The shared units' looks
|
|
3
|
+
* (`src/examples/common/looks.ts`), then what each category adds over them (its `looks.ts`); an example's own look and
|
|
4
|
+
* controls are laid over this as its scene opens (`src/shell/scene.ts`)
|
|
5
|
+
*/
|
|
6
|
+
import { type GameView, hotView, presetView } from '@v12-engine/client'
|
|
7
|
+
import { sharedLooks } from './examples/common/looks'
|
|
8
|
+
import { categoryLooks } from './examples/looks'
|
|
9
|
+
import { registry } from './shell/registry'
|
|
10
|
+
|
|
11
|
+
const shared: GameView = presetView({
|
|
12
|
+
world: {
|
|
13
|
+
// trees and scenery in front of the player's own units faded out
|
|
14
|
+
seeThrough: {},
|
|
15
|
+
// looks by name an example can play its map with (`ExampleDef.environment`)
|
|
16
|
+
environments: {
|
|
17
|
+
dusk: {
|
|
18
|
+
sky: [0.55, 0.42, 0.5],
|
|
19
|
+
clouds: { cover: 0.4, color: [1, 0.8, 0.7] },
|
|
20
|
+
light: { sunUp: 12, sunAround: 250, sunColor: [1, 0.62, 0.42], sun: 1, ambient: 0.45, ambientColor: [0.8, 0.7, 0.9] },
|
|
21
|
+
fog: { start: 60, end: 200 },
|
|
22
|
+
mist: { ground: { base: 0, top: 2, density: 0.25 } }
|
|
23
|
+
},
|
|
24
|
+
night: {
|
|
25
|
+
sky: [0.05, 0.07, 0.14],
|
|
26
|
+
light: { sunUp: 35, sunAround: 200, sunColor: [0.55, 0.65, 1], sun: 0.35, ambient: 0.25, ambientColor: [0.35, 0.4, 0.7] },
|
|
27
|
+
fog: { start: 40, end: 160 }
|
|
28
|
+
},
|
|
29
|
+
storm: {
|
|
30
|
+
sky: [0.3, 0.33, 0.38],
|
|
31
|
+
clouds: { cover: 0.9, color: [0.55, 0.57, 0.6] },
|
|
32
|
+
cloudLayer: { height: 18, cover: 0.7, opacity: 0.55, shadows: 0.5 },
|
|
33
|
+
light: { sun: 0.7, ambient: 0.5 },
|
|
34
|
+
wind: { around: 70, strength: 0.8, gusts: 0.6 },
|
|
35
|
+
weather: { kind: 'rain', amount: 0.9, splashes: 0.6 },
|
|
36
|
+
fog: { start: 40, end: 140 }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
units: { looks: sharedLooks }
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
/** The look every example starts from: the shared one, with each category's laid over it in the list's order */
|
|
44
|
+
export const view: GameView = categoryLooks.reduce((made, each) => each.view?.(made) ?? made, shared)
|
|
45
|
+
|
|
46
|
+
// each example's own look and controls, by its id, where its scene finds them as it opens
|
|
47
|
+
registry.looks = Object.assign({}, ...categoryLooks.map((each) => each.looks ?? {}))
|
|
48
|
+
|
|
49
|
+
// saved while it runs (npm run dev): the game and the editor open take its new look, with no new page
|
|
50
|
+
if (import.meta.hot) import.meta.hot.accept((module) => hotView(import.meta.hot, 'examples', view, module?.view))
|
package/starters/melee/README.md
CHANGED
|
@@ -22,12 +22,16 @@ A real-time strategy game made with V12, whole: melee. Two races, the humans and
|
|
|
22
22
|
<!-- @end -->
|
|
23
23
|
| Check the types | `npm run typecheck` |
|
|
24
24
|
| Build the page to host | `npm run build`, into `dist/` |
|
|
25
|
+
<!-- @if desktop -->
|
|
26
|
+
| Play it as an app | `npm run app`: the page in a window of its own (Electron, fetched the first time) |
|
|
27
|
+
| Make the app to hand out | `npm run package`, into `out/` |
|
|
28
|
+
<!-- @end -->
|
|
25
29
|
|
|
26
30
|
## What is where
|
|
27
31
|
|
|
28
|
-
- `src/melee/`: the game. `index.ts` puts the rules together (the engine's packs it turns on: stances, capture points,
|
|
29
|
-
- `src/
|
|
30
|
-
- `src/maps/`: the maps, made in the editor. A new one is imported in `src/maps.ts
|
|
32
|
+
- `src/melee/`: the game. `index.ts` puts the rules together (the engine's packs it turns on: stances, capture points, and the grass of `foliagePack`: tall grass that hides, dry grass that burns); `units.ts` the humans, `undead.ts` the Restless, `heroes.ts` the eight heroes (their callings, innates and abilities; the spells every hero brings two of are the engine's, `defaultHeroSpells`), `abilities.ts` and `items.ts` what they cast and carry; `blight.ts` the Restless' ground; `generator.ts` the random maps and `bay.ts` the Bay made in code; `maps.ts` what a melee map must have (a town hall for someone, and someone to play against); `view/` how it looks; `data.json`, `looks.json`, `hud.json`, `fx.json`, `floats.json`, `pings.json`, `quality.json`, `cellLayers.json` and `sounds.json` what the editor's panels write
|
|
33
|
+
- `src/game.ts`: the game (`createGame`) the page, the server, the editor and the tests take: melee's rules, its look fetched as the page first plays it, its maps (`src/maps.ts`). Its `onStart` seats the computer at every player but you
|
|
34
|
+
- `src/maps/`: the maps, made in the editor. A new one is imported in `src/maps.ts`. The Bay has a wind, tall grass on the road out of each corner and dry grass on the island, which burns
|
|
31
35
|
- `src/main.ts`: the page, its scenes made by `appScenes`
|
|
32
36
|
|
|
33
37
|
<!-- @if editor -->
|
|
@@ -56,6 +60,11 @@ A real-time strategy game made with V12, whole: melee. Two races, the humans and
|
|
|
56
60
|
|
|
57
61
|
- `tests/`: the rules tested with no page
|
|
58
62
|
|
|
63
|
+
<!-- @end -->
|
|
64
|
+
<!-- @if desktop -->
|
|
65
|
+
|
|
66
|
+
- `electron/`: the shell around the page, Electron's main process: `config.mjs` (the app's name, its identifier and the updater's address, yours to settle before it is handed out), `app.mjs` (the window, its full screen, what the page may ask), `preload.cjs` (the bridge the page asks through), `files.mjs` (saves, settings and the error log as files), `updates.mjs`, `presentation.m` (the addon that hides the Dock and the menu bar on macOS), `dev.mjs` (`npm run app`) and `package.mjs` (`npm run package`). `src/shell.ts` is all the page asks of it and `src/desktop.ts` what it does with that: over the whole screen with a Windowed setting, saves and settings as files, the game saved as the window closes, an error log, Exit on the start menu, Quit on the pause menu. The updater's address (`UPDATES`) is a placeholder until the docs' A desktop game, offline says what goes there
|
|
67
|
+
|
|
59
68
|
<!-- @end -->
|
|
60
69
|
|
|
61
70
|
Every model it plays with is of the shared art, `@v12-engine/assets` (whose each is: its group's `CREDITS.md` in `node_modules/@v12-engine/assets/groups/`)
|