"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.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.effects.push(new Effect("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 === "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.effects.push(new Effect("ring", this.x, this.y, { size: 30, life: 0.22, color: "rgba(190,142,92,0.38)" })); world.effects.push(new Effect("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; if (options.log !== false) world?.log?.("\u306C\u3044\u3050\u308B\u307F\u304C\u56DE\u8EE2\u3057\u306A\u304C\u3089\u98DB\u3093\u3067\u3044\u3063\u305F\u3002", "observe", { eventType: "plushie_fling", participants: owner ? [owner] : [] }); 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 === "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?.nearbyItems?.(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: "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);