Compare commits

..

2 Commits

Author SHA1 Message Date
hypercross 63e55a828e refactor: rename entityKey to entityId in combat triggers
Rename `entityKey` and `sourceEntityKey` to `entityId` and
`sourceEntityId` across the slay-the-spire-like sample to improve
naming consistency with the rest of the project.
2026-04-23 00:38:10 +08:00
hypercross 8ec95cbf81 refactor: rename 'user' target to 'source' in slay-the-spire-like
Updates the card effect and intent target types from 'user' to 'source'
within the desert data files and combat effect system to maintain
consistency. Also reformat command tests to use 2-space indentation.
2026-04-23 00:35:28 +08:00
12 changed files with 296 additions and 294 deletions

View File

@ -1,5 +1,5 @@
# type CardEffectTrigger = 'onPlay' | 'onDraw' | 'onDiscard' # type CardEffectTrigger = 'onPlay' | 'onDraw' | 'onDiscard'
# type CardEffectTarget = 'user' | 'eachTarget' | 'eachEnemy' | 'randomEnemy' | 'player' # type CardEffectTarget = 'source' | 'eachTarget' | 'eachEnemy' | 'randomEnemy' | 'player'
# type CardEffectList = [effect: @effect; stacks: number][] # type CardEffectList = [effect: @effect; stacks: number][]
id,card,trigger,target,effects id,card,trigger,target,effects
@ -9,28 +9,28 @@ greataxe,greataxe,onPlay,eachTarget,[attack;5]
spear,spear,onPlay,eachTarget,[attack;2];[attack;2];[attack;2] spear,spear,onPlay,eachTarget,[attack;2];[attack;2];[attack;2]
dagger,dagger,onPlay,eachTarget,[attack;3];[attack;3] dagger,dagger,onPlay,eachTarget,[attack;3];[attack;3]
dart,dart,onPlay,eachTarget,[attack;1] dart,dart,onPlay,eachTarget,[attack;1]
dart-draw,dart,onPlay,user,[draw;1] dart-draw,dart,onPlay,source,[draw;1]
crossbow,crossbow,onPlay,eachTarget,[attack;6] crossbow,crossbow,onPlay,eachTarget,[attack;6]
crossbow-combo,crossbow,onPlay,user,[crossbow;0] crossbow-combo,crossbow,onPlay,source,[crossbow;0]
shield,shield,onPlay,user,[defend;3] shield,shield,onPlay,source,[defend;3]
hat,hat,onPlay,user,[defend;8] hat,hat,onPlay,source,[defend;8]
cape,cape,onPlay,user,[defend;2];[defendNext;2] cape,cape,onPlay,source,[defend;2];[defendNext;2]
bracer,bracer,onPlay,user,[defend;1];[draw;1] bracer,bracer,onPlay,source,[defend;1];[draw;1]
greatshield,greatshield,onPlay,user,[defend;5] greatshield,greatshield,onPlay,source,[defend;5]
chainmail,chainmail,onPlay,user,[damageReduce;3] chainmail,chainmail,onPlay,source,[damageReduce;3]
bandage,bandage,onPlay,user,[removeWound;1] bandage,bandage,onPlay,source,[removeWound;1]
poisonPotion,poisonPotion,onPlay,user,[attackBuff;2] poisonPotion,poisonPotion,onPlay,source,[attackBuff;2]
fortifyPotion,fortifyPotion,onPlay,user,[defendBuff;2] fortifyPotion,fortifyPotion,onPlay,source,[defendBuff;2]
vitalityPotion,vitalityPotion,onPlay,user,[gainEnergy;1] vitalityPotion,vitalityPotion,onPlay,source,[gainEnergy;1]
focusPotion,focusPotion,onPlay,user,[draw;2] focusPotion,focusPotion,onPlay,source,[draw;2]
healingPotion,healingPotion,onPlay,user,[removeWound;3] healingPotion,healingPotion,onPlay,source,[removeWound;3]
waterBag,waterBag,onPlay,user,[energyNext;1];[drawNext;2] waterBag,waterBag,onPlay,source,[energyNext;1];[drawNext;2]
rope,rope,onPlay,user,[defendBuffUntilPlay;2] rope,rope,onPlay,source,[defendBuffUntilPlay;2]
belt,belt,onPlay,user,[drawChoice;1] belt,belt,onPlay,source,[drawChoice;1]
torch,torch,onPlay,user,[burnForEnergy;1] torch,torch,onPlay,source,[burnForEnergy;1]
whetstone,whetstone,onPlay,user,[attackBuffUntilPlay;3] whetstone,whetstone,onPlay,source,[attackBuffUntilPlay;3]
blacksmithHammer,blacksmithHammer,onPlay,user,[transformRandom;1] blacksmithHammer,blacksmithHammer,onPlay,source,[transformRandom;1]
venom,venom,onDiscard,user,[attack;3] venom,venom,onDiscard,source,[attack;3]
curse,curse,onDraw,user,[curse;1] curse,curse,onDraw,source,[curse;1]
static,static,onDraw,user,[static;1] static,static,onDraw,source,[static;1]
vultureEye,vultureEye,onDraw,user,[expose;3] vultureEye,vultureEye,onDraw,source,[expose;3]

1 # type CardEffectTrigger = 'onPlay' | 'onDraw' | 'onDiscard'
2 # type CardEffectTarget = 'user' | 'eachTarget' | 'eachEnemy' | 'randomEnemy' | 'player' # type CardEffectTarget = 'source' | 'eachTarget' | 'eachEnemy' | 'randomEnemy' | 'player'
3 # type CardEffectList = [effect: @effect; stacks: number][]
4 id,card,trigger,target,effects
5 string,@card,CardEffectTrigger,CardEffectTarget,CardEffectList
9 dagger,dagger,onPlay,eachTarget,[attack;3];[attack;3]
10 dart,dart,onPlay,eachTarget,[attack;1]
11 dart-draw,dart,onPlay,user,[draw;1] dart-draw,dart,onPlay,source,[draw;1]
12 crossbow,crossbow,onPlay,eachTarget,[attack;6]
13 crossbow-combo,crossbow,onPlay,user,[crossbow;0] crossbow-combo,crossbow,onPlay,source,[crossbow;0]
14 shield,shield,onPlay,user,[defend;3] shield,shield,onPlay,source,[defend;3]
15 hat,hat,onPlay,user,[defend;8] hat,hat,onPlay,source,[defend;8]
16 cape,cape,onPlay,user,[defend;2];[defendNext;2] cape,cape,onPlay,source,[defend;2];[defendNext;2]
17 bracer,bracer,onPlay,user,[defend;1];[draw;1] bracer,bracer,onPlay,source,[defend;1];[draw;1]
18 greatshield,greatshield,onPlay,user,[defend;5] greatshield,greatshield,onPlay,source,[defend;5]
19 chainmail,chainmail,onPlay,user,[damageReduce;3] chainmail,chainmail,onPlay,source,[damageReduce;3]
20 bandage,bandage,onPlay,user,[removeWound;1] bandage,bandage,onPlay,source,[removeWound;1]
21 poisonPotion,poisonPotion,onPlay,user,[attackBuff;2] poisonPotion,poisonPotion,onPlay,source,[attackBuff;2]
22 fortifyPotion,fortifyPotion,onPlay,user,[defendBuff;2] fortifyPotion,fortifyPotion,onPlay,source,[defendBuff;2]
23 vitalityPotion,vitalityPotion,onPlay,user,[gainEnergy;1] vitalityPotion,vitalityPotion,onPlay,source,[gainEnergy;1]
24 focusPotion,focusPotion,onPlay,user,[draw;2] focusPotion,focusPotion,onPlay,source,[draw;2]
25 healingPotion,healingPotion,onPlay,user,[removeWound;3] healingPotion,healingPotion,onPlay,source,[removeWound;3]
26 waterBag,waterBag,onPlay,user,[energyNext;1];[drawNext;2] waterBag,waterBag,onPlay,source,[energyNext;1];[drawNext;2]
27 rope,rope,onPlay,user,[defendBuffUntilPlay;2] rope,rope,onPlay,source,[defendBuffUntilPlay;2]
28 belt,belt,onPlay,user,[drawChoice;1] belt,belt,onPlay,source,[drawChoice;1]
29 torch,torch,onPlay,user,[burnForEnergy;1] torch,torch,onPlay,source,[burnForEnergy;1]
30 whetstone,whetstone,onPlay,user,[attackBuffUntilPlay;3] whetstone,whetstone,onPlay,source,[attackBuffUntilPlay;3]
31 blacksmithHammer,blacksmithHammer,onPlay,user,[transformRandom;1] blacksmithHammer,blacksmithHammer,onPlay,source,[transformRandom;1]
32 venom,venom,onDiscard,user,[attack;3] venom,venom,onDiscard,source,[attack;3]
33 curse,curse,onDraw,user,[curse;1] curse,curse,onDraw,source,[curse;1]
34 static,static,onDraw,user,[static;1] static,static,onDraw,source,[static;1]
35 vultureEye,vultureEye,onDraw,user,[expose;3] vultureEye,vultureEye,onDraw,source,[expose;3]
36

View File

@ -2,7 +2,7 @@ import type { Card } from './card.csv';
import type { Effect } from './effect.csv'; import type { Effect } from './effect.csv';
export type CardEffectTrigger = 'onPlay' | 'onDraw' | 'onDiscard'; export type CardEffectTrigger = 'onPlay' | 'onDraw' | 'onDiscard';
export type CardEffectTarget = 'user' | 'eachTarget' | 'eachEnemy' | 'randomEnemy' | 'player'; export type CardEffectTarget = 'source' | 'eachTarget' | 'eachEnemy' | 'randomEnemy' | 'player';
export type CardEffectList = [effect: Effect, stacks: number][]; export type CardEffectList = [effect: Effect, stacks: number][];
type CardEffectTable = readonly { type CardEffectTable = readonly {

View File

@ -6,50 +6,50 @@
# initBuffs: initial buffs for this intent (applied when intent becomes active) # initBuffs: initial buffs for this intent (applied when intent becomes active)
# effects: effects executed when this intent is active # effects: effects executed when this intent is active
# type IntentEffectTarget = 'user' | 'eachEnemy' | 'randomEnemy' | 'player' # type IntentEffectTarget = 'source' | 'eachEnemy' | 'randomEnemy' | 'player'
# type IntentEffect = [IntentEffectTarget;@effect;number] # type IntentEffect = [IntentEffectTarget;@effect;number]
# type IntentEffectList = IntentEffect[] # type IntentEffectList = IntentEffect[]
id,enemy,initialIntent,nextIntents,brokenIntent,effects id,enemy,initialIntent,nextIntents,brokenIntent,effects
string,@enemy,boolean,@intent[],@intent[],IntentEffectList string,@enemy,boolean,@intent[],@intent[],IntentEffectList
仙人掌怪-boost,仙人掌怪,true,仙人掌怪-boost;仙人掌怪-defend,,[user;spike;1];[user;defend;4] 仙人掌怪-boost,仙人掌怪,true,仙人掌怪-boost;仙人掌怪-defend,,[source;spike;1];[source;defend;4]
仙人掌怪-defend,仙人掌怪,false,仙人掌怪-attack,,[user;defend;8] 仙人掌怪-defend,仙人掌怪,false,仙人掌怪-attack,,[source;defend;8]
仙人掌怪-attack,仙人掌怪,false,仙人掌怪-boost,,[player;attack;5] 仙人掌怪-attack,仙人掌怪,false,仙人掌怪-boost,,[player;attack;5]
蛇-poison,蛇,true,蛇-attack;蛇-attack,,[player;venom;1];[player;attack;4] 蛇-poison,蛇,true,蛇-attack;蛇-attack,,[player;venom;1];[player;attack;4]
蛇-attack,蛇,false,蛇-poison;蛇-boost,,[player;attack;6] 蛇-attack,蛇,false,蛇-poison;蛇-boost,,[player;attack;6]
蛇-boost,蛇,false,蛇-poison;蛇-attack,,[user;defend;3];[player;venom;1] 蛇-boost,蛇,false,蛇-poison;蛇-attack,,[source;defend;3];[player;venom;1]
木乃伊-attack,木乃伊,true,木乃伊-defend;木乃伊-curse,,[player;attack;6] 木乃伊-attack,木乃伊,true,木乃伊-defend;木乃伊-curse,,[player;attack;6]
木乃伊-defend,木乃伊,false,木乃伊-attack,,[user;defend;6] 木乃伊-defend,木乃伊,false,木乃伊-attack,,[source;defend;6]
木乃伊-curse,木乃伊,false,木乃伊-defend;木乃伊-attack,木乃伊-attack,[player;curse;1] 木乃伊-curse,木乃伊,false,木乃伊-defend;木乃伊-attack,木乃伊-attack,[player;curse;1]
枪手-aim,枪手,true,枪手-attack,,[user;aim;2] 枪手-aim,枪手,true,枪手-attack,,[source;aim;2]
枪手-attack,枪手,false,枪手-aim;枪手-defend,枪手-aim,[player;attack;8] 枪手-attack,枪手,false,枪手-aim;枪手-defend,枪手-aim,[player;attack;8]
枪手-defend,枪手,false,枪手-aim,枪手-aim,[user;defend;5] 枪手-defend,枪手,false,枪手-aim,枪手-aim,[source;defend;5]
风卷草-boost,风卷草,true,风卷草-defend;风卷草-defend;风卷草-boost,,[user;roll;5];[user;defend;4] 风卷草-boost,风卷草,true,风卷草-defend;风卷草-defend;风卷草-boost,,[source;roll;5];[source;defend;4]
风卷草-defend,风卷草,false,风卷草-boost;风卷草-attack,,[user;defend;8] 风卷草-defend,风卷草,false,风卷草-boost;风卷草-attack,,[source;defend;8]
风卷草-attack,风卷草,false,风卷草-boost,,[player;rollDamage;0] 风卷草-attack,风卷草,false,风卷草-boost,,[player;rollDamage;0]
秃鹫-attack,秃鹫,true,秃鹫-defend;秃鹫-defend,,[player;attack;6];[player;vultureEye;1] 秃鹫-attack,秃鹫,true,秃鹫-defend;秃鹫-defend,,[player;attack;6];[player;vultureEye;1]
秃鹫-defend,秃鹫,false,秃鹫-attack;秃鹫-attack,,[user;defend;5] 秃鹫-defend,秃鹫,false,秃鹫-attack;秃鹫-attack,,[source;defend;5]
沙蝎-boost,沙蝎,true,沙蝎-attack;沙蝎-attack,,[user;tailSting;2] 沙蝎-boost,沙蝎,true,沙蝎-attack;沙蝎-attack,,[source;tailSting;2]
沙蝎-attack,沙蝎,false,沙蝎-boost;沙蝎-attack,,[player;attack;6] 沙蝎-attack,沙蝎,false,沙蝎-boost;沙蝎-attack,,[player;attack;6]
幼沙虫-defend,幼沙虫,true,幼沙虫-defend;幼沙虫-boost,,[user;defend;6] 幼沙虫-defend,幼沙虫,true,幼沙虫-defend;幼沙虫-boost,,[source;defend;6]
幼沙虫-boost,幼沙虫,false,幼沙虫-attack;幼沙虫-defend,,[user;energyDrain;1];[user;defend;4] 幼沙虫-boost,幼沙虫,false,幼沙虫-attack;幼沙虫-defend,,[source;energyDrain;1];[source;defend;4]
幼沙虫-attack,幼沙虫,false,幼沙虫-defend;幼沙虫-defend,,[player;attack;5] 幼沙虫-attack,幼沙虫,false,幼沙虫-defend;幼沙虫-defend,,[player;attack;5]
蜥蜴-attack,蜥蜴,true,蜥蜴-defend;蜥蜴-molt,,[player;attack;5] 蜥蜴-attack,蜥蜴,true,蜥蜴-defend;蜥蜴-molt,,[player;attack;5]
蜥蜴-defend,蜥蜴,false,蜥蜴-attack;蜥蜴-attack,,[user;defend;6] 蜥蜴-defend,蜥蜴,false,蜥蜴-attack;蜥蜴-attack,,[source;defend;6]
蜥蜴-molt,蜥蜴,false,蜥蜴-defend;蜥蜴-attack,,[user;molt;3] 蜥蜴-molt,蜥蜴,false,蜥蜴-defend;蜥蜴-attack,,[source;molt;3]
沙匪-attack,沙匪,true,沙匪-attack;沙匪-heavyAttack,,[player;attack;6] 沙匪-attack,沙匪,true,沙匪-attack;沙匪-heavyAttack,,[player;attack;6]
沙匪-heavyAttack,沙匪,false,沙匪-attack;沙匪-attack;沙匪-debuff,,[player;attack;10] 沙匪-heavyAttack,沙匪,false,沙匪-attack;沙匪-attack;沙匪-debuff,,[player;attack;10]
沙匪-debuff,沙匪,false,沙匪-attack;沙匪-attack,,[player;discard;1] 沙匪-debuff,沙匪,false,沙匪-attack;沙匪-attack,,[player;discard;1]
风暴之灵-storm,风暴之灵,true,风暴之灵-attack;风暴之灵-storm,,[user;storm;2];[user;defend;3] 风暴之灵-storm,风暴之灵,true,风暴之灵-attack;风暴之灵-storm,,[source;storm;2];[source;defend;3]
风暴之灵-attack,风暴之灵,false,风暴之灵-storm;风暴之灵-defend,,[player;attack;8];[player;static;1] 风暴之灵-attack,风暴之灵,false,风暴之灵-storm;风暴之灵-defend,,[player;attack;8];[player;static;1]
风暴之灵-defend,风暴之灵,false,风暴之灵-storm;风暴之灵-attack,,[user;defend;8] 风暴之灵-defend,风暴之灵,false,风暴之灵-storm;风暴之灵-attack,,[source;defend;8]
骑马枪手-charge,骑马枪手,true,骑马枪手-attack,,[user;charge;2] 骑马枪手-charge,骑马枪手,true,骑马枪手-attack,,[source;charge;2]
骑马枪手-attack,骑马枪手,false,骑马枪手-charge;骑马枪手-defend,骑马枪手-charge,[player;attack;6] 骑马枪手-attack,骑马枪手,false,骑马枪手-charge;骑马枪手-defend,骑马枪手-charge,[player;attack;6]
骑马枪手-defend,骑马枪手,false,骑马枪手-charge;骑马枪手-attack,骑马枪手-charge,[user;defend;5] 骑马枪手-defend,骑马枪手,false,骑马枪手-charge;骑马枪手-attack,骑马枪手-charge,[source;defend;5]
沙虫王-summon,沙虫王,true,沙虫王-attack;沙虫王-defend,,[user;summonSandwormLarva;18] 沙虫王-summon,沙虫王,true,沙虫王-attack;沙虫王-defend,,[source;summonSandwormLarva;18]
沙虫王-attack,沙虫王,false,沙虫王-summon;沙虫王-defend,,[player;attack;9] 沙虫王-attack,沙虫王,false,沙虫王-summon;沙虫王-defend,,[player;attack;9]
沙虫王-defend,沙虫王,false,沙虫王-attack;沙虫王-summon,,[user;defend;6] 沙虫王-defend,沙虫王,false,沙虫王-attack;沙虫王-summon,,[source;defend;6]
沙漠守卫-summon,沙漠守卫,true,沙漠守卫-attack;沙漠守卫-defend,,[user;summonMummy;14] 沙漠守卫-summon,沙漠守卫,true,沙漠守卫-attack;沙漠守卫-defend,,[source;summonMummy;14]
沙漠守卫-attack,沙漠守卫,false,沙漠守卫-defend;沙漠守卫-summon,,[player;attack;8] 沙漠守卫-attack,沙漠守卫,false,沙漠守卫-defend;沙漠守卫-summon,,[player;attack;8]
沙漠守卫-defend,沙漠守卫,false,沙漠守卫-attack;沙漠守卫-revive,,[user;defend;8] 沙漠守卫-defend,沙漠守卫,false,沙漠守卫-attack;沙漠守卫-revive,,[source;defend;8]
沙漠守卫-revive,沙漠守卫,false,沙漠守卫-attack;沙漠守卫-summon,,[user;reviveMummy;1] 沙漠守卫-revive,沙漠守卫,false,沙漠守卫-attack;沙漠守卫-summon,,[source;reviveMummy;1]

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

View File

@ -1,7 +1,7 @@
import type { Enemy } from './enemy.csv'; import type { Enemy } from './enemy.csv';
import type { Effect } from './effect.csv'; import type { Effect } from './effect.csv';
export type IntentEffectTarget = 'user' | 'eachEnemy' | 'randomEnemy' | 'player'; export type IntentEffectTarget = 'source' | 'eachEnemy' | 'randomEnemy' | 'player';
export type IntentEffect = [IntentEffectTarget, Effect, number]; export type IntentEffect = [IntentEffectTarget, Effect, number];
export type IntentEffectList = IntentEffect[]; export type IntentEffectList = IntentEffect[];

View File

@ -30,9 +30,9 @@ export function addCardEventTriggers(triggers: Triggers, run: IRunContext) {
for (let i = 0; i < storm; i++) { for (let i = 0; i < storm; i++) {
await triggers.onEffectApplied.execute(ctx.game, { await triggers.onEffectApplied.execute(ctx.game, {
effect: findEffect("static"), effect: findEffect("static"),
entityKey: "player", entityId: "player",
stacks: 1, stacks: 1,
sourceEntityKey: ctx.enemyId, sourceEntityId: ctx.enemyId,
}); });
} }
} }
@ -52,7 +52,7 @@ export function addCardEventTriggers(triggers: Triggers, run: IRunContext) {
await triggers.onCardPlayed.execute(ctx.game, { await triggers.onCardPlayed.execute(ctx.game, {
cardId: id, cardId: id,
targetId: ctx.targetId, targetId: ctx.targetId,
sourceEntityKey: "player", sourceEntityId: "player",
}); });
} }
} }
@ -113,17 +113,17 @@ export function addCardEventTriggers(triggers: Triggers, run: IRunContext) {
await next(); await next();
const dealt = ctx.amount - (ctx.prevented ?? 0); const dealt = ctx.amount - (ctx.prevented ?? 0);
if (dealt <= 0 || !ctx.sourceEntityKey) return; if (dealt <= 0 || !ctx.sourceEntityId) return;
const attacker = getCombatEntity(ctx.game.value, ctx.sourceEntityKey); const attacker = getCombatEntity(ctx.game.value, ctx.sourceEntityId);
if (!attacker || !("enemy" in attacker) || attacker.enemy.id !== "秃鹫") if (!attacker || !("enemy" in attacker) || attacker.enemy.id !== "秃鹫")
return; return;
await triggers.onEffectApplied.execute(ctx.game, { await triggers.onEffectApplied.execute(ctx.game, {
effect: findEffect("vultureEye"), effect: findEffect("vultureEye"),
entityKey: "player", entityId: "player",
stacks: 1, stacks: 1,
sourceEntityKey: ctx.sourceEntityKey, sourceEntityId: ctx.sourceEntityId,
}); });
}); });
} }

