Compare commits

...

4 Commits

Author SHA1 Message Date
hypercross a80852bc59 fix: encounter generation 2026-04-17 15:45:52 +08:00
hypercross af0906561c fix: add effect triggering fixes 2026-04-17 15:30:28 +08:00
hypercross aedf82d264 fix: type issues 2026-04-17 15:14:01 +08:00
hypercross 2f085cc0b6 refactor: update csv types 2026-04-17 14:46:09 +08:00
13 changed files with 407 additions and 116 deletions

View File

@ -8,7 +8,7 @@ type CardTable = readonly {
readonly costType: "energy" | "uses" | "none"; readonly costType: "energy" | "uses" | "none";
readonly costCount: number; readonly costCount: number;
readonly targetType: "single" | "none"; readonly targetType: "single" | "none";
readonly effects: readonly ["onPlay" | "onDraw" | "onDiscard", "self" | "target" | "all" | "random", Effect, number]; readonly effects: ["onPlay" | "onDraw" | "onDiscard", "self" | "target" | "all" | "random", Effect, number][];
}[]; }[];
export type Card = CardTable[number]; export type Card = CardTable[number];

View File

@ -4,24 +4,24 @@
# shop (2): merchant who sells different stuff # shop (2): merchant who sells different stuff
# camp (2): consumable restock and heal # camp (2): consumable restock and heal
# curio (8): random pickup of treasure or resources # curio (8): random pickup of treasure or resources
# enemies: array of [enemyId; hp; buffs[]] # enemies: array of [enemyId; initialHp; buffs[]]
id,type,name,description,enemies,dialogue id,type,name,description,enemies,dialogue
string,'minion'|'elite'|'event'|'shop'|'camp'|'curio',string,string,[@enemy; int; [effect: @effect;stacks: int]][],string string,'minion'|'elite'|'event'|'shop'|'camp'|'curio',string,string,[data: @enemy; hp: int; effects: [effect: @effect;stacks: int][]][],string
cactus_pair,minion,仙人掌怪,概念:防+强化。【尖刺X】对攻击者造成X点伤害。,[仙人掌怪;20;[]];[仙人掌怪;20;[]], cactus_pair,minion,仙人掌怪,概念:防+强化。【尖刺X】对攻击者造成X点伤害。,[仙人掌怪;12;[]];[仙人掌怪;12;[]],
snake_pair,minion,蛇,概念:攻+强化。给玩家塞入蛇毒牌1费打出时移除此牌。弃掉时受到3点伤害。,[蛇;14;[]];[蛇;14;[]], snake_pair,minion,蛇,概念:攻+强化。给玩家塞入蛇毒牌1费打出时移除此牌。弃掉时受到3点伤害。,[蛇;10;[]],
mummy_cactus,minion,木乃伊,概念:攻+防。【诅咒】:受攻击时物品【攻击】-1直到弃掉一张该物品的牌。,[木乃伊;18;[]];[仙人掌怪;20;[]], mummy_cactus,minion,木乃伊,概念:攻+防。【诅咒】:受攻击时物品【攻击】-1直到弃掉一张该物品的牌。,[木乃伊;14;[]];[仙人掌怪;12;[]],
gunslinger,minion,枪手,概念单回高攻。【瞄准X】造成双倍伤害。受伤时失去等量【瞄准】,[枪手;16;[]], gunslinger,minion,枪手,概念单回高攻。【瞄准X】造成双倍伤害。受伤时失去等量【瞄准】,[枪手;12;[]],
tumbleweed_pair,minion,风卷草,概念:防+强化。【滚动X】攻击时每消耗10点【滚动】造成等量伤害。,[风卷草;22;[]];[风卷草;22;[]], tumbleweed_pair,minion,风卷草,概念:防+强化。【滚动X】攻击时每消耗10点【滚动】造成等量伤害。,[风卷草;16;[]],
vulture_cactus,minion,秃鹫,概念:攻+防。若造成伤害玩家获得秃鹫之眼0费状态牌打出时移除。抓到时获得3层暴露。,[秃鹫;16;[]];[仙人掌怪;20;[]], vulture_cactus,minion,秃鹫,概念:攻+防。若造成伤害玩家获得秃鹫之眼0费状态牌打出时移除。抓到时获得3层暴露。,[秃鹫;12;[]];[仙人掌怪;12;[]],
scorpion_snake,minion,沙蝎,概念:攻+强化。【尾刺X】姿态buff攻击时伤害提升X。,[沙蝎;14;[]];[蛇;14;[]], scorpion_snake,minion,沙蝎,概念:攻+强化。【尾刺X】姿态buff攻击时伤害提升X。,[沙蝎;10;[]];[蛇;10;[]],
sandworm_larva,minion,幼沙虫,概念:防+强化。每回合第一次受伤时玩家失去1点能量。,[幼沙虫;24;[]], sandworm_larva,minion,幼沙虫,概念:防+强化。每回合第一次受伤时玩家失去1点能量。,[幼沙虫;18;[]],
lizard_pair,minion,蜥蜴,概念:攻+防+逃跑。【脱皮】:若脱皮达到生命上限,则怪物逃跑,玩家不能获得战斗奖励。,[蜥蜴;20;[]];[蜥蜴;20;[]], lizard_pair,minion,蜥蜴,概念:攻+防+逃跑。【脱皮】:若脱皮达到生命上限,则怪物逃跑,玩家不能获得战斗奖励。,[蜥蜴;14;[]],
bandit_gunslinger,minion,沙匪,概念弱化玩家。【劫掠】对玩家施加的延时debuff。回合开始时随机弃掉一张手牌。,[沙匪;16;[]];[枪手;16;[]], bandit_gunslinger,minion,沙匪,概念弱化玩家。【劫掠】对玩家施加的延时debuff。回合开始时随机弃掉一张手牌。,[沙匪;12;[]];[枪手;12;[]],
storm_spirit,elite,风暴之灵,【风暴X】攻击时玩家获得1张静电。受伤时失去等量【风暴】。静电在手里时受【电击】伤害+1,[风暴之灵;44;[]], storm_spirit,elite,风暴之灵,【风暴X】攻击时玩家获得1张静电。受伤时失去等量【风暴】。静电在手里时受【电击】伤害+1,[风暴之灵;30;[]],
mounted_gunslinger,elite,骑马枪手,【冲锋X】受到或造成的伤害翻倍并消耗等量的冲锋。,[骑马枪手;50;[]];[枪手;20;[]], mounted_gunslinger,elite,骑马枪手,【冲锋X】受到或造成的伤害翻倍并消耗等量的冲锋。,[骑马枪手;25;[]];[枪手;12;[]],
sandworm_king,elite,沙虫王,召唤幼体沙虫每当玩家弃掉一张牌恢复1生命。,[沙虫王;55;[]], sandworm_king,elite,沙虫王,召唤幼体沙虫每当玩家弃掉一张牌恢复1生命。,[沙虫王;40;[]],
desert_guard,elite,沙漠守卫,召唤木乃伊会复活木乃伊2次。,[沙漠守卫;48;[]];[木乃伊;20;[]], desert_guard,elite,沙漠守卫,召唤木乃伊会复活木乃伊2次。,[沙漠守卫;35;[]];[木乃伊;14;[]],
desert_merchant,shop,沙漠商人,商店:可以恢复生命、出售装备、附魔物品。,, desert_merchant,shop,沙漠商人,商店:可以恢复生命、出售装备、附魔物品。,,
nomad_caravan,shop,游牧商队,商队:出售稀有物品、移除牌组中一张牌。,, nomad_caravan,shop,游牧商队,商队:出售稀有物品、移除牌组中一张牌。,,
oasis_campfire,camp,绿洲篝火,篝火可以恢复生命、补充药水使用次数、获得下次战斗Buff。,, oasis_campfire,camp,绿洲篝火,篝火可以恢复生命、补充药水使用次数、获得下次战斗Buff。,,

1 # minion (10): minor enemies
4 # shop (2): merchant who sells different stuff
5 # camp (2): consumable restock and heal
6 # curio (8): random pickup of treasure or resources
7 # enemies: array of [enemyId; hp; buffs[]] # enemies: array of [enemyId; initialHp; buffs[]]
8 id,type,name,description,enemies,dialogue
9 string,'minion'|'elite'|'event'|'shop'|'camp'|'curio',string,string,[@enemy; int; [effect: @effect;stacks: int]][],string string,'minion'|'elite'|'event'|'shop'|'camp'|'curio',string,string,[data: @enemy; hp: int; effects: [effect: @effect;stacks: int][]][],string
10 cactus_pair,minion,仙人掌怪,概念:防+强化。【尖刺X】:对攻击者造成X点伤害。,[仙人掌怪;20;[]];[仙人掌怪;20;[]], cactus_pair,minion,仙人掌怪,概念:防+强化。【尖刺X】:对攻击者造成X点伤害。,[仙人掌怪;12;[]];[仙人掌怪;12;[]],
11 snake_pair,minion,蛇,概念:攻+强化。给玩家塞入蛇毒牌(1费:打出时移除此牌。弃掉时受到3点伤害)。,[蛇;14;[]];[蛇;14;[]], snake_pair,minion,蛇,概念:攻+强化。给玩家塞入蛇毒牌(1费:打出时移除此牌。弃掉时受到3点伤害)。,[蛇;10;[]],
12 mummy_cactus,minion,木乃伊,概念:攻+防。【诅咒】:受攻击时物品【攻击】-1,直到弃掉一张该物品的牌。,[木乃伊;18;[]];[仙人掌怪;20;[]], mummy_cactus,minion,木乃伊,概念:攻+防。【诅咒】:受攻击时物品【攻击】-1,直到弃掉一张该物品的牌。,[木乃伊;14;[]];[仙人掌怪;12;[]],
13 gunslinger,minion,枪手,概念:单回高攻。【瞄准X】:造成双倍伤害。受伤时失去等量【瞄准】,[枪手;16;[]], gunslinger,minion,枪手,概念:单回高攻。【瞄准X】:造成双倍伤害。受伤时失去等量【瞄准】,[枪手;12;[]],
14 tumbleweed_pair,minion,风卷草,概念:防+强化。【滚动X】:攻击时,每消耗10点【滚动】,造成等量伤害。,[风卷草;22;[]];[风卷草;22;[]], tumbleweed_pair,minion,风卷草,概念:防+强化。【滚动X】:攻击时,每消耗10点【滚动】,造成等量伤害。,[风卷草;16;[]],
15 vulture_cactus,minion,秃鹫,概念:攻+防。若造成伤害,玩家获得秃鹫之眼(0费状态牌:打出时移除。抓到时获得3层暴露)。,[秃鹫;16;[]];[仙人掌怪;20;[]], vulture_cactus,minion,秃鹫,概念:攻+防。若造成伤害,玩家获得秃鹫之眼(0费状态牌:打出时移除。抓到时获得3层暴露)。,[秃鹫;12;[]];[仙人掌怪;12;[]],
16 scorpion_snake,minion,沙蝎,概念:攻+强化。【尾刺X】:姿态buff,攻击时,伤害提升X。,[沙蝎;14;[]];[蛇;14;[]], scorpion_snake,minion,沙蝎,概念:攻+强化。【尾刺X】:姿态buff,攻击时,伤害提升X。,[沙蝎;10;[]];[蛇;10;[]],
17 sandworm_larva,minion,幼沙虫,概念:防+强化。每回合第一次受伤时,玩家失去1点能量。,[幼沙虫;24;[]], sandworm_larva,minion,幼沙虫,概念:防+强化。每回合第一次受伤时,玩家失去1点能量。,[幼沙虫;18;[]],
18 lizard_pair,minion,蜥蜴,概念:攻+防+逃跑。【脱皮】:若脱皮达到生命上限,则怪物逃跑,玩家不能获得战斗奖励。,[蜥蜴;20;[]];[蜥蜴;20;[]], lizard_pair,minion,蜥蜴,概念:攻+防+逃跑。【脱皮】:若脱皮达到生命上限,则怪物逃跑,玩家不能获得战斗奖励。,[蜥蜴;14;[]],
19 bandit_gunslinger,minion,沙匪,概念:弱化玩家。【劫掠】:对玩家施加的延时debuff。回合开始时,随机弃掉一张手牌。,[沙匪;16;[]];[枪手;16;[]], bandit_gunslinger,minion,沙匪,概念:弱化玩家。【劫掠】:对玩家施加的延时debuff。回合开始时,随机弃掉一张手牌。,[沙匪;12;[]];[枪手;12;[]],
20 storm_spirit,elite,风暴之灵,【风暴X】:攻击时,玩家获得1张静电。受伤时失去等量【风暴】。(静电:在手里时受【电击】伤害+1),[风暴之灵;44;[]], storm_spirit,elite,风暴之灵,【风暴X】:攻击时,玩家获得1张静电。受伤时失去等量【风暴】。(静电:在手里时受【电击】伤害+1),[风暴之灵;30;[]],
21 mounted_gunslinger,elite,骑马枪手,【冲锋X】:受到或造成的伤害翻倍并消耗等量的冲锋。,[骑马枪手;50;[]];[枪手;20;[]], mounted_gunslinger,elite,骑马枪手,【冲锋X】:受到或造成的伤害翻倍并消耗等量的冲锋。,[骑马枪手;25;[]];[枪手;12;[]],
22 sandworm_king,elite,沙虫王,召唤幼体沙虫;每当玩家弃掉一张牌,恢复1生命。,[沙虫王;55;[]], sandworm_king,elite,沙虫王,召唤幼体沙虫;每当玩家弃掉一张牌,恢复1生命。,[沙虫王;40;[]],
23 desert_guard,elite,沙漠守卫,召唤木乃伊;会复活木乃伊2次。,[沙漠守卫;48;[]];[木乃伊;20;[]], desert_guard,elite,沙漠守卫,召唤木乃伊;会复活木乃伊2次。,[沙漠守卫;35;[]];[木乃伊;14;[]],
24 desert_merchant,shop,沙漠商人,商店:可以恢复生命、出售装备、附魔物品。,,
25 nomad_caravan,shop,游牧商队,商队:出售稀有物品、移除牌组中一张牌。,,
26 oasis_campfire,camp,绿洲篝火,篝火:可以恢复生命、补充药水使用次数、获得下次战斗Buff。,,
27 cave_shelter,camp,岩洞庇护所,篝火:可以恢复生命、升级一张牌。,,

View File

@ -6,7 +6,7 @@ type EncounterTable = readonly {
readonly type: "minion" | "elite" | "event" | "shop" | "camp" | "curio"; readonly type: "minion" | "elite" | "event" | "shop" | "camp" | "curio";
readonly name: string; readonly name: string;
readonly description: string; readonly description: string;
readonly enemies: readonly [Enemy, number, readonly [readonly effect: Effect, readonly stacks: number]]; readonly enemies: [data: Enemy, hp: number, effects: [effect: Effect, stacks: number][]][];
readonly dialogue: string; readonly dialogue: string;
}[]; }[];

View File

@ -1,16 +1,16 @@
id,name,description id,name,intents,description
string,string,string string,string,@intent[],string
仙人掌怪,仙人掌怪,防+强化。【尖刺X】对攻击者造成X点伤害。 仙人掌怪,仙人掌怪,[仙人掌怪-boost;仙人掌怪-defend;仙人掌怪-attack],防+强化。【尖刺X】对攻击者造成X点伤害。
蛇,蛇,攻+强化。给玩家塞入蛇毒牌1费打出时移除此牌。弃掉时受到3点伤害 蛇,蛇,[蛇-poison;蛇-attack;蛇-boost],攻+强化。给玩家塞入蛇毒牌1费打出时移除此牌。弃掉时受到3点伤害
木乃伊,木乃伊,攻+防。【诅咒】:受攻击时物品【攻击】-1直到弃掉一张该物品的牌。 木乃伊,木乃伊,[木乃伊-attack;木乃伊-defend;木乃伊-curse],攻+防。【诅咒】:受攻击时物品【攻击】-1直到弃掉一张该物品的牌。
枪手,枪手,单回高攻。【瞄准X】造成双倍伤害。受伤时失去等量【瞄准】。 枪手,枪手,[枪手-aim;枪手-attack;枪手-defend],单回高攻。【瞄准X】造成双倍伤害。受伤时失去等量【瞄准】。
风卷草,风卷草,防+强化。【滚动X】攻击时每消耗10点【滚动】造成等量伤害。 风卷草,风卷草,[风卷草-boost;风卷草-defend;风卷草-attack],防+强化。【滚动X】攻击时每消耗10点【滚动】造成等量伤害。
秃鹫,秃鹫,攻+防。若造成伤害玩家获得秃鹫之眼0费状态牌打出时移除。抓到时获得3层暴露 秃鹫,秃鹫,[秃鹫-attack;秃鹫-defend],攻+防。若造成伤害玩家获得秃鹫之眼0费状态牌打出时移除。抓到时获得3层暴露
沙蝎,沙蝎,攻+强化。【尾刺X】姿态buff攻击时伤害提升X。 沙蝎,沙蝎,[沙蝎-boost;沙蝎-attack],攻+强化。【尾刺X】姿态buff攻击时伤害提升X。
幼沙虫,幼沙虫,防+强化。每回合第一次受伤时玩家失去1点能量。 幼沙虫,幼沙虫,[幼沙虫-defend;幼沙虫-boost;幼沙虫-attack],防+强化。每回合第一次受伤时玩家失去1点能量。
蜥蜴,蜥蜴,攻+防+逃跑。【脱皮】:若脱皮达到生命上限,则怪物逃跑,玩家不能获得战斗奖励。 蜥蜴,蜥蜴,[蜥蜴-attack;蜥蜴-defend;蜥蜴-molt],攻+防+逃跑。【脱皮】:若脱皮达到生命上限,则怪物逃跑,玩家不能获得战斗奖励。
沙匪,沙匪,弱化玩家。【劫掠】对玩家施加的延时debuff。回合开始时随机弃掉一张手牌。 沙匪,沙匪,[沙匪-attack;沙匪-heavyAttack;沙匪-debuff],弱化玩家。【劫掠】对玩家施加的延时debuff。回合开始时随机弃掉一张手牌。
风暴之灵,风暴之灵,【风暴X】攻击时玩家获得1张静电。受伤时失去等量【风暴】。静电在手里时受【电击】伤害+1 风暴之灵,风暴之灵,[风暴之灵-storm;风暴之灵-attack;风暴之灵-defend],【风暴X】攻击时玩家获得1张静电。受伤时失去等量【风暴】。静电在手里时受【电击】伤害+1
骑马枪手,骑马枪手,【冲锋X】受到或造成的伤害翻倍并消耗等量的冲锋。 骑马枪手,骑马枪手,[骑马枪手-charge;骑马枪手-attack;骑马枪手-defend],【冲锋X】受到或造成的伤害翻倍并消耗等量的冲锋。
沙虫王,沙虫王,召唤幼体沙虫每当玩家弃掉一张牌恢复1生命。 沙虫王,沙虫王,[沙虫王-summon;沙虫王-attack;沙虫王-defend],召唤幼体沙虫每当玩家弃掉一张牌恢复1生命。
沙漠守卫,沙漠守卫,召唤木乃伊会复活木乃伊2次。 沙漠守卫,沙漠守卫,[沙漠守卫-summon;沙漠守卫-attack;沙漠守卫-defend;沙漠守卫-revive],召唤木乃伊会复活木乃伊2次。

1 id name intents description
2 string string @intent[] string
3 仙人掌怪 仙人掌怪 [仙人掌怪-boost;仙人掌怪-defend;仙人掌怪-attack] 防+强化。【尖刺X】:对攻击者造成X点伤害。
4 [蛇-poison;蛇-attack;蛇-boost] 攻+强化。给玩家塞入蛇毒牌(1费:打出时移除此牌。弃掉时受到3点伤害)。
5 木乃伊 木乃伊 [木乃伊-attack;木乃伊-defend;木乃伊-curse] 攻+防。【诅咒】:受攻击时物品【攻击】-1,直到弃掉一张该物品的牌。
6 枪手 枪手 [枪手-aim;枪手-attack;枪手-defend] 单回高攻。【瞄准X】:造成双倍伤害。受伤时失去等量【瞄准】。
7 风卷草 风卷草 [风卷草-boost;风卷草-defend;风卷草-attack] 防+强化。【滚动X】:攻击时,每消耗10点【滚动】,造成等量伤害。
8 秃鹫 秃鹫 [秃鹫-attack;秃鹫-defend] 攻+防。若造成伤害,玩家获得秃鹫之眼(0费状态牌:打出时移除。抓到时获得3层暴露)。
9 沙蝎 沙蝎 [沙蝎-boost;沙蝎-attack] 攻+强化。【尾刺X】:姿态buff,攻击时,伤害提升X。
10 幼沙虫 幼沙虫 [幼沙虫-defend;幼沙虫-boost;幼沙虫-attack] 防+强化。每回合第一次受伤时,玩家失去1点能量。
11 蜥蜴 蜥蜴 [蜥蜴-attack;蜥蜴-defend;蜥蜴-molt] 攻+防+逃跑。【脱皮】:若脱皮达到生命上限,则怪物逃跑,玩家不能获得战斗奖励。
12 沙匪 沙匪 [沙匪-attack;沙匪-heavyAttack;沙匪-debuff] 弱化玩家。【劫掠】:对玩家施加的延时debuff。回合开始时,随机弃掉一张手牌。
13 风暴之灵 风暴之灵 [风暴之灵-storm;风暴之灵-attack;风暴之灵-defend] 【风暴X】:攻击时,玩家获得1张静电。受伤时失去等量【风暴】。(静电:在手里时受【电击】伤害+1)
14 骑马枪手 骑马枪手 [骑马枪手-charge;骑马枪手-attack;骑马枪手-defend] 【冲锋X】:受到或造成的伤害翻倍并消耗等量的冲锋。
15 沙虫王 沙虫王 [沙虫王-summon;沙虫王-attack;沙虫王-defend] 召唤幼体沙虫;每当玩家弃掉一张牌,恢复1生命。
16 沙漠守卫 沙漠守卫 [沙漠守卫-summon;沙漠守卫-attack;沙漠守卫-defend;沙漠守卫-revive] 召唤木乃伊;会复活木乃伊2次。

View File

@ -1,6 +1,9 @@
import type { Intent } from './intent.csv';
type EnemyTable = readonly { type EnemyTable = readonly {
readonly id: string; readonly id: string;
readonly name: string; readonly name: string;
readonly intents: Intent[];
readonly description: string; readonly description: string;
}[]; }[];

View File

