refactor
This commit is contained in:
parent
0a6148c73f
commit
e8f1d1db1e
60 changed files with 2417 additions and 612 deletions
253
js/items.js
253
js/items.js
|
|
@ -27,6 +27,19 @@ function passiveFoodDecayRate(item) {
|
|||
return PASSIVE_FOOD_DECAY_PER_SECOND * sizeMul * typeMul;
|
||||
}
|
||||
|
||||
function itemVisualFor(type = "") {
|
||||
return typeof itemVisualDefinition === "function" ? itemVisualDefinition(type) : null;
|
||||
}
|
||||
|
||||
function visualPaletteFor(type = "", fallback = {}) {
|
||||
return itemVisualFor(type)?.palette || fallback || {};
|
||||
}
|
||||
|
||||
function visualFieldScaleFor(type = "") {
|
||||
const visual = itemVisualFor(type);
|
||||
return visual?.fieldScale || visual?.scale || 1;
|
||||
}
|
||||
|
||||
function drawOvalWaterDropSprite(ctx, r, palette = {}) {
|
||||
const fill = palette.fill || "rgba(86, 157, 224, 0.82)";
|
||||
const stroke = palette.stroke || "rgba(45, 101, 168, 0.52)";
|
||||
|
|
@ -48,20 +61,23 @@ function drawOvalWaterDropSprite(ctx, r, palette = {}) {
|
|||
ctx.restore();
|
||||
}
|
||||
|
||||
function drawSleepTabletSprite(ctx, r) {
|
||||
function drawSleepTabletSprite(ctx, r, palette = {}) {
|
||||
const fill = palette.fill || "#ebe4ff";
|
||||
const stroke = palette.stroke || "rgba(74, 60, 144, 0.72)";
|
||||
const accent = palette.accent || "rgba(112, 91, 188, 0.82)";
|
||||
ctx.save();
|
||||
ctx.rotate(-0.18);
|
||||
ctx.fillStyle = "#ebe4ff";
|
||||
ctx.strokeStyle = "rgba(74, 60, 144, 0.72)";
|
||||
ctx.fillStyle = fill;
|
||||
ctx.strokeStyle = stroke;
|
||||
ctx.lineWidth = Math.max(1.8, r * 0.14);
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, 0, r * 0.92, r * 0.66, 0, 0, Math.PI * 2);
|
||||
ctx.fill(); ctx.stroke();
|
||||
ctx.fillStyle = "rgba(112, 91, 188, 0.82)";
|
||||
ctx.fillStyle = accent;
|
||||
ctx.beginPath();
|
||||
ctx.arc(r * 0.12, -r * 0.04, r * 0.38, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = "#ebe4ff";
|
||||
ctx.fillStyle = fill;
|
||||
ctx.beginPath();
|
||||
ctx.arc(r * 0.28, -r * 0.11, r * 0.38, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
|
@ -186,6 +202,10 @@ class Item {
|
|||
this.comfort = rand(0.86, 1.14);
|
||||
this.wear = 0;
|
||||
}
|
||||
if (type === "signboard") {
|
||||
this.text = "";
|
||||
this.textEditedAt = -999;
|
||||
}
|
||||
if (type === "ball") {
|
||||
this.vx = rand(-5, 5);
|
||||
this.vy = rand(-5, 5);
|
||||
|
|
@ -232,7 +252,7 @@ class Item {
|
|||
this.lingerTimer = 0;
|
||||
this.lingerDuration = 2.0;
|
||||
}
|
||||
if (type === "pushpin") {
|
||||
if (isPinType(type)) {
|
||||
this.amount = 100;
|
||||
this.vx = rand(-120, 120);
|
||||
this.vy = rand(-90, 90);
|
||||
|
|
@ -322,7 +342,7 @@ class Item {
|
|||
}
|
||||
}
|
||||
if (this.type === "ball") this.updateBall(dt, worldRef);
|
||||
if (this.type === "pushpin") this.updatePushpin(dt, worldRef);
|
||||
if (isPinType(this.type)) this.updatePushpin(dt, worldRef);
|
||||
if (this.type === "zunchi") this.updateZunchiMotion(dt, worldRef);
|
||||
if (this.type === "grass") {
|
||||
this.lifecycleTimer += dt;
|
||||
|
|
@ -347,20 +367,59 @@ class Item {
|
|||
updateZunchiMotion(dt, worldRef) {
|
||||
const speed = Math.hypot(this.vx || 0, this.vy || 0);
|
||||
if (speed < 0.08) { this.vx = 0; this.vy = 0; return; }
|
||||
this.prevX = this.x;
|
||||
this.prevY = this.y;
|
||||
this.x += (this.vx || 0) * dt;
|
||||
this.y += (this.vy || 0) * dt;
|
||||
const p = Math.max(28, CONFIG.worldPadding || 30);
|
||||
if (this.x < p) { this.x = p; this.vx = Math.abs(this.vx || 0) * 0.34; }
|
||||
if (this.x > worldRef.w - p) { this.x = worldRef.w - p; this.vx = -Math.abs(this.vx || 0) * 0.34; }
|
||||
if (this.y < p) { this.y = p; this.vy = Math.abs(this.vy || 0) * 0.34; }
|
||||
if (this.y > worldRef.h - p) { this.y = worldRef.h - p; this.vy = -Math.abs(this.vy || 0) * 0.34; }
|
||||
const slow = Math.pow(0.68, dt * 2.4);
|
||||
this.vx *= slow;
|
||||
this.vy *= slow;
|
||||
if (window.TarinaiPhysics?.applyKinematicItemMotion) {
|
||||
window.TarinaiPhysics.applyKinematicItemMotion(this, worldRef, dt, { bounce: 0.34, frictionBase: 0.68, frictionRate: 2.4, spin: false, stopSpeed: 0.08 });
|
||||
} else {
|
||||
this.prevX = this.x;
|
||||
this.prevY = this.y;
|
||||
this.x += (this.vx || 0) * dt;
|
||||
this.y += (this.vy || 0) * dt;
|
||||
const p = Math.max(28, CONFIG.worldPadding || 30);
|
||||
if (this.x < p) { this.x = p; this.vx = Math.abs(this.vx || 0) * 0.34; }
|
||||
if (this.x > worldRef.w - p) { this.x = worldRef.w - p; this.vx = -Math.abs(this.vx || 0) * 0.34; }
|
||||
if (this.y < p) { this.y = p; this.vy = Math.abs(this.vy || 0) * 0.34; }
|
||||
if (this.y > worldRef.h - p) { this.y = worldRef.h - p; this.vy = -Math.abs(this.vy || 0) * 0.34; }
|
||||
const slow = Math.pow(0.68, dt * 2.4);
|
||||
this.vx *= slow;
|
||||
this.vy *= slow;
|
||||
worldRef.drawListDirty = true;
|
||||
}
|
||||
this.resolveHighSpeedZunchiTarinaiCollision?.(dt, worldRef, speed);
|
||||
this.spin = (this.spin || 0) + speed * dt / Math.max(8, this.r || 14);
|
||||
worldRef.drawListDirty = true;
|
||||
}
|
||||
|
||||
resolveHighSpeedZunchiTarinaiCollision(dt, worldRef, speed = 0) {
|
||||
if (!worldRef || speed < 140 || (this.amount || 0) <= 0) return;
|
||||
const search = Math.max(38, (this.r || 14) + speed * Math.max(dt || 0.016, 0.016) + 18);
|
||||
for (const t of worldRef.nearbyTarinai?.(this.x, this.y, search, true) || []) {
|
||||
if (!t || t.dead || worldRef.isTarinaiHiddenInNestBox?.(t)) continue;
|
||||
const d = distXY(this.x, this.y, t.x, t.y);
|
||||
if (d > (t.radius || 20) * 0.72 + (this.r || 14)) continue;
|
||||
const now = worldRef.time || 0;
|
||||
if ((this.lastHitTarinaiAt || {})[t.id] && this.lastHitTarinaiAt[t.id] + 0.55 > now) continue;
|
||||
this.lastHitTarinaiAt = this.lastHitTarinaiAt || {};
|
||||
this.lastHitTarinaiAt[t.id] = now;
|
||||
const nx = speed > 0 ? (this.vx || 1) / speed : rand(-1, 1);
|
||||
const ny = speed > 0 ? (this.vy || 0) / speed : rand(-1, 1);
|
||||
const impulse = clamp(speed * 1.15, 150, 620);
|
||||
t.vx = (t.vx || 0) + nx * impulse + rand(-20, 20);
|
||||
t.vy = (t.vy || 0) + ny * impulse * 0.72 + rand(-18, 10);
|
||||
t.fallTimer = Math.max(t.fallTimer || 0, 0.85);
|
||||
t.fallMax = Math.max(t.fallMax || 0.85, t.fallTimer);
|
||||
t.fallDir = nx < 0 ? -1 : 1;
|
||||
if (t.enterPanic) t.enterPanic({ threat: this, reason: "高速ずんちにぶつかって吹き飛んでいる", fear: 0.55, stress: 7, stressDuration: 3.2, surpriseTimer: 0.5, cause: "fast_zunchi_hit" });
|
||||
else {
|
||||
t.surpriseTimer = Math.max(t.surpriseTimer || 0, 0.5);
|
||||
t.fearTimer = Math.max(t.fearTimer || 0, 0.55);
|
||||
t.setActionState?.("panic", { target: t.panicDestination ? t.panicDestination(this) : null, reason: "高速ずんちにぶつかって吹き飛んでいる", wake: true });
|
||||
t.addStress ? t.addStress(7, { threshold: 8, duration: 3.2 }) : (t.stress = clamp((t.stress || 0) + 7, 0, 130));
|
||||
}
|
||||
worldRef.spawnFallEffect?.(t.x, t.y + (t.radius || 20) * 0.45, 0.65);
|
||||
worldRef.effects?.push(new Effect("zunchi_miasma", this.x, this.y - 4, { size: 14, life: 0.38, color: "rgba(77,92,42,0.36)" }));
|
||||
this.vx *= -0.18;
|
||||
this.vy *= -0.18;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
updateBall(dt, worldRef) {
|
||||
|
|
@ -571,29 +630,13 @@ class Item {
|
|||
this.updateLodgedPushpin(dt, worldRef);
|
||||
return;
|
||||
}
|
||||
this.prevX = this.x;
|
||||
this.prevY = this.y;
|
||||
const moving = Math.hypot(this.vx || 0, this.vy || 0);
|
||||
if (moving > 0.05) {
|
||||
if (window.TarinaiPhysics?.applyKinematicItemMotion) {
|
||||
window.TarinaiPhysics.applyKinematicItemMotion(this, 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 {
|
||||
this.prevX = this.x;
|
||||
this.prevY = this.y;
|
||||
this.x += (this.vx || 0) * dt;
|
||||
this.y += (this.vy || 0) * dt;
|
||||
const p = Math.max(26, CONFIG.worldPadding || 30);
|
||||
if (this.x < p) { this.x = p; this.vx = Math.abs(this.vx || 0) * 0.44; this.spinVelocity *= -0.68; }
|
||||
if (this.x > worldRef.w - p) { this.x = worldRef.w - p; this.vx = -Math.abs(this.vx || 0) * 0.44; this.spinVelocity *= -0.68; }
|
||||
if (this.y < p) { this.y = p; this.vy = Math.abs(this.vy || 0) * 0.44; this.spinVelocity *= -0.68; }
|
||||
if (this.y > worldRef.h - p) { this.y = worldRef.h - p; this.vy = -Math.abs(this.vy || 0) * 0.44; this.spinVelocity *= -0.68; }
|
||||
const friction = Math.pow(0.18, dt * 0.85);
|
||||
this.vx *= friction;
|
||||
this.vy *= friction;
|
||||
this.spin = (this.spin || 0) + (this.spinVelocity || 0) * dt + (this.vx || 0) * dt / Math.max(7, this.r || 16);
|
||||
this.spinVelocity *= Math.pow(0.38, dt);
|
||||
if (Math.hypot(this.vx || 0, this.vy || 0) < 7.5) { this.vx = 0; this.vy = 0; this.spinVelocity *= 0.3; }
|
||||
worldRef.drawListDirty = true;
|
||||
} else {
|
||||
this.vx = 0;
|
||||
this.vy = 0;
|
||||
this.spinVelocity *= Math.pow(0.08, dt);
|
||||
this.spin = (this.spin || 0) + (this.spinVelocity || 0) * dt;
|
||||
}
|
||||
this.tryStickPushpin(worldRef);
|
||||
}
|
||||
|
|
@ -617,15 +660,17 @@ class Item {
|
|||
attachPushpin(t, worldRef, d = null) {
|
||||
if (!t || t.dead) return false;
|
||||
if (t.stuckPushpinId && t.stuckPushpinId !== this.id) return false;
|
||||
const behavior = typeof pinBehaviorFor === "function" ? pinBehaviorFor(this.type) : null;
|
||||
const isOshibyo = Boolean(behavior?.blocksZunchi);
|
||||
const dx = this.x - t.x;
|
||||
const dy = this.y - t.y;
|
||||
const distToTarget = Number.isFinite(d) ? d : Math.hypot(dx, dy);
|
||||
this.pinState = "lodged";
|
||||
this.deletable = false;
|
||||
this.pinTargetId = t.id;
|
||||
this.pinAttachAngle = Math.atan2(dy || -1, dx || (t.facingDir ? t.facingDir() : 1));
|
||||
this.pinAttachDistance = clamp(distToTarget || (t.radius || 16) * 0.58, (t.radius || 16) * 0.20, (t.radius || 16) * 0.74);
|
||||
this.pinOffsetY = clamp(dy, -(t.radius || 16) * 0.74, (t.radius || 16) * 0.38);
|
||||
this.pinAttachAngle = isOshibyo ? 0.62 : Math.atan2(dy || -1, dx || (t.facingDir ? t.facingDir() : 1));
|
||||
this.pinAttachDistance = isOshibyo ? (t.radius || 16) * 0.58 : clamp(distToTarget || (t.radius || 16) * 0.58, (t.radius || 16) * 0.20, (t.radius || 16) * 0.74);
|
||||
this.pinOffsetY = isOshibyo ? (t.radius || 16) * 0.30 : clamp(dy, -(t.radius || 16) * 0.74, (t.radius || 16) * 0.38);
|
||||
this.pinDamageTick = 0;
|
||||
this.pinFallCheckTimer = 0;
|
||||
this.vx = 0;
|
||||
|
|
@ -635,16 +680,25 @@ class Item {
|
|||
t.stuckPushpinId = this.id;
|
||||
t.sleeping = false;
|
||||
t.awakeLockTimer = Math.max(t.awakeLockTimer || 0, 7);
|
||||
t.hurtTimer = Math.max(t.hurtTimer || 0, 1.2);
|
||||
t.surpriseTimer = Math.max(t.surpriseTimer || 0, 0.8);
|
||||
t.fearTimer = Math.max(t.fearTimer || 0, 1.3);
|
||||
t.state = "panic";
|
||||
t.target = t.panicDestination ? t.panicDestination(this) : { x: t.x + rand(-80, 80), y: t.y + rand(-80, 80) };
|
||||
t.addStress ? t.addStress(18, { threshold: 8 }) : (t.stress = clamp((t.stress || 0) + 18, 0, 130));
|
||||
if (t.damage) t.damage(6, "\u753b\u92f2");
|
||||
t.surpriseTimer = Math.max(t.surpriseTimer || 0, isOshibyo ? 0.35 : 0.8);
|
||||
if (isOshibyo) {
|
||||
t.thought = "おしり鋲が刺さってずんちが出なくなった";
|
||||
worldRef.log?.(`${t.name}におしり鋲が刺さった。`, "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: this, reason: "画鋲が刺さってパニックになっている", 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(this) : { x: t.x + rand(-80, 80), y: t.y + rand(-80, 80) }, reason: "画鋲が刺さってパニックになっている", wake: true });
|
||||
t.addStress ? t.addStress(behavior?.stressOnAttach ?? 18, { threshold: 8 }) : (t.stress = clamp((t.stress || 0) + (behavior?.stressOnAttach ?? 18), 0, 130));
|
||||
}
|
||||
if (t.damage && (behavior?.damageOnAttach ?? 6) > 0) t.damage(behavior?.damageOnAttach ?? 6, "画鋲");
|
||||
if ((worldRef.time || 0) >= (this.pinLogAt || -999) + 1.2) {
|
||||
this.pinLogAt = worldRef.time || 0;
|
||||
worldRef.log?.(`${t.name}\u306b\u753b\u92f2\u304c\u523a\u3055\u3063\u305f\u3002`, "accident", { participants: [t] });
|
||||
worldRef.log?.(`${t.name}に画鋲が刺さった。`, "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;
|
||||
|
|
@ -656,30 +710,37 @@ class Item {
|
|||
this.detachPushpin(worldRef, "owner-lost");
|
||||
return;
|
||||
}
|
||||
const behavior = typeof pinBehaviorFor === "function" ? pinBehaviorFor(this.type) : null;
|
||||
const isOshibyo = Boolean(behavior?.blocksZunchi);
|
||||
t.stuckPushpinId = this.id;
|
||||
this.deletable = false;
|
||||
this.x = t.x + (t.radius || 16) * 0.36;
|
||||
this.y = t.y - (t.radius || 16) * 0.04;
|
||||
this.x = isOshibyo ? t.x + (t.radius || 16) * 0.42 : t.x + (t.radius || 16) * 0.36;
|
||||
this.y = isOshibyo ? t.y + (t.radius || 16) * 0.34 : t.y - (t.radius || 16) * 0.04;
|
||||
this.prevX = this.x;
|
||||
this.prevY = this.y;
|
||||
this.spin = 0.28;
|
||||
this.spin = isOshibyo ? 0.52 : 0.28;
|
||||
this.vx = t.vx || 0;
|
||||
this.vy = t.vy || 0;
|
||||
if (isOshibyo) {
|
||||
if ((t.oshiriByoZunchiStock || 0) >= 6 && t.state !== "eat" && t.state !== "sleep") t.thought = "おしり鋲でずんちが溜まってつらい";
|
||||
return;
|
||||
}
|
||||
this.pinDamageTick = (this.pinDamageTick || 0) + dt;
|
||||
while (this.pinDamageTick >= 0.55) {
|
||||
this.pinDamageTick -= 0.55;
|
||||
if (t.damage) t.damage(1.4, "\u753b\u92f2");
|
||||
if (t.damage && (behavior?.damagePerTick ?? 1.4) > 0) t.damage(behavior?.damagePerTick ?? 1.4, "画鋲");
|
||||
t.hurtTimer = Math.max(t.hurtTimer || 0, 0.50);
|
||||
t.addStress ? t.addStress(2.6, { threshold: 8, duration: 3.4 }) : (t.stress = clamp((t.stress || 0) + 2.6, 0, 130));
|
||||
t.addStress ? t.addStress(behavior?.stressPerTick ?? 2.6, { threshold: 8, duration: 3.4 }) : (t.stress = clamp((t.stress || 0) + (behavior?.stressPerTick ?? 2.6), 0, 130));
|
||||
if (Math.random() < 0.22) t.bubble?.("!!", 0.6, "rgba(168,72,72,0.82)");
|
||||
}
|
||||
t.sleeping = false;
|
||||
t.state = "panic";
|
||||
t.target = t.panicDestination ? t.panicDestination(this) : { x: t.x + rand(-80, 80), y: t.y + rand(-80, 80) };
|
||||
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);
|
||||
t.thought = "\u753b\u92f2\u304c\u523a\u3055\u3063\u3066\u30d1\u30cb\u30c3\u30af\u306b\u306a\u3063\u3066\u3044\u308b";
|
||||
if (t.enterPanic) t.enterPanic({ threat: this, reason: "画鋲が刺さってパニックになっている", fear: 0.65, surpriseTimer: 0.22, awakeLockTimer: 2.4, cause: "pushpin_lodged" });
|
||||
else {
|
||||
t.setActionState?.("panic", { target: t.panicDestination ? t.panicDestination(this) : { x: t.x + rand(-80, 80), y: t.y + rand(-80, 80) }, reason: "画鋲が刺さってパニックになっている", 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);
|
||||
}
|
||||
this.pinFallCheckTimer = (this.pinFallCheckTimer || 0) + dt;
|
||||
while (this.pinFallCheckTimer >= 1.0) {
|
||||
this.pinFallCheckTimer -= 1.0;
|
||||
|
|
@ -692,7 +753,11 @@ class Item {
|
|||
|
||||
detachPushpin(worldRef, reason = "released") {
|
||||
const t = worldRef?.tarinai?.find(o => o && o.id === this.pinTargetId) || null;
|
||||
const behavior = typeof pinBehaviorFor === "function" ? pinBehaviorFor(this.type) : null;
|
||||
const wasOshibyo = Boolean(behavior?.blocksZunchi);
|
||||
const storedZunchi = wasOshibyo && t ? Math.max(0, Math.floor(t.oshiriByoZunchiStock || 0)) : 0;
|
||||
if (t && t.stuckPushpinId === this.id) t.stuckPushpinId = null;
|
||||
if (t && wasOshibyo) t.oshiriByoZunchiStock = 0;
|
||||
this.pinState = "loose";
|
||||
this.pinTargetId = "";
|
||||
this.pinDamageTick = 0;
|
||||
|
|
@ -714,6 +779,10 @@ class Item {
|
|||
worldRef?.effects?.push(new Effect("ring", this.x, this.y, { size: Math.max(14, (this.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 || this, storedZunchi, this);
|
||||
}
|
||||
}
|
||||
|
||||
updateGrass(dt, worldRef) {
|
||||
|
|
@ -928,7 +997,7 @@ class Item {
|
|||
}
|
||||
}
|
||||
} else if (typeof isParamEffectItemType === "function" && isParamEffectItemType(this.type)) {
|
||||
const visual = typeof itemVisualDefinition === "function" ? itemVisualDefinition(this.type) : null;
|
||||
const visual = itemVisualFor(this.type);
|
||||
const palette = visual?.palette || {};
|
||||
const renderer = visual?.renderer || "split_pill";
|
||||
const meta = {
|
||||
|
|
@ -1008,7 +1077,7 @@ class Item {
|
|||
ctx.restore();
|
||||
}
|
||||
} else if (this.type === "sleep_drug") {
|
||||
drawSleepTabletSprite(ctx, this.r);
|
||||
drawSleepTabletSprite(ctx, this.r * visualFieldScaleFor("sleep_drug"), visualPaletteFor("sleep_drug"));
|
||||
} else if (this.type === "genkotsu") {
|
||||
const img = typeof getRenderableImage === "function" ? getRenderableImage("genkotsu", "genkotsu") : images.get("genkotsu");
|
||||
const impact = clamp(this.impactFlash || 0, 0, 1);
|
||||
|
|
@ -1045,9 +1114,10 @@ class Item {
|
|||
ctx.fill();
|
||||
}
|
||||
ctx.restore();
|
||||
} else if (this.type === "pushpin") {
|
||||
} else if (isPinType(this.type)) {
|
||||
const stuck = this.pinState === "lodged";
|
||||
const assetId = stuck ? "pushpin_stuck" : "pushpin";
|
||||
const behavior = typeof pinBehaviorFor === "function" ? pinBehaviorFor(this.type) : null;
|
||||
const assetId = behavior ? (stuck ? behavior.lodgedAsset : behavior.looseAsset) : (stuck ? "pushpin_stuck" : "pushpin");
|
||||
const img = typeof getRenderableImage === "function" ? getRenderableImage(assetId, assetId) : images.get(assetId);
|
||||
ctx.save();
|
||||
if (!stuck) ctx.rotate((this.spin || 0) + Math.PI * 0.5);
|
||||
|
|
@ -1057,7 +1127,7 @@ class Item {
|
|||
if (img) {
|
||||
const metrics = typeof getImageMetrics === "function" ? getImageMetrics(assetId) : null;
|
||||
const ratio = metrics?.ratio || (stuck ? 1 : 2.0);
|
||||
const w = stuck ? this.r * 2.15 : this.r * 1.78;
|
||||
const w = isOshibyo ? (stuck ? this.r * 3.1 : this.r * 1.55) : (stuck ? this.r * 2.15 : this.r * 1.78);
|
||||
const h = w * ratio;
|
||||
ctx.drawImage(img, -w * 0.5, -h * 0.5, w, h);
|
||||
} else if (stuck) {
|
||||
|
|
@ -1090,26 +1160,23 @@ class Item {
|
|||
}
|
||||
ctx.restore();
|
||||
} else if (this.type === "water_bowl") {
|
||||
ctx.fillStyle = "rgba(171, 120, 76, 0.72)";
|
||||
ctx.strokeStyle = "rgba(94, 69, 48, 0.55)";
|
||||
const palette = visualPaletteFor("water_bowl");
|
||||
ctx.fillStyle = palette.bowlFill || "rgba(171, 120, 76, 0.72)";
|
||||
ctx.strokeStyle = palette.bowlStroke || "rgba(94, 69, 48, 0.55)";
|
||||
ctx.lineWidth = 2.2;
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, 2, this.r * 1.35, this.r * 0.78, 0, 0, Math.PI * 2);
|
||||
ctx.fill(); ctx.stroke();
|
||||
ctx.fillStyle = "rgba(86, 157, 224, 0.58)";
|
||||
ctx.strokeStyle = "rgba(62, 113, 178, 0.36)";
|
||||
ctx.fillStyle = palette.waterFill || "rgba(86, 157, 224, 0.58)";
|
||||
ctx.strokeStyle = palette.waterStroke || "rgba(62, 113, 178, 0.36)";
|
||||
ctx.lineWidth = 1.6;
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, -1, this.r * 1.02, this.r * 0.50, 0, 0, Math.PI * 2);
|
||||
ctx.fill(); ctx.stroke();
|
||||
ctx.fillStyle = "rgba(255,255,255,0.54)";
|
||||
ctx.fillStyle = palette.highlight || "rgba(255,255,255,0.54)";
|
||||
ctx.beginPath(); ctx.arc(-this.r * 0.32, -this.r * 0.18, 2.8, 0, Math.PI * 2); ctx.fill();
|
||||
} else if (this.type === "water") {
|
||||
drawOvalWaterDropSprite(ctx, this.r, {
|
||||
fill: "rgba(86, 157, 224, 0.82)",
|
||||
stroke: "rgba(45, 101, 168, 0.52)",
|
||||
highlight: "rgba(255,255,255,0.68)",
|
||||
});
|
||||
drawOvalWaterDropSprite(ctx, this.r * visualFieldScaleFor("water"), visualPaletteFor("water"));
|
||||
} else if (this.type === "grass") {
|
||||
const growth = this.growth ?? clamp(this.amount / 120, 0, 1);
|
||||
const stage = growth < 0.22 ? "sprout" : growth < 0.48 ? "young" : "mature";
|
||||
|
|
@ -1193,6 +1260,36 @@ class Item {
|
|||
ctx.beginPath();
|
||||
ctx.arc(0, -this.r * 0.08, this.r * 0.40, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
} else if (this.type === "signboard") {
|
||||
ctx.save();
|
||||
ctx.fillStyle = styleNight ? "#6d543b" : (styleWarm ? "#a67848" : "#8d633e");
|
||||
ctx.strokeStyle = styleNight ? "#34281c" : "#573c27";
|
||||
ctx.lineWidth = 2.2;
|
||||
ctx.fillRect(-this.r * 0.14, -this.r * 0.05, this.r * 0.28, this.r * 1.58);
|
||||
ctx.strokeRect(-this.r * 0.14, -this.r * 0.05, this.r * 0.28, this.r * 1.58);
|
||||
roundedRect(ctx, -this.r * 1.18, -this.r * 1.18, this.r * 2.36, this.r * 1.18, this.r * 0.14);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = "rgba(255,255,255,0.16)";
|
||||
ctx.fillRect(-this.r * 0.92, -this.r * 0.96, this.r * 1.02, this.r * 0.14);
|
||||
const raw = String(this.text || "").replace(/\s+/g, " ").trim();
|
||||
if (raw) {
|
||||
const charsPerLine = 7;
|
||||
const lines = [];
|
||||
for (let i = 0; i < raw.length && lines.length < 2; i += charsPerLine) lines.push(raw.slice(i, i + charsPerLine));
|
||||
ctx.fillStyle = styleNight ? "rgba(255,249,236,0.92)" : "rgba(51,34,19,0.92)";
|
||||
ctx.font = `bold ${Math.max(8, Math.round(this.r * 0.38))}px ui-rounded, sans-serif`;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
lines.forEach((line, index) => ctx.fillText(line, 0, -this.r * 0.68 + index * this.r * 0.34));
|
||||
} else {
|
||||
ctx.fillStyle = styleNight ? "rgba(255,249,236,0.62)" : "rgba(51,34,19,0.50)";
|
||||
ctx.font = `bold ${Math.max(8, Math.round(this.r * 0.30))}px ui-rounded, sans-serif`;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText("…", 0, -this.r * 0.58);
|
||||
}
|
||||
ctx.restore();
|
||||
} else if (this.type === "ant_nest") {
|
||||
const count = clamp(Math.round(this.antCount ?? ANT_NEST_START_COUNT ?? 6), 0, ANT_NEST_MAX_COUNT ?? 10);
|
||||
ctx.fillStyle = styleNight ? "#5e4634" : (styleWarm ? "#956b42" : "#77583a");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue