refactor(slay-the-spire-like): use IRunContext in combat system
Update the combat system to use `IRunContext` instead of passing raw inventory objects. This provides a cleaner abstraction for accessing item data and managing consumed uses. - Update `canPlayCard` and `payCardCost` to use `IRunContext` - Pass `IRunContext` through the trigger registration chain - Fix logic in `canPlayCard` for "uses" cost validation - Update combat effect tests to use a mock `IRunContext`
This commit is contained in:
parent
5019bc6324
commit
5e172c61bb
|
|
@ -1,12 +1,13 @@
|
|||
import { IRunContext } from "@/samples/slay-the-spire-like/system/combat/types";
|
||||
import { Triggers } from "@/samples/slay-the-spire-like/system/combat/triggers";
|
||||
import { addInstantEffectTriggers } from "./instant";
|
||||
import { addDamageTriggers } from "./damage";
|
||||
import { addTurnStartTriggers } from "./turn-start";
|
||||
import { addCardEventTriggers } from "./card-events";
|
||||
|
||||
export function addDesertTriggers(triggers: Triggers) {
|
||||
export function addDesertTriggers(triggers: Triggers, run: IRunContext) {
|
||||
addInstantEffectTriggers(triggers);
|
||||
addDamageTriggers(triggers);
|
||||
addTurnStartTriggers(triggers);
|
||||
addCardEventTriggers(triggers);
|
||||
addCardEventTriggers(triggers, run);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,9 +145,9 @@ export function canPlayCard(
|
|||
if (costType === "uses") {
|
||||
const item = run.getItemData(itemId);
|
||||
if (!item) return false;
|
||||
const maxUses = item?.card.costType === "energy" ? item.card.costCount : 0;
|
||||
const maxUses = item?.card.costType === "uses" ? item.card.costCount : 0;
|
||||
const consumed = run.getConsumedUses(itemId);
|
||||
return consumed + costCount <= maxUses;
|
||||
return consumed < maxUses;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,10 +17,14 @@ import type {
|
|||
CombatEntity,
|
||||
CombatState,
|
||||
EffectTable,
|
||||
IRunContext,
|
||||
PlayerEntity,
|
||||
EnemyEntity,
|
||||
} from "@/samples/slay-the-spire-like/system/combat/types";
|
||||
import type { EffectData } from "@/samples/slay-the-spire-like/system/types";
|
||||
import type {
|
||||
EffectData,
|
||||
ItemData,
|
||||
} from "@/samples/slay-the-spire-like/system/types";
|
||||
import type {
|
||||
CellKey,
|
||||
GridInventory,
|
||||
|
|
@ -30,6 +34,31 @@ import type { GameItemMeta } from "@/samples/slay-the-spire-like/system/progress
|
|||
import type { ParsedShape } from "@/samples/slay-the-spire-like/system/utils/parse-shape";
|
||||
import type { Transform2D } from "@/samples/slay-the-spire-like/system/utils/shape-collision";
|
||||
|
||||
function createRunContext(
|
||||
items: Map<string, InventoryItem<GameItemMeta>>,
|
||||
): IRunContext {
|
||||
return {
|
||||
getItemData(id: string): ItemData | null {
|
||||
const item = items.get(id);
|
||||
return item?.meta.itemData ?? null;
|
||||
},
|
||||
getNeighborItems(_id: string): Iterable<string> {
|
||||
return [];
|
||||
},
|
||||
getConsumedUses(id: string): number {
|
||||
const item = items.get(id);
|
||||
return item?.meta.consumedUses ?? 0;
|
||||
},
|
||||
setConsumedUsesAsync(id: string, uses: number): Promise<void> {
|
||||
const item = items.get(id);
|
||||
if (item?.meta) {
|
||||
item.meta.consumedUses = uses;
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createEffect(
|
||||
id: string,
|
||||
lifecycle: EffectData["lifecycle"],
|
||||
|
|
@ -517,8 +546,9 @@ describe("combat/effects", () => {
|
|||
const player = createPlayerEntity();
|
||||
player.energy = 3;
|
||||
const inventory = createInventory([]);
|
||||
const run = createRunContext(inventory.items);
|
||||
|
||||
const result = canPlayCard(player, "energy", 2, "any", inventory);
|
||||
const result = canPlayCard(player, "energy", 2, "any", run);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
|
@ -527,8 +557,9 @@ describe("combat/effects", () => {
|
|||
const player = createPlayerEntity();
|
||||
player.energy = 1;
|
||||
const inventory = createInventory([]);
|
||||
const run = createRunContext(inventory.items);
|
||||
|
||||
const result = canPlayCard(player, "energy", 2, "any", inventory);
|
||||
const result = canPlayCard(player, "energy", 2, "any", run);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
|
@ -537,8 +568,9 @@ describe("combat/effects", () => {
|
|||
const player = createPlayerEntity();
|
||||
const item = createItem("potion-1", "potion-card", "uses", 3, 1);
|
||||
const inventory = createInventory([item]);
|
||||
const run = createRunContext(inventory.items);
|
||||
|
||||
const result = canPlayCard(player, "uses", 3, "potion-1", inventory);
|
||||
const result = canPlayCard(player, "uses", 3, "potion-1", run);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
|
@ -547,8 +579,9 @@ describe("combat/effects", () => {
|
|||
const player = createPlayerEntity();
|
||||
const item = createItem("potion-1", "potion-card", "uses", 3, 3);
|
||||
const inventory = createInventory([item]);
|
||||
const run = createRunContext(inventory.items);
|
||||
|
||||
const result = canPlayCard(player, "uses", 3, "potion-1", inventory);
|
||||
const result = canPlayCard(player, "uses", 3, "potion-1", run);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
|
@ -556,8 +589,9 @@ describe("combat/effects", () => {
|
|||
it("should reject playing uses card when item not in inventory", () => {
|
||||
const player = createPlayerEntity();
|
||||
const inventory = createInventory([]);
|
||||
const run = createRunContext(inventory.items);
|
||||
|
||||
const result = canPlayCard(player, "uses", 1, "missing", inventory);
|
||||
const result = canPlayCard(player, "uses", 1, "missing", run);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
|
@ -566,51 +600,56 @@ describe("combat/effects", () => {
|
|||
const player = createPlayerEntity();
|
||||
player.energy = 0;
|
||||
const inventory = createInventory([]);
|
||||
const run = createRunContext(inventory.items);
|
||||
|
||||
const result = canPlayCard(player, "none", 0, "any", inventory);
|
||||
const result = canPlayCard(player, "none", 0, "any", run);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("payCardCost", () => {
|
||||
it("should deduct energy for energy cost card", () => {
|
||||
it("should deduct energy for energy cost card", async () => {
|
||||
const player = createPlayerEntity();
|
||||
player.energy = 3;
|
||||
const inventory = createInventory([]);
|
||||
const run = createRunContext(inventory.items);
|
||||
|
||||
payCardCost(player, "energy", 2, "any", inventory);
|
||||
await payCardCost(player, "energy", 2, "any", run);
|
||||
|
||||
expect(player.energy).toBe(1);
|
||||
});
|
||||
|
||||
it("should increment depletion for uses cost card", () => {
|
||||
it("should increment depletion for uses cost card", async () => {
|
||||
const player = createPlayerEntity();
|
||||
const item = createItem("potion-1", "potion-card", "uses", 3, 1);
|
||||
const inventory = createInventory([item]);
|
||||
const run = createRunContext(inventory.items);
|
||||
|
||||
payCardCost(player, "uses", 3, "potion-1", inventory);
|
||||
await payCardCost(player, "uses", 3, "potion-1", run);
|
||||
|
||||
expect(item.meta?.consumedUses).toBe(4);
|
||||
});
|
||||
|
||||
it("should do nothing for none cost card", () => {
|
||||
it("should do nothing for none cost card", async () => {
|
||||
const player = createPlayerEntity();
|
||||
player.energy = 3;
|
||||
const inventory = createInventory([]);
|
||||
const run = createRunContext(inventory.items);
|
||||
|
||||
payCardCost(player, "none", 0, "any", inventory);
|
||||
await payCardCost(player, "none", 0, "any", run);
|
||||
|
||||
expect(player.energy).toBe(3);
|
||||
});
|
||||
|
||||
it("should handle missing item gracefully for uses cost", () => {
|
||||
it("should handle missing item gracefully for uses cost", async () => {
|
||||
const player = createPlayerEntity();
|
||||
const inventory = createInventory([]);
|
||||
const run = createRunContext(inventory.items);
|
||||
|
||||
expect(() =>
|
||||
payCardCost(player, "uses", 1, "missing", inventory),
|
||||
).not.toThrow();
|
||||
await expect(
|
||||
payCardCost(player, "uses", 1, "missing", run),
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { addTriggers } from "@/samples/slay-the-spire-like/data/desert/triggers"
|
|||
import {
|
||||
CombatState,
|
||||
EnemyEntity,
|
||||
IRunContext,
|
||||
} from "@/samples/slay-the-spire-like/system/combat/types";
|
||||
import { EffectData } from "@/samples/slay-the-spire-like/system/types";
|
||||
import {
|
||||
|
|
@ -138,7 +139,6 @@ function createCombatState(overrides: Partial<CombatState> = {}): CombatState {
|
|||
itemEffects: {},
|
||||
},
|
||||
enemies: [],
|
||||
inventory: createInventory([]),
|
||||
phase: "playerTurn",
|
||||
turnNumber: 1,
|
||||
result: null,
|
||||
|
|
@ -155,8 +155,18 @@ function createTestContext(state?: CombatState): IGameContext<CombatState> {
|
|||
}
|
||||
|
||||
function getTriggers(): Triggers {
|
||||
const triggers = createTriggers();
|
||||
addTriggers(triggers);
|
||||
const run: IRunContext = {
|
||||
getConsumedUses() {
|
||||
return 0;
|
||||
},
|
||||
getItemData() {
|
||||
return null;
|
||||
},
|
||||
*getNeighborItems() {},
|
||||
async setConsumedUsesAsync() {},
|
||||
};
|
||||
const triggers = createTriggers(run);
|
||||
addTriggers(triggers, run);
|
||||
return triggers;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue