- 새 게임: 회관만 있는 빈 마을(고양이 0·자원 0)에서 시작 - 촌장 냥이(코숏 스프라이트) + 말풍선 가이드 오버레이 (진행도 n/10, 태스크 칩) - 라인: 인사 → 첫 입양 → 부두 건설(무료·강물 위)·배치 → 둘째 입양 → 벌목장 건설(생선 30)·배치 → 부엌 건설(목재 60) → 회관 Lv2 증축 → 작별 - 조건 충족 시 자동 진행, 인사·작별은 버튼. 완료 후 자유 운영(step 99) - 부두·벌목장을 상점 구매 건물로 이동 (기존 세이브는 "보유 중"+튜토리얼 생략) - 튜토리얼 입양 후보는 필요 적성(낚시→벌목) 보장 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
293 라인
13 KiB
TypeScript
293 라인
13 KiB
TypeScript
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 = [
|
||
"치즈", "나비", "콩이", "보리", "두부", "마루", "모찌", "호두",
|
||
"감자", "앙꼬", "밤이", "구름", "초코", "우유", "깨비", "솜이",
|
||
"달이", "별이", "쿠키", "만두", "생강", "참깨", "누룽지", "조약돌",
|
||
];
|