fulfillment
This commit is contained in:
parent
e8f1d1db1e
commit
2e90add2e1
67 changed files with 5427 additions and 690 deletions
287
js/items.js
287
js/items.js
|
|
@ -174,6 +174,43 @@ function drawBulletSprite(ctx, r) {
|
|||
ctx.restore();
|
||||
}
|
||||
|
||||
function machineDishPosition(machine) {
|
||||
const r = machine?.r || 42;
|
||||
return { x: machine.x, y: machine.y + r * 1.18 };
|
||||
}
|
||||
|
||||
function machineMuzzlePosition(machine) {
|
||||
const r = machine?.r || 42;
|
||||
const a = Number.isFinite(machine?.cannonAngle) ? machine.cannonAngle : 0;
|
||||
return { x: machine.x + Math.cos(a) * r * 0.98, y: machine.y + Math.sin(a) * r * 0.98 };
|
||||
}
|
||||
|
||||
function makeMachineConsumableCopy(type, x, y, source = null, opts = {}) {
|
||||
const it = new Item(type, x, y);
|
||||
it.machineProduced = true;
|
||||
it.machineProducerId = source?.id || "";
|
||||
it.sourceMachineType = source?.type || "";
|
||||
it.noDecay = true;
|
||||
if (opts.infiniteServing) it.infiniteServing = true;
|
||||
if (opts.projectile) {
|
||||
it.cannonProjectile = true;
|
||||
it.projectileSourceId = source?.id || "";
|
||||
it.projectileTimer = 0;
|
||||
it.projectileHitMemo = {};
|
||||
}
|
||||
if (typeof isServingFoodType === "function" && isServingFoodType(type)) {
|
||||
it.toolSize = source?.machineSlotSize || "medium";
|
||||
it.foodServingScale = 1;
|
||||
it.foodServingsMax = typeof foodServingsForSize === "function" ? foodServingsForSize(it.toolSize) : 5;
|
||||
it.foodServingsRemaining = it.foodServingsMax;
|
||||
it.amount = it.foodServingsMax;
|
||||
}
|
||||
if (type === "water") it.amount = 120;
|
||||
if (type === "grass") { it.amount = Math.max(it.amount || 0, 120); it.growth = Math.max(it.growth || 0, 0.82); }
|
||||
if (type === "zunchi") { it.amount = 240; it.stage = "fresh"; it.stageTimer = 0; it.freshness = 1; }
|
||||
return it;
|
||||
}
|
||||
|
||||
class Item {
|
||||
constructor(type, x, y) {
|
||||
this.id = crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random()}`;
|
||||
|
|
@ -206,6 +243,14 @@ class Item {
|
|||
this.text = "";
|
||||
this.textEditedAt = -999;
|
||||
}
|
||||
if (type === "duplicator" || type === "cannon") {
|
||||
this.machineSlotType = "";
|
||||
this.machineSlotSize = "medium";
|
||||
this.machineSpawnTimer = rand(0.4, 1.1);
|
||||
this.machineLastSlotType = "";
|
||||
this.cannonAngle = type === "cannon" ? -Math.PI * 0.25 : 0;
|
||||
this.cannonFireTimer = rand(0.8, 1.6);
|
||||
}
|
||||
if (type === "ball") {
|
||||
this.vx = rand(-5, 5);
|
||||
this.vy = rand(-5, 5);
|
||||
|
|
@ -278,6 +323,8 @@ class Item {
|
|||
this.passiveFoodDecayTimer = rand(0, passiveFoodDecayInterval());
|
||||
this.amount = this.foodServingsRemaining;
|
||||
}
|
||||
if (window.TarinaiNeedSystem?.itemRolesFor) window.TarinaiNeedSystem.itemRolesFor(this);
|
||||
if (window.TarinaiNeedSystem?.ensureItemNeedEffects) window.TarinaiNeedSystem.ensureItemNeedEffects(this);
|
||||
}
|
||||
update(dt, worldRef = world) {
|
||||
this.age += dt;
|
||||
|
|
@ -289,8 +336,8 @@ class Item {
|
|||
if (worldRef.itemDropImpact) worldRef.itemDropImpact(this);
|
||||
}
|
||||
}
|
||||
if (this.type === "water") this.amount -= dt * 0.9;
|
||||
if (isServingFoodType(this.type)) {
|
||||
if (this.type === "water" && !this.noDecay && !this.infiniteServing) this.amount -= dt * 0.9;
|
||||
if (isServingFoodType(this.type) && !this.noDecay && !this.infiniteServing) {
|
||||
const interval = passiveFoodDecayInterval(worldRef);
|
||||
this.passiveFoodDecayTimer = (this.passiveFoodDecayTimer || 0) + dt;
|
||||
let ticks = 0;
|
||||
|
|
@ -344,6 +391,9 @@ class Item {
|
|||
if (this.type === "ball") this.updateBall(dt, worldRef);
|
||||
if (isPinType(this.type)) this.updatePushpin(dt, worldRef);
|
||||
if (this.type === "zunchi") this.updateZunchiMotion(dt, worldRef);
|
||||
if (this.cannonProjectile) this.updateConsumableProjectile(dt, worldRef);
|
||||
if (this.type === "duplicator") this.updateDuplicator(dt, worldRef);
|
||||
if (this.type === "cannon") this.updateCannon(dt, worldRef);
|
||||
if (this.type === "grass") {
|
||||
this.lifecycleTimer += dt;
|
||||
const factor = worldRef.grassUpdateFactor ? worldRef.grassUpdateFactor() : 1;
|
||||
|
|
@ -364,6 +414,152 @@ class Item {
|
|||
}
|
||||
}
|
||||
|
||||
machineDishPosition() {
|
||||
return machineDishPosition(this);
|
||||
}
|
||||
|
||||
clearMachineProducedItems(worldRef, keepType = "") {
|
||||
for (const it of worldRef?.items || []) {
|
||||
if (!it || it.dead || it.machineProducerId !== this.id) continue;
|
||||
if (keepType && it.type === keepType && this.type === "duplicator") continue;
|
||||
it.amount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
updateDuplicator(dt, worldRef) {
|
||||
if (!worldRef || !this.machineSlotType) return;
|
||||
if (this.machineLastSlotType && this.machineLastSlotType !== this.machineSlotType) this.clearMachineProducedItems(worldRef);
|
||||
this.machineLastSlotType = this.machineSlotType;
|
||||
this.machineSpawnTimer = Math.max(0, (this.machineSpawnTimer || 0) - dt);
|
||||
const dish = this.machineDishPosition();
|
||||
let serving = null;
|
||||
for (const it of worldRef.items || []) {
|
||||
if (!it || it.dead || it.machineProducerId !== this.id || it.type !== this.machineSlotType) continue;
|
||||
serving = it;
|
||||
break;
|
||||
}
|
||||
if (!serving && this.machineSpawnTimer <= 0) {
|
||||
serving = makeMachineConsumableCopy(this.machineSlotType, dish.x, dish.y, this, { infiniteServing: true });
|
||||
serving.r = Math.max(6, (serving.r || 12) * 0.92);
|
||||
worldRef.items.push(serving);
|
||||
worldRef.itemCounts[serving.type] = (worldRef.itemCounts[serving.type] || 0) + 1;
|
||||
this.machineSpawnTimer = 1.2;
|
||||
worldRef.rebuildSpatial?.(true);
|
||||
}
|
||||
if (serving) {
|
||||
serving.x = dish.x;
|
||||
serving.y = dish.y;
|
||||
serving.vx = 0;
|
||||
serving.vy = 0;
|
||||
serving.prevX = serving.x;
|
||||
serving.prevY = serving.y;
|
||||
serving.noDecay = true;
|
||||
serving.infiniteServing = true;
|
||||
serving.machineProduced = true;
|
||||
serving.machineProducerId = this.id;
|
||||
if (typeof isServingFoodType === "function" && isServingFoodType(serving.type)) {
|
||||
if (!Number.isFinite(serving.foodServingsMax) || serving.foodServingsMax <= 0) serving.foodServingsMax = typeof foodServingsForSize === "function" ? foodServingsForSize(serving.toolSize || "medium") : 5;
|
||||
serving.foodServingsRemaining = serving.foodServingsMax;
|
||||
serving.amount = serving.foodServingsMax;
|
||||
} else if (serving.type === "water") serving.amount = Math.max(serving.amount || 0, 120);
|
||||
else if (serving.type === "grass") serving.amount = Math.max(serving.amount || 0, 120);
|
||||
else if (serving.type === "zunchi") serving.amount = Math.max(serving.amount || 0, 240);
|
||||
}
|
||||
}
|
||||
|
||||
updateCannon(dt, worldRef) {
|
||||
if (!worldRef || !this.machineSlotType) return;
|
||||
this.cannonFireTimer = Math.max(0, (this.cannonFireTimer || 0) - dt);
|
||||
if (this.cannonFireTimer > 0) return;
|
||||
this.cannonFireTimer = rand(2.2, 3.4);
|
||||
const a = Number.isFinite(this.cannonAngle) ? this.cannonAngle : 0;
|
||||
const muzzle = machineMuzzlePosition(this);
|
||||
const shot = makeMachineConsumableCopy(this.machineSlotType, muzzle.x, muzzle.y, this, { projectile: true });
|
||||
shot.r = Math.max(5, (shot.r || 12) * 0.78);
|
||||
const speed = rand(470, 620);
|
||||
shot.vx = Math.cos(a) * speed + rand(-18, 18);
|
||||
shot.vy = Math.sin(a) * speed + rand(-18, 18);
|
||||
shot.prevX = shot.x;
|
||||
shot.prevY = shot.y;
|
||||
shot.spinVelocity = rand(-6, 6) + speed / Math.max(8, shot.r || 10) * 0.12;
|
||||
shot.amount = Math.max(shot.amount || 0, typeof isServingFoodType === "function" && isServingFoodType(shot.type) ? (shot.foodServingsMax || 5) : itemAmountFor(shot.type, 60));
|
||||
worldRef.items.push(shot);
|
||||
worldRef.itemCounts[shot.type] = (worldRef.itemCounts[shot.type] || 0) + 1;
|
||||
worldRef.effects?.push(new Effect("ring", muzzle.x, muzzle.y, { size: Math.max(16, (this.r || 42) * 0.42), life: 0.22, color: "rgba(255,238,160,0.52)" }));
|
||||
worldRef.log?.(`大砲が${toolLabel(shot.type)}を撃ち出した。`, "accident");
|
||||
audio.place?.(shot.type);
|
||||
worldRef.rebuildSpatial?.(true);
|
||||
}
|
||||
|
||||
updateConsumableProjectile(dt, worldRef) {
|
||||
if (!worldRef) return;
|
||||
this.projectileTimer = (this.projectileTimer || 0) + dt;
|
||||
this.prevX = this.x;
|
||||
this.prevY = this.y;
|
||||
const speedBefore = Math.hypot(this.vx || 0, this.vy || 0);
|
||||
if (speedBefore > 0.05) {
|
||||
if (window.TarinaiPhysics?.applyKinematicItemMotion) {
|
||||
window.TarinaiPhysics.applyKinematicItemMotion(this, worldRef, dt, { padding: 28, bounce: 0.46, frictionBase: 0.62, frictionRate: 1.25, spin: false, stopSpeed: 0.12, zeroBelow: 14 });
|
||||
} else {
|
||||
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.46; }
|
||||
if (this.x > worldRef.w - p) { this.x = worldRef.w - p; this.vx = -Math.abs(this.vx || 0) * 0.46; }
|
||||
if (this.y < p) { this.y = p; this.vy = Math.abs(this.vy || 0) * 0.46; }
|
||||
if (this.y > worldRef.h - p) { this.y = worldRef.h - p; this.vy = -Math.abs(this.vy || 0) * 0.46; }
|
||||
const slow = Math.pow(0.62, dt * 1.25);
|
||||
this.vx *= slow;
|
||||
this.vy *= slow;
|
||||
}
|
||||
this.spin = (this.spin || 0) + speedBefore * dt / Math.max(6, this.r || 10);
|
||||
this.resolveProjectileTarinaiCollision(dt, worldRef, speedBefore);
|
||||
}
|
||||
const speedAfter = Math.hypot(this.vx || 0, this.vy || 0);
|
||||
if (speedAfter < 18 && (this.projectileTimer || 0) > 0.30) {
|
||||
this.cannonProjectile = false;
|
||||
this.noDecay = false;
|
||||
this.projectileHitMemo = {};
|
||||
this.vx = 0;
|
||||
this.vy = 0;
|
||||
}
|
||||
if ((this.projectileTimer || 0) > 28) this.cannonProjectile = false;
|
||||
}
|
||||
|
||||
resolveProjectileTarinaiCollision(dt, worldRef, speed = 0) {
|
||||
if (!worldRef || speed < 210 || (this.amount || 0) <= 0) return;
|
||||
const range = Math.max(42, (this.r || 12) + speed * Math.max(dt || 0.016, 0.016) + 22);
|
||||
for (const t of worldRef.nearbyTarinai?.(this.x, this.y, range, 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 || 12)) continue;
|
||||
const now = worldRef.time || 0;
|
||||
this.projectileHitMemo = this.projectileHitMemo || {};
|
||||
if (this.projectileHitMemo[t.id] && this.projectileHitMemo[t.id] + 0.58 > now) continue;
|
||||
this.projectileHitMemo[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 * 0.95, 130, 560);
|
||||
t.vx = (t.vx || 0) + nx * impulse + rand(-18, 18);
|
||||
t.vy = (t.vy || 0) + ny * impulse * 0.70 + rand(-14, 12);
|
||||
t.fallTimer = Math.max(t.fallTimer || 0, 0.72);
|
||||
t.fallMax = Math.max(t.fallMax || 0.72, t.fallTimer);
|
||||
t.fallDir = nx < 0 ? -1 : 1;
|
||||
const label = toolLabel(this.type) || "食べ物";
|
||||
if (t.enterPanic) t.enterPanic({ threat: this, reason: `大砲から飛んできた${label}にぶつかって吹き飛んでいる`, fear: 0.72, stress: 6, stressDuration: 3.0, surpriseTimer: 0.48, cause: "cannon_projectile_hit" });
|
||||
else t.setActionState?.("panic", { target: { x: this.x, y: this.y }, reason: `大砲から飛んできた${label}にぶつかって吹き飛んでいる`, wake: true });
|
||||
if (worldRef.applyImpactDamage) worldRef.applyImpactDamage(t, clamp((speed - 190) / 30, 1.4, 16), "大砲");
|
||||
worldRef.spawnFallEffect?.(t.x, t.y + (t.radius || 20) * 0.50, 0.78);
|
||||
worldRef.effects?.push(new Effect("ring", this.x, this.y, { size: Math.max(17, (this.r || 12) * 1.15), life: 0.22, color: "rgba(220,160,70,0.52)" }));
|
||||
if (worldRef.relationNotice?.(t.id, this.id || "projectile", "cannon-projectile-hit", 2.8)) worldRef.log?.(`${t.name}は大砲から飛んできた${label}にぶつかって吹き飛ばされた。`, "accident", { participants: [t] });
|
||||
this.vx *= -0.12;
|
||||
this.vy *= -0.12;
|
||||
this.cannonProjectile = false;
|
||||
this.noDecay = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
updateZunchiMotion(dt, worldRef) {
|
||||
const speed = Math.hypot(this.vx || 0, this.vy || 0);
|
||||
if (speed < 0.08) { this.vx = 0; this.vy = 0; return; }
|
||||
|
|
@ -668,9 +864,11 @@ class Item {
|
|||
this.pinState = "lodged";
|
||||
this.deletable = false;
|
||||
this.pinTargetId = t.id;
|
||||
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);
|
||||
const visualSide = (t.facingDir ? t.facingDir() : (t.facing || 1)) > 0 ? -1 : 1;
|
||||
this.pinAttachAngle = isOshibyo ? (visualSide < 0 ? -0.48 : 0.48) : Math.atan2(dy || -1, dx || visualSide);
|
||||
this.pinAttachDistance = isOshibyo ? (t.radius || 16) * 0.76 : clamp(distToTarget || (t.radius || 16) * 0.58, (t.radius || 16) * 0.20, (t.radius || 16) * 0.74);
|
||||
this.pinOffsetY = isOshibyo ? (t.radius || 16) * 0.22 : clamp(dy, -(t.radius || 16) * 0.74, (t.radius || 16) * 0.38);
|
||||
this.pinFlipX = visualSide;
|
||||
this.pinDamageTick = 0;
|
||||
this.pinFallCheckTimer = 0;
|
||||
this.vx = 0;
|
||||
|
|
@ -714,11 +912,13 @@ class Item {
|
|||
const isOshibyo = Boolean(behavior?.blocksZunchi);
|
||||
t.stuckPushpinId = this.id;
|
||||
this.deletable = false;
|
||||
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;
|
||||
const visualSide = (t.facingDir ? t.facingDir() : (t.facing || 1)) > 0 ? -1 : 1;
|
||||
this.pinFlipX = visualSide;
|
||||
this.x = t.x + visualSide * (isOshibyo ? (t.radius || 16) * 0.72 : (t.radius || 16) * 0.36);
|
||||
this.y = isOshibyo ? t.y + (t.radius || 16) * 0.22 : t.y - (t.radius || 16) * 0.04;
|
||||
this.prevX = this.x;
|
||||
this.prevY = this.y;
|
||||
this.spin = isOshibyo ? 0.52 : 0.28;
|
||||
this.spin = (isOshibyo ? 0.42 : 0.28) * (this.pinFlipX < 0 ? -1 : 1);
|
||||
this.vx = t.vx || 0;
|
||||
this.vy = t.vy || 0;
|
||||
if (isOshibyo) {
|
||||
|
|
@ -1114,20 +1314,89 @@ class Item {
|
|||
ctx.fill();
|
||||
}
|
||||
ctx.restore();
|
||||
} else if (this.type === "duplicator" || this.type === "cannon") {
|
||||
const r = this.r || 42;
|
||||
const isCannon = this.type === "cannon";
|
||||
ctx.save();
|
||||
ctx.shadowColor = "rgba(40,35,30,0.24)";
|
||||
ctx.shadowBlur = 6;
|
||||
ctx.shadowOffsetY = 3;
|
||||
if (!isCannon) {
|
||||
// Upper machine body.
|
||||
const g = ctx.createLinearGradient(0, -r * 1.0, 0, r * 0.32);
|
||||
g.addColorStop(0, "#e7f1ff");
|
||||
g.addColorStop(1, "#8aa2bd");
|
||||
ctx.fillStyle = g;
|
||||
ctx.strokeStyle = "rgba(48,68,92,0.70)";
|
||||
ctx.lineWidth = Math.max(2, r * 0.06);
|
||||
roundedRect(ctx, -r * 0.82, -r * 1.05, r * 1.64, r * 0.92, r * 0.18);
|
||||
ctx.fill(); ctx.stroke();
|
||||
ctx.fillStyle = this.machineSlotType ? "rgba(100,214,102,0.92)" : "rgba(255,180,72,0.92)";
|
||||
ctx.beginPath(); ctx.arc(r * 0.48, -r * 0.76, r * 0.12, 0, Math.PI * 2); ctx.fill();
|
||||
ctx.strokeStyle = "rgba(42,55,68,0.54)";
|
||||
ctx.lineWidth = Math.max(1.6, r * 0.04);
|
||||
ctx.beginPath(); ctx.moveTo(-r * 0.48, -r * 0.78); ctx.lineTo(r * 0.20, -r * 0.78); ctx.stroke();
|
||||
// Plate for tarinai below.
|
||||
ctx.fillStyle = "rgba(245,244,233,0.96)";
|
||||
ctx.strokeStyle = "rgba(116,96,75,0.58)";
|
||||
ctx.lineWidth = Math.max(2, r * 0.05);
|
||||
ctx.beginPath(); ctx.ellipse(0, r * 1.18, r * 0.95, r * 0.34, 0, 0, Math.PI * 2); ctx.fill(); ctx.stroke();
|
||||
ctx.fillStyle = "rgba(196,180,154,0.22)";
|
||||
ctx.beginPath(); ctx.ellipse(0, r * 1.18, r * 0.58, r * 0.16, 0, 0, Math.PI * 2); ctx.fill();
|
||||
if (this.machineSlotType) {
|
||||
ctx.fillStyle = "rgba(42,36,28,0.72)";
|
||||
ctx.font = `${Math.max(10, Math.round(r * 0.25))}px ui-rounded, sans-serif`;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(toolLabel(this.machineSlotType), 0, r * 0.52);
|
||||
}
|
||||
} else {
|
||||
const a = Number.isFinite(this.cannonAngle) ? this.cannonAngle : 0;
|
||||
// Base plate.
|
||||
ctx.fillStyle = "#6c7685";
|
||||
ctx.strokeStyle = "rgba(42,48,58,0.72)";
|
||||
ctx.lineWidth = Math.max(2, r * 0.06);
|
||||
ctx.beginPath(); ctx.ellipse(0, r * 0.72, r * 0.78, r * 0.30, 0, 0, Math.PI * 2); ctx.fill(); ctx.stroke();
|
||||
// Barrel.
|
||||
ctx.rotate(a);
|
||||
const bg = ctx.createLinearGradient(-r * 0.34, -r * 0.26, r * 1.08, r * 0.26);
|
||||
bg.addColorStop(0, "#596575");
|
||||
bg.addColorStop(0.55, "#2f3946");
|
||||
bg.addColorStop(1, "#1f2630");
|
||||
ctx.fillStyle = bg;
|
||||
ctx.strokeStyle = "rgba(16,20,26,0.82)";
|
||||
roundedRect(ctx, -r * 0.28, -r * 0.26, r * 1.32, r * 0.52, r * 0.18);
|
||||
ctx.fill(); ctx.stroke();
|
||||
ctx.fillStyle = "rgba(10,12,16,0.88)";
|
||||
ctx.beginPath(); ctx.ellipse(r * 1.02, 0, r * 0.16, r * 0.23, 0, 0, Math.PI * 2); ctx.fill();
|
||||
ctx.rotate(-a);
|
||||
ctx.fillStyle = "#8793a5";
|
||||
ctx.beginPath(); ctx.arc(0, 0, r * 0.36, 0, Math.PI * 2); ctx.fill(); ctx.stroke();
|
||||
if (this.machineSlotType) {
|
||||
ctx.fillStyle = "rgba(42,36,28,0.72)";
|
||||
ctx.font = `${Math.max(10, Math.round(r * 0.23))}px ui-rounded, sans-serif`;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(toolLabel(this.machineSlotType), 0, -r * 0.62);
|
||||
}
|
||||
}
|
||||
ctx.restore();
|
||||
} else if (isPinType(this.type)) {
|
||||
const stuck = this.pinState === "lodged";
|
||||
const behavior = typeof pinBehaviorFor === "function" ? pinBehaviorFor(this.type) : null;
|
||||
const assetId = behavior ? (stuck ? behavior.lodgedAsset : behavior.looseAsset) : (stuck ? "pushpin_stuck" : "pushpin");
|
||||
const isOshibyo = Boolean(behavior?.blocksZunchi);
|
||||
const img = typeof getRenderableImage === "function" ? getRenderableImage(assetId, assetId) : images.get(assetId);
|
||||
ctx.save();
|
||||
if (!stuck) ctx.rotate((this.spin || 0) + Math.PI * 0.5);
|
||||
else if ((this.pinFlipX || 1) < 0) ctx.scale(-1, 1);
|
||||
ctx.shadowColor = stuck ? "rgba(116,24,32,0.18)" : "rgba(54,42,31,0.16)";
|
||||
ctx.shadowBlur = 3;
|
||||
ctx.shadowOffsetY = 2;
|
||||
if (img) {
|
||||
const metrics = typeof getImageMetrics === "function" ? getImageMetrics(assetId) : null;
|
||||
const ratio = metrics?.ratio || (stuck ? 1 : 2.0);
|
||||
const w = isOshibyo ? (stuck ? this.r * 3.1 : this.r * 1.55) : (stuck ? this.r * 2.15 : this.r * 1.78);
|
||||
const w = isOshibyo ? (stuck ? this.r * 2.45 : this.r * 1.25) : (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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue