stable
This commit is contained in:
parent
fbdac9ebf1
commit
86226ccc94
97 changed files with 5790 additions and 2307 deletions
195
js/item_dynamic_pin_system.js
Normal file
195
js/item_dynamic_pin_system.js
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
"use strict";
|
||||
|
||||
// Layer: entity-runtime/items/dynamic-system
|
||||
// Owns pushpin/oshibyo item motion, attachment, lodged damage, and detach
|
||||
// behavior. Prototype methods remain as compatibility facades.
|
||||
(function (global) {
|
||||
function isPin(item) {
|
||||
return Boolean(item && typeof global.isPinType === "function" && global.isPinType(item.type));
|
||||
}
|
||||
|
||||
function update(item, dt, worldRef) {
|
||||
if (!isPin(item) || item.dead) return false;
|
||||
if (!worldRef) return false;
|
||||
if (item.pinState === "lodged") {
|
||||
updateLodged(item, dt, worldRef);
|
||||
return true;
|
||||
}
|
||||
if (global.TarinaiPhysics?.applyKinematicItemMotion) {
|
||||
global.TarinaiPhysics.applyKinematicItemMotion(item, worldRef, dt, { padding: 26, bounce: 0.44, spinBounce: -0.68, frictionBase: 0.18, frictionRate: 0.85, spinFrictionBase: 0.38, spinRestDamp: 0.08, stopSpeed: 0.05, zeroBelow: 7.5 });
|
||||
} else {
|
||||
item.prevX = item.x;
|
||||
item.prevY = item.y;
|
||||
item.x += (item.vx || 0) * dt;
|
||||
item.y += (item.vy || 0) * dt;
|
||||
}
|
||||
tryStick(item, worldRef);
|
||||
return true;
|
||||
}
|
||||
|
||||
function tryStick(item, worldRef) {
|
||||
if (!worldRef?.tarinai?.length) return false;
|
||||
if (item.pinState === "lodged") return false;
|
||||
const now = worldRef.time || 0;
|
||||
if ((item.noStickUntil || 0) > now) return false;
|
||||
let best = null;
|
||||
let bestD = Infinity;
|
||||
const hitRange = Math.max(10, (item.r || 8) * 1.25);
|
||||
const candidates = worldRef.nearbyTarinai?.(item.x, item.y, hitRange + 32, true) || worldRef.tarinai;
|
||||
for (const t of candidates) {
|
||||
if (!t || t.dead) continue;
|
||||
if (item.noStickTargetId && item.noStickTargetId === t.id && (item.noStickTargetUntil || 0) > now) continue;
|
||||
const d = distXY(item.x, item.y, t.x, t.y);
|
||||
const hit = (t.radius || 16) * 0.86 + hitRange;
|
||||
if (d <= hit && d < bestD) { best = t; bestD = d; }
|
||||
}
|
||||
if (!best) return false;
|
||||
return attach(item, best, worldRef, bestD);
|
||||
}
|
||||
|
||||
function attach(item, t, worldRef, d = null) {
|
||||
if (!t || t.dead) return false;
|
||||
if (t.stuckPushpinId && t.stuckPushpinId !== item.id) return false;
|
||||
const behavior = typeof pinBehaviorFor === "function" ? pinBehaviorFor(item.type) : null;
|
||||
const isOshibyo = Boolean(behavior?.blocksZunchi);
|
||||
const dx = item.x - t.x;
|
||||
const dy = item.y - t.y;
|
||||
const distToTarget = Number.isFinite(d) ? d : Math.hypot(dx, dy);
|
||||
item.pinState = "lodged";
|
||||
item.deletable = false;
|
||||
item.pinTargetId = t.id;
|
||||
const faceDir = t.facingDir ? t.facingDir() : (t.facing || 1);
|
||||
const visualSide = isOshibyo ? -faceDir : faceDir;
|
||||
item.pinVisualSide = visualSide;
|
||||
item.pinAttachAngle = isOshibyo ? visualSide * 0.36 : Math.atan2(dy || -1, dx || visualSide);
|
||||
item.pinAttachDistance = isOshibyo ? (t.radius || 16) * 0.72 : clamp(distToTarget || (t.radius || 16) * 0.58, (t.radius || 16) * 0.20, (t.radius || 16) * 0.74);
|
||||
item.pinOffsetY = isOshibyo ? (t.radius || 16) * 0.40 : clamp(dy, -(t.radius || 16) * 0.74, (t.radius || 16) * 0.38);
|
||||
item.pinDamageTick = 0;
|
||||
item.pinFallCheckTimer = 0;
|
||||
item.vx = 0;
|
||||
item.vy = 0;
|
||||
item.spinVelocity = 0;
|
||||
item.spin = item.pinAttachAngle;
|
||||
t.stuckPushpinId = item.id;
|
||||
t.sleeping = false;
|
||||
t.awakeLockTimer = Math.max(t.awakeLockTimer || 0, 7);
|
||||
t.surpriseTimer = Math.max(t.surpriseTimer || 0, isOshibyo ? 0.35 : 0.8);
|
||||
if (isOshibyo) {
|
||||
t.thought = "\u304a\u3057\u308a\u92f2\u304c\u523a\u3055\u3063\u3066\u305a\u3093\u3061\u304c\u51fa\u306a\u304f\u306a\u3063\u305f";
|
||||
worldRef.log?.(`${t.name}\u306b\u304a\u3057\u308a\u92f2\u304c\u523a\u3055\u3063\u305f\u3002`, "accident", { participants: [t] });
|
||||
worldRef.effects?.push(new Effect("ring", t.x, t.y + (t.radius || 16) * 0.34, { size: Math.max(14, (t.radius || 16) * 0.58), life: 0.18, color: "rgba(73,119,205,0.36)" }));
|
||||
return true;
|
||||
}
|
||||
if (t.enterPanic) {
|
||||
t.enterPanic({ threat: item, reason: "\u753b\u92f2\u304c\u523a\u3055\u3063\u3066\u30d1\u30cb\u30c3\u30af\u306b\u306a\u3063\u3066\u3044\u308b", fear: 1.3, stress: behavior?.stressOnAttach ?? 18, hurtTimer: 1.2, awakeLockTimer: 7, surpriseTimer: 0.8, cause: "pushpin_attach" });
|
||||
} else {
|
||||
t.hurtTimer = Math.max(t.hurtTimer || 0, 1.2);
|
||||
t.fearTimer = Math.max(t.fearTimer || 0, 1.3);
|
||||
t.setActionState?.("panic", { target: t.panicDestination ? t.panicDestination(item) : { x: t.x + rand(-80, 80), y: t.y + rand(-80, 80) }, reason: "\u753b\u92f2\u304c\u523a\u3055\u3063\u3066\u30d1\u30cb\u30c3\u30af\u306b\u306a\u3063\u3066\u3044\u308b", wake: true });
|
||||
if (t.addStress) t.addStress(behavior?.stressOnAttach ?? 18, { threshold: 8 });
|
||||
}
|
||||
if (t.damage && (behavior?.damageOnAttach ?? 6) > 0) t.damage(behavior?.damageOnAttach ?? 6, "\u753b\u92f2");
|
||||
if ((worldRef.time || 0) >= (item.pinLogAt || -999) + 1.2) {
|
||||
item.pinLogAt = worldRef.time || 0;
|
||||
worldRef.log?.(`${t.name}\u306b\u753b\u92f2\u304c\u523a\u3055\u3063\u305f\u3002`, "accident", { participants: [t] });
|
||||
}
|
||||
worldRef.effects?.push(new Effect("ring", t.x, t.y - (t.radius || 16) * 0.12, { size: Math.max(18, (t.radius || 16) * 0.90), life: 0.22, color: "rgba(214,72,72,0.50)" }));
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateLodged(item, dt, worldRef) {
|
||||
const t = worldRef.liveTarinaiById?.(item.pinTargetId) || null;
|
||||
if (!t) {
|
||||
detach(item, worldRef, "owner-lost");
|
||||
return;
|
||||
}
|
||||
const behavior = typeof pinBehaviorFor === "function" ? pinBehaviorFor(item.type) : null;
|
||||
const isOshibyo = Boolean(behavior?.blocksZunchi);
|
||||
t.stuckPushpinId = item.id;
|
||||
item.deletable = false;
|
||||
const faceDir = t.facingDir ? t.facingDir() : (t.facing || 1);
|
||||
const visualSide = isOshibyo ? -faceDir : faceDir;
|
||||
item.pinVisualSide = visualSide;
|
||||
item.x = isOshibyo ? t.x + visualSide * (t.radius || 16) * 0.72 : t.x + visualSide * (t.radius || 16) * 0.36;
|
||||
item.y = isOshibyo ? t.y + (t.radius || 16) * 0.42 : t.y - (t.radius || 16) * 0.04;
|
||||
item.prevX = item.x;
|
||||
item.prevY = item.y;
|
||||
item.spin = visualSide * (isOshibyo ? 0.52 : 0.28);
|
||||
item.vx = t.vx || 0;
|
||||
item.vy = t.vy || 0;
|
||||
if (isOshibyo) {
|
||||
if ((t.oshiriByoZunchiStock || 0) >= 6 && t.state !== "eat" && t.state !== "sleep") t.thought = "\u304a\u3057\u308a\u92f2\u3067\u305a\u3093\u3061\u304c\u6e9c\u307e\u3063\u3066\u3064\u3089\u3044";
|
||||
return;
|
||||
}
|
||||
item.pinDamageTick = (item.pinDamageTick || 0) + dt;
|
||||
while (item.pinDamageTick >= 0.55) {
|
||||
item.pinDamageTick -= 0.55;
|
||||
if (t.damage && (behavior?.damagePerTick ?? 1.4) > 0) t.damage(behavior?.damagePerTick ?? 1.4, "\u753b\u92f2");
|
||||
t.hurtTimer = Math.max(t.hurtTimer || 0, 0.50);
|
||||
if (t.addStress) t.addStress(behavior?.stressPerTick ?? 2.6, { threshold: 8, duration: 3.4 });
|
||||
if (Math.random() < 0.22) t.bubble?.("!!", 0.6, "rgba(168,72,72,0.82)");
|
||||
}
|
||||
t.sleeping = false;
|
||||
if (t.enterPanic) t.enterPanic({ threat: item, reason: "\u753b\u92f2\u304c\u523a\u3055\u3063\u3066\u30d1\u30cb\u30c3\u30af\u306b\u306a\u3063\u3066\u3044\u308b", fear: 0.65, surpriseTimer: 0.22, awakeLockTimer: 2.4, cause: "pushpin_lodged" });
|
||||
else {
|
||||
t.setActionState?.("panic", { target: t.panicDestination ? t.panicDestination(item) : { x: t.x + rand(-80, 80), y: t.y + rand(-80, 80) }, reason: "\u753b\u92f2\u304c\u523a\u3055\u3063\u3066\u30d1\u30cb\u30c3\u30af\u306b\u306a\u3063\u3066\u3044\u308b", wake: true });
|
||||
t.fearTimer = Math.max(t.fearTimer || 0, 0.65);
|
||||
t.surpriseTimer = Math.max(t.surpriseTimer || 0, 0.22);
|
||||
t.awakeLockTimer = Math.max(t.awakeLockTimer || 0, 2.4);
|
||||
}
|
||||
item.pinFallCheckTimer = (item.pinFallCheckTimer || 0) + dt;
|
||||
while (item.pinFallCheckTimer >= 1.0) {
|
||||
item.pinFallCheckTimer -= 1.0;
|
||||
if (Math.random() < 0.10) {
|
||||
detach(item, worldRef, "fall");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function detach(item, worldRef, reason = "released") {
|
||||
const t = worldRef?.liveTarinaiById?.(item.pinTargetId) || null;
|
||||
const behavior = typeof pinBehaviorFor === "function" ? pinBehaviorFor(item.type) : null;
|
||||
const wasOshibyo = Boolean(behavior?.blocksZunchi);
|
||||
const storedZunchi = wasOshibyo && t ? Math.max(0, Math.floor(t.oshiriByoZunchiStock || 0)) : 0;
|
||||
if (t && t.stuckPushpinId === item.id) t.stuckPushpinId = null;
|
||||
if (t && wasOshibyo) t.oshiriByoZunchiStock = 0;
|
||||
if (reason === "pinch") {
|
||||
const now = worldRef?.time || 0;
|
||||
item.noStickUntil = now + 1.0;
|
||||
item.noStickTargetId = t?.id || "";
|
||||
item.noStickTargetUntil = now + 1.0;
|
||||
} else if (t) {
|
||||
const now = worldRef?.time || 0;
|
||||
item.noStickTargetId = t.id || "";
|
||||
item.noStickTargetUntil = now + 0.55;
|
||||
}
|
||||
item.pinState = "loose";
|
||||
item.pinTargetId = "";
|
||||
item.pinDamageTick = 0;
|
||||
item.pinFallCheckTimer = 0;
|
||||
item.deletable = true;
|
||||
if (reason === "pinch") {
|
||||
item.vx = 0;
|
||||
item.vy = 0;
|
||||
item.spinVelocity = 0;
|
||||
} else {
|
||||
item.vx = rand(-24, 24);
|
||||
item.vy = rand(-10, 18);
|
||||
item.spinVelocity = rand(-1.6, 1.6);
|
||||
item.spin += rand(-0.25, 0.25);
|
||||
if (t) {
|
||||
item.x = clamp(t.x + rand(-(t.radius || 16) * 0.75, (t.radius || 16) * 0.75), CONFIG.worldPadding || 30, (worldRef?.w || item.x) - (CONFIG.worldPadding || 30));
|
||||
item.y = clamp(t.y + rand((t.radius || 16) * 0.12, (t.radius || 16) * 0.72), CONFIG.worldPadding || 30, (worldRef?.h || item.y) - (CONFIG.worldPadding || 30));
|
||||
}
|
||||
worldRef?.effects?.push(new Effect("ring", item.x, item.y, { size: Math.max(14, (item.r || 16) * 0.92), life: 0.16, color: "rgba(214,112,112,0.40)" }));
|
||||
if (reason === "fall" && t) worldRef?.log?.(`${t.name}\u306e\u753b\u92f2\u304c\u629c\u3051\u843d\u3061\u305f\u3002`, "observe", { participants: [t] });
|
||||
}
|
||||
|
||||
if (wasOshibyo && storedZunchi > 0 && worldRef?.spawnBurstZunchi) {
|
||||
worldRef.spawnBurstZunchi(t || item, storedZunchi, item);
|
||||
}
|
||||
}
|
||||
|
||||
global.TarinaiItemDynamicPinSystem = Object.freeze({ update, isPin, tryStick, attach, updateLodged, detach });
|
||||
})(typeof window !== "undefined" ? window : globalThis);
|
||||
Loading…
Add table
Add a link
Reference in a new issue