From 3cbad820a3ce7c539a9e9090fc63f22186d12d28 Mon Sep 17 00:00:00 2001 From: kjs Date: Fri, 14 Aug 2026 09:49:29 +0900 Subject: [PATCH] =?UTF-8?q?=ED=96=89=EB=B3=B5=EB=8F=84=20=EC=8B=9C?= =?UTF-8?q?=EC=8A=A4=ED=85=9C=20=ED=95=B4=EA=B8=88=EC=A0=9C:=20=ED=9A=8C?= =?UTF-8?q?=EA=B4=80=20Lv3=20+=20=ED=96=87=EB=B3=95=20=EB=8D=B0=ED=81=AC?= =?UTF-8?q?=20=EC=84=A4=EC=B9=98=20=EC=8B=9C=20=EA=B0=9C=EB=B0=A9=20(?= =?UTF-8?q?=EC=86=8C=ED=94=84=ED=8A=B8=EB=9D=BD=20=EC=88=98=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 해금 전: 행복도 증감·파업 로직 완전 비활성, 속도 배율 중립, UI에서 💗 숨김 → 초반에 행복도를 올릴 수단이 없어 파업 소프트락에 걸리던 문제 해결 - 해금 조건 달성 시 촌장 냥이가 재등장해 행복도 시스템 안내 (1회) - 해금 순간 전원 행복도 80으로 초기화 - 기존 세이브 마이그레이션: 조건 미충족이면 파업 해제 + 행복도 리셋, 이미 충족한 마을은 안내 생략 - 햇볕 데크 설명에 해금 역할 명시 Co-Authored-By: Claude Fable 5 --- src/components/game/Game.tsx | 35 ++++++++++++++++++++++ src/lib/game/data.ts | 2 +- src/lib/game/engine.ts | 56 ++++++++++++++++++++++++++++++++++-- src/lib/game/types.ts | 2 ++ 4 files changed, 91 insertions(+), 4 deletions(-) diff --git a/src/components/game/Game.tsx b/src/components/game/Game.tsx index 911b128..3321c3a 100644 --- a/src/components/game/Game.tsx +++ b/src/components/game/Game.tsx @@ -369,6 +369,36 @@ export default function Game() { ); })()} + {/* ── 촌장 냥이: 행복도 시스템 안내 (해금 시 1회) ── */} + {state.happinessUnlocked && + !state.happinessIntroSeen && + (state.tutorialStep ?? TUTORIAL_DONE) >= TUTORIAL_STEPS.length && ( +
+ {/* eslint-disable-next-line @next/next/no-img-element */} + 촌장 냥이 +
+
🎗️ 촌장 냥이
+

+ 오랜만이야! 마을이 근사해졌네. 햇볕 데크도 생겼으니 이제 + 친구들의 💗 행복도를 챙길 때야. 배부르게 먹고 푹 쉬면 + 행복해지고, 굶거나 고양이 집이 모자라면 시무룩해져. 행복도가 + 너무 낮으면 파업할 수도 있으니 잘 돌봐줘! 80 이상이면 + 일도 10% 빨라진단다. +

+ +
+
+ )} + {/* ── DB 상태 (좌하단 미니) ── */}
@@ -464,6 +494,7 @@ export default function Game() { key={cat.id} cat={cat} buildings={state.buildings} + happinessUnlocked={state.happinessUnlocked} onInvest={(catId, key) => run(() => engine.investAptitude(catId, key))} /> ))} @@ -1147,10 +1178,12 @@ function CatCard({ cat, buildings, onInvest, + happinessUnlocked, }: { cat: Cat; buildings: Building[]; onInvest: (catId: string, key: AptitudeKey) => void; + happinessUnlocked?: boolean; }) { const workplace = buildings.find((b) => b.id === cat.assignedTo); const expNeeded = cat.level * 90; @@ -1232,6 +1265,7 @@ function CatCard({ ⚡ {Math.floor(cat.status.energy)}/{cat.stats.stamina}
+ {happinessUnlocked && (
)}
+ )} {cat.passives.length > 0 && (
diff --git a/src/lib/game/data.ts b/src/lib/game/data.ts index 3c53527..ee03029 100644 --- a/src/lib/game/data.ts +++ b/src/lib/game/data.ts @@ -201,7 +201,7 @@ export const BUILD_DEFS: BuildDef[] = [ { id: "sun_deck_1", type: "sun_deck", name: "햇볕 데크", emoji: "🌞", kind: "passive", aptitude: "grooming", maxSlots: 0, cost: { wood: 120, stone: 40 }, reqHallLv: 3, buildTime: 90, - desc: "따끈한 햇살 아래 뒹굴뒹굴. 마을 전체의 행복도 회복이 1.5배 빨라집니다.", + desc: "따끈한 햇살 아래 뒹굴뒹굴. 설치하면 마을에 💗 행복도가 눈뜨고, 회복 속도도 1.5배!", }, { id: "market_1", type: "market", name: "장터", emoji: "🏪", kind: "passive", diff --git a/src/lib/game/engine.ts b/src/lib/game/engine.ts index 0d312de..91d5871 100644 --- a/src/lib/game/engine.ts +++ b/src/lib/game/engine.ts @@ -158,8 +158,9 @@ export function fullnessPenalty(cat: Cat): number { return 1; } -/** 행복도 배율: 80↑ +10%, 20~49 -30% */ +/** 행복도 배율: 80↑ +10%, 50 미만 -30% (시스템 해금 전엔 중립) */ export function happinessMul(cat: Cat): number { + if (!state?.happinessUnlocked) return 1; const h = cat.status.happiness; if (h >= 80) return 1.1; if (h < 50) return 0.7; @@ -306,6 +307,20 @@ export const engine = { state.decors ??= []; state.tutorialStep ??= 99; // 기존 마을은 튜토리얼 생략 state.coins ??= 0; + if (state.happinessUnlocked === undefined) { + const deckBuilt = state.buildings.some( + (b) => b.type === "sun_deck" && b.constructionUntil === undefined, + ); + state.happinessUnlocked = state.hall.level >= 3 && deckBuilt; + state.happinessIntroSeen = state.happinessUnlocked; // 기존 해금 유저는 안내 생략 + if (!state.happinessUnlocked) { + // 해금 전 소프트락 방지: 파업 해제 + 행복도 초기화 + for (const c of state.cats) { + if (c.state === "striking") c.state = null; + c.status.happiness = 80; + } + } + } // 보드 개편(크기·풋프린트 변경) 시 건물 위치를 새 기본값으로 재배치 if (state.boardVersion !== BOARD_VERSION) { for (const b of state.buildings) @@ -332,6 +347,12 @@ export const engine = { return r; }, + /** 행복도 안내 확인 처리 */ + markHappinessIntroSeen() { + if (!state) return; + state.happinessIntroSeen = true; + }, + /** 튜토리얼 진행 갱신 (단계 진입 시 촌장 냥이의 지원 지급) */ setTutorialStep(step: number) { if (!state) return; @@ -457,6 +478,18 @@ export const engine = { } } + // 행복도 시스템 해금: 회관 Lv3 + 햇볕 데크 완공 + if ( + !state.happinessUnlocked && + state.hall.level >= 3 && + state.buildings.some( + (b) => b.type === "sun_deck" && !isUnderConstruction(b), + ) + ) { + state.happinessUnlocked = true; + for (const c of state.cats) c.status.happiness = 80; // 산뜻하게 시작 + } + // ---------- 체력·휴식·행복도 (Phase B) ---------- const houses = state.buildings.filter( (b) => b.type === "cat_house" && !isUnderConstruction(b), @@ -500,7 +533,8 @@ export const engine = { const inHouse = houses.length > 0 && idx >= 0 && idx < houseCapacity; const recover = inHouse ? HOUSE_RECOVER_PER_SEC : REST_RECOVER_PER_SEC; st.energy = Math.min(cat.stats.stamina, st.energy + recover * dt); - st.happiness = Math.min(100, st.happiness + (3 / 60) * gainMul * dt); + if (state.happinessUnlocked) + st.happiness = Math.min(100, st.happiness + (3 / 60) * gainMul * dt); if (st.energy >= cat.stats.stamina) tryReturn(cat); } else if (cat.assignedTo) { st.energy = Math.max(0, st.energy - ENERGY_DRAIN_PER_SEC * sturdyMul * dt); @@ -519,7 +553,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) @@ -557,6 +592,7 @@ export const engine = { } if (cat.state === "striking" && st.happiness >= STRIKE_RESUME) tryReturn(cat); + } // happinessUnlocked } state.updatedAt = Date.now(); @@ -675,6 +711,20 @@ export const engine = { state.decors ??= []; state.tutorialStep ??= 99; // 기존 마을은 튜토리얼 생략 state.coins ??= 0; + if (state.happinessUnlocked === undefined) { + const deckBuilt = state.buildings.some( + (b) => b.type === "sun_deck" && b.constructionUntil === undefined, + ); + state.happinessUnlocked = state.hall.level >= 3 && deckBuilt; + state.happinessIntroSeen = state.happinessUnlocked; // 기존 해금 유저는 안내 생략 + if (!state.happinessUnlocked) { + // 해금 전 소프트락 방지: 파업 해제 + 행복도 초기화 + for (const c of state.cats) { + if (c.state === "striking") c.state = null; + c.status.happiness = 80; + } + } + } // 같은 타일의 기존 장식 제거 후 배치 state.decors = state.decors.filter( (d) => !(d.pos.i === pos.i && d.pos.j === pos.j), diff --git a/src/lib/game/types.ts b/src/lib/game/types.ts index d345f8b..66f38ee 100644 --- a/src/lib/game/types.ts +++ b/src/lib/game/types.ts @@ -73,6 +73,8 @@ export interface GameState { boardVersion?: number; // 보드 개편 마이그레이션 마커 tutorialStep?: number; // 튜토리얼 진행 (99 = 완료) coins?: number; // 냥코인 + happinessUnlocked?: boolean; // 행복도 시스템 해금 (회관 Lv3 + 햇볕 데크) + happinessIntroSeen?: boolean; // 촌장 냥이 행복도 안내를 봤는가 decors?: DecoTile[]; // 꾸미기 바닥 타일 resources: Record; cats: Cat[];