)}
+ )}
{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[];