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

93 lines
4.0 KiB
TypeScript
Raw Normal View History

2026-04-17 08:33:02 +08:00
import {createMiddlewareChain} from "../utils/middleware";
import {CombatGameContext} from "./types";
2026-04-17 09:27:20 +08:00
import {getAliveEnemies} from "@/samples/slay-the-spire-like/system/combat/utils";
import {
2026-04-17 10:00:14 +08:00
onDiscard, onDraw,
2026-04-17 09:27:20 +08:00
onEntityEffectUpkeep,
onPlayerItemEffectUpkeep
} from "@/samples/slay-the-spire-like/system/combat/effects";
import {promptMainAction} from "@/samples/slay-the-spire-like/system/combat/prompts";
2026-04-17 09:27:20 +08:00
type TriggerTypes = {
onCombatStart: {},
2026-04-17 08:33:02 +08:00
onTurnStart: { entityKey: "player" | string, },
onTurnEnd: { entityKey: "player" | string, },
onShuffle: { entityKey: "player" | string, },
2026-04-17 10:00:14 +08:00
onCardPlayed: { cardId: string, targetId?: string },
2026-04-17 08:33:02 +08:00
onCardDiscarded: { cardId: string, },
onCardDrawn: { cardId: string, },
onEffectApplied: { effectId: string, entityKey: "player" | string, stacks: number, },
}
2026-04-17 09:27:20 +08:00
function createTriggers(){
2026-04-17 08:33:02 +08:00
return {
2026-04-17 09:27:20 +08:00
onCombatStart: createTrigger("onCombatStart"),
2026-04-17 08:33:02 +08:00
onTurnStart: createTrigger("onTurnStart"),
onTurnEnd: createTrigger("onTurnEnd"),
onShuffle: createTrigger("onShuffle"),
onCardPlayed: createTrigger("onCardPlayed"),
onCardDiscarded: createTrigger("onCardDiscarded"),
onCardDrawn: createTrigger("onCardDrawn"),
onEffectApplied: createTrigger("onEffectApplied"),
}
}
2026-04-17 09:27:20 +08:00
export type Triggers = ReturnType<typeof createTriggers>
export function createStartWith(build: (triggers: Triggers) => void){
const triggers = createTriggers();
build(triggers);
return async function(game: CombatGameContext){
await triggers.onCombatStart.execute(game,{});
2026-04-17 10:00:14 +08:00
// TODO at the end of a damage effect, if win/loss is achieved, break the loop with a throw
// catch the throw and return the result here
2026-04-17 09:27:20 +08:00
while(true){
await triggers.onTurnStart.execute(game,{entityKey: "player"});
await game.produceAsync(draft => {
onEntityEffectUpkeep(draft.player);
onPlayerItemEffectUpkeep(draft.player);
});
while(true){
const action = await promptMainAction(game);
if(action.action === "end-turn") break;
2026-04-17 10:00:14 +08:00
if(action.action === "play"){
await game.produceAsync(draft => onDiscard(draft.player, action.cardId));
await triggers.onCardPlayed.execute(game, action);
}
}
for(const cardId of [...game.value.player.deck.hand]){
await game.produceAsync(draft => onDiscard(draft.player, cardId));
await triggers.onCardDiscarded.execute(game,{cardId});
2026-04-17 09:27:20 +08:00
}
await triggers.onTurnEnd.execute(game,{entityKey: "player"});
2026-04-17 10:00:14 +08:00
await game.produceAsync(draft => draft.player.energy = draft.player.maxEnergy);
for(let i = 0; i < 5; i++){
const cardId = game.value.player.deck.drawPile[0]; // TODO: should this be drawPile[-1] ?
if(!cardId) break;
await game.produceAsync(draft => onDraw(draft.player, cardId));
await triggers.onCardDrawn.execute(game,{cardId});
}
2026-04-17 09:27:20 +08:00
for(const enemy of getAliveEnemies(game.value)){
await triggers.onTurnStart.execute(game,{entityKey: enemy.id});
}
await game.produceAsync(draft => {
for(const enemy of getAliveEnemies(game.value)){
onEntityEffectUpkeep(enemy);
}
});
// TODO execute enemy intent, then update with new one here
for(const enemy of getAliveEnemies(game.value)){
await triggers.onTurnEnd.execute(game,{entityKey: enemy.id});
}
}
}
}
2026-04-17 09:27:20 +08:00
function createTrigger<TKey extends keyof TriggerTypes>(event: TKey) {
type Ctx = TriggerTypes[TKey] & { event: TKey, game: CombatGameContext };
const {use, execute} = createMiddlewareChain<Ctx>();
return {
use,
execute: (game: CombatGameContext, ctx: TriggerTypes[TKey]) => execute({...ctx, event, game}),
}
2026-04-17 08:33:02 +08:00
}