import { CombatEntity, CombatGameContext, CombatState, EffectTable, IRunContext, PlayerEntity, } from "./types"; import { CardData, CardEffectTarget, EffectData, IntentEffectTarget, } from "@/samples/slay-the-spire-like/system/types"; 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]; else if (current.stacks !== 0 && !effects[effect.id]) effects[effect.id] = current; } 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, ) { entity.itemEffects[itemKey] = entity.itemEffects[itemKey] || {}; addEffect(entity.itemEffects[itemKey], effect, stacks); } export function onEntityEffectUpkeep(entity: CombatEntity) { for (const effect of Object.values(entity.effects)) { const lifecycle = effect.data.lifecycle; if (lifecycle === "temporary") addEntityEffect(entity, effect.data, -effect.stacks); else if (lifecycle === "lingering") addEntityEffect(entity, effect.data, effect.stacks >= 0 ? -1 : 1); } } export function onEntityPostureDamage(entity: CombatEntity, damage: number) { for (const effect of Object.values(entity.effects)) { const lifecycle = effect.data.lifecycle; if (lifecycle === "posture") addEntityEffect(entity, effect.data, -Math.min(damage, effect.stacks)); } } 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") addItemEffect(entity, itemKey, effect.data, -effect.stacks); } } } 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") { addItemEffect(entity, itemKey, effect.data, -effect.stacks); } } } 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") { addItemEffect(entity, itemKey, effect.data, -effect.stacks); } } } export function* getAliveEnemies(state: CombatState) { for (let enemy of state.enemies) { if (enemy.isAlive) { yield enemy; } } } export function* getEffectTargets( target: CardEffectTarget | IntentEffectTarget, game: CombatGameContext, targetId?: string, sourceEntityKey: "player" | string = "player", ) { if (target === "eachEnemy") { for (const enemy of getAliveEnemies(game.value)) { yield enemy; } } else if (target === "user") { const entity = getCombatEntity(game.value, sourceEntityKey); if (entity) yield entity; } else if (target === "player") { yield game.value.player; } else if (target === "eachTarget") { if (!targetId) return; const entity = getCombatEntity(game.value, targetId); if (entity) yield entity; } else if (target === "randomEnemy") { const aliveEnemies = [...getAliveEnemies(game.value)]; if (aliveEnemies.length === 0) return; const index = game.rng.nextInt(aliveEnemies.length); yield aliveEnemies[index]; } } export function getCombatEntity(state: CombatState, entityKey: string) { return entityKey === "player" ? state.player : state.enemies.find((e) => e.id === entityKey); } export function canPlayCard( player: PlayerEntity, costType: CardData["costType"], costCount: number, itemId: string, run: IRunContext, ): boolean { if (costType === "energy") { return player.energy >= costCount; } if (costType === "uses") { const item = run.getItemData(itemId); if (!item) return false; const maxUses = item?.card.costType === "uses" ? item.card.costCount : 0; const consumed = run.getConsumedUses(itemId); return consumed < maxUses; } return true; } export async function payCardCost( player: PlayerEntity, costType: CardData["costType"], costCount: number, itemId: string, run: IRunContext, ): Promise { if (costType === "energy") { player.energy -= costCount; } else if (costType === "uses") { const consumed = run.getConsumedUses(itemId); await run.setConsumedUsesAsync(itemId, consumed + costCount); } }