nucleation 0.1.184 → 0.2.2
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 +63 -305
- package/api.js +608 -0
- package/nucleation-original.js +52 -28
- package/nucleation.d.ts +144 -131
- package/nucleation_bg.wasm +0 -0
- package/package.json +5 -1
package/api.js
ADDED
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
// Polished JS API for Nucleation, layered over the wasm-bindgen output.
|
|
2
|
+
//
|
|
3
|
+
// The default `nucleation` import still exposes `init`, `SchematicWrapper`,
|
|
4
|
+
// `SchematicBuilderWrapper`, etc. — that surface is unchanged. This sub-module
|
|
5
|
+
// (`nucleation/api`) adds an ergonomic facade analogous to the Python API
|
|
6
|
+
// described in api_upgrade.md.
|
|
7
|
+
//
|
|
8
|
+
// Usage:
|
|
9
|
+
// import init from 'nucleation';
|
|
10
|
+
// import { Schematic, Block, UseBlock } from 'nucleation/api';
|
|
11
|
+
// await init();
|
|
12
|
+
//
|
|
13
|
+
// const schem = Schematic.new('test');
|
|
14
|
+
// schem.setBlock([0, 0, 0], 'minecraft:repeater', { state: { delay: 4 } });
|
|
15
|
+
// schem.save('out.litematic'); // browser: returns Uint8Array if no fs.
|
|
16
|
+
|
|
17
|
+
import init, * as raw from './nucleation.js';
|
|
18
|
+
|
|
19
|
+
export { init };
|
|
20
|
+
export const _raw = raw;
|
|
21
|
+
|
|
22
|
+
// --- Block ----------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
const SNBT_RAW = '__snbt__';
|
|
25
|
+
|
|
26
|
+
export class Block {
|
|
27
|
+
/**
|
|
28
|
+
* @param {string} id
|
|
29
|
+
* @param {object} [opts]
|
|
30
|
+
* @param {Record<string, string|number|boolean>} [opts.state]
|
|
31
|
+
* @param {Record<string, any>} [opts.nbt]
|
|
32
|
+
*/
|
|
33
|
+
constructor(id, { state = null, nbt = null } = {}) {
|
|
34
|
+
this.id = id;
|
|
35
|
+
this.state = state;
|
|
36
|
+
this.nbt = nbt;
|
|
37
|
+
// Cached lazily on first toString() call. Block is otherwise frozen below.
|
|
38
|
+
this._payload = null;
|
|
39
|
+
this._hasExtras = !!(state && Object.keys(state).length) || !!(nbt && Object.keys(nbt).length);
|
|
40
|
+
Object.seal(this); // allow _payload assignment, prevent new props
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Parse "id[k=v,...]{snbt}" form. */
|
|
44
|
+
static parse(s) {
|
|
45
|
+
if (typeof s !== 'string') {
|
|
46
|
+
throw new TypeError(`Block.parse() expects a string, got ${typeof s}`);
|
|
47
|
+
}
|
|
48
|
+
let rest = s.trim();
|
|
49
|
+
let snbt = null;
|
|
50
|
+
if (rest.endsWith('}')) {
|
|
51
|
+
let depth = 0;
|
|
52
|
+
let cut = -1;
|
|
53
|
+
for (let i = rest.length - 1; i >= 0; i--) {
|
|
54
|
+
const ch = rest[i];
|
|
55
|
+
if (ch === '}') depth++;
|
|
56
|
+
else if (ch === '{') {
|
|
57
|
+
depth--;
|
|
58
|
+
if (depth === 0) {
|
|
59
|
+
cut = i;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (cut >= 0) {
|
|
65
|
+
snbt = rest.slice(cut + 1, -1);
|
|
66
|
+
rest = rest.slice(0, cut);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
let state = null;
|
|
70
|
+
if (rest.endsWith(']')) {
|
|
71
|
+
const cut = rest.lastIndexOf('[');
|
|
72
|
+
if (cut >= 0) {
|
|
73
|
+
const inner = rest.slice(cut + 1, -1);
|
|
74
|
+
rest = rest.slice(0, cut);
|
|
75
|
+
state = {};
|
|
76
|
+
for (const kv of inner.split(',')) {
|
|
77
|
+
if (!kv.trim()) continue;
|
|
78
|
+
const eq = kv.indexOf('=');
|
|
79
|
+
if (eq < 0) continue;
|
|
80
|
+
const k = kv.slice(0, eq).trim();
|
|
81
|
+
const v = kv.slice(eq + 1).trim();
|
|
82
|
+
state[k] = coerceStateValue(v);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const nbt = snbt !== null ? { [SNBT_RAW]: snbt } : null;
|
|
87
|
+
return new Block(rest, { state, nbt });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
withState(extra) {
|
|
91
|
+
return new Block(this.id, {
|
|
92
|
+
state: { ...(this.state || {}), ...extra },
|
|
93
|
+
nbt: this.nbt,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
withNbt(extra) {
|
|
98
|
+
return new Block(this.id, {
|
|
99
|
+
state: this.state,
|
|
100
|
+
nbt: { ...(this.nbt || {}), ...extra },
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
toString() {
|
|
105
|
+
if (this._payload !== null) return this._payload;
|
|
106
|
+
let out = this.id;
|
|
107
|
+
if (this.state) {
|
|
108
|
+
const parts = Object.entries(this.state).map(
|
|
109
|
+
([k, v]) => `${k}=${stateToStr(v)}`
|
|
110
|
+
);
|
|
111
|
+
if (parts.length) out += `[${parts.join(',')}]`;
|
|
112
|
+
}
|
|
113
|
+
if (this.nbt) {
|
|
114
|
+
if (SNBT_RAW in this.nbt && Object.keys(this.nbt).length === 1) {
|
|
115
|
+
out += `{${this.nbt[SNBT_RAW]}}`;
|
|
116
|
+
} else {
|
|
117
|
+
out += `{${dictToSnbt(this.nbt)}}`;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
this._payload = out;
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function coerceStateValue(v) {
|
|
126
|
+
if (v === 'true') return true;
|
|
127
|
+
if (v === 'false') return false;
|
|
128
|
+
if (/^-?\d+$/.test(v)) return parseInt(v, 10);
|
|
129
|
+
return v;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function stateToStr(v) {
|
|
133
|
+
if (typeof v === 'boolean') return v ? 'true' : 'false';
|
|
134
|
+
return String(v);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function valueToSnbt(v) {
|
|
138
|
+
if (typeof v === 'boolean') return v ? '1b' : '0b';
|
|
139
|
+
if (typeof v === 'number') {
|
|
140
|
+
return Number.isInteger(v) ? `${v}` : `${v}f`;
|
|
141
|
+
}
|
|
142
|
+
if (typeof v === 'string') {
|
|
143
|
+
return `"${v.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
144
|
+
}
|
|
145
|
+
if (Array.isArray(v)) return `[${v.map(valueToSnbt).join(',')}]`;
|
|
146
|
+
if (v && typeof v === 'object') return `{${dictToSnbt(v)}}`;
|
|
147
|
+
throw new TypeError(`Cannot encode ${typeof v} as SNBT`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function dictToSnbt(d) {
|
|
151
|
+
if (SNBT_RAW in d && Object.keys(d).length === 1) return String(d[SNBT_RAW]);
|
|
152
|
+
return Object.entries(d)
|
|
153
|
+
.map(([k, v]) => `${k}:${valueToSnbt(v)}`)
|
|
154
|
+
.join(',');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// --- Minecraft helpers (chest, sign, text, Item) -------------------------
|
|
158
|
+
//
|
|
159
|
+
// Modern (1.20+) NBT schemas. For pre-1.20 layouts, build the dict by hand
|
|
160
|
+
// or use the `__snbt__` escape hatch in setBlock.
|
|
161
|
+
|
|
162
|
+
export function text(s, opts = {}) {
|
|
163
|
+
if (typeof s !== 'string') {
|
|
164
|
+
throw new TypeError(`text() expects a string, got ${typeof s}`);
|
|
165
|
+
}
|
|
166
|
+
const out = { text: s };
|
|
167
|
+
if (opts.color !== undefined) out.color = opts.color;
|
|
168
|
+
for (const k of ['bold', 'italic', 'underlined', 'strikethrough', 'obfuscated']) {
|
|
169
|
+
if (opts[k] !== undefined) out[k] = opts[k];
|
|
170
|
+
}
|
|
171
|
+
return JSON.stringify(out);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export class Item {
|
|
175
|
+
constructor(id, { count = 1, slot = null, components = null } = {}) {
|
|
176
|
+
this.id = id;
|
|
177
|
+
this.count = count;
|
|
178
|
+
this.slot = slot;
|
|
179
|
+
this.components = components;
|
|
180
|
+
Object.freeze(this);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function coerceItem(x, defaultSlot) {
|
|
185
|
+
if (x instanceof Item) {
|
|
186
|
+
const d = {
|
|
187
|
+
Slot: x.slot != null ? x.slot : defaultSlot,
|
|
188
|
+
id: x.id,
|
|
189
|
+
Count: x.count,
|
|
190
|
+
};
|
|
191
|
+
if (x.components) d.components = { ...x.components };
|
|
192
|
+
return d;
|
|
193
|
+
}
|
|
194
|
+
if (typeof x === 'string') {
|
|
195
|
+
return { Slot: defaultSlot, id: x, Count: 1 };
|
|
196
|
+
}
|
|
197
|
+
if (Array.isArray(x)) {
|
|
198
|
+
if (x.length === 2) {
|
|
199
|
+
const [id, count] = x;
|
|
200
|
+
return { Slot: defaultSlot, id, Count: count };
|
|
201
|
+
}
|
|
202
|
+
if (x.length === 3) {
|
|
203
|
+
const [id, count, components] = x;
|
|
204
|
+
const d = { Slot: defaultSlot, id, Count: count };
|
|
205
|
+
if (components) d.components = { ...components };
|
|
206
|
+
return d;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
throw new TypeError(
|
|
210
|
+
`Unsupported chest item: ${JSON.stringify(x)}. ` +
|
|
211
|
+
`Use Item(...), 'minecraft:foo', or [id, count, components?].`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function chest(items, { name = null, lock = null, lootTable = null } = {}) {
|
|
216
|
+
const out = [];
|
|
217
|
+
if (items && !Array.isArray(items) && typeof items === 'object') {
|
|
218
|
+
// Plain object → explicit-slot map.
|
|
219
|
+
for (const [slotStr, x] of Object.entries(items)) {
|
|
220
|
+
const slot = parseInt(slotStr, 10);
|
|
221
|
+
const d = coerceItem(x, slot);
|
|
222
|
+
d.Slot = slot;
|
|
223
|
+
out.push(d);
|
|
224
|
+
}
|
|
225
|
+
} else {
|
|
226
|
+
let i = 0;
|
|
227
|
+
for (const x of items || []) {
|
|
228
|
+
out.push(coerceItem(x, i));
|
|
229
|
+
i++;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const nbt = { Items: out };
|
|
233
|
+
if (name !== null) nbt.CustomName = name.startsWith('{') ? name : text(name);
|
|
234
|
+
if (lock !== null) nbt.Lock = lock;
|
|
235
|
+
if (lootTable !== null) nbt.LootTable = lootTable;
|
|
236
|
+
return nbt;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function signMessages(lines) {
|
|
240
|
+
const msgs = [];
|
|
241
|
+
for (const line of lines || []) {
|
|
242
|
+
if (line == null || line === '') {
|
|
243
|
+
msgs.push('""');
|
|
244
|
+
} else if (typeof line === 'string') {
|
|
245
|
+
msgs.push(line.startsWith('{') ? line : text(line));
|
|
246
|
+
} else {
|
|
247
|
+
throw new TypeError('Sign line must be string (plain or JSON-component)');
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
while (msgs.length < 4) msgs.push('""');
|
|
251
|
+
if (msgs.length > 4) throw new RangeError('A sign has at most 4 lines per side');
|
|
252
|
+
return msgs;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function sign(lines = [], { back = [], color = 'black', glowing = false, waxed = false } = {}) {
|
|
256
|
+
return {
|
|
257
|
+
front_text: { messages: signMessages(lines), color, has_glowing_text: glowing },
|
|
258
|
+
back_text: { messages: signMessages(back), color, has_glowing_text: glowing },
|
|
259
|
+
is_waxed: waxed,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// --- Events ---------------------------------------------------------------
|
|
264
|
+
|
|
265
|
+
export class UseBlock {
|
|
266
|
+
constructor(pos) {
|
|
267
|
+
this.pos = pos;
|
|
268
|
+
Object.freeze(this);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export class ButtonPress {
|
|
273
|
+
constructor(pos) {
|
|
274
|
+
this.pos = pos;
|
|
275
|
+
Object.freeze(this);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// --- Cursor ---------------------------------------------------------------
|
|
280
|
+
|
|
281
|
+
export class Cursor {
|
|
282
|
+
constructor(schem, origin, step) {
|
|
283
|
+
this._schem = schem;
|
|
284
|
+
this._origin = origin;
|
|
285
|
+
this.pos = origin;
|
|
286
|
+
this.step = step;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
place(block, opts = {}) {
|
|
290
|
+
const offset = opts.offset || [0, 0, 0];
|
|
291
|
+
const target = [
|
|
292
|
+
this.pos[0] + offset[0],
|
|
293
|
+
this.pos[1] + offset[1],
|
|
294
|
+
this.pos[2] + offset[2],
|
|
295
|
+
];
|
|
296
|
+
this._schem.setBlock(target, block, {
|
|
297
|
+
state: opts.state,
|
|
298
|
+
nbt: opts.nbt,
|
|
299
|
+
});
|
|
300
|
+
return this;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
advance(n = 1) {
|
|
304
|
+
this.pos = [
|
|
305
|
+
this.pos[0] + this.step[0] * n,
|
|
306
|
+
this.pos[1] + this.step[1] * n,
|
|
307
|
+
this.pos[2] + this.step[2] * n,
|
|
308
|
+
];
|
|
309
|
+
return this;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
reset() {
|
|
313
|
+
this.pos = this._origin;
|
|
314
|
+
return this;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// --- Schematic ------------------------------------------------------------
|
|
319
|
+
|
|
320
|
+
const LOAD_EXTS = ['.schem', '.litematic', '.nbt', '.schematic', '.mcstructure'];
|
|
321
|
+
|
|
322
|
+
function suffixOf(path) {
|
|
323
|
+
const m = String(path).toLowerCase().match(/\.[^.\/\\]+$/);
|
|
324
|
+
return m ? m[0] : '';
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export class Schematic {
|
|
328
|
+
constructor(rawWrapper, { pack = null } = {}) {
|
|
329
|
+
this.raw = rawWrapper;
|
|
330
|
+
this.pack = pack;
|
|
331
|
+
this._pendingBuilder = null;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** Create a blank schematic. */
|
|
335
|
+
static new(name = 'untitled', { pack = null } = {}) {
|
|
336
|
+
if (typeof name !== 'string') {
|
|
337
|
+
throw new TypeError(`Schematic.new() name must be a string, got ${typeof name}`);
|
|
338
|
+
}
|
|
339
|
+
const w = new raw.SchematicWrapper();
|
|
340
|
+
if (typeof w.set_name === 'function') {
|
|
341
|
+
try { w.set_name(name); } catch {}
|
|
342
|
+
}
|
|
343
|
+
return new Schematic(w, { pack });
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Load from binary data (Uint8Array). Format inferred from the second arg
|
|
348
|
+
* if it ends in a known extension; otherwise from_data() autodetects.
|
|
349
|
+
*/
|
|
350
|
+
static open(data, { hint = null, pack = null } = {}) {
|
|
351
|
+
const w = new raw.SchematicWrapper();
|
|
352
|
+
const ext = hint ? suffixOf(hint) : '';
|
|
353
|
+
if (ext === '.litematic') w.from_litematic(data);
|
|
354
|
+
else if (ext === '.schem' || ext === '.schematic') w.from_schematic(data);
|
|
355
|
+
else if (ext === '.mcstructure') w.from_mcstructure(data);
|
|
356
|
+
else w.from_data(data);
|
|
357
|
+
return new Schematic(w, { pack });
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** Build from an ASCII-art template via SchematicBuilderWrapper. */
|
|
361
|
+
static fromTemplate(template, { name = 'untitled', pack = null } = {}) {
|
|
362
|
+
const builder = raw.SchematicBuilderWrapper.from_template
|
|
363
|
+
? raw.SchematicBuilderWrapper.from_template(template)
|
|
364
|
+
: new raw.SchematicBuilderWrapper().from_template(template);
|
|
365
|
+
if (typeof builder.name === 'function') builder.name(name);
|
|
366
|
+
const schem = new Schematic(/* placeholder */ null, { pack });
|
|
367
|
+
schem._pendingBuilder = builder;
|
|
368
|
+
schem._pendingName = name;
|
|
369
|
+
return schem;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
_ensureBuilt() {
|
|
373
|
+
if (this._pendingBuilder) {
|
|
374
|
+
this.raw = this._pendingBuilder.build();
|
|
375
|
+
this._pendingBuilder = null;
|
|
376
|
+
}
|
|
377
|
+
return this.raw;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Pre-resolve a plain block id to a palette index. Pair with `place()`
|
|
382
|
+
* in hot loops to skip the per-call name lookup. Returns the index.
|
|
383
|
+
*/
|
|
384
|
+
prepareBlock(name) {
|
|
385
|
+
this._ensureBuilt();
|
|
386
|
+
return this.raw.prepareBlock(name);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Place a block by pre-resolved palette index. Pair with `prepareBlock`.
|
|
391
|
+
*
|
|
392
|
+
* Hot-loop pattern (~10 M placements/sec):
|
|
393
|
+
*
|
|
394
|
+
* const stone = schem.prepareBlock('minecraft:stone');
|
|
395
|
+
* const place = schem.raw.place.bind(schem.raw);
|
|
396
|
+
* for (const [x, y, z] of positions) place(x, y, z, stone);
|
|
397
|
+
*/
|
|
398
|
+
place(x, y, z, paletteIndex) {
|
|
399
|
+
this._ensureBuilt();
|
|
400
|
+
this.raw.place(x, y, z, paletteIndex);
|
|
401
|
+
return this;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* setBlock([x, y, z], "id" | Block, { state?, nbt? })
|
|
406
|
+
* setBlock(x, y, z, "id") — legacy form
|
|
407
|
+
*
|
|
408
|
+
* Performance: plain id strings and reused `Block` instances take fast
|
|
409
|
+
* paths (Block payloads cache after first use). For uniform regions or
|
|
410
|
+
* many positions of one block, prefer fill() / raw set_blocks().
|
|
411
|
+
*/
|
|
412
|
+
setBlock(...args) {
|
|
413
|
+
let x, y, z, block, opts;
|
|
414
|
+
if (args.length === 4 && typeof args[0] === 'number') {
|
|
415
|
+
[x, y, z, block] = args;
|
|
416
|
+
opts = null;
|
|
417
|
+
} else if ((args.length === 2 || args.length === 3) && Array.isArray(args[0])) {
|
|
418
|
+
const pos = args[0];
|
|
419
|
+
if (pos.length !== 3 || !pos.every((v) => typeof v === 'number')) {
|
|
420
|
+
throw new TypeError(
|
|
421
|
+
`setBlock((x, y, z), block): first arg must be a 3-tuple of numbers, got [${pos.join(', ')}]`
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
[[x, y, z], block, opts = null] = args;
|
|
425
|
+
} else {
|
|
426
|
+
throw new TypeError(
|
|
427
|
+
`setBlock expected (x, y, z, block) or ([x, y, z], block, opts?), got ${args.length} args`
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
if (typeof block !== 'string' && !(block instanceof Block)) {
|
|
431
|
+
throw new TypeError(`block must be a string or Block, got ${typeof block}`);
|
|
432
|
+
}
|
|
433
|
+
if (opts && typeof opts !== 'object') {
|
|
434
|
+
throw new TypeError(`setBlock opts must be an object, got ${typeof opts}`);
|
|
435
|
+
}
|
|
436
|
+
if (opts && opts.state != null && (typeof opts.state !== 'object' || Array.isArray(opts.state))) {
|
|
437
|
+
throw new TypeError(`setBlock opts.state must be an object, got ${typeof opts.state}`);
|
|
438
|
+
}
|
|
439
|
+
if (opts && opts.nbt != null && (typeof opts.nbt !== 'object' || Array.isArray(opts.nbt))) {
|
|
440
|
+
throw new TypeError(
|
|
441
|
+
`setBlock opts.nbt must be an object, got ${typeof opts.nbt}. ` +
|
|
442
|
+
`For raw SNBT pass nbt: { __snbt__: '<your-snbt>' }.`
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
const inner = this.raw || this._ensureBuilt();
|
|
446
|
+
|
|
447
|
+
// Ultra-fast path: plain id, no opts.
|
|
448
|
+
if (!opts && typeof block === 'string') {
|
|
449
|
+
if (block.indexOf('[') < 0 && block.indexOf('{') < 0) {
|
|
450
|
+
inner.set_block(x, y, z, block);
|
|
451
|
+
return this;
|
|
452
|
+
}
|
|
453
|
+
inner.set_block_from_string(x, y, z, block);
|
|
454
|
+
return this;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Fast path: reused Block, no override opts.
|
|
458
|
+
if (!opts && block instanceof Block) {
|
|
459
|
+
if (!block._hasExtras) {
|
|
460
|
+
inner.set_block(x, y, z, block.id);
|
|
461
|
+
} else if (block.nbt) {
|
|
462
|
+
inner.set_block_from_string(x, y, z, block.toString());
|
|
463
|
+
} else {
|
|
464
|
+
const props = {};
|
|
465
|
+
for (const [k, v] of Object.entries(block.state)) props[k] = stateToStr(v);
|
|
466
|
+
inner.set_block_with_properties(x, y, z, block.id, props);
|
|
467
|
+
}
|
|
468
|
+
return this;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Slow path: opts may augment.
|
|
472
|
+
opts = opts || {};
|
|
473
|
+
let blockObj;
|
|
474
|
+
if (block instanceof Block) blockObj = block;
|
|
475
|
+
else if (typeof block === 'string') {
|
|
476
|
+
if (!opts.state && !opts.nbt && (block.includes('[') || block.includes('{'))) {
|
|
477
|
+
blockObj = Block.parse(block);
|
|
478
|
+
} else {
|
|
479
|
+
blockObj = new Block(block, { state: opts.state, nbt: opts.nbt });
|
|
480
|
+
}
|
|
481
|
+
} else {
|
|
482
|
+
throw new TypeError(`block must be string or Block, got ${typeof block}`);
|
|
483
|
+
}
|
|
484
|
+
const effState = { ...(blockObj.state || {}), ...(opts.state || {}) };
|
|
485
|
+
const effNbt = { ...(blockObj.nbt || {}), ...(opts.nbt || {}) };
|
|
486
|
+
const hasState = Object.keys(effState).length > 0;
|
|
487
|
+
const hasNbt = Object.keys(effNbt).length > 0;
|
|
488
|
+
|
|
489
|
+
if (hasNbt) {
|
|
490
|
+
const snbt = (SNBT_RAW in effNbt && Object.keys(effNbt).length === 1)
|
|
491
|
+
? String(effNbt[SNBT_RAW])
|
|
492
|
+
: dictToSnbt(effNbt);
|
|
493
|
+
let payload = blockObj.id;
|
|
494
|
+
if (hasState) {
|
|
495
|
+
payload += '[' + Object.entries(effState).map(([k, v]) => `${k}=${stateToStr(v)}`).join(',') + ']';
|
|
496
|
+
}
|
|
497
|
+
payload += '{' + snbt + '}';
|
|
498
|
+
inner.set_block_from_string(x, y, z, payload);
|
|
499
|
+
} else if (hasState) {
|
|
500
|
+
const props = {};
|
|
501
|
+
for (const [k, v] of Object.entries(effState)) props[k] = stateToStr(v);
|
|
502
|
+
inner.set_block_with_properties(x, y, z, blockObj.id, props);
|
|
503
|
+
} else {
|
|
504
|
+
inner.set_block(x, y, z, blockObj.id);
|
|
505
|
+
}
|
|
506
|
+
return this;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
map(char, block, { state = null, nbt = null } = {}) {
|
|
510
|
+
if (!this._pendingBuilder) {
|
|
511
|
+
throw new Error('map() is only valid on Schematic.fromTemplate()');
|
|
512
|
+
}
|
|
513
|
+
let payload;
|
|
514
|
+
if (block instanceof Block) payload = block.toString();
|
|
515
|
+
else if (state || nbt) payload = new Block(block, { state, nbt }).toString();
|
|
516
|
+
else payload = block;
|
|
517
|
+
this._pendingBuilder.map(char, payload);
|
|
518
|
+
return this;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
fill([p1, p2], block) {
|
|
522
|
+
const blockId = block instanceof Block ? block.toString() : block;
|
|
523
|
+
const inner = this._ensureBuilt();
|
|
524
|
+
inner.fillCuboid(p1[0], p1[1], p1[2], p2[0], p2[1], p2[2], blockId);
|
|
525
|
+
return this;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
cursor({ origin = [0, 0, 0], step = [1, 0, 0] } = {}) {
|
|
529
|
+
this._ensureBuilt();
|
|
530
|
+
return new Cursor(this, origin, step);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
withPack(pack) {
|
|
534
|
+
this.pack = pack;
|
|
535
|
+
return this;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
copy() {
|
|
539
|
+
const inner = this._ensureBuilt();
|
|
540
|
+
const data = inner.to_litematic();
|
|
541
|
+
const w = new raw.SchematicWrapper();
|
|
542
|
+
w.from_litematic(data);
|
|
543
|
+
return new Schematic(w, { pack: this.pack });
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/** Run a redstone simulation, then sync results back into this schematic. */
|
|
547
|
+
simulate({ ticks = 1, events = [] } = {}) {
|
|
548
|
+
const inner = this._ensureBuilt();
|
|
549
|
+
if (typeof inner.create_simulation_world !== 'function') {
|
|
550
|
+
throw new Error('simulation feature is not available in this WASM build');
|
|
551
|
+
}
|
|
552
|
+
const world = inner.create_simulation_world();
|
|
553
|
+
for (const ev of events) {
|
|
554
|
+
if (ev instanceof UseBlock || ev instanceof ButtonPress) {
|
|
555
|
+
const [x, y, z] = ev.pos;
|
|
556
|
+
world.on_use_block(x, y, z);
|
|
557
|
+
} else {
|
|
558
|
+
throw new TypeError('Unsupported event; use UseBlock or ButtonPress');
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
world.tick(ticks);
|
|
562
|
+
world.sync_to_schematic();
|
|
563
|
+
if (typeof world.into_schematic === 'function') {
|
|
564
|
+
this.raw = world.into_schematic();
|
|
565
|
+
} else if (typeof world.get_schematic === 'function') {
|
|
566
|
+
this.raw = world.get_schematic();
|
|
567
|
+
}
|
|
568
|
+
return this;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Serialize to bytes by format extension; if a Node fs is provided
|
|
573
|
+
* (the default in Node), write to disk.
|
|
574
|
+
*/
|
|
575
|
+
async save(path, { format = null, fs = null } = {}) {
|
|
576
|
+
const inner = this._ensureBuilt();
|
|
577
|
+
const ext = format ? '.' + format : suffixOf(path);
|
|
578
|
+
let bytes;
|
|
579
|
+
if (ext === '.litematic') bytes = inner.to_litematic();
|
|
580
|
+
else if (ext === '.schem' || ext === '.schematic') bytes = inner.to_schematic();
|
|
581
|
+
else if (ext === '.mcstructure') bytes = inner.to_mcstructure();
|
|
582
|
+
else throw new Error(`save: unknown format for ${path}`);
|
|
583
|
+
bytes = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
584
|
+
if (typeof process !== 'undefined' && process.versions?.node) {
|
|
585
|
+
const nodeFs = fs || (await import('node:fs/promises'));
|
|
586
|
+
await nodeFs.writeFile(path, bytes);
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
return bytes;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// --- SchematicBuilder shim ------------------------------------------------
|
|
594
|
+
|
|
595
|
+
export class SchematicBuilder {
|
|
596
|
+
constructor() {
|
|
597
|
+
if (typeof console !== 'undefined' && console.warn) {
|
|
598
|
+
console.warn(
|
|
599
|
+
'[nucleation] SchematicBuilder is deprecated; use Schematic.fromTemplate(...)'
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
this._raw = new raw.SchematicBuilderWrapper();
|
|
603
|
+
}
|
|
604
|
+
name(n) { this._raw.name(n); return this; }
|
|
605
|
+
fromTemplate(t) { this._raw = raw.SchematicBuilderWrapper.from_template(t); return this; }
|
|
606
|
+
map(c, b) { this._raw.map(c, b); return this; }
|
|
607
|
+
build() { return new Schematic(this._raw.build()); }
|
|
608
|
+
}
|
package/nucleation-original.js
CHANGED
|
@@ -4224,6 +4224,19 @@ export class SchematicWrapper {
|
|
|
4224
4224
|
throw takeFromExternrefTable0(ret[0]);
|
|
4225
4225
|
}
|
|
4226
4226
|
}
|
|
4227
|
+
/**
|
|
4228
|
+
* Pre-resolve a plain block id to a palette index. Pair with
|
|
4229
|
+
* `place(x, y, z, idx)` for the absolute fastest per-block placement
|
|
4230
|
+
* in tight loops with multiple unique ids.
|
|
4231
|
+
* @param {string} block_name
|
|
4232
|
+
* @returns {number}
|
|
4233
|
+
*/
|
|
4234
|
+
prepareBlock(block_name) {
|
|
4235
|
+
const ptr0 = passStringToWasm0(block_name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
4236
|
+
const len0 = WASM_VECTOR_LEN;
|
|
4237
|
+
const ret = wasm.schematicwrapper_prepareBlock(this.__wbg_ptr, ptr0, len0);
|
|
4238
|
+
return ret >>> 0;
|
|
4239
|
+
}
|
|
4227
4240
|
/**
|
|
4228
4241
|
* Remove a mobile entity by index. Returns true if removed.
|
|
4229
4242
|
* @param {number} index
|
|
@@ -5065,6 +5078,17 @@ export class SchematicWrapper {
|
|
|
5065
5078
|
SchematicWrapperFinalization.register(this, this.__wbg_ptr, this);
|
|
5066
5079
|
return this;
|
|
5067
5080
|
}
|
|
5081
|
+
/**
|
|
5082
|
+
* Place a block by pre-resolved palette index. Caller must have called
|
|
5083
|
+
* `prepareBlock(name)` first to obtain `palette_index`.
|
|
5084
|
+
* @param {number} x
|
|
5085
|
+
* @param {number} y
|
|
5086
|
+
* @param {number} z
|
|
5087
|
+
* @param {number} palette_index
|
|
5088
|
+
*/
|
|
5089
|
+
place(x, y, z, palette_index) {
|
|
5090
|
+
wasm.schematicwrapper_place(this.__wbg_ptr, x, y, z, palette_index);
|
|
5091
|
+
}
|
|
5068
5092
|
/**
|
|
5069
5093
|
* @returns {Array<any>}
|
|
5070
5094
|
*/
|
|
@@ -5226,6 +5250,34 @@ export class SchematicWrapper {
|
|
|
5226
5250
|
const len0 = WASM_VECTOR_LEN;
|
|
5227
5251
|
wasm.schematicwrapper_set_block(this.__wbg_ptr, x, y, z, ptr0, len0);
|
|
5228
5252
|
}
|
|
5253
|
+
/**
|
|
5254
|
+
* Creates a simulation world for this schematic with default options
|
|
5255
|
+
*
|
|
5256
|
+
* This allows you to simulate redstone circuits and interact with them.
|
|
5257
|
+
* @returns {MchprsWorldWrapper}
|
|
5258
|
+
*/
|
|
5259
|
+
create_simulation_world() {
|
|
5260
|
+
const ret = wasm.schematicwrapper_create_simulation_world(this.__wbg_ptr);
|
|
5261
|
+
if (ret[2]) {
|
|
5262
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
5263
|
+
}
|
|
5264
|
+
return MchprsWorldWrapper.__wrap(ret[0]);
|
|
5265
|
+
}
|
|
5266
|
+
/**
|
|
5267
|
+
* Creates a simulation world for this schematic with custom options
|
|
5268
|
+
*
|
|
5269
|
+
* This allows you to configure simulation behavior like wire state tracking.
|
|
5270
|
+
* @param {SimulationOptionsWrapper} options
|
|
5271
|
+
* @returns {MchprsWorldWrapper}
|
|
5272
|
+
*/
|
|
5273
|
+
create_simulation_world_with_options(options) {
|
|
5274
|
+
_assertClass(options, SimulationOptionsWrapper);
|
|
5275
|
+
const ret = wasm.schematicwrapper_create_simulation_world_with_options(this.__wbg_ptr, options.__wbg_ptr);
|
|
5276
|
+
if (ret[2]) {
|
|
5277
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
5278
|
+
}
|
|
5279
|
+
return MchprsWorldWrapper.__wrap(ret[0]);
|
|
5280
|
+
}
|
|
5229
5281
|
/**
|
|
5230
5282
|
* Generate raw mesh data for custom rendering pipelines.
|
|
5231
5283
|
* @param {ResourcePackWrapper} pack
|
|
@@ -5397,34 +5449,6 @@ export class SchematicWrapper {
|
|
|
5397
5449
|
}
|
|
5398
5450
|
return MeshOutputWrapper.__wrap(ret[0]);
|
|
5399
5451
|
}
|
|
5400
|
-
/**
|
|
5401
|
-
* Creates a simulation world for this schematic with default options
|
|
5402
|
-
*
|
|
5403
|
-
* This allows you to simulate redstone circuits and interact with them.
|
|
5404
|
-
* @returns {MchprsWorldWrapper}
|
|
5405
|
-
*/
|
|
5406
|
-
create_simulation_world() {
|
|
5407
|
-
const ret = wasm.schematicwrapper_create_simulation_world(this.__wbg_ptr);
|
|
5408
|
-
if (ret[2]) {
|
|
5409
|
-
throw takeFromExternrefTable0(ret[1]);
|
|
5410
|
-
}
|
|
5411
|
-
return MchprsWorldWrapper.__wrap(ret[0]);
|
|
5412
|
-
}
|
|
5413
|
-
/**
|
|
5414
|
-
* Creates a simulation world for this schematic with custom options
|
|
5415
|
-
*
|
|
5416
|
-
* This allows you to configure simulation behavior like wire state tracking.
|
|
5417
|
-
* @param {SimulationOptionsWrapper} options
|
|
5418
|
-
* @returns {MchprsWorldWrapper}
|
|
5419
|
-
*/
|
|
5420
|
-
create_simulation_world_with_options(options) {
|
|
5421
|
-
_assertClass(options, SimulationOptionsWrapper);
|
|
5422
|
-
const ret = wasm.schematicwrapper_create_simulation_world_with_options(this.__wbg_ptr, options.__wbg_ptr);
|
|
5423
|
-
if (ret[2]) {
|
|
5424
|
-
throw takeFromExternrefTable0(ret[1]);
|
|
5425
|
-
}
|
|
5426
|
-
return MchprsWorldWrapper.__wrap(ret[0]);
|
|
5427
|
-
}
|
|
5428
5452
|
}
|
|
5429
5453
|
if (Symbol.dispose) SchematicWrapper.prototype[Symbol.dispose] = SchematicWrapper.prototype.free;
|
|
5430
5454
|
|