@volter/play 0.5.212 → 0.5.213
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 +82 -51
- package/contributions/game-panel.tsx +228 -329
- package/contributions/levels.finder.ts +43 -0
- package/contributions/play.page.tsx +56 -174
- package/contributions/play.verbs.ts +74 -0
- package/contributions/{std-scene.document.tsx → std-level.document.tsx} +154 -22
- package/dist-node/serving.mjs +67 -36
- package/package.json +75 -53
- package/serving/index.ts +1 -1
- package/serving/std-dev.ts +86 -53
- package/serving/std-scene-runtime.js +128 -7
- package/serving/std-scene.ts +48 -21
- package/src/editor-blender.ts +4 -19
- package/src/editor-threejs.ts +1 -1
- package/src/game-controls.ts +111 -0
- package/src/game-run.ts +165 -0
- package/src/game-view.tsx +219 -0
- package/src/play-clock.ts +4 -4
- package/src/play-log.ts +57 -73
- package/src/std-controls.ts +7 -7
- package/src/std-export.ts +0 -8
- package/src/std-game-adapter.ts +10 -4
- package/src/std-game.ts +316 -190
- package/src/std-level-files.ts +18 -1
- package/src/std-level.ts +4 -4
- package/src/std-view-bar.tsx +7 -17
- package/contributions/model-play.verbs.ts +0 -44
- package/src/model-play.ts +0 -149
- package/src/std-scene-level.ts +0 -59
package/src/play-log.ts
CHANGED
|
@@ -1,34 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* THE
|
|
2
|
+
* THE PLAY LOG: what the game's run says happened.
|
|
3
3
|
*
|
|
4
4
|
* Each player's page traces its run (std's trace, as the dev server's receiver writes it, `build/runs/<run>/<page>.jsonl`)
|
|
5
5
|
* and the run reads each trace into this log, each entry tagged with its player's slot (`std-game.ts`, `source:
|
|
6
6
|
* 'script'`). Play adds its own lifecycle entries (`source: 'play'`): `play-start`, `running`, `player-joined`,
|
|
7
7
|
* `player-page-loaded`, `player-left`, `view`, `autoplay-on` and `autoplay-off` (each with `by`), `pause`, `resume`,
|
|
8
8
|
* `step` and `speed` (each with the clock std answered), `capture` (its file), `script-error` (with its `phase`) and
|
|
9
|
-
* `play-stop`. An agent reads them back with `cyclotron play-log` or `editor.contributedVerb('
|
|
9
|
+
* `play-stop`. An agent reads them back with `cyclotron play-log` or `editor.contributedVerb('play-log')` in
|
|
10
10
|
* `eval`: the session's console feed keeps warnings and errors only.
|
|
11
11
|
*
|
|
12
12
|
* WHEN EACH HAPPENED: a trace entry carries its page's own time, its trace's step (std's trace format 3: `s`, sixty a
|
|
13
|
-
* second of the page's
|
|
14
|
-
* elapsed seconds since its `play-start` as `simT`, and the newest trace step heard
|
|
15
|
-
* run's seconds, a page's from its own start.
|
|
13
|
+
* second of the page's game clock, its `performance.now` on a live page) as `tick` and that step in seconds as `simT`;
|
|
14
|
+
* Play's own entries carry the run's elapsed seconds since its `play-start` as `simT`, and the newest trace step heard
|
|
15
|
+
* as `tick`. So `--since` reads the run's seconds, a page's from its own start.
|
|
16
16
|
*
|
|
17
|
-
* ONE LOG
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* once that run has ended its handle writes nothing, so a stale timer of an old run cannot reach the next run's log.
|
|
17
|
+
* ONE LOG, the latest run's. Each Play replaces it, and it outlives Stop so a finished run can be read. The run writes
|
|
18
|
+
* through the {@link PlayLogRun} handle Play started: once that run has ended its handle writes nothing, so a stale
|
|
19
|
+
* timer of an old run cannot reach the next run's log.
|
|
21
20
|
*
|
|
22
|
-
* BOUNDED AND PAGE-LIFETIME. The newest {@link
|
|
23
|
-
* ones are counted in `dropped`); one entry's facts are kept as JSON of at most {@link
|
|
21
|
+
* BOUNDED AND PAGE-LIFETIME. The newest {@link PLAY_LOG_CAPACITY} entries of a run are kept in a ring (older
|
|
22
|
+
* ones are counted in `dropped`); one entry's facts are kept as JSON of at most {@link PLAY_LOG_FACTS_CHARS}
|
|
24
23
|
* characters. It is never stored: a reload of the page forgets it, as it forgets the run.
|
|
25
24
|
*
|
|
26
|
-
* THE GAME PANEL DRAWS IT LIVE: {@link
|
|
27
|
-
*
|
|
25
|
+
* THE GAME PANEL DRAWS IT LIVE: {@link subscribePlayLog} hears every write, and {@link tailPlayLog} reads the newest
|
|
26
|
+
* entries without copying the ring.
|
|
28
27
|
*/
|
|
29
28
|
|
|
30
|
-
/** One entry of
|
|
31
|
-
export interface
|
|
29
|
+
/** One entry of the play log (`play-log`): read from a player's page's trace, or written by Play. */
|
|
30
|
+
export interface PlayLogEntry {
|
|
32
31
|
/** The entry's place in its run, from 0: a gap in `seq` is entries the bounded log dropped. */
|
|
33
32
|
readonly seq: number;
|
|
34
33
|
/** Wall-clock ms. */
|
|
@@ -44,19 +43,16 @@ export interface ModelPlayLogEntry {
|
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
/** Entries a run's ring keeps; the oldest beyond this are dropped and counted. */
|
|
47
|
-
const
|
|
46
|
+
const PLAY_LOG_CAPACITY = 5000;
|
|
48
47
|
/** The longest JSON one entry's facts keep; longer facts are kept truncated, as a string. */
|
|
49
|
-
const
|
|
48
|
+
const PLAY_LOG_FACTS_CHARS = 2048;
|
|
50
49
|
|
|
51
|
-
export interface
|
|
50
|
+
export interface PlayLogReading {
|
|
52
51
|
/** The run is still playing; false after Stop, or when nothing has played. */
|
|
53
52
|
readonly playing: boolean;
|
|
54
|
-
/**
|
|
55
|
-
readonly documentId: string | null;
|
|
53
|
+
/** What the run ran (the game's dev server command); null when nothing has played. */
|
|
56
54
|
readonly script: string | null;
|
|
57
55
|
readonly startedAt: number | null;
|
|
58
|
-
/** Every model document with a log, so a read can name another. */
|
|
59
|
-
readonly documents: readonly string[];
|
|
60
56
|
/** The run's clock now. */
|
|
61
57
|
readonly simT: number;
|
|
62
58
|
readonly tick: number;
|
|
@@ -65,12 +61,10 @@ export interface ModelPlayLogReading {
|
|
|
65
61
|
readonly total: number;
|
|
66
62
|
readonly dropped: number;
|
|
67
63
|
/** The kept entries that match the read's filters, oldest first. */
|
|
68
|
-
readonly entries: readonly
|
|
64
|
+
readonly entries: readonly PlayLogEntry[];
|
|
69
65
|
}
|
|
70
66
|
|
|
71
|
-
export interface
|
|
72
|
-
/** The model document whose log to read; the active Play's when omitted. */
|
|
73
|
-
readonly documentId?: string;
|
|
67
|
+
export interface PlayLogQuery {
|
|
74
68
|
/** Only entries at or after this game time (seconds). */
|
|
75
69
|
readonly since?: number;
|
|
76
70
|
/** Only entries of this kind. */
|
|
@@ -78,32 +72,31 @@ export interface ModelPlayLogQuery {
|
|
|
78
72
|
}
|
|
79
73
|
|
|
80
74
|
interface Run {
|
|
81
|
-
readonly documentId: string;
|
|
82
75
|
readonly script: string;
|
|
83
76
|
readonly startedAt: number;
|
|
84
77
|
playing: boolean;
|
|
85
78
|
simT: number;
|
|
86
79
|
tick: number;
|
|
87
|
-
readonly ring:
|
|
80
|
+
readonly ring: PlayLogEntry[];
|
|
88
81
|
total: number;
|
|
89
82
|
/** Every kind this run has written, for the panel's filter. */
|
|
90
83
|
readonly kinds: Set<string>;
|
|
91
84
|
}
|
|
92
85
|
|
|
93
86
|
/** The run's door onto its own log: inert once the run has ended. */
|
|
94
|
-
export interface
|
|
87
|
+
export interface PlayLogRun {
|
|
95
88
|
/** An entry, stamped with `at` (a trace line's page time) or else the run's elapsed seconds. */
|
|
96
|
-
append(source:
|
|
89
|
+
append(source: PlayLogEntry['source'], kind: string, facts?: unknown, at?: { readonly simT: number; readonly tick: number }): void;
|
|
97
90
|
/** `play-stop`, then nothing more is written; the entries stay readable. */
|
|
98
91
|
end(facts?: Record<string, unknown>): void;
|
|
99
92
|
}
|
|
100
93
|
|
|
101
94
|
// One registry per page, whichever copy of this module a contribution loaded it through: the
|
|
102
95
|
// run writes it, the session verb reads it from the command module.
|
|
103
|
-
const key = Symbol.for('volter.
|
|
104
|
-
const page = globalThis as typeof globalThis & { [key]?:
|
|
105
|
-
const
|
|
106
|
-
const listenersKey = Symbol.for('volter.
|
|
96
|
+
const key = Symbol.for('volter.play-log');
|
|
97
|
+
const page = globalThis as typeof globalThis & { [key]?: { run: Run | null } };
|
|
98
|
+
const held: { run: Run | null } = page[key] ??= { run: null };
|
|
99
|
+
const listenersKey = Symbol.for('volter.play-log.listeners');
|
|
107
100
|
const pageListeners = globalThis as typeof globalThis & { [listenersKey]?: Set<() => void> };
|
|
108
101
|
const listeners: Set<() => void> = pageListeners[listenersKey] ??= new Set();
|
|
109
102
|
|
|
@@ -113,8 +106,8 @@ function publish(): void {
|
|
|
113
106
|
}
|
|
114
107
|
}
|
|
115
108
|
|
|
116
|
-
/** Hear every write to
|
|
117
|
-
export function
|
|
109
|
+
/** Hear every write to the log (several may land in one frame; coalesce). */
|
|
110
|
+
export function subscribePlayLog(listener: () => void): () => void {
|
|
118
111
|
listeners.add(listener);
|
|
119
112
|
return () => { listeners.delete(listener); };
|
|
120
113
|
}
|
|
@@ -153,39 +146,36 @@ function keptFacts(facts: unknown): Record<string, unknown> | undefined {
|
|
|
153
146
|
try { json = JSON.stringify(ordered); }
|
|
154
147
|
catch (error) { return { unserializable: error instanceof Error ? error.message : String(error) }; }
|
|
155
148
|
if (json === undefined) return undefined;
|
|
156
|
-
if (json.length >
|
|
157
|
-
if (json.length >
|
|
149
|
+
if (json.length > PLAY_LOG_FACTS_CHARS && ordered !== facts) json = JSON.stringify(shortened(ordered as Record<string, unknown>));
|
|
150
|
+
if (json.length > PLAY_LOG_FACTS_CHARS) return { truncated: json.slice(0, PLAY_LOG_FACTS_CHARS) };
|
|
158
151
|
// A snapshot, so a script that goes on mutating what it logged does not rewrite history.
|
|
159
152
|
const value: unknown = JSON.parse(json);
|
|
160
153
|
return typeof value === 'object' && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : { value };
|
|
161
154
|
}
|
|
162
155
|
|
|
163
|
-
function append(run: Run, source:
|
|
156
|
+
function append(run: Run, source: PlayLogEntry['source'], kind: string, facts?: unknown, at?: { readonly simT: number; readonly tick: number }): void {
|
|
164
157
|
try {
|
|
165
158
|
const kept = keptFacts(facts);
|
|
166
159
|
const t = Date.now();
|
|
167
160
|
const simT = at?.simT ?? (t - run.startedAt) / 1000;
|
|
168
161
|
if (at !== undefined) run.tick = Math.max(run.tick, at.tick);
|
|
169
162
|
run.simT = Math.max(run.simT, (t - run.startedAt) / 1000);
|
|
170
|
-
const entry:
|
|
163
|
+
const entry: PlayLogEntry = {
|
|
171
164
|
seq: run.total, t, simT, tick: at?.tick ?? run.tick, kind: String(kind), source,
|
|
172
165
|
...(kept ? { facts: kept } : {}),
|
|
173
166
|
};
|
|
174
|
-
run.ring[run.total %
|
|
167
|
+
run.ring[run.total % PLAY_LOG_CAPACITY] = entry;
|
|
175
168
|
run.total += 1;
|
|
176
169
|
run.kinds.add(entry.kind);
|
|
177
170
|
} catch { /* A log that cannot be written is not the run's failure. */ }
|
|
178
171
|
publish();
|
|
179
172
|
}
|
|
180
173
|
|
|
181
|
-
/**
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
runs.delete(documentId);
|
|
187
|
-
runs.set(documentId, run);
|
|
188
|
-
append(run, 'play', 'play-start', { documentId, script });
|
|
174
|
+
/** A run started: the log replaced by an empty one at zero, opened by `play-start`. */
|
|
175
|
+
export function beginPlayLog(script: string): PlayLogRun {
|
|
176
|
+
const run: Run = { script, startedAt: Date.now(), playing: true, simT: 0, tick: 0, ring: [], total: 0, kinds: new Set() };
|
|
177
|
+
held.run = run;
|
|
178
|
+
append(run, 'play', 'play-start', { script });
|
|
189
179
|
return {
|
|
190
180
|
append(source, kind, facts, at) { if (run.playing) append(run, source, kind, facts, at); },
|
|
191
181
|
end(facts) {
|
|
@@ -196,52 +186,46 @@ export function beginModelPlayLog(documentId: string, script: string): ModelPlay
|
|
|
196
186
|
};
|
|
197
187
|
}
|
|
198
188
|
|
|
199
|
-
function
|
|
200
|
-
const
|
|
201
|
-
return started.find((run) => run.playing) ?? started[0];
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export function readModelPlayLog(query: ModelPlayLogQuery = {}): ModelPlayLogReading {
|
|
205
|
-
const run = query.documentId === undefined ? activeRun() : runs.get(query.documentId);
|
|
206
|
-
const documents = [...runs.keys()];
|
|
189
|
+
export function readPlayLog(query: PlayLogQuery = {}): PlayLogReading {
|
|
190
|
+
const { run } = held;
|
|
207
191
|
if (!run) {
|
|
208
192
|
return {
|
|
209
|
-
playing: false,
|
|
210
|
-
simT: 0, tick: 0, capacity:
|
|
193
|
+
playing: false, script: null, startedAt: null,
|
|
194
|
+
simT: 0, tick: 0, capacity: PLAY_LOG_CAPACITY, total: 0, dropped: 0, entries: [],
|
|
211
195
|
};
|
|
212
196
|
}
|
|
213
|
-
const dropped = Math.max(0, run.total -
|
|
214
|
-
const entries:
|
|
197
|
+
const dropped = Math.max(0, run.total - PLAY_LOG_CAPACITY);
|
|
198
|
+
const entries: PlayLogEntry[] = [];
|
|
215
199
|
for (let seq = dropped; seq < run.total; seq++) {
|
|
216
|
-
const entry = run.ring[seq %
|
|
200
|
+
const entry = run.ring[seq % PLAY_LOG_CAPACITY]!;
|
|
217
201
|
if (query.since !== undefined && entry.simT < query.since) continue;
|
|
218
202
|
if (query.kind !== undefined && entry.kind !== query.kind) continue;
|
|
219
203
|
entries.push(entry);
|
|
220
204
|
}
|
|
221
205
|
return {
|
|
222
|
-
playing: run.playing,
|
|
206
|
+
playing: run.playing, script: run.script, startedAt: run.startedAt,
|
|
223
207
|
simT: run.playing ? Math.max(run.simT, (Date.now() - run.startedAt) / 1000) : run.simT, tick: run.tick,
|
|
224
|
-
capacity:
|
|
208
|
+
capacity: PLAY_LOG_CAPACITY, total: run.total, dropped, entries,
|
|
225
209
|
};
|
|
226
210
|
}
|
|
227
211
|
|
|
228
|
-
export interface
|
|
229
|
-
/** Entries the
|
|
212
|
+
export interface PlayLogTail {
|
|
213
|
+
/** Entries the latest run has written; 0 when nothing has played. */
|
|
230
214
|
readonly total: number;
|
|
231
215
|
/** Every kind that run has written, in the order first seen. */
|
|
232
216
|
readonly kinds: readonly string[];
|
|
233
217
|
/** The newest kept entries (of the asked kind), at most `last`, oldest first. */
|
|
234
|
-
readonly entries: readonly
|
|
218
|
+
readonly entries: readonly PlayLogEntry[];
|
|
235
219
|
}
|
|
236
220
|
|
|
237
|
-
/** The newest `last` entries of
|
|
238
|
-
export function
|
|
239
|
-
const run =
|
|
221
|
+
/** The newest `last` entries of the log, walked from the newest end. */
|
|
222
|
+
export function tailPlayLog(last: number, kind?: string): PlayLogTail {
|
|
223
|
+
const { run } = held;
|
|
240
224
|
if (!run) return { total: 0, kinds: [], entries: [] };
|
|
241
|
-
const oldest = Math.max(0, run.total -
|
|
242
|
-
const entries:
|
|
225
|
+
const oldest = Math.max(0, run.total - PLAY_LOG_CAPACITY);
|
|
226
|
+
const entries: PlayLogEntry[] = [];
|
|
243
227
|
for (let seq = run.total - 1; seq >= oldest && entries.length < last; seq--) {
|
|
244
|
-
const entry = run.ring[seq %
|
|
228
|
+
const entry = run.ring[seq % PLAY_LOG_CAPACITY]!;
|
|
245
229
|
if (kind === undefined || entry.kind === kind) entries.push(entry);
|
|
246
230
|
}
|
|
247
231
|
return { total: run.total, kinds: [...run.kinds], entries: entries.reverse() };
|
package/src/std-controls.ts
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* Colyseus has no pause for a room.
|
|
11
11
|
* - `inspect`: `{ control: 'inspect' }`, answered with the scene the page last drew and its labelled screen nodes,
|
|
12
12
|
* `{ objects, screen, truncated? }` (at most 2000 objects and 500 nodes, `truncated` saying how many were left out).
|
|
13
|
-
* - `act`: `{ control: 'act', actions, observe? }`, std-driver/
|
|
14
|
-
* with the observation (std-driver/
|
|
13
|
+
* - `act`: `{ control: 'act', actions, observe? }`, std-driver/3 `Action`s through the page's own input path, answered
|
|
14
|
+
* with the observation (std-driver/3 `Observation`) where `observe` asks for one, else `{ ok: true }`. A refused
|
|
15
15
|
* action fails the whole control, the actions before it already taken.
|
|
16
16
|
*
|
|
17
17
|
* `clock`, `inspect` and `act` need a device of exactly one page (a tab or a frame, as each of Play's players is).
|
|
@@ -62,7 +62,7 @@ export function stdClock(device: string, asked: StdClockAction): Promise<StdCloc
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/** One object of the scene a page last drew, as std's inspect reads it. */
|
|
65
|
-
|
|
65
|
+
interface StdInspectedObject {
|
|
66
66
|
readonly name: string;
|
|
67
67
|
/** three's own `type`. */
|
|
68
68
|
readonly type: string;
|
|
@@ -83,8 +83,8 @@ export interface StdInspection {
|
|
|
83
83
|
readonly truncated?: { readonly objects: number; readonly screen: number };
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
/** One labelled node of a page's screen, as std's inspect names it (std-driver/
|
|
87
|
-
|
|
86
|
+
/** One labelled node of a page's screen, as std's inspect names it (std-driver/3's `NodeObserved`). */
|
|
87
|
+
interface StdScreenNode {
|
|
88
88
|
readonly role?: string;
|
|
89
89
|
readonly name?: string;
|
|
90
90
|
readonly [key: string]: unknown;
|
|
@@ -94,13 +94,13 @@ export function stdInspect(device: string): Promise<StdInspection> {
|
|
|
94
94
|
return control<StdInspection>(device, { control: 'inspect' });
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
/** One std-driver/
|
|
97
|
+
/** One std-driver/3 action: a key pressed or let go, or the pointer at a point of the page (0 to 1, x right, y down)
|
|
98
98
|
* with `button` pressed or let go. */
|
|
99
99
|
export type StdAction =
|
|
100
100
|
| { readonly key: { readonly key: string; readonly down: boolean } }
|
|
101
101
|
| { readonly pointer: { readonly x: number; readonly y: number; readonly button?: number; readonly down?: boolean } };
|
|
102
102
|
|
|
103
|
-
/** Actions taken through the page's own input path, as a player's own; the observation after them (std-driver/
|
|
103
|
+
/** Actions taken through the page's own input path, as a player's own; the observation after them (std-driver/3's
|
|
104
104
|
* `Observation`) where asked, else `{ ok: true }`. */
|
|
105
105
|
export function stdAct(device: string, actions: readonly StdAction[], observe = false): Promise<unknown> {
|
|
106
106
|
return control(device, { control: 'act', actions, ...(observe ? { observe: true } : {}) });
|
package/src/std-export.ts
CHANGED
|
@@ -49,14 +49,6 @@ function publish(next: StdExport): void {
|
|
|
49
49
|
for (const listener of listeners) listener();
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
/** Whether the Game panel's Export menu is open: its Escape closes it alone, never the game's view as well
|
|
53
|
-
* (`std-game.ts`'s view keys, which hear a key before the menu does). */
|
|
54
|
-
let menuOpen = false;
|
|
55
|
-
export const exportMenuOpen = (): boolean => menuOpen;
|
|
56
|
-
export function setExportMenuOpen(open: boolean): void {
|
|
57
|
-
menuOpen = open;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
52
|
/** The latest export, running or ended; null before any. */
|
|
61
53
|
export const stdExport = (): StdExport | null => current;
|
|
62
54
|
|
package/src/std-game-adapter.ts
CHANGED
|
@@ -3,18 +3,24 @@
|
|
|
3
3
|
* `std.json`). A std game carries no editor code: std's design has the editor add nothing to the game (design 8), so
|
|
4
4
|
* the adapter a Cyclotron project would keep in `editor/volter.adapter.ts` is Play's. It is the one a models project
|
|
5
5
|
* gets from `cyclotron create`, Blender's layout, keymap and properties inspector, with the game's Blender files as its
|
|
6
|
-
* documents: every `.blend` under `levels/` and every prefab's under `prefabs/` (std's `prefabs/<name>/<name>.blend`)
|
|
7
|
-
*
|
|
6
|
+
* documents: every `.blend` under `levels/` and every prefab's under `prefabs/` (std's `prefabs/<name>/<name>.blend`),
|
|
7
|
+
* and each level (`levels/<Name>Level.tsx`) a document of its own (`stdLevelDocuments`), the main level the one the
|
|
8
|
+
* project opens on.
|
|
8
9
|
*/
|
|
9
10
|
import { defineAdapter } from '@volter/project/adapter/adapter-module';
|
|
10
11
|
import { ModelLayout } from '@volter/editor-blender/layouts';
|
|
11
12
|
import { blenderKeymap } from '@volter/editor-blender/looks';
|
|
12
13
|
// (the game's dev server serves this module, so the SDK's change channel it brings is that server's: it joins the
|
|
13
|
-
// editor's page to the server's file events, which a std game's own modules never import, and
|
|
14
|
+
// editor's page to the server's file events, which a std game's own modules never import, and a level's document and
|
|
14
15
|
// Play's remount hear each save's export through it)
|
|
15
16
|
import '@volter/sdk/project-module-changes';
|
|
16
17
|
|
|
17
18
|
export default defineAdapter({
|
|
18
19
|
editor: { Layout: ModelLayout, keymap: blenderKeymap, inspector: 'properties' },
|
|
19
|
-
documents: {
|
|
20
|
+
documents: {
|
|
21
|
+
find: [
|
|
22
|
+
{ finder: 'modelsFromBlendFiles', include: ['levels/**/*.blend', 'prefabs/**/*.blend'] },
|
|
23
|
+
{ finder: 'stdLevelDocuments', include: ['levels/**/*.tsx', 'levels/**/*.blend'] },
|
|
24
|
+
],
|
|
25
|
+
},
|
|
20
26
|
});
|