boardgame-core/src/samples/slay-the-spire-like/system/combat/effects.ts

172 lines
4.7 KiB
TypeScript

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<void> {
if (costType === "energy") {
player.energy -= costCount;
} else if (costType === "uses") {
const consumed = run.getConsumedUses(itemId);
await run.setConsumedUsesAsync(itemId, consumed + costCount);
}
}