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 { Triggers } from "@/samples/slay-the-spire-like/system/combat/triggers";
|
||||||
import { addInstantEffectTriggers } from "./instant";
|
import { addInstantEffectTriggers } from "./instant";
|
||||||
import { addDamageTriggers } from "./damage";
|
import { addDamageTriggers } from "./damage";
|
||||||
import { addTurnStartTriggers } from "./turn-start";
|
import { addTurnStartTriggers } from "./turn-start";
|
||||||
import { addCardEventTriggers } from "./card-events";
|
import { addCardEventTriggers } from "./card-events";
|
||||||
|
|
||||||
export function addDesertTriggers(triggers: Triggers) {
|
export function addDesertTriggers(triggers: Triggers, run: IRunContext) {
|
||||||
addInstantEffectTriggers(triggers);
|
addInstantEffectTriggers(triggers);
|
||||||
addDamageTriggers(triggers);
|
addDamageTriggers(triggers);
|
||||||
addTurnStartTriggers(triggers);
|
addTurnStartTriggers(triggers);
|
||||||
addCardEventTriggers(triggers);
|
addCardEventTriggers(triggers, run);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -145,9 +145,9 @@ export function canPlayCard(
|
||||||
if (costType === "uses") {
|
if (costType === "uses") {
|
||||||
const item = run.getItemData(itemId);
|
const item = run.getItemData(itemId);
|
||||||
if (!item) return false;
|
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);
|
const consumed = run.getConsumedUses(itemId);
|
||||||
return consumed + costCount <= maxUses;
|
return consumed < maxUses;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,14 @@ import type {
|
||||||
CombatEntity,
|
CombatEntity,
|
||||||
CombatState,
|
CombatState,
|
||||||
EffectTable,
|
EffectTable,
|
||||||
|
IRunContext,
|
||||||
PlayerEntity,
|
PlayerEntity,
|
||||||
EnemyEntity,
|
EnemyEntity,
|
||||||
} from "@/samples/slay-the-spire-like/system/combat/types";
|
} 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 {
|
import type {
|
||||||
CellKey,
|
CellKey,
|
||||||
GridInventory,
|
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 { ParsedShape } from "@/samples/slay-the-spire-like/system/utils/parse-shape";
|
||||||
import type { Transform2D } from "@/samples/slay-the-spire-like/system/utils/shape-collision";
|
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(
|
function createEffect(
|
||||||
id: string,
|
id: string,
|
||||||
lifecycle: EffectData["lifecycle"],
|
lifecycle: EffectData["lifecycle"],
|
||||||
|
|
@ -517,8 +546,9 @@ describe("combat/effects", () => {
|
||||||
const player = createPlayerEntity();
|
const player = createPlayerEntity();
|
||||||
player.energy = 3;
|
player.energy = 3;
|
||||||
const inventory = createInventory([]);
|
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);
|
expect(result).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
@ -527,8 +557,9 @@ describe("combat/effects", () => {
|
||||||
const player = createPlayerEntity();
|
const player = createPlayerEntity();
|
||||||
player.energy = 1;
|
player.energy = 1;
|
||||||
const inventory = createInventory([]);
|
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);
|
expect(result).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
@ -537,8 +568,9 @@ describe("combat/effects", () => {
|
||||||
const player = createPlayerEntity();
|
const player = createPlayerEntity();
|
||||||
const item = createItem("potion-1", "potion-card", "uses", 3, 1);
|
const item = createItem("potion-1", "potion-card", "uses", 3, 1);
|
||||||
const inventory = createInventory([item]);
|
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);
|
expect(result).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
@ -547,8 +579,9 @@ describe("combat/effects", () => {
|
||||||
const player = createPlayerEntity();
|
const player = createPlayerEntity();
|
||||||
const item = createItem("potion-1", "potion-card", "uses", 3, 3);
|
const item = createItem("potion-1", "potion-card", "uses", 3, 3);
|
||||||
const inventory = createInventory([item]);
|
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);
|
expect(result).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
@ -556,8 +589,9 @@ describe("combat/effects", () => {
|
||||||
it("should reject playing uses card when item not in inventory", () => {
|
it("should reject playing uses card when item not in inventory", () => {
|
||||||
const player = createPlayerEntity();
|
const player = createPlayerEntity();
|
||||||
const inventory = createInventory([]);
|
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);
|
expect(result).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
@ -566,51 +600,56 @@ describe("combat/effects", () => {
|
||||||
const player = createPlayerEntity();
|
const player = createPlayerEntity();
|
||||||
player.energy = 0;
|
player.energy = 0;
|
||||||
const inventory = createInventory([]);
|
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);
|
expect(result).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("payCardCost", () => {
|
describe("payCardCost", () => {
|
||||||
it("should deduct energy for energy cost card", () => {
|
it("should deduct energy for energy cost card", async () => {
|
||||||
const player = createPlayerEntity();
|
const player = createPlayerEntity();
|
||||||
player.energy = 3;
|
player.energy = 3;
|
||||||
const inventory = createInventory([]);
|
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);
|
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 player = createPlayerEntity();
|
||||||
const item = createItem("potion-1", "potion-card", "uses", 3, 1);
|
const item = createItem("potion-1", "potion-card", "uses", 3, 1);
|
||||||
const inventory = createInventory([item]);
|
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);
|
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();
|
const player = createPlayerEntity();
|
||||||
player.energy = 3;
|
player.energy = 3;
|
||||||
const inventory = createInventory([]);
|
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);
|
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 player = createPlayerEntity();
|
||||||
const inventory = createInventory([]);
|
const inventory = createInventory([]);
|
||||||
|
const run = createRunContext(inventory.items);
|
||||||
|
|
||||||
expect(() =>
|
await expect(
|
||||||
payCardCost(player, "uses", 1, "missing", inventory),
|
payCardCost(player, "uses", 1, "missing", run),
|
||||||
).not.toThrow();
|
).resolves.not.toThrow();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { addTriggers } from "@/samples/slay-the-spire-like/data/desert/triggers"
|
||||||
import {
|
import {
|
||||||
CombatState,
|
CombatState,
|
||||||
EnemyEntity,
|
EnemyEntity,
|
||||||
|
IRunContext,
|
||||||
} from "@/samples/slay-the-spire-like/system/combat/types";
|
} from "@/samples/slay-the-spire-like/system/combat/types";
|
||||||
import { EffectData } from "@/samples/slay-the-spire-like/system/types";
|
import { EffectData } from "@/samples/slay-the-spire-like/system/types";
|
||||||
import {
|
import {
|
||||||
|
|
@ -138,7 +139,6 @@ function createCombatState(overrides: Partial<CombatState> = {}): CombatState {
|
||||||
itemEffects: {},
|
itemEffects: {},
|
||||||
},
|
},
|
||||||
enemies: [],
|
enemies: [],
|
||||||
inventory: createInventory([]),
|
|
||||||
phase: "playerTurn",
|
phase: "playerTurn",
|
||||||
turnNumber: 1,
|
turnNumber: 1,
|
||||||
result: null,
|
result: null,
|
||||||
|
|
@ -155,8 +155,18 @@ function createTestContext(state?: CombatState): IGameContext<CombatState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTriggers(): Triggers {
|
function getTriggers(): Triggers {
|
||||||
const triggers = createTriggers();
|
const run: IRunContext = {
|
||||||
addTriggers(triggers);
|
getConsumedUses() {
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
getItemData() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
*getNeighborItems() {},
|
||||||
|
async setConsumedUsesAsync() {},
|
||||||
|
};
|
||||||
|
const triggers = createTriggers(run);
|
||||||
|
addTriggers(triggers, run);
|
||||||
return triggers;
|
return triggers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue