Compare commits
No commits in common. "a80852bc590acad5ef8ccbb57cd5fecc70d21ffd" and "fb66ec55c49c3950cfb5493b3b169a1de212b45d" have entirely different histories.
a80852bc59
...
fb66ec55c4
|
|
@ -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: ["onPlay" | "onDraw" | "onDiscard", "self" | "target" | "all" | "random", Effect, number][];
|
readonly effects: readonly ["onPlay" | "onDraw" | "onDiscard", "self" | "target" | "all" | "random", Effect, number];
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
export type Card = CardTable[number];
|
export type Card = CardTable[number];
|
||||||
|
|
|
||||||
|
|
@ -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; initialHp; buffs[]]
|
# enemies: array of [enemyId; hp; buffs[]]
|
||||||
|
|
||||||
id,type,name,description,enemies,dialogue
|
id,type,name,description,enemies,dialogue
|
||||||
string,'minion'|'elite'|'event'|'shop'|'camp'|'curio',string,string,[data: @enemy; hp: int; effects: [effect: @effect;stacks: int][]][],string
|
string,'minion'|'elite'|'event'|'shop'|'camp'|'curio',string,string,[@enemy; int; [effect: @effect;stacks: int]][],string
|
||||||
cactus_pair,minion,仙人掌怪,概念:防+强化。【尖刺X】:对攻击者造成X点伤害。,[仙人掌怪;12;[]];[仙人掌怪;12;[]],
|
cactus_pair,minion,仙人掌怪,概念:防+强化。【尖刺X】:对攻击者造成X点伤害。,[仙人掌怪;20;[]];[仙人掌怪;20;[]],
|
||||||
snake_pair,minion,蛇,概念:攻+强化。给玩家塞入蛇毒牌(1费:打出时移除此牌。弃掉时受到3点伤害)。,[蛇;10;[]],
|
snake_pair,minion,蛇,概念:攻+强化。给玩家塞入蛇毒牌(1费:打出时移除此牌。弃掉时受到3点伤害)。,[蛇;14;[]];[蛇;14;[]],
|
||||||
mummy_cactus,minion,木乃伊,概念:攻+防。【诅咒】:受攻击时物品【攻击】-1,直到弃掉一张该物品的牌。,[木乃伊;14;[]];[仙人掌怪;12;[]],
|
mummy_cactus,minion,木乃伊,概念:攻+防。【诅咒】:受攻击时物品【攻击】-1,直到弃掉一张该物品的牌。,[木乃伊;18;[]];[仙人掌怪;20;[]],
|
||||||
gunslinger,minion,枪手,概念:单回高攻。【瞄准X】:造成双倍伤害。受伤时失去等量【瞄准】,[枪手;12;[]],
|
gunslinger,minion,枪手,概念:单回高攻。【瞄准X】:造成双倍伤害。受伤时失去等量【瞄准】,[枪手;16;[]],
|
||||||
tumbleweed_pair,minion,风卷草,概念:防+强化。【滚动X】:攻击时,每消耗10点【滚动】,造成等量伤害。,[风卷草;16;[]],
|
tumbleweed_pair,minion,风卷草,概念:防+强化。【滚动X】:攻击时,每消耗10点【滚动】,造成等量伤害。,[风卷草;22;[]];[风卷草;22;[]],
|
||||||
vulture_cactus,minion,秃鹫,概念:攻+防。若造成伤害,玩家获得秃鹫之眼(0费状态牌:打出时移除。抓到时获得3层暴露)。,[秃鹫;12;[]];[仙人掌怪;12;[]],
|
vulture_cactus,minion,秃鹫,概念:攻+防。若造成伤害,玩家获得秃鹫之眼(0费状态牌:打出时移除。抓到时获得3层暴露)。,[秃鹫;16;[]];[仙人掌怪;20;[]],
|
||||||
scorpion_snake,minion,沙蝎,概念:攻+强化。【尾刺X】:姿态buff,攻击时,伤害提升X。,[沙蝎;10;[]];[蛇;10;[]],
|
scorpion_snake,minion,沙蝎,概念:攻+强化。【尾刺X】:姿态buff,攻击时,伤害提升X。,[沙蝎;14;[]];[蛇;14;[]],
|
||||||
sandworm_larva,minion,幼沙虫,概念:防+强化。每回合第一次受伤时,玩家失去1点能量。,[幼沙虫;18;[]],
|
sandworm_larva,minion,幼沙虫,概念:防+强化。每回合第一次受伤时,玩家失去1点能量。,[幼沙虫;24;[]],
|
||||||
lizard_pair,minion,蜥蜴,概念:攻+防+逃跑。【脱皮】:若脱皮达到生命上限,则怪物逃跑,玩家不能获得战斗奖励。,[蜥蜴;14;[]],
|
lizard_pair,minion,蜥蜴,概念:攻+防+逃跑。【脱皮】:若脱皮达到生命上限,则怪物逃跑,玩家不能获得战斗奖励。,[蜥蜴;20;[]];[蜥蜴;20;[]],
|
||||||
bandit_gunslinger,minion,沙匪,概念:弱化玩家。【劫掠】:对玩家施加的延时debuff。回合开始时,随机弃掉一张手牌。,[沙匪;12;[]];[枪手;12;[]],
|
bandit_gunslinger,minion,沙匪,概念:弱化玩家。【劫掠】:对玩家施加的延时debuff。回合开始时,随机弃掉一张手牌。,[沙匪;16;[]];[枪手;16;[]],
|
||||||
storm_spirit,elite,风暴之灵,【风暴X】:攻击时,玩家获得1张静电。受伤时失去等量【风暴】。(静电:在手里时受【电击】伤害+1),[风暴之灵;30;[]],
|
storm_spirit,elite,风暴之灵,【风暴X】:攻击时,玩家获得1张静电。受伤时失去等量【风暴】。(静电:在手里时受【电击】伤害+1),[风暴之灵;44;[]],
|
||||||
mounted_gunslinger,elite,骑马枪手,【冲锋X】:受到或造成的伤害翻倍并消耗等量的冲锋。,[骑马枪手;25;[]];[枪手;12;[]],
|
mounted_gunslinger,elite,骑马枪手,【冲锋X】:受到或造成的伤害翻倍并消耗等量的冲锋。,[骑马枪手;50;[]];[枪手;20;[]],
|
||||||
sandworm_king,elite,沙虫王,召唤幼体沙虫;每当玩家弃掉一张牌,恢复1生命。,[沙虫王;40;[]],
|
sandworm_king,elite,沙虫王,召唤幼体沙虫;每当玩家弃掉一张牌,恢复1生命。,[沙虫王;55;[]],
|
||||||
desert_guard,elite,沙漠守卫,召唤木乃伊;会复活木乃伊2次。,[沙漠守卫;35;[]];[木乃伊;14;[]],
|
desert_guard,elite,沙漠守卫,召唤木乃伊;会复活木乃伊2次。,[沙漠守卫;48;[]];[木乃伊;20;[]],
|
||||||
desert_merchant,shop,沙漠商人,商店:可以恢复生命、出售装备、附魔物品。,,
|
desert_merchant,shop,沙漠商人,商店:可以恢复生命、出售装备、附魔物品。,,
|
||||||
nomad_caravan,shop,游牧商队,商队:出售稀有物品、移除牌组中一张牌。,,
|
nomad_caravan,shop,游牧商队,商队:出售稀有物品、移除牌组中一张牌。,,
|
||||||
oasis_campfire,camp,绿洲篝火,篝火:可以恢复生命、补充药水使用次数、获得下次战斗Buff。,,
|
oasis_campfire,camp,绿洲篝火,篝火:可以恢复生命、补充药水使用次数、获得下次战斗Buff。,,
|
||||||
|
|
|
||||||
|
|
|
@ -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: [data: Enemy, hp: number, effects: [effect: Effect, stacks: number][]][];
|
readonly enemies: readonly [Enemy, number, readonly [readonly effect: Effect, readonly stacks: number]];
|
||||||
readonly dialogue: string;
|
readonly dialogue: string;
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
id,name,intents,description
|
id,name,description
|
||||||
string,string,@intent[],string
|
string,string,string
|
||||||
仙人掌怪,仙人掌怪,[仙人掌怪-boost;仙人掌怪-defend;仙人掌怪-attack],防+强化。【尖刺X】:对攻击者造成X点伤害。
|
仙人掌怪,仙人掌怪,防+强化。【尖刺X】:对攻击者造成X点伤害。
|
||||||
蛇,蛇,[蛇-poison;蛇-attack;蛇-boost],攻+强化。给玩家塞入蛇毒牌(1费:打出时移除此牌。弃掉时受到3点伤害)。
|
蛇,蛇,攻+强化。给玩家塞入蛇毒牌(1费:打出时移除此牌。弃掉时受到3点伤害)。
|
||||||
木乃伊,木乃伊,[木乃伊-attack;木乃伊-defend;木乃伊-curse],攻+防。【诅咒】:受攻击时物品【攻击】-1,直到弃掉一张该物品的牌。
|
木乃伊,木乃伊,攻+防。【诅咒】:受攻击时物品【攻击】-1,直到弃掉一张该物品的牌。
|
||||||
枪手,枪手,[枪手-aim;枪手-attack;枪手-defend],单回高攻。【瞄准X】:造成双倍伤害。受伤时失去等量【瞄准】。
|
枪手,枪手,单回高攻。【瞄准X】:造成双倍伤害。受伤时失去等量【瞄准】。
|
||||||
风卷草,风卷草,[风卷草-boost;风卷草-defend;风卷草-attack],防+强化。【滚动X】:攻击时,每消耗10点【滚动】,造成等量伤害。
|
风卷草,风卷草,防+强化。【滚动X】:攻击时,每消耗10点【滚动】,造成等量伤害。
|
||||||
秃鹫,秃鹫,[秃鹫-attack;秃鹫-defend],攻+防。若造成伤害,玩家获得秃鹫之眼(0费状态牌:打出时移除。抓到时获得3层暴露)。
|
秃鹫,秃鹫,攻+防。若造成伤害,玩家获得秃鹫之眼(0费状态牌:打出时移除。抓到时获得3层暴露)。
|
||||||
沙蝎,沙蝎,[沙蝎-boost;沙蝎-attack],攻+强化。【尾刺X】:姿态buff,攻击时,伤害提升X。
|
沙蝎,沙蝎,攻+强化。【尾刺X】:姿态buff,攻击时,伤害提升X。
|
||||||
幼沙虫,幼沙虫,[幼沙虫-defend;幼沙虫-boost;幼沙虫-attack],防+强化。每回合第一次受伤时,玩家失去1点能量。
|
幼沙虫,幼沙虫,防+强化。每回合第一次受伤时,玩家失去1点能量。
|
||||||
蜥蜴,蜥蜴,[蜥蜴-attack;蜥蜴-defend;蜥蜴-molt],攻+防+逃跑。【脱皮】:若脱皮达到生命上限,则怪物逃跑,玩家不能获得战斗奖励。
|
蜥蜴,蜥蜴,攻+防+逃跑。【脱皮】:若脱皮达到生命上限,则怪物逃跑,玩家不能获得战斗奖励。
|
||||||
沙匪,沙匪,[沙匪-attack;沙匪-heavyAttack;沙匪-debuff],弱化玩家。【劫掠】:对玩家施加的延时debuff。回合开始时,随机弃掉一张手牌。
|
沙匪,沙匪,弱化玩家。【劫掠】:对玩家施加的延时debuff。回合开始时,随机弃掉一张手牌。
|
||||||
风暴之灵,风暴之灵,[风暴之灵-storm;风暴之灵-attack;风暴之灵-defend],【风暴X】:攻击时,玩家获得1张静电。受伤时失去等量【风暴】。(静电:在手里时受【电击】伤害+1)
|
风暴之灵,风暴之灵,【风暴X】:攻击时,玩家获得1张静电。受伤时失去等量【风暴】。(静电:在手里时受【电击】伤害+1)
|
||||||
骑马枪手,骑马枪手,[骑马枪手-charge;骑马枪手-attack;骑马枪手-defend],【冲锋X】:受到或造成的伤害翻倍并消耗等量的冲锋。
|
骑马枪手,骑马枪手,【冲锋X】:受到或造成的伤害翻倍并消耗等量的冲锋。
|
||||||
沙虫王,沙虫王,[沙虫王-summon;沙虫王-attack;沙虫王-defend],召唤幼体沙虫;每当玩家弃掉一张牌,恢复1生命。
|
沙虫王,沙虫王,召唤幼体沙虫;每当玩家弃掉一张牌,恢复1生命。
|
||||||
沙漠守卫,沙漠守卫,[沙漠守卫-summon;沙漠守卫-attack;沙漠守卫-defend;沙漠守卫-revive],召唤木乃伊;会复活木乃伊2次。
|
沙漠守卫,沙漠守卫,召唤木乃伊;会复活木乃伊2次。
|
||||||
|
|
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
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;
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,52 @@
|
||||||
# intentId: unique intent state ID (e.g. "仙人掌怪-boost")
|
# enemyDesert: enemy templates and their intent state machines
|
||||||
# enemy: enemy template this intent belongs to
|
# enemy: enemy template ID (unique identifier for this enemy type)
|
||||||
|
# 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 intent (applied when intent becomes active)
|
# initBuffs: initial buffs for this enemy type (applied to all instances)
|
||||||
# effects: effects executed when this intent is active
|
# effects: effects executed when this intent is active
|
||||||
|
|
||||||
id,enemy,initialIntent,nextIntents,brokenIntent,effects
|
enemy,intentId,initialIntent,nextIntents,brokenIntent,initBuffs,effects
|
||||||
string,@enemy,boolean,@intent[],@intent[],['self'|'player'|'team';@effect;number][]
|
@enemy,string,boolean,string[],string[],[@effect;stacks: int][],['self'|'player'|'team';@effect;number][]
|
||||||
仙人掌怪-boost,仙人掌怪,true,仙人掌怪-boost;仙人掌怪-defend,,[self;spike;1];[self;defend;4]
|
仙人掌怪,boost,true,boost;defend;defend,,[spike;1],[self;spike;1];[self;defend;4]
|
||||||
仙人掌怪-defend,仙人掌怪,false,仙人掌怪-attack,,[self;defend;8]
|
仙人掌怪,defend,false,attack,,[spike;1],[self;defend;8]
|
||||||
仙人掌怪-attack,仙人掌怪,false,仙人掌怪-boost,,[player;attack;5]
|
仙人掌怪,attack,false,boost,,[spike;1],[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,,[self;tailSting;2]
|
沙蝎,boost,true,attack;attack,,[tailSting;1],[self;tailSting;2]
|
||||||
沙蝎-attack,沙蝎,false,沙蝎-boost;沙蝎-attack,,[player;attack;6]
|
沙蝎,attack,false,boost;attack,,[tailSting;1],[player;attack;6]
|
||||||
幼沙虫-defend,幼沙虫,true,幼沙虫-defend;幼沙虫-boost,,[self;defend;6]
|
幼沙虫,defend,true,defend;boost,,[energyDrain;1],[self;defend;6]
|
||||||
幼沙虫-boost,幼沙虫,false,幼沙虫-attack;幼沙虫-defend,,[self;energyDrain;1];[self;defend;4]
|
幼沙虫,boost,false,attack;defend,,[energyDrain;1],[self;energyDrain;1];[self;defend;4]
|
||||||
幼沙虫-attack,幼沙虫,false,幼沙虫-defend;幼沙虫-defend,,[player;attack;5]
|
幼沙虫,attack,false,defend;defend,,[energyDrain;1],[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.
|
|
|
@ -2,12 +2,13 @@ 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: Intent[];
|
readonly nextIntents: readonly string[];
|
||||||
readonly brokenIntent: Intent[];
|
readonly brokenIntent: readonly string[];
|
||||||
readonly effects: ["self" | "player" | "team", Effect, number][];
|
readonly initBuffs: readonly [Effect, readonly stacks: number];
|
||||||
|
readonly effects: readonly ["self" | "player" | "team", Effect, number];
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
export type Intent = IntentTable[number];
|
export type Intent = IntentTable[number];
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,5 @@
|
||||||
import {CombatEntity, CombatGameContext, CombatState, EffectTable, PlayerEntity} from "./types";
|
import {CombatEntity, CombatState, EffectTable, PlayerEntity} from "./types";
|
||||||
import {
|
import {CardData, EffectData} from "@/samples/slay-the-spire-like/system/types";
|
||||||
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";
|
||||||
|
|
||||||
|
|
@ -86,25 +80,6 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import {
|
||||||
addItemEffect,
|
addItemEffect,
|
||||||
getAliveEnemies, onEntityPostureDamage,
|
getAliveEnemies, onEntityPostureDamage,
|
||||||
onEntityEffectUpkeep,
|
onEntityEffectUpkeep,
|
||||||
onPlayerItemEffectUpkeep, onItemDiscard, onItemPlay, payCardCost, getCombatEntity, getEffectTargets
|
onPlayerItemEffectUpkeep, onItemDiscard, onItemPlay, payCardCost
|
||||||
} 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 = getCombatEntity(draft, ctx.entityKey);
|
const entity = ctx.entityKey === "player" ? draft.player : draft.enemies.find(e => e.id === 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,13 +65,6 @@ 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 => {
|
||||||
|
|
@ -79,26 +72,12 @@ 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;
|
||||||
|
|
@ -159,12 +138,26 @@ 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.currentIntent;
|
const intent = enemy.intents[enemy.currentIntentId];
|
||||||
if(!intent) return;
|
if(!intent) return;
|
||||||
|
|
||||||
for(const [target, effect, stacks] of intent.effects){
|
for(const [target, effect, stacks] of intent.effects){
|
||||||
for(const entity of getEffectTargets(target, ctx.game))
|
if(target === 'team'){
|
||||||
await triggers.onEffectApplied.execute(ctx.game, { effect, entityKey: entity.id, stacks, });
|
for(const enemy of getAliveEnemies(ctx.game.value)){
|
||||||
|
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 => {
|
||||||
|
|
@ -172,13 +165,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.currentIntent;
|
const intent = enemy.intents[enemy.currentIntentId];
|
||||||
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.currentIntent = nextIntents[nextIndex];
|
enemy.currentIntentId = nextIntents[nextIndex];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ 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;
|
||||||
|
|
@ -22,9 +21,10 @@ 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>;
|
||||||
currentIntent: IntentData;
|
currentIntentId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CombatPhase = "playerTurn" | "enemyTurn" | "combatEnd";
|
export type CombatPhase = "playerTurn" | "enemyTurn" | "combatEnd";
|
||||||
|
|
|
||||||
|
|
@ -1,245 +0,0 @@
|
||||||
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 };
|
|
||||||
}
|
|
||||||
|
|
@ -18,19 +18,6 @@ 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;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ 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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -37,16 +36,17 @@ export type EncounterData = {
|
||||||
readonly type: EncounterType;
|
readonly type: EncounterType;
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly description: string;
|
readonly description: string;
|
||||||
readonly enemies: readonly [data: EnemyData, hp: number, effects: [EffectData, stacks: number][]][];
|
readonly enemies: readonly [EnemyData, number, readonly [effect: 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 IntentData[];
|
readonly nextIntents: readonly string[];
|
||||||
readonly brokenIntent: readonly IntentData[];
|
readonly brokenIntent: readonly string[];
|
||||||
|
readonly initBuffs: readonly [EffectData, stacks: number];
|
||||||
readonly effects: readonly [EffectTarget, EffectData, number][];
|
readonly effects: readonly [EffectTarget, EffectData, number][];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue