509 lines
24 KiB
JavaScript
509 lines
24 KiB
JavaScript
"use strict";
|
|
|
|
(function (global) {
|
|
function createStructureId(prefix = "structure") {
|
|
return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`;
|
|
}
|
|
|
|
const clonePlain = global.TarinaiCoreHelpers?.clonePlain || (value => JSON.parse(JSON.stringify(value || {})));
|
|
|
|
class TarinaiStructure {
|
|
constructor(definition, owner, x, y, world) {
|
|
this.id = createStructureId("structure");
|
|
this.type = definition.type;
|
|
this.label = definition.label;
|
|
this.isStructure = true;
|
|
this.ownerId = owner?.id || "";
|
|
this.hp = definition.maxHp || 1;
|
|
this.maxHp = definition.maxHp || this.hp;
|
|
this.attachment = definition.type === "plushie" ? 30 : 20;
|
|
this.usedCount = 0;
|
|
this.x = x;
|
|
this.y = y;
|
|
this.r = definition.type === "plushie" ? 6.5 : (definition.type === "grass_bed" ? 18 : 24);
|
|
this.amount = this.hp;
|
|
this.seed = Math.random() * 1000;
|
|
this.createdAt = Number.isFinite(Number(world?.time)) ? Number(world.time) : 0;
|
|
this.age = 0;
|
|
this.roles = clonePlain(definition.roles);
|
|
this.needEffects = clonePlain(definition.needEffects);
|
|
this.carriedById = definition.type === "plushie" ? this.ownerId : "";
|
|
this.onHead = definition.type === "plushie";
|
|
if (definition.type === "plushie") {
|
|
this.plushieFlingTimer = 0;
|
|
this.plushieTrailTimer = 0;
|
|
this.spin = 0;
|
|
this.spinVelocity = 0;
|
|
this.vx = 0;
|
|
this.vy = 0;
|
|
}
|
|
this.dead = false;
|
|
this.world = world || null;
|
|
}
|
|
|
|
owner(worldRef = this.world) {
|
|
return worldRef?.liveTarinaiById?.(this.ownerId) || (worldRef?.tarinai || []).find(t => t && !t.dead && t.id === this.ownerId) || null;
|
|
}
|
|
|
|
update(dt, worldRef = this.world) {
|
|
this.world = worldRef || this.world;
|
|
if (this.dead || this.hp <= 0) {
|
|
this.amount = 0;
|
|
return;
|
|
}
|
|
if (this.type === "plushie") {
|
|
if ((this.plushieFlingTimer || 0) > 0) {
|
|
this.carriedById = "";
|
|
this.onHead = false;
|
|
this.x += (Number(this.vx || 0) || 0) * dt;
|
|
this.y += (Number(this.vy || 0) || 0) * dt;
|
|
this.vy = (Number(this.vy || 0) || 0) + 34 * dt;
|
|
this.spin = (Number(this.spin || 0) || 0) + (Number(this.spinVelocity || 0) || 0) * dt;
|
|
this.plushieFlingTimer = Math.max(0, (this.plushieFlingTimer || 0) - dt);
|
|
this.plushieTrailTimer = Math.max(0, (this.plushieTrailTimer || 0) - dt);
|
|
if (worldRef?.effects && typeof Effect !== "undefined" && this.plushieTrailTimer <= 0) {
|
|
this.plushieTrailTimer = 0.045;
|
|
worldRef.spawnEffect("ring", this.x, this.y, { size: 10 + Math.random() * 8, life: 0.16, color: "rgba(190,142,92,0.28)" });
|
|
}
|
|
const margin = 96;
|
|
if (this.plushieFlingTimer <= 0 || this.x < -margin || this.x > (worldRef?.w || 0) + margin || this.y < -margin || this.y > (worldRef?.h || 0) + margin) {
|
|
this.dead = true;
|
|
this.hp = 0;
|
|
this.amount = 0;
|
|
global.StructureRegistry?.get?.(this.type)?.onDestroyed?.(this, worldRef, { type: "fling_out", name: "\u753b\u9762\u5916" });
|
|
global.TarinaiStructureLifecycle?.triggerOwnedStructureDestroyedPanic?.(worldRef, this, null, "plushie-fling-out");
|
|
worldRef?.markItemBucketsDirty?.("plushie-fling-out");
|
|
worldRef?.markSpatialDirty?.("plushie-fling-out");
|
|
}
|
|
worldRef && (worldRef.drawListDirty = true);
|
|
return;
|
|
}
|
|
const owner = this.owner(worldRef);
|
|
if (!this.ownerId || !owner) {
|
|
this.dead = true;
|
|
this.hp = 0;
|
|
this.amount = 0;
|
|
this.carriedById = "";
|
|
this.onHead = false;
|
|
worldRef && (worldRef.drawListDirty = true);
|
|
worldRef?.markItemBucketsDirty?.("plushie-owner-gone");
|
|
worldRef?.markSpatialDirty?.("plushie-owner-gone");
|
|
return;
|
|
}
|
|
if (this.playerHeld || this._heldByPlayer) {
|
|
this.carriedById = "";
|
|
this.onHead = false;
|
|
this.amount = Math.max(0.001, Number(this.hp || this.amount || 1) || 1);
|
|
worldRef && (worldRef.drawListDirty = true);
|
|
return;
|
|
}
|
|
this.ownerId = owner.id || this.ownerId;
|
|
this.carriedById = owner.id || this.ownerId || this.carriedById;
|
|
this.onHead = true;
|
|
this.x = owner.x;
|
|
this.y = owner.y - Math.max(22, (owner.radius || 24) * 1.02);
|
|
if (owner.needs) owner.needs.fulfill = Math.max(0, (owner.needs.fulfill || 0) - dt * 2.8);
|
|
}
|
|
if (this.type === "grave") {
|
|
if (global.TarinaiMemorialBellSystem?.updateGraveNaturalDecay?.(this, worldRef)) return;
|
|
}
|
|
if (this.type === "grass_bed") {
|
|
if (!Number.isFinite(this.hp) || this.hp <= 0) {
|
|
this.hp = Math.max(1, Number(this.maxHp || 100) || 100);
|
|
this.amount = this.hp;
|
|
}
|
|
const owner = this.owner(worldRef);
|
|
if (!owner && !this.singleUsePending) {
|
|
this.unownedDecayTimer = (this.unownedDecayTimer || 0) + dt;
|
|
const rainBoost = worldRef?.weather === "light_rain" ? 1.65 : 1;
|
|
const ageBoost = 1 + Math.min(1.8, this.unownedDecayTimer / 360);
|
|
this.damage(dt * 0.070 * rainBoost * ageBoost, worldRef, null);
|
|
if (this.dead) return;
|
|
} else {
|
|
this.unownedDecayTimer = 0;
|
|
}
|
|
}
|
|
this.amount = Math.max(0.001, Number(this.hp || this.amount || 1) || 1);
|
|
}
|
|
|
|
use(user, worldRef = this.world, options = {}) {
|
|
const definition = global.StructureRegistry?.get?.(this.type);
|
|
return definition?.onUse?.(user, this, worldRef, options) || false;
|
|
}
|
|
|
|
damage(amount = 1, worldRef = this.world, breaker = null) {
|
|
if (this.dead) return false;
|
|
this.hp = Math.max(0, (this.hp || 0) - Math.max(0, amount || 0));
|
|
this.amount = this.hp;
|
|
if (this.hp > 0) return false;
|
|
this.dead = true;
|
|
this.amount = 0;
|
|
global.StructureRegistry?.get?.(this.type)?.onDestroyed?.(this, worldRef, breaker);
|
|
global.TarinaiStructureLifecycle.triggerOwnedStructureDestroyedPanic(worldRef, this, breaker, "structure-damaged-destroyed");
|
|
return true;
|
|
}
|
|
|
|
fling(worldRef = this.world, sourceX = this.x, sourceY = this.y, options = {}) {
|
|
if (this.dead || this.type !== "plushie") return false;
|
|
const world = worldRef || this.world;
|
|
this.world = world || this.world;
|
|
const owner = this.owner(world);
|
|
let vx = Number(options.vx);
|
|
let vy = Number(options.vy);
|
|
let speed = Math.hypot(Number.isFinite(vx) ? vx : 0, Number.isFinite(vy) ? vy : 0);
|
|
if (!Number.isFinite(vx) || !Number.isFinite(vy) || speed < 80) {
|
|
let dx = (Number(this.x || 0) || 0) - (Number(sourceX || 0) || 0);
|
|
let dy = (Number(this.y || 0) || 0) - (Number(sourceY || 0) || 0);
|
|
let len = Math.hypot(dx, dy);
|
|
if (options.source === "poke" && owner) {
|
|
const fromOwnerX = (Number(this.x || owner.x || 0) || 0) - (Number(owner.x || 0) || 0);
|
|
const pointerSide = Math.sign((Number(this.x || 0) || 0) - (Number(sourceX || 0) || 0)) || Math.sign(fromOwnerX) || Number(owner.facing || owner.faceDir || 1) || 1;
|
|
if (len < 18 || Math.abs(dx) < Math.abs(dy) * 0.42) {
|
|
dx = pointerSide;
|
|
dy = -0.42;
|
|
len = Math.hypot(dx, dy);
|
|
}
|
|
}
|
|
if (len < 0.001 && owner) {
|
|
dx = Number(owner.facing || owner.faceDir || 1) || 1;
|
|
dy = -0.34;
|
|
len = Math.hypot(dx, dy);
|
|
}
|
|
if (len < 0.001) {
|
|
dx = Math.random() < 0.5 ? -1 : 1;
|
|
dy = -0.24;
|
|
len = Math.hypot(dx, dy);
|
|
}
|
|
const base = Math.max(520 / 3, Math.min(980 / 3, Number(options.speed || (650 / 3)) || (650 / 3)));
|
|
vx = dx / len * base;
|
|
vy = dy / len * base - Math.max(80, base * 0.18);
|
|
if (options.source === "poke") {
|
|
const side = Math.sign(vx) || Math.sign(dx) || Number(owner?.facing || owner?.faceDir || 1) || 1;
|
|
vx = side * Math.max(Math.abs(vx), base * 0.58);
|
|
vy = Math.min(vy, -base * 0.26);
|
|
}
|
|
speed = Math.hypot(vx, vy);
|
|
} else if (speed < 520 / 3) {
|
|
const scale = (520 / 3) / Math.max(1, speed);
|
|
vx *= scale;
|
|
vy *= scale;
|
|
speed = Math.hypot(vx, vy);
|
|
}
|
|
this.vx = clamp(vx, -1700, 1700);
|
|
this.vy = clamp(vy, -1350, 1100);
|
|
this.prevX = this.x;
|
|
this.prevY = this.y;
|
|
this.detachedUntil = world ? Math.max(this.detachedUntil || 0, (Number(world.time || 0) || 0) + 0.35) : (this.detachedUntil || 0);
|
|
this.spin = Number(this.spin || 0) || 0;
|
|
const spinSign = Math.sign(this.vx) || (Math.random() < 0.5 ? -1 : 1);
|
|
const autoSpin = spinSign * (11 + Math.min(14, speed / 80));
|
|
this.spinVelocity = clamp(Number(options.spinVelocity || autoSpin) || autoSpin, -28, 28);
|
|
this.plushieFlingTimer = Math.max(1.3, Math.min(options.destroyOffscreen ? 5.4 : 3.4, Number(options.life || 2.7) || 2.7));
|
|
this.plushieTrailTimer = 0;
|
|
this.carriedById = "";
|
|
this.onHead = false;
|
|
this.playerHeld = false;
|
|
this._heldByPlayer = false;
|
|
if (owner) {
|
|
owner.target = null;
|
|
owner.thought = "\u306C\u3044\u3050\u308B\u307F\u304C\u98DB\u3093\u3067\u3044\u3063\u305F";
|
|
owner.pinchThoughtTimer = Math.max(owner.pinchThoughtTimer || 0, 1.1);
|
|
owner.surpriseTimer = Math.max(owner.surpriseTimer || 0, 0.45);
|
|
if (typeof applyNeedShock === "function") applyNeedShock(owner, { fulfill: 12, safety: 4 }, this);
|
|
}
|
|
if (world?.effects && typeof Effect !== "undefined") {
|
|
world.spawnEffect("ring", this.x, this.y, { size: 30, life: 0.22, color: "rgba(190,142,92,0.38)" });
|
|
world.spawnEffect("fall", this.x, this.y, { size: 18, life: 0.28, color: "rgba(190,142,92,0.28)" });
|
|
}
|
|
world?.markItemBucketsDirty?.("plushie-fling");
|
|
world?.markSpatialDirty?.("plushie-fling");
|
|
if (world) world.drawListDirty = true;
|
|
// Player-triggered plushie flings are intentionally excluded from the event log.
|
|
return true;
|
|
}
|
|
|
|
draw(ctx, time = 0, lighting = {}) {
|
|
if (this.dead) return;
|
|
let ownerAlpha = 1;
|
|
if (this.type === "plushie") {
|
|
const owner = this.owner(this.world);
|
|
ownerAlpha = owner ? clamp(1 - (owner.nestFade || (owner.insideNestBoxId ? 1 : 0)), 0, 1) : 1;
|
|
if (ownerAlpha <= 0.02) return;
|
|
}
|
|
ctx.save();
|
|
if (this.type === "plushie") ctx.globalAlpha *= ownerAlpha;
|
|
ctx.translate(this.x, this.y);
|
|
const night = (lighting?.light || 1) < 0.42;
|
|
if (this.type === "grass_bed") {
|
|
// \u304b\u3093\u305f\u3093\u30d9\u30c3\u30c9: \u7dd1\u8272\u306e\u5c0f\u3055\u3044\u5e72\u8349\u5bdd\u5e8a\u3002\u901a\u5e38\u306e\u5e72\u8349\u5bdd\u5e8a\u3088\u308a\u4e00\u56de\u308a\u5c0f\u3055\u304f\u3001\u8349\u675f\u611f\u3092\u5f37\u3081\u308b\u3002
|
|
const grassFill = night ? "rgba(78,132,67,0.90)" : "rgba(84,177,72,0.93)";
|
|
const grassStroke = night ? "rgba(39,78,43,0.76)" : "rgba(45,112,48,0.78)";
|
|
const strawStroke = night ? "rgba(168,207,119,0.42)" : "rgba(205,234,128,0.66)";
|
|
ctx.fillStyle = grassFill;
|
|
ctx.strokeStyle = grassStroke;
|
|
ctx.lineWidth = 2;
|
|
ctx.beginPath();
|
|
ctx.ellipse(0, 4, this.r * 1.72, this.r * 0.66, -0.08, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
ctx.strokeStyle = strawStroke;
|
|
ctx.lineWidth = 1.45;
|
|
for (let i = 0; i < 15; i++) {
|
|
const x = -this.r * 1.14 + i * this.r * 0.16;
|
|
const y = this.r * 0.38 + Math.sin(i * 1.7 + this.seed) * 1.8;
|
|
const endX = x + Math.cos(-0.55 + i * 0.08) * this.r * 0.74;
|
|
const endY = -this.r * 0.30 + Math.sin(time * 0.7 + i) * 2.6;
|
|
ctx.beginPath();
|
|
ctx.moveTo(x, y);
|
|
ctx.quadraticCurveTo(Math.sin(time + i) * 3.2, -this.r * 0.10, endX, endY);
|
|
ctx.stroke();
|
|
}
|
|
ctx.fillStyle = night ? "rgba(42,93,42,0.34)" : "rgba(66,145,47,0.36)";
|
|
ctx.beginPath();
|
|
ctx.ellipse(0, this.r * 0.22, this.r * 1.28, this.r * 0.28, -0.06, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
} else if (this.type === "grave") {
|
|
const favoriteMemorial = Boolean(this.memorialFavorite);
|
|
const kingMemorial = Boolean(this.memorialTarinaiKing);
|
|
const slaveMemorial = Boolean(this.memorialZunchiSlave);
|
|
const graveScale = slaveMemorial ? 0.72 : (kingMemorial ? 1.10 : 1);
|
|
ctx.scale(graveScale, graveScale);
|
|
const stone = night ? (kingMemorial ? "rgb(139,139,149)" : favoriteMemorial ? "rgb(126,126,133)" : "rgb(111,112,116)") : (kingMemorial ? "rgb(197,190,174)" : favoriteMemorial ? "rgb(181,176,164)" : "rgb(163,158,149)");
|
|
const edge = night ? "rgb(58,60,66)" : "rgb(91,87,80)";
|
|
ctx.fillStyle = "rgba(56,47,40,0.18)";
|
|
ctx.beginPath(); ctx.ellipse(0, this.r * 0.58, this.r * 1.02, this.r * 0.28, 0, 0, Math.PI * 2); ctx.fill();
|
|
ctx.fillStyle = stone;
|
|
ctx.strokeStyle = edge;
|
|
ctx.lineWidth = kingMemorial ? 2.6 : 2;
|
|
ctx.beginPath();
|
|
ctx.moveTo(-this.r * 0.78, this.r * 0.22);
|
|
ctx.bezierCurveTo(-this.r * 0.88, -this.r * 0.38, -this.r * 0.46, -this.r * 0.80, this.r * 0.06, -this.r * 0.72);
|
|
ctx.bezierCurveTo(this.r * 0.62, -this.r * 0.68, this.r * 0.88, -this.r * 0.20, this.r * 0.72, this.r * 0.30);
|
|
ctx.bezierCurveTo(this.r * 0.44, this.r * 0.64, -this.r * 0.50, this.r * 0.62, -this.r * 0.78, this.r * 0.22);
|
|
ctx.closePath(); ctx.fill(); ctx.stroke();
|
|
ctx.fillStyle = night ? "rgba(165,166,174,0.42)" : "rgba(225,220,208,0.48)";
|
|
ctx.beginPath(); ctx.ellipse(-this.r * 0.18, -this.r * 0.30, this.r * 0.30, this.r * 0.13, -0.35, 0, Math.PI * 2); ctx.fill();
|
|
|
|
if (favoriteMemorial || kingMemorial) {
|
|
ctx.fillStyle = night ? "rgb(101,102,108)" : "rgb(154,150,143)";
|
|
const stones = kingMemorial ? [
|
|
[-1.02,0.40,0.42,0.26,-0.18],[0.94,0.42,0.38,0.24,0.18],[-0.66,0.62,0.29,0.18,0.10],[0.58,0.63,0.28,0.18,-0.12],[0.02,0.72,0.24,0.14,0]
|
|
] : [
|
|
[-0.92,0.43,0.34,0.22,-0.18],[0.82,0.48,0.29,0.19,0.20],[-0.58,0.58,0.22,0.14,0.10]
|
|
];
|
|
for (const stoneSpec of stones) {
|
|
ctx.beginPath();
|
|
ctx.ellipse(this.r * stoneSpec[0], this.r * stoneSpec[1], this.r * stoneSpec[2], this.r * stoneSpec[3], stoneSpec[4], 0, Math.PI * 2);
|
|
ctx.fill(); ctx.stroke();
|
|
}
|
|
}
|
|
|
|
const drawMemorialFlower = (flowerX, scale = 1, lean = 0, flowerY = 0) => {
|
|
const flowerBaseY = this.r * (0.50 + flowerY);
|
|
const flowerTopY = -this.r * (0.05 + 0.03 * scale - flowerY);
|
|
ctx.strokeStyle = night ? "rgb(72,114,73)" : "rgb(76,139,72)";
|
|
ctx.lineWidth = Math.max(1.1, this.r * 0.085 * scale);
|
|
ctx.beginPath(); ctx.moveTo(flowerX, flowerBaseY);
|
|
ctx.quadraticCurveTo(flowerX - this.r * (0.10 + lean), this.r * 0.20, flowerX + this.r * lean, flowerTopY); ctx.stroke();
|
|
ctx.fillStyle = night ? "rgb(128,149,104)" : "rgb(112,166,92)";
|
|
ctx.beginPath(); ctx.ellipse(flowerX - this.r * 0.10, this.r * 0.23, this.r * 0.18 * scale, this.r * 0.09 * scale, -0.55, 0, Math.PI * 2); ctx.fill();
|
|
const petal = night ? "rgb(205,190,203)" : "rgb(246,219,232)";
|
|
const petalR = Math.max(2.0, this.r * 0.14 * scale);
|
|
ctx.fillStyle = petal;
|
|
const cx = flowerX + this.r * lean;
|
|
for (let i = 0; i < 5; i++) {
|
|
const a = -Math.PI / 2 + i * Math.PI * 2 / 5;
|
|
ctx.beginPath(); ctx.arc(cx + Math.cos(a) * petalR * 0.78, flowerTopY + Math.sin(a) * petalR * 0.78, petalR * 0.66, 0, Math.PI * 2); ctx.fill();
|
|
}
|
|
ctx.fillStyle = night ? "rgb(196,153,70)" : "rgb(235,175,56)";
|
|
ctx.beginPath(); ctx.arc(cx, flowerTopY, petalR * 0.48, 0, Math.PI * 2); ctx.fill();
|
|
};
|
|
const drawWeed = (x, scale = 1, tilt = 0) => {
|
|
ctx.strokeStyle = night ? "rgb(74,94,65)" : "rgb(89,118,72)";
|
|
ctx.lineWidth = Math.max(1, this.r * 0.07 * scale);
|
|
for (let i = -1; i <= 1; i++) {
|
|
ctx.beginPath(); ctx.moveTo(x + i * this.r * 0.06, this.r * 0.56);
|
|
ctx.quadraticCurveTo(x + this.r * (tilt + i * 0.06), this.r * 0.18, x + this.r * (tilt + i * 0.12), -this.r * (0.08 + 0.06 * Math.abs(i))); ctx.stroke();
|
|
}
|
|
};
|
|
|
|
if (slaveMemorial) {
|
|
drawWeed(this.r * 0.56, 0.9, 0.04);
|
|
drawWeed(-this.r * 0.62, 0.72, -0.03);
|
|
} else if (kingMemorial) {
|
|
drawMemorialFlower(-this.r * 0.98, 0.90, -0.03, 0.03);
|
|
drawMemorialFlower(-this.r * 0.55, 1.05, 0.02, 0);
|
|
drawMemorialFlower(this.r * 0.62, 1.08, -0.02, 0);
|
|
drawMemorialFlower(this.r * 1.04, 0.90, 0.03, 0.04);
|
|
} else {
|
|
drawMemorialFlower(this.r * 0.78, 1, 0);
|
|
if (favoriteMemorial) {
|
|
drawMemorialFlower(-this.r * 0.72, 0.82, 0.035);
|
|
drawMemorialFlower(this.r * 1.02, 0.76, 0.035);
|
|
}
|
|
}
|
|
} else if (this.type === "plushie") {
|
|
const spriteId = "plushie_bear";
|
|
const img = getRenderableImage(spriteId, spriteId);
|
|
ctx.rotate((Number(this.spin || 0) || 0) + Math.sin(time * 2 + this.seed) * 0.05);
|
|
if (img) {
|
|
const metrics = getImageMetrics(spriteId) || getImageMetrics("smile");
|
|
const w = this.r * 3.35;
|
|
const h = w * (metrics?.ratio || 1.05);
|
|
ctx.drawImage(img, -w * 0.50, -h * 0.62, w, h);
|
|
} else {
|
|
ctx.fillStyle = "#f7dfc0";
|
|
ctx.strokeStyle = "rgba(86,62,48,0.70)";
|
|
ctx.lineWidth = 1.7;
|
|
ctx.beginPath();
|
|
ctx.arc(0, 0, this.r, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
ctx.fillStyle = "rgba(52,43,38,0.88)";
|
|
ctx.beginPath(); ctx.arc(-this.r * 0.28, -this.r * 0.08, 1.5, 0, Math.PI * 2); ctx.fill();
|
|
ctx.beginPath(); ctx.arc(this.r * 0.28, -this.r * 0.08, 1.5, 0, Math.PI * 2); ctx.fill();
|
|
}
|
|
}
|
|
ctx.restore();
|
|
}
|
|
}
|
|
|
|
const StructureRegistry = {
|
|
defs: {},
|
|
register(definition) { this.defs[definition.type] = definition; return definition; },
|
|
get(type) { return this.defs[type] || null; },
|
|
create(type, owner, x, y, world) {
|
|
const definition = this.get(type);
|
|
if (!definition) return null;
|
|
return definition.create ? definition.create(owner, x, y, world) : new TarinaiStructure(definition, owner, x, y, world);
|
|
},
|
|
};
|
|
|
|
function applyStructureNeedShock(owner, safetyGain, fulfillGain, breaker = null) {
|
|
if (!owner || owner.dead) return;
|
|
if (typeof global.applyNeedShock === "function") global.applyNeedShock(owner, { safety: safetyGain || 0, fulfill: fulfillGain || 0 }, breaker);
|
|
else {
|
|
owner.needShock = owner.needShock || {};
|
|
owner.needShock.safety = Math.max(owner.needShock.safety || 0, safetyGain || 0);
|
|
owner.needShock.fulfill = Math.max(owner.needShock.fulfill || 0, fulfillGain || 0);
|
|
owner.lastNeedShockBreaker = breaker || null;
|
|
}
|
|
if (typeof global.triggerNeedShockReaction === "function") {
|
|
const reason = breaker ? "\u81ea\u5206\u306e\u3082\u306e\u304c\u58ca\u308c\u3066\u30d1\u30cb\u30c3\u30af\u306b\u306a\u3063\u3066\u3044\u308b" : "\u81ea\u5206\u306e\u3082\u306e\u304c\u58ca\u308c\u3066\u3044\u308b";
|
|
global.triggerNeedShockReaction(owner, breaker, reason);
|
|
}
|
|
}
|
|
|
|
function damageNearbyStructures(worldRef, x, y, radius, amount, breaker = null) {
|
|
let hit = 0;
|
|
for (const structure of worldRef?.nearbyNonGrassItems?.(x, y, radius + 80) || worldRef?.items || []) {
|
|
if (!structure?.isStructure || structure.dead) continue;
|
|
const d = distXY(x, y, structure.x, structure.y);
|
|
const p = clamp(1 - d / Math.max(1, radius + (structure.r || 12)), 0, 1);
|
|
if (p <= 0) continue;
|
|
if (structure.damage?.(Math.max(1, amount * (0.35 + p * 0.65)), worldRef, breaker)) hit++;
|
|
}
|
|
return hit;
|
|
}
|
|
|
|
StructureRegistry.register({
|
|
type: "grave",
|
|
label: "墓",
|
|
buildNeed: "social",
|
|
buildTime: 9,
|
|
maxHp: 70,
|
|
materials: { splat: 1 },
|
|
roles: { memorial: true },
|
|
needEffects: { food: 0, sleep: 0, health: 0, safety: 0, social: 0, fulfill: 0 },
|
|
create(owner, x, y, world) {
|
|
const structure = new TarinaiStructure(this, null, x, y, world);
|
|
structure.ownerId = "";
|
|
structure.r = 18;
|
|
structure.memorialEligibleFamilyKeys = [];
|
|
return structure;
|
|
},
|
|
onDestroyed(structure, world, breaker) {
|
|
global.TarinaiMemorialBellSystem?.onGraveDestroyed?.(world, structure, breaker || null);
|
|
},
|
|
});
|
|
|
|
StructureRegistry.register({
|
|
type: "grass_bed",
|
|
label: "\u304b\u3093\u305f\u3093\u30d9\u30c3\u30c9",
|
|
buildNeed: "fulfill",
|
|
buildTime: 4,
|
|
maxHp: 100,
|
|
materials: { grassMaterial: 4 },
|
|
roles: { sleepPlace: true, ownedStructure: true },
|
|
needEffects: { food: 0, sleep: 60, health: 5, safety: 25, social: 0, fulfill: 40 },
|
|
create(owner, x, y, world) { return new TarinaiStructure(this, owner, x, y, world); },
|
|
onUse(user, structure, worldRef, options = {}) {
|
|
if (user?.id && !structure.ownerId) {
|
|
structure.ownerId = user.id;
|
|
structure.attachment = Math.max(structure.attachment || 0, 24);
|
|
structure.unownedDecayTimer = 0;
|
|
structure.dead = false;
|
|
structure.maxHp = Math.max(1, Number(structure.maxHp || 100) || 100);
|
|
structure.hp = Math.max(1, Number(structure.hp || 0) || structure.maxHp);
|
|
structure.amount = Math.max(0.001, structure.hp);
|
|
worldRef?.markItemBucketsDirty?.("grass-bed-owner-adopted");
|
|
worldRef?.markTerrainDirty?.("grass-bed-owner-adopted");
|
|
}
|
|
const ownerUse = user?.id && user.id === structure.ownerId;
|
|
if (ownerUse) structure.attachment = Math.min(100, (structure.attachment || 0) + 2);
|
|
structure.usedCount = (structure.usedCount || 0) + 1;
|
|
if (options?.purpose === "sleep" || options?.sleep === true) {
|
|
// \u304b\u3093\u305f\u3093\u30d9\u30c3\u30c9\u306f\u300c\u5bdd\u305f\u77ac\u9593\u300d\u3067\u306f\u306a\u304f\u300c\u8d77\u304d\u305f\u6642\u300d\u306b\u6d88\u3048\u308b\u3002
|
|
// \u7761\u7720\u4e2d\u306f\u5bdd\u5834\u6240\u3068\u3057\u3066\u6b8b\u3059\u3053\u3068\u3067\u3001\u898b\u305f\u76ee\u3068\u30db\u30d0\u30fc\u60c5\u5831\u304c\u81ea\u7136\u306b\u306a\u308b\u3002
|
|
structure.dead = false;
|
|
structure.maxHp = Math.max(1, Number(structure.maxHp || 100) || 100);
|
|
structure.hp = Math.max(1, Number(structure.hp || 0) || structure.maxHp);
|
|
structure.amount = Math.max(0.001, structure.hp);
|
|
structure.unownedDecayTimer = 0;
|
|
structure.singleUsePending = true;
|
|
structure.singleUseConsumedById = user?.id || "";
|
|
structure.amount = Math.max(0.001, structure.hp || structure.amount || 1);
|
|
worldRef?.markItemBucketsDirty?.("grass-bed-sleep-reserved");
|
|
worldRef?.markTerrainDirty?.("grass-bed-sleep-reserved");
|
|
}
|
|
return true;
|
|
},
|
|
onDestroyed(structure, world, breaker) {
|
|
applyStructureNeedShock(structure.owner(world), 42, 46, breaker);
|
|
},
|
|
});
|
|
|
|
StructureRegistry.register({
|
|
type: "plushie",
|
|
label: "\u306c\u3044\u3050\u308b\u307f",
|
|
buildNeed: "fulfill",
|
|
buildTime: 4,
|
|
maxHp: 1,
|
|
materials: { grassMaterial: 4 },
|
|
roles: { carriedObject: true, ownedStructure: true, fulfillObject: true },
|
|
needEffects: { food: 0, sleep: 0, health: 0, safety: 5, social: 0, fulfill: 50 },
|
|
create(owner, x, y, world) { return new TarinaiStructure(this, owner, x, y, world); },
|
|
onUse(user, structure, worldRef) {
|
|
if (user?.id === structure.ownerId) structure.attachment = Math.min(100, (structure.attachment || 0) + 1);
|
|
structure.usedCount = (structure.usedCount || 0) + 1;
|
|
if (user?.id === structure.ownerId) {
|
|
structure.carriedById = user.id;
|
|
structure.onHead = true;
|
|
structure.x = user.x;
|
|
structure.y = user.y - Math.max(28, (user.radius || 24) * 1.28);
|
|
user.needShock = user.needShock || {};
|
|
user.needShock.fulfill = Math.min(0, (user.needShock.fulfill || 0) - 24);
|
|
user.needShock.safety = Math.min(0, (user.needShock.safety || 0) - 4);
|
|
worldRef?.markSpatialDirty?.("plushie-use-head");
|
|
worldRef && (worldRef.drawListDirty = true);
|
|
}
|
|
return true;
|
|
},
|
|
onDestroyed(structure, world, breaker) {
|
|
applyStructureNeedShock(structure.owner(world), 55, 64, breaker);
|
|
},
|
|
});
|
|
|
|
global.StructureRegistry = StructureRegistry;
|
|
global.damageNearbyStructures = damageNearbyStructures;
|
|
})(typeof window !== "undefined" ? window : globalThis);
|