This commit is contained in:
33333-33333 2026-06-22 02:03:19 +09:00
commit e8f1d1db1e
60 changed files with 2417 additions and 612 deletions

View file

@ -7,6 +7,8 @@ function bindUI() {
syncAudioControls();
applyToolTips();
renderEcologyCards();
window.TarinaiSaveSystem?.bindSaveSystem?.();
window.TarinaiFreezeSystem?.bindFreezeSystem?.();
uiCache.toolButtons = Array.from(ui.toolPalette?.querySelectorAll(".tool") || []);
syncToolSizeBadges();
for (const toggle of document.querySelectorAll(".panel-card-toggle")) {
@ -266,7 +268,6 @@ function bindUI() {
transferEdgeScroll(ui.log, rightPanel);
const inputModeManager = window.TarinaiInputMode;
const isTouchDevice = () => Boolean(inputModeManager?.snapshot?.().isTouchDevice || matchMedia?.("(pointer: coarse)")?.matches || navigator.maxTouchPoints > 0);
const setMobileInputMode = (mode = "auto") => {
mode = inputModeManager?.setMode?.(mode) || (["auto", "camera", "tool", "family"].includes(mode) ? mode : "auto");
uiCache.mobileInputMode = mode;
@ -317,6 +318,16 @@ function bindUI() {
const sy = clientY - rect.top;
return world.screenToWorld ? world.screenToWorld(sx, sy) : { x: sx, y: sy, inside: sx >= 0 && sy >= 0 };
};
const 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 };
};
const setZoomAroundClientPoint = (clientX, clientY, nextZoom) => {
if (!world.setFieldZoom) return false;
const rect = canvas.getBoundingClientRect();
@ -331,6 +342,41 @@ function bindUI() {
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) && window.TarinaiFreezeSystem?.freezeTarinai) {
window.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 = "フィールドに戻された";
target.pinchThoughtTimer = Math.max(target.pinchThoughtTimer || 0, 0.8);
showToast("フィールド外なので元の位置に戻しました。");
uiCache.grabOrigin = null;
return true;
}
uiCache.grabOrigin = null;
return false;
}
const beginTouchGrab = (p, e) => {
const target = world.findGrabTargetAt ? world.findGrabTargetAt(p.x, p.y) : null;
if (!target) return false;
@ -339,16 +385,19 @@ function bindUI() {
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 = 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";
if (target.state === "sleep" || target.state === "seek_bed") target.goIdle?.("つままれている");
target.surpriseTimer = Math.max(target.surpriseTimer || 0, 0.18);
target.thought = "つままれている";
target.pinchThoughtTimer = Math.max(target.pinchThoughtTimer || 0, 1.2);
} else {
if (target.type === "pushpin" && target.pinState === "lodged" && target.detachPushpin) target.detachPushpin(world, "pinch");
if (isPinType(target.type) && target.pinState === "lodged" && target.detachPushpin) target.detachPushpin(world, "pinch");
target.dropTimer = 0;
target.dropMax = 0;
target.dropImpactDone = true;
@ -468,14 +517,19 @@ function bindUI() {
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);
if (uiCache.grabKind === "tarinai" && !p.inside) {
const raw = clientToWorldRaw(e.touches?.[0]?.clientX || uiCache.touchLastX || 0, e.touches?.[0]?.clientY || uiCache.touchLastY || 0);
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.state = "idle";
target.target = null;
target.sleeping = false;
target.goIdle?.("つままれている");
target.pinchThoughtTimer = Math.max(target.pinchThoughtTimer || 0, 0.8);
} else if (target.type === "ball") {
target.prevX = target.x - dx;
target.prevY = target.y - dy;
@ -513,6 +567,7 @@ function bindUI() {
target.target = null;
target.targetKey = "";
target.surpriseTimer = Math.max(target.surpriseTimer || 0, 0.15);
finishTarinaiGrab(target, e.changedTouches?.[0]?.clientX || uiCache.touchLastX || 0, e.changedTouches?.[0]?.clientY || uiCache.touchLastY || 0);
} else if (target.type === "ball") {
target.prevX = target.x;
target.prevY = target.y;
@ -585,16 +640,19 @@ function bindUI() {
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 = e.clientX;
uiCache.grabStartY = e.clientY;
if (uiCache.grabKind === "tarinai") {
target.sleeping = false;
if (target.state === "sleep" || target.state === "seek_bed") target.state = "idle";
if (target.state === "sleep" || target.state === "seek_bed") target.goIdle?.("つままれている");
target.surpriseTimer = Math.max(target.surpriseTimer || 0, 0.18);
target.thought = "つままれている";
target.pinchThoughtTimer = Math.max(target.pinchThoughtTimer || 0, 1.2);
} else {
if (target.type === "pushpin" && target.pinState === "lodged" && target.detachPushpin) target.detachPushpin(world, "pinch");
if (isPinType(target.type) && target.pinState === "lodged" && target.detachPushpin) target.detachPushpin(world, "pinch");
target.dropTimer = 0;
target.dropMax = 0;
target.dropImpactDone = true;
@ -628,6 +686,7 @@ function bindUI() {
target.target = null;
target.targetKey = "";
target.surpriseTimer = Math.max(target.surpriseTimer || 0, 0.15);
finishTarinaiGrab(target, e.clientX, e.clientY);
} else if (target.type === "ball") {
target.prevX = target.x;
target.prevY = target.y;
@ -679,14 +738,18 @@ function bindUI() {
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);
if (uiCache.grabKind === "tarinai" && !p.inside) {
const raw = clientToWorldRaw(e.clientX, e.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.state = "idle";
target.target = null;
target.sleeping = false;
target.goIdle?.("つままれている");
} else if (target.type === "ball") {
target.prevX = target.x - dx;
target.prevY = target.y - dy;