"use strict"; (function (global) { function cursorForTool(tool) { if (tool === "water_hose") return "crosshair"; if (tool === "pinch") return "grab"; if (tool === "observe") return "default"; if (tool === "poke" || tool === "delete" || tool === "area_delete") return "pointer"; return "crosshair"; } function createInputContext({ canvas, world, ui, uiCache }) { if (!canvas || !world || !uiCache) return null; const inputModeManager = global.TarinaiInputMode; function setMobileInputMode(mode = "auto") { mode = inputModeManager?.setMode?.(mode) || (["auto", "camera", "tool", "family"].includes(mode) ? mode : "auto"); uiCache.mobileInputMode = mode; ui.mobileModeControls?.querySelectorAll("[data-mobile-mode]").forEach(btn => btn.classList.toggle("active", btn.dataset.mobileMode === mode)); document.body.classList.toggle("mobile-mode-camera", mode === "camera"); document.body.classList.toggle("mobile-mode-tool", mode === "tool"); document.body.classList.toggle("mobile-mode-family", mode === "family"); return mode; } function updateTouchMobileUiClass() { const enabled = Boolean(inputModeManager?.refresh?.().touchFirst); document.body.classList.toggle("touch-mobile-ui", enabled); if (ui.mobileModeControls) ui.mobileModeControls.hidden = !enabled; return enabled; } function mobileInputMode() { return inputModeManager?.currentMode || uiCache.mobileInputMode || "auto"; } function bindMobileModeControls() { updateTouchMobileUiClass(); window.addEventListener("resize", updateTouchMobileUiClass, { passive: true }); window.matchMedia?.("(pointer: coarse)")?.addEventListener?.("change", updateTouchMobileUiClass); setMobileInputMode(uiCache.mobileInputMode || "auto"); ui.mobileModeControls?.addEventListener("click", (e) => { const btn = e.target.closest("[data-mobile-mode]"); if (!btn) return; audio.uiClick?.(); setMobileInputMode(btn.dataset.mobileMode || "auto"); showToast(`\u30b9\u30de\u30db\u64cd\u4f5c: ${btn.textContent || btn.dataset.mobileMode}`); }); } function touchPoint(touches, index = 0) { const t = touches?.[index]; if (!t) return null; return { clientX: t.clientX, clientY: t.clientY }; } function touchCenter(touches) { const a = touchPoint(touches, 0); const b = touchPoint(touches, 1); if (!a) return null; if (!b) return a; return { clientX: (a.clientX + b.clientX) / 2, clientY: (a.clientY + b.clientY) / 2 }; } function touchDistance(touches) { const a = touchPoint(touches, 0); const b = touchPoint(touches, 1); if (!a || !b) return 0; return Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY); } function screenToWorldClient(clientX, clientY) { const rect = canvas.getBoundingClientRect(); const sx = clientX - rect.left; const sy = clientY - rect.top; return world.screenToWorld ? world.screenToWorld(sx, sy) : { x: sx, y: sy, inside: sx >= 0 && sy >= 0 }; } function screenToWorldEvent(e) { if (typeof screenToWorld === "function") return screenToWorld(e); return screenToWorldClient(e.clientX, e.clientY); } function clientToWorldRaw(clientX, clientY) { const rect = canvas.getBoundingClientRect(); const sx = clientX - rect.left; const sy = clientY - rect.top; const scale = world.viewScale ? world.viewScale() : 1; const off = world.fieldScreenOffset ? world.fieldScreenOffset() : { x: 0, y: 0 }; const rawX = (sx - off.x) / scale + (world.cameraX || 0); const rawY = (sy - off.y) / scale + (world.cameraY || 0); return { x: rawX, y: rawY, inside: rawX >= 0 && rawY >= 0 && rawX <= world.w && rawY <= world.h }; } function setZoomAroundClientPoint(clientX, clientY, nextZoom) { if (!world.setFieldZoom) return false; const rect = canvas.getBoundingClientRect(); const sx = clientX - rect.left; const sy = clientY - rect.top; const focus = world.screenToWorld ? world.screenToWorld(sx, sy) : { x: sx, y: sy }; if (!world.setFieldZoom(nextZoom)) return false; const off = world.fieldScreenOffset ? world.fieldScreenOffset() : { x: 0, y: 0 }; const scale = world.viewScale ? world.viewScale() : 1; world.cameraX = focus.x - (sx - off.x) / scale; world.cameraY = focus.y - (sy - off.y) / scale; world.clampCamera?.(); return true; } function isClientOverFrozenPanel(clientX, clientY) { const panel = document.getElementById("frozenPanel"); if (!panel) return false; const r = panel.getBoundingClientRect(); return clientX >= r.left && clientX <= r.right && clientY >= r.top && clientY <= r.bottom; } function rememberGrabOrigin(target) { if (!target || uiCache.grabKind !== "tarinai") return; uiCache.grabOrigin = { x: target.x, y: target.y, vx: target.vx || 0, vy: target.vy || 0 }; } function finishTarinaiGrab(target, clientX, clientY) { if (!target || uiCache.grabKind !== "tarinai") return false; if (isClientOverFrozenPanel(clientX, clientY) && global.TarinaiFreezeSystem?.freezeTarinai) { global.TarinaiFreezeSystem.freezeTarinai(target, world); uiCache.grabOrigin = null; return true; } const p = screenToWorldClient(clientX, clientY); if (!p.inside && uiCache.grabOrigin) { target.x = uiCache.grabOrigin.x; target.y = uiCache.grabOrigin.y; target.vx = uiCache.grabOrigin.vx || 0; target.vy = uiCache.grabOrigin.vy || 0; target.thought = "\u30d5\u30a3\u30fc\u30eb\u30c9\u306b\u623b\u3055\u308c\u305f"; target.pinchThoughtTimer = Math.max(target.pinchThoughtTimer || 0, 0.8); showToast("\u30d5\u30a3\u30fc\u30eb\u30c9\u5916\u306a\u306e\u3067\u5143\u306e\u4f4d\u7f6e\u306b\u623b\u3057\u307e\u3057\u305f\u3002"); uiCache.grabOrigin = null; return true; } uiCache.grabOrigin = null; return false; } function prepareGrabTarget(target) { if (uiCache.grabKind === "tarinai") { const lodged = target.currentLodgedPin?.() || (world.items || []).find(it => it && isPinType(it.type) && it.pinState === "lodged" && it.pinTargetId === target.id); if (lodged?.detachPushpin) lodged.detachPushpin(world, "pinch"); target.sleeping = false; if (target.state === "sleep" || target.state === "seek_bed") target.goIdle?.("\u3064\u307e\u307e\u308c\u3066\u3044\u308b"); target.surpriseTimer = Math.max(target.surpriseTimer || 0, 0.18); target.thought = "\u3064\u307e\u307e\u308c\u3066\u3044\u308b"; target.pinchThoughtTimer = Math.max(target.pinchThoughtTimer || 0, 1.2); } else { if (isPinType(target.type) && target.pinState === "lodged" && target.detachPushpin) target.detachPushpin(world, "pinch"); target.dropTimer = 0; target.dropMax = 0; target.dropImpactDone = true; } } function beginGrab(p, clientX, clientY, options = {}) { const target = world.findGrabTargetAt ? world.findGrabTargetAt(p.x, p.y) : null; if (!target) { if (options.notifyNoTarget) showToast("\u3064\u307e\u3081\u308b\u5bfe\u8c61\u304c\u3042\u308a\u307e\u305b\u3093\u3002"); return false; } audio.grab?.(); uiCache.grabbing = true; uiCache.grabMoved = false; uiCache.grabTarget = target; uiCache.grabKind = target instanceof Tarinai ? "tarinai" : "item"; rememberGrabOrigin(target); uiCache.grabLastWorldX = p.x; uiCache.grabLastWorldY = p.y; uiCache.grabStartX = clientX || 0; uiCache.grabStartY = clientY || 0; target.playerHeld = true; target._heldByPlayer = true; target._playerGrabStartedAt = world.time || 0; if (target.type === "duplicator") { target.duplicatorLoadSuppressedUntil = Math.max(target.duplicatorLoadSuppressedUntil || 0, (world.time || 0) + 1.0); target._duplicatorCandidateKey = ""; target._duplicatorCandidateSince = 0; } prepareGrabTarget(target); if (options.touchMode) uiCache.touchMode = options.touchMode; renderStats(); return true; } function moveGrab(p, clientX, clientY, options = {}) { const target = uiCache.grabTarget; if (!target || target.dead) { uiCache.grabbing = false; uiCache.grabTarget = null; if (options.touch) uiCache.touchMode = ""; canvas.style.cursor = world.tool === "pinch" ? "grab" : "default"; return false; } const dx = p.x - (uiCache.grabLastWorldX ?? p.x); const dy = p.y - (uiCache.grabLastWorldY ?? p.y); uiCache.grabLastWorldX = p.x; uiCache.grabLastWorldY = p.y; const pad = target.radius || target.r || 16; if (uiCache.grabKind === "tarinai" && !p.inside) { const raw = clientToWorldRaw(clientX, clientY); target.x = raw.x; target.y = raw.y; } else { target.x = clamp(p.x, CONFIG.worldPadding + pad * 0.15, world.w - CONFIG.worldPadding - pad * 0.15); target.y = clamp(p.y, CONFIG.worldPadding + pad * 0.15, world.h - CONFIG.worldPadding - pad * 0.15); } target.vx = clamp(dx * 16, -160, 160); target.vy = clamp(dy * 16, -160, 160); if (uiCache.grabKind === "tarinai") { target.goIdle?.("\u3064\u307e\u307e\u308c\u3066\u3044\u308b"); if (options.touch) target.pinchThoughtTimer = Math.max(target.pinchThoughtTimer || 0, 0.8); } else if (target.type === "ball") { target.prevX = target.x - dx; target.prevY = target.y - dy; target.spinVelocity = clamp((target.spinVelocity || 0) + dx * 0.06, -38, 38); } world.drawListDirty = true; const now = performance.now(); if (!uiCache.grabLastSpatialAt || now - uiCache.grabLastSpatialAt > 80) { world.rebuildSpatial?.(); uiCache.grabLastSpatialAt = now; } canvas.style.cursor = "grabbing"; if (options.renderFrame) render(); return true; } function endGrab(clientX, clientY) { if (!uiCache.grabbing) return false; const target = uiCache.grabTarget; if (target) { if (uiCache.grabKind === "tarinai") { target.target = null; target.targetKey = ""; target.surpriseTimer = Math.max(target.surpriseTimer || 0, 0.15); finishTarinaiGrab(target, clientX, clientY); } else if (target.type === "ball") { target.prevX = target.x; target.prevY = target.y; } target.playerHeld = false; target._heldByPlayer = false; target._lastPlayerReleasedAt = world.time || 0; if (target.type === "duplicator") { target.duplicatorLoadSuppressedUntil = Math.max(target.duplicatorLoadSuppressedUntil || 0, (world.time || 0) + 1.2); target._duplicatorCandidateKey = ""; target._duplicatorCandidateSince = 0; } } audio.drop?.(); uiCache.grabbing = false; uiCache.grabTarget = null; uiCache.grabKind = ""; world.rebuildSpatial?.(); uiCache.grabLastSpatialAt = 0; canvas.style.cursor = cursorForTool(world.tool); renderStats(); return true; } function beginHose(p, clientX, clientY, options = {}) { audio.waterHose?.(); uiCache.hosing = true; uiCache.hoseMoved = false; uiCache.hoseLastWorldX = p.x; uiCache.hoseLastWorldY = p.y; uiCache.hoseStartX = clientX; uiCache.hoseStartY = clientY; world.applyWaterHose?.(p.x, p.y, 0, 0, 0.10); if (options.touchMode) uiCache.touchMode = options.touchMode; render(); return true; } function moveHose(p, clientX, clientY, options = {}) { const dx = p.x - (uiCache.hoseLastWorldX ?? p.x); const dy = p.y - (uiCache.hoseLastWorldY ?? p.y); uiCache.hoseLastWorldX = p.x; uiCache.hoseLastWorldY = p.y; const totalMove = Math.hypot(clientX - (uiCache.hoseStartX || clientX), clientY - (uiCache.hoseStartY || clientY)); if (totalMove > 3) { uiCache.hoseMoved = true; if (options.touch) uiCache.touchMoved = true; } if (p.inside) world.applyWaterHose?.(p.x, p.y, dx, dy, 0.10); canvas.style.cursor = "crosshair"; render(); } function endHose() { if (!uiCache.hosing) return false; uiCache.hosing = false; canvas.style.cursor = cursorForTool(world.tool); world.rebuildSpatial?.(); renderStats(); return true; } function beginPan(clientX, clientY, button = null) { uiCache.panning = true; uiCache.panMoved = false; uiCache.panButton = button; uiCache.panStartX = clientX; uiCache.panStartY = clientY; uiCache.panLastX = clientX; uiCache.panLastY = clientY; } function movePan(clientX, clientY) { if (!uiCache.panning) return false; const dx = clientX - uiCache.panLastX; const dy = clientY - uiCache.panLastY; uiCache.panLastX = clientX; uiCache.panLastY = clientY; const totalMove = Math.hypot(clientX - uiCache.panStartX, clientY - uiCache.panStartY); if (totalMove > 8) { canvas.style.cursor = "grabbing"; world.panCamera?.(-dx, -dy); uiCache.panMoved = true; render(); } return true; } function endPan(button = null) { if (!uiCache.panning) return false; if (uiCache.panButton !== null && button !== null && button !== uiCache.panButton) return false; uiCache.panning = false; uiCache.panButton = null; canvas.style.cursor = cursorForTool(world.tool); return true; } function updatePointerFromEvent(e) { const p = screenToWorldEvent(e); const prevX = world.pointer.x; const prevY = world.pointer.y; world.pointer.x = p.x; world.pointer.y = p.y; world.pointer.inside = !!p.inside; const delta = Math.hypot((p.x || 0) - (prevX || 0), (p.y || 0) - (prevY || 0)); world.pointer.motion = clamp(delta / 18, 0, 2); world.pointer.movedAt = performance.now(); } function cancelPointerActions() { uiCache.touchMode = ""; uiCache.touchMoved = false; uiCache.hosing = false; uiCache.grabbing = false; uiCache.grabTarget = null; uiCache.grabKind = ""; } return { canvas, world, ui, uiCache, cursorForTool, setMobileInputMode, updateTouchMobileUiClass, mobileInputMode, bindMobileModeControls, touchPoint, touchCenter, touchDistance, screenToWorldClient, screenToWorldEvent, clientToWorldRaw, setZoomAroundClientPoint, beginGrab, moveGrab, endGrab, beginHose, moveHose, endHose, beginPan, movePan, endPan, updatePointerFromEvent, cancelPointerActions, }; } global.TarinaiUIInputShared = Object.freeze({ createInputContext, cursorForTool }); })(window);