"use strict"; (function (global) { const World = global.World; if (!World) throw new Error("World is not available for mixin: world_tool_actions.js"); Object.defineProperties(World.prototype, Object.getOwnPropertyDescriptors({ _forceImmediateToolVisualRefresh(reason = "tool-action") { this.drawListDirty = true; if (this._renderStack) this._renderStack.signature = ""; if (this._visibleRenderStack) { this._visibleRenderStack.backItems = []; this._visibleRenderStack.layered = []; this._visibleRenderStack.carriedPlushies = []; this._visibleRenderStack.lodgedPins = []; } this.markItemBucketsDirty?.(reason); this.markSpatialDirty?.(reason); }, findDeleteToolTargetAt(x, y) { let best = null; let bestD = Infinity; const itemCandidates = this.nearbyItems?.(x, y, 190, true) || this.items || []; for (let i = itemCandidates.length - 1; i >= 0; i -= 1) { const it = itemCandidates[i]; if (!it || it.dead) continue; let d = distXY(x, y, it.x, it.y); if (["rope", "rod", "spring", "wire", "insulated_wire"].includes(it.type)) { const runtime = global.TarinaiLinkRuntime; const pb = global.TarinaiPhysicsBodySystem; const a = runtime?.endpointWorld?.(pb?.endpoint?.(it, 0), this); const b = runtime?.endpointWorld?.(pb?.endpoint?.(it, 1), this); d = global.TarinaiGeometry.pointSegmentDistance(x, y, a?.x ?? it.x, a?.y ?? it.y, b?.x ?? it.x, b?.y ?? it.y); if (d < 18 && d < bestD) { best = it; bestD = d; } continue; } const result = global.TarinaiCollisionFootprints.hitTestItem(this, it, x, y, { padding: this.screenSizeToWorld ? this.screenSizeToWorld(12) : 12, radiusMultiplier: 2.0 }) || { hit: false, distance: d }; if (result.hit && result.distance < bestD) { best = it; bestD = result.distance; } } return best; }, deleteItemAt(x, y) { global.TarinaiAchievements?.recordIntervention?.({ world: this, tool: "delete", x, y }); const target = this.findDeleteToolTargetAt?.(x, y); if (!target) { showToast("\u6d88\u305b\u308b\u3082\u306e\u304c\u3042\u308a\u307e\u305b\u3093\u3002"); return true; } if (isPinType(target.type) && target.pinState === "lodged") { showToast("\u523a\u3055\u3063\u305f\u92f2\u306f\u3001\u3064\u307e\u3093\u3067\u629c\u3044\u3066\u304f\u3060\u3055\u3044\u3002"); return true; } const label = toolLabel(target.type); const removed = global.TarinaiStructureLifecycle.deleteItem(this, target, { reason: "delete-tool", userReason: target.type === "grass_bed" ? "\u30d9\u30c3\u30c9\u304c\u306a\u304f\u306a\u3063\u305f" : "\u7f6e\u304d\u3082\u306e\u304c\u306a\u304f\u306a\u3063\u305f", wake: true, }); if (!removed) return true; global.TarinaiAchievements?.recordPlayerDeletion?.({ world: this, item: target, tool: "delete" }); audio.delete?.(); this.compactItems?.(); this.updateItemCounts?.(); this.rebuildSpatial?.(); this.log?.(`${label}\u3092\u6d88\u3057\u305f\u3002`, "observe"); return true; }, areaDeleteRadius() { return 138; }, deleteItemsInArea(x, y, options = {}) { global.TarinaiAchievements?.recordIntervention?.({ world: this, tool: "area_delete", x, y }); const radius = this.areaDeleteRadius?.() || 138; const candidates = []; const itemCandidates = this.nearbyItems?.(x, y, 190, true) || this.items || []; for (let i = itemCandidates.length - 1; i >= 0; i -= 1) { const it = itemCandidates[i]; if (!it || it.dead) continue; if (isPinType(it.type) && it.pinState === "lodged") continue; const hitR = Math.max(8, it.r || itemRadiusFor?.(it.type, 12) || 12); let d = distXY(x, y, it.x, it.y); if (["rope", "rod", "spring", "wire", "insulated_wire"].includes(it.type)) { const runtime = global.TarinaiLinkRuntime; const pb = global.TarinaiPhysicsBodySystem; const a = runtime?.endpointWorld?.(pb?.endpoint?.(it, 0), this); const b = runtime?.endpointWorld?.(pb?.endpoint?.(it, 1), this); d = global.TarinaiGeometry.pointSegmentDistance(x, y, a?.x ?? it.x, a?.y ?? it.y, b?.x ?? it.x, b?.y ?? it.y); if (d <= radius + 8) candidates.push(it); continue; } const result = global.TarinaiCollisionFootprints.hitTestItem(this, it, x, y, { padding: radius, radiusMultiplier: 0.85 }) || { hit: d <= radius + hitR * 0.85 }; if (result.hit || d <= radius + hitR * 0.85) candidates.push(it); } if (!candidates.length) { if (!options.silentEmpty) showToast("\u7bc4\u56f2\u5185\u306b\u6d88\u305b\u308b\u3082\u306e\u304c\u3042\u308a\u307e\u305b\u3093\u3002"); return true; } let removedCount = 0; const kindCounts = new Map(); for (const target of candidates) { if (!target || target.dead) continue; const removed = global.TarinaiStructureLifecycle.deleteItem(this, target, { reason: "area-delete-tool", userReason: target.type === "grass_bed" ? "\u30d9\u30c3\u30c9\u304c\u306a\u304f\u306a\u3063\u305f" : "\u7f6e\u304d\u3082\u306e\u304c\u306a\u304f\u306a\u3063\u305f", wake: true, }); if (!removed) continue; global.TarinaiAchievements?.recordPlayerDeletion?.({ world: this, item: target, tool: "area_delete" }); removedCount += 1; const label = toolLabel(target.type); kindCounts.set(label, (kindCounts.get(label) || 0) + 1); } if (!removedCount) { if (!options.silentEmpty) showToast("\u7bc4\u56f2\u5185\u306b\u6d88\u305b\u308b\u3082\u306e\u304c\u3042\u308a\u307e\u305b\u3093\u3002"); return true; } audio.delete?.(); this.compactItems?.(); this.updateItemCounts?.(); this.rebuildSpatial?.(); this.drawListDirty = true; if (typeof global.render === "function") global.render(); if (options.drag) this._areaDeleteDragRemoved = (this._areaDeleteDragRemoved || 0) + removedCount; if (!options.silentLog) { const detail = [...kindCounts.entries()].slice(0, 4).map(([label, count]) => `${label}${count > 1 ? `\u00d7${count}` : ""}`).join("\u3001"); this.log?.(`\u7bc4\u56f2\u524a\u9664\u3067${removedCount}\u500b\u6d88\u3057\u305f${detail ? `\uff08${detail}\uff09` : ""}\u3002`, "observe"); showToast(`${removedCount}\u500b\u6d88\u3057\u307e\u3057\u305f\u3002`); } return true; }, linkPointLocalForItem(item, x, y, useClickPoint = false) { const angle = itemAngleFor(item); const dx = (Number(x || 0) || 0) - (Number(item?.x || 0) || 0); const dy = (Number(y || 0) || 0) - (Number(item?.y || 0) || 0); if (!useClickPoint) return { localX: 0, localY: 0 }; const c = Math.cos(-angle), s = Math.sin(-angle); return { localX: dx * c - dy * s, localY: dx * s + dy * c }; }, findLinkEndpointAt(x, y) { let best = null; let bestD = Infinity; const wireTool = this.tool === "wire" || this.tool === "insulated_wire"; for (let i = (this.tarinai || []).length - 1; i >= 0; i -= 1) { const t = this.tarinai[i]; if (!t || t.dead || this.isTarinaiHiddenInNestBox?.(t)) continue; const hit = Math.max(18, (t.radius || 18) * 1.12); const d = distXY(x, y, t.x, t.y); if (d <= hit && d < bestD) { best = { kind: "tarinai", id: t.id || "", liveToken: t.liveToken ?? null, _ref: t, type: t.type || "tarinai", label: t.name || "\u305f\u308a\u306a\u3044", x: t.x, y: t.y, localX: 0, localY: 0, center: true }; bestD = d; } } const itemCandidates = this.nearbyItems?.(x, y, 190, true) || this.items || []; for (let i = itemCandidates.length - 1; i >= 0; i -= 1) { const it = itemCandidates[i]; if (!it || it.dead || it.type === "splat") continue; if ((this.tool === "rope" || this.tool === "rod" || this.tool === "spring") && global.TarinaiPhysicsSoftClear.isSoftPlacementType(it.type)) continue; if (wireTool && ["rope", "rod", "spring", "wire", "insulated_wire"].includes(it.type)) continue; if (wireTool && it.type === "circuit_board") { const portHit = global.TarinaiCircuitBoardSystem?.nearestPort?.(it, x, y, 30); if (!portHit || portHit.distance >= bestD) continue; best = { kind: "item", id: it.id, type: it.type, label: `${toolLabel(it.type)} ${portHit.port.label}`, x: portHit.x, y: portHit.y, localX: portHit.localX, localY: portHit.localY, center: false, portId: portHit.port.portId }; bestD = portHit.distance; continue; } const isMechanism = Boolean(global.TarinaiMechanicalSystem.isMechanicalType(it.type)); const isFence = this.isFenceType?.(it.type) || false; const isConstraint = ["rope", "rod", "spring", "wire", "insulated_wire"].includes(it.type); let hit = false; let d = distXY(x, y, it.x, it.y); if (isConstraint) { const runtime = global.TarinaiLinkRuntime; const pb = global.TarinaiPhysicsBodySystem; const a = runtime?.endpointWorld?.(pb?.endpoint?.(it, 0), this); const b = runtime?.endpointWorld?.(pb?.endpoint?.(it, 1), this); if (!a || !b) continue; d = global.TarinaiGeometry.pointSegmentDistance(x, y, a.x, a.y, b.x, b.y); if (d > 20 || d >= bestD) continue; const vx = b.x - a.x, vy = b.y - a.y; const len2 = vx * vx + vy * vy || 1; const attachT = Math.max(0, Math.min(1, ((x - a.x) * vx + (y - a.y) * vy) / len2)); const px = a.x + vx * attachT; const py = a.y + vy * attachT; best = { kind: "item", id: it.id, type: it.type, label: toolLabel(it.type), x: px, y: py, localX: 0, localY: 0, center: false, attachT }; bestD = d; continue; } if (isMechanism || isFence) { const result = global.TarinaiCollisionFootprints.hitTestItem(this, it, x, y, { padding: 18, radiusMultiplier: 1.28 }) || { hit: false, distance: d }; hit = Boolean(result.hit); d = Math.min(d, result.distance ?? d); } else { const result = global.TarinaiCollisionFootprints.hitTestItem(this, it, x, y, { radiusMultiplier: 1.35 }) || { hit: false, distance: d }; hit = Boolean(result.hit); d = Math.min(d, result.distance ?? d); } if (!hit || d >= bestD) continue; const local = this.linkPointLocalForItem?.(it, x, y, isMechanism || isFence) || { localX: 0, localY: 0 }; best = { kind: "item", id: it.id, type: it.type, label: toolLabel(it.type), x: isMechanism || isFence ? x : it.x, y: isMechanism || isFence ? y : it.y, localX: local.localX, localY: local.localY, center: !(isMechanism || isFence) }; if (isMechanism) global.TarinaiLinkRuntime.snapEndpointToTarget(best, this); bestD = d; } if ((this.tool === "rope" || this.tool === "rod" || this.tool === "spring") && bestD > 14) { for (let i = (this.items || []).length - 1; i >= 0; i -= 1) { const it = this.items[i]; if (!it || it.dead || !global.TarinaiMechanicalSystem.isMechanicalType(it.type)) continue; const reach = global.TarinaiMechanicalSystem.reach?.(it) || this.rotatorExtent?.(it) || Math.max(64, it.r || 64); if (distXY(x, y, it.x, it.y) > reach + 32) continue; const result = global.TarinaiCollisionFootprints.hitTestItem(this, it, x, y, { padding: 26, radiusMultiplier: 1.45 }) || { hit: false, distance: Infinity }; if (!result.hit || result.distance >= bestD) continue; const local = this.linkPointLocalForItem?.(it, x, y, true) || { localX: 0, localY: 0 }; best = { kind: "item", id: it.id, type: it.type, label: toolLabel(it.type), x, y, localX: local.localX, localY: local.localY, center: false }; global.TarinaiLinkRuntime.snapEndpointToTarget(best, this); bestD = result.distance; } } return best; }, useLinkToolAt(x, y) { global.TarinaiAchievements?.recordIntervention?.({ world: this, tool: this.tool, x, y }); const endpoint = this.findLinkEndpointAt?.(x, y); const linkNames = { rope: "\u30d2\u30e2", rod: "\u68d2", spring: "\u30d0\u30cd", wire: "\u96fb\u7dda", insulated_wire: "\u7d76\u7e01\u96fb\u7dda" }; const toolName = linkNames[this.tool] || "\u63a5\u7d9a"; if (!endpoint) { showToast(`${toolName}\u3092\u3064\u306a\u3050\u5bfe\u8c61\u304c\u3042\u308a\u307e\u305b\u3093\u3002`); return true; } if (!this.pendingLinkEndpoint) { this.pendingLinkEndpoint = endpoint; showToast(`${endpoint.label || "\u5bfe\u8c61"}\u306b${toolName}\u306e\u7247\u7aef\u3092\u3064\u306a\u304e\u307e\u3057\u305f\u3002\u3082\u3046\u7247\u7aef\u3092\u9078\u3093\u3067\u304f\u3060\u3055\u3044\u3002`); this.drawListDirty = true; if (typeof global.render === "function") global.render(); return true; } const first = this.pendingLinkEndpoint; global.TarinaiLinkRuntime.snapEndpointToTarget(first, this); global.TarinaiLinkRuntime.snapEndpointToTarget(endpoint, this); if (first.kind === endpoint.kind && first.id === endpoint.id && distXY(first.x, first.y, endpoint.x, endpoint.y) < 8) { showToast("\u5225\u306e\u4f4d\u7f6e\u307e\u305f\u306f\u5bfe\u8c61\u3092\u9078\u3093\u3067\u304f\u3060\u3055\u3044\u3002"); return true; } const runtime = global.TarinaiLinkRuntime; const p1 = runtime?.endpointWorld?.(first, this) || { x: first.x, y: first.y }; const p2 = runtime?.endpointWorld?.(endpoint, this) || { x: endpoint.x, y: endpoint.y }; const constraintType = ["rod", "spring", "wire", "insulated_wire"].includes(this.tool) ? this.tool : "rope"; const item = new Item(constraintType, (p1.x + p2.x) * 0.5, (p1.y + p2.y) * 0.5); item.world = this; const pb = global.TarinaiPhysicsBodySystem; pb?.ensureConstraint?.(item, this, { syncFromLegacy: false }); pb?.setEndpoint?.(item, 0, { ...first, x: p1.x, y: p1.y }); pb?.setEndpoint?.(item, 1, { ...endpoint, x: p2.x, y: p2.y }); const linkLen = Math.max(24, distXY(p1.x, p1.y, p2.x, p2.y)); pb?.setLinkScalar?.(item, "len", linkLen); pb?.setLinkScalar?.(item, "midX", (p1.x + p2.x) * 0.5); pb?.setLinkScalar?.(item, "midY", (p1.y + p2.y) * 0.5); pb?.setLinkScalar?.(item, "midVx", 0); pb?.setLinkScalar?.(item, "midVy", 0); item.r = Math.max(18, linkLen * 0.5); item.amount = 999; const placed = this.addItem?.(item, constraintType); this.pendingLinkEndpoint = null; if (!placed) { showToast(`${toolName}\u3092\u8ffd\u52a0\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002`); return true; } global.TarinaiAchievements?.recordLinkPlaced?.(constraintType, { world: this, item, type: constraintType }); global.TarinaiPhysicsSoftClear.clearForItem(this, item, `${constraintType}-soft-overlap-clear`); this._forceImmediateToolVisualRefresh?.("tool-action"); this.log?.(`${toolName}\u3092\u3064\u306a\u3044\u3060\u3002`, "observe"); showToast(`${toolName}\u3092\u3064\u306a\u304e\u307e\u3057\u305f\u3002`); return true; }, shootAimPoint() { const p = this.pointer || {}; const pad = Math.max(0, CONFIG.worldPadding || 0); const x = clamp((Number(p.x) || 0) + (Number(this.shootCursorOffsetX) || 0), pad, Math.max(pad, (this.w || 0) - pad)); const y = clamp((Number(p.y) || 0) + (Number(this.shootCursorOffsetY) || 0), pad, Math.max(pad, (this.h || 0) - pad)); return { x, y, inside: Boolean(p.inside) }; }, setShootCursorFromPointer(p = this.pointer || {}) { this.shootPointerRawX = Number(p.x) || 0; this.shootPointerRawY = Number(p.y) || 0; this.shootCursorActive = true; return this.shootAimPoint?.() || { x: this.shootPointerRawX, y: this.shootPointerRawY }; }, moveShootCursorBy() { // Mouse/touch movement should move the base pointer, not accumulate extra // logical drift. Only recoil changes shootCursorOffsetX/Y. this.shootCursorActive = true; this.drawListDirty = true; return this.shootAimPoint?.() || { x: this.pointer?.x || 0, y: this.pointer?.y || 0 }; }, applyShootToolRecoil(power = 1) { const unit = this.screenSizeToWorld ? this.screenSizeToWorld(rand(18, 34)) : rand(18, 34); const mag = Math.max(8, unit) * Math.max(0.65, Number(power) || 1); const angle = -Math.PI / 2 + rand(-0.52, 0.52); const dx = Math.cos(angle) * mag; const dy = Math.sin(angle) * mag; this.shootCursorOffsetX = clamp((Number(this.shootCursorOffsetX) || 0) + dx, -130, 130); this.shootCursorOffsetY = clamp((Number(this.shootCursorOffsetY) || 0) + dy, -150, 70); this.shootCursorActive = true; this.drawListDirty = true; return this.shootAimPoint?.() || { x: this.pointer?.x || 0, y: this.pointer?.y || 0 }; }, findShootToolTargetAt(x, y) { let best = null; let bestD = Infinity; const tarinaiCandidates = this.nearbyTarinai?.(x, y, 82, true) || this.tarinai || []; for (let i = tarinaiCandidates.length - 1; i >= 0; i -= 1) { const t = tarinaiCandidates[i]; if (!t || t.dead || this.isTarinaiHiddenInNestBox?.(t)) continue; const hit = t.contains ? t.contains(x, y) : distXY(x, y, t.x, t.y) <= (t.radius || 22) * 1.28; const d = distXY(x, y, t.x, t.y); if (hit && d < bestD) { best = { kind: "tarinai", target: t, distance: d }; bestD = d; } } const antCandidates = this.nearbyAnts?.(x, y, 32, true) || this.ants || []; for (let i = antCandidates.length - 1; i >= 0; i -= 1) { const ant = antCandidates[i]; if (!ant || ant.dead) continue; const hitR = Math.max(6, (ant.r || 6) * 1.35); const d = distXY(x, y, ant.x, ant.y); if (d <= hitR && d < bestD) { best = { kind: "ant", target: ant, distance: d }; bestD = d; } } const itemCandidates = this.nearbyItems?.(x, y, 190, true) || this.items || []; for (let i = itemCandidates.length - 1; i >= 0; i -= 1) { const it = itemCandidates[i]; if (!it || it.dead || it.type === "trace" || it.type === "splat") continue; let d = distXY(x, y, it.x, it.y); let hit = false; if (["rope", "rod", "spring", "wire", "insulated_wire"].includes(it.type)) { const runtime = global.TarinaiLinkRuntime; const pb = global.TarinaiPhysicsBodySystem; const a = runtime?.endpointWorld?.(pb?.endpoint?.(it, 0), this); const b = runtime?.endpointWorld?.(pb?.endpoint?.(it, 1), this); d = global.TarinaiGeometry.pointSegmentDistance(x, y, a?.x ?? it.x, a?.y ?? it.y, b?.x ?? it.x, b?.y ?? it.y); hit = d < 18; } else if (global.TarinaiMechanicalSystem.isMechanicalType(it.type)) { const result = global.TarinaiMechanicalSystem.hitTest(this, it, x, y, { padding: 14 }); hit = Boolean(result.hit); d = Math.min(d, result.distance ?? d); } else { const result = global.TarinaiCollisionFootprints.hitTestItem(this, it, x, y, { radiusMultiplier: (it.type === "ball" || it.type === "balloon") ? 3.2 : 1.65, padding: 10 }) || { hit: false, distance: d }; hit = Boolean(result.hit); d = Math.min(d, result.distance ?? d); } if (hit && d < bestD) { best = { kind: "item", target: it, distance: d }; bestD = d; } } return best; }, shootFleeTargetFor(t, x, y, minDist = 150, maxDist = 250) { if (!t) return null; const dx = (Number(t.x) || 0) - (Number(x) || 0); const dy = (Number(t.y) || 0) - (Number(y) || 0); const d = Math.hypot(dx, dy); const a = d > 3 ? Math.atan2(dy, dx) : rand(-Math.PI, Math.PI); const fleeDist = rand(minDist, maxDist); const pad = Math.max(CONFIG.worldPadding || 0, (t.radius || 20) + 8); return { x: clamp((Number(t.x) || 0) + Math.cos(a) * fleeDist, pad, Math.max(pad, (this.w || 0) - pad)), y: clamp((Number(t.y) || 0) + Math.sin(a) * fleeDist, pad, Math.max(pad, (this.h || 0) - pad)), dead: false, detour: true, panic: true, }; }, panicTarinaiFromShootImpact(t, x, y, options = {}) { if (!t || t.dead || this.isTarinaiHiddenInNestBox?.(t)) return false; const direct = Boolean(options.direct); const strength = Math.max(0.25, Number(options.strength) || 1); const target = this.shootFleeTargetFor?.(t, x, y, direct ? 170 : 115, direct ? 260 : 205) || null; const reason = direct ? "\u6483\u305f\u308c\u3066\u30d1\u30cb\u30c3\u30af\u3067\u9003\u3052\u3066\u3044\u308b" : "\u8fd1\u304f\u306b\u5f3e\u304c\u5f53\u305f\u3063\u3066\u30d1\u30cb\u30c3\u30af\u3067\u9003\u3052\u3066\u3044\u308b"; const threat = { x: Number(x) || t.x, y: Number(y) || t.y, type: "shoot", dead: false }; t.fearTimer = Math.max(t.fearTimer || 0, (direct ? 2.8 : 1.25) * strength); t.surpriseTimer = Math.max(t.surpriseTimer || 0, (direct ? 0.34 : 0.28) * strength); if (direct) t.hurtTimer = Math.max(t.hurtTimer || 0, 1.35); if (t.enterPanic) t.enterPanic({ threat, destination: target, target, forceDestination: true, reason, fear: direct ? 1.25 : 0.85 * strength, wake: true, cause: direct ? "shoot_tool" : "shoot_impact_nearby", hurtTimer: direct ? 1.35 : undefined, surpriseTimer: direct ? 0.34 : 0.25, awakeLockTimer: direct ? 1.0 : 0.45 }); t.setActionState?.("panic", { target, reason, wake: true, sleeping: false }); t.target = target; t.panicTarget = target; t.panicTargetUntil = (this.time || 0) + (direct ? 2.4 : 1.55 * strength); const dx = (Number(t.x) || 0) - (Number(x) || 0); const dy = (Number(t.y) || 0) - (Number(y) || 0); const d = Math.hypot(dx, dy); const a = d > 3 ? Math.atan2(dy, dx) : rand(-Math.PI, Math.PI); t.vx = clamp((t.vx || 0) + Math.cos(a) * rand(direct ? 28 : 12, direct ? 56 : 30) * strength, -220, 220); t.vy = clamp((t.vy || 0) + Math.sin(a) * rand(direct ? 24 : 10, direct ? 48 : 26) * strength, -220, 220); return true; }, panicTarinaiAroundShootImpact(x, y, directTarget = null) { const radius = 138; let count = 0; const candidates = this.nearbyTarinai?.(x, y, radius + 32, true) || this.tarinai || []; for (const t of candidates) { if (!t || t.dead || t === directTarget || this.isTarinaiHiddenInNestBox?.(t)) continue; const d = distXY(x, y, t.x, t.y); if (d > radius) continue; const strength = clamp(1 - d / radius, 0.22, 1.0); if (this.panicTarinaiFromShootImpact?.(t, x, y, { direct: false, strength })) count += 1; } return count; }, fireShootTool(x = null, y = null) { const aim = (x == null || y == null) ? this.shootAimPoint?.() : { x: Number(x) || 0, y: Number(y) || 0, inside: true }; if (!aim?.inside) return false; global.TarinaiAchievements?.recordShotFired?.({ world: this, x: aim.x, y: aim.y }); global.TarinaiAchievements?.recordIntervention?.({ world: this, tool: "shoot", x: aim.x, y: aim.y }); const hit = this.findShootToolTargetAt?.(aim.x, aim.y); audio.shoot?.(); this.effects?.push(new Effect("shoot_impact", aim.x, aim.y, { size: hit ? 22 : 16, life: hit ? 0.28 : 0.22, color: hit ? "rgba(255,208,116,0.96)" : "rgba(190,212,240,0.64)" })); this.effects?.push(new Effect("ring", aim.x, aim.y, { size: hit ? 20 : 14, life: hit ? 0.18 : 0.14, color: hit ? "rgba(255,246,206,0.74)" : "rgba(196,220,255,0.42)" })); let handled = false; if (hit?.kind === "tarinai") { const t = hit.target; const damage = 16; t.damage?.(damage, "\u5c04\u6483", { source: { id: "shoot", type: "shoot" } }); t.hurtTimer = Math.max(t.hurtTimer || 0, 1.35); t.pokeFlashTimer = Math.max(t.pokeFlashTimer || 0, 0.22); t.fearTimer = Math.max(t.fearTimer || 0, 2.8); this.panicTarinaiFromShootImpact?.(t, aim.x, aim.y, { direct: true, strength: 1 }); handled = true; } else if (hit?.kind === "ant") { const ant = hit.target; const damage = ant.kind === "queen" ? 24 : 34; global.TarinaiAchievements?.recordAntDamageSource?.(ant, { id: "shoot", type: "shoot" }, { world: this }); ant.hp = Math.max(0, (Number(ant.hp) || Number(ant.maxHp) || 0) - damage); ant.hpBarTimer = Math.max(ant.hpBarTimer || 0, 1.3); if (ant.hp <= 0) ant.dieAsCorpse?.(ant.homeNest?.() || null); handled = true; } else if (hit?.kind === "item") { handled = true; const target = hit.target; if (target?.type === "balloon") { globalThis.TarinaiItemDynamicBallSystem?.popBalloon?.(target, this, { type: "shoot", x: aim.x, y: aim.y, id: "shoot" }); this.markItemBucketsDirty?.("shoot-water-balloon-pop"); this.markSpatialDirty?.("shoot-water-balloon-pop"); this.drawListDirty = true; } else if (target && isPinType(target.type)) { const blasted = this.blastLoosePinsFrom?.(aim.x, aim.y, 92, target, 1.25, "shoot-tool") || 0; if (!blasted && target.pinState !== "lodged") { let dx = (target.x || 0) - aim.x; let dy = (target.y || 0) - aim.y; let d = Math.hypot(dx, dy) || 1; if (d < 0.001) { const a = deterministicAngle(this, "shoot-pin-overlap", target, aim.x, aim.y); dx = Math.cos(a); dy = Math.sin(a); d = 1; } const pinSystem = globalThis.TarinaiItemDynamicPinSystem; if (target.pinState === "ball_lodged") pinSystem?.detach?.(target, this, "shoot"); this.applyImpulse?.(target, dx / d * 610 + deterministicRange(this, "shoot-pin-jitter-x", -70, 70, target, aim.x, aim.y), dy / d * 610 + deterministicRange(this, "shoot-pin-jitter-y", -70, 70, target, aim.x, aim.y) ); target.spinVelocity = clamp((target.spinVelocity || 0) + deterministicRange(this, "shoot-pin-spin", -20, 20, target, aim.x, aim.y), -40, 40); target.noStickUntil = (this.time || 0) + 0.22; target.lastPokedAt = this.time || 0; } this.effects?.push(new Effect("ring", target.x || aim.x, target.y || aim.y, { size: Math.max(12, (target.r || 8) * 1.8), life: 0.18, color: "rgba(255,230,116,0.48)" })); this.markSpatialDirty?.("shoot-pin-blast"); this.drawListDirty = true; } else { const shouldRemove = Math.random() < 0.30; if (shouldRemove) { const removed = global.TarinaiStructureLifecycle.deleteItem(this, target, { reason: "shoot-tool", userReason: "\u5c04\u6483\u3067\u6d88\u3048\u305f", wake: true, }); if (removed) { this.markItemBucketsDirty?.("shoot-delete"); this.markSpatialDirty?.("shoot-delete"); this.drawListDirty = true; } else { } } else { } } } else { } this.panicTarinaiAroundShootImpact?.(aim.x, aim.y, hit?.kind === "tarinai" ? hit.target : null); this.lastShootAt = this.time || 0; this.applyShootToolRecoil?.(handled ? 1 : 0.88); this.drawListDirty = true; return true; }, useToolAt(x, y) { if (this.tool === "delete") return this.deleteItemAt(x, y); if (this.tool === "area_delete") return this.deleteItemsInArea(x, y); if (["rope", "rod", "spring", "wire", "insulated_wire"].includes(this.tool)) return this.useLinkToolAt(x, y); return false; }, })); })(window);