158 lines
6.2 KiB
JavaScript
158 lines
6.2 KiB
JavaScript
"use strict";
|
|
|
|
(function (global) {
|
|
function hashString(value = "") {
|
|
const str = String(value || "");
|
|
let h = 2166136261;
|
|
for (let i = 0; i < str.length; i++) {
|
|
h ^= str.charCodeAt(i);
|
|
h = Math.imul(h, 16777619);
|
|
}
|
|
return (h >>> 0).toString(36);
|
|
}
|
|
|
|
function worldSeedFromWords(high = 0, low = 0) {
|
|
return `w${(Number(high) >>> 0).toString(16).padStart(8, "0")}${(Number(low) >>> 0).toString(16).padStart(8, "0")}`;
|
|
}
|
|
|
|
function createWorldSeed() {
|
|
const cryptoObj = global.crypto;
|
|
if (cryptoObj?.getRandomValues) {
|
|
const buf = new Uint32Array(2);
|
|
cryptoObj.getRandomValues(buf);
|
|
return worldSeedFromWords(buf[0], buf[1]);
|
|
}
|
|
const high = Math.floor(Date.now() / 0x100000000) ^ Math.floor(Math.random() * 0xffffffff);
|
|
const low = (Date.now() >>> 0) ^ Math.floor(Math.random() * 0xffffffff);
|
|
return worldSeedFromWords(high, low);
|
|
}
|
|
|
|
function ensureWorldSeed(worldRef = null) {
|
|
if (!worldRef) return createWorldSeed();
|
|
if (!worldRef.worldSeed) worldRef.worldSeed = createWorldSeed();
|
|
if (!Number.isFinite(worldRef.birthSerial)) worldRef.birthSerial = 0;
|
|
return worldRef.worldSeed;
|
|
}
|
|
|
|
function childSeed(worldSeed = "", serial = 0, parentSeeds = []) {
|
|
const safeSerial = Math.max(0, Math.floor(Number(serial) || 0));
|
|
const parents = (parentSeeds || []).filter(Boolean).join("+");
|
|
// \u533a\u5207\u308a\u3092\u5165\u308c\u3066\u304a\u304f\u3068\u3001\u5c06\u6765 birthSeed \u81ea\u4f53\u3092\u4fdd\u5b58\u3057\u306a\u3044\u5f62\u5f0f\u3078\u79fb\u308b\u6642\u306b
|
|
// birthSerial \u3092\u5b89\u5168\u306b\u53d6\u308a\u51fa\u305b\u308b\u3002
|
|
return `b${safeSerial.toString(36)}_${hashString(`${worldSeed}|${safeSerial}|${parents}`)}`;
|
|
}
|
|
|
|
function serialFromBirthSeed(seed = "") {
|
|
const match = /^b([0-9a-z]+)_/i.exec(String(seed || ""));
|
|
if (!match) return 0;
|
|
const n = parseInt(match[1], 36);
|
|
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
}
|
|
|
|
function prepareAddOptions(worldRef, opts = {}) {
|
|
const next = { ...(opts || {}) };
|
|
ensureWorldSeed(worldRef);
|
|
if (!next.birthSeed) {
|
|
worldRef.birthSerial = Math.max(0, Number(worldRef.birthSerial) || 0) + 1;
|
|
next.birthSerial = worldRef.birthSerial;
|
|
// Save schema 33 derives identity from worldSeed + birthSerial. Parentage is
|
|
// stored independently, so it must not be duplicated inside birthSeed.
|
|
next.birthSeed = childSeed(worldRef.worldSeed, next.birthSerial, []);
|
|
} else {
|
|
if (!Number.isFinite(next.birthSerial)) {
|
|
const parsedSerial = serialFromBirthSeed(next.birthSeed);
|
|
if (parsedSerial > 0) next.birthSerial = parsedSerial;
|
|
}
|
|
if (Number.isFinite(next.birthSerial)) {
|
|
worldRef.birthSerial = Math.max(Number(worldRef.birthSerial) || 0, Number(next.birthSerial) || 0);
|
|
}
|
|
}
|
|
if (!next.familyKey) next.familyKey = next.birthSeed;
|
|
return next;
|
|
}
|
|
|
|
function seedUnit(seed = "", key = "") {
|
|
return stableUnit(seed, key);
|
|
}
|
|
|
|
function seedPick(seed = "", key = "", list = []) {
|
|
if (!list?.length) return null;
|
|
const i = Math.min(list.length - 1, Math.floor(seedUnit(seed, key) * list.length));
|
|
return list[i];
|
|
}
|
|
|
|
function seededName(seed = "") {
|
|
const len = 2 + Math.floor(seedUnit(seed, "name.len") * 6); // 2..7
|
|
const hasStop = len >= 2 && seedUnit(seed, "name.stop") < 0.30;
|
|
const middleLen = Math.max(0, len - 1 - (hasStop ? 1 : 0));
|
|
let name = "\u306f";
|
|
for (let i = 0; i < middleLen; i++) name += seedUnit(seed, `name.u.${i}`) < 0.64 ? "\u3046" : "\u3045";
|
|
if (hasStop) name += "\u3063";
|
|
return name;
|
|
}
|
|
|
|
function seededType(seed = "") {
|
|
const list = typeof spawnableSprites === "function" ? spawnableSprites().map(s => s.id).filter(Boolean) : ["smile"];
|
|
return seedPick(seed, "sprite.base", list) || "smile";
|
|
}
|
|
|
|
function seededGoodMode(seed = "") {
|
|
const list = typeof displayNormalSprites === "function" ? displayNormalSprites() : ["smile", "jito", "normal_smirk"];
|
|
return seedPick(seed, "sprite.good", list) || "smile";
|
|
}
|
|
|
|
function seededPersonality(seed = "") {
|
|
if (typeof randomBirthPersonality === "function") return randomBirthPersonality(seed);
|
|
return { aggression: 0, openness: 0, sociability: 0, neuroticism: 0 };
|
|
}
|
|
|
|
function seededGenetics(seed = "") {
|
|
if (typeof normalizeGenetics === "function") return normalizeGenetics(null, seed);
|
|
return { lifeSpanMul: 1, attackMul: 1, speedMul: 1, sizeMul: 1, temperatureOffset: 0 };
|
|
}
|
|
|
|
function seededAdultScale(seed = "", genetics = null) {
|
|
if (typeof adultScaleFromGenetics === "function") return adultScaleFromGenetics(seed, genetics);
|
|
return 0.28;
|
|
}
|
|
|
|
function seededLifeSpan(seed = "", genetics = null) {
|
|
const g = genetics || seededGenetics(seed);
|
|
const base = 1400 + seedUnit(seed, "life.span") * 800;
|
|
let span = base * (g.lifeSpanMul || 1);
|
|
if (span < 900) span = (1200 + seedUnit(seed, "life.span.low") * 600) * (g.lifeSpanMul || 1);
|
|
return span;
|
|
}
|
|
|
|
function birthProfile(seed = "", opts = {}) {
|
|
const zunchiSlave = Boolean(opts.isZunchiSlave || opts.zunchiSlaveLocked);
|
|
const genetics = opts.genetics && typeof normalizeGenetics === "function" ? normalizeGenetics(opts.genetics, seed) : seededGenetics(seed);
|
|
const personality = zunchiSlave && typeof zunchiSlavePersonalityValue === "function" ? zunchiSlavePersonalityValue() : seededPersonality(seed);
|
|
return {
|
|
birthSeed: seed,
|
|
name: zunchiSlave ? "\u305a\u3093\u3061\u3069\u308c\u3044" : seededName(seed),
|
|
type: zunchiSlave ? "zunchi_slave" : seededType(seed),
|
|
goodMode: zunchiSlave ? "zunchi_slave" : seededGoodMode(seed),
|
|
birthPersonality: personality,
|
|
genetics,
|
|
adultScale: seededAdultScale(seed, genetics),
|
|
lifeSpan: seededLifeSpan(seed, genetics),
|
|
};
|
|
}
|
|
|
|
function personalityArray(seed = "") {
|
|
const p = birthProfile(seed).birthPersonality || {};
|
|
return ["aggression", "openness", "sociability", "neuroticism"].map(k => Math.round(((Number(p[k]) || 0) + 1) * 100));
|
|
}
|
|
|
|
global.TarinaiSeedFactory = Object.freeze({
|
|
createWorldSeed,
|
|
worldSeedFromWords,
|
|
ensureWorldSeed,
|
|
childSeed,
|
|
serialFromBirthSeed,
|
|
prepareAddOptions,
|
|
birthProfile,
|
|
personalityArray,
|
|
});
|
|
})(typeof window !== "undefined" ? window : globalThis);
|