@ -1,52 +1,51 @@
# enemyDesert: enemy templates and their intent state machines # intentId: unique intent state ID (e.g. "仙人掌怪-boost")
# enemy: enemy template ID (unique identifier for this enemy type) # enemy: enemy template this intent belongs to
# intentId: intent state ID (unique within each enemy)
# initialIntent: true if this is the enemy's starting intent # initialIntent: true if this is the enemy's starting intent
# nextIntents: possible next intent IDs after this intent resolves # nextIntents: possible next intent IDs after this intent resolves
# brokenIntent: possible intent IDs when defend is broken # brokenIntent: possible intent IDs when defend is broken
# initBuffs: initial buffs for this enemy type (applied to all instances) # 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
enemy,intentId,initialIntent,nextIntents,brokenIntent,initBuffs,effects id,enemy,initialIntent,nextIntents,brokenIntent,effects
@enemy,string,boolean,string[],string[],[@effect;stacks: int][],['self'|'player'|'team';@effect;number][] string,@enemy,boolean,@intent[],@intent[],['self'|'player'|'team';@effect;number][]
仙人掌怪,boost,true,boost;defend;defend,,[spike;1],[self;spike;1];[self;defend;4] 仙人掌怪-boost,仙人掌怪,true,仙人掌怪-boost;仙人掌怪-defend,,[self;spike;1];[self;defend;4]
仙人掌怪,defend,false,attack,,[spike;1],[self;defend;8] 仙人掌怪-defend,仙人掌怪,false,仙人掌怪-attack,,[self;defend;8]
仙人掌怪,attack,false,boost,,[spike;1],[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,,,,[self;defend;3];[player;venom;1] -boost,蛇,false,蛇-poison;蛇-attack,,[self;defend;3];[player;venom;1]
木乃伊,attack,true,defend;curse,,,,[player;attack;6] 木乃伊-attack,木乃伊,true,木乃伊-defend;木乃伊-curse,,[player;attack;6]
木乃伊,defend,false,attack,,,,[self;defend;6] 木乃伊-defend,木乃伊,false,木乃伊-attack,,[self;defend;6]
木乃伊,curse,false,defend;attack,attack,,[player;curse;1] 木乃伊-curse,木乃伊,false,木乃伊-defend;木乃伊-attack,木乃伊-attack,[player;curse;1]
枪手,aim,true,attack,,,,[self;aim;2] 枪手-aim,枪手,true,枪手-attack,,[self;aim;2]
枪手,attack,false,aim;defend,aim,,[player;attack;8] 枪手-attack,枪手,false,枪手-aim;枪手-defend,枪手-aim,[player;attack;8]
枪手,defend,false,aim,aim,,[self;defend;5] 枪手-defend,枪手,false,枪手-aim,枪手-aim,[self;defend;5]
风卷草,boost,true,defend;defend;boost,,,,[self;roll;5];[self;defend;4] 风卷草-boost,风卷草,true,风卷草-defend;风卷草-defend;风卷草-boost,,[self;roll;5];[self;defend;4]
风卷草,defend,false,boost;attack,,,,[self;defend;8] 风卷草-defend,风卷草,false,风卷草-boost;风卷草-attack,,[self;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,,,,[self;defend;5] 秃鹫-defend,秃鹫,false,秃鹫-attack;秃鹫-attack,,[self;defend;5]
沙蝎,boost,true,attack;attack,,[tailSting;1],[self;tailSting;2] 沙蝎-boost,沙蝎,true,沙蝎-attack;沙蝎-attack,,[self;tailSting;2]
沙蝎,attack,false,boost;attack,,[tailSting;1],[player;attack;6] 沙蝎-attack,沙蝎,false,沙蝎-boost;沙蝎-attack,,[player;attack;6]
幼沙虫,defend,true,defend;boost,,[energyDrain;1],[self;defend;6] 幼沙虫-defend,幼沙虫,true,幼沙虫-defend;幼沙虫-boost,,[self;defend;6]
幼沙虫,boost,false,attack;defend,,[energyDrain;1],[self;energyDrain;1];[self;defend;4] 幼沙虫-boost,幼沙虫,false,幼沙虫-attack;幼沙虫-defend,,[self;energyDrain;1];[self;defend;4]
幼沙虫,attack,false,defend;defend,,[energyDrain;1],[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,,,,[self;defend;6] 蜥蜴-defend,蜥蜴,false,蜥蜴-attack;蜥蜴-attack,,[self;defend;6]
蜥蜴,molt,false,defend;attack,,,,[self;molt;3] 蜥蜴-molt,蜥蜴,false,蜥蜴-defend;蜥蜴-attack,,[self;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,,,,[self;storm;2];[self;defend;3] 风暴之灵-storm,风暴之灵,true,风暴之灵-attack;风暴之灵-storm,,[self;storm;2];[self;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,,,,[self;defend;8] 风暴之灵-defend,风暴之灵,false,风暴之灵-storm;风暴之灵-attack,,[self;defend;8]
骑马枪手,charge,true,attack,,,,[self;charge;2] 骑马枪手-charge,骑马枪手,true,骑马枪手-attack,,[self;charge;2]
骑马枪手,attack,false,charge;defend,charge,,[player;attack;6] 骑马枪手-attack,骑马枪手,false,骑马枪手-charge;骑马枪手-defend,骑马枪手-charge,[player;attack;6]
骑马枪手,defend,false,charge;attack,charge,,[self;defend;5] 骑马枪手-defend,骑马枪手,false,骑马枪手-charge;骑马枪手-attack,骑马枪手-charge,[self;defend;5]
沙虫王,summon,true,attack;defend,,,,[self;summonSandwormLarva;1] 沙虫王-summon,沙虫王,true,沙虫王-attack;沙虫王-defend,,[self;summonSandwormLarva;1]
沙虫王,attack,false,summon;defend,,,,[player;attack;9] 沙虫王-attack,沙虫王,false,沙虫王-summon;沙虫王-defend,,[player;attack;9]
沙虫王,defend,false,attack;summon,,,,[self;defend;6] 沙虫王-defend,沙虫王,false,沙虫王-attack;沙虫王-summon,,[self;defend;6]
沙漠守卫,summon,true,attack;defend,,,,[self;summonMummy;1] 沙漠守卫-summon,沙漠守卫,true,沙漠守卫-attack;沙漠守卫-defend,,[self;summonMummy;1]
沙漠守卫,attack,false,defend;summon,,,,[player;attack;8] 沙漠守卫-attack,沙漠守卫,false,沙漠守卫-defend;沙漠守卫-summon,,[player;attack;8]
沙漠守卫,defend,false,attack;revive,,,,[self;defend;8] 沙漠守卫-defend,沙漠守卫,false,沙漠守卫-attack;沙漠守卫-revive,,[self;defend;8]
沙漠守卫,revive,false,attack;summon,,,,[self;reviveMummy;1] 沙漠守卫-revive,沙漠守卫,false,沙漠守卫-attack;沙漠守卫-summon,,[self;reviveMummy;1]

Can't render this file because it contains an unexpected character in line 1 and column 42.

View File

@ -2,13 +2,12 @@ import type { Enemy } from './enemy.csv';
import type { Effect } from './effect.csv'; import type { Effect } from './effect.csv';
type IntentTable = readonly { type IntentTable = readonly {
readonly id: string;
readonly enemy: Enemy; readonly enemy: Enemy;
readonly intentId: string;
readonly initialIntent: boolean; readonly initialIntent: boolean;
readonly nextIntents: readonly string[]; readonly nextIntents: Intent[];
readonly brokenIntent: readonly string[]; readonly brokenIntent: Intent[];
readonly initBuffs: readonly [Effect, readonly stacks: number]; readonly effects: ["self" | "player" | "team", Effect, number][];
readonly effects: readonly ["self" | "player" | "team", Effect, number];
}[]; }[];
export type Intent = IntentTable[number]; export type Intent = IntentTable[number];

View File

@ -1,5 +1,11 @@
import {CombatEntity, CombatState, EffectTable, PlayerEntity} from "./types"; import {CombatEntity, CombatGameContext, CombatState, EffectTable, PlayerEntity} from "./types";
import {CardData, EffectData} from "@/samples/slay-the-spire-like/system/types"; import {
CardData,
CardEffectTarget,
CardTargetType,
EffectData,
EffectTarget
} from "@/samples/slay-the-spire-like/system/types";
import {GameItemMeta} from "@/samples/slay-the-spire-like/system/progress/types"; import {GameItemMeta} from "@/samples/slay-the-spire-like/system/progress/types";
import {GridInventory} from "@/samples/slay-the-spire-like/system/grid-inventory/types"; import {GridInventory} from "@/samples/slay-the-spire-like/system/grid-inventory/types";
@ -80,6 +86,25 @@ export function* getAliveEnemies(state: CombatState) {
} }
} }
export function* getEffectTargets(target: CardEffectTarget | EffectTarget, game: CombatGameContext, targetId?: string){
if(target === 'all' || target === 'team'){
for(const enemy of getAliveEnemies(game.value)){
yield enemy;
}
} else if(target === 'self') {
yield game.value.player;
} else if(target === 'target'){
if(!targetId) return;
const entity = getCombatEntity(game.value, targetId);
if(entity) yield entity;
} else if(target === 'random'){
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){ export function getCombatEntity(state: CombatState, entityKey: string){
return entityKey === 'player' ? state.player : state.enemies.find(e => e.id === entityKey); return entityKey === 'player' ? state.player : state.enemies.find(e => e.id === entityKey);
} }

View File

@ -4,7 +4,7 @@ import {
addItemEffect, addItemEffect,
getAliveEnemies, onEntityPostureDamage, getAliveEnemies, onEntityPostureDamage,
onEntityEffectUpkeep, onEntityEffectUpkeep,
onPlayerItemEffectUpkeep, onItemDiscard, onItemPlay, payCardCost onPlayerItemEffectUpkeep, onItemDiscard, onItemPlay, payCardCost, getCombatEntity, getEffectTargets
} from "@/samples/slay-the-spire-like/system/combat/effects"; } from "@/samples/slay-the-spire-like/system/combat/effects";
import {promptMainAction} from "@/samples/slay-the-spire-like/system/combat/prompts"; import {promptMainAction} from "@/samples/slay-the-spire-like/system/combat/prompts";
import {moveToRegion, shuffle} from "@/core/region"; import {moveToRegion, shuffle} from "@/core/region";
@ -34,7 +34,7 @@ function createTriggers(){
onCombatStart: createTrigger("onCombatStart"), onCombatStart: createTrigger("onCombatStart"),
onTurnStart: createTrigger("onTurnStart", async ctx => { onTurnStart: createTrigger("onTurnStart", async ctx => {
await ctx.game.produceAsync(draft => { await ctx.game.produceAsync(draft => {
const entity = ctx.entityKey === "player" ? draft.player : draft.enemies.find(e => e.id === ctx.entityKey); const entity = getCombatEntity(draft, ctx.entityKey);
if(entity) onEntityEffectUpkeep(entity); if(entity) onEntityEffectUpkeep(entity);
if(entity === draft.player) if(entity === draft.player)
onPlayerItemEffectUpkeep(draft.player); onPlayerItemEffectUpkeep(draft.player);
@ -65,6 +65,13 @@ function createTriggers(){
moveToRegion(card, regions.hand, regions.discardPile); moveToRegion(card, regions.hand, regions.discardPile);
onItemPlay(draft.player, card.itemId); onItemPlay(draft.player, card.itemId);
}); });
const {cards, regions} = ctx.game.value.player.deck;
const card = cards[ctx.cardId];
for(const [trigger, target, effect, stacks] of card.cardData.effects){
if(trigger !== 'onPlay') continue;
for(const entity of getEffectTargets(target, ctx.game, ctx.targetId))
await triggers.onEffectApplied.execute(ctx.game,{effect, entityKey: entity.id, stacks, cardId: ctx.cardId});
}
}), }),
onCardDiscarded: createTrigger("onCardDiscarded", async ctx => { onCardDiscarded: createTrigger("onCardDiscarded", async ctx => {
await ctx.game.produceAsync(draft => { await ctx.game.produceAsync(draft => {
@ -72,12 +79,26 @@ function createTriggers(){
moveToRegion(cards[ctx.cardId], regions.hand, regions.discardPile); moveToRegion(cards[ctx.cardId], regions.hand, regions.discardPile);
onItemDiscard(draft.player, cards[ctx.cardId].itemId); onItemDiscard(draft.player, cards[ctx.cardId].itemId);
}); });
const {cards, regions} = ctx.game.value.player.deck;
const card = cards[ctx.cardId];
for(const [trigger, target, effect, stacks] of card.cardData.effects){
if(trigger !== 'onDiscard') continue;
for(const entity of getEffectTargets(target, ctx.game))
await triggers.onEffectApplied.execute(ctx.game,{effect, entityKey: entity.id, stacks, cardId: ctx.cardId});
}
}), }),
onCardDrawn: createTrigger("onCardDrawn", async ctx => { onCardDrawn: createTrigger("onCardDrawn", async ctx => {
await ctx.game.produceAsync(draft => { await ctx.game.produceAsync(draft => {
const {cards, regions} = draft.player.deck; const {cards, regions} = draft.player.deck;
moveToRegion(cards[ctx.cardId], regions.drawPile, regions.hand); moveToRegion(cards[ctx.cardId], regions.drawPile, regions.hand);
}); });
const {cards, regions} = ctx.game.value.player.deck;
const card = cards[ctx.cardId];
for(const [trigger, target, effect, stacks] of card.cardData.effects){
if(trigger !== 'onDraw') continue;
for(const entity of getEffectTargets(target, ctx.game))
await triggers.onEffectApplied.execute(ctx.game,{effect, entityKey: entity.id, stacks, cardId: ctx.cardId});
}
}), }),
onDraw: createTrigger("onDraw", async ctx => { onDraw: createTrigger("onDraw", async ctx => {
let toDraw = ctx.count; let toDraw = ctx.count;
@ -138,26 +159,12 @@ function createTriggers(){
const enemy = ctx.game.value.enemies.find(e => e.id === ctx.enemyId); const enemy = ctx.game.value.enemies.find(e => e.id === ctx.enemyId);
if(!enemy || !enemy.isAlive) return; if(!enemy || !enemy.isAlive) return;
const intent = enemy.intents[enemy.currentIntentId]; const intent = enemy.currentIntent;
if(!intent) return; if(!intent) return;
for(const [target, effect, stacks] of intent.effects){ for(const [target, effect, stacks] of intent.effects){
if(target === 'team'){ for(const entity of getEffectTargets(target, ctx.game))
for(const enemy of getAliveEnemies(ctx.game.value)){ await triggers.onEffectApplied.execute(ctx.game, { effect, entityKey: entity.id, stacks, });
await triggers.onEffectApplied.execute(ctx.game, {
effect,
entityKey: enemy.id,
stacks,
});
}
}else {
const entityKey = target === 'self' ? ctx.enemyId : 'player';
await triggers.onEffectApplied.execute(ctx.game, {
effect,
entityKey,
stacks,
});
}
} }
}), }),
onIntentUpdate: createTrigger("onIntentUpdate", async ctx => { onIntentUpdate: createTrigger("onIntentUpdate", async ctx => {
@ -165,13 +172,13 @@ function createTriggers(){
const enemy = draft.enemies.find(e => e.id === ctx.enemyId); const enemy = draft.enemies.find(e => e.id === ctx.enemyId);
if(!enemy) return; if(!enemy) return;
const intent = enemy.intents[enemy.currentIntentId]; const intent = enemy.currentIntent;
if(!intent) return; if(!intent) return;
const nextIntents = intent.nextIntents; const nextIntents = intent.nextIntents;
if(nextIntents.length > 0){ if(nextIntents.length > 0){
const nextIndex = ctx.game.rng.nextInt(nextIntents.length); const nextIndex = ctx.game.rng.nextInt(nextIntents.length);
enemy.currentIntentId = nextIntents[nextIndex]; enemy.currentIntent = nextIntents[nextIndex];
} }
}); });
}), }),

View File

@ -7,6 +7,7 @@ import {GameItemMeta} from "@/samples/slay-the-spire-like/system/progress";
export type EffectTable = Record<string, {data: EffectData, stacks: number}>; export type EffectTable = Record<string, {data: EffectData, stacks: number}>;
export type CombatEntity = { export type CombatEntity = {
id: string; // player is just "player"
effects: EffectTable; effects: EffectTable;
hp: number; hp: number;
maxHp: number; maxHp: number;
@ -21,10 +22,9 @@ export type PlayerEntity = CombatEntity & {
} }
export type EnemyEntity = CombatEntity & { export type EnemyEntity = CombatEntity & {
id: string;
enemy: EnemyData; enemy: EnemyData;
intents: Record<string, IntentData>; intents: Record<string, IntentData>;
currentIntentId: string; currentIntent: IntentData;
}; };
export type CombatPhase = "playerTurn" | "enemyTurn" | "combatEnd"; export type CombatPhase = "playerTurn" | "enemyTurn" | "combatEnd";

View File

@ -0,0 +1,245 @@
import type { PointCrawlMap } from '../map/types';
import type { CombatState, EnemyEntity, PlayerEntity, EffectTable } from '../combat/types';
import type { EncounterData, EnemyData, EffectData, IntentData } from '../types';
import type { RunState } from './types';
import { generateDeckFromInventory } from '../deck/factory';
import { ReadonlyRNG } from '@/utils/rng';
// -- Encounter assignment to nodes --
/**
* Assigns an encounter from a pool to a specific node.
* Replaces any existing encounter on that node.
*/
export function assignEncounterToNode(
map: PointCrawlMap,
nodeId: string,
encounter: EncounterData
): void {
const node = map.nodes.get(nodeId);
if (!node) {
throw new Error(`Node "${nodeId}" not found`);
}
node.encounter = encounter;
}
/**
* Assigns encounters from a typed pool to all unassigned nodes of matching type.
* Uses RNG for random selection; each encounter can be assigned multiple times.
*/
export function assignEncountersFromPool(
map: PointCrawlMap,
encounterPool: EncounterData[],
rng: ReadonlyRNG
): void {
if (encounterPool.length === 0) return;
for (const node of map.nodes.values()) {
if (node.type === 'start' || node.type === 'end') continue;
if (node.encounter) continue;
const assigned = encounterPool[rng.nextInt(encounterPool.length)];
node.encounter = assigned;
}
}
/**
* Batch-assigns encounters for all node types from a typed index.
* Keys in the index should match encounter type strings (e.g. 'minion', 'elite').
*/
export function assignAllEncounters(
map: PointCrawlMap,
encounterIndex: Map<string, EncounterData[]>,
rng: ReadonlyRNG
): void {
for (const node of map.nodes.values()) {
if (node.type === 'start' || node.type === 'end') continue;
if (node.encounter) continue;
const encounterType = node.type;
const pool = encounterIndex.get(encounterType);
if (!pool || pool.length === 0) continue;
node.encounter = pool[rng.nextInt(pool.length)];
}
}
// -- CombatState construction --
/**
* Builds a full CombatState from an encounter and the current run state.
* - Creates EnemyEntity instances with HP, initial buffs, and intents
* - Creates PlayerEntity with energy (3), deck from inventory, and HP from run state
* - Sets initial phase to 'playerTurn', turn 1
*/
export function buildCombatState(
encounter: EncounterData,
runState: RunState,
): CombatState {
const enemies = createEnemyEntities(encounter);
const deck = generateDeckFromInventory(runState.inventory);
const player = createPlayerEntity(runState, deck);
return {
enemies,
player,
inventory: runState.inventory,
phase: 'playerTurn',
turnNumber: 1,
result: null,
loot: [],
};
}
/**
* Creates EnemyEntity instances from encounter enemy definitions.
* Each enemy gets: HP from encounter tuple, initial buffs from encounter, intents from enemy definition.
*/
export function createEnemyEntities(
encounter: EncounterData,
): EnemyEntity[] {
const enemies: EnemyEntity[] = [];
let instanceCounter = 0;
for (const [enemyData, hp, encounterBuffs] of encounter.enemies) {
const instanceId = `${enemyData.id}-${instanceCounter++}`;
const intents = buildIntentMap(enemyData);
const initialIntent = findInitialIntent(enemyData);
const effects = buildEffectTable(encounterBuffs);
const entity: EnemyEntity = {
id: instanceId,
enemy: enemyData,
hp,
maxHp: hp,
isAlive: true,
effects,
intents,
currentIntent: initialIntent,
};
enemies.push(entity);
}
return enemies;
}
/**
* Builds a map of intent ID -> IntentData for an enemy.
*/
function buildIntentMap(
enemy: EnemyData,
): Record<string, IntentData> {
const intents: Record<string, IntentData> = {};
for (const intent of enemy.intents) {
intents[intent.id] = intent;
}
return intents;
}
/**
* Finds the initial intent ID for an enemy.
*/
function findInitialIntent(enemy: EnemyData): IntentData {
for (const intent of enemy.intents) {
if (intent.initialIntent) {
return intent;
}
}
if (enemy.intents.length === 0) {
throw new Error(`Enemy "${enemy.id}" has no intents`);
}
return enemy.intents[0];
}
/**
* Builds an EffectTable from encounter buff definitions.
*/
function buildEffectTable(buffs: readonly [EffectData, number][]): EffectTable {
const table: EffectTable = {};
for (const [effect, stacks] of buffs) {
table[effect.id] = { data: effect, stacks };
}
return table;
}
/**
* Creates a PlayerEntity from the run state and deck.
*/
function createPlayerEntity(runState: RunState, deck: ReturnType<typeof generateDeckFromInventory>): PlayerEntity {
return {
id: "player",
hp: runState.player.currentHp,
maxHp: runState.player.maxHp,
isAlive: runState.player.currentHp > 0,
energy: 3,
maxEnergy: 3,
deck,
itemEffects: {},
effects: {},
};
}
// -- Encounter lifecycle --
/**
* Gets the encounter data for the current node.
*/
export function getCurrentEncounterData(runState: RunState): EncounterData | undefined {
const node = runState.map.nodes.get(runState.currentNodeId);
return node?.encounter;
}
/**
* Checks if the current node has a combat encounter.
*/
export function isCombatEncounter(runState: RunState): boolean {
const encounter = getCurrentEncounterData(runState);
return encounter !== undefined && encounter.enemies.length > 0;
}
/**
* Starts the encounter at the current node.
* Returns the constructed CombatState, or null if no combat encounter.
*/
export function startEncounter(runState: RunState): CombatState | null {
const encounter = getCurrentEncounterData(runState);
if (!encounter || encounter.enemies.length === 0) {
return null;
}
return buildCombatState(encounter, runState);
}
/**
* Resolves a completed combat and applies rewards to the run state.
* Handles: gold loot, item rewards, HP changes.
* Marks the encounter as resolved.
*/
export function resolveCombatEncounter(
runState: RunState,
combatState: CombatState
): { success: true } | { success: false; reason: string } {
if (runState.currentEncounter.resolved) {
return { success: false, reason: '该遭遇已解决' };
}
// Apply HP from combat state back to run state
runState.player.currentHp = Math.max(0, combatState.player.hp);
// Apply loot
for (const loot of combatState.loot) {
if (loot.type === 'gold') {
runState.player.gold += loot.amount;
}
// Item rewards are handled by the caller via addItem()
}
// Mark as resolved
runState.currentEncounter.resolved = true;
runState.currentEncounter.result = {
hpLost: runState.player.maxHp - runState.player.currentHp,
};
runState.resolvedNodeIds.add(runState.currentNodeId);
return { success: true };
}

View File

@ -18,6 +18,19 @@ export type {
RunState, RunState,
} from './types'; } from './types';
// Re-export encounter construction functions
export {
assignEncounterToNode,
assignEncountersFromPool,
assignAllEncounters,
buildCombatState,
createEnemyEntities,
getCurrentEncounterData,
isCombatEncounter,
startEncounter,
resolveCombatEncounter,
} from './encounter';
// -- Constants -- // -- Constants --
const INVENTORY_WIDTH = 6; const INVENTORY_WIDTH = 6;

View File

@ -9,6 +9,7 @@ export type EffectLifecycle = "instant" | "temporary" | "lingering" | "permanent
export type EnemyData = { export type EnemyData = {
readonly id: string; readonly id: string;
readonly name: string; readonly name: string;
readonly intents: readonly IntentData[];
readonly description: string; readonly description: string;
}; };
@ -36,17 +37,16 @@ export type EncounterData = {
readonly type: EncounterType; readonly type: EncounterType;
readonly name: string; readonly name: string;
readonly description: string; readonly description: string;
readonly enemies: readonly [EnemyData, number, readonly [effect: EffectData, stacks: number]]; readonly enemies: readonly [data: EnemyData, hp: number, effects: [EffectData, stacks: number][]][];
readonly dialogue: string; readonly dialogue: string;
}; };
export type IntentData = { export type IntentData = {
readonly id: string;
readonly enemy: EnemyData; readonly enemy: EnemyData;
readonly intentId: string;
readonly initialIntent: boolean; readonly initialIntent: boolean;
readonly nextIntents: readonly string[]; readonly nextIntents: readonly IntentData[];
readonly brokenIntent: readonly string[]; readonly brokenIntent: readonly IntentData[];
readonly initBuffs: readonly [EffectData, stacks: number];
readonly effects: readonly [EffectTarget, EffectData, number][]; readonly effects: readonly [EffectTarget, EffectData, number][];
}; };