파일
project/src/lib/game/data.ts
T
kjs그리고Claude Fable 5 6225403290 튜토리얼: 촌장 냥이가 안내하는 10단계 온보딩
- 새 게임: 회관만 있는 빈 마을(고양이 0·자원 0)에서 시작
- 촌장 냥이(코숏 스프라이트) + 말풍선 가이드 오버레이 (진행도 n/10, 태스크 칩)
- 라인: 인사 → 첫 입양 → 부두 건설(무료·강물 위)·배치 → 둘째 입양 →
  벌목장 건설(생선 30)·배치 → 부엌 건설(목재 60) → 회관 Lv2 증축 → 작별
- 조건 충족 시 자동 진행, 인사·작별은 버튼. 완료 후 자유 운영(step 99)
- 부두·벌목장을 상점 구매 건물로 이동 (기존 세이브는 "보유 중"+튜토리얼 생략)
- 튜토리얼 입양 후보는 필요 적성(낚시→벌목) 보장

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 17:58:58 +09:00

293 라인
13 KiB
TypeScript
Raw Blame 히스토리

이 파일에는 모호한 유니코드 문자가 포함되어 있습니다
이 파일에는 다른 문자와 혼동될 수 있는 유니코드 문자가 포함되어 있습니다. 이것이 의도적인 것이라고 판단되면, 이 경고를 무시해도 됩니다. Escape 버튼을 눌러 보이지 않는 문자를 표시할 수 있습니다.
import type { AptitudeKey } from "./types";
// 적성 레벨별 초당 작업량 (기획서 4.4) — BASE_RATE로 전체 속도 스케일 조정
export const APTITUDE_RATE = [0, 1.0, 2.2, 4.8, 10.0, 21.0, 45.0];
export const BASE_RATE = 0.2;
// 품종별 스프라이트 폴더 (public/assets/cats/{폴더}/). 미등록 품종은 기본(오렌지 태비) 사용
export const CAT_SPRITE_DIR: Record<string, string> = {
korean_shorthair: "korean_shorthair",
munchkin: "munchkin",
norwegian_forest: "norwegian_forest",
bengal: "bengal",
russian_blue: "russian_blue",
persian: "persian",
sphynx: "sphynx",
maine_coon: "maine_coon",
};
export type CatFrame = "idle" | "walk1" | "walk2" | "work" | "cook" | "sleep";
export function catSprite(breed: string, frame: CatFrame): string {
const dir = CAT_SPRITE_DIR[breed];
return dir ? `/assets/cats/${dir}/${frame}.png` : `/assets/cats/${frame}.png`;
}
// 건물 일러스트 (public/assets/buildings/)
export const BUILDING_IMG: Record<string, string> = {
construction: "/assets/buildings/construction.png",
hall: "/assets/buildings/hall.png",
dock: "/assets/buildings/dock.png",
lumberyard: "/assets/buildings/lumberyard.png",
quarry: "/assets/buildings/quarry.png",
kitchen: "/assets/buildings/kitchen.png",
};
// 적성별 도구 일러스트 (public/assets/tools/)
export const TOOL_IMG: Record<AptitudeKey, string> = {
fishing: "/assets/tools/fishing.png",
logging: "/assets/tools/logging.png",
mining: "/assets/tools/mining.png",
foraging: "/assets/tools/foraging.png",
cooking: "/assets/tools/cooking.png",
crafting: "/assets/tools/crafting.png",
grooming: "/assets/tools/grooming.png",
};
export const APTITUDE_LABEL: Record<AptitudeKey, string> = {
fishing: "낚시",
logging: "벌목",
mining: "채굴",
foraging: "채집",
cooking: "요리",
crafting: "제작",
grooming: "손질",
};
export interface BreedDef {
key: string;
name: string;
emoji: string;
rarity: number; // 별 개수
weight: number; // 추첨 가중치
bias: AptitudeKey[]; // 적성 편향
aptitudeCap: number;
appetiteMod: number;
}
// 기획서 4.2 품종표
export const BREEDS: BreedDef[] = [
{ key: "korean_shorthair", name: "코리안 숏헤어", emoji: "🐱", rarity: 1, weight: 35, bias: [], aptitudeCap: 5, appetiteMod: -1 },
{ key: "munchkin", name: "먼치킨", emoji: "😺", rarity: 1, weight: 25, bias: ["foraging", "grooming"], aptitudeCap: 5, appetiteMod: 0 },
{ key: "norwegian_forest", name: "노르웨이 숲", emoji: "🐈", rarity: 2, weight: 15, bias: ["logging", "mining"], aptitudeCap: 5, appetiteMod: 1 },
{ key: "bengal", name: "뱅갈", emoji: "🐆", rarity: 2, weight: 10, bias: ["fishing", "foraging"], aptitudeCap: 5, appetiteMod: 0 },
{ key: "russian_blue", name: "러시안 블루", emoji: "🐈‍⬛", rarity: 2, weight: 8, bias: ["crafting", "cooking"], aptitudeCap: 5, appetiteMod: 0 },
{ key: "persian", name: "페르시안", emoji: "😸", rarity: 3, weight: 4, bias: ["cooking", "grooming"], aptitudeCap: 5, appetiteMod: 0 },
{ key: "sphynx", name: "스핑크스", emoji: "🐯", rarity: 3, weight: 2, bias: ["crafting", "mining"], aptitudeCap: 5, appetiteMod: 0 },
{ key: "maine_coon", name: "메인쿤", emoji: "🦁", rarity: 4, weight: 1, bias: ["logging", "mining"], aptitudeCap: 6, appetiteMod: 2 },
];
export interface PassiveDef {
key: string;
name: string;
kind: "positive" | "negative" | "tradeoff";
desc: string;
speedMul: number; // 1단계에서는 작업 속도 효과만 적용
}
// 기획서 5장 중 1단계에서 의미 있는 패시브 위주
export const PASSIVES: PassiveDef[] = [
{ key: "diligent", name: "부지런함", kind: "positive", desc: "작업 속도 +25%", speedMul: 1.25 },
{ key: "sturdy", name: "튼튼함", kind: "positive", desc: "체력 소모 -30%", speedMul: 1 },
{ key: "small_eater", name: "소식가", kind: "positive", desc: "식량 소모 -30%", speedMul: 1 },
{ key: "lucky", name: "복덩이", kind: "positive", desc: "확률적으로 냥코인 획득", speedMul: 1 },
{ key: "lazy", name: "게으름뱅이", kind: "negative", desc: "작업 속도 -30%", speedMul: 0.7 },
{ key: "big_eater", name: "대식가", kind: "negative", desc: "식량 소모 +50%", speedMul: 1 },
{ key: "workaholic", name: "일중독", kind: "tradeoff", desc: "작업 속도 +60%, 행복도 감소 +40%", speedMul: 1.6 },
{ key: "optimist", name: "낙천적", kind: "positive", desc: "행복도 감소 속도 -40%", speedMul: 1 },
{ key: "picky", name: "까다로움", kind: "negative", desc: "행복도 감소 속도 +50%", speedMul: 1 },
{ key: "popular", name: "인기쟁이", kind: "positive", desc: "같은 건물 동료의 행복도가 서서히 상승", speedMul: 1 },
];
// ---------- 마을 보드 ----------
import type { BuildingType } from "./types";
export const BOARD_COLS = 15;
export const BOARD_ROWS = 15;
export const BOARD_VERSION = 2; // 보드 개편 시 증가 → 세이브 위치 리셋
export const HALL_TILE = { i: 6, j: 3 };
export const TREE_TILES: Array<{ i: number; j: number }> = [
{ i: 0, j: 9 }, { i: 1, j: 11 }, { i: 3, j: 12 }, { i: 0, j: 13 },
{ i: 1, j: 8 }, { i: 4, j: 13 }, { i: 11, j: 1 }, { i: 2, j: 0 },
];
// 강은 동쪽 2열
export const isWaterTile = (i: number) => i >= BOARD_COLS - 2;
// 건물 풋프린트 (타일 단위, pos는 좌상단 앵커)
export const FOOTPRINTS: Record<string, { w: number; h: number }> = {
hall: { w: 3, h: 3 },
dock: { w: 2, h: 2 },
lumberyard: { w: 2, h: 2 },
quarry: { w: 2, h: 2 },
kitchen: { w: 2, h: 2 },
cat_house: { w: 2, h: 2 },
sun_deck: { w: 2, h: 2 },
};
export const footprintOf = (type: string) => FOOTPRINTS[type] ?? { w: 1, h: 1 };
// 배치 규칙: 부두는 물 위에만
export type PlacementRule = "land" | "onWater";
export const PLACEMENT_RULES: Partial<Record<BuildingType, PlacementRule>> = {
dock: "onWater",
};
// 보드 v2 기본 위치 (마이그레이션 시 재배치)
export const DEFAULT_BUILDING_POS: Record<string, { i: number; j: number }> = {
dock_1: { i: 13, j: 6 },
lumberyard_1: { i: 2, j: 9 },
kitchen_1: { i: 8, j: 8 },
quarry_1: { i: 1, j: 2 },
};
// ---------- 성장 ----------
export const EXP_PER_LEVEL = 90; // 레벨업 필요 경험치 = 레벨 × 90
export const APT_POINT_EVERY = 3; // 3레벨마다 적성 포인트 1
// ---------- 건설 가능한 건물 ----------
export interface BuildDef {
id: string; // 건물 인스턴스 id (단일 건설)
type: "dock" | "lumberyard" | "quarry" | "kitchen" | "cat_house" | "sun_deck";
kind?: "work" | "passive"; // passive: 고양이 배치 없음 (기본 work)
name: string;
emoji: string;
aptitude: AptitudeKey;
resource?: "fish" | "wood" | "stone";
maxSlots: number;
cost: Partial<Record<"fish" | "wood" | "stone", number>>;
reqHallLv: number;
buildTime: number; // 건설 소요 시간 (초)
desc: string;
}
export const BUILD_DEFS: BuildDef[] = [
{
id: "dock_1", type: "dock", name: "부두", emoji: "⚓",
aptitude: "fishing", resource: "fish", maxSlots: 3, cost: {}, reqHallLv: 1, buildTime: 15,
desc: "낚시 적성 고양이가 생선을 잡습니다. 강물 위에 지어요. 첫 건물이라 무료!",
},
{
id: "lumberyard_1", type: "lumberyard", name: "벌목장", emoji: "🪓",
aptitude: "logging", resource: "wood", maxSlots: 3, cost: { fish: 30 }, reqHallLv: 1, buildTime: 30,
desc: "벌목 적성 고양이가 목재를 모읍니다. 목재는 모든 건설의 기본!",
},
{
id: "kitchen_1", type: "kitchen", name: "부엌", emoji: "🍳",
aptitude: "cooking", maxSlots: 2, cost: { wood: 60 }, reqHallLv: 1, buildTime: 60,
desc: "요리 적성 고양이가 생선으로 요리를 만들고, 배고픈 고양이들이 알아서 먹으러 옵니다.",
},
{
id: "quarry_1", type: "quarry", name: "채석장", emoji: "⛏️",
aptitude: "mining", resource: "stone", maxSlots: 3, cost: { wood: 120 }, reqHallLv: 2, buildTime: 120,
desc: "채굴 적성 고양이가 돌을 캡니다. 돌은 회관 증축과 중급 건물에 필요합니다.",
},
{
id: "cat_house_1", type: "cat_house", name: "고양이 집", emoji: "🛖", kind: "passive",
aptitude: "grooming", maxSlots: 0, cost: { wood: 100, stone: 20 }, reqHallLv: 3, buildTime: 90,
desc: "지친 고양이가 푹 쉬는 보금자리. 휴식 회복이 빨라지고(분당 +30) 행복도 하락을 막아줍니다.",
},
{
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배 빨라집니다.",
},
];
// ---------- 꾸미기 (바닥 장식) ----------
export interface DecoDef {
key: string;
name: string;
cost: Partial<Record<"fish" | "wood" | "stone", number>>;
img: string;
}
export const DECO_DEFS: DecoDef[] = [
{ key: "dirt", name: "흙마당", cost: { wood: 10 }, img: "/assets/deco/dirt.png" },
{ key: "boulders", name: "바위 터", cost: { stone: 15 }, img: "/assets/deco/boulders.png" },
{ key: "pavement", name: "돌바닥", cost: { stone: 25 }, img: "/assets/deco/pavement.png" },
{ key: "deck", name: "나무 데크", cost: { wood: 30 }, img: "/assets/deco/deck.png" },
{ key: "pebbles", name: "자갈길", cost: { stone: 12 }, img: "/assets/deco/pebbles.png" },
{ key: "stump", name: "그루터기 쉼터", cost: { wood: 20 }, img: "/assets/deco/stump.png" },
{ key: "flower_grass", name: "꽃잔디", cost: { wood: 15 }, img: "/assets/deco/flower_grass.png" },
{ key: "plaza", name: "돌광장", cost: { stone: 30 }, img: "/assets/deco/plaza.png" },
{ key: "boardwalk", name: "나무길", cost: { wood: 25 }, img: "/assets/deco/boardwalk.png" },
];
// ---------- 요리 (부엌) ----------
export interface RecipeDef {
key: string;
name: string;
emoji: string;
input: Partial<Record<"fish" | "wood" | "stone", number>>; // 재료 (추후 획득처 확장)
work: number; // 요리 1개당 필요 작업량
fullness: number; // 먹었을 때 포만도 회복량
reqHallLv: number; // 해금 조건: 회관 레벨
reqKitchenLv: number; // 해금 조건: 부엌 레벨
}
export const RECIPES: RecipeDef[] = [
{
key: "grilled_fish", name: "물고기 구이", emoji: "🍖",
input: { fish: 2 }, work: 3, fullness: 40,
reqHallLv: 1, reqKitchenLv: 1,
},
{
key: "smoked_fish", name: "훈제 생선", emoji: "🐟",
input: { fish: 3 }, work: 6, fullness: 70,
reqHallLv: 3, reqKitchenLv: 2, // 아직 잠김 — 회관·부엌 증축으로 해금 예정
},
];
export const KITCHEN_MEAL_CAPS = [10, 15]; // 부엌 레벨별 선반 보관 수
export const KITCHEN_UPGRADE_COST = { wood: 150, stone: 60 }; // Lv1→2
export const mealCap = (kitchenLv: number) =>
KITCHEN_MEAL_CAPS[kitchenLv - 1] ?? KITCHEN_MEAL_CAPS[KITCHEN_MEAL_CAPS.length - 1];
export const EAT_THRESHOLD = 65; // 이 포만도 이하면 먹으러 감
export const FULLNESS_DECAY_PER_SEC = 0.03; // × 식욕 수치
// ---------- 체력·행복도 (Phase B) ----------
export const ENERGY_DRAIN_PER_SEC = 2 / 60; // 작업 중 체력 소모 (분당 2)
export const REST_RECOVER_PER_SEC = 20 / 60; // 휴식 회복 (분당 20)
export const HOUSE_RECOVER_PER_SEC = 30 / 60; // 고양이 집 휴식 회복 (분당 30)
export const IDLE_RECOVER_PER_SEC = 10 / 60; // 배회 중 자연 회복 (분당 10)
export const CAT_HOUSE_CAPACITY = 4; // 고양이 집 1채당 휴식 정원
export const STRIKE_THRESHOLD = 20; // 행복도가 이 밑이면 파업
export const STRIKE_RESUME = 40; // 이 이상 회복되면 복귀
// ---------- 마을 회관 증축 ----------
export interface HallLevelDef {
capacity: number;
cost: Partial<Record<"fish" | "wood" | "stone", number>>;
reqCat?: { level: number; count: number };
reqBuildings?: Array<{ type: "quarry" | "kitchen"; level?: number; label: string }>;
locked?: string; // 아직 구현 안 된 선행 요소 안내
}
export const HALL_LEVELS: Record<number, HallLevelDef> = {
2: { capacity: 5, cost: { wood: 100, fish: 80 }, reqCat: { level: 3, count: 1 } },
3: {
capacity: 8, cost: { wood: 250, stone: 150 },
reqCat: { level: 5, count: 2 },
reqBuildings: [
{ type: "quarry", label: "채석장 건설" },
{ type: "kitchen", level: 2, label: "부엌 Lv2" },
],
},
4: {
capacity: 12, cost: { stone: 400 }, reqCat: { level: 8, count: 3 },
locked: "목공소(목판)가 추가된 뒤 해금됩니다",
},
};
export const CAT_NAMES = [
"치즈", "나비", "콩이", "보리", "두부", "마루", "모찌", "호두",
"감자", "앙꼬", "밤이", "구름", "초코", "우유", "깨비", "솜이",
"달이", "별이", "쿠키", "만두", "생강", "참깨", "누룽지", "조약돌",
];