diff --git a/src/samples/slay-the-spire-like/data/desert/triggers/card-events.ts b/src/samples/slay-the-spire-like/data/desert/triggers/card-events.ts index a979c65..5329099 100644 --- a/src/samples/slay-the-spire-like/data/desert/triggers/card-events.ts +++ b/src/samples/slay-the-spire-like/data/desert/triggers/card-events.ts @@ -1,11 +1,10 @@ import { Triggers } from "@/samples/slay-the-spire-like/system/combat/triggers"; import { getCombatEntity } from "@/samples/slay-the-spire-like/system/combat/effects"; -import { getAdjacentItems } from "@/samples/slay-the-spire-like/system/grid-inventory"; -import { GameItemMeta } from "@/samples/slay-the-spire-like/system/progress"; import { EffectData } from "@/samples/slay-the-spire-like/system/types"; import getEffects from "../effect.csv"; +import { IRunContext } from "@/samples/slay-the-spire-like/system/combat/types"; -export function addCardEventTriggers(triggers: Triggers) { +export function addCardEventTriggers(triggers: Triggers, run: IRunContext) { const effects = getEffects(); function findEffect(id: string): EffectData { @@ -67,21 +66,21 @@ export function addCardEventTriggers(triggers: Triggers) { if (!card) return; const playedItemId = card.itemId; - const adjacent = getAdjacentItems( - ctx.game.value.inventory, - playedItemId, - ); - for (const [adjItemId] of adjacent) { + const adjacent = run.getNeighborItems(playedItemId); + for (const adjItemId of adjacent) { const adjEffects = ctx.game.value.player.itemEffects[adjItemId]; if (!adjEffects) continue; const burn = adjEffects.burnForEnergy; if (!burn || burn.stacks <= 0) continue; + const item = run.getItemData(adjItemId); + const maxUses = + item?.card.costType === "energy" ? item.card.costCount : 0; + const consumed = run.getConsumedUses(adjItemId); + const toConsume = Math.min(maxUses - consumed, burn.stacks); + + await run.setConsumedUsesAsync(adjItemId, consumed + toConsume); await ctx.game.produceAsync((draft) => { - const item = draft.inventory.items.get(adjItemId); - if (item) { - draft.inventory.items.delete(adjItemId); - } draft.player.energy += burn.stacks; delete draft.player.itemEffects[adjItemId]; }); diff --git a/src/samples/slay-the-spire-like/system/combat/effects.ts b/src/samples/slay-the-spire-like/system/combat/effects.ts index 7aba3e1..b50c95f 100644 --- a/src/samples/slay-the-spire-like/system/combat/effects.ts +++ b/src/samples/slay-the-spire-like/system/combat/effects.ts @@ -3,6 +3,7 @@ import { CombatGameContext, CombatState, EffectTable, + IRunContext, PlayerEntity, } from "./types"; import { @@ -12,8 +13,6 @@ import { EffectData, EffectTarget, } from "@/samples/slay-the-spire-like/system/types"; -import { GameItemMeta } from "@/samples/slay-the-spire-like/system/progress/types"; -import { GridInventory } from "@/samples/slay-the-spire-like/system/grid-inventory/types"; export function addEffect( effects: EffectTable, @@ -138,33 +137,32 @@ export function canPlayCard( costType: CardData["costType"], costCount: number, itemId: string, - inventory: GridInventory, + run: IRunContext, ): boolean { if (costType === "energy") { return player.energy >= costCount; } if (costType === "uses") { - const item = inventory.items.get(itemId); - if (!item || !item.meta) return false; - const depletion = item.meta.consumedUses ?? 0; - return depletion < costCount; + const item = run.getItemData(itemId); + if (!item) return false; + const maxUses = item?.card.costType === "energy" ? item.card.costCount : 0; + const consumed = run.getConsumedUses(itemId); + return consumed + costCount <= maxUses; } return true; } -export function payCardCost( +export async function payCardCost( player: PlayerEntity, costType: CardData["costType"], costCount: number, itemId: string, - inventory: GridInventory, -): void { + run: IRunContext, +): Promise { if (costType === "energy") { player.energy -= costCount; } else if (costType === "uses") { - const item = inventory.items.get(itemId); - if (item && item.meta) { - item.meta.consumedUses = (item.meta.consumedUses ?? 0) + costCount; - } + const consumed = run.getConsumedUses(itemId); + await run.setConsumedUsesAsync(itemId, consumed + costCount); } } diff --git a/src/samples/slay-the-spire-like/system/combat/prompts.ts b/src/samples/slay-the-spire-like/system/combat/prompts.ts index 26f8bf7..bff2944 100644 --- a/src/samples/slay-the-spire-like/system/combat/prompts.ts +++ b/src/samples/slay-the-spire-like/system/combat/prompts.ts @@ -1,43 +1,56 @@ import { createPromptDef } from "@/core/game"; -import {CombatGameContext} from "./types"; -import {canPlayCard} from "@/samples/slay-the-spire-like/system/combat/effects"; +import { CombatGameContext, IRunContext } from "./types"; +import { canPlayCard } from "@/samples/slay-the-spire-like/system/combat/effects"; export const prompts = { - mainAction: createPromptDef<[string, string?]>( - "main-action [targetId:string]", - "选择卡牌并指定目标" - ), + mainAction: createPromptDef<[string, string?]>( + "main-action [targetId:string]", + "选择卡牌并指定目标", + ), }; -export async function promptMainAction(game: CombatGameContext){ - return await game.prompt(prompts.mainAction, (cardId, targetId) => { - if(cardId === 'end-turn') return { - action: 'end-turn' as 'end-turn' - }; - - const exists = game.value.player.deck.regions.hand.childIds.includes(cardId); - if(!exists) throw `卡牌"${cardId}"不在手牌中`; - - const card = game.value.player.deck.cards[cardId]; - const {cardData, itemId} = card; - if(!canPlayCard(game.value.player, cardData.costType, cardData.costCount, itemId, game.value.inventory)){ - throw `无法支付卡牌"${cardId}"的费用`; - } - - const {targetType} = cardData; - if(targetType === 'single'){ - if(!targetId) throw `请指定目标`; - const target = game.value.enemies.find(e => e.id === targetId); - if(!target) throw `目标"${targetId}"不存在`; - if(!target.isAlive) throw `目标"${targetId}"已死亡`; - }else if(targetType === 'none'){ - if(targetId) throw `目标"${targetId}"无效`; - } - - return { - action: 'play' as 'play', - cardId, - targetId - }; - }); -} \ No newline at end of file +export async function promptMainAction( + game: CombatGameContext, + run: IRunContext, +) { + return await game.prompt(prompts.mainAction, (cardId, targetId) => { + if (cardId === "end-turn") + return { + action: "end-turn" as "end-turn", + }; + + const exists = + game.value.player.deck.regions.hand.childIds.includes(cardId); + if (!exists) throw `卡牌"${cardId}"不在手牌中`; + + const card = game.value.player.deck.cards[cardId]; + const { cardData, itemId } = card; + if ( + !canPlayCard( + game.value.player, + cardData.costType, + cardData.costCount, + itemId, + run, + ) + ) { + throw `无法支付卡牌"${cardId}"的费用`; + } + + const { targetType } = cardData; + if (targetType === "single") { + if (!targetId) throw `请指定目标`; + const target = game.value.enemies.find((e) => e.id === targetId); + if (!target) throw `目标"${targetId}"不存在`; + if (!target.isAlive) throw `目标"${targetId}"已死亡`; + } else if (targetType === "none") { + if (targetId) throw `目标"${targetId}"无效`; + } + + return { + action: "play" as "play", + cardId, + targetId, + }; + }); +} diff --git a/src/samples/slay-the-spire-like/system/combat/triggers.ts b/src/samples/slay-the-spire-like/system/combat/triggers.ts index 458ade0..7a0e3f4 100644 --- a/src/samples/slay-the-spire-like/system/combat/triggers.ts +++ b/src/samples/slay-the-spire-like/system/combat/triggers.ts @@ -1,4 +1,4 @@ -import { CombatGameContext } from "./types"; +import { CombatGameContext, IRunContext } from "./types"; import { addEntityEffect, addItemEffect, @@ -51,7 +51,7 @@ type TriggerTypes = { onIntentUpdate: { enemyId: string }; }; -export function createTriggers() { +export function createTriggers(run: IRunContext) { const triggers = { onCombatStart: createTrigger("onCombatStart"), onTurnStart: createTrigger("onTurnStart", async (ctx) => { @@ -89,7 +89,7 @@ export function createTriggers() { card.cardData.costType, card.cardData.costCount, card.itemId, - draft.inventory, + run, ); moveToRegion(card, regions.hand, regions.discardPile); onItemPlay(draft.player, card.itemId); @@ -176,11 +176,8 @@ export function createTriggers() { if (ctx.effect.lifecycle.startsWith("item")) { if (ctx.cardId) { const card = ctx.game.value.player.deck.cards[ctx.cardId]; - const nearby = getAdjacentItems( - ctx.game.value.inventory, - card.itemId, - ); - for (const itemId of nearby.keys()) { + const nearby = run.getNeighborItems(card.itemId); + for (const itemId of nearby) { await ctx.game.produceAsync((draft) => { addItemEffect(draft.player, itemId, ctx.effect, ctx.stacks); }); @@ -269,9 +266,12 @@ export function createTriggers() { return triggers; } export type Triggers = ReturnType; -export function createStartWith(build: (triggers: Triggers) => void) { - const triggers = createTriggers(); - build(triggers); +export function createStartWith( + build: (triggers: Triggers, run: IRunContext) => void, + run: IRunContext, +) { + const triggers = createTriggers(run); + build(triggers, run); return async function (game: CombatGameContext) { await triggers.onCombatStart.execute(game, {}); @@ -279,7 +279,7 @@ export function createStartWith(build: (triggers: Triggers) => void) { while (true) { await triggers.onTurnStart.execute(game, { entityKey: "player" }); while (true) { - const action = await promptMainAction(game); + const action = await promptMainAction(game, run); if (action.action === "end-turn") break; if (action.action === "play") { await triggers.onCardPlayed.execute(game, action); diff --git a/src/samples/slay-the-spire-like/system/combat/types.ts b/src/samples/slay-the-spire-like/system/combat/types.ts index 66a273f..eb0d790 100644 --- a/src/samples/slay-the-spire-like/system/combat/types.ts +++ b/src/samples/slay-the-spire-like/system/combat/types.ts @@ -2,10 +2,9 @@ import type { PlayerDeck } from "../deck/types"; import { EnemyData, IntentData, + ItemData, } 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; @@ -46,7 +45,6 @@ export type LootEntry = export type CombatState = { enemies: EnemyEntity[]; player: PlayerEntity; - inventory: GridInventory; phase: CombatPhase; turnNumber: number; @@ -55,5 +53,13 @@ export type CombatState = { loot: LootEntry[]; }; +export interface IRunContext { + getItemData(id: string): ItemData | null; + getNeighborItems(id: string): Iterable; + + getConsumedUses(id: string): number; + setConsumedUsesAsync(id: string, uses: number): Promise; +} + export type CombatGameContext = import("@/core/game").IGameContextExport; diff --git a/src/samples/slay-the-spire-like/system/progress/encounter.ts b/src/samples/slay-the-spire-like/system/progress/encounter.ts index 7beda7c..81f48cb 100644 --- a/src/samples/slay-the-spire-like/system/progress/encounter.ts +++ b/src/samples/slay-the-spire-like/system/progress/encounter.ts @@ -94,7 +94,6 @@ export function buildCombatState(runState: RunState): CombatState { return { enemies, player, - inventory: runState.inventory, phase: "playerTurn", turnNumber: 1, result: null,