From 1b438d392a7337fbd6dac6ea9a17a736f05415e8 Mon Sep 17 00:00:00 2001 From: kjs Date: Fri, 14 Aug 2026 11:25:59 +0900 Subject: [PATCH] =?UTF-8?q?=EC=86=8C=ED=94=84=ED=8A=B8=EB=9D=BD=20?= =?UTF-8?q?=EA=B0=90=EC=82=AC:=20=EC=9E=AC=EC=B6=94=EC=B2=A8=20=EC=9E=A5?= =?UTF-8?q?=ED=84=B0=20=ED=95=B4=EA=B8=88=20+=20=EC=A7=84=ED=96=89=20?= =?UTF-8?q?=EB=B6=88=EA=B0=80=20=EC=9C=84=ED=97=98=203=EA=B1=B4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 입양 재추첨 버튼: 장터 완공 전까지 숨김 (완공 후 해금) - 부엌 있는데 요리 가능 고양이가 없으면 입양 후보에 요리 적성 보장 - 서버 접속 불가 시에도 닉네임 결정 가능 (오프라인 시작 → 이후 자동 재등록) - 저장 403(미등록 닉네임) 시 재등록 후 재시도 - 파업 중 굶주림 행복도 하락 제외 + 자연 회복 +2/분 (전원 파업 데드락 방지) Co-Authored-By: Claude Fable 5 --- src/components/game/Game.tsx | 48 ++++++++++++++++++--------- src/components/game/NicknameModal.tsx | 8 +++-- src/lib/game/engine.ts | 6 +++- src/lib/game/sync.ts | 23 +++++++++---- 4 files changed, 60 insertions(+), 25 deletions(-) diff --git a/src/components/game/Game.tsx b/src/components/game/Game.tsx index 3ca14da..77b83c0 100644 --- a/src/components/game/Game.tsx +++ b/src/components/game/Game.tsx @@ -315,7 +315,10 @@ export default function Game() { 🐾 고양이 - + {rerollUnlocked && ( +
+ +
+ )}

한 마리만 선택할 수 있어요 · 패시브에 마우스를 올리면 상세 정보가 보입니다

diff --git a/src/components/game/NicknameModal.tsx b/src/components/game/NicknameModal.tsx index 39696b8..1c404e0 100644 --- a/src/components/game/NicknameModal.tsx +++ b/src/components/game/NicknameModal.tsx @@ -56,10 +56,12 @@ export default function NicknameModal({ onDone }: { onDone: () => void }) { }; }, [trimmed, mode]); - // 모드별 결정 가능 조건: 새 마을=사용 가능, 불러오기=이미 존재 + // 모드별 결정 가능 조건: 새 마을=사용 가능(서버 불가 시에도 허용), 불러오기=이미 존재 const canConfirm = !submitting && - (mode === "new" ? status === "available" : status === "taken"); + (mode === "new" + ? status === "available" || status === "offline" + : status === "taken"); const confirm = async () => { if (!canConfirm) return; @@ -96,7 +98,7 @@ export default function NicknameModal({ onDone }: { onDone: () => void }) { checking: { text: "다른 마을과 겹치는지 확인 중… 🐾", cls: "text-[#8a7551]" }, available: { text: "좋아, 이 이름은 비어 있어! ✨", cls: "text-[#5c8a2a]" }, taken: { text: "앗, 이미 다른 마을이 쓰고 있는 이름이야 🙀", cls: "text-[#b0472a]" }, - offline: { text: "서버에 닿지 않아… 잠시 후 다시 시도해줘", cls: "text-[#b0472a]" }, + offline: { text: "확인은 못 했지만 일단 시작할 수 있어! (나중에 자동 확인)", cls: "text-[#8a6316]" }, }, recover: { idle: { text: "예전에 쓰던 마을 이름을 알려줘!", cls: "text-[#8a7551]" }, diff --git a/src/lib/game/engine.ts b/src/lib/game/engine.ts index bcf80cd..ccae08f 100644 --- a/src/lib/game/engine.ts +++ b/src/lib/game/engine.ts @@ -598,7 +598,8 @@ export const engine = { if (state.happinessUnlocked) { if (st.fullness >= 80) st.happiness = Math.min(100, st.happiness + (1 / 60) * gainMul * dt); - if (st.fullness <= 0) + // 굶주림 하락 — 파업 중엔 제외 (회복이 하락을 못 이기면 영구 파업 데드락) + if (st.fullness <= 0 && cat.state !== "striking") st.happiness = Math.max(0, st.happiness - (4 / 60) * decayMul * dt); // 고양이 집 부족: 하락하되 이 요인만으로는 파업까지 가지 않도록 바닥 25 유지 if ( @@ -631,6 +632,9 @@ export const engine = { cat.assignedTo = null; cat.state = "striking"; } + // 파업 중엔 놀면서 서서히 기분이 풀림 (전원 파업 데드락 방지) + if (cat.state === "striking") + st.happiness = Math.min(100, st.happiness + (2 / 60) * dt); if (cat.state === "striking" && st.happiness >= STRIKE_RESUME) tryReturn(cat); } // happinessUnlocked diff --git a/src/lib/game/sync.ts b/src/lib/game/sync.ts index 095900e..4f380c8 100644 --- a/src/lib/game/sync.ts +++ b/src/lib/game/sync.ts @@ -38,16 +38,27 @@ export async function registerNickname( export async function pushSave(): Promise { const s = engine.peek(); if (!s?.nickname || !s.syncToken) return; + const body = JSON.stringify({ + nickname: s.nickname, + token: s.syncToken, + data: s, + }); try { - await fetch("/api/village/save", { + const res = await fetch("/api/village/save", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - nickname: s.nickname, - token: s.syncToken, - data: s, - }), + body, }); + if (res.status === 403) { + // 오프라인 상태에서 시작해 서버에 등록되지 않은 닉네임 — 재등록 후 재시도 + const ok = await registerNickname(s.nickname, s.syncToken); + if (ok) + await fetch("/api/village/save", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body, + }); + } } catch { // 오프라인이면 다음 주기에 재시도 }