refactor: update damage triggers and combat start logic

- Update `defend` effect lifecycle to `temporary` in desert data
- Refactor `onDamage` triggers to improve readability and logic flow
- Implement shuffle and draw actions on `onCombatStart` trigger
This commit is contained in:
hypercross 2026-04-22 11:06:19 +08:00
parent 5235ba7def
commit 2eec668851
3 changed files with 196 additions and 172 deletions

View File

@ -14,7 +14,7 @@
id, name, description, lifecycle
string, string, string, 'instant'|'temporary'|'lingering'|'permanent'|'posture'|'item'|'itemTemporary'|'itemUntilPlay'|'itemUntilDiscard'|'itemPermanent'
attack, 攻击, 对对手造成伤害, instant
defend, 防御, 抵消下次行动前受到的伤害, posture
defend, 防御, 抵消下次行动前受到的伤害, temporary
spike, 尖刺, 对攻击者造成X点伤害, permanent
venom, 蛇毒, 同名状态牌/1费打出时移除此牌。弃掉时受到3点伤害, instant
curse, 诅咒, 受攻击时物品攻击-1直到弃掉一张该物品的牌, lingering

Can't render this file because it has a wrong number of fields in line 14.

View File

@ -3,7 +3,6 @@ import {
addEntityEffect,
getCombatEntity,
} from "@/samples/slay-the-spire-like/system/combat/effects";
import { CombatGameContext } from "@/samples/slay-the-spire-like/system/combat/types";
import { EffectData } from "@/samples/slay-the-spire-like/system/types";
import getEffects from "../effect.csv";
@ -13,7 +12,12 @@ export function addDamageTriggers(triggers: Triggers) {
function findEffect(id: string): EffectData {
const found = effects.find((e: EffectData) => e.id === id);
if (found) return found;
return { id, name: id, description: "", lifecycle: "instant" } as EffectData;
return {
id,
name: id,
description: "",
lifecycle: "instant",
} as EffectData;
}
// block / damage prevention
@ -23,13 +27,7 @@ export function addDamageTriggers(triggers: Triggers) {
let preventable = ctx.amount - (ctx.prevented ?? 0);
const blocks = entity.effects.defend?.stacks ?? 0;
const blocked = Math.min(blocks, preventable);
if (blocked) {
ctx.prevented = (ctx.prevented ?? 0) + blocked;
preventable -= blocked;
}
// 1. apply damageReduce before block
const damageReduce = entity.effects.damageReduce?.stacks ?? 0;
if (damageReduce > 0) {
const reduced = Math.min(damageReduce, preventable);
@ -37,6 +35,18 @@ export function addDamageTriggers(triggers: Triggers) {
preventable -= reduced;
}
// 2. consume defend for damage prevented
const blocks = entity.effects.defend?.stacks ?? 0;
const blocked = Math.min(blocks, preventable);
if (blocked > 0) {
ctx.prevented = (ctx.prevented ?? 0) + blocked;
preventable -= blocked;
await ctx.game.produceAsync((draft) => {
const e = getCombatEntity(draft, ctx.entityKey);
if (e) addEntityEffect(e, findEffect("defend"), -blocked);
});
}
const expose = entity.effects.expose?.stacks ?? 0;
if (expose > 0) {
ctx.amount += expose;
@ -71,9 +81,12 @@ export function addDamageTriggers(triggers: Triggers) {
const energyDrain = entity.effects.energyDrain?.stacks ?? 0;
if (energyDrain > 0 && ctx.entityKey !== "player") {
const dealt = Math.min(Math.max(0, entity.hp), ctx.amount - (ctx.prevented ?? 0));
const dealt = Math.min(
Math.max(0, entity.hp),
ctx.amount - (ctx.prevented ?? 0),
);
if (dealt > 0) {
await ctx.game.produceAsync(draft => {
await ctx.game.produceAsync((draft) => {
draft.player.energy = Math.max(0, draft.player.energy - energyDrain);
});
}
@ -92,13 +105,15 @@ export function addDamageTriggers(triggers: Triggers) {
const molt = entity.effects.molt?.stacks ?? 0;
if (molt >= entity.maxHp) {
await ctx.game.produceAsync(draft => {
const e = draft.enemies.find(en => en.id === ctx.entityKey);
await ctx.game.produceAsync((draft) => {
const e = draft.enemies.find((en) => en.id === ctx.entityKey);
if (e) {
e.isAlive = false;
e.hp = 0;
}
draft.result = draft.enemies.every(en => !en.isAlive) ? "victory" : null;
draft.result = draft.enemies.every((en) => !en.isAlive)
? "victory"
: null;
});
if (ctx.game.value.result) throw ctx.game.value;
return;
@ -127,7 +142,7 @@ export function addDamageTriggers(triggers: Triggers) {
if (roll >= 10) {
const rollDamage = Math.floor(roll / 10) * 10;
ctx.amount += rollDamage;
await ctx.game.produceAsync(draft => {
await ctx.game.produceAsync((draft) => {
addEntityEffect(draft.player, findEffect("roll"), -rollDamage);
});
}
@ -155,11 +170,14 @@ export function addDamageTriggers(triggers: Triggers) {
if (entity) {
const charge = entity.effects.charge?.stacks ?? 0;
if (charge > 0) {
const dealt = Math.min(Math.max(0, entity.hp), ctx.amount - (ctx.prevented ?? 0));
const dealt = Math.min(
Math.max(0, entity.hp),
ctx.amount - (ctx.prevented ?? 0),
);
const consumed = Math.min(charge, dealt);
ctx.amount += dealt;
if (consumed > 0) {
await ctx.game.produceAsync(draft => {
await ctx.game.produceAsync((draft) => {
const e = getCombatEntity(draft, ctx.entityKey);
if (e) addEntityEffect(e, findEffect("charge"), -consumed);
});
@ -174,11 +192,14 @@ export function addDamageTriggers(triggers: Triggers) {
if (charge > 0) {
const baseAmount = ctx.amount;
const targetEntity = getCombatEntity(ctx.game.value, ctx.entityKey);
const dealt = Math.min(Math.max(0, targetEntity?.hp ?? 0), baseAmount - (ctx.prevented ?? 0));
const dealt = Math.min(
Math.max(0, targetEntity?.hp ?? 0),
baseAmount - (ctx.prevented ?? 0),
);
const consumed = Math.min(charge, dealt);
ctx.amount += dealt;
if (consumed > 0) {
await ctx.game.produceAsync(draft => {
await ctx.game.produceAsync((draft) => {
const a = getCombatEntity(draft, ctx.sourceEntityKey!);
if (a) addEntityEffect(a, findEffect("charge"), -consumed);
});

View File

@ -51,7 +51,10 @@ type TriggerTypes = {
export function createTriggers(run: IRunContext) {
const triggers = {
onCombatStart: createTrigger("onCombatStart"),
onCombatStart: createTrigger("onCombatStart", async (ctx) => {
await triggers.onShuffle.execute(ctx.game, {});
await triggers.onDraw.execute(ctx.game, { count: 5 });
}),
onTurnStart: createTrigger("onTurnStart", async (ctx) => {
await ctx.game.produceAsync((draft) => {
const entity = getCombatEntity(draft, ctx.entityKey);