1.9
This commit is contained in:
parent
fc7c94bd69
commit
2cc2f003df
71 changed files with 3359 additions and 1027 deletions
|
|
@ -635,7 +635,7 @@
|
|||
return [field, ground, mood, tick, weather, lastBirthAt, generation, deadCount, seed, birthSerial, tarinaiPopulationLimit, objectLimit];
|
||||
}
|
||||
|
||||
function birthHash32(seed = "") {
|
||||
const birthHash32 = global.TarinaiCoreHelpers?.birthHash32 || (seed => {
|
||||
const match = /^b[0-9a-z]+_([0-9a-z]+)$/i.exec(String(seed || ""));
|
||||
if (match) {
|
||||
const parsed = parseInt(match[1], 36);
|
||||
|
|
@ -648,7 +648,7 @@
|
|||
h = Math.imul(h, 16777619);
|
||||
}
|
||||
return h >>> 0;
|
||||
}
|
||||
});
|
||||
function birthSeedFromDescriptor(descriptor = [], worldSeed = "", fallbackSerial = 1) {
|
||||
const serial = Math.max(1, Math.floor(Number(descriptor?.[0]) || fallbackSerial || 1));
|
||||
const expected = global.TarinaiSeedFactory?.childSeed?.(worldSeed, serial, []) || `b${serial.toString(36)}_0`;
|
||||
|
|
@ -1095,6 +1095,7 @@
|
|||
if (!indexed && (Number(endpoint.x) || 0) !== (Number(row?.[1]) || 0)) mask |= 128;
|
||||
if (!indexed && (Number(endpoint.y) || 0) !== (Number(row?.[2]) || 0)) mask |= 256;
|
||||
if (endpoint.t != null && Number.isFinite(Number(endpoint.t))) mask |= 512;
|
||||
if (endpoint.port) mask |= 1024;
|
||||
writer.u(mask);
|
||||
if (mask & 1) writer.str(endpoint.id);
|
||||
if (mask & 2) writer.str(endpoint.type);
|
||||
|
|
@ -1105,6 +1106,7 @@
|
|||
if (mask & 128) writer.s((Number(endpoint.x) || 0) - (Number(row?.[1]) || 0));
|
||||
if (mask & 256) writer.s((Number(endpoint.y) || 0) - (Number(row?.[2]) || 0));
|
||||
if (mask & 512) writer.u(endpoint.t);
|
||||
if (mask & 1024) writer.str(endpoint.port);
|
||||
}
|
||||
function readPhysicsEndpoint(reader, row = []) {
|
||||
const [present] = readBitFields(reader, [1]);
|
||||
|
|
@ -1124,7 +1126,8 @@
|
|||
const x = (Number(row?.[1]) || 0) + ((mask & 128) ? reader.s() : 0);
|
||||
const y = (Number(row?.[2]) || 0) + ((mask & 256) ? reader.s() : 0);
|
||||
const t = (mask & 512) ? reader.u() : null;
|
||||
return { kind, refIdx, id, type, label, token, lx, ly, center, x, y, t };
|
||||
const port = (mask & 1024) ? reader.str() : "";
|
||||
return { kind, refIdx, id, type, label, token, lx, ly, center, x, y, t, port };
|
||||
}
|
||||
function writePhysicsConstraintExtra(writer, extra, type, row = []) {
|
||||
const constraint = extra?.constraint && typeof extra.constraint === "object" ? extra.constraint : {};
|
||||
|
|
@ -1215,11 +1218,12 @@
|
|||
else if (type === "zunchi") writeFixedSignedDefaults(writer, list, [0, 100, 100], 3);
|
||||
else if (type === "signboard") writeSaveString(writer, list[0] || "");
|
||||
else if (type === "robot_cleaner") writeFixedSignedDefaults(writer, list, [3, 0], 2);
|
||||
else if (type === "circuit_board") writeSaveString(writer, list[0] || "{}");
|
||||
else if (type === "duplicator") writeFixedSignedDefaults(writer, list, [-1], 1);
|
||||
else if (type === "ball" || type === "balloon") writeFixedSignedDefaults(writer, list, [0, 0], 2);
|
||||
else if (type === "ant_nest") writeFixedSignedDefaults(writer, list, [0, 0], 2);
|
||||
else if (type === "ant_corpse" || type === "firecracker" || type === "flame_firecracker" || type === "fire") writeFixedSignedDefaults(writer, list, [0], 1);
|
||||
else if (type === "sticky_bomb") writeFixedSignedDefaults(writer, list, [0, -1], 2);
|
||||
else if (type === "sticky_bomb") writeFixedSignedDefaults(writer, list, [0, -1, 0], 3);
|
||||
else if (type === "nest_box") writeFixedSignedDefaults(writer, list, [0, 0, 0, 0], 4);
|
||||
else if (isPinSaveType(type)) writeFixedSignedDefaults(writer, list, [0, -1, 0, 0, 0], 5);
|
||||
else if (type === "fan") {
|
||||
|
|
@ -1242,11 +1246,12 @@
|
|||
if (type === "zunchi") return readFixedSignedDefaults(reader, [0, 100, 100], 3);
|
||||
if (type === "signboard") return [readSaveString(reader)];
|
||||
if (type === "robot_cleaner") return readFixedSignedDefaults(reader, [3, 0], 2);
|
||||
if (type === "circuit_board") return [readSaveString(reader)];
|
||||
if (type === "duplicator") return readFixedSignedDefaults(reader, [-1], 1);
|
||||
if (type === "ball" || type === "balloon") return readFixedSignedDefaults(reader, [0, 0], 2);
|
||||
if (type === "ant_nest") return readFixedSignedDefaults(reader, [0, 0], 2);
|
||||
if (type === "ant_corpse" || type === "firecracker" || type === "flame_firecracker" || type === "fire") return readFixedSignedDefaults(reader, [0], 1);
|
||||
if (type === "sticky_bomb") return readFixedSignedDefaults(reader, [0, -1], 2);
|
||||
if (type === "sticky_bomb") return readFixedSignedDefaults(reader, [0, -1, 0], 3);
|
||||
if (type === "nest_box") return readFixedSignedDefaults(reader, [0, 0, 0, 0], 4);
|
||||
if (isPinSaveType(type)) return readFixedSignedDefaults(reader, [0, -1, 0, 0, 0], 5);
|
||||
if (type === "fan") {
|
||||
|
|
@ -1459,16 +1464,11 @@
|
|||
return items;
|
||||
}
|
||||
|
||||
const LEGACY_JSON_BINARY_SCHEMA_VERSION = 41;
|
||||
const LEGACY_COMPACT_BINARY_SCHEMA_VERSION = 42;
|
||||
const ACHIEVEMENT_METADATA_BINARY_VERSION = 1;
|
||||
const ACHIEVEMENT_SPELL_BINARY_VERSION = 3;
|
||||
const PREVIOUS_ACHIEVEMENT_SPELL_BINARY_VERSION = 2;
|
||||
const ACHIEVEMENT_MASK_BYTES = 7;
|
||||
const LEGACY_ACHIEVEMENT_PROGRESS_BITS = Object.freeze([7, 4, 7, 12, 5, 5, 5, 11, 6, 10]);
|
||||
const LEGACY_ACHIEVEMENT_PROGRESS_MAX = Object.freeze([100, 15, 100, 3333, 30, 20, 20, 2047, 50, 666]);
|
||||
const ACHIEVEMENT_PROGRESS_BITS = Object.freeze([7, 4, 7, 12, 5, 5, 5, 11, 6, 10, 9, 10]);
|
||||
const ACHIEVEMENT_PROGRESS_MAX = Object.freeze([100, 15, 100, 3333, 30, 20, 20, 2047, 50, 666, 333, 721]);
|
||||
const ACHIEVEMENT_SPELL_BINARY_VERSION = 5;
|
||||
const ACHIEVEMENT_MASK_BYTES = 8;
|
||||
const ACHIEVEMENT_PROGRESS_BITS = Object.freeze([7, 4, 7, 12, 5, 5, 5, 11, 6, 10, 9, 10, 3, 17]);
|
||||
const ACHIEVEMENT_PROGRESS_MAX = Object.freeze([100, 15, 100, 3333, 30, 20, 20, 2047, 50, 666, 333, 721, 7, 131071]);
|
||||
|
||||
function finiteNumberOrNull(value) {
|
||||
const n = Number(value);
|
||||
|
|
@ -1493,34 +1493,14 @@
|
|||
function achievementMaskBytesFromSpell(spell = null) {
|
||||
const bytes = new Uint8Array(ACHIEVEMENT_MASK_BYTES);
|
||||
if (!Array.isArray(spell)) return { bytes, baseMinute: 0, deltas: [], progress: [] };
|
||||
const version = Number(spell[0]);
|
||||
let baseMinute = 0;
|
||||
let deltas = [];
|
||||
let progress = [];
|
||||
if (version === 1) {
|
||||
const maskHex = String(spell[1] || "").toLowerCase();
|
||||
if (!/^[0-9a-f]{14}$/.test(maskHex)) throw new Error("invalid achievement spell mask");
|
||||
for (let i = 0; i < ACHIEVEMENT_MASK_BYTES; i++) bytes[i] = parseInt(maskHex.slice(i * 2, i * 2 + 2), 16);
|
||||
const baseSecond = toSafeUInt(spell[2]);
|
||||
const secondDeltas = Array.isArray(spell[3]) ? spell[3] : [];
|
||||
const absoluteMinutes = secondDeltas.map(delta => Math.floor((baseSecond + toSafeUInt(delta)) / 60));
|
||||
baseMinute = absoluteMinutes.length ? Math.min(...absoluteMinutes) : 0;
|
||||
deltas = absoluteMinutes.map(value => value - baseMinute);
|
||||
progress = Array.isArray(spell[4]) ? spell[4] : [];
|
||||
} else if (version === PREVIOUS_ACHIEVEMENT_SPELL_BINARY_VERSION || version === ACHIEVEMENT_SPELL_BINARY_VERSION) {
|
||||
const low = Math.max(0, Math.min(0xffffffff, Math.floor(Number(spell[1]) || 0)));
|
||||
const high = Math.max(0, Math.min(0xffffff, Math.floor(Number(spell[2]) || 0)));
|
||||
for (let i = 0; i < 4; i++) bytes[i] = Math.floor(low / (2 ** (i * 8))) & 255;
|
||||
for (let i = 0; i < 3; i++) bytes[i + 4] = Math.floor(high / (2 ** (i * 8))) & 255;
|
||||
baseMinute = toSafeUInt(spell[3]);
|
||||
deltas = Array.isArray(spell[4]) ? spell[4].map(toSafeUInt) : [];
|
||||
const rawProgress = Array.isArray(spell[5]) ? spell[5] : [];
|
||||
progress = version === ACHIEVEMENT_SPELL_BINARY_VERSION
|
||||
? rawProgress
|
||||
: [...rawProgress.slice(0, 9), 0, 0, 0];
|
||||
} else {
|
||||
throw new Error("unsupported achievement spell version");
|
||||
}
|
||||
if (Number(spell[0]) !== ACHIEVEMENT_SPELL_BINARY_VERSION) throw new Error("unsupported achievement spell version");
|
||||
const low = Math.max(0, Math.min(0xffffffff, Math.floor(Number(spell[1]) || 0)));
|
||||
const high = Math.max(0, Math.min(0xffffffff, Math.floor(Number(spell[2]) || 0)));
|
||||
for (let i = 0; i < 4; i++) bytes[i] = Math.floor(low / (2 ** (i * 8))) & 255;
|
||||
for (let i = 0; i < 4; i++) bytes[i + 4] = Math.floor(high / (2 ** (i * 8))) & 255;
|
||||
const baseMinute = toSafeUInt(spell[3]);
|
||||
const deltas = Array.isArray(spell[4]) ? spell[4].map(toSafeUInt) : [];
|
||||
const progress = Array.isArray(spell[5]) ? spell[5] : [];
|
||||
let unlockedCount = 0;
|
||||
for (const byte of bytes) {
|
||||
let value = byte;
|
||||
|
|
@ -1529,11 +1509,12 @@
|
|||
if (deltas.length !== unlockedCount) throw new Error("invalid achievement spell timestamps");
|
||||
return { bytes, baseMinute, deltas, progress };
|
||||
}
|
||||
|
||||
function spellFromAchievementMaskBytes(bytes = [], baseMinute = 0, deltas = [], progress = []) {
|
||||
let low = 0;
|
||||
let high = 0;
|
||||
for (let i = 0; i < 4; i++) low += (Number(bytes[i]) || 0) * (2 ** (i * 8));
|
||||
for (let i = 0; i < 3; i++) high += (Number(bytes[i + 4]) || 0) * (2 ** (i * 8));
|
||||
for (let i = 0; i < 4; i++) high += (Number(bytes[i + 4]) || 0) * (2 ** (i * 8));
|
||||
return [ACHIEVEMENT_SPELL_BINARY_VERSION, low >>> 0, high >>> 0, toSafeUInt(baseMinute), deltas.map(toSafeUInt), progress];
|
||||
}
|
||||
function writeAchievementProgress(writer, values = [], bits = ACHIEVEMENT_PROGRESS_BITS, maxima = ACHIEVEMENT_PROGRESS_MAX) {
|
||||
|
|
@ -1568,7 +1549,7 @@
|
|||
for (const delta of normalized.deltas) writer.u(delta);
|
||||
writeAchievementProgress(writer, normalized.progress);
|
||||
}
|
||||
function readAchievementSpell(reader, schema = BINARY_SCHEMA_VERSION) {
|
||||
function readAchievementSpell(reader) {
|
||||
const bytes = reader.raw(ACHIEVEMENT_MASK_BYTES);
|
||||
let unlockedCount = 0;
|
||||
for (const byte of bytes) {
|
||||
|
|
@ -1578,11 +1559,8 @@
|
|||
const baseMinute = reader.u();
|
||||
const deltas = [];
|
||||
for (let i = 0; i < unlockedCount; i++) deltas.push(reader.u());
|
||||
const legacy = schema === LEGACY_COMPACT_BINARY_SCHEMA_VERSION;
|
||||
const progress = legacy
|
||||
? readAchievementProgress(reader, LEGACY_ACHIEVEMENT_PROGRESS_BITS, LEGACY_ACHIEVEMENT_PROGRESS_MAX)
|
||||
: readAchievementProgress(reader);
|
||||
return spellFromAchievementMaskBytes(bytes, baseMinute, deltas, legacy ? [...progress.slice(0, 9), 0, 0, 0] : progress);
|
||||
const progress = readAchievementProgress(reader);
|
||||
return spellFromAchievementMaskBytes(bytes, baseMinute, deltas, progress);
|
||||
}
|
||||
function writeAchievementMetadata(writer, metadata = null, tarinaiCount = 0, itemCount = 0) {
|
||||
const value = metadata && typeof metadata === "object" ? metadata : {};
|
||||
|
|
@ -1749,7 +1727,7 @@
|
|||
const formatHeader = reader.b();
|
||||
const schema = formatHeader & 0x7f;
|
||||
const hasStringTable = Boolean(formatHeader & 0x80);
|
||||
if (![BINARY_SCHEMA_VERSION, LEGACY_COMPACT_BINARY_SCHEMA_VERSION, LEGACY_JSON_BINARY_SCHEMA_VERSION].includes(schema)) throw new Error("unsupported binary save schema");
|
||||
if (schema !== BINARY_SCHEMA_VERSION) throw new Error("unsupported binary save schema");
|
||||
readStringTable(reader, hasStringTable);
|
||||
const w = readWorld(reader);
|
||||
const tCount = reader.u();
|
||||
|
|
@ -1760,12 +1738,7 @@
|
|||
const items = readItemBlocks(reader);
|
||||
let g = null;
|
||||
try {
|
||||
if (schema === LEGACY_JSON_BINARY_SCHEMA_VERSION) {
|
||||
const encodedMetadata = reader.str();
|
||||
g = encodedMetadata ? JSON.parse(encodedMetadata) : null;
|
||||
} else {
|
||||
g = readAchievementMetadata(reader, tCount, items.length, schema);
|
||||
}
|
||||
g = readAchievementMetadata(reader, tCount, items.length, schema);
|
||||
} catch (_) {
|
||||
throw new Error("invalid achievement metadata in binary save");
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue