refactor: type rewrite
This commit is contained in:
@@ -41,4 +41,9 @@ export function onPlayerItemEffectUpkeep(entity: PlayerEntity){
|
||||
addItemEffect(entity, itemKey, effect.data, -effect.stacks);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
export function onDraw(entity: PlayerEntity, cardId:string ){}
|
||||
// TODO
|
||||
export function onDiscard(entity: PlayerEntity, cardId: string){}
|
||||
|
||||
@@ -2,6 +2,7 @@ import {createMiddlewareChain} from "../utils/middleware";
|
||||
import {CombatGameContext} from "./types";
|
||||
import {getAliveEnemies} from "@/samples/slay-the-spire-like/system/combat/utils";
|
||||
import {
|
||||
onDiscard, onDraw,
|
||||
onEntityEffectUpkeep,
|
||||
onPlayerItemEffectUpkeep
|
||||
} from "@/samples/slay-the-spire-like/system/combat/effects";
|
||||
@@ -12,7 +13,7 @@ type TriggerTypes = {
|
||||
onTurnStart: { entityKey: "player" | string, },
|
||||
onTurnEnd: { entityKey: "player" | string, },
|
||||
onShuffle: { entityKey: "player" | string, },
|
||||
onCardPlayed: { cardId: string, },
|
||||
onCardPlayed: { cardId: string, targetId?: string },
|
||||
onCardDiscarded: { cardId: string, },
|
||||
onCardDrawn: { cardId: string, },
|
||||
onEffectApplied: { effectId: string, entityKey: "player" | string, stacks: number, },
|
||||
@@ -37,6 +38,8 @@ export function createStartWith(build: (triggers: Triggers) => void){
|
||||
return async function(game: CombatGameContext){
|
||||
await triggers.onCombatStart.execute(game,{});
|
||||
|
||||
// TODO at the end of a damage effect, if win/loss is achieved, break the loop with a throw
|
||||
// catch the throw and return the result here
|
||||
while(true){
|
||||
await triggers.onTurnStart.execute(game,{entityKey: "player"});
|
||||
await game.produceAsync(draft => {
|
||||
@@ -46,11 +49,23 @@ export function createStartWith(build: (triggers: Triggers) => void){
|
||||
while(true){
|
||||
const action = await promptMainAction(game);
|
||||
if(action.action === "end-turn") break;
|
||||
//TODO resolve action here
|
||||
if(action.action === "play"){
|
||||
await game.produceAsync(draft => onDiscard(draft.player, action.cardId));
|
||||
await triggers.onCardPlayed.execute(game, action);
|
||||
}
|
||||
}
|
||||
for(const cardId of [...game.value.player.deck.hand]){
|
||||
await game.produceAsync(draft => onDiscard(draft.player, cardId));
|
||||
await triggers.onCardDiscarded.execute(game,{cardId});
|
||||
}
|
||||
// TODO discard cards here
|
||||
await triggers.onTurnEnd.execute(game,{entityKey: "player"});
|
||||
// TODO recover energy, draw new cards here
|
||||
await game.produceAsync(draft => draft.player.energy = draft.player.maxEnergy);
|
||||
for(let i = 0; i < 5; i++){
|
||||
const cardId = game.value.player.deck.drawPile[0]; // TODO: should this be drawPile[-1] ?
|
||||
if(!cardId) break;
|
||||
await game.produceAsync(draft => onDraw(draft.player, cardId));
|
||||
await triggers.onCardDrawn.execute(game,{cardId});
|
||||
}
|
||||
|
||||
for(const enemy of getAliveEnemies(game.value)){
|
||||
await triggers.onTurnStart.execute(game,{entityKey: enemy.id});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { PlayerDeck } from "../deck/types";
|
||||
import {EnemyData, IntentData} from "@/samples/slay-the-spire-like/system/types";
|
||||
import {EffectData} from "@/samples/slay-the-spire-like/system/types";
|
||||
import {GridInventory} from "@/samples/slay-the-spire-like/system/grid-inventory";
|
||||
import {GameItemMeta} from "@/samples/slay-the-spire-like/system/progress";
|
||||
|
||||
export type EffectTable = Record<string, {data: EffectData, stacks: number}>;
|
||||
|
||||
@@ -39,6 +41,7 @@ export type LootEntry = {
|
||||
export type CombatState = {
|
||||
enemies: EnemyEntity[];
|
||||
player: PlayerEntity;
|
||||
inventory: GridInventory<GameItemMeta>;
|
||||
|
||||
phase: CombatPhase;
|
||||
turnNumber: number;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {CombatState} from "@/samples/slay-the-spire-like/system";
|
||||
import {CombatState} from "./types";
|
||||
|
||||
export function* getAliveEnemies(state: CombatState) {
|
||||
for (let enemy of state.enemies) {
|
||||
|
||||
@@ -1,188 +1,66 @@
|
||||
import type { CellKey, GridInventory, InventoryItem } from '../grid-inventory/types';
|
||||
import {moveToRegion } from '@/core/region';
|
||||
import { createRegion } from '@/core/region';
|
||||
import type { GridInventory } from '../grid-inventory/types';
|
||||
import type { GameItemMeta } from '../progress/types';
|
||||
import { createRegion, createRegionAxis } from '@/core/region';
|
||||
import type { GameCard, GameCardMeta, PlayerDeck, DeckRegions } from './types';
|
||||
import { cardDesertData } from '../data';
|
||||
import type { CardData } from '../types';
|
||||
import type {DeckRegions, GameCard, PlayerDeck} 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.
|
||||
*/
|
||||
function createItemCard(
|
||||
itemId: string,
|
||||
itemData: GameItemMeta['itemData'],
|
||||
cellKey: CellKey,
|
||||
cellIndex: number
|
||||
): GameCard {
|
||||
const cardId = generateCardId(itemId, cellIndex);
|
||||
const cardData = cardDesertData.find(c => c.id === itemData.card.id);
|
||||
|
||||
function createCard( itemId: string, cardData: CardData, cellIndex: number): GameCard {
|
||||
return {
|
||||
id: cardId,
|
||||
id: generateCardId(itemId, cellIndex),
|
||||
regionId: '',
|
||||
position: [],
|
||||
sourceItemId: itemId,
|
||||
itemData: cardData ?? null,
|
||||
cellKey,
|
||||
displayName: cardData?.name ?? itemData.name,
|
||||
description: cardData?.desc ?? '',
|
||||
itemId,
|
||||
cardData
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a status card that does not correspond to any inventory item.
|
||||
* Status cards represent temporary effects like wounds, stuns, etc.
|
||||
*/
|
||||
function createStatusCard(
|
||||
id: string,
|
||||
displayName: string,
|
||||
description: string
|
||||
): GameCard {
|
||||
function createDeckRegions(): DeckRegions {
|
||||
return {
|
||||
id,
|
||||
regionId: '',
|
||||
position: [],
|
||||
sourceItemId: null,
|
||||
itemData: null,
|
||||
cellKey: null,
|
||||
displayName,
|
||||
description,
|
||||
drawPile: createRegion('drawPile', []),
|
||||
hand: createRegion('hand', []),
|
||||
discardPile: createRegion('discardPile', []),
|
||||
exhaustPile: createRegion('exhaustPile', []),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a complete player deck from the current inventory state.
|
||||
*/
|
||||
function generateDeckFromInventory(inventory: GridInventory<GameItemMeta>): PlayerDeck {
|
||||
const cards: Record<string, GameCard> = {};
|
||||
const drawPile: string[] = [];
|
||||
const regions = createDeckRegions();
|
||||
|
||||
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);
|
||||
const count = item.shape.count;
|
||||
for (let i = 0; i < count; i++) {
|
||||
const card = createCard(item.id, itemData.card, i);
|
||||
cards[card.id] = card;
|
||||
drawPile.push(card.id);
|
||||
moveToRegion(card, null, regions.drawPile);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
cards,
|
||||
drawPile,
|
||||
hand: [],
|
||||
discardPile: [],
|
||||
exhaustPile: [],
|
||||
regions
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates region definitions for deck management.
|
||||
*/
|
||||
function createDeckRegions(): DeckRegions {
|
||||
return {
|
||||
drawPile: createRegion('drawPile', [
|
||||
createRegionAxis('index', 0, 0),
|
||||
]),
|
||||
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: [],
|
||||
regions: createDeckRegions(),
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
generateDeckFromInventory,
|
||||
createStatusCard,
|
||||
createDeckRegions,
|
||||
createCard,
|
||||
createPlayerDeck,
|
||||
createDeckRegions,
|
||||
generateCardId,
|
||||
};
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
export type { GameCard, GameCardMeta, PlayerDeck, DeckRegions } from './types';
|
||||
export {
|
||||
generateDeckFromInventory,
|
||||
createStatusCard,
|
||||
createDeckRegions,
|
||||
createCard,
|
||||
createPlayerDeck,
|
||||
generateCardId,
|
||||
} from './factory';
|
||||
|
||||
@@ -1,40 +1,14 @@
|
||||
import type { Part } from '@/core/part';
|
||||
import type { Region } from '@/core/region';
|
||||
import type { HeroItemFighter1 } from '../data/heroItemFighter1.csv';
|
||||
import type { CardDesert } from '../data/cardDesert.csv';
|
||||
import type { CellKey } from '../grid-inventory/types';
|
||||
import {CardData} from "@/samples/slay-the-spire-like/system/types";
|
||||
import {Region} from "@/core/region";
|
||||
|
||||
/**
|
||||
* Metadata for a game card.
|
||||
* Bridges inventory item data with the card system.
|
||||
*/
|
||||
export interface GameCardMeta {
|
||||
/**
|
||||
* Source item instance ID that this card was generated from.
|
||||
* `null` for status cards (e.g. wound, stun) that don't correspond to an inventory item.
|
||||
*/
|
||||
sourceItemId: string | null;
|
||||
/**
|
||||
* Original card data from cardDesert.csv. `null` for status cards not in the CSV.
|
||||
*/
|
||||
itemData: CardDesert | null;
|
||||
/**
|
||||
* The cell key ("x,y") this card represents within the source item's shape.
|
||||
* `null` for status cards.
|
||||
*/
|
||||
cellKey: CellKey | null;
|
||||
/**
|
||||
* Display name of the card.
|
||||
* For item cards: derived from itemData.name.
|
||||
* For status cards: custom name (e.g. "伤口", "眩晕").
|
||||
*/
|
||||
displayName: string;
|
||||
/**
|
||||
* Card description / ability text.
|
||||
* For item cards: derived from itemData.desc.
|
||||
* For status cards: custom description.
|
||||
*/
|
||||
description: string;
|
||||
cardData: CardData;
|
||||
itemId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,26 +23,17 @@ export type GameCard = Part<GameCardMeta>;
|
||||
export interface PlayerDeck {
|
||||
/** All cards indexed by ID */
|
||||
cards: Record<string, GameCard>;
|
||||
/** Card IDs in the draw pile */
|
||||
drawPile: string[];
|
||||
/** Card IDs in the player's hand */
|
||||
hand: string[];
|
||||
/** Card IDs in the discard pile */
|
||||
discardPile: string[];
|
||||
/** Card IDs in the exhaust pile (removed from combat) */
|
||||
exhaustPile: string[];
|
||||
|
||||
regions: DeckRegions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Region structure for deck management.
|
||||
*/
|
||||
export interface DeckRegions {
|
||||
/** Draw pile region */
|
||||
export interface DeckRegions{
|
||||
/** Card IDs in the draw pile */
|
||||
drawPile: Region;
|
||||
/** Hand region */
|
||||
/** Card IDs in the player's hand */
|
||||
hand: Region;
|
||||
/** Discard pile region */
|
||||
/** Card IDs in the discard pile */
|
||||
discardPile: Region;
|
||||
/** Exhaust pile region */
|
||||
/** Card IDs in the exhaust pile (removed from combat) */
|
||||
exhaustPile: Region;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { PointCrawlMap } from '../map/types';
|
||||
import type { GridInventory, InventoryItem } from '../grid-inventory/types';
|
||||
import type { ParsedShape } from '../utils/parse-shape';
|
||||
import type { HeroItemFighter1 } from '../data/heroItemFighter1.csv';
|
||||
import {ItemData} from "@/samples/slay-the-spire-like/system/types";
|
||||
|
||||
/**
|
||||
* Result of an encounter (combat, event, etc.).
|
||||
@@ -35,9 +35,11 @@ export interface EncounterState {
|
||||
*/
|
||||
export interface GameItemMeta {
|
||||
/** Original CSV item data */
|
||||
itemData: HeroItemFighter1;
|
||||
itemData: ItemData;
|
||||
/** Parsed shape for grid placement */
|
||||
shape: ParsedShape;
|
||||
/** Consumed uses, if card cost type is uses**/
|
||||
depletion?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user