꾸미기 시스템: 바닥 장식 타일 9종 (deco.png)
- deco 시트 9분할 (흙마당·바위 터·돌바닥·나무 데크·자갈길·그루터기 쉼터· 꽃잔디·돌광장·나무길), 원본은 assets-src 보관 - 상점에 🌿 꾸미기 섹션 (3열 그리드, 여러 개 배치 가능) - 구매 → 배치 모드: 연속 배치 지원 (클릭마다 비용 차감, ESC로 종료), 같은 타일 재배치 시 교체, 건물·나무·물 타일 차단 - 정사각 탑뷰 아트를 아핀 변환으로 아이소 다이아몬드에 매핑해 바닥에 밀착 - 배치된 장식 클릭 → 패널에서 🗑 치우기 (건물 히트가 우선) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
이 커밋은 다음에 포함됨:
@@ -16,6 +16,7 @@ import {
|
||||
BREEDS,
|
||||
BUILD_DEFS,
|
||||
CAT_NAMES,
|
||||
DECO_DEFS,
|
||||
DEFAULT_BUILDING_POS,
|
||||
EAT_THRESHOLD,
|
||||
EXP_PER_LEVEL,
|
||||
@@ -227,6 +228,26 @@ export function canPlaceAt(
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 데코를 놓을 수 있는 타일인가 (잔디 위, 건물·나무·물 제외) */
|
||||
export function canPlaceDecoAt(s: GameState, i: number, j: number): boolean {
|
||||
if (!Number.isInteger(i) || !Number.isInteger(j)) return false;
|
||||
if (i < 0 || j < 0 || i >= BOARD_COLS || j >= BOARD_ROWS) return false;
|
||||
if (isWaterTile(i)) return false;
|
||||
if (TREE_TILES.some((t) => t.i === i && t.j === j)) return false;
|
||||
const hallFp = footprintOf("hall");
|
||||
if (
|
||||
i >= HALL_TILE.i && i < HALL_TILE.i + hallFp.w &&
|
||||
j >= HALL_TILE.j && j < HALL_TILE.j + hallFp.h
|
||||
)
|
||||
return false;
|
||||
for (const b of s.buildings) {
|
||||
const fp = footprintOf(b.type);
|
||||
if (i >= b.pos.i && i < b.pos.i + fp.w && j >= b.pos.j && j < b.pos.j + fp.h)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 현재 해금된 레시피 중 가장 마지막 것 (부엌이 요리할 메뉴) */
|
||||
export function activeRecipe(s: GameState): RecipeDef {
|
||||
const kitchenLv = s.buildings.find((b) => b.type === "kitchen")?.level ?? 1;
|
||||
@@ -269,6 +290,7 @@ export const engine = {
|
||||
}
|
||||
for (const b of state.buildings)
|
||||
b.pos ??= { ...(DEFAULT_BUILDING_POS[b.id] ?? { i: 4, j: 5 }) };
|
||||
state.decors ??= [];
|
||||
// 보드 개편(크기·풋프린트 변경) 시 건물 위치를 새 기본값으로 재배치
|
||||
if (state.boardVersion !== BOARD_VERSION) {
|
||||
for (const b of state.buildings)
|
||||
@@ -525,6 +547,37 @@ export const engine = {
|
||||
return null;
|
||||
},
|
||||
|
||||
/** 꾸미기 배치 (같은 타일의 기존 장식은 교체) */
|
||||
placeDeco(kind: string, pos: { i: number; j: number }): string | null {
|
||||
if (!state) return null;
|
||||
const def = DECO_DEFS.find((d) => d.key === kind);
|
||||
if (!def) return null;
|
||||
if (!canPlaceDecoAt(state, pos.i, pos.j)) return "그곳에는 놓을 수 없습니다";
|
||||
const missing = Object.entries(def.cost).filter(
|
||||
([key, amount]) =>
|
||||
state!.resources[key as keyof GameState["resources"]] < (amount ?? 0),
|
||||
);
|
||||
if (missing.length > 0)
|
||||
return `자원이 부족합니다 (${missing
|
||||
.map(([k, v]) => `${RES_LABEL[k] ?? k} ${v}`)
|
||||
.join(", ")})`;
|
||||
consume(state, def.cost);
|
||||
state.decors ??= [];
|
||||
// 같은 타일의 기존 장식 제거 후 배치
|
||||
state.decors = state.decors.filter(
|
||||
(d) => !(d.pos.i === pos.i && d.pos.j === pos.j),
|
||||
);
|
||||
state.decors.push({ id: nextId("deco"), kind, pos: { ...pos } });
|
||||
return null;
|
||||
},
|
||||
|
||||
/** 꾸미기 제거 (환불 없음) */
|
||||
removeDeco(id: string): string | null {
|
||||
if (!state?.decors) return null;
|
||||
state.decors = state.decors.filter((d) => d.id !== id);
|
||||
return null;
|
||||
},
|
||||
|
||||
/** 부엌 증축 Lv1→2: 훈제 생선 해금 조건 + 선반 확장 */
|
||||
upgradeKitchen(): string | null {
|
||||
if (!state) return null;
|
||||
|
||||
새 이슈에서 참조
사용자 차단