incanto 0.32.0 → 0.33.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/dist/2d.d.ts +59 -2
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +8 -3
- package/dist/3d.js +4 -4
- package/dist/{behavior-D_jMpFh8.d.ts → behavior-CKwTCjfR.d.ts} +140 -1
- package/dist/{create-game-bKHgHcsZ.js → create-game-D2QU5x7S.js} +13 -6
- package/dist/{create-game-gXI7PYl0.js → create-game-sFuTLqjD.js} +11 -5
- package/dist/debug.d.ts +1 -1
- package/dist/{duplicate-KRPtUtzl.js → duplicate-BgnG1Lqz.js} +1 -1
- package/dist/editor.js +35 -0
- package/dist/{environment-presets--DigHNg4.js → environment-presets-CQtEGogB.js} +2 -2
- package/dist/{gameplay-BftxM_It.js → gameplay-BpQCbABv.js} +90 -2
- package/dist/gameplay.d.ts +1 -1
- package/dist/gameplay.js +1 -1
- package/dist/index.d.ts +125 -23
- package/dist/index.js +5 -58
- package/dist/{loader-BlaRQGaA.js → loader-Buk8Bu1h.js} +53 -0
- package/dist/{loader-BYBrqTxP.d.ts → loader-COn5fS0o.d.ts} +1 -1
- package/dist/net.d.ts +1 -1
- package/dist/net.js +3 -3
- package/dist/{pathfinding-BwhqPD3i.d.ts → pathfinding-DUw9mir9.d.ts} +1 -1
- package/dist/{physics-2d-D9wquBvK.js → physics-2d-DjXR5DMu.js} +12 -2
- package/dist/{physics-3d-CnPygVGo.js → physics-3d-DF8npb1O.js} +55 -9
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-Dzkd6-os.js → register-CscIzJEO.js} +551 -13
- package/dist/{register-CUY284Is.js → register-CtI-itec.js} +2 -2
- package/dist/{register-tkR_8tWg.js → register-FIJtNbub.js} +145 -8
- package/dist/{test-WwRIlXsK.js → test-BRxLd2jH.js} +10 -10
- package/dist/test.d.ts +2 -2
- package/dist/test.js +1 -1
- package/dist/vite.js +1 -1
- package/editor/assets/{agent8-C5k1nTCH.js → agent8-BdDP3xKW.js} +1 -1
- package/editor/assets/{debug-BT_0mjk-.js → debug-DbjTyTlC.js} +1 -1
- package/editor/assets/{index-gfyrByWw.js → index-BWCudoz1.js} +90 -90
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +520 -0
- package/skills/incanto-audio.md +43 -0
- package/skills/incanto-building-2d-games.md +36 -0
- package/skills/incanto-hud.md +41 -0
- package/skills/incanto-node-reference.md +89 -0
- package/skills/incanto-physics-and-input.md +50 -0
- package/templates-app/beacon-isle-3d/docs/project-3d-rules.md +40 -19
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/tps-3d/docs/project-3d-rules.md +41 -19
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/village-quest-3d/docs/project-3d-rules.md +40 -19
- package/templates-app/village-quest-3d/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { f as Node, h as InputMap, x as Signal } from "./loader-
|
|
1
|
+
import { f as Node, h as InputMap, x as Signal } from "./loader-Buk8Bu1h.js";
|
|
2
2
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
3
3
|
import { t as Rng } from "./rng-DP-SR7eg.js";
|
|
4
4
|
import { l as registerNode } from "./registry-CJdGpT2V.js";
|
|
@@ -28,19 +28,19 @@ var AudioBuses = class {
|
|
|
28
28
|
return this._master;
|
|
29
29
|
}
|
|
30
30
|
set master(v) {
|
|
31
|
-
this.
|
|
31
|
+
this.assign("_master", v);
|
|
32
32
|
}
|
|
33
33
|
get sfx() {
|
|
34
34
|
return this._sfx;
|
|
35
35
|
}
|
|
36
36
|
set sfx(v) {
|
|
37
|
-
this.
|
|
37
|
+
this.assign("_sfx", v);
|
|
38
38
|
}
|
|
39
39
|
get music() {
|
|
40
40
|
return this._music;
|
|
41
41
|
}
|
|
42
42
|
set music(v) {
|
|
43
|
-
this.
|
|
43
|
+
this.assign("_music", v);
|
|
44
44
|
}
|
|
45
45
|
get muted() {
|
|
46
46
|
return this._muted;
|
|
@@ -57,11 +57,20 @@ var AudioBuses = class {
|
|
|
57
57
|
return this._master * busGain * clamp01$3(sourceVolume);
|
|
58
58
|
}
|
|
59
59
|
/** Clamp + finite-guard a new value; emit only on a real change. */
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Write THEN emit.
|
|
62
|
+
*
|
|
63
|
+
* This used to emit from inside the expression assigning the field, so every
|
|
64
|
+
* listener of `changed` read the OLD value — including the thing the signal
|
|
65
|
+
* exists for ("live re-gain of playing sounds"), which therefore re-gained to
|
|
66
|
+
* the volume you just left.
|
|
67
|
+
*/
|
|
68
|
+
assign(key, next) {
|
|
69
|
+
if (!Number.isFinite(next)) return;
|
|
62
70
|
const clamped = clamp01$3(next);
|
|
63
|
-
if (clamped
|
|
64
|
-
|
|
71
|
+
if (clamped === this[key]) return;
|
|
72
|
+
this[key] = clamped;
|
|
73
|
+
this.changed.emit();
|
|
65
74
|
}
|
|
66
75
|
};
|
|
67
76
|
function clamp01$3(v) {
|
|
@@ -1009,6 +1018,196 @@ var LogManager = class {
|
|
|
1009
1018
|
}
|
|
1010
1019
|
};
|
|
1011
1020
|
//#endregion
|
|
1021
|
+
//#region src/core/save.ts
|
|
1022
|
+
const memoryStores = /* @__PURE__ */ new Map();
|
|
1023
|
+
function createSaveStore(namespace) {
|
|
1024
|
+
const prefix = `incanto:${namespace}:`;
|
|
1025
|
+
const local = storageOrNull();
|
|
1026
|
+
if (!local) {
|
|
1027
|
+
let mem = memoryStores.get(namespace);
|
|
1028
|
+
if (!mem) {
|
|
1029
|
+
mem = /* @__PURE__ */ new Map();
|
|
1030
|
+
memoryStores.set(namespace, mem);
|
|
1031
|
+
}
|
|
1032
|
+
const backing = mem;
|
|
1033
|
+
return {
|
|
1034
|
+
get: (key, fallback) => parse(backing.get(key), fallback),
|
|
1035
|
+
set: (key, value) => void backing.set(key, JSON.stringify(value)),
|
|
1036
|
+
remove: (key) => void backing.delete(key),
|
|
1037
|
+
clear: () => backing.clear()
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
return {
|
|
1041
|
+
get: (key, fallback) => parse(local.getItem(prefix + key) ?? void 0, fallback),
|
|
1042
|
+
set: (key, value) => local.setItem(prefix + key, JSON.stringify(value)),
|
|
1043
|
+
remove: (key) => local.removeItem(prefix + key),
|
|
1044
|
+
clear: () => {
|
|
1045
|
+
const doomed = [];
|
|
1046
|
+
for (let i = 0; i < local.length; i++) {
|
|
1047
|
+
const k = local.key(i);
|
|
1048
|
+
if (k?.startsWith(prefix)) doomed.push(k);
|
|
1049
|
+
}
|
|
1050
|
+
for (const k of doomed) local.removeItem(k);
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
function parse(raw, fallback) {
|
|
1055
|
+
if (raw === void 0 || raw === null) return fallback;
|
|
1056
|
+
try {
|
|
1057
|
+
return JSON.parse(raw);
|
|
1058
|
+
} catch {
|
|
1059
|
+
return fallback;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
function storageOrNull() {
|
|
1063
|
+
try {
|
|
1064
|
+
if (typeof localStorage === "undefined") return null;
|
|
1065
|
+
const probe = "__incanto_probe__";
|
|
1066
|
+
localStorage.setItem(probe, "1");
|
|
1067
|
+
localStorage.removeItem(probe);
|
|
1068
|
+
return localStorage;
|
|
1069
|
+
} catch {
|
|
1070
|
+
return null;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
//#endregion
|
|
1074
|
+
//#region src/core/settings.ts
|
|
1075
|
+
const DEFAULTS = {
|
|
1076
|
+
master: 1,
|
|
1077
|
+
sfx: 1,
|
|
1078
|
+
music: 1,
|
|
1079
|
+
muted: false,
|
|
1080
|
+
quality: "high",
|
|
1081
|
+
sensitivity: 1,
|
|
1082
|
+
invertY: false,
|
|
1083
|
+
reduceMotion: false
|
|
1084
|
+
};
|
|
1085
|
+
const TIERS = [
|
|
1086
|
+
"low",
|
|
1087
|
+
"medium",
|
|
1088
|
+
"high"
|
|
1089
|
+
];
|
|
1090
|
+
/**
|
|
1091
|
+
* A default tier from what the browser will admit to.
|
|
1092
|
+
*
|
|
1093
|
+
* Deliberately crude: this picks a STARTING point, and the options menu is the
|
|
1094
|
+
* real answer. A phone that reports four cores and a coarse pointer should not
|
|
1095
|
+
* open on the setting tuned for a desktop GPU.
|
|
1096
|
+
*/
|
|
1097
|
+
function suggestQuality(hints) {
|
|
1098
|
+
const cores = hints.hardwareConcurrency ?? 8;
|
|
1099
|
+
const memory = hints.deviceMemory ?? 8;
|
|
1100
|
+
if (hints.coarsePointer && (cores <= 6 || memory <= 4)) return "low";
|
|
1101
|
+
if (cores <= 4 || memory <= 4 || (hints.maxTextureSize ?? 8192) < 8192) return "low";
|
|
1102
|
+
if (cores <= 8 || memory <= 8) return "medium";
|
|
1103
|
+
return "high";
|
|
1104
|
+
}
|
|
1105
|
+
/** Read what this environment will tell us, safely, without touching WebGL. */
|
|
1106
|
+
function readDeviceHints() {
|
|
1107
|
+
const nav = globalThis.navigator;
|
|
1108
|
+
const mm = globalThis.matchMedia;
|
|
1109
|
+
const out = {};
|
|
1110
|
+
if (typeof nav?.hardwareConcurrency === "number") out.hardwareConcurrency = nav.hardwareConcurrency;
|
|
1111
|
+
if (typeof nav?.deviceMemory === "number") out.deviceMemory = nav.deviceMemory;
|
|
1112
|
+
try {
|
|
1113
|
+
if (mm) out.coarsePointer = mm("(pointer: coarse)").matches;
|
|
1114
|
+
} catch {}
|
|
1115
|
+
return out;
|
|
1116
|
+
}
|
|
1117
|
+
var Settings = class {
|
|
1118
|
+
/** Fires after any change, with the key that changed. */
|
|
1119
|
+
changed = new Signal();
|
|
1120
|
+
store;
|
|
1121
|
+
values;
|
|
1122
|
+
constructor(namespace = "settings", store) {
|
|
1123
|
+
this.store = store ?? createSaveStore(namespace);
|
|
1124
|
+
this.values = { ...DEFAULTS };
|
|
1125
|
+
for (const key of Object.keys(DEFAULTS)) {
|
|
1126
|
+
const saved = this.store.get(key, DEFAULTS[key]);
|
|
1127
|
+
this.values[key] = saved;
|
|
1128
|
+
}
|
|
1129
|
+
if (!TIERS.includes(this.values.quality)) this.values.quality = DEFAULTS.quality;
|
|
1130
|
+
}
|
|
1131
|
+
get(key) {
|
|
1132
|
+
return this.values[key];
|
|
1133
|
+
}
|
|
1134
|
+
set(key, value) {
|
|
1135
|
+
if (this.values[key] === value) return;
|
|
1136
|
+
this.values[key] = value;
|
|
1137
|
+
this.store.set(key, value);
|
|
1138
|
+
this.changed.emit(key);
|
|
1139
|
+
}
|
|
1140
|
+
/** Every value at once — for an options screen, or a save-file dump. */
|
|
1141
|
+
all() {
|
|
1142
|
+
return { ...this.values };
|
|
1143
|
+
}
|
|
1144
|
+
/** Back to defaults, on disk too. */
|
|
1145
|
+
reset() {
|
|
1146
|
+
for (const key of Object.keys(DEFAULTS)) this.set(key, DEFAULTS[key]);
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Push the saved volumes into the engine's buses, and keep writing them back
|
|
1150
|
+
* whenever anything changes them.
|
|
1151
|
+
*
|
|
1152
|
+
* The subscription is the point: a game that sets `engine.audio.music = 0.4`
|
|
1153
|
+
* from a slider has already persisted it, without knowing this class exists.
|
|
1154
|
+
*/
|
|
1155
|
+
bindAudio(audio) {
|
|
1156
|
+
audio.master = this.values.master;
|
|
1157
|
+
audio.sfx = this.values.sfx;
|
|
1158
|
+
audio.music = this.values.music;
|
|
1159
|
+
audio.muted = this.values.muted;
|
|
1160
|
+
return audio.changed.connect(() => {
|
|
1161
|
+
this.set("master", audio.master);
|
|
1162
|
+
this.set("sfx", audio.sfx);
|
|
1163
|
+
this.set("music", audio.music);
|
|
1164
|
+
this.set("muted", audio.muted);
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
};
|
|
1168
|
+
/**
|
|
1169
|
+
* What a quality tier MEANS, as an environment patch.
|
|
1170
|
+
*
|
|
1171
|
+
* Only the levers that actually cost: shadows, post, clouds, and the resolution
|
|
1172
|
+
* the renderer draws at. Deliberately an `environment` patch, because
|
|
1173
|
+
* `setEnvironment3D(engine, patch)` already applies one live and validated —
|
|
1174
|
+
* this is not a second rendering pipeline, it is three presets for the one that
|
|
1175
|
+
* exists.
|
|
1176
|
+
*/
|
|
1177
|
+
function qualityEnvironment(tier) {
|
|
1178
|
+
if (tier === "low") return {
|
|
1179
|
+
shadows: { enabled: false },
|
|
1180
|
+
bloom: { enabled: false },
|
|
1181
|
+
post: { enabled: false },
|
|
1182
|
+
clouds: { enabled: false },
|
|
1183
|
+
rendering: {
|
|
1184
|
+
antialias: false,
|
|
1185
|
+
pixelRatio: 1
|
|
1186
|
+
}
|
|
1187
|
+
};
|
|
1188
|
+
if (tier === "medium") return {
|
|
1189
|
+
shadows: {
|
|
1190
|
+
enabled: true,
|
|
1191
|
+
static: true
|
|
1192
|
+
},
|
|
1193
|
+
bloom: { enabled: true },
|
|
1194
|
+
post: { enabled: false },
|
|
1195
|
+
rendering: {
|
|
1196
|
+
antialias: true,
|
|
1197
|
+
pixelRatio: 1
|
|
1198
|
+
}
|
|
1199
|
+
};
|
|
1200
|
+
return {
|
|
1201
|
+
shadows: { enabled: true },
|
|
1202
|
+
bloom: { enabled: true },
|
|
1203
|
+
post: { enabled: true },
|
|
1204
|
+
rendering: {
|
|
1205
|
+
antialias: true,
|
|
1206
|
+
pixelRatio: "device"
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
//#endregion
|
|
1012
1211
|
//#region src/core/engine.ts
|
|
1013
1212
|
const MAX_DT_SECONDS = .25;
|
|
1014
1213
|
function rafScheduler(cb) {
|
|
@@ -1047,9 +1246,27 @@ var Engine = class {
|
|
|
1047
1246
|
rng;
|
|
1048
1247
|
/** The engine log channel (debug overlay + headless harness tail this). */
|
|
1049
1248
|
log = new LogManager();
|
|
1249
|
+
/**
|
|
1250
|
+
* Screen pixel → the node under it, when a renderer has offered one.
|
|
1251
|
+
*
|
|
1252
|
+
* The raycast has always existed (`Renderer3D.pick` / `Renderer2D.pick`) and
|
|
1253
|
+
* was reachable only from whoever held the renderer — so a Behavior, which
|
|
1254
|
+
* holds a node, could not use it. Every mouse-driven genre needs exactly this
|
|
1255
|
+
* and had no way to ask. Instance-scoped, set by the renderer on construction
|
|
1256
|
+
* and cleared on dispose; null when nothing renders (headless).
|
|
1257
|
+
*/
|
|
1258
|
+
picker = null;
|
|
1050
1259
|
/** Global volume buses — `master` × `sfx`/`music` gain + `muted`. AudioPlayer
|
|
1051
1260
|
* routes its volume through this; games set it for global volume/mute. */
|
|
1052
1261
|
audio = new AudioBuses();
|
|
1262
|
+
/**
|
|
1263
|
+
* Persisted player settings — volume, quality, sensitivity.
|
|
1264
|
+
*
|
|
1265
|
+
* `createSaveStore` was good and had zero callers, so every game's volume
|
|
1266
|
+
* reset on reload. Reading it here costs nothing (localStorage, once) and
|
|
1267
|
+
* `bindAudio` makes the volumes write themselves.
|
|
1268
|
+
*/
|
|
1269
|
+
settings = new Settings();
|
|
1053
1270
|
/** Low-latency procedural-SFX player (WebAudio, headless-safe). AudioPlayer
|
|
1054
1271
|
* presets play through it; games may call `engine.sfx.play(...)` directly. */
|
|
1055
1272
|
sfx = new SfxEngine();
|
|
@@ -1597,17 +1814,42 @@ var HudWidgetBase = class extends Node {
|
|
|
1597
1814
|
}
|
|
1598
1815
|
onReady() {
|
|
1599
1816
|
if (!hasDom()) return;
|
|
1600
|
-
|
|
1601
|
-
|
|
1817
|
+
this._mountWidget();
|
|
1818
|
+
}
|
|
1819
|
+
/**
|
|
1820
|
+
* @internal Build and attach, once a host exists.
|
|
1821
|
+
*
|
|
1822
|
+
* A widget inside a UiPanel mounts into the PANEL, not the layer's anchor
|
|
1823
|
+
* slot — that is what turns structure in the tree into structure on screen,
|
|
1824
|
+
* and it is what a menu is. Ready order does not decide it: the panel builds
|
|
1825
|
+
* on demand when a child asks (`_container`), and `update` retries for
|
|
1826
|
+
* anything attached at runtime before its host existed.
|
|
1827
|
+
*/
|
|
1828
|
+
_mountWidget() {
|
|
1829
|
+
if (this._element || !hasDom()) return;
|
|
1830
|
+
const host = this.host() ?? this.layer()?._slot(this.anchor);
|
|
1831
|
+
if (!host) return;
|
|
1602
1832
|
this._element = this._build();
|
|
1603
|
-
|
|
1833
|
+
host.appendChild(this._element);
|
|
1834
|
+
}
|
|
1835
|
+
/** The nearest ancestor that holds widgets itself (a UiPanel). */
|
|
1836
|
+
host() {
|
|
1837
|
+
for (let p = this.parent; p; p = p.parent) {
|
|
1838
|
+
const holder = p;
|
|
1839
|
+
if (typeof holder._container === "function") return holder._container();
|
|
1840
|
+
if (p instanceof HudLayer) return null;
|
|
1841
|
+
}
|
|
1842
|
+
return null;
|
|
1604
1843
|
}
|
|
1605
1844
|
onExitTree() {
|
|
1606
1845
|
this._element?.remove();
|
|
1607
1846
|
this._element = null;
|
|
1608
1847
|
}
|
|
1609
1848
|
update(_dt) {
|
|
1610
|
-
if (!this._element)
|
|
1849
|
+
if (!this._element) {
|
|
1850
|
+
this._mountWidget();
|
|
1851
|
+
if (!this._element) return;
|
|
1852
|
+
}
|
|
1611
1853
|
this._element.style.display = this.visible ? "" : "none";
|
|
1612
1854
|
this._sync();
|
|
1613
1855
|
}
|
|
@@ -2028,6 +2270,297 @@ var Timer = class extends Node {
|
|
|
2028
2270
|
}
|
|
2029
2271
|
};
|
|
2030
2272
|
//#endregion
|
|
2273
|
+
//#region src/core/nodes/ui-kit.ts
|
|
2274
|
+
function px(n) {
|
|
2275
|
+
return `${n}px`;
|
|
2276
|
+
}
|
|
2277
|
+
/**
|
|
2278
|
+
* A box that holds other widgets — the thing a menu actually is.
|
|
2279
|
+
*
|
|
2280
|
+
* Widgets normally mount into their HudLayer's anchor slot, which is why a
|
|
2281
|
+
* "menu" used to be a flat pile of anchored items with no way to sit them in a
|
|
2282
|
+
* panel, space them, or lay them out in a grid. A `UiPanel` becomes the parent
|
|
2283
|
+
* for every widget under it, so structure in the TREE is structure on SCREEN.
|
|
2284
|
+
*
|
|
2285
|
+
* ```json
|
|
2286
|
+
* { "name": "Pause", "type": "UiPanel", "props": { "anchor": "center", "layout": "column", "gap": 12 },
|
|
2287
|
+
* "children": [
|
|
2288
|
+
* { "name": "Title", "type": "UiText", "props": { "text": "PAUSED", "size": 28 } },
|
|
2289
|
+
* { "name": "Resume", "type": "UiButton", "props": { "label": "Resume" } }
|
|
2290
|
+
* ] }
|
|
2291
|
+
* ```
|
|
2292
|
+
*/
|
|
2293
|
+
var UiPanel = class extends HudWidgetBase {
|
|
2294
|
+
static typeName = "UiPanel";
|
|
2295
|
+
static props = {
|
|
2296
|
+
...HudWidgetBase.props,
|
|
2297
|
+
layout: {
|
|
2298
|
+
default: "column",
|
|
2299
|
+
options: [
|
|
2300
|
+
"column",
|
|
2301
|
+
"row",
|
|
2302
|
+
"grid"
|
|
2303
|
+
]
|
|
2304
|
+
},
|
|
2305
|
+
/** Columns when `layout: "grid"` — an inventory is a grid of slots. */
|
|
2306
|
+
columns: { default: 4 },
|
|
2307
|
+
gap: { default: 8 },
|
|
2308
|
+
padding: { default: 12 },
|
|
2309
|
+
background: { default: "rgba(0,0,0,0.55)" },
|
|
2310
|
+
radius: { default: 10 },
|
|
2311
|
+
/** 0 = size to the contents. */
|
|
2312
|
+
width: { default: 0 },
|
|
2313
|
+
height: { default: 0 },
|
|
2314
|
+
border: { default: "" }
|
|
2315
|
+
};
|
|
2316
|
+
layout = "column";
|
|
2317
|
+
columns = 4;
|
|
2318
|
+
gap = 8;
|
|
2319
|
+
padding = 12;
|
|
2320
|
+
background = "rgba(0,0,0,0.55)";
|
|
2321
|
+
radius = 10;
|
|
2322
|
+
width = 0;
|
|
2323
|
+
height = 0;
|
|
2324
|
+
border = "";
|
|
2325
|
+
lastKey = "";
|
|
2326
|
+
_build() {
|
|
2327
|
+
const el = document.createElement("div");
|
|
2328
|
+
el.style.cssText = this.css();
|
|
2329
|
+
this.lastKey = this.css();
|
|
2330
|
+
return el;
|
|
2331
|
+
}
|
|
2332
|
+
/**
|
|
2333
|
+
* @internal Children mount HERE, not in the layer's anchor slot.
|
|
2334
|
+
*
|
|
2335
|
+
* Builds on demand: a child asking for its host must not depend on whose
|
|
2336
|
+
* `onReady` the tree happened to run first.
|
|
2337
|
+
*/
|
|
2338
|
+
_container() {
|
|
2339
|
+
if (!this._element) this._mountWidget();
|
|
2340
|
+
return this._element;
|
|
2341
|
+
}
|
|
2342
|
+
css() {
|
|
2343
|
+
return `${this.layout === "grid" ? `display:grid;grid-template-columns:repeat(${Math.max(1, this.columns)},auto);` : `display:flex;flex-direction:${this.layout};align-items:stretch;`}gap:${px(this.gap)};padding:${px(this.padding)};background:${this.background};border-radius:${px(this.radius)};` + (this.width > 0 ? `width:${px(this.width)};` : "") + (this.height > 0 ? `height:${px(this.height)};` : "") + (this.border ? `border:${this.border};` : "") + "box-sizing:border-box;pointer-events:auto;";
|
|
2344
|
+
}
|
|
2345
|
+
_sync() {
|
|
2346
|
+
const next = this.css();
|
|
2347
|
+
if (next !== this.lastKey && this._element) {
|
|
2348
|
+
this.lastKey = next;
|
|
2349
|
+
this._element.style.cssText = next;
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
};
|
|
2353
|
+
/** A picture: an item icon, a portrait, a logo. `src` takes a url or `$assetKey`. */
|
|
2354
|
+
var UiImage = class extends HudWidgetBase {
|
|
2355
|
+
static typeName = "UiImage";
|
|
2356
|
+
static props = {
|
|
2357
|
+
...HudWidgetBase.props,
|
|
2358
|
+
src: { default: "" },
|
|
2359
|
+
width: { default: 48 },
|
|
2360
|
+
height: { default: 48 },
|
|
2361
|
+
/** How the picture fills its box — `contain` never crops. */
|
|
2362
|
+
fit: {
|
|
2363
|
+
default: "contain",
|
|
2364
|
+
options: [
|
|
2365
|
+
"contain",
|
|
2366
|
+
"cover",
|
|
2367
|
+
"fill"
|
|
2368
|
+
]
|
|
2369
|
+
},
|
|
2370
|
+
/** Multiply the image by a colour: greyed-out items, team tints. */
|
|
2371
|
+
tint: { default: "" },
|
|
2372
|
+
opacity: { default: 1 }
|
|
2373
|
+
};
|
|
2374
|
+
src = "";
|
|
2375
|
+
width = 48;
|
|
2376
|
+
height = 48;
|
|
2377
|
+
fit = "contain";
|
|
2378
|
+
tint = "";
|
|
2379
|
+
opacity = 1;
|
|
2380
|
+
last = "\0";
|
|
2381
|
+
_build() {
|
|
2382
|
+
const el = document.createElement("div");
|
|
2383
|
+
el.style.cssText = this.css();
|
|
2384
|
+
this.last = this.key();
|
|
2385
|
+
return el;
|
|
2386
|
+
}
|
|
2387
|
+
resolved() {
|
|
2388
|
+
if (!this.src.startsWith("$")) return this.src;
|
|
2389
|
+
return (this.tree?.engine?.scene?.assets)?.[this.src.slice(1)]?.url ?? "";
|
|
2390
|
+
}
|
|
2391
|
+
key() {
|
|
2392
|
+
return `${this.resolved()}|${this.width}|${this.height}|${this.fit}|${this.tint}|${this.opacity}`;
|
|
2393
|
+
}
|
|
2394
|
+
css() {
|
|
2395
|
+
const url = this.resolved();
|
|
2396
|
+
return `width:${px(this.width)};height:${px(this.height)};flex:none;` + (url ? `background-image:url("${url}");` : "") + `background-size:${this.fit === "fill" ? "100% 100%" : this.fit};background-position:center;background-repeat:no-repeat;opacity:${this.opacity};` + (this.tint ? `background-color:${this.tint};background-blend-mode:multiply;` : "");
|
|
2397
|
+
}
|
|
2398
|
+
_sync() {
|
|
2399
|
+
const next = this.key();
|
|
2400
|
+
if (next !== this.last && this._element) {
|
|
2401
|
+
this.last = next;
|
|
2402
|
+
this._element.style.cssText = this.css();
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
};
|
|
2406
|
+
/**
|
|
2407
|
+
* A value the player sets — volume, sensitivity, difficulty.
|
|
2408
|
+
*
|
|
2409
|
+
* Emits `changed(value)`. `incanto-audio.md` promised "a settings slider needs
|
|
2410
|
+
* no extra wiring" while no slider existed; this is that slider.
|
|
2411
|
+
*/
|
|
2412
|
+
var UiSlider = class extends HudWidgetBase {
|
|
2413
|
+
static typeName = "UiSlider";
|
|
2414
|
+
static props = {
|
|
2415
|
+
...HudWidgetBase.props,
|
|
2416
|
+
label: { default: "" },
|
|
2417
|
+
value: { default: .5 },
|
|
2418
|
+
min: { default: 0 },
|
|
2419
|
+
max: { default: 1 },
|
|
2420
|
+
step: { default: .01 },
|
|
2421
|
+
width: { default: 180 },
|
|
2422
|
+
color: { default: "#6ee7dc" }
|
|
2423
|
+
};
|
|
2424
|
+
static signals = ["changed"];
|
|
2425
|
+
label = "";
|
|
2426
|
+
value = .5;
|
|
2427
|
+
min = 0;
|
|
2428
|
+
max = 1;
|
|
2429
|
+
step = .01;
|
|
2430
|
+
width = 180;
|
|
2431
|
+
color = "#6ee7dc";
|
|
2432
|
+
input = null;
|
|
2433
|
+
text = null;
|
|
2434
|
+
last = NaN;
|
|
2435
|
+
_build() {
|
|
2436
|
+
const wrap = document.createElement("div");
|
|
2437
|
+
wrap.style.cssText = "display:flex;align-items:center;gap:8px;pointer-events:auto;font:600 13px system-ui,-apple-system,'Segoe UI',sans-serif;color:#fff;";
|
|
2438
|
+
const text = document.createElement("span");
|
|
2439
|
+
text.textContent = this.label;
|
|
2440
|
+
const input = document.createElement("input");
|
|
2441
|
+
input.type = "range";
|
|
2442
|
+
input.min = String(this.min);
|
|
2443
|
+
input.max = String(this.max);
|
|
2444
|
+
input.step = String(this.step);
|
|
2445
|
+
input.value = String(this.value);
|
|
2446
|
+
input.style.cssText = `width:${px(this.width)};accent-color:${this.color};`;
|
|
2447
|
+
input.addEventListener("input", () => {
|
|
2448
|
+
this.value = Number(input.value);
|
|
2449
|
+
this.last = this.value;
|
|
2450
|
+
this.emit("changed", this.value);
|
|
2451
|
+
});
|
|
2452
|
+
if (this.label) wrap.appendChild(text);
|
|
2453
|
+
wrap.appendChild(input);
|
|
2454
|
+
this.input = input;
|
|
2455
|
+
this.text = text;
|
|
2456
|
+
this.last = this.value;
|
|
2457
|
+
return wrap;
|
|
2458
|
+
}
|
|
2459
|
+
_sync() {
|
|
2460
|
+
if (this.input && this.value !== this.last) {
|
|
2461
|
+
this.last = this.value;
|
|
2462
|
+
this.input.value = String(this.value);
|
|
2463
|
+
}
|
|
2464
|
+
if (this.text && this.text.textContent !== this.label) this.text.textContent = this.label;
|
|
2465
|
+
}
|
|
2466
|
+
};
|
|
2467
|
+
/** An on/off switch — mute, invert-Y, fullscreen. Emits `changed(boolean)`. */
|
|
2468
|
+
var UiToggle = class extends HudWidgetBase {
|
|
2469
|
+
static typeName = "UiToggle";
|
|
2470
|
+
static props = {
|
|
2471
|
+
...HudWidgetBase.props,
|
|
2472
|
+
label: { default: "" },
|
|
2473
|
+
value: { default: false }
|
|
2474
|
+
};
|
|
2475
|
+
static signals = ["changed"];
|
|
2476
|
+
label = "";
|
|
2477
|
+
value = false;
|
|
2478
|
+
input = null;
|
|
2479
|
+
last = null;
|
|
2480
|
+
_build() {
|
|
2481
|
+
const wrap = document.createElement("label");
|
|
2482
|
+
wrap.style.cssText = "display:flex;align-items:center;gap:8px;pointer-events:auto;cursor:pointer;font:600 13px system-ui,-apple-system,'Segoe UI',sans-serif;color:#fff;";
|
|
2483
|
+
const input = document.createElement("input");
|
|
2484
|
+
input.type = "checkbox";
|
|
2485
|
+
input.checked = this.value;
|
|
2486
|
+
input.addEventListener("change", () => {
|
|
2487
|
+
this.value = input.checked;
|
|
2488
|
+
this.last = this.value;
|
|
2489
|
+
this.emit("changed", this.value);
|
|
2490
|
+
});
|
|
2491
|
+
const text = document.createElement("span");
|
|
2492
|
+
text.textContent = this.label;
|
|
2493
|
+
wrap.append(input, text);
|
|
2494
|
+
this.input = input;
|
|
2495
|
+
this.last = this.value;
|
|
2496
|
+
return wrap;
|
|
2497
|
+
}
|
|
2498
|
+
_sync() {
|
|
2499
|
+
if (this.input && this.value !== this.last) {
|
|
2500
|
+
this.last = this.value;
|
|
2501
|
+
this.input.checked = this.value;
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
};
|
|
2505
|
+
/** One of several — quality, resolution, language. Emits `changed(value)`. */
|
|
2506
|
+
var UiSelect = class extends HudWidgetBase {
|
|
2507
|
+
static typeName = "UiSelect";
|
|
2508
|
+
static props = {
|
|
2509
|
+
...HudWidgetBase.props,
|
|
2510
|
+
label: { default: "" },
|
|
2511
|
+
/** Comma-separated choices: `"low,medium,high"`. */
|
|
2512
|
+
options: { default: "" },
|
|
2513
|
+
value: { default: "" }
|
|
2514
|
+
};
|
|
2515
|
+
static signals = ["changed"];
|
|
2516
|
+
label = "";
|
|
2517
|
+
options = "";
|
|
2518
|
+
value = "";
|
|
2519
|
+
select = null;
|
|
2520
|
+
lastOptions = "\0";
|
|
2521
|
+
last = "\0";
|
|
2522
|
+
_build() {
|
|
2523
|
+
const wrap = document.createElement("label");
|
|
2524
|
+
wrap.style.cssText = "display:flex;align-items:center;gap:8px;pointer-events:auto;font:600 13px system-ui,-apple-system,'Segoe UI',sans-serif;color:#fff;";
|
|
2525
|
+
const text = document.createElement("span");
|
|
2526
|
+
text.textContent = this.label;
|
|
2527
|
+
const select = document.createElement("select");
|
|
2528
|
+
select.addEventListener("change", () => {
|
|
2529
|
+
this.value = select.value;
|
|
2530
|
+
this.last = this.value;
|
|
2531
|
+
this.emit("changed", this.value);
|
|
2532
|
+
});
|
|
2533
|
+
if (this.label) wrap.appendChild(text);
|
|
2534
|
+
wrap.appendChild(select);
|
|
2535
|
+
this.select = select;
|
|
2536
|
+
this.fillOptions();
|
|
2537
|
+
return wrap;
|
|
2538
|
+
}
|
|
2539
|
+
fillOptions() {
|
|
2540
|
+
const select = this.select;
|
|
2541
|
+
if (!select || this.options === this.lastOptions) return;
|
|
2542
|
+
this.lastOptions = this.options;
|
|
2543
|
+
select.textContent = "";
|
|
2544
|
+
for (const raw of this.options.split(",")) {
|
|
2545
|
+
const value = raw.trim();
|
|
2546
|
+
if (!value) continue;
|
|
2547
|
+
const option = document.createElement("option");
|
|
2548
|
+
option.value = value;
|
|
2549
|
+
option.textContent = value;
|
|
2550
|
+
select.appendChild(option);
|
|
2551
|
+
}
|
|
2552
|
+
if (this.value) select.value = this.value;
|
|
2553
|
+
this.last = this.value;
|
|
2554
|
+
}
|
|
2555
|
+
_sync() {
|
|
2556
|
+
this.fillOptions();
|
|
2557
|
+
if (this.select && this.value !== this.last) {
|
|
2558
|
+
this.last = this.value;
|
|
2559
|
+
this.select.value = this.value;
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
};
|
|
2563
|
+
//#endregion
|
|
2031
2564
|
//#region src/core/register.ts
|
|
2032
2565
|
/**
|
|
2033
2566
|
* Register the core node types. Call once in your game entry before loading
|
|
@@ -2044,6 +2577,11 @@ function registerCoreNodes() {
|
|
|
2044
2577
|
registerNode(UiBanner);
|
|
2045
2578
|
registerNode(UiButton);
|
|
2046
2579
|
registerNode(UiDialogue);
|
|
2580
|
+
registerNode(UiPanel);
|
|
2581
|
+
registerNode(UiImage);
|
|
2582
|
+
registerNode(UiSlider);
|
|
2583
|
+
registerNode(UiToggle);
|
|
2584
|
+
registerNode(UiSelect);
|
|
2047
2585
|
}
|
|
2048
2586
|
//#endregion
|
|
2049
|
-
export {
|
|
2587
|
+
export { MusicManager as A, isAudioContextAvailable as C, SFX_PRESETS as D, spatialPan as E, crossfadeGains as M, fadeGain as N, SFX_PRESET_NAMES as O, AudioBuses as P, SfxEngine as S, spatialGain as T, qualityEnvironment as _, UiSlider as a, createSaveStore as b, UiButton as c, UiBanner as d, UiBar as f, Settings as g, Engine as h, UiSelect as i, WebAudioMusicBackend as j, synthSfx as k, UiDialogue as l, AudioPlayer as m, UiImage as n, UiToggle as o, UiText as p, UiPanel as r, Timer as s, registerCoreNodes as t, HudLayer as u, readDeviceHints as v, ROLLOFF_MODELS as w, LogManager as x, suggestQuality as y };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { f as Node, t as buildNodeJson, x as Signal } from "./loader-
|
|
2
|
-
import { t as registerCoreNodes } from "./register-
|
|
1
|
+
import { f as Node, t as buildNodeJson, x as Signal } from "./loader-Buk8Bu1h.js";
|
|
2
|
+
import { t as registerCoreNodes } from "./register-CscIzJEO.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
4
|
import { n as jsonEquals, t as jsonClone } from "./json-BLk7H2Qa.js";
|
|
5
5
|
import { l as registerNode } from "./registry-CJdGpT2V.js";
|