This commit is contained in:
33333-33333 2026-06-23 11:48:11 +09:00
commit db81a509cf
6 changed files with 56 additions and 21 deletions

View file

@ -267,11 +267,26 @@
return structure;
}
function hydrateStructure(structure) {
if (!structure || !structure.isStructure) return structure;
structure.update = updateStructure;
structure.draw = drawStructure;
structure.damage = damageStructure;
const def = StructureRegistry[structure.type];
if (def) {
structure.roles = structure.roles || { ...def.roles };
structure.needEffects = structure.needEffects || { ...def.needEffects };
structure.r = structure.r || (structure.type === "plushie" ? 14 : 28);
structure.amount = Math.max(0, structure.hp || structure.amount || 1);
}
return structure;
}
function consumeGrassMaterial(grass) {
if (!grass || grass.dead) return false;
grass.amount = Math.max(0, Number(grass.amount || 0) - 34);
grass.growth = clamp((grass.growth || 0.5) - 0.24, 0.04, 1.2);
if (grass.amount <= 1) grass.dead = true;
if (grass.amount <= 1) grass.amount = 0;
return true;
}
@ -406,8 +421,12 @@
const w = world || global.world;
const def = StructureRegistry[this.type];
def?.onDestroyed?.(this, w, breaker);
this.dead = true;
this.amount = 0;
try {
Object.defineProperty(this, "dead", { value: true, configurable: true, writable: true });
} catch (_) {
// Item.dead is a getter derived from amount; amount=0 is enough for those instances.
}
w?.markTerrainDirty?.(`structure-destroyed:${this.type}`);
return true;
}
@ -529,6 +548,7 @@
addNeedShock,
runNeedDecision,
createStructure,
hydrateStructure,
damageStructure,
};

View file

@ -870,8 +870,8 @@ function drawSelectedCard(ctx, world, lighting) {
if (!t || t.dead || !world.tarinai.includes(t)) return;
const w = world.viewportW || world.w;
const h = world.viewportH || world.h;
const cardW = 152;
const cardH = 80;
const cardW = 172;
const cardH = 96;
let x = t.x + 18;
let y = t.y - 100;
x = clamp(x, 10, w - cardW - 10);
@ -889,9 +889,25 @@ function drawSelectedCard(ctx, world, lighting) {
const child = t.juvenile && t.parentToFollow?.() ? " / \u5b50" : "";
const zHint = t.digest > 4.9 ? " / \u305a\u3093\u3061" : "";
ctx.fillText(`${stateLabel(t.state)}${child}${zHint}`, x + 10, y + 35);
const genLabel = (t.displayGeneration && t.displayGeneration()) ? ` \u4e16\u4ee3 ${t.generation}` : "";
ctx.fillText(`\u7dcf\u5408\u7684\u6c17\u5206 ${fmt(t.mood)}${genLabel} \u4f53\u529b ${fmt(t.energy)}`, x + 10, y + 52);
ctx.fillText(`\u30b9\u30c8\u30ec\u30b9 ${fmt(t.stress)} \u7a7a\u8179 ${fmt(t.hunger)}`, x + 10, y + 68);
const needSystem = window.TarinaiNeedSystem;
const needLabels = needSystem?.NEED_LABELS || {
food: "\u6442\u990c",
sleep: "\u7761\u7720",
health: "\u5065\u5eb7",
safety: "\u5b89\u5168",
social: "\u95a2\u4fc2",
fulfill: "\u5145\u8db3",
};
const needKeys = needSystem?.NEED_KEYS || ["food", "sleep", "health", "safety", "social", "fulfill"];
const needValue = key => needSystem?.getNeedDisplayValue ? needSystem.getNeedDisplayValue(t.needs?.[key] || 0) : Math.round((t.needs?.[key] || 0) / 10);
const activeNeed = t.intent?.need || t.need || "";
const rowA = needKeys.slice(0, 3).map(key => `${needLabels[key]}${key === activeNeed ? "\u25b2" : ""} ${needValue(key)}`).join(" ");
const rowB = needKeys.slice(3).map(key => `${needLabels[key]}${key === activeNeed ? "\u25b2" : ""} ${needValue(key)}`).join(" ");
ctx.fillText(rowA, x + 10, y + 52);
ctx.fillText(rowB, x + 10, y + 68);
let actionText = `\u3044\u307e ${t.intent?.actionLabel || stateLabel(t.state)}`;
while (actionText.length > 4 && ctx.measureText(actionText).width > cardW - 20) actionText = `${actionText.slice(0, -4)}...`;
ctx.fillText(actionText, x + 10, y + 84);
ctx.restore();
}

View file

@ -2926,6 +2926,7 @@
if (!saved || !saved.type) continue;
const item = new Item(saved.type, Number(saved.x) || 0, Number(saved.y) || 0);
applyOwnData(item, saved, new Set(["target", "targetRef", "world"]));
if (item.isStructure && global.TarinaiNeedSystem?.hydrateStructure) global.TarinaiNeedSystem.hydrateStructure(item);
worldRef.items.push(item);
}

