not baaad
This commit is contained in:
parent
16074232aa
commit
fbdac9ebf1
44 changed files with 2152 additions and 454 deletions
154
js/tarinai_seed_factory.js
Normal file
154
js/tarinai_seed_factory.js
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
"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 createWorldSeed() {
|
||||
const cryptoObj = global.crypto;
|
||||
if (cryptoObj?.getRandomValues) {
|
||||
const buf = new Uint32Array(2);
|
||||
cryptoObj.getRandomValues(buf);
|
||||
return `w${buf[0].toString(36)}${buf[1].toString(36)}`;
|
||||
}
|
||||
return `w${Date.now().toString(36)}${Math.floor(Math.random() * 0xffffffff).toString(36)}`;
|
||||
}
|
||||
|
||||
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("+");
|
||||
// 区切りを入れておくと、将来 birthSeed 自体を保存しない形式へ移る時に
|
||||
// birthSerial を安全に取り出せる。
|
||||
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;
|
||||
const parentSeeds = Array.isArray(next.seedParents)
|
||||
? next.seedParents.map(p => typeof p === "string" ? p : (p?.birthSeed || p?.familyKey || p?.id || ""))
|
||||
: [];
|
||||
next.birthSeed = childSeed(worldRef.worldSeed, next.birthSerial, parentSeeds);
|
||||
} 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 = "") {
|
||||
if (typeof stableUnit === "function") return stableUnit(seed, key);
|
||||
return parseInt(hashString(`${seed}:${key}`).slice(0, 6), 36) / (36 ** 6);
|
||||
}
|
||||
|
||||
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 = "は";
|
||||
for (let i = 0; i < middleLen; i++) name += seedUnit(seed, `name.u.${i}`) < 0.64 ? "う" : "ぅ";
|
||||
if (hasStop) name += "っ";
|
||||
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 };
|
||||
}
|
||||
|
||||
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 = seededGenetics(seed);
|
||||
const personality = zunchiSlave && typeof zunchiSlavePersonalityValue === "function" ? zunchiSlavePersonalityValue() : seededPersonality(seed);
|
||||
return {
|
||||
birthSeed: seed,
|
||||
name: zunchiSlave ? "ずんちどれい" : 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,
|
||||
ensureWorldSeed,
|
||||
childSeed,
|
||||
serialFromBirthSeed,
|
||||
prepareAddOptions,
|
||||
birthProfile,
|
||||
personalityArray,
|
||||
hashString,
|
||||
});
|
||||
})(typeof window !== "undefined" ? window : globalThis);
|
||||
Loading…
Add table
Add a link
Reference in a new issue