diff --git a/js/need_behavior_system.js b/js/need_behavior_system.js index 6681fc7..de476ba 100644 --- a/js/need_behavior_system.js +++ b/js/need_behavior_system.js @@ -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, }; diff --git a/js/render.js b/js/render.js index 4f06772..480150e 100644 --- a/js/render.js +++ b/js/render.js @@ -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(); } diff --git a/js/snapshot_system.js b/js/snapshot_system.js index 6ee9789..c358ebb 100644 --- a/js/snapshot_system.js +++ b/js/snapshot_system.js @@ -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); } diff --git a/js/tarinai_action_state.js b/js/tarinai_action_state.js index f432e9d..0492f3e 100644 --- a/js/tarinai_action_state.js +++ b/js/tarinai_action_state.js @@ -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"; diff --git a/js/tarinai_needs_items.js b/js/tarinai_needs_items.js index ddd1dd7..fd65450 100644 --- a/js/tarinai_needs_items.js +++ b/js/tarinai_needs_items.js @@ -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; } diff --git a/js/ui_selected.js b/js/ui_selected.js index a245833..f6c9ed9 100644 --- a/js/ui_selected.js +++ b/js/ui_selected.js @@ -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 = `
\u4e16\u4ee3${escapeHtml(generationDisplay || "\u4e0d\u660e")}
\u72b6\u614b${escapeHtml(stateLabel(t.state))}
-
\u3044\u307e${escapeHtml(t.intent?.actionLabel || stateLabel(t.state))}
-
\u7406\u7531${escapeHtml(t.intent?.reasonText || reasonLabel(t))}
\u5bfe\u8c61${escapeHtml(targetLabel(t.target))}
\u5e74\u9f62${fmt(t.age)} / ${fmt(t.lifeSpan)}
\u6700\u5927\u5bff\u547d${geneticPercentText(t, "life")}
@@ -268,7 +267,11 @@ function renderSelected() {
\u79fb\u52d5\u901f\u5ea6${geneticPercentText(t, "speed")}
\u30b5\u30a4\u30ba${geneticSizeText(t)}
`; - const healthHtml = selectedNeedRowsHtml(t) + selectedInfoRowHtml("\u30b9\u30c8\u30ec\u30b9", fmt(t.stress), "stress"); + const needsHtml = ` + ${selectedNeedRowsHtml(t)} +
\u3044\u307e${escapeHtml(t.intent?.actionLabel || stateLabel(t.state))}
+
\u7406\u7531${escapeHtml(t.intent?.reasonText || reasonLabel(t))}
+ `; const itemEffectHtml = itemEffectSummary.length ? itemEffectSummary.map(e => `
${escapeHtml(e.label)}${escapeHtml(e.detail)}
`).join("") : `
\u30a2\u30a4\u30c6\u30e0\u52b9\u679c\u306a\u3057
`; @@ -288,7 +291,7 @@ function renderSelected() { ${effectBadges.length ? `
${effectBadges.join("")}
` : ""}
${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)}