sounds
This commit is contained in:
parent
c6ba3e38a2
commit
37087d08a7
189 changed files with 12864 additions and 9589 deletions
752
js/ui_bind.js
Normal file
752
js/ui_bind.js
Normal file
|
|
@ -0,0 +1,752 @@
|
|||
"use strict";
|
||||
|
||||
function bindUI() {
|
||||
loadLogPushSettings();
|
||||
syncLogPushControls();
|
||||
syncAudioControls();
|
||||
applyToolTips();
|
||||
renderEcologyCards();
|
||||
uiCache.toolButtons = Array.from(ui.toolPalette?.querySelectorAll(".tool") || []);
|
||||
syncToolSizeBadges();
|
||||
for (const toggle of document.querySelectorAll(".panel-card-toggle")) {
|
||||
toggle.addEventListener("click", () => {
|
||||
const card = toggle.closest(".collapsible-card");
|
||||
if (!card) return;
|
||||
audio.uiFold?.();
|
||||
const collapsed = card.classList.toggle("collapsed");
|
||||
toggle.setAttribute("aria-expanded", collapsed ? "false" : "true");
|
||||
const state = toggle.querySelector(".panel-card-state");
|
||||
if (state) state.textContent = collapsed ? "展開" : "折り畳み";
|
||||
});
|
||||
}
|
||||
ui.colonyChartTabs?.addEventListener("click", (e) => {
|
||||
const btn = e.target.closest("button[data-chart]");
|
||||
if (!btn) return;
|
||||
uiCache.chartMode = btn.dataset.chart || "population";
|
||||
for (const b of ui.colonyChartTabs.querySelectorAll("button")) b.classList.toggle("active", b === btn);
|
||||
uiCache.lastChartDraw = "";
|
||||
uiCache.lastChartDrawAt = 0;
|
||||
renderStats();
|
||||
});
|
||||
syncToolSizeBadges();
|
||||
ui.logPushEnabled?.addEventListener("change", () => {
|
||||
uiCache.logPushEnabled = Boolean(ui.logPushEnabled.checked);
|
||||
audio.notify?.();
|
||||
saveLogPushSettings();
|
||||
showToast(uiCache.logPushEnabled ? "ログのプッシュ通知をONにしました。" : "ログのプッシュ通知をOFFにしました。");
|
||||
});
|
||||
ui.logPushControls?.addEventListener("change", (e) => {
|
||||
const input = e.target.closest("[data-push-kind]");
|
||||
if (!input) return;
|
||||
const kind = input.dataset.pushKind || "";
|
||||
audio.uiClick?.();
|
||||
if (input.checked) uiCache.logPushKinds.add(kind);
|
||||
else uiCache.logPushKinds.delete(kind);
|
||||
saveLogPushSettings();
|
||||
});
|
||||
ui.soundCategoryControls?.addEventListener("change", (e) => {
|
||||
const input = e.target.closest("[data-sound-category]");
|
||||
if (!input) return;
|
||||
const category = input.dataset.soundCategory || "";
|
||||
audio.setCategory?.(category, Boolean(input.checked));
|
||||
if (audio.enabled && input.checked) audio.uiClick?.();
|
||||
syncAudioControls();
|
||||
showToast(`${audio.soundCategories?.[category] || category} ${input.checked ? "ON" : "OFF"}`);
|
||||
});
|
||||
ui.archiveContent?.addEventListener("scroll", scheduleArchiveWindowRender, { passive: true });
|
||||
window.addEventListener("scroll", schedulePendingArchiveRender, { passive: true });
|
||||
window.addEventListener("resize", schedulePendingArchiveRender, { passive: true });
|
||||
const bindArchiveHorizontal = (root) => {
|
||||
if (!root) return;
|
||||
root.addEventListener("wheel", (e) => {
|
||||
const scroller = e.target.closest(".lineage-family-tree, .lineage-main-content");
|
||||
if (!scroller) return;
|
||||
const canX = scroller.scrollWidth > scroller.clientWidth + 4;
|
||||
if (!canX) return;
|
||||
const dominant = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : (e.shiftKey ? e.deltaY : 0);
|
||||
if (!dominant) return;
|
||||
e.preventDefault();
|
||||
scroller.scrollLeft += dominant;
|
||||
}, { passive: false });
|
||||
};
|
||||
bindArchiveHorizontal(ui.archiveContent);
|
||||
ui.archiveContent?.addEventListener("click", (e) => {
|
||||
const node = e.target.closest("[data-family-tarinai-id]");
|
||||
if (!node) return;
|
||||
const id = node.getAttribute("data-family-tarinai-id");
|
||||
const t = world.tarinai.find(x => (x.id === id || x.familyKey === id) && !x.dead);
|
||||
if (!t) { showToast("\u305d\u306e\u500b\u4f53\u306f\u3082\u3046\u9078\u629e\u3067\u304d\u307e\u305b\u3093\u3002"); return; }
|
||||
world.selected = t;
|
||||
t.focusPulseTimer = Math.max(t.focusPulseTimer || 0, 1.15);
|
||||
selectTool("observe");
|
||||
if (Number.isFinite(t.x) && Number.isFinite(t.y)) {
|
||||
world.cameraX = t.x - (world.visibleWorldW ? world.visibleWorldW() : (world.viewportW || world.w)) / 2;
|
||||
world.cameraY = t.y - (world.visibleWorldH ? world.visibleWorldH() : (world.viewportH || world.h)) / 2;
|
||||
world.clampCamera?.();
|
||||
}
|
||||
renderStats();
|
||||
showToast(`${t.name}\u3092\u9078\u629e\u3057\u307e\u3057\u305f\u3002`);
|
||||
});
|
||||
|
||||
if (!uiCache.archiveAutoRefreshTimer && typeof window !== "undefined") {
|
||||
uiCache.archiveAutoRefreshTimer = window.setInterval(() => {
|
||||
if (!ui.archiveContent || !archiveAutoUpdateEnabled()) return;
|
||||
scheduleArchiveWindowRender();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
ui.selectedInfo?.addEventListener("click", (e) => {
|
||||
const categoryToggle = e.target.closest(".selected-category-toggle");
|
||||
if (categoryToggle) {
|
||||
const category = categoryToggle.closest(".selected-category");
|
||||
const id = category?.dataset?.selectedCategory || "";
|
||||
if (!category || !id) return;
|
||||
if (!uiCache.selectedCollapsedCategories) uiCache.selectedCollapsedCategories = new Set();
|
||||
audio.uiFold?.();
|
||||
category.classList.toggle("collapsed");
|
||||
const collapsed = category.classList.contains("collapsed");
|
||||
if (collapsed) uiCache.selectedCollapsedCategories.add(id);
|
||||
else uiCache.selectedCollapsedCategories.delete(id);
|
||||
categoryToggle.setAttribute("aria-expanded", collapsed ? "false" : "true");
|
||||
return;
|
||||
}
|
||||
const focusBtn = e.target.closest("[data-focus-tarinai-id]");
|
||||
if (focusBtn) {
|
||||
e.preventDefault();
|
||||
focusTarinaiById(focusBtn.getAttribute("data-focus-tarinai-id"));
|
||||
return;
|
||||
}
|
||||
const btn = e.target.closest("[data-selected-action=\"favorite\"]");
|
||||
if (!btn || !world.selected || world.selected.dead) return;
|
||||
world.selected.favorite = !world.selected.favorite;
|
||||
uiCache.selectedSnapshot = "";
|
||||
renderSelected();
|
||||
render();
|
||||
showToast(world.selected.favorite ? "\u304a\u6c17\u306b\u5165\u308a\u306b\u3057\u307e\u3057\u305f\u3002" : "\u304a\u6c17\u306b\u5165\u308a\u3092\u89e3\u9664\u3057\u307e\u3057\u305f\u3002");
|
||||
});
|
||||
ui.selectedInfo?.addEventListener("change", (e) => {
|
||||
const input = e.target.closest("[data-selected-name]");
|
||||
if (!input) return;
|
||||
renameSelectedTarinai(input.value);
|
||||
});
|
||||
ui.selectedInfo?.addEventListener("keydown", (e) => {
|
||||
const input = e.target.closest("[data-selected-name]");
|
||||
if (!input) return;
|
||||
if (e.key === "Enter") { e.preventDefault(); input.blur(); }
|
||||
if (e.key === "Escape") { e.preventDefault(); input.value = world.selected?.name || ""; input.blur(); }
|
||||
});
|
||||
ui.selectedCloseBtn?.addEventListener("click", () => {
|
||||
world.selected = null;
|
||||
uiCache.selectedSnapshot = "";
|
||||
uiCache.selectedEmpty = false;
|
||||
renderSelected();
|
||||
render();
|
||||
});
|
||||
|
||||
ui.pauseBtn?.addEventListener("click", () => {
|
||||
audio.uiClick?.();
|
||||
world.paused = !world.paused;
|
||||
const archiveStale = uiCache.archiveFamilyVersion !== (world.familyVersion || 0);
|
||||
if (archiveAutoUpdateEnabled() && world.paused && (uiCache.archivePendingWhileRunning || archiveStale)) {
|
||||
uiCache.archivePendingWhileRunning = false;
|
||||
scheduleArchiveWindowRender();
|
||||
}
|
||||
syncTopButtons();
|
||||
});
|
||||
ui.speedBtn?.addEventListener("click", () => {
|
||||
audio.uiClick?.();
|
||||
const speeds = [1, 2, 4];
|
||||
const i = speeds.indexOf(world.speed);
|
||||
world.speed = speeds[(i + 1) % speeds.length];
|
||||
syncTopButtons();
|
||||
});
|
||||
ui.ecologyBtn?.addEventListener("click", () => { audio.uiClick?.(); openEcologyDialog(); });
|
||||
ui.soundBtn?.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
toggleSoundPanel();
|
||||
});
|
||||
ui.soundPanel?.addEventListener("click", (e) => e.stopPropagation());
|
||||
ui.soundMasterToggle?.addEventListener("change", () => {
|
||||
const on = audio.setEnabled ? audio.setEnabled(ui.soundMasterToggle.checked) : audio.toggle();
|
||||
if (on) audio.uiClick?.();
|
||||
syncAudioControls();
|
||||
showToast(on ? "効果音をONにしました。" : "効果音をOFFにしました。");
|
||||
});
|
||||
document.addEventListener("click", (e) => {
|
||||
if (!ui.soundMenu || ui.soundMenu.contains(e.target)) return;
|
||||
closeSoundPanel();
|
||||
});
|
||||
ui.creditsBtn?.addEventListener("click", () => { audio.uiClick?.(); openCreditsDialog(); });
|
||||
ui.creditsCloseBtn?.addEventListener("click", closeCreditsDialog);
|
||||
ui.creditsDialog?.addEventListener("click", (e) => {
|
||||
if (e.target === ui.creditsDialog) closeCreditsDialog();
|
||||
});
|
||||
ui.resetBtn?.addEventListener("click", () => {
|
||||
audio.uiClick?.();
|
||||
openFieldDialog();
|
||||
});
|
||||
ui.fieldCancelBtn?.addEventListener("click", closeFieldDialog);
|
||||
ui.fieldDialog?.addEventListener("click", (e) => {
|
||||
if (e.target === ui.fieldDialog) { closeFieldDialog(); return; }
|
||||
const btn = e.target.closest("[data-field-type]");
|
||||
if (!btn) return;
|
||||
resetWithField(btn.dataset.fieldType || "garden");
|
||||
});
|
||||
ui.ecologyCloseBtn?.addEventListener("click", closeEcologyDialog);
|
||||
ui.ecologyDialog?.addEventListener("click", (e) => {
|
||||
if (e.target === ui.ecologyDialog) closeEcologyDialog();
|
||||
});
|
||||
ui.archiveUpdateToggleBtn?.addEventListener("click", () => { audio.uiClick?.(); setArchiveUpdateEnabled(!archiveAutoUpdateEnabled()); });
|
||||
syncArchiveUpdateButton();
|
||||
ui.archiveResetBtn?.addEventListener("click", () => { audio.uiClick?.(); openArchiveResetDialog(); });
|
||||
ui.archiveResetCancelBtn?.addEventListener("click", closeArchiveResetDialog);
|
||||
ui.archiveResetDialog?.addEventListener("click", (e) => {
|
||||
if (e.target === ui.archiveResetDialog) closeArchiveResetDialog();
|
||||
});
|
||||
ui.archiveResetConfirmBtn?.addEventListener("click", () => {
|
||||
world.resetFamilyTree?.();
|
||||
window.resetArchiveRenderState?.();
|
||||
closeArchiveResetDialog();
|
||||
renderArchive();
|
||||
renderSelected();
|
||||
showToast("\u5bb6\u7cfb\u56f3\u3092\u30ea\u30bb\u30c3\u30c8\u3057\u307e\u3057\u305f\u3002");
|
||||
});
|
||||
|
||||
ui.toolPalette?.addEventListener("click", (e) => {
|
||||
const toggle = e.target.closest(".tool-category-toggle");
|
||||
if (toggle) {
|
||||
const category = toggle.closest(".tool-category");
|
||||
if (!category) return;
|
||||
audio.uiFold?.();
|
||||
category.classList.toggle("collapsed");
|
||||
toggle.setAttribute("aria-expanded", category.classList.contains("collapsed") ? "false" : "true");
|
||||
return;
|
||||
}
|
||||
const btn = e.target.closest(".tool");
|
||||
if (!btn) return;
|
||||
const tool = btn.dataset.tool;
|
||||
audio.uiClick?.();
|
||||
if (world.tool === tool && cycleToolSize(tool)) return;
|
||||
selectTool(tool);
|
||||
});
|
||||
|
||||
const chainScroll = (el) => {
|
||||
if (!el) return;
|
||||
el.addEventListener("wheel", (e) => {
|
||||
if (Math.abs(e.deltaY) <= Math.abs(e.deltaX)) return;
|
||||
const atTop = el.scrollTop <= 0;
|
||||
const atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 1;
|
||||
if ((e.deltaY < 0 && atTop) || (e.deltaY > 0 && atBottom)) {
|
||||
const page = document.scrollingElement || document.documentElement;
|
||||
page.scrollTop += e.deltaY;
|
||||
}
|
||||
}, { passive: true });
|
||||
};
|
||||
const transferEdgeScroll = (child, parent) => {
|
||||
if (!child || !parent) return;
|
||||
child.addEventListener("wheel", (e) => {
|
||||
if (Math.abs(e.deltaY) <= Math.abs(e.deltaX)) return;
|
||||
const childCanScroll = child.scrollHeight > child.clientHeight + 1;
|
||||
const atTop = child.scrollTop <= 0;
|
||||
const atBottom = child.scrollTop + child.clientHeight >= child.scrollHeight - 1;
|
||||
const movingInside = childCanScroll && ((e.deltaY < 0 && !atTop) || (e.deltaY > 0 && !atBottom));
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (movingInside) {
|
||||
child.scrollTop += e.deltaY;
|
||||
return;
|
||||
}
|
||||
parent.scrollTop += e.deltaY;
|
||||
}, { passive: false });
|
||||
};
|
||||
const rightPanel = document.querySelector(".panel");
|
||||
chainScroll(rightPanel);
|
||||
chainScroll(ui.archiveContent);
|
||||
transferEdgeScroll(ui.log, rightPanel);
|
||||
|
||||
const isTouchDevice = () => matchMedia?.("(pointer: coarse)")?.matches || navigator.maxTouchPoints > 0;
|
||||
const setMobileInputMode = (mode = "auto") => {
|
||||
if (!["auto", "camera", "tool", "family"].includes(mode)) mode = "auto";
|
||||
uiCache.mobileInputMode = mode;
|
||||
try { localStorage.setItem("tarinai_mobile_input_mode_v1", mode); } catch (_) {}
|
||||
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");
|
||||
};
|
||||
const mobileInputMode = () => uiCache.mobileInputMode || "auto";
|
||||
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(`スマホ操作: ${btn.textContent || btn.dataset.mobileMode}`);
|
||||
});
|
||||
|
||||
const touchPoint = (touches, index = 0) => {
|
||||
const t = touches?.[index];
|
||||
if (!t) return null;
|
||||
return { clientX: t.clientX, clientY: t.clientY };
|
||||
};
|
||||
const 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 };
|
||||
};
|
||||
const 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);
|
||||
};
|
||||
const 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 };
|
||||
};
|
||||
const 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;
|
||||
};
|
||||
const beginTouchGrab = (p, e) => {
|
||||
const target = world.findGrabTargetAt ? world.findGrabTargetAt(p.x, p.y) : null;
|
||||
if (!target) return false;
|
||||
audio.grab?.();
|
||||
uiCache.grabbing = true;
|
||||
uiCache.grabMoved = false;
|
||||
uiCache.grabTarget = target;
|
||||
uiCache.grabKind = target instanceof Tarinai ? "tarinai" : "item";
|
||||
uiCache.grabLastWorldX = p.x;
|
||||
uiCache.grabLastWorldY = p.y;
|
||||
uiCache.grabStartX = e.touches?.[0]?.clientX || 0;
|
||||
uiCache.grabStartY = e.touches?.[0]?.clientY || 0;
|
||||
if (uiCache.grabKind === "tarinai") {
|
||||
target.sleeping = false;
|
||||
if (target.state === "sleep" || target.state === "seek_bed") target.state = "idle";
|
||||
target.surpriseTimer = Math.max(target.surpriseTimer || 0, 0.18);
|
||||
} else {
|
||||
target.dropTimer = 0;
|
||||
target.dropMax = 0;
|
||||
target.dropImpactDone = true;
|
||||
}
|
||||
uiCache.touchMode = "grab";
|
||||
renderStats();
|
||||
return true;
|
||||
};
|
||||
|
||||
canvas?.addEventListener("touchstart", (e) => {
|
||||
if (!e.touches?.length) return;
|
||||
e.preventDefault();
|
||||
uiCache.touchMoved = false;
|
||||
const mode = mobileInputMode();
|
||||
if ((mode === "camera" || mode === "family") && e.touches.length === 1) {
|
||||
const t = e.touches[0];
|
||||
uiCache.touchStartX = t.clientX;
|
||||
uiCache.touchStartY = t.clientY;
|
||||
uiCache.touchLastX = t.clientX;
|
||||
uiCache.touchLastY = t.clientY;
|
||||
uiCache.touchMode = "cameraPan";
|
||||
return;
|
||||
}
|
||||
if (e.touches.length >= 2) {
|
||||
const c = touchCenter(e.touches);
|
||||
uiCache.touchMode = "pinchZoom";
|
||||
uiCache.pinchStartDistance = Math.max(1, touchDistance(e.touches));
|
||||
uiCache.pinchStartZoom = world.fieldZoom || 1;
|
||||
uiCache.pinchLastCenterX = c?.clientX || 0;
|
||||
uiCache.pinchLastCenterY = c?.clientY || 0;
|
||||
return;
|
||||
}
|
||||
const t = e.touches[0];
|
||||
const p = screenToWorldClient(t.clientX, t.clientY);
|
||||
uiCache.touchStartX = t.clientX;
|
||||
uiCache.touchStartY = t.clientY;
|
||||
uiCache.touchLastX = t.clientX;
|
||||
uiCache.touchLastY = t.clientY;
|
||||
uiCache.touchLastWorldX = p.x;
|
||||
uiCache.touchLastWorldY = p.y;
|
||||
if (mode === "tool" && world.tool === "observe") {
|
||||
uiCache.touchMode = "toolTapOnly";
|
||||
return;
|
||||
}
|
||||
if (world.tool === "water_hose" && p.inside) {
|
||||
audio.waterHose?.();
|
||||
uiCache.hosing = true;
|
||||
uiCache.hoseMoved = false;
|
||||
uiCache.hoseLastWorldX = p.x;
|
||||
uiCache.hoseLastWorldY = p.y;
|
||||
uiCache.hoseStartX = t.clientX;
|
||||
uiCache.hoseStartY = t.clientY;
|
||||
world.applyWaterHose?.(p.x, p.y, 0, 0, 0.10);
|
||||
uiCache.touchMode = "hose";
|
||||
render();
|
||||
return;
|
||||
}
|
||||
if (world.tool === "pinch" && p.inside && beginTouchGrab(p, e)) return;
|
||||
uiCache.touchMode = mode === "tool" ? "toolTapOnly" : "panOrTap";
|
||||
}, { passive: false });
|
||||
|
||||
canvas?.addEventListener("touchmove", (e) => {
|
||||
if (!e.touches?.length || !uiCache.touchMode) return;
|
||||
e.preventDefault();
|
||||
if (uiCache.touchMode === "pinchZoom" && e.touches.length >= 2) {
|
||||
const c = touchCenter(e.touches);
|
||||
const distNow = Math.max(1, touchDistance(e.touches));
|
||||
const ratio = distNow / Math.max(1, uiCache.pinchStartDistance || distNow);
|
||||
setZoomAroundClientPoint(c.clientX, c.clientY, (uiCache.pinchStartZoom || 1) * ratio);
|
||||
const dx = c.clientX - (uiCache.pinchLastCenterX || c.clientX);
|
||||
const dy = c.clientY - (uiCache.pinchLastCenterY || c.clientY);
|
||||
uiCache.pinchLastCenterX = c.clientX;
|
||||
uiCache.pinchLastCenterY = c.clientY;
|
||||
if (Math.hypot(dx, dy) > 0.1) world.panCamera?.(-dx, -dy);
|
||||
uiCache.touchMoved = true;
|
||||
render();
|
||||
return;
|
||||
}
|
||||
const t = e.touches[0];
|
||||
const p = screenToWorldClient(t.clientX, t.clientY);
|
||||
const screenDx = t.clientX - (uiCache.touchLastX || t.clientX);
|
||||
const screenDy = t.clientY - (uiCache.touchLastY || t.clientY);
|
||||
const totalMove = Math.hypot(t.clientX - (uiCache.touchStartX || t.clientX), t.clientY - (uiCache.touchStartY || t.clientY));
|
||||
uiCache.touchLastX = t.clientX;
|
||||
uiCache.touchLastY = t.clientY;
|
||||
if (uiCache.touchMode === "cameraPan") {
|
||||
if (totalMove > 2) uiCache.touchMoved = true;
|
||||
world.panCamera?.(-screenDx, -screenDy);
|
||||
render();
|
||||
return;
|
||||
}
|
||||
if (uiCache.touchMode === "toolTapOnly") {
|
||||
if (totalMove > 8) uiCache.touchMoved = true;
|
||||
return;
|
||||
}
|
||||
if (uiCache.touchMode === "hose" && uiCache.hosing) {
|
||||
const dx = p.x - (uiCache.hoseLastWorldX ?? p.x);
|
||||
const dy = p.y - (uiCache.hoseLastWorldY ?? p.y);
|
||||
uiCache.hoseLastWorldX = p.x;
|
||||
uiCache.hoseLastWorldY = p.y;
|
||||
if (totalMove > 3) { uiCache.hoseMoved = true; uiCache.touchMoved = true; }
|
||||
if (p.inside) world.applyWaterHose?.(p.x, p.y, dx, dy, 0.10);
|
||||
render();
|
||||
return;
|
||||
}
|
||||
if (uiCache.touchMode === "grab" && uiCache.grabbing) {
|
||||
const target = uiCache.grabTarget;
|
||||
if (!target || target.dead) {
|
||||
uiCache.grabbing = false;
|
||||
uiCache.grabTarget = null;
|
||||
uiCache.touchMode = "";
|
||||
return;
|
||||
}
|
||||
const dx = p.x - (uiCache.grabLastWorldX ?? p.x);
|
||||
const dy = p.y - (uiCache.grabLastWorldY ?? p.y);
|
||||
uiCache.grabLastWorldX = p.x;
|
||||
uiCache.grabLastWorldY = p.y;
|
||||
if (totalMove > 3) { uiCache.grabMoved = true; uiCache.touchMoved = true; }
|
||||
const pad = target.radius || target.r || 16;
|
||||
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.state = "idle";
|
||||
target.target = null;
|
||||
target.sleeping = false;
|
||||
} 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;
|
||||
}
|
||||
render();
|
||||
return;
|
||||
}
|
||||
if (uiCache.touchMode === "panOrTap") {
|
||||
if (totalMove > 7) {
|
||||
uiCache.touchMoved = true;
|
||||
world.panCamera?.(-screenDx, -screenDy);
|
||||
render();
|
||||
}
|
||||
}
|
||||
}, { passive: false });
|
||||
|
||||
canvas?.addEventListener("touchend", (e) => {
|
||||
e.preventDefault();
|
||||
if (uiCache.hosing) {
|
||||
uiCache.hosing = false;
|
||||
world.rebuildSpatial?.();
|
||||
renderStats();
|
||||
}
|
||||
if (uiCache.grabbing) {
|
||||
const target = uiCache.grabTarget;
|
||||
if (target) {
|
||||
if (uiCache.grabKind === "tarinai") {
|
||||
target.target = null;
|
||||
target.targetKey = "";
|
||||
target.surpriseTimer = Math.max(target.surpriseTimer || 0, 0.15);
|
||||
} else if (target.type === "ball") {
|
||||
target.prevX = target.x;
|
||||
target.prevY = target.y;
|
||||
}
|
||||
}
|
||||
audio.drop?.();
|
||||
uiCache.grabbing = false;
|
||||
uiCache.grabTarget = null;
|
||||
uiCache.grabKind = "";
|
||||
world.rebuildSpatial?.();
|
||||
uiCache.grabLastSpatialAt = 0;
|
||||
renderStats();
|
||||
} else if (uiCache.touchMode === "toolTapOnly" && !uiCache.touchMoved) {
|
||||
const p = screenToWorldClient(uiCache.touchLastX || uiCache.touchStartX, uiCache.touchLastY || uiCache.touchStartY);
|
||||
if (p.inside) {
|
||||
world.handleClick(p.x, p.y);
|
||||
renderStats();
|
||||
}
|
||||
} else if (uiCache.touchMode === "panOrTap" && !uiCache.touchMoved) {
|
||||
const p = screenToWorldClient(uiCache.touchLastX || uiCache.touchStartX, uiCache.touchLastY || uiCache.touchStartY);
|
||||
if (p.inside) {
|
||||
world.handleClick(p.x, p.y);
|
||||
renderStats();
|
||||
}
|
||||
}
|
||||
if (!e.touches?.length) {
|
||||
uiCache.touchMode = "";
|
||||
uiCache.touchMoved = false;
|
||||
}
|
||||
}, { passive: false });
|
||||
|
||||
canvas?.addEventListener("touchcancel", () => {
|
||||
uiCache.touchMode = "";
|
||||
uiCache.touchMoved = false;
|
||||
uiCache.hosing = false;
|
||||
uiCache.grabbing = false;
|
||||
uiCache.grabTarget = null;
|
||||
uiCache.grabKind = "";
|
||||
}, { passive: true });
|
||||
|
||||
|
||||
canvas?.addEventListener("contextmenu", (e) => e.preventDefault());
|
||||
canvas?.addEventListener("mousedown", (e) => {
|
||||
if (e.button === 0 && world.tool === "water_hose") {
|
||||
audio.waterHose?.();
|
||||
const p = screenToWorld(e);
|
||||
if (!p.inside) return;
|
||||
e.preventDefault();
|
||||
uiCache.hosing = true;
|
||||
uiCache.hoseMoved = false;
|
||||
uiCache.hoseLastWorldX = p.x;
|
||||
uiCache.hoseLastWorldY = p.y;
|
||||
uiCache.hoseStartX = e.clientX;
|
||||
uiCache.hoseStartY = e.clientY;
|
||||
world.applyWaterHose?.(p.x, p.y, 0, 0, 0.10);
|
||||
render();
|
||||
return;
|
||||
}
|
||||
if (e.button === 0 && world.tool === "pinch") {
|
||||
const p = screenToWorld(e);
|
||||
if (!p.inside) return;
|
||||
const target = world.findGrabTargetAt ? world.findGrabTargetAt(p.x, p.y) : null;
|
||||
if (!target) {
|
||||
showToast("\u3064\u307e\u3081\u308b\u5bfe\u8c61\u304c\u3042\u308a\u307e\u305b\u3093\u3002");
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
audio.grab?.();
|
||||
uiCache.grabbing = true;
|
||||
uiCache.grabMoved = false;
|
||||
uiCache.grabTarget = target;
|
||||
uiCache.grabKind = target instanceof Tarinai ? "tarinai" : "item";
|
||||
uiCache.grabLastWorldX = p.x;
|
||||
uiCache.grabLastWorldY = p.y;
|
||||
uiCache.grabStartX = e.clientX;
|
||||
uiCache.grabStartY = e.clientY;
|
||||
if (uiCache.grabKind === "tarinai") {
|
||||
target.sleeping = false;
|
||||
if (target.state === "sleep" || target.state === "seek_bed") target.state = "idle";
|
||||
target.surpriseTimer = Math.max(target.surpriseTimer || 0, 0.18);
|
||||
} else {
|
||||
target.dropTimer = 0;
|
||||
target.dropMax = 0;
|
||||
target.dropImpactDone = true;
|
||||
}
|
||||
canvas.style.cursor = "grabbing";
|
||||
renderStats();
|
||||
return;
|
||||
}
|
||||
if (e.button !== 2) return;
|
||||
e.preventDefault();
|
||||
uiCache.panning = true;
|
||||
uiCache.panMoved = false;
|
||||
uiCache.panButton = e.button;
|
||||
uiCache.panStartX = e.clientX;
|
||||
uiCache.panStartY = e.clientY;
|
||||
uiCache.panLastX = e.clientX;
|
||||
uiCache.panLastY = e.clientY;
|
||||
});
|
||||
window.addEventListener("mouseup", (e) => {
|
||||
if (uiCache.hosing) {
|
||||
uiCache.hosing = false;
|
||||
canvas.style.cursor = world.tool === "water_hose" ? "crosshair" : (world.tool === "observe" ? "default" : (world.tool === "pinch" ? "grab" : (world.tool === "poke" || world.tool === "delete" ? "pointer" : "crosshair")));
|
||||
world.rebuildSpatial?.();
|
||||
renderStats();
|
||||
return;
|
||||
}
|
||||
if (uiCache.grabbing) {
|
||||
const target = uiCache.grabTarget;
|
||||
if (target) {
|
||||
if (uiCache.grabKind === "tarinai") {
|
||||
target.target = null;
|
||||
target.targetKey = "";
|
||||
target.surpriseTimer = Math.max(target.surpriseTimer || 0, 0.15);
|
||||
} else if (target.type === "ball") {
|
||||
target.prevX = target.x;
|
||||
target.prevY = target.y;
|
||||
}
|
||||
}
|
||||
audio.drop?.();
|
||||
uiCache.grabbing = false;
|
||||
uiCache.grabTarget = null;
|
||||
uiCache.grabKind = "";
|
||||
world.rebuildSpatial?.();
|
||||
uiCache.grabLastSpatialAt = 0;
|
||||
canvas.style.cursor = world.tool === "pinch" ? "grab" : (world.tool === "observe" ? "default" : (world.tool === "poke" || world.tool === "delete" ? "pointer" : "crosshair"));
|
||||
renderStats();
|
||||
return;
|
||||
}
|
||||
if (!uiCache.panning) return;
|
||||
if (uiCache.panButton !== null && e.button !== uiCache.panButton) return;
|
||||
uiCache.panning = false;
|
||||
uiCache.panButton = null;
|
||||
canvas.style.cursor = world.tool === "observe" ? "default" : (world.tool === "pinch" ? "grab" : (world.tool === "poke" || world.tool === "delete" ? "pointer" : "crosshair"));
|
||||
});
|
||||
window.addEventListener("mousemove", (e) => {
|
||||
if (uiCache.hosing) {
|
||||
const p = screenToWorld(e);
|
||||
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(e.clientX - (uiCache.hoseStartX || e.clientX), e.clientY - (uiCache.hoseStartY || e.clientY));
|
||||
if (totalMove > 3) uiCache.hoseMoved = true;
|
||||
if (p.inside) world.applyWaterHose?.(p.x, p.y, dx, dy, 0.10);
|
||||
canvas.style.cursor = "crosshair";
|
||||
render();
|
||||
return;
|
||||
}
|
||||
if (uiCache.grabbing) {
|
||||
const target = uiCache.grabTarget;
|
||||
if (!target || target.dead) {
|
||||
uiCache.grabbing = false;
|
||||
uiCache.grabTarget = null;
|
||||
canvas.style.cursor = world.tool === "pinch" ? "grab" : "default";
|
||||
return;
|
||||
}
|
||||
const p = screenToWorld(e);
|
||||
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 totalMove = Math.hypot(e.clientX - (uiCache.grabStartX || e.clientX), e.clientY - (uiCache.grabStartY || e.clientY));
|
||||
if (totalMove > 3) uiCache.grabMoved = true;
|
||||
const pad = target.radius || target.r || 16;
|
||||
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.state = "idle";
|
||||
target.target = null;
|
||||
target.sleeping = false;
|
||||
} 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";
|
||||
return;
|
||||
}
|
||||
if (!uiCache.panning) return;
|
||||
const dx = e.clientX - uiCache.panLastX;
|
||||
const dy = e.clientY - uiCache.panLastY;
|
||||
uiCache.panLastX = e.clientX;
|
||||
uiCache.panLastY = e.clientY;
|
||||
const totalMove = Math.hypot(e.clientX - uiCache.panStartX, e.clientY - uiCache.panStartY);
|
||||
if (totalMove > 8) {
|
||||
canvas.style.cursor = "grabbing";
|
||||
world.panCamera?.(-dx, -dy);
|
||||
uiCache.panMoved = true;
|
||||
render();
|
||||
}
|
||||
});
|
||||
|
||||
canvas?.addEventListener("click", (e) => {
|
||||
if (uiCache.hoseMoved) {
|
||||
uiCache.hoseMoved = false;
|
||||
return;
|
||||
}
|
||||
if (uiCache.grabMoved) {
|
||||
uiCache.grabMoved = false;
|
||||
return;
|
||||
}
|
||||
if (uiCache.panMoved) {
|
||||
uiCache.panMoved = false;
|
||||
return;
|
||||
}
|
||||
const p = screenToWorld(e);
|
||||
if (!p.inside) return;
|
||||
world.handleClick(p.x, p.y);
|
||||
renderStats();
|
||||
});
|
||||
|
||||
canvas?.addEventListener("mousemove", (e) => {
|
||||
const p = screenToWorld(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();
|
||||
});
|
||||
canvas?.addEventListener("wheel", resizeFieldFromWheel, { passive: false });
|
||||
canvas?.addEventListener("mouseleave", () => {
|
||||
world.pointer.inside = false;
|
||||
world.pointer.motion = 0;
|
||||
});
|
||||
|
||||
window.addEventListener("keydown", (e) => {
|
||||
if (e.key !== "Escape") return;
|
||||
closeFieldDialog();
|
||||
closeEcologyDialog();
|
||||
closeArchiveResetDialog();
|
||||
closeCreditsDialog();
|
||||
});
|
||||
|
||||
window.addEventListener("resize", resizeCanvas);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue