@origingame/engine 0.0.0-stage → 0.1.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/bin/og.mjs +25 -0
- package/dist/web/origin-engine.d.ts +1233 -0
- package/dist/web/origin-engine.js +8628 -0
- package/dist/web/origin-engine_bg.wasm +0 -0
- package/dist/web/origin-engine_bg.wasm.d.ts +366 -0
- package/dist/web-publish/origin-engine-lanes.js +8509 -0
- package/dist/web-publish/origin-engine-lanes_bg.wasm +0 -0
- package/dist/web-publish/origin-engine.d.ts +1176 -0
- package/dist/web-publish/origin-engine.js +8313 -0
- package/dist/web-publish/origin-engine_bg.wasm +0 -0
- package/dist/web-publish/origin-engine_bg.wasm.d.ts +347 -0
- package/dist/web-publish/snippets/wasm-bindgen-rayon-38edf6e439f6d70d/src/workerHelpers.no-bundler.js +77 -0
- package/dist/web-publish-2d/origin-engine-lanes.js +8509 -0
- package/dist/web-publish-2d/origin-engine-lanes_bg.wasm +0 -0
- package/dist/web-publish-2d/origin-engine.d.ts +1176 -0
- package/dist/web-publish-2d/origin-engine.js +8313 -0
- package/dist/web-publish-2d/origin-engine_bg.wasm +0 -0
- package/dist/web-publish-2d/origin-engine_bg.wasm.d.ts +347 -0
- package/dist/web-publish-2d/snippets/wasm-bindgen-rayon-38edf6e439f6d70d/src/workerHelpers.no-bundler.js +77 -0
- package/knowledge/README.md +31 -0
- package/knowledge/knowledge.json +13 -0
- package/knowledge/looks/catalogue.json +1871 -0
- package/knowledge/package.json +33 -0
- package/knowledge/rules/platform.json +329 -0
- package/knowledge/scaffold/README.md +27 -0
- package/knowledge/scaffold/distribution.json +48 -0
- package/knowledge/scaffold/origin-engine.json +19 -0
- package/knowledge/scaffold/template/.origin/setup +14 -0
- package/knowledge/scaffold/template/LICENSES.md +23 -0
- package/knowledge/scaffold/template/README.md +37 -0
- package/knowledge/scaffold/template/assets/ai/guard.ogt +48 -0
- package/knowledge/scaffold/template/assets/audio/click.ogt +9 -0
- package/knowledge/scaffold/template/assets/fonts/NotoSansSC-Regular-subset.ttf.ogt +12 -0
- package/knowledge/scaffold/template/assets/fonts/OFL.txt +93 -0
- package/knowledge/scaffold/template/assets/looks/origin-game.ogt +12 -0
- package/knowledge/scaffold/template/assets/nav/yard.ogt +27 -0
- package/knowledge/scaffold/template/assets/scenes/main.ogt +34 -0
- package/knowledge/scaffold/template/assets/strings/en.ogt +14 -0
- package/knowledge/scaffold/template/assets/textures/stone_n.png.ogt +7 -0
- package/knowledge/scaffold/template/gitattributes +22 -0
- package/knowledge/scaffold/template/package.json +18 -0
- package/knowledge/scaffold/template/project.ogt +25 -0
- package/knowledge/scaffold/template/src/main.ts +246 -0
- package/knowledge/scaffold/template/tests/guard.og.ts +67 -0
- package/knowledge/scaffold/template/tests/smoke.og.ts +43 -0
- package/knowledge/scaffold/template/tsconfig.json +17 -0
- package/knowledge/scripts/lib.ts +203 -0
- package/knowledge/scripts/mount.ts +210 -0
- package/knowledge/scripts/pack.ts +24 -0
- package/knowledge/scripts/scaffold.ts +72 -0
- package/knowledge/scripts/validate.ts +749 -0
- package/knowledge/skills/og-ai/SKILL.md +386 -0
- package/knowledge/skills/og-audio/SKILL.md +191 -0
- package/knowledge/skills/og-build/SKILL.md +330 -0
- package/knowledge/skills/og-core/SKILL.md +285 -0
- package/knowledge/skills/og-feel/SKILL.md +385 -0
- package/knowledge/skills/og-game/SKILL.md +1479 -0
- package/knowledge/skills/og-i18n/SKILL.md +220 -0
- package/knowledge/skills/og-platform-sdk/SKILL.md +199 -0
- package/knowledge/skills/og-taste/SKILL.md +537 -0
- package/knowledge/skills/og-ui/SKILL.md +444 -0
- package/knowledge/wiki/ai.md +627 -0
- package/knowledge/wiki/anim.md +1187 -0
- package/knowledge/wiki/atmosphere.md +636 -0
- package/knowledge/wiki/audio.md +732 -0
- package/knowledge/wiki/camera.md +711 -0
- package/knowledge/wiki/commands.md +357 -0
- package/knowledge/wiki/game.md +220 -0
- package/knowledge/wiki/i18n.md +213 -0
- package/knowledge/wiki/index.md +1027 -0
- package/knowledge/wiki/input.md +1714 -0
- package/knowledge/wiki/load.md +1280 -0
- package/knowledge/wiki/math.md +166 -0
- package/knowledge/wiki/nav.md +1154 -0
- package/knowledge/wiki/physics.md +3142 -0
- package/knowledge/wiki/platform.md +857 -0
- package/knowledge/wiki/probe.md +1692 -0
- package/knowledge/wiki/reflect.md +285 -0
- package/knowledge/wiki/render.md +3511 -0
- package/knowledge/wiki/rng.md +393 -0
- package/knowledge/wiki/stream.md +376 -0
- package/knowledge/wiki/system.md +197 -0
- package/knowledge/wiki/systems.md +43 -0
- package/knowledge/wiki/terrain.md +727 -0
- package/knowledge/wiki/tilemap.md +422 -0
- package/knowledge/wiki/time.md +150 -0
- package/knowledge/wiki/transform.md +230 -0
- package/knowledge/wiki/tween.md +580 -0
- package/knowledge/wiki/ui.md +8000 -0
- package/knowledge/wiki/world.md +909 -0
- package/package.json +68 -4
- package/schema/api.schema.json +29675 -0
- package/shell/README.md +22 -0
- package/shell/brand/README.md +23 -0
- package/shell/brand/icon-192.png +0 -0
- package/shell/brand/icon-512.png +0 -0
- package/shell/brand/mark-on-paper.svg +6 -0
- package/shell/brand/origin-mark.svg +5 -0
- package/shell/brand/origin-motion.css +251 -0
- package/shell/brand/tokens.css +20 -0
- package/shell/brand.ts +132 -0
- package/shell/page.ts +715 -0
- package/shell/platform.ts +203 -0
- package/shell/player.ts +238 -0
- package/shell/shell.css +185 -0
- package/shell/sw.ts +177 -0
- package/shell/transport.ts +515 -0
- package/shell/wire.ts +369 -0
- package/src/e6.ts +69 -0
- package/src/env.d.ts +100 -0
- package/src/generated/abi.ts +980 -0
- package/src/index.ts +193 -0
- package/src/node/audio.ts +86 -0
- package/src/node/build.ts +109 -0
- package/src/node/device.ts +981 -0
- package/src/node/doctor.ts +60 -0
- package/src/node/index.ts +102 -0
- package/src/node/platform.ts +24 -0
- package/src/node/playtest-run.ts +249 -0
- package/src/node/png.ts +130 -0
- package/src/node/polish.ts +976 -0
- package/src/node/run.ts +373 -0
- package/src/node/web-build.ts +808 -0
- package/src/p2.ts +35 -0
- package/src/platform-local.ts +211 -0
- package/src/playtest.ts +131 -0
- package/src/runtime.ts +1509 -0
- package/src/systems/cameras.ts +247 -0
- package/src/ui-kit/bind.ts +141 -0
- package/src/ui-kit/huds.ts +303 -0
- package/src/ui-kit/index.ts +36 -0
- package/src/ui-kit/menus.ts +187 -0
- package/src/ui-kit/settings.ts +131 -0
- package/src/ui-kit/store.ts +222 -0
- package/src/ui-kit/story.ts +89 -0
- package/src/ui-kit/util.ts +106 -0
- package/src/web/a11y.ts +429 -0
- package/src/web/audio-worklet.ts +193 -0
- package/src/web/audio.ts +307 -0
- package/src/web/basis.ts +239 -0
- package/src/web/gamepad.ts +123 -0
- package/src/web/host.ts +166 -0
- package/src/web/input.ts +237 -0
- package/src/web/lanes.ts +21 -0
- package/src/web/play.ts +20 -0
- package/src/web/rpc-ring.ts +110 -0
- package/src/web/settle.ts +122 -0
- package/src/web/shell.ts +643 -0
- package/src/web/text-input.ts +248 -0
- package/src/web/worker.ts +1002 -0
- package/types/bindings.json +13654 -0
- package/types/index.d.ts +20090 -0
- package/vendor/basis/LICENSE +201 -0
- package/vendor/basis/NOTICE +15 -0
- package/vendor/basis/README.md +18 -0
- package/vendor/basis/basis_transcoder.js +19 -0
- package/vendor/basis/basis_transcoder.wasm +0 -0
- package/README.md +0 -3
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// The camera controllers of og.systems.cameras (IDL `camera`: og.OrbitCamera,
|
|
2
|
+
// og.FollowCamera, og.Follow2D). They are game-level systems the engine ships
|
|
3
|
+
// in TypeScript: a game lists them in GameDesc.systems, so the schedule shows
|
|
4
|
+
// them and nothing moves the camera unless the game asked. Each drives
|
|
5
|
+
// og.Camera from the first entity in query order that carries its component.
|
|
6
|
+
//
|
|
7
|
+
// The engine's og.camera system (Rust, after the game's systems) adds what
|
|
8
|
+
// needs more than one query: og.CameraRail, og.CameraZone (on the frame), and
|
|
9
|
+
// the decay of shake, punch and kick.
|
|
10
|
+
//
|
|
11
|
+
// og.Camera is world state (snapshots, the world hash), so every number here
|
|
12
|
+
// comes from + - * / and sqrt, which JavaScript engines round identically:
|
|
13
|
+
// angles go through `sincos` below, never Math.sin, and rotations through
|
|
14
|
+
// og.transform.lookAt, which the engine computes.
|
|
15
|
+
|
|
16
|
+
import { defineSystem, transform, ui, BUILTINS, type SystemToken, type Token, type WorldImpl } from "../runtime.ts";
|
|
17
|
+
|
|
18
|
+
const T = BUILTINS as unknown as Record<string, Token>;
|
|
19
|
+
const Transform = T.Transform!;
|
|
20
|
+
const Input = T.Input!;
|
|
21
|
+
const Camera = T.Camera!;
|
|
22
|
+
const OrbitCamera = T.OrbitCamera!;
|
|
23
|
+
const FollowCamera = T.FollowCamera!;
|
|
24
|
+
const Follow2D = T.Follow2D!;
|
|
25
|
+
|
|
26
|
+
const PI = 3.141592653589793;
|
|
27
|
+
const TAU = 2 * PI;
|
|
28
|
+
|
|
29
|
+
/** sin and cos of `x` with basic arithmetic only: the same bits on every host. */
|
|
30
|
+
export function sincos(x: number): [number, number] {
|
|
31
|
+
// Reduce to r in [-pi/4, pi/4] and a quadrant.
|
|
32
|
+
const q = Math.round(x / (PI / 2));
|
|
33
|
+
const r = x - q * (PI / 2);
|
|
34
|
+
const r2 = r * r;
|
|
35
|
+
// Taylor series to r^15 / r^14: below 1e-15 on [-pi/4, pi/4].
|
|
36
|
+
let s = 1 / 1307674368000;
|
|
37
|
+
s = s * -r2 + 1 / 6227020800;
|
|
38
|
+
s = s * -r2 + 1 / 39916800;
|
|
39
|
+
s = s * -r2 + 1 / 362880;
|
|
40
|
+
s = s * -r2 + 1 / 5040;
|
|
41
|
+
s = s * -r2 + 1 / 120;
|
|
42
|
+
s = s * -r2 + 1 / 6;
|
|
43
|
+
s = r - r * r2 * s;
|
|
44
|
+
let c = 1 / 87178291200;
|
|
45
|
+
c = c * -r2 + 1 / 479001600;
|
|
46
|
+
c = c * -r2 + 1 / 3628800;
|
|
47
|
+
c = c * -r2 + 1 / 40320;
|
|
48
|
+
c = c * -r2 + 1 / 720;
|
|
49
|
+
c = c * -r2 + 1 / 24;
|
|
50
|
+
c = c * -r2 + 1 / 2;
|
|
51
|
+
c = 1 - r2 * c;
|
|
52
|
+
switch (((q % 4) + 4) % 4) {
|
|
53
|
+
case 0:
|
|
54
|
+
return [s, c];
|
|
55
|
+
case 1:
|
|
56
|
+
return [c, -s];
|
|
57
|
+
case 2:
|
|
58
|
+
return [-s, -c];
|
|
59
|
+
default:
|
|
60
|
+
return [-c, s];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const clamp = (v: number, lo: number, hi: number) => (v < lo ? lo : v > hi ? hi : v);
|
|
65
|
+
|
|
66
|
+
type V3 = { x: number; y: number; z: number };
|
|
67
|
+
type Q = { x: number; y: number; z: number; w: number };
|
|
68
|
+
|
|
69
|
+
/** q * v * q^-1. */
|
|
70
|
+
function rotate(q: Q, v: V3): V3 {
|
|
71
|
+
const tx = 2 * (q.y * v.z - q.z * v.y);
|
|
72
|
+
const ty = 2 * (q.z * v.x - q.x * v.z);
|
|
73
|
+
const tz = 2 * (q.x * v.y - q.y * v.x);
|
|
74
|
+
return {
|
|
75
|
+
x: v.x + q.w * tx + (q.y * tz - q.z * ty),
|
|
76
|
+
y: v.y + q.w * ty + (q.z * tx - q.x * tz),
|
|
77
|
+
z: v.z + q.w * tz + (q.x * ty - q.y * tx),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The fraction of the gap a smoothed follower closes in one tick. */
|
|
82
|
+
const ease = (dt: number, smoothTime: number) => (smoothTime > 0 ? Math.min(1, dt / smoothTime) : 1);
|
|
83
|
+
|
|
84
|
+
interface CameraValue {
|
|
85
|
+
position: V3;
|
|
86
|
+
quaternion: Q;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function camera(world: WorldImpl): CameraValue | null {
|
|
90
|
+
const c = world.resource(Camera);
|
|
91
|
+
return c.ok ? (c.value as unknown as CameraValue) : null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function aim(cam: CameraValue, eye: V3, target: V3): void {
|
|
95
|
+
cam.position = eye;
|
|
96
|
+
cam.quaternion = transform.lookAt(eye, target) as Q;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
interface ArchetypeLike {
|
|
100
|
+
readonly count: number;
|
|
101
|
+
column(t: Token): Record<string, ArrayLike<number> & { [i: number]: number }>;
|
|
102
|
+
}
|
|
103
|
+
interface QueryLike {
|
|
104
|
+
archetypes(): readonly ArchetypeLike[];
|
|
105
|
+
}
|
|
106
|
+
interface InputValue {
|
|
107
|
+
pointer: { x: number; y: number };
|
|
108
|
+
pointerInside: boolean;
|
|
109
|
+
wheel: { x: number; y: number };
|
|
110
|
+
buttonDown(b: string): boolean;
|
|
111
|
+
buttonPressed(b: string): boolean;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** og.orbitCamera: OrbitControls over og.Input. */
|
|
115
|
+
export const orbitCamera = defineSystem(
|
|
116
|
+
"og.orbitCamera",
|
|
117
|
+
{ reads: [Transform, Input], writes: [OrbitCamera, Camera] },
|
|
118
|
+
(q: QueryLike, time: { dt: number }, world: WorldImpl) => {
|
|
119
|
+
for (const a of q.archetypes()) {
|
|
120
|
+
if (a.count === 0) continue;
|
|
121
|
+
const o = a.column(OrbitCamera);
|
|
122
|
+
const p = a.column(Transform).position;
|
|
123
|
+
let azimuth = o.azimuth[0]!;
|
|
124
|
+
let polar = o.polar[0]!;
|
|
125
|
+
let distance = o.distance[0]!;
|
|
126
|
+
const input = world.resource(Input);
|
|
127
|
+
if (o.enabled[0] && input.ok) {
|
|
128
|
+
const i = input.value as unknown as InputValue;
|
|
129
|
+
const ptr = o.pointer;
|
|
130
|
+
if (i.buttonPressed("left") && i.pointerInside) {
|
|
131
|
+
o.dragging[0] = 1;
|
|
132
|
+
ptr[0] = i.pointer.x;
|
|
133
|
+
ptr[1] = i.pointer.y;
|
|
134
|
+
}
|
|
135
|
+
if (o.dragging[0] && i.buttonDown("left")) {
|
|
136
|
+
const height = Math.max(1, (ui.viewport(world) as { height: number }).height);
|
|
137
|
+
const k = (TAU / height) * o.rotateSpeed[0]!;
|
|
138
|
+
azimuth -= (i.pointer.x - ptr[0]!) * k;
|
|
139
|
+
polar -= (i.pointer.y - ptr[1]!) * k;
|
|
140
|
+
ptr[0] = i.pointer.x;
|
|
141
|
+
ptr[1] = i.pointer.y;
|
|
142
|
+
}
|
|
143
|
+
if (!i.buttonDown("left")) o.dragging[0] = 0;
|
|
144
|
+
if (i.wheel.y !== 0) distance *= clamp(1 + 0.001 * i.wheel.y * o.zoomSpeed[0]!, 0.5, 2);
|
|
145
|
+
}
|
|
146
|
+
if (o.autoRotate[0] && !o.dragging[0]) azimuth += o.autoRotateSpeed[0]! * time.dt;
|
|
147
|
+
azimuth -= Math.floor(azimuth / TAU) * TAU;
|
|
148
|
+
polar = clamp(polar, o.minPolarAngle[0]!, o.maxPolarAngle[0]!);
|
|
149
|
+
distance = clamp(distance, o.minDistance[0]!, o.maxDistance[0]!);
|
|
150
|
+
o.azimuth[0] = azimuth;
|
|
151
|
+
o.polar[0] = polar;
|
|
152
|
+
o.distance[0] = distance;
|
|
153
|
+
const [sa, ca] = sincos(azimuth);
|
|
154
|
+
const [sp, cp] = sincos(polar);
|
|
155
|
+
const t = o.target;
|
|
156
|
+
const point = { x: p[0]! + t[0]!, y: p[1]! + t[1]!, z: p[2]! + t[2]! };
|
|
157
|
+
const eye = { x: point.x + distance * sp * sa, y: point.y + distance * cp, z: point.z + distance * sp * ca };
|
|
158
|
+
const cam = camera(world);
|
|
159
|
+
if (cam) aim(cam, eye, point);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
/** og.followCamera: a smoothed chase camera. */
|
|
166
|
+
export const followCamera = defineSystem(
|
|
167
|
+
"og.followCamera",
|
|
168
|
+
{ reads: [Transform, FollowCamera], writes: [Camera] },
|
|
169
|
+
(q: QueryLike, time: { dt: number }, world: WorldImpl) => {
|
|
170
|
+
for (const a of q.archetypes()) {
|
|
171
|
+
if (a.count === 0) continue;
|
|
172
|
+
const f = a.column(FollowCamera);
|
|
173
|
+
const tr = a.column(Transform);
|
|
174
|
+
const p = tr.position;
|
|
175
|
+
const r = tr.quaternion;
|
|
176
|
+
const pos = { x: p[0]!, y: p[1]!, z: p[2]! };
|
|
177
|
+
let offset = { x: f.offset[0]!, y: f.offset[1]!, z: f.offset[2]! };
|
|
178
|
+
if (f.followRotation[0]) offset = rotate({ x: r[0]!, y: r[1]!, z: r[2]!, w: r[3]! }, offset);
|
|
179
|
+
const cam = camera(world);
|
|
180
|
+
if (!cam) return;
|
|
181
|
+
const k = ease(time.dt, f.smoothTime[0]!);
|
|
182
|
+
const c = cam.position;
|
|
183
|
+
const eye = {
|
|
184
|
+
x: c.x + (pos.x + offset.x - c.x) * k,
|
|
185
|
+
y: c.y + (pos.y + offset.y - c.y) * k,
|
|
186
|
+
z: c.z + (pos.z + offset.z - c.z) * k,
|
|
187
|
+
};
|
|
188
|
+
aim(cam, eye, { x: pos.x + f.lookOffset[0]!, y: pos.y + f.lookOffset[1]!, z: pos.z + f.lookOffset[2]! });
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
/** og.follow2D: a dead-zone follower in the xy plane, with look-ahead and per-axis smoothing. */
|
|
195
|
+
export const follow2D = defineSystem(
|
|
196
|
+
"og.follow2D",
|
|
197
|
+
{ reads: [Transform], writes: [Follow2D, Camera] },
|
|
198
|
+
(q: QueryLike, time: { dt: number }, world: WorldImpl) => {
|
|
199
|
+
for (const a of q.archetypes()) {
|
|
200
|
+
if (a.count === 0) continue;
|
|
201
|
+
const f = a.column(Follow2D);
|
|
202
|
+
const p = a.column(Transform).position;
|
|
203
|
+
const cam = camera(world);
|
|
204
|
+
if (!cam) return;
|
|
205
|
+
const c = cam.position;
|
|
206
|
+
const goal = (cur: number, target: number, dead: number) => {
|
|
207
|
+
const d = target - cur;
|
|
208
|
+
return d > dead ? target - dead : d < -dead ? target + dead : cur;
|
|
209
|
+
};
|
|
210
|
+
// Look-ahead: lead by the entity's velocity times lookAhead, eased.
|
|
211
|
+
const last = f.last;
|
|
212
|
+
const lead = f.lead;
|
|
213
|
+
const px = p[0]!;
|
|
214
|
+
const py = p[1]!;
|
|
215
|
+
const la = f.lookAhead[0]!;
|
|
216
|
+
if (la > 0 && time.dt > 0) {
|
|
217
|
+
const fresh = last[0] === 0 && last[1] === 0;
|
|
218
|
+
const vx = fresh ? 0 : (px - last[0]!) / time.dt;
|
|
219
|
+
const vy = fresh ? 0 : (py - last[1]!) / time.dt;
|
|
220
|
+
const max = f.lookAheadMax;
|
|
221
|
+
const k = ease(time.dt, f.lookAheadSmoothing[0]!);
|
|
222
|
+
lead[0] = lead[0]! + (clamp(vx * la, -max[0]!, max[0]!) - lead[0]!) * k;
|
|
223
|
+
lead[1] = lead[1]! + (clamp(vy * la, -max[1]!, max[1]!) - lead[1]!) * k;
|
|
224
|
+
} else {
|
|
225
|
+
lead[0] = 0;
|
|
226
|
+
lead[1] = 0;
|
|
227
|
+
}
|
|
228
|
+
last[0] = px;
|
|
229
|
+
last[1] = py;
|
|
230
|
+
const st = f.smoothTime[0]!;
|
|
231
|
+
const sy = f.smoothTimeY[0]!;
|
|
232
|
+
const kx = ease(time.dt, st);
|
|
233
|
+
const ky = ease(time.dt, sy >= 0 ? sy : st);
|
|
234
|
+
let x = c.x + (goal(c.x, px + f.offset[0]! + lead[0]!, f.deadZone[0]!) - c.x) * kx;
|
|
235
|
+
let y = c.y + (goal(c.y, py + f.offset[1]! + lead[1]!, f.deadZone[1]!) - c.y) * ky;
|
|
236
|
+
if (f.clamp[0]) {
|
|
237
|
+
x = clamp(x, f.boundsMin[0]!, Math.max(f.boundsMin[0]!, f.boundsMax[0]!));
|
|
238
|
+
y = clamp(y, f.boundsMin[1]!, Math.max(f.boundsMin[1]!, f.boundsMax[1]!));
|
|
239
|
+
}
|
|
240
|
+
if (x !== c.x || y !== c.y) cam.position = { x, y, z: c.z };
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
/** og.systems.cameras, in the IDL's order. */
|
|
247
|
+
export const cameras: readonly SystemToken[] = Object.freeze([orbitCamera, followCamera, follow2D]);
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// The game widget set's host calls (IDL module `ui`, crates/og-ui) and the
|
|
2
|
+
// TypeScript sugar over them: anchor, transition, modal, list and grid take
|
|
3
|
+
// a build callback and close what they open even when it throws. The screen
|
|
4
|
+
// templates (./templates) are written against `kit`, which is og.ui with
|
|
5
|
+
// these members added.
|
|
6
|
+
|
|
7
|
+
import type * as Api from "@origingame/engine";
|
|
8
|
+
import { boundHost, ui as base } from "../runtime.ts";
|
|
9
|
+
|
|
10
|
+
type HostAbi = NonNullable<ReturnType<typeof boundHost>>["abi"];
|
|
11
|
+
|
|
12
|
+
function abi(): HostAbi {
|
|
13
|
+
const host = boundHost();
|
|
14
|
+
if (!host) throw new Error("no engine host is loaded: start the game through @origingame/engine's web or node loader");
|
|
15
|
+
return host.abi;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const parse = <T>(text: string): T => JSON.parse(text) as T;
|
|
19
|
+
const json = (value: unknown): string | undefined => (value === undefined ? undefined : JSON.stringify(value));
|
|
20
|
+
const worldId = (world: Api.World): number => (world as unknown as { id: number }).id;
|
|
21
|
+
|
|
22
|
+
/** Runs `build` between an open and its close, closing even when it throws. */
|
|
23
|
+
function within(open: () => void, build: () => void, close: () => void): void {
|
|
24
|
+
open();
|
|
25
|
+
try {
|
|
26
|
+
build();
|
|
27
|
+
} finally {
|
|
28
|
+
close();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const widgets = {
|
|
33
|
+
breakpoint: (): Api.Breakpoint => abi().og_ui_breakpoint() as Api.Breakpoint,
|
|
34
|
+
frameViewport: (): Api.Viewport => parse(abi().og_ui_frame_viewport()),
|
|
35
|
+
frameTheme: (): Api.UiTheme => parse(abi().og_ui_frame_theme()),
|
|
36
|
+
hasIcon: (name: string): boolean => abi().og_ui_has_icon(name),
|
|
37
|
+
seconds: (): number => abi().og_ui_seconds(),
|
|
38
|
+
openAnchor: (id: string, desc?: Api.AnchorDesc): void => abi().og_ui_open_anchor(id, json(desc)),
|
|
39
|
+
anchor: (id: string, desc: Api.AnchorDesc, build: Api.UiBuild): void =>
|
|
40
|
+
within(() => abi().og_ui_open_anchor(id, json(desc)), build, () => abi().og_ui_close()),
|
|
41
|
+
openTransition: (id: string, show: boolean, desc?: Api.TransitionDesc): Api.Presence =>
|
|
42
|
+
parse(abi().og_ui_open_transition(id, show, json(desc))),
|
|
43
|
+
transition: (id: string, show: boolean, desc: Api.TransitionDesc, build: Api.UiBuild): Api.Presence => {
|
|
44
|
+
const p = parse<Api.Presence>(abi().og_ui_open_transition(id, show, json(desc)));
|
|
45
|
+
within(() => {}, () => { if (p.visible) build(); }, () => abi().og_ui_close());
|
|
46
|
+
return p;
|
|
47
|
+
},
|
|
48
|
+
openModal: (id: string, open: boolean, desc?: Api.ModalDesc): Api.ModalResult => parse(abi().og_ui_open_modal(id, open, json(desc))),
|
|
49
|
+
closeModal: (): void => abi().og_ui_close_modal(),
|
|
50
|
+
modal: (id: string, open: boolean, desc: Api.ModalDesc, build: Api.UiBuild): Api.ModalResult => {
|
|
51
|
+
const r = parse<Api.ModalResult>(abi().og_ui_open_modal(id, open, json(desc)));
|
|
52
|
+
within(() => {}, () => { if (r.visible) build(); }, () => abi().og_ui_close_modal());
|
|
53
|
+
return r;
|
|
54
|
+
},
|
|
55
|
+
spotlight: (id: string, hole: Api.UiRect | null, desc?: Api.SpotlightDesc): Api.Response =>
|
|
56
|
+
parse(abi().og_ui_spotlight(id, JSON.stringify(hole ?? null), json(desc))),
|
|
57
|
+
bounds: (path: string): Api.UiRect | null => parse(abi().og_ui_bounds(path)),
|
|
58
|
+
openList: (id: string, desc?: Api.ListDesc): Api.ListRange => parse(abi().og_ui_open_list(id, json(desc))),
|
|
59
|
+
openGrid: (id: string, desc?: Api.GridDesc): Api.ListRange => parse(abi().og_ui_open_grid(id, json(desc))),
|
|
60
|
+
listItem: (index: number, selected: boolean): Api.Response => parse(abi().og_ui_list_item(index, selected)),
|
|
61
|
+
gridItem: (index: number, selected: boolean): Api.Response => parse(abi().og_ui_grid_item(index, selected)),
|
|
62
|
+
closeList: (): Api.Response => parse(abi().og_ui_close_list()),
|
|
63
|
+
dragging: (id: string): number | null => parse(abi().og_ui_dragging(id)),
|
|
64
|
+
list: (id: string, desc: Api.ListDesc, build: Api.UiItemBuild): Api.ListResult => items(id, desc, build, false),
|
|
65
|
+
grid: (id: string, desc: Api.GridDesc, build: Api.UiItemBuild): Api.ListResult => items(id, desc, build, true),
|
|
66
|
+
tabs: (id: string, items: readonly string[], selected: number, desc?: Api.TabsDesc): Api.ChoiceResult =>
|
|
67
|
+
parse(abi().og_ui_tabs(id, JSON.stringify(items), selected, json(desc))),
|
|
68
|
+
select: (id: string, options: readonly string[], selected: number, desc?: Api.SelectDesc): Api.ChoiceResult =>
|
|
69
|
+
parse(abi().og_ui_select(id, JSON.stringify(options), selected, json(desc))),
|
|
70
|
+
stepper: (id: string, value: number, desc?: Api.StepperDesc): Api.StepperResult => parse(abi().og_ui_stepper(id, value, json(desc))),
|
|
71
|
+
radio: (id: string, options: readonly string[], selected: number, desc?: Api.RadioDesc): Api.ChoiceResult =>
|
|
72
|
+
parse(abi().og_ui_radio(id, JSON.stringify(options), selected, json(desc))),
|
|
73
|
+
checkbox: (id: string, on: boolean, label: string): Api.ToggleResult => parse(abi().og_ui_checkbox(id, on, label)),
|
|
74
|
+
checkboxList: (id: string, labels: readonly string[], values: readonly boolean[]): Api.CheckListResult =>
|
|
75
|
+
parse(abi().og_ui_checkbox_list(id, JSON.stringify(labels), JSON.stringify(values))),
|
|
76
|
+
chip: (id: string, text: string, desc?: Api.ChipDesc): Api.Response => parse(abi().og_ui_chip(id, text, json(desc))),
|
|
77
|
+
badge: (id: string, text: string, tone: Api.Tone): void => abi().og_ui_badge(id, text, tone),
|
|
78
|
+
divider: (id: string, vertical: boolean): void => abi().og_ui_divider(id, vertical),
|
|
79
|
+
spacer: (id: string, size: Api.Size | null): void => abi().og_ui_spacer(id, JSON.stringify(size ?? null)),
|
|
80
|
+
keyValue: (id: string, key: string, value: string, mono: boolean): void => abi().og_ui_key_value(id, key, value, mono),
|
|
81
|
+
skeleton: (id: string, desc?: Api.SkeletonDesc): void => abi().og_ui_skeleton(id, json(desc)),
|
|
82
|
+
spinner: (id: string, size: number): void => abi().og_ui_spinner(id, size),
|
|
83
|
+
icon: (id: string, name: string, desc?: Api.IconDesc): void => abi().og_ui_icon(id, name, json(desc)),
|
|
84
|
+
meter: (id: string, value: number, max: number, desc?: Api.MeterDesc): void => abi().og_ui_meter(id, value, max, json(desc)),
|
|
85
|
+
cooldown: (id: string, remaining: number, desc?: Api.CooldownDesc): Api.Response => parse(abi().og_ui_cooldown(id, remaining, json(desc))),
|
|
86
|
+
minimap: (id: string, desc?: Api.MinimapDesc): void => abi().og_ui_minimap(id, json(desc)),
|
|
87
|
+
zoomView: (id: string, desc?: Api.ZoomViewDesc): Api.ZoomResult => parse(abi().og_ui_zoom_view(id, json(desc))),
|
|
88
|
+
dialogue: (id: string, desc?: Api.DialogueDesc): Api.DialogueResult => parse(abi().og_ui_dialogue(id, json(desc))),
|
|
89
|
+
joystick: (id: string, desc?: Api.JoystickDesc): Api.JoystickResult => parse(abi().og_ui_joystick(id, json(desc))),
|
|
90
|
+
dpad: (id: string, desc?: Api.DpadDesc): Api.DpadResult => parse(abi().og_ui_dpad(id, json(desc))),
|
|
91
|
+
actionButton: (id: string, text: string, desc?: Api.ActionButtonDesc): Api.Response => parse(abi().og_ui_action_button(id, text, json(desc))),
|
|
92
|
+
toast: (world: Api.World, text: string, desc?: Api.ToastDesc): number => abi().og_ui_toast(worldId(world), text, json(desc)),
|
|
93
|
+
dismissToast: (world: Api.World, toastId: number): void => abi().og_ui_dismiss_toast(worldId(world), toastId),
|
|
94
|
+
navigate: (world: Api.World, intent: Api.NavIntent): void => abi().og_ui_navigate(worldId(world), intent),
|
|
95
|
+
back: (): boolean => abi().og_ui_back(),
|
|
96
|
+
setNavigation: (on: boolean): void => abi().og_ui_set_navigation(on),
|
|
97
|
+
setPlayers: (count: number): void => abi().og_ui_set_players(count),
|
|
98
|
+
focus: (pad: number): string => abi().og_ui_focus(pad),
|
|
99
|
+
a11y: (): Api.A11yTree => parse(abi().og_ui_a11y()),
|
|
100
|
+
memory: (id: string, initial: number): number => abi().og_ui_memory(id, initial),
|
|
101
|
+
remember: (id: string, value: number): void => abi().og_ui_remember(id, value),
|
|
102
|
+
tick: (): number => abi().og_ui_tick(),
|
|
103
|
+
counter: (id: string, value: number, desc?: Api.CounterDesc): number => abi().og_ui_counter(id, value, json(desc)),
|
|
104
|
+
burst: (desc?: Api.BurstDesc): void => abi().og_ui_burst(json(desc)),
|
|
105
|
+
shake: (desc?: Api.ShakeDesc): void => abi().og_ui_shake(json(desc)),
|
|
106
|
+
screenTransition: (id: string, screen: string, desc?: Api.ScreenTransitionDesc): Api.ScreenTransitionResult =>
|
|
107
|
+
parse(abi().og_ui_screen_transition(id, screen, json(desc))),
|
|
108
|
+
previewViewport: (world: Api.World, viewport: Api.Viewport | null): void =>
|
|
109
|
+
abi().og_ui_preview_viewport(worldId(world), JSON.stringify(viewport ?? null)),
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/** og.ui.list / og.ui.grid: open, build the items in view, collect clicks and drops, close. */
|
|
113
|
+
function items(id: string, desc: Api.ListDesc | Api.GridDesc, build: Api.UiItemBuild, grid: boolean): Api.ListResult {
|
|
114
|
+
const range = grid ? widgets.openGrid(id, desc as Api.GridDesc) : widgets.openList(id, desc as Api.ListDesc);
|
|
115
|
+
const selected = desc.selected ?? null;
|
|
116
|
+
let clicked: number | null = null;
|
|
117
|
+
let dropFrom: number | null = null;
|
|
118
|
+
let dropTo: number | null = null;
|
|
119
|
+
try {
|
|
120
|
+
for (let i = range.first; i < range.last; i++) {
|
|
121
|
+
const r = grid ? widgets.gridItem(i, i === selected) : widgets.listItem(i, i === selected);
|
|
122
|
+
if (r.clicked) clicked = i;
|
|
123
|
+
if (r.dropped !== null && r.dropped !== undefined) {
|
|
124
|
+
dropFrom = r.dropped;
|
|
125
|
+
dropTo = i;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
build(i);
|
|
129
|
+
} finally {
|
|
130
|
+
base.close();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} finally {
|
|
134
|
+
widgets.closeList();
|
|
135
|
+
}
|
|
136
|
+
return { clicked, dropFrom, dropTo };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** og.ui as the templates use it: the core calls and the widget set. */
|
|
140
|
+
export const kit = { ...base, ...widgets };
|
|
141
|
+
export type Kit = typeof kit;
|