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-16 14:00:49 +08:00
|
|
|
|
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-16 14:00:49 +08:00
|
|
|
}
|
|
|
|
|
|
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-16 14:00:49 +08:00
|
|
|
}
|
|
|
|
|
}
|
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-16 21:52:28 +08:00
|
|
|
|
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
|
|
|
}
|