refactor(slay-the-spire-like): rename depletion to consumedUses

Rename `depletion` to `consumedUses` in `GameItemMeta` to better reflect
its purpose. Update combat effect logic and tests to use the new field
name. Also apply consistent formatting and import organization to
combat effect modules.
This commit is contained in:
hypercross 2026-04-20 10:28:58 +08:00
parent 08c6a67d16
commit 9bed2ca13e
3 changed files with 190 additions and 150 deletions

View File

@ -1,31 +1,49 @@
import {CombatEntity, CombatGameContext, CombatState, EffectTable, PlayerEntity} from "./types";
import {
CombatEntity,
CombatGameContext,
CombatState,
EffectTable,
PlayerEntity,
} from "./types";
import {
CardData,
CardEffectTarget,
CardTargetType,
EffectData,
EffectTarget
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, effect: EffectData, stacks: number){
export function addEffect(
effects: EffectTable,
effect: EffectData,
stacks: number,
) {
let current = effects[effect.id];
if (!current) current = { data: effect, stacks };
else current.stacks += stacks;
if(current.stacks === 0 && effects[effect.id])
delete effects[effect.id];
if (current.stacks === 0 && effects[effect.id]) delete effects[effect.id];
else if (current.stacks !== 0 && !effects[effect.id])
effects[effect.id] = current;
}
export function addEntityEffect(entity: CombatEntity, effect: EffectData, stacks: number){
export function addEntityEffect(
entity: CombatEntity,
effect: EffectData,
stacks: number,
) {
addEffect(entity.effects, effect, stacks);
}
export function addItemEffect(entity: PlayerEntity, itemKey: string, effect: EffectData, stacks: number){
export function addItemEffect(
entity: PlayerEntity,
itemKey: string,
effect: EffectData,
stacks: number,
) {
entity.itemEffects[itemKey] = entity.itemEffects[itemKey] || {};
addEffect(entity.itemEffects[itemKey], effect, stacks);
}
@ -33,9 +51,9 @@ export function addItemEffect(entity: PlayerEntity, itemKey: string, effect: Eff
export function onEntityEffectUpkeep(entity: CombatEntity) {
for (const effect of Object.values(entity.effects)) {
const lifecycle = effect.data.lifecycle;
if(lifecycle === 'temporary')
if (lifecycle === "temporary")
addEntityEffect(entity, effect.data, -effect.stacks);
else if(lifecycle === 'lingering')
else if (lifecycle === "lingering")
addEntityEffect(entity, effect.data, effect.stacks >= 0 ? -1 : 1);
}
}
@ -43,7 +61,7 @@ export function onEntityEffectUpkeep(entity: CombatEntity){
export function onEntityPostureDamage(entity: CombatEntity, damage: number) {
for (const effect of Object.values(entity.effects)) {
const lifecycle = effect.data.lifecycle;
if(lifecycle === 'posture')
if (lifecycle === "posture")
addEntityEffect(entity, effect.data, -Math.min(damage, effect.stacks));
}
}
@ -52,7 +70,7 @@ export function onPlayerItemEffectUpkeep(entity: PlayerEntity){
for (const [itemKey, itemEffects] of Object.entries(entity.itemEffects)) {
for (const effect of Object.values(itemEffects)) {
const lifecycle = effect.data.lifecycle;
if(lifecycle === 'itemTemporary')
if (lifecycle === "itemTemporary")
addItemEffect(entity, itemKey, effect.data, -effect.stacks);
}
}
@ -62,7 +80,7 @@ export function onItemPlay(entity: PlayerEntity, itemKey: string){
const effects = entity.itemEffects[itemKey];
if (!effects) return;
for (const effect of Object.values(effects)) {
if(effect.data.lifecycle === 'itemUntilPlay'){
if (effect.data.lifecycle === "itemUntilPlay") {
addItemEffect(entity, itemKey, effect.data, -effect.stacks);
}
}
@ -72,7 +90,7 @@ export function onItemDiscard(entity: PlayerEntity, itemKey: string){
const effects = entity.itemEffects[itemKey];
if (!effects) return;
for (const effect of Object.values(effects)) {
if(effect.data.lifecycle === 'itemUntilDiscard'){
if (effect.data.lifecycle === "itemUntilDiscard") {
addItemEffect(entity, itemKey, effect.data, -effect.stacks);
}
}
@ -86,18 +104,22 @@ export function* getAliveEnemies(state: CombatState) {
}
}
export function* getEffectTargets(target: CardEffectTarget | EffectTarget, game: CombatGameContext, targetId?: string){
if(target === 'all' || target === 'team'){
export function* getEffectTargets(
target: CardEffectTarget | EffectTarget,
game: CombatGameContext,
targetId?: string,
) {
if (target === "all" || target === "team") {
for (const enemy of getAliveEnemies(game.value)) {
yield enemy;
}
} else if(target === 'self') {
} else if (target === "self") {
yield game.value.player;
} else if(target === 'target'){
} else if (target === "target") {
if (!targetId) return;
const entity = getCombatEntity(game.value, targetId);
if (entity) yield entity;
} else if(target === 'random'){
} else if (target === "random") {
const aliveEnemies = [...getAliveEnemies(game.value)];
if (aliveEnemies.length === 0) return;
const index = game.rng.nextInt(aliveEnemies.length);
@ -106,29 +128,43 @@ export function* getEffectTargets(target: CardEffectTarget | EffectTarget, game:
}
export function getCombatEntity(state: CombatState, entityKey: string) {
return entityKey === 'player' ? state.player : state.enemies.find(e => e.id === entityKey);
return entityKey === "player"
? state.player
: state.enemies.find((e) => e.id === entityKey);
}
export function canPlayCard(player: PlayerEntity, costType: CardData['costType'], costCount: number, itemId: string, inventory: GridInventory<GameItemMeta>): boolean {
if (costType === 'energy') {
export function canPlayCard(
player: PlayerEntity,
costType: CardData["costType"],
costCount: number,
itemId: string,
inventory: GridInventory<GameItemMeta>,
): boolean {
if (costType === "energy") {
return player.energy >= costCount;
}
if (costType === 'uses') {
if (costType === "uses") {
const item = inventory.items.get(itemId);
if (!item || !item.meta) return false;
const depletion = item.meta.depletion ?? 0;
const depletion = item.meta.consumedUses ?? 0;
return depletion < costCount;
}
return true;
}
export function payCardCost(player: PlayerEntity, costType: CardData['costType'], costCount: number, itemId: string, inventory: GridInventory<GameItemMeta>): void {
if (costType === 'energy') {
export function payCardCost(
player: PlayerEntity,
costType: CardData["costType"],
costCount: number,
itemId: string,
inventory: GridInventory<GameItemMeta>,
): void {
if (costType === "energy") {
player.energy -= costCount;
} else if (costType === 'uses') {
} else if (costType === "uses") {
const item = inventory.items.get(itemId);
if (item && item.meta) {
item.meta.depletion = (item.meta.depletion ?? 0) + costCount;
item.meta.consumedUses = (item.meta.consumedUses ?? 0) + costCount;
}
}
}

View File

@ -1,6 +1,6 @@
import type { PointCrawlMap } from '../map/types';
import type { GridInventory, InventoryItem } from '../grid-inventory/types';
import type { ParsedShape } from '../utils/parse-shape';
import type { PointCrawlMap } from "../map/types";
import type { GridInventory, InventoryItem } from "../grid-inventory/types";
import type { ParsedShape } from "../utils/parse-shape";
import { ItemData } from "@/samples/slay-the-spire-like/system/types";
/**
@ -39,7 +39,9 @@ export interface GameItemMeta {
/** Parsed shape for grid placement */
shape: ParsedShape;
/** Consumed uses, if card cost type is uses**/
depletion?: number;
consumedUses?: number;
/** Effects applied to the item */
effects?: Record<string, number>;
}
/**
@ -84,4 +86,6 @@ export interface RunState {
/**
* Result of a mutation operation on the run state.
*/
export type RunMutationResult = { success: true } | { success: false; reason: string };
export type RunMutationResult =
| { success: true }
| { success: false; reason: string };

View File

@ -82,7 +82,7 @@ function createItem(
description: "",
},
shape: { id: "1x1", cells: [{ x: 0, y: 0 }] } as unknown as ParsedShape,
depletion: costType === "uses" ? depletion : undefined,
consumedUses: costType === "uses" ? depletion : undefined,
},
};
}
@ -591,7 +591,7 @@ describe("combat/effects", () => {
payCardCost(player, "uses", 3, "potion-1", inventory);
expect(item.meta?.depletion).toBe(4);
expect(item.meta?.consumedUses).toBe(4);
});
it("should do nothing for none cost card", () => {