View file

@ -79,8 +79,7 @@
this.fallMax = Math.max(this.fallMax || 0, this.fallTimer);
}
if (Number.isFinite(opts.stress) && opts.stress > 0) {
if (this.addStress) this.addStress(opts.stress, { threshold: opts.stressThreshold ?? 8, duration: opts.stressDuration ?? 4.2 });
else this.stress = clamp((this.stress || 0) + opts.stress, 0, 130);
global.TarinaiNeedSystem?.addNeedShock?.(this, { safety: opts.stress }, threat);
}
if (opts.bubble) this.bubble?.(opts.bubble, opts.bubbleCooldown ?? 0.9, opts.bubbleColor || "rgba(84,66,86,0.80)");
this.lastPanicCause = opts.cause || opts.reason || this.thought || "panic";

View file

@ -263,10 +263,7 @@
if (colonyMoodId === "zunda_party" || colonyMoodId === "ball_party") this.stress = clamp((this.stress || 0) - dt * 0.34, 0, 130);
if (colonyMoodId === "ant_overrun" && this.state !== "panic") {
const antSeen = (this.world?.nearbyAnts?.(this.x, this.y, 110, true) || []).some(a => a && !a.dead);
if (antSeen) {
if (this.enterPanic) this.enterPanic({ target: null, reason: "アリだ!", fear: 1.1, wake: true, cause: "colony_ant_overrun" });
else { this.setActionState?.("panic", { target: null, reason: "アリだ!", wake: true }); this.fearTimer = Math.max(this.fearTimer || 0, 1.1 * this.personalityProfile().fear); }
}
if (antSeen) window.TarinaiNeedSystem?.addNeedShock?.(this, { safety: 18 }, null);
}
const wasFighting = this.fightTimer > 0.04;
this.fightTimer = Math.max(0, this.fightTimer - dt);
@ -378,6 +375,7 @@
if (this.world.resolveFenceCollision) this.world.resolveFenceCollision(this);
const sleepingAtFrameEnd = this.state === "sleep" || this.sleeping;
if (wasSleepingAtFrameStart && !sleepingAtFrameEnd && !this.dead) this.awakeLockTimer = Math.max(this.awakeLockTimer || 0, 10);
if (this.needsInitialized && window.TarinaiNeedSystem?.deriveStressFromNeeds) this.stress = window.TarinaiNeedSystem.deriveStressFromNeeds(this.needs || {});
this.checkLife(dt);
},
@ -586,13 +584,11 @@
const pin = this.currentLodgedPin?.() || null;
const pinBehavior = this.currentLodgedPinBehavior?.() || null;
if (pin && pinBehavior?.panicOnAttach) {
if (this.enterPanic) this.enterPanic({ threat: pin, reason: "画鋲が刺さってパニックになっている", fear: 0.60, hurtTimer: 0.24, awakeLockTimer: 1.8, bubble: "!!", bubbleColor: "rgba(168,72,72,0.82)", cause: "pushpin_stuck" });
else { this.setActionState?.("panic", { target: this.panicDestination(pin), reason: "画鋲が刺さってパニックになっている", wake: true }); this.fearTimer = Math.max(this.fearTimer || 0, 0.60); }
window.TarinaiNeedSystem?.addNeedShock?.(this, { safety: 45, health: 20 }, pin);
this.hurtTimer = Math.max(this.hurtTimer || 0, 0.24);
this.awakeLockTimer = Math.max(this.awakeLockTimer || 0, 1.8);
this.bubble("!!", 0.9, "rgba(168,72,72,0.82)");
this.thought = "\u753b\u92f2\u304c\u523a\u3055\u3063\u3066\u30d1\u30cb\u30c3\u30af\u306b\u306a\u3063\u3066\u3044\u308b";
return;
this.thought = "\u753b\u92f2\u304c\u523a\u3055\u3063\u3066\u3044\u308b";
}
this.stuckPushpinId = null;
}

View file

@ -213,6 +213,7 @@ function renderSelected() {
const itemEffectSummary = t.activeItemEffectSummary ? t.activeItemEffectSummary() : [];
const snapshot = [
t.id, t.type, t.name, generationDisplay, t.state, t.thought, targetLabel(t.target),
JSON.stringify(t.needs || {}), JSON.stringify(t.intent || {}),
fmt(t.age), fmt(t.lifeSpan), fmt(t.hunger), fmt(t.loneliness),
fmt(t.energy), fmt(t.stress), fmt(t.mood),
t.parentNames?.join("+") || "", t.children?.length || 0, fmt(t.lack),
@ -259,8 +260,6 @@ function renderSelected() {
const basicHtml = `
<div class="info-row"><span>\u4e16\u4ee3</span><strong>${escapeHtml(generationDisplay || "\u4e0d\u660e")}</strong></div>
<div class="info-row"><span>\u72b6\u614b</span><strong>${escapeHtml(stateLabel(t.state))}</strong></div>
<div class="info-row"><span>\u3044\u307e</span><strong>${escapeHtml(t.intent?.actionLabel || stateLabel(t.state))}</strong></div>
<div class="info-row"><span>\u7406\u7531</span><strong>${escapeHtml(t.intent?.reasonText || reasonLabel(t))}</strong></div>
<div class="info-row"><span>\u5bfe\u8c61</span><strong>${escapeHtml(targetLabel(t.target))}</strong></div>
<div class="info-row"><span>\u5e74\u9f62</span><strong>${fmt(t.age)} / ${fmt(t.lifeSpan)}</strong></div>
<div class="info-row"><span>\u6700\u5927\u5bff\u547d</span><strong>${geneticPercentText(t, "life")}</strong></div>
@ -268,7 +267,11 @@ function renderSelected() {
<div class="info-row"><span>\u79fb\u52d5\u901f\u5ea6</span><strong>${geneticPercentText(t, "speed")}</strong></div>
<div class="info-row"><span>\u30b5\u30a4\u30ba</span><strong>${geneticSizeText(t)}</strong></div>
`;
const healthHtml = selectedNeedRowsHtml(t) + selectedInfoRowHtml("\u30b9\u30c8\u30ec\u30b9", fmt(t.stress), "stress");
const needsHtml = `
${selectedNeedRowsHtml(t)}
<div class="info-row"><span>\u3044\u307e</span><strong>${escapeHtml(t.intent?.actionLabel || stateLabel(t.state))}</strong></div>
<div class="info-row"><span>\u7406\u7531</span><strong>${escapeHtml(t.intent?.reasonText || reasonLabel(t))}</strong></div>
`;
const itemEffectHtml = itemEffectSummary.length
? itemEffectSummary.map(e => `<div class="info-row"><span>${escapeHtml(e.label)}</span><strong>${escapeHtml(e.detail)}</strong></div>`).join("")
: `<div class="info-row"><span>\u30a2\u30a4\u30c6\u30e0\u52b9\u679c</span><strong>\u306a\u3057</strong></div>`;
@ -288,7 +291,7 @@ function renderSelected() {
${effectBadges.length ? `<div class="selected-effects">${effectBadges.join("")}</div>` : ""}
<div class="selected-data-groups">
${selectedDataCategoryHtml("basic", "\u57fa\u672c", basicHtml)}
${selectedDataCategoryHtml("health", "\u4f53\u8abf", healthHtml)}
${selectedDataCategoryHtml("needs", "\u30cb\u30fc\u30ba", needsHtml)}
${selectedDataCategoryHtml("effects", "\u30a2\u30a4\u30c6\u30e0\u52b9\u679c", itemEffectHtml)}
${selectedDataCategoryHtml("personality", "\u6027\u683c", personalityHtml)}
${selectedDataCategoryHtml("relation", "\u95a2\u4fc2", relationHtml)}