View File

@ -22,7 +22,7 @@ export function addDamageTriggers(triggers: Triggers) {
// block / damage prevention // block / damage prevention
triggers.onDamage.use(async (ctx, next) => { triggers.onDamage.use(async (ctx, next) => {
const entity = getCombatEntity(ctx.game.value, ctx.entityKey); const entity = getCombatEntity(ctx.game.value, ctx.entityId);
if (!entity) return; if (!entity) return;
let preventable = ctx.amount - (ctx.prevented ?? 0); let preventable = ctx.amount - (ctx.prevented ?? 0);
@ -42,7 +42,7 @@ export function addDamageTriggers(triggers: Triggers) {
ctx.prevented = (ctx.prevented ?? 0) + blocked; ctx.prevented = (ctx.prevented ?? 0) + blocked;
preventable -= blocked; preventable -= blocked;
await ctx.game.produceAsync((draft) => { await ctx.game.produceAsync((draft) => {
const e = getCombatEntity(draft, ctx.entityKey); const e = getCombatEntity(draft, ctx.entityId);
if (e) addEntityEffect(e, findEffect("defend"), -blocked); if (e) addEntityEffect(e, findEffect("defend"), -blocked);
}); });
} }
@ -61,26 +61,26 @@ export function addDamageTriggers(triggers: Triggers) {
if (ctx.amount - (ctx.prevented ?? 0) <= 0) return; if (ctx.amount - (ctx.prevented ?? 0) <= 0) return;
const entity = getCombatEntity(ctx.game.value, ctx.entityKey); const entity = getCombatEntity(ctx.game.value, ctx.entityId);
if (!entity || !entity.isAlive) return; if (!entity || !entity.isAlive) return;
const spike = entity.effects.spike?.stacks ?? 0; const spike = entity.effects.spike?.stacks ?? 0;
if (spike > 0 && ctx.sourceEntityKey) { if (spike > 0 && ctx.sourceEntityId) {
await triggers.onDamage.execute(ctx.game, { await triggers.onDamage.execute(ctx.game, {
entityKey: ctx.sourceEntityKey, entityId: ctx.sourceEntityId,
amount: spike, amount: spike,
sourceEntityKey: ctx.entityKey, sourceEntityId: ctx.entityId,
}); });
} }
}); });
// energyDrain: player loses energy when enemy takes damage // energyDrain: player loses energy when enemy takes damage
triggers.onDamage.use(async (ctx, next) => { triggers.onDamage.use(async (ctx, next) => {
const entity = getCombatEntity(ctx.game.value, ctx.entityKey); const entity = getCombatEntity(ctx.game.value, ctx.entityId);
if (!entity) return; if (!entity) return;
const energyDrain = entity.effects.energyDrain?.stacks ?? 0; const energyDrain = entity.effects.energyDrain?.stacks ?? 0;
if (energyDrain > 0 && ctx.entityKey !== "player") { if (energyDrain > 0 && ctx.entityId !== "player") {
const dealt = Math.min( const dealt = Math.min(
Math.max(0, entity.hp), Math.max(0, entity.hp),
ctx.amount - (ctx.prevented ?? 0), ctx.amount - (ctx.prevented ?? 0),
@ -97,7 +97,7 @@ export function addDamageTriggers(triggers: Triggers) {
// molt: enemy flees if molt >= maxHp // molt: enemy flees if molt >= maxHp
triggers.onDamage.use(async (ctx, next) => { triggers.onDamage.use(async (ctx, next) => {
const entity = getCombatEntity(ctx.game.value, ctx.entityKey); const entity = getCombatEntity(ctx.game.value, ctx.entityId);
if (!entity || !entity.isAlive) { if (!entity || !entity.isAlive) {
await next(); await next();
return; return;
@ -106,7 +106,7 @@ export function addDamageTriggers(triggers: Triggers) {
const molt = entity.effects.molt?.stacks ?? 0; const molt = entity.effects.molt?.stacks ?? 0;
if (molt >= entity.maxHp) { if (molt >= entity.maxHp) {
await ctx.game.produceAsync((draft) => { await ctx.game.produceAsync((draft) => {
const e = draft.enemies.find((en) => en.id === ctx.entityKey); const e = draft.enemies.find((en) => en.id === ctx.entityId);
if (e) { if (e) {
e.isAlive = false; e.isAlive = false;
e.hp = 0; e.hp = 0;
@ -124,7 +124,7 @@ export function addDamageTriggers(triggers: Triggers) {
// aim: double damage, lose aim on damage // aim: double damage, lose aim on damage
triggers.onDamage.use(async (ctx, next) => { triggers.onDamage.use(async (ctx, next) => {
if (ctx.sourceEntityKey === "player") { if (ctx.sourceEntityId === "player") {
const player = ctx.game.value.player; const player = ctx.game.value.player;
const aim = player.effects.aim?.stacks ?? 0; const aim = player.effects.aim?.stacks ?? 0;
if (aim > 0) { if (aim > 0) {
@ -136,7 +136,7 @@ export function addDamageTriggers(triggers: Triggers) {
// roll: consume 10 roll per 10 damage // roll: consume 10 roll per 10 damage
triggers.onDamage.use(async (ctx, next) => { triggers.onDamage.use(async (ctx, next) => {
if (ctx.sourceEntityKey === "player") { if (ctx.sourceEntityId === "player") {
const player = ctx.game.value.player; const player = ctx.game.value.player;
const roll = player.effects.roll?.stacks ?? 0; const roll = player.effects.roll?.stacks ?? 0;
if (roll >= 10) { if (roll >= 10) {
@ -152,8 +152,8 @@ export function addDamageTriggers(triggers: Triggers) {
// tailSting: bonus damage on attack // tailSting: bonus damage on attack
triggers.onDamage.use(async (ctx, next) => { triggers.onDamage.use(async (ctx, next) => {
if (ctx.sourceEntityKey && ctx.sourceEntityKey !== "player") { if (ctx.sourceEntityId && ctx.sourceEntityId !== "player") {
const attacker = getCombatEntity(ctx.game.value, ctx.sourceEntityKey); const attacker = getCombatEntity(ctx.game.value, ctx.sourceEntityId);
if (attacker) { if (attacker) {
const tailSting = attacker.effects.tailSting?.stacks ?? 0; const tailSting = attacker.effects.tailSting?.stacks ?? 0;
if (tailSting > 0) { if (tailSting > 0) {
@ -166,7 +166,7 @@ export function addDamageTriggers(triggers: Triggers) {
// charge: double damage dealt/received, consume equal charge // charge: double damage dealt/received, consume equal charge
triggers.onDamage.use(async (ctx, next) => { triggers.onDamage.use(async (ctx, next) => {
const entity = getCombatEntity(ctx.game.value, ctx.entityKey); const entity = getCombatEntity(ctx.game.value, ctx.entityId);
if (entity) { if (entity) {
const charge = entity.effects.charge?.stacks ?? 0; const charge = entity.effects.charge?.stacks ?? 0;
if (charge > 0) { if (charge > 0) {
@ -178,20 +178,20 @@ export function addDamageTriggers(triggers: Triggers) {
ctx.amount += dealt; ctx.amount += dealt;
if (consumed > 0) { if (consumed > 0) {
await ctx.game.produceAsync((draft) => { await ctx.game.produceAsync((draft) => {
const e = getCombatEntity(draft, ctx.entityKey); const e = getCombatEntity(draft, ctx.entityId);
if (e) addEntityEffect(e, findEffect("charge"), -consumed); if (e) addEntityEffect(e, findEffect("charge"), -consumed);
}); });
} }
} }
} }
if (ctx.sourceEntityKey) { if (ctx.sourceEntityId) {
const attacker = getCombatEntity(ctx.game.value, ctx.sourceEntityKey); const attacker = getCombatEntity(ctx.game.value, ctx.sourceEntityId);
if (attacker) { if (attacker) {
const charge = attacker.effects.charge?.stacks ?? 0; const charge = attacker.effects.charge?.stacks ?? 0;
if (charge > 0) { if (charge > 0) {
const baseAmount = ctx.amount; const baseAmount = ctx.amount;
const targetEntity = getCombatEntity(ctx.game.value, ctx.entityKey); const targetEntity = getCombatEntity(ctx.game.value, ctx.entityId);
const dealt = Math.min( const dealt = Math.min(
Math.max(0, targetEntity?.hp ?? 0), Math.max(0, targetEntity?.hp ?? 0),
baseAmount - (ctx.prevented ?? 0), baseAmount - (ctx.prevented ?? 0),
@ -200,7 +200,7 @@ export function addDamageTriggers(triggers: Triggers) {
ctx.amount += dealt; ctx.amount += dealt;
if (consumed > 0) { if (consumed > 0) {
await ctx.game.produceAsync((draft) => { await ctx.game.produceAsync((draft) => {
const a = getCombatEntity(draft, ctx.sourceEntityKey!); const a = getCombatEntity(draft, ctx.sourceEntityId!);
if (a) addEntityEffect(a, findEffect("charge"), -consumed); if (a) addEntityEffect(a, findEffect("charge"), -consumed);
}); });
} }

View File

@ -93,10 +93,10 @@ export function addInstantEffectTriggers(triggers: Triggers) {
triggers.onEffectApplied.use(async (ctx, next) => { triggers.onEffectApplied.use(async (ctx, next) => {
if (ctx.effect.id === "attack") { if (ctx.effect.id === "attack") {
await triggers.onDamage.execute(ctx.game, { await triggers.onDamage.execute(ctx.game, {
entityKey: ctx.entityKey, entityId: ctx.entityId,
amount: ctx.stacks, amount: ctx.stacks,
sourceEntityKey: sourceEntityId:
(ctx.sourceEntityKey ?? ctx.entityKey === "player") (ctx.sourceEntityId ?? ctx.entityId === "player")
? undefined ? undefined
: "player", : "player",
}); });

View File

@ -19,7 +19,7 @@ export function addTurnStartTriggers(triggers: Triggers) {
// discard: random discard at turn start // discard: random discard at turn start
triggers.onTurnStart.use(async (ctx, next) => { triggers.onTurnStart.use(async (ctx, next) => {
if (ctx.entityKey !== "player") { if (ctx.entityId !== "player") {
await next(); await next();
return; return;
} }
@ -56,7 +56,7 @@ export function addTurnStartTriggers(triggers: Triggers) {
// energyNext: gain energy next turn // energyNext: gain energy next turn
triggers.onTurnStart.use(async (ctx, next) => { triggers.onTurnStart.use(async (ctx, next) => {
if (ctx.entityKey !== "player") { if (ctx.entityId !== "player") {
await next(); await next();
return; return;
} }
@ -74,7 +74,7 @@ export function addTurnStartTriggers(triggers: Triggers) {
// drawNext: draw extra cards next turn // drawNext: draw extra cards next turn
triggers.onTurnStart.use(async (ctx, next) => { triggers.onTurnStart.use(async (ctx, next) => {
if (ctx.entityKey !== "player") { if (ctx.entityId !== "player") {
await next(); await next();
return; return;
} }

View File

@ -112,7 +112,7 @@ export function* getEffectTargets(
for (const enemy of getAliveEnemies(game.value)) { for (const enemy of getAliveEnemies(game.value)) {
yield enemy; yield enemy;
} }
} else if (target === "user") { } else if (target === "source") {
const entity = getCombatEntity(game.value, sourceEntityKey); const entity = getCombatEntity(game.value, sourceEntityKey);
if (entity) yield entity; if (entity) yield entity;
} else if (target === "player") { } else if (target === "player") {

View File

@ -19,33 +19,33 @@ import { EffectData } from "@/samples/slay-the-spire-like/system/types";
type TriggerTypes = { type TriggerTypes = {
onCombatStart: {}; onCombatStart: {};
onTurnStart: { entityKey: "player" | string }; onTurnStart: { entityId: "player" | string };
onTurnEnd: { entityKey: "player" | string }; onTurnEnd: { entityId: "player" | string };
onShuffle: {}; onShuffle: {};
onCardPlayed: { onCardPlayed: {
cardId: string; cardId: string;
targetId?: string; targetId?: string;
sourceEntityKey?: "player" | string; sourceEntityId?: "player" | string;
}; };
onCardDiscarded: { cardId: string; sourceEntityKey?: "player" | string }; onCardDiscarded: { cardId: string; sourceEntityId?: "player" | string };
onCardDrawn: { cardId: string; sourceEntityKey?: "player" | string }; onCardDrawn: { cardId: string; sourceEntityId?: "player" | string };
onDraw: { count: number }; onDraw: { count: number };
onEffectApplied: { onEffectApplied: {
effect: EffectData; effect: EffectData;
entityKey: "player" | string; entityId: "player" | string;
stacks: number; stacks: number;
cardId?: string; cardId?: string;
sourceEntityKey?: "player" | string; sourceEntityId?: "player" | string;
targetId?: string; targetId?: string;
}; };
onHpChange: { entityKey: "player" | string; amount: number }; onHpChange: { entityId: "player" | string; amount: number };
onDamage: { onDamage: {
entityKey: "player" | string; entityId: "player" | string;
amount: number; amount: number;
prevented?: number; prevented?: number;
sourceEntityKey?: "player" | string; sourceEntityId?: "player" | string;
}; };
onEnemyIntent: { enemyId: string; sourceEntityKey?: "player" | string }; onEnemyIntent: { enemyId: string; sourceEntityId?: "player" | string };
onIntentUpdate: { enemyId: string }; onIntentUpdate: { enemyId: string };
}; };
@ -57,13 +57,13 @@ export function createTriggers(run: IRunContext) {
}), }),
onTurnStart: createTrigger("onTurnStart", async (ctx) => { onTurnStart: createTrigger("onTurnStart", async (ctx) => {
await ctx.game.produceAsync((draft) => { await ctx.game.produceAsync((draft) => {
const entity = getCombatEntity(draft, ctx.entityKey); const entity = getCombatEntity(draft, ctx.entityId);
if (entity) onEntityEffectUpkeep(entity); if (entity) onEntityEffectUpkeep(entity);
if (entity === draft.player) onPlayerItemEffectUpkeep(draft.player); if (entity === draft.player) onPlayerItemEffectUpkeep(draft.player);
}); });
}), }),
onTurnEnd: createTrigger("onTurnEnd", async (ctx) => { onTurnEnd: createTrigger("onTurnEnd", async (ctx) => {
if (ctx.entityKey !== "player") return; if (ctx.entityId !== "player") return;
const { regions } = ctx.game.value.player.deck; const { regions } = ctx.game.value.player.deck;
for (const cardId of Object.values(regions.hand.childIds)) { for (const cardId of Object.values(regions.hand.childIds)) {
await triggers.onCardDiscarded.execute(ctx.game, { cardId }); await triggers.onCardDiscarded.execute(ctx.game, { cardId });
@ -97,7 +97,7 @@ export function createTriggers(run: IRunContext) {
}); });
const { cards, regions } = ctx.game.value.player.deck; const { cards, regions } = ctx.game.value.player.deck;
const card = cards[ctx.cardId]; const card = cards[ctx.cardId];
const source = ctx.sourceEntityKey ?? "player"; const source = ctx.sourceEntityId ?? "player";
for (const { trigger, target, effects } of card.cardData.effects) { for (const { trigger, target, effects } of card.cardData.effects) {
if (trigger !== "onPlay") continue; if (trigger !== "onPlay") continue;
for (const [effect, stacks] of effects) for (const [effect, stacks] of effects)
@ -109,10 +109,10 @@ export function createTriggers(run: IRunContext) {
)) ))
await triggers.onEffectApplied.execute(ctx.game, { await triggers.onEffectApplied.execute(ctx.game, {
effect, effect,
entityKey: entity.id, entityId: entity.id,
stacks, stacks,
cardId: ctx.cardId, cardId: ctx.cardId,
sourceEntityKey: source, sourceEntityId: source,
targetId: ctx.targetId, targetId: ctx.targetId,
}); });
} }
@ -125,7 +125,7 @@ export function createTriggers(run: IRunContext) {
}); });
const { cards, regions } = ctx.game.value.player.deck; const { cards, regions } = ctx.game.value.player.deck;
const card = cards[ctx.cardId]; const card = cards[ctx.cardId];
const source = ctx.sourceEntityKey ?? "player"; const source = ctx.sourceEntityId ?? "player";
for (const { trigger, target, effects } of card.cardData.effects) { for (const { trigger, target, effects } of card.cardData.effects) {
if (trigger !== "onDiscard") continue; if (trigger !== "onDiscard") continue;
for (const [effect, stacks] of effects) for (const [effect, stacks] of effects)
@ -137,10 +137,10 @@ export function createTriggers(run: IRunContext) {
)) ))
await triggers.onEffectApplied.execute(ctx.game, { await triggers.onEffectApplied.execute(ctx.game, {
effect, effect,
entityKey: entity.id, entityId: entity.id,
stacks, stacks,
cardId: ctx.cardId, cardId: ctx.cardId,
sourceEntityKey: source, sourceEntityId: source,
}); });
} }
}), }),
@ -151,7 +151,7 @@ export function createTriggers(run: IRunContext) {
}); });
const { cards, regions } = ctx.game.value.player.deck; const { cards, regions } = ctx.game.value.player.deck;
const card = cards[ctx.cardId]; const card = cards[ctx.cardId];
const source = ctx.sourceEntityKey ?? "player"; const source = ctx.sourceEntityId ?? "player";
for (const { trigger, target, effects } of card.cardData.effects) { for (const { trigger, target, effects } of card.cardData.effects) {
if (trigger !== "onDraw") continue; if (trigger !== "onDraw") continue;
for (const [effect, stacks] of effects) for (const [effect, stacks] of effects)
@ -163,10 +163,10 @@ export function createTriggers(run: IRunContext) {
)) ))
await triggers.onEffectApplied.execute(ctx.game, { await triggers.onEffectApplied.execute(ctx.game, {
effect, effect,
entityKey: entity.id, entityId: entity.id,
stacks, stacks,
cardId: ctx.cardId, cardId: ctx.cardId,
sourceEntityKey: source, sourceEntityId: source,
}); });
} }
}), }),
@ -204,18 +204,18 @@ export function createTriggers(run: IRunContext) {
await ctx.game.produceAsync((draft) => { await ctx.game.produceAsync((draft) => {
const entity = const entity =
ctx.entityKey === "player" ctx.entityId === "player"
? draft.player ? draft.player
: draft.enemies.find((e) => e.id === ctx.entityKey); : draft.enemies.find((e) => e.id === ctx.entityId);
if (entity) addEntityEffect(entity, ctx.effect, ctx.stacks); if (entity) addEntityEffect(entity, ctx.effect, ctx.stacks);
}); });
}), }),
onHpChange: createTrigger("onHpChange", async (ctx) => { onHpChange: createTrigger("onHpChange", async (ctx) => {
await ctx.game.produceAsync((draft) => { await ctx.game.produceAsync((draft) => {
const entity = const entity =
ctx.entityKey === "player" ctx.entityId === "player"
? draft.player ? draft.player
: draft.enemies.find((e) => e.id === ctx.entityKey); : draft.enemies.find((e) => e.id === ctx.entityId);
if (!entity) return; if (!entity) return;
entity.hp += ctx.amount; entity.hp += ctx.amount;
entity.isAlive = entity.hp > 0; entity.isAlive = entity.hp > 0;
@ -229,9 +229,9 @@ export function createTriggers(run: IRunContext) {
}), }),
onDamage: createTrigger("onDamage", async (ctx) => { onDamage: createTrigger("onDamage", async (ctx) => {
const entity = const entity =
ctx.entityKey === "player" ctx.entityId === "player"
? ctx.game.value.player ? ctx.game.value.player
: ctx.game.value.enemies.find((e) => e.id === ctx.entityKey); : ctx.game.value.enemies.find((e) => e.id === ctx.entityId);
if (!entity || !entity.isAlive) return; if (!entity || !entity.isAlive) return;
const dealt = Math.min( const dealt = Math.min(
Math.max(0, entity.hp), Math.max(0, entity.hp),
@ -241,7 +241,7 @@ export function createTriggers(run: IRunContext) {
onEntityPostureDamage(entity, dealt); onEntityPostureDamage(entity, dealt);
}); });
await triggers.onHpChange.execute(ctx.game, { await triggers.onHpChange.execute(ctx.game, {
entityKey: ctx.entityKey, entityId: ctx.entityId,
amount: -dealt, amount: -dealt,
}); });
}), }),
@ -252,7 +252,7 @@ export function createTriggers(run: IRunContext) {
const intent = enemy.currentIntent; const intent = enemy.currentIntent;
if (!intent) return; if (!intent) return;
const source = ctx.sourceEntityKey ?? enemy.id; const source = ctx.sourceEntityId ?? enemy.id;
for (const [target, effect, stacks] of intent.effects) { for (const [target, effect, stacks] of intent.effects) {
for (const entity of getEffectTargets( for (const entity of getEffectTargets(
target, target,
@ -262,9 +262,9 @@ export function createTriggers(run: IRunContext) {
)) ))
await triggers.onEffectApplied.execute(ctx.game, { await triggers.onEffectApplied.execute(ctx.game, {
effect, effect,
entityKey: entity.id, entityId: entity.id,
stacks, stacks,
sourceEntityKey: source, sourceEntityId: source,
}); });
} }
}), }),
@ -298,7 +298,7 @@ export function createStartWith(
try { try {
while (true) { while (true) {
await triggers.onTurnStart.execute(game, { entityKey: "player" }); await triggers.onTurnStart.execute(game, { entityId: "player" });
while (true) { while (true) {
const action = await promptMainAction(game, run); const action = await promptMainAction(game, run);
if (action.action === "end-turn") break; if (action.action === "end-turn") break;
@ -306,17 +306,17 @@ export function createStartWith(
await triggers.onCardPlayed.execute(game, action); await triggers.onCardPlayed.execute(game, action);
} }
} }
await triggers.onTurnEnd.execute(game, { entityKey: "player" }); await triggers.onTurnEnd.execute(game, { entityId: "player" });
for (const enemy of getAliveEnemies(game.value)) { for (const enemy of getAliveEnemies(game.value)) {
await triggers.onTurnStart.execute(game, { entityKey: enemy.id }); await triggers.onTurnStart.execute(game, { entityId: enemy.id });
} }
for (const enemy of getAliveEnemies(game.value)) { for (const enemy of getAliveEnemies(game.value)) {
await triggers.onEnemyIntent.execute(game, { enemyId: enemy.id }); await triggers.onEnemyIntent.execute(game, { enemyId: enemy.id });
await triggers.onIntentUpdate.execute(game, { enemyId: enemy.id }); await triggers.onIntentUpdate.execute(game, { enemyId: enemy.id });
} }
for (const enemy of getAliveEnemies(game.value)) { for (const enemy of getAliveEnemies(game.value)) {
await triggers.onTurnEnd.execute(game, { entityKey: enemy.id }); await triggers.onTurnEnd.execute(game, { entityId: enemy.id });
} }
} }
} catch (e) { } catch (e) {

View File

@ -205,9 +205,9 @@ describe("desert triggers", () => {
await triggers.onEffectApplied.execute(ctx, { await triggers.onEffectApplied.execute(ctx, {
effect: attackEffect, effect: attackEffect,
entityKey: "player", entityId: "player",
stacks: 5, stacks: 5,
sourceEntityKey: "enemy-0", sourceEntityId: "enemy-0",
}); });
expect(ctx.value.player.hp).toBe(25); expect(ctx.value.player.hp).toBe(25);
@ -223,7 +223,7 @@ describe("desert triggers", () => {
await triggers.onEffectApplied.execute(ctx, { await triggers.onEffectApplied.execute(ctx, {
effect: drawEffect, effect: drawEffect,
entityKey: "player", entityId: "player",
stacks: 2, stacks: 2,
}); });
@ -239,7 +239,7 @@ describe("desert triggers", () => {
const initialEnergy = ctx.value.player.energy; const initialEnergy = ctx.value.player.energy;
await triggers.onEffectApplied.execute(ctx, { await triggers.onEffectApplied.execute(ctx, {
effect: gainEnergyEffect, effect: gainEnergyEffect,
entityKey: "player", entityId: "player",
stacks: 2, stacks: 2,
}); });
@ -261,7 +261,7 @@ describe("desert triggers", () => {
await triggers.onEffectApplied.execute(ctx, { await triggers.onEffectApplied.execute(ctx, {
effect: removeWoundEffect, effect: removeWoundEffect,
entityKey: "player", entityId: "player",
stacks: 2, stacks: 2,
}); });
@ -286,9 +286,9 @@ describe("desert triggers", () => {
}); });
await triggers.onDamage.execute(ctx, { await triggers.onDamage.execute(ctx, {
entityKey: "player", entityId: "player",
amount: 8, amount: 8,
sourceEntityKey: "enemy-0", sourceEntityId: "enemy-0",
}); });
expect(ctx.value.player.hp).toBe(27); expect(ctx.value.player.hp).toBe(27);
@ -312,9 +312,9 @@ describe("desert triggers", () => {
}); });
await triggers.onDamage.execute(ctx, { await triggers.onDamage.execute(ctx, {
entityKey: "player", entityId: "player",
amount: 8, amount: 8,
sourceEntityKey: "enemy-0", sourceEntityId: "enemy-0",
}); });
expect(ctx.value.player.hp).toBe(25); expect(ctx.value.player.hp).toBe(25);
@ -334,9 +334,9 @@ describe("desert triggers", () => {
}); });
await triggers.onDamage.execute(ctx, { await triggers.onDamage.execute(ctx, {
entityKey: "player", entityId: "player",
amount: 5, amount: 5,
sourceEntityKey: "enemy-0", sourceEntityId: "enemy-0",
}); });
expect(ctx.value.player.hp).toBe(23); expect(ctx.value.player.hp).toBe(23);
@ -359,9 +359,9 @@ describe("desert triggers", () => {
}); });
await triggers.onDamage.execute(ctx, { await triggers.onDamage.execute(ctx, {
entityKey: "仙人掌怪-0", entityId: "仙人掌怪-0",
amount: 5, amount: 5,
sourceEntityKey: "player", sourceEntityId: "player",
}); });
expect(ctx.value.player.hp).toBe(27); expect(ctx.value.player.hp).toBe(27);
@ -402,9 +402,9 @@ describe("desert triggers", () => {
}); });
await triggers.onDamage.execute(ctx, { await triggers.onDamage.execute(ctx, {
entityKey: "幼沙虫-0", entityId: "幼沙虫-0",
amount: 5, amount: 5,
sourceEntityKey: "player", sourceEntityId: "player",
}); });
expect(ctx.value.player.energy).toBe(2); expect(ctx.value.player.energy).toBe(2);
@ -429,9 +429,9 @@ describe("desert triggers", () => {
let threw = false; let threw = false;
try { try {
await triggers.onDamage.execute(ctx, { await triggers.onDamage.execute(ctx, {
entityKey: "蜥蜴-0", entityId: "蜥蜴-0",
amount: 1, amount: 1,
sourceEntityKey: "player", sourceEntityId: "player",
}); });
} catch (e) { } catch (e) {
threw = true; threw = true;
@ -457,7 +457,7 @@ describe("desert triggers", () => {
draft.player.effects.discard = { data: discardEffect, stacks: 1 }; draft.player.effects.discard = { data: discardEffect, stacks: 1 };
}); });
await triggers.onTurnStart.execute(ctx, { entityKey: "player" }); await triggers.onTurnStart.execute(ctx, { entityId: "player" });
expect(ctx.value.player.deck.regions.hand.childIds.length).toBe(2); expect(ctx.value.player.deck.regions.hand.childIds.length).toBe(2);
expect(ctx.value.player.deck.regions.discardPile.childIds.length).toBe(1); expect(ctx.value.player.deck.regions.discardPile.childIds.length).toBe(1);
@ -474,7 +474,7 @@ describe("desert triggers", () => {
draft.player.effects.defendNext = { data: defendNextEffect, stacks: 5 }; draft.player.effects.defendNext = { data: defendNextEffect, stacks: 5 };
}); });
await triggers.onTurnStart.execute(ctx, { entityKey: "player" }); await triggers.onTurnStart.execute(ctx, { entityId: "player" });
expect(ctx.value.player.effects.defend?.stacks).toBe(5); expect(ctx.value.player.effects.defend?.stacks).toBe(5);
expect(ctx.value.player.effects.defendNext).toBeUndefined(); expect(ctx.value.player.effects.defendNext).toBeUndefined();
@ -489,7 +489,7 @@ describe("desert triggers", () => {
draft.player.effects.energyNext = { data: energyNextEffect, stacks: 2 }; draft.player.effects.energyNext = { data: energyNextEffect, stacks: 2 };
}); });
await triggers.onTurnStart.execute(ctx, { entityKey: "player" }); await triggers.onTurnStart.execute(ctx, { entityId: "player" });
expect(ctx.value.player.energy).toBe(5); expect(ctx.value.player.energy).toBe(5);
expect(ctx.value.player.effects.energyNext).toBeUndefined(); expect(ctx.value.player.effects.energyNext).toBeUndefined();
@ -508,7 +508,7 @@ describe("desert triggers", () => {
draft.player.effects.drawNext = { data: drawNextEffect, stacks: 2 }; draft.player.effects.drawNext = { data: drawNextEffect, stacks: 2 };
}); });
await triggers.onTurnStart.execute(ctx, { entityKey: "player" }); await triggers.onTurnStart.execute(ctx, { entityId: "player" });
expect(ctx.value.player.deck.regions.hand.childIds.length).toBe(2); expect(ctx.value.player.deck.regions.hand.childIds.length).toBe(2);
expect(ctx.value.player.effects.drawNext).toBeUndefined(); expect(ctx.value.player.effects.drawNext).toBeUndefined();
@ -530,9 +530,9 @@ describe("desert triggers", () => {
}); });
await triggers.onDamage.execute(ctx, { await triggers.onDamage.execute(ctx, {
entityKey: "仙人掌怪-0", entityId: "仙人掌怪-0",
amount: 5, amount: 5,
sourceEntityKey: "player", sourceEntityId: "player",
}); });
expect(ctx.value.enemies[0].hp).toBe(2); expect(ctx.value.enemies[0].hp).toBe(2);
@ -552,9 +552,9 @@ describe("desert triggers", () => {
}); });
await triggers.onDamage.execute(ctx, { await triggers.onDamage.execute(ctx, {
entityKey: "仙人掌怪-0", entityId: "仙人掌怪-0",
amount: 5, amount: 5,
sourceEntityKey: "player", sourceEntityId: "player",
}); });
expect(ctx.value.enemies[0].hp).toBe(74); expect(ctx.value.enemies[0].hp).toBe(74);
@ -576,9 +576,9 @@ describe("desert triggers", () => {
}); });
await triggers.onDamage.execute(ctx, { await triggers.onDamage.execute(ctx, {
entityKey: "player", entityId: "player",
amount: 5, amount: 5,
sourceEntityKey: "沙蝎-0", sourceEntityId: "沙蝎-0",
}); });
expect(ctx.value.player.hp).toBe(23); expect(ctx.value.player.hp).toBe(23);
@ -599,9 +599,9 @@ describe("desert triggers", () => {
}); });
await triggers.onDamage.execute(ctx, { await triggers.onDamage.execute(ctx, {
entityKey: "player", entityId: "player",
amount: 5, amount: 5,
sourceEntityKey: "骑马枪手-0", sourceEntityId: "骑马枪手-0",
}); });
expect(ctx.value.player.hp).toBe(20); expect(ctx.value.player.hp).toBe(20);
@ -623,10 +623,10 @@ describe("desert triggers", () => {
await triggers.onEffectApplied.execute(ctx, { await triggers.onEffectApplied.execute(ctx, {
effect: crossbowEffect, effect: crossbowEffect,
entityKey: "player", entityId: "player",
stacks: 0, stacks: 0,
cardId: "crossbow-1", cardId: "crossbow-1",
sourceEntityKey: "player", sourceEntityId: "player",
targetId: "仙人掌怪-0", targetId: "仙人掌怪-0",
}); });
@ -647,7 +647,7 @@ describe("desert triggers", () => {
await triggers.onCardDiscarded.execute(ctx, { await triggers.onCardDiscarded.execute(ctx, {
cardId: "fatigue-1", cardId: "fatigue-1",
sourceEntityKey: "player", sourceEntityId: "player",
}); });
expect(ctx.value.enemies[0].hp).toBe(40); expect(ctx.value.enemies[0].hp).toBe(40);
@ -664,9 +664,9 @@ describe("desert triggers", () => {
const triggers = getTriggers(); const triggers = getTriggers();
await triggers.onDamage.execute(ctx, { await triggers.onDamage.execute(ctx, {
entityKey: "player", entityId: "player",
amount: 5, amount: 5,
sourceEntityKey: "秃鹫-0", sourceEntityId: "秃鹫-0",
}); });
const vultureEyeCards = Object.values(ctx.value.player.deck.cards).filter( const vultureEyeCards = Object.values(ctx.value.player.deck.cards).filter(

View File

@ -1,174 +1,176 @@
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from "vitest";
import { parseCommand, type Command } from '@/utils/command'; import { parseCommand, type Command } from "@/utils/command";
describe('parseCommand', () => { describe("parseCommand", () => {
it('should parse empty string', () => { it("should parse empty string", () => {
const result = parseCommand(''); const result = parseCommand("");
expect(result).toEqual({ expect(result).toEqual({
name: '', name: "",
flags: {}, flags: {},
options: {}, options: {},
params: [] params: [],
});
}); });
});
it('should parse command name only', () => { it("should parse command name only", () => {
const result = parseCommand('move'); const result = parseCommand("move");
expect(result).toEqual({ expect(result).toEqual({
name: 'move', name: "move",
flags: {}, flags: {},
options: {}, options: {},
params: [] params: [],
});
}); });
});
it('should parse command with params', () => { it("should parse command with params", () => {
const result = parseCommand('move meeple1 region1'); const result = parseCommand("move meeple1 region1");
expect(result).toEqual({ expect(result).toEqual({
name: 'move', name: "move",
flags: {}, flags: {},
options: {}, options: {},
params: ['meeple1', 'region1'] params: ["meeple1", "region1"],
});
}); });
});
it('should parse command with long flags', () => { it("should parse command with long flags", () => {
const result = parseCommand('move meeple1 --force --quiet'); const result = parseCommand("move meeple1 --force --quiet");
expect(result).toEqual({ expect(result).toEqual({
name: 'move', name: "move",
flags: { force: true, quiet: true }, flags: { force: true, quiet: true },
options: {}, options: {},
params: ['meeple1'] params: ["meeple1"],
});
}); });
});
it('should parse command with short flags', () => { it("should parse command with short flags", () => {
const result = parseCommand('move meeple1 -f -q'); const result = parseCommand("move meeple1 -f -q");
expect(result).toEqual({ expect(result).toEqual({
name: 'move', name: "move",
flags: { f: true, q: true }, flags: { f: true, q: true },
options: {}, options: {},
params: ['meeple1'] params: ["meeple1"],
});
}); });
});
it('should parse command with long options', () => { it("should parse command with long options", () => {
const result = parseCommand('move meeple1 --x 10 --y 20'); const result = parseCommand("move meeple1 --x 10 --y 20");
expect(result).toEqual({ expect(result).toEqual({
name: 'move', name: "move",
flags: {}, flags: {},
options: { x: '10', y: '20' }, options: { x: "10", y: "20" },
params: ['meeple1'] params: ["meeple1"],
});
}); });
});
it('should parse command with short options', () => { it("should parse command with short options", () => {
const result = parseCommand('move meeple1 -x 10 -y 20'); const result = parseCommand("move meeple1 -x 10 -y 20");
expect(result).toEqual({ expect(result).toEqual({
name: 'move', name: "move",
flags: {}, flags: {},
options: { x: '10', y: '20' }, options: { x: "10", y: "20" },
params: ['meeple1'] params: ["meeple1"],
});
}); });
});
it('should parse command with mixed flags和选项', () => { it("should parse command with mixed flags和选项", () => {
const result = parseCommand('move meeple1 region1 --force -x 10 -q'); const result = parseCommand("move meeple1 region1 --force -x 10 -q");
expect(result).toEqual({ expect(result).toEqual({
name: 'move', name: "move",
flags: { force: true, q: true }, flags: { force: true, q: true },
options: { x: '10' }, options: { x: "10" },
params: ['meeple1', 'region1'] params: ["meeple1", "region1"],
});
}); });
});
it('should handle extra whitespace', () => { it("should handle extra whitespace", () => {
const result = parseCommand(' move meeple1 --force '); const result = parseCommand(" move meeple1 --force ");
expect(result).toEqual({ expect(result).toEqual({
name: 'move', name: "move",
flags: { force: true }, flags: { force: true },
options: {}, options: {},
params: ['meeple1'] params: ["meeple1"],
});
}); });
});
it('should parse complex command', () => { it("should parse complex command", () => {
const result = parseCommand('place meeple1 board --x 5 --y 3 --rotate 90 --force'); const result = parseCommand(
expect(result).toEqual({ "place meeple1 board --x 5 --y 3 --rotate 90 --force",
name: 'place', );
flags: { force: true }, expect(result).toEqual({
options: { x: '5', y: '3', rotate: '90' }, name: "place",
params: ['meeple1', 'board'] flags: { force: true },
}); options: { x: "5", y: "3", rotate: "90" },
params: ["meeple1", "board"],
}); });
});
it('should treat negative number as option value', () => { it("should treat negative number as option value", () => {
const result = parseCommand('set --value -10'); const result = parseCommand("set --value -10");
expect(result).toEqual({ expect(result).toEqual({
name: 'set', name: "set",
flags: {}, flags: {},
options: { value: '-10' }, options: { value: "-10" },
params: [] params: [],
});
}); });
});
it('should parse quoted string with double quotes', () => { it("should parse quoted string with double quotes", () => {
const result = parseCommand('place tile "large castle" --x 5'); const result = parseCommand('place tile "large castle" --x 5');
expect(result).toEqual({ expect(result).toEqual({
name: 'place', name: "place",
flags: {}, flags: {},
options: { x: '5' }, options: { x: "5" },
params: ['tile', 'large castle'] params: ["tile", "large castle"],
});
}); });
});
it('should parse quoted string with single quotes', () => { it("should parse quoted string with single quotes", () => {
const result = parseCommand("place tile 'large castle' --x 5"); const result = parseCommand("place tile 'large castle' --x 5");
expect(result).toEqual({ expect(result).toEqual({
name: 'place', name: "place",
flags: {}, flags: {},
options: { x: '5' }, options: { x: "5" },
params: ['tile', 'large castle'] params: ["tile", "large castle"],
});
}); });
});
it('should handle escaped quotes', () => { it("should handle escaped quotes", () => {
const result = parseCommand('say "hello \\"world\\""'); const result = parseCommand('say "hello \\"world\\""');
expect(result).toEqual({ expect(result).toEqual({
name: 'say', name: "say",
flags: {}, flags: {},
options: {}, options: {},
params: ['hello "world"'] params: ['hello "world"'],
});
}); });
});
it('should handle escaped backslash', () => { it("should handle escaped backslash", () => {
const result = parseCommand('set path "C:\\\\Users"'); const result = parseCommand('set path "C:\\\\Users"');
expect(result).toEqual({ expect(result).toEqual({
name: 'set', name: "set",
flags: {}, flags: {},
options: {}, options: {},
params: ['path', 'C:\\Users'] params: ["path", "C:\\Users"],
});
}); });
});
it('should handle mixed quotes', () => { it("should handle mixed quotes", () => {
const result = parseCommand('cmd "hello world" \'foo bar\' --flag'); const result = parseCommand("cmd \"hello world\" 'foo bar' --flag");
expect(result).toEqual({ expect(result).toEqual({
name: 'cmd', name: "cmd",
flags: { flag: true }, flags: { flag: true },
options: {}, options: {},
params: ['hello world', 'foo bar'] params: ["hello world", "foo bar"],
});
}); });
});
it('should handle quote in middle of argument', () => { it("should handle quote in middle of argument", () => {
const result = parseCommand('cmd "hello\'s world"'); const result = parseCommand('cmd "hello\'s world"');
expect(result).toEqual({ expect(result).toEqual({
name: 'cmd', name: "cmd",
flags: {}, flags: {},
options: {}, options: {},
params: ["hello's world"] params: ["hello's world"],
});
}); });
});
}); });