"use strict"; // Two-creature leisure equipment. Seats are reserved independently and only // produce play/friendship while both ends are occupied. (function (global) { const num = global.TarinaiCoreHelpers?.finiteOr || ((v, fallback = 0) => Number.isFinite(Number(v)) ? Number(v) : fallback); const clamp = global.TarinaiCoreHelpers?.clampNumber || ((v, a, b) => Math.max(a, Math.min(b, Number(v) || 0))); function ensure(item) { if (!item) return null; if (!Array.isArray(item.seesawSeatIds)) item.seesawSeatIds = ["", ""]; if (item.seesawSeatIds.length < 2) item.seesawSeatIds = [item.seesawSeatIds[0] || "", item.seesawSeatIds[1] || ""]; item.angle = 0; item.seesawPhase = num(item.seesawPhase); item.seesawTilt = num(item.seesawTilt); return item; } function seatWorld(item, index = 0) { ensure(item); const side = index === 1 ? 1 : -1; const reach = Math.max(48, num(item?.r, 60) * 0.94); return { x: num(item?.x) + reach * side, y: num(item?.y) - 2, index: index === 1 ? 1 : 0 }; } function actorById(worldRef, id) { if (!id) return null; return worldRef?.liveTarinaiById?.(id) || (worldRef?.tarinai || []).find(t => t && !t.dead && t.id === id) || null; } function release(tarinai, item = null) { const board = item || tarinai?.target; if (board?.type === "seesaw") { ensure(board); for (let i = 0; i < 2; i += 1) if (board.seesawSeatIds[i] === tarinai?.id) board.seesawSeatIds[i] = ""; } if (tarinai) { tarinai.seesawItemId = ""; tarinai.seesawSeatIndex = -1; tarinai.seesawWaitStartedAt = -Infinity; tarinai.seesawPlayStartedAt = -Infinity; } return true; } function cleanReservations(item, worldRef) { ensure(item); for (let i = 0; i < 2; i += 1) { const actor = actorById(worldRef, item.seesawSeatIds[i]); if (!actor || actor.dead || actor.seesawItemId !== item.id || actor.seesawSeatIndex !== i || actor.state !== "play_seesaw") item.seesawSeatIds[i] = ""; } } function findAvailable(worldRef, tarinai, range = 560) { let best = null; let bestScore = Number(range) || 560; const list = worldRef?.itemsOfType?.("seesaw") || (worldRef?.items || []).filter(item => item?.type === "seesaw"); for (const item of list) { if (!item || item.dead) continue; cleanReservations(item, worldRef); for (let index = 0; index < 2; index += 1) { const reserved = item.seesawSeatIds[index]; if (reserved && reserved !== tarinai?.id) continue; const point = seatWorld(item, index); const d = Math.hypot(num(tarinai?.x) - point.x, num(tarinai?.y) - point.y); const hasPartner = Boolean(item.seesawSeatIds[1 - index]); const score = d - (hasPartner ? 90 : 0); if (score < bestScore) { bestScore = score; best = { item, seatIndex: index, point, hasPartner }; } } } return best; } function shouldSeek(worldRef, tarinai, needs = null) { const found = findAvailable(worldRef, tarinai, 620); if (!found) return false; const fulfill = num(needs?.fulfill ?? tarinai?.needs?.fulfill); const social = num(needs?.social ?? tarinai?.needs?.social); if (found.hasPartner) return Math.max(fulfill, social) >= 8; return fulfill >= 26 || social >= 40; } function actionPriority(worldRef, tarinai) { const found = findAvailable(worldRef, tarinai, 620); if (!found) return 0; return found.hasPartner ? 74 : 34; } function isPlaying(tarinai, worldRef = tarinai?.world) { const reservation = validReservation(tarinai, worldRef); if (!reservation) return false; const partnerId = reservation.item.seesawSeatIds[1 - reservation.index]; const partner = actorById(worldRef, partnerId); if (!partner || partner.dead || partner.state !== "play_seesaw") return false; const ownPoint = reservation.point; const partnerPoint = seatWorld(reservation.item, 1 - reservation.index); const ownArrived = Math.hypot(num(tarinai.x) - ownPoint.x, num(tarinai.y) - ownPoint.y) <= Math.max(28, num(tarinai.radius, 20) * 1.25); const partnerArrived = Math.hypot(num(partner.x) - partnerPoint.x, num(partner.y) - partnerPoint.y) <= Math.max(28, num(partner.radius, 20) * 1.25); return ownArrived && partnerArrived; } function reserve(item, tarinai, preferredIndex = null) { if (!item || item.dead || item.type !== "seesaw" || !tarinai || tarinai.dead) return -1; ensure(item); const candidates = preferredIndex === 0 || preferredIndex === 1 ? [preferredIndex, 1 - preferredIndex] : [0, 1]; for (const index of candidates) { if (!item.seesawSeatIds[index] || item.seesawSeatIds[index] === tarinai.id) { item.seesawSeatIds[index] = tarinai.id; tarinai.seesawItemId = item.id || ""; tarinai.seesawSeatIndex = index; tarinai.seesawWaitStartedAt = num(tarinai.world?.time, 0); tarinai.seesawPlayStartedAt = -Infinity; return index; } } return -1; } function start(tarinai, worldRef, item = null) { const found = item ? { item, seatIndex: null } : findAvailable(worldRef, tarinai, 620); const target = found?.item; if (!target) return false; cleanReservations(target, worldRef); const index = reserve(target, tarinai, found.seatIndex); if (index < 0) return false; tarinai.setActionState?.("play_seesaw", { target, reason: "\u30b7\u30fc\u30bd\u30fc\u3067\u904a\u307c\u3046\u3068\u3057\u3066\u3044\u308b", sleeping: false, wake: true }); tarinai.target = target; tarinai.thought = "\u30b7\u30fc\u30bd\u30fc\u3067\u904a\u307c\u3046\u3068\u3057\u3066\u3044\u308b"; return true; } function validReservation(tarinai, worldRef) { if (!tarinai || tarinai.dead || tarinai.state !== "play_seesaw") return null; const item = worldRef?.itemById?.(tarinai.seesawItemId) || (worldRef?.items || []).find(value => value && !value.dead && value.id === tarinai.seesawItemId); if (!item || item.type !== "seesaw") return null; cleanReservations(item, worldRef); const index = tarinai.seesawSeatIndex === 1 ? 1 : 0; if (item.seesawSeatIds[index] !== tarinai.id) return null; return { item, index, point: seatWorld(item, index) }; } function tickAction(tarinai, worldRef) { const reservation = validReservation(tarinai, worldRef); if (!reservation) return false; const d = Math.hypot(num(tarinai.x) - reservation.point.x, num(tarinai.y) - reservation.point.y); tarinai.thought = d > Math.max(22, num(tarinai.radius, 20)) ? "\u30b7\u30fc\u30bd\u30fc\u3078\u5411\u304b\u3063\u3066\u3044\u308b" : (reservation.item.seesawSeatIds[1 - reservation.index] ? "\u4e00\u7dd2\u306b\u30b7\u30fc\u30bd\u30fc\u3067\u904a\u3093\u3067\u3044\u308b" : "\u76f8\u624b\u3092\u5f85\u3063\u3066\u3044\u308b"); const now = num(worldRef?.time); if (Number.isFinite(tarinai.seesawPlayStartedAt) && tarinai.seesawPlayStartedAt > -Infinity) { if (now - tarinai.seesawPlayStartedAt >= 18) return "finished"; } else { const dayLength = Math.max(1, num(worldRef?.config?.dayLength ?? global.CONFIG?.dayLength, 120)); if (now - num(tarinai.seesawWaitStartedAt, now) >= Math.max(30, dayLength * 0.5)) return "finished"; } return true; } function updateWorld(worldRef, dt = 0.016) { if (!worldRef?.items) return false; const step = Math.max(0, Math.min(0.1, num(dt, 0.016))); const seesaws = worldRef.itemsOfType?.("seesaw") || worldRef.items.filter(item => item?.type === "seesaw"); for (const item of seesaws) { if (!item || item.dead) continue; ensure(item); cleanReservations(item, worldRef); const actors = [actorById(worldRef, item.seesawSeatIds[0]), actorById(worldRef, item.seesawSeatIds[1])]; const arrived = actors.map((actor, index) => { if (!actor) return false; const point = seatWorld(item, index); return Math.hypot(num(actor.x) - point.x, num(actor.y) - point.y) <= Math.max(28, num(actor.radius, 20) * 1.25); }); const playing = Boolean(actors[0] && actors[1] && arrived[0] && arrived[1]); const pairKey = playing ? `${actors[0].id}|${actors[1].id}` : ""; if (pairKey && item.seesawPairKey !== pairKey) { item.seesawPairKey = pairKey; const now = num(worldRef.time); actors[0].seesawPlayStartedAt = now; actors[1].seesawPlayStartedAt = now; } else if (!pairKey) item.seesawPairKey = ""; if (playing) item.seesawPhase += step * 4.6; const targetTilt = playing ? Math.sin(item.seesawPhase) * 0.16 : 0; item.seesawTilt += (targetTilt - item.seesawTilt) * Math.min(1, step * 8); for (let index = 0; index < 2; index += 1) { const actor = actors[index]; if (!actor || !arrived[index]) continue; const base = seatWorld(item, index); const side = index === 1 ? 1 : -1; const lift = Math.sin(item.seesawPhase) * 9 * side; actor.x = base.x; actor.y = base.y - 12 + (playing ? lift : 0); actor.vx = 0; actor.vy = 0; const actionText = playing ? "\u30b7\u30fc\u30bd\u30fc\u3092\u3057\u3066\u3044\u308b\u3002" : "\u76f8\u624b\u3092\u5f85\u3063\u3066\u3044\u308b"; actor.thought = actionText; if (playing && typeof global.setBehaviorText === "function") { const behavior = global.currentTarinaiBehavior?.(actor); if (behavior?.actionId === "play_seesaw" && behavior?.reason !== actionText) { global.setBehaviorText(actor, { need: "fulfill", subNeed: "social_play", actionId: "play_seesaw", actionLabel: actionText, reasonText: actionText, target: item, phase: "perform", source: behavior?.source || "need" }); } } } if (playing) { const [a, b] = actors; a.adjustRelation?.(b, step * 0.70, -step * 0.025, "seesaw_play"); b.adjustRelation?.(a, step * 0.70, -step * 0.025, "seesaw_play"); if (typeof global.applyNeedSatisfaction === "function") { global.applyNeedSatisfaction(a, { fulfill: step * 1.4, social: step * 0.55 }, "seesaw"); global.applyNeedSatisfaction(b, { fulfill: step * 1.4, social: step * 0.55 }, "seesaw"); } else { a.stress = clamp(num(a.stress) - step * 0.35, 0, 100); b.stress = clamp(num(b.stress) - step * 0.35, 0, 100); } item.seesawPlayTime = num(item.seesawPlayTime) + step; } } return true; } global.TarinaiSeesawSystem = Object.freeze({ ensure, seatWorld, findAvailable, shouldSeek, actionPriority, isPlaying, reserve, release, start, tickAction, updateWorld, validReservation }); })(typeof window !== "undefined" ? window : globalThis);