205 lines
5.6 KiB
TypeScript
205 lines
5.6 KiB
TypeScript
import type { CellKey, GridInventory, InventoryItem } from '../grid-inventory/types';
|
|
import type { GameItemMeta } from '../progress/types';
|
|
import { createRegion, createRegionAxis } from '@/core/region';
|
|
import type { GameCard, GameCardMeta, PlayerDeck, DeckRegions } from './types';
|
|
|
|
/**
|
|
* Generates a unique card ID for a cell within an item.
|
|
*/
|
|
function generateCardId(itemId: string, cellIndex: number): string {
|
|
return `card-${itemId}-${cellIndex}`;
|
|
}
|
|
|
|
/**
|
|
* Collects all cell keys from an item's shape in a deterministic order.
|
|
* Iterates the shape grid row by row, left to right, top to bottom.
|
|
*/
|
|
function getItemCells(item: InventoryItem<GameItemMeta>): CellKey[] {
|
|
const cells: CellKey[] = [];
|
|
const { shape, transform } = item;
|
|
const { grid } = shape;
|
|
const { offset, rotation, flipX, flipY } = transform;
|
|
|
|
// Track local dimensions (may swap on rotation)
|
|
let localWidth = grid[0]?.length || 1;
|
|
let localHeight = grid.length;
|
|
|
|
for (let gy = 0; gy < grid.length; gy++) {
|
|
for (let gx = 0; gx < grid[gy].length; gx++) {
|
|
if (!grid[gy][gx]) continue;
|
|
|
|
// Start from grid coordinates
|
|
let x = gx;
|
|
let y = gy;
|
|
|
|
// Apply rotation (90 degree increments, clockwise)
|
|
const rotTimes = ((rotation % 4) + 4) % 4;
|
|
for (let r = 0; r < rotTimes; r++) {
|
|
const newX = localHeight - 1 - y;
|
|
const newY = x;
|
|
x = newX;
|
|
y = newY;
|
|
// Swap dimensions for next iteration
|
|
const tmp = localWidth;
|
|
localWidth = localHeight;
|
|
localHeight = tmp;
|
|
}
|
|
|
|
// Reset local dimensions for fresh computation per cell
|
|
localWidth = grid[0]?.length || 1;
|
|
localHeight = grid.length;
|
|
|
|
// Apply flips
|
|
if (flipX) {
|
|
x = -x;
|
|
}
|
|
if (flipY) {
|
|
y = -y;
|
|
}
|
|
|
|
// Apply offset
|
|
const finalX = x + offset.x;
|
|
const finalY = y + offset.y;
|
|
|
|
cells.push(`${finalX},${finalY}`);
|
|
}
|
|
}
|
|
|
|
return cells;
|
|
}
|
|
|
|
/**
|
|
* Creates a single card from an inventory item.
|
|
*
|
|
* @param itemId - The inventory item instance ID
|
|
* @param itemData - The CSV item data
|
|
* @param cellKey - The cell key ("x,y") this card represents
|
|
* @param cellIndex - Index of the cell for unique ID generation
|
|
*/
|
|
function createItemCard(
|
|
itemId: string,
|
|
itemData: GameItemMeta['itemData'],
|
|
cellKey: CellKey,
|
|
cellIndex: number
|
|
): GameCard {
|
|
const cardId = generateCardId(itemId, cellIndex);
|
|
|
|
return {
|
|
id: cardId,
|
|
regionId: '',
|
|
position: [],
|
|
sourceItemId: itemId,
|
|
itemData,
|
|
cellKey,
|
|
displayName: itemData.name,
|
|
description: itemData.desc,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a status card that does not correspond to any inventory item.
|
|
* Status cards represent temporary effects like wounds, stuns, etc.
|
|
*
|
|
* @param id - Unique identifier for the card instance
|
|
* @param displayName - Display name (e.g. "伤口", "眩晕")
|
|
* @param description - Card description / ability text
|
|
*/
|
|
function createStatusCard(
|
|
id: string,
|
|
displayName: string,
|
|
description: string
|
|
): GameCard {
|
|
return {
|
|
id,
|
|
regionId: '',
|
|
position: [],
|
|
sourceItemId: null,
|
|
itemData: null,
|
|
cellKey: null,
|
|
displayName,
|
|
description,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generates a complete player deck from the current inventory state.
|
|
*
|
|
* For each item in the inventory, N cards are generated where N is the
|
|
* number of cells the item occupies (shape.count).
|
|
*
|
|
* All generated cards are placed in the draw pile initially.
|
|
*
|
|
* @param inventory - The player's grid inventory
|
|
* @returns A PlayerDeck with all cards in the draw pile
|
|
*/
|
|
function generateDeckFromInventory(inventory: GridInventory<GameItemMeta>): PlayerDeck {
|
|
const cards: Record<string, GameCard> = {};
|
|
const drawPile: string[] = [];
|
|
|
|
for (const item of inventory.items.values()) {
|
|
const itemData = item.meta?.itemData;
|
|
if (!itemData) continue;
|
|
|
|
// Generate one card per occupied cell in the item's shape
|
|
const cellCount = item.shape.count;
|
|
const cells = getItemCells(item);
|
|
|
|
for (let i = 0; i < cellCount; i++) {
|
|
const cellKey = cells[i] ?? `${i},0`;
|
|
const card = createItemCard(item.id, itemData, cellKey, i);
|
|
cards[card.id] = card;
|
|
drawPile.push(card.id);
|
|
}
|
|
}
|
|
|
|
return {
|
|
cards,
|
|
drawPile,
|
|
hand: [],
|
|
discardPile: [],
|
|
exhaustPile: [],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates region definitions for deck management.
|
|
* Returns regions for draw pile, hand, discard pile, and exhaust pile.
|
|
*/
|
|
function createDeckRegions(): DeckRegions {
|
|
return {
|
|
drawPile: createRegion('drawPile', [
|
|
createRegionAxis('index', 0, 0), // unbounded
|
|
]),
|
|
hand: createRegion('hand', [
|
|
createRegionAxis('index', 0, 0),
|
|
]),
|
|
discardPile: createRegion('discardPile', [
|
|
createRegionAxis('index', 0, 0),
|
|
]),
|
|
exhaustPile: createRegion('exhaustPile', [
|
|
createRegionAxis('index', 0, 0),
|
|
]),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates an empty player deck structure.
|
|
*/
|
|
function createPlayerDeck(): PlayerDeck {
|
|
return {
|
|
cards: {},
|
|
drawPile: [],
|
|
hand: [],
|
|
discardPile: [],
|
|
exhaustPile: [],
|
|
};
|
|
}
|
|
|
|
export {
|
|
generateDeckFromInventory,
|
|
createStatusCard,
|
|
createDeckRegions,
|
|
createPlayerDeck,
|
|
generateCardId,
|
|
};
|