Compare commits
No commits in common. "a5e2e4888e2db4eeac5df4fea02697a41f619ea2" and "2eec66885161a48b7ec8b0b4c3e4d5c4cc7fdf72" have entirely different histories.
a5e2e4888e
...
2eec668851
|
|
@ -2,42 +2,37 @@
|
||||||
# type: 'item' = inventory item card, 'status' = status effect card
|
# type: 'item' = inventory item card, 'status' = status effect card
|
||||||
# costType: 'energy' = costs energy per turn, 'uses' = limited uses, 'none' = free
|
# costType: 'energy' = costs energy per turn, 'uses' = limited uses, 'none' = free
|
||||||
# targetType: 'single' = target one enemy, 'none' = no target
|
# targetType: 'single' = target one enemy, 'none' = no target
|
||||||
|
|
||||||
# type CardType = 'item' | 'status'
|
|
||||||
# type CardCostType = 'energy' | 'uses' | 'none'
|
|
||||||
# type CardTargetType = 'player' | 'enemy' | 'enemies'
|
|
||||||
|
|
||||||
# inject effects = ~cardEffect(card)
|
# inject effects = ~cardEffect(card)
|
||||||
|
|
||||||
id,name,desc,type,costType,costCount,targetType
|
id,name,desc,type,costType,costCount,targetType
|
||||||
string,string,string,CardType,CardCostType,int,CardTargetType
|
string,string,string,'item'|'status','energy'|'uses'|'none',int,'single'|'none'
|
||||||
sword,剑,【攻击2】【攻击2】,item,energy,1,enemy
|
sword,剑,【攻击2】【攻击2】,item,energy,1,single
|
||||||
greataxe,长斧,对全体【攻击5】,item,energy,2,enemies
|
greataxe,长斧,对全体【攻击5】,item,energy,2,none
|
||||||
spear,长枪,【攻击2】【攻击2】【攻击2】,item,energy,1,enemy
|
spear,长枪,【攻击2】【攻击2】【攻击2】,item,energy,1,single
|
||||||
dagger,短刀,【攻击3】【攻击3】,item,energy,1,enemy
|
dagger,短刀,【攻击3】【攻击3】,item,energy,1,single
|
||||||
dart,飞镖,【攻击1】抓一张牌,item,energy,0,enemy
|
dart,飞镖,【攻击1】抓一张牌,item,energy,0,single
|
||||||
crossbow,十字弩,【攻击6】对同一目标打出其他十字弩,item,energy,2,enemy
|
crossbow,十字弩,【攻击6】对同一目标打出其他十字弩,item,energy,2,single
|
||||||
shield,盾,【防御3】,item,energy,1,player
|
shield,盾,【防御3】,item,energy,1,none
|
||||||
hat,斗笠,【防御8】,item,energy,2,player
|
hat,斗笠,【防御8】,item,energy,2,none
|
||||||
cape,披风,【防御2】下回合【防御2】,item,energy,1,player
|
cape,披风,【防御2】下回合【防御2】,item,energy,1,none
|
||||||
bracer,护腕,【防御1】抓1张牌,item,energy,0,player
|
bracer,护腕,【防御1】抓1张牌,item,energy,0,none
|
||||||
greatshield,大盾,【防御5】,item,energy,1,player
|
greatshield,大盾,【防御5】,item,energy,1,none
|
||||||
chainmail,锁子甲,本回合受到伤害-3,item,energy,1,player
|
chainmail,锁子甲,本回合受到伤害-3,item,energy,1,none
|
||||||
bandage,绷带,从牌堆或弃牌堆随机移除1张伤口,item,uses,3,player
|
bandage,绷带,从牌堆或弃牌堆随机移除1张伤口,item,uses,3,none
|
||||||
poisonPotion,淬毒药剂,周围物品的【攻击】+2,item,uses,3,player
|
poisonPotion,淬毒药剂,周围物品的【攻击】+2,item,uses,3,none
|
||||||
fortifyPotion,强固药剂,周围物品的【防御】+2,item,uses,3,player
|
fortifyPotion,强固药剂,周围物品的【防御】+2,item,uses,3,none
|
||||||
vitalityPotion,活力药剂,获得1点能量,item,uses,3,player
|
vitalityPotion,活力药剂,获得1点能量,item,uses,3,none
|
||||||
focusPotion,集中药剂,抓2张牌,item,uses,3,player
|
focusPotion,集中药剂,抓2张牌,item,uses,3,none
|
||||||
healingPotion,治疗药剂,从牌堆或弃牌堆移除3张伤口,item,uses,3,player
|
healingPotion,治疗药剂,从牌堆或弃牌堆移除3张伤口,item,uses,3,none
|
||||||
waterBag,水袋,下回合开始时获得1能量抓2张牌,item,energy,1,player
|
waterBag,水袋,下回合开始时获得1能量抓2张牌,item,energy,1,none
|
||||||
rope,绳索,周围物品的牌【防御】+2直到打出,item,energy,1,player
|
rope,绳索,周围物品的牌【防御】+2直到打出,item,energy,1,none
|
||||||
belt,腰带,从牌堆周围物品的牌当中选择一张加入手牌,item,energy,0,player
|
belt,腰带,从牌堆周围物品的牌当中选择一张加入手牌,item,energy,0,none
|
||||||
torch,火把,下次打出周围物品的牌时将其消耗并获得1能量,item,energy,1,player
|
torch,火把,下次打出周围物品的牌时将其消耗并获得1能量,item,energy,1,none
|
||||||
whetstone,磨刀石,周围物品的牌【攻击】+3直到打出,item,energy,1,player
|
whetstone,磨刀石,周围物品的牌【攻击】+3直到打出,item,energy,1,none
|
||||||
blacksmithHammer,铁匠锤,从牌堆/弃牌堆选择一张牌随机变为一张周围物品的牌,item,energy,1,player
|
blacksmithHammer,铁匠锤,从牌堆/弃牌堆选择一张牌随机变为一张周围物品的牌,item,energy,1,none
|
||||||
wound,伤口,无效果占用手牌和牌堆,status,none,0,player
|
wound,伤口,无效果占用手牌和牌堆,status,none,0,none
|
||||||
venom,蛇毒,弃掉时受到3点伤害,status,none,0,player
|
venom,蛇毒,弃掉时受到3点伤害,status,none,0,none
|
||||||
curse,诅咒,受攻击时物品攻击-1直到弃掉一张该物品的牌,status,none,0,player
|
curse,诅咒,受攻击时物品攻击-1直到弃掉一张该物品的牌,status,none,0,none
|
||||||
static,静电,在手里时受电击伤害+1,status,none,0,player
|
static,静电,在手里时受电击伤害+1,status,none,0,none
|
||||||
fatigue,疲劳,占用手牌,status,none,0,player
|
fatigue,疲劳,占用手牌,status,none,0,none
|
||||||
vultureEye,秃鹫之眼,抓到时获得3层暴露,status,none,0,player
|
vultureEye,秃鹫之眼,抓到时获得3层暴露,status,none,0,none
|
||||||
|
|
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
import type { CardEffect } from './cardEffect.csv';
|
import type { CardEffect } from './cardEffect.csv';
|
||||||
|
|
||||||
export type CardType = 'item' | 'status';
|
|
||||||
export type CardCostType = 'energy' | 'uses' | 'none';
|
|
||||||
export type CardTargetType = 'player' | 'enemy' | 'enemies';
|
|
||||||
|
|
||||||
type CardTable = readonly {
|
type CardTable = readonly {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly desc: string;
|
readonly desc: string;
|
||||||
readonly type: CardType;
|
readonly type: "item" | "status";
|
||||||
readonly costType: CardCostType;
|
readonly costType: "energy" | "uses" | "none";
|
||||||
readonly costCount: number;
|
readonly costCount: number;
|
||||||
readonly targetType: CardTargetType;
|
readonly targetType: "single" | "none";
|
||||||
readonly effects: CardEffect[];
|
readonly effects: CardEffect[];
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,32 @@
|
||||||
# type CardEffectTrigger = 'onPlay' | 'onDraw' | 'onDiscard'
|
|
||||||
# type CardEffectTarget = 'user' | 'eachTarget' | 'eachEnemy' | 'randomEnemy' | 'player'
|
|
||||||
# type CardEffectList = [effect: @effect; stacks: number][]
|
|
||||||
|
|
||||||
id,card,trigger,target,effects
|
id,card,trigger,target,effects
|
||||||
string,@card,CardEffectTrigger,CardEffectTarget,CardEffectList
|
string,@card,'onPlay'|'onDraw'|'onDiscard','self'|'target'|'all'|'random',[@effect;number][]
|
||||||
sword,sword,onPlay,eachTarget,[attack;2];[attack;2]
|
sword,sword,onPlay,target,[attack;2];[attack;2]
|
||||||
greataxe,greataxe,onPlay,eachTarget,[attack;5]
|
greataxe,greataxe,onPlay,all,[attack;5]
|
||||||
spear,spear,onPlay,eachTarget,[attack;2];[attack;2];[attack;2]
|
spear,spear,onPlay,target,[attack;2];[attack;2];[attack;2]
|
||||||
dagger,dagger,onPlay,eachTarget,[attack;3];[attack;3]
|
dagger,dagger,onPlay,target,[attack;3];[attack;3]
|
||||||
dart,dart,onPlay,eachTarget,[attack;1]
|
dart,dart,onPlay,target,[attack;1]
|
||||||
dart-draw,dart,onPlay,user,[draw;1]
|
dart-draw,dart,onPlay,self,[draw;1]
|
||||||
crossbow,crossbow,onPlay,eachTarget,[attack;6]
|
crossbow,crossbow,onPlay,target,[attack;6]
|
||||||
crossbow-combo,crossbow,onPlay,user,[crossbow;0]
|
crossbow-combo,crossbow,onPlay,self,[crossbow;0]
|
||||||
shield,shield,onPlay,user,[defend;3]
|
shield,shield,onPlay,self,[defend;3]
|
||||||
hat,hat,onPlay,user,[defend;8]
|
hat,hat,onPlay,self,[defend;8]
|
||||||
cape,cape,onPlay,user,[defend;2];[defendNext;2]
|
cape,cape,onPlay,self,[defend;2];[defendNext;2]
|
||||||
bracer,bracer,onPlay,user,[defend;1];[draw;1]
|
bracer,bracer,onPlay,self,[defend;1];[draw;1]
|
||||||
greatshield,greatshield,onPlay,user,[defend;5]
|
greatshield,greatshield,onPlay,self,[defend;5]
|
||||||
chainmail,chainmail,onPlay,user,[damageReduce;3]
|
chainmail,chainmail,onPlay,self,[damageReduce;3]
|
||||||
bandage,bandage,onPlay,user,[removeWound;1]
|
bandage,bandage,onPlay,self,[removeWound;1]
|
||||||
poisonPotion,poisonPotion,onPlay,user,[attackBuff;2]
|
poisonPotion,poisonPotion,onPlay,self,[attackBuff;2]
|
||||||
fortifyPotion,fortifyPotion,onPlay,user,[defendBuff;2]
|
fortifyPotion,fortifyPotion,onPlay,self,[defendBuff;2]
|
||||||
vitalityPotion,vitalityPotion,onPlay,user,[gainEnergy;1]
|
vitalityPotion,vitalityPotion,onPlay,self,[gainEnergy;1]
|
||||||
focusPotion,focusPotion,onPlay,user,[draw;2]
|
focusPotion,focusPotion,onPlay,self,[draw;2]
|
||||||
healingPotion,healingPotion,onPlay,user,[removeWound;3]
|
healingPotion,healingPotion,onPlay,self,[removeWound;3]
|
||||||
waterBag,waterBag,onPlay,user,[energyNext;1];[drawNext;2]
|
waterBag,waterBag,onPlay,self,[energyNext;1];[drawNext;2]
|
||||||
rope,rope,onPlay,user,[defendBuffUntilPlay;2]
|
rope,rope,onPlay,self,[defendBuffUntilPlay;2]
|
||||||
belt,belt,onPlay,user,[drawChoice;1]
|
belt,belt,onPlay,self,[drawChoice;1]
|
||||||
torch,torch,onPlay,user,[burnForEnergy;1]
|
torch,torch,onPlay,self,[burnForEnergy;1]
|
||||||
whetstone,whetstone,onPlay,user,[attackBuffUntilPlay;3]
|
whetstone,whetstone,onPlay,self,[attackBuffUntilPlay;3]
|
||||||
blacksmithHammer,blacksmithHammer,onPlay,user,[transformRandom;1]
|
blacksmithHammer,blacksmithHammer,onPlay,self,[transformRandom;1]
|
||||||
venom,venom,onDiscard,user,[attack;3]
|
venom,venom,onDiscard,self,[attack;3]
|
||||||
curse,curse,onDraw,user,[curse;1]
|
curse,curse,onDraw,self,[curse;1]
|
||||||
static,static,onDraw,user,[static;1]
|
static,static,onDraw,self,[static;1]
|
||||||
vultureEye,vultureEye,onDraw,user,[expose;3]
|
vultureEye,vultureEye,onDraw,self,[expose;3]
|
||||||
|
|
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
import type { Card } from './card.csv';
|
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 CardEffectTarget = 'user' | 'eachTarget' | 'eachEnemy' | 'randomEnemy' | 'player';
|
|
||||||
export type CardEffectList = [effect: Effect, stacks: number][];
|
|
||||||
|
|
||||||
type CardEffectTable = readonly {
|
type CardEffectTable = readonly {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly card: Card;
|
readonly card: Card;
|
||||||
readonly trigger: CardEffectTrigger;
|
readonly trigger: "onPlay" | "onDraw" | "onDiscard";
|
||||||
readonly target: CardEffectTarget;
|
readonly target: "self" | "target" | "all" | "random";
|
||||||
readonly effects: CardEffectList;
|
readonly effects: [Effect, number][];
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
export type CardEffect = CardEffectTable[number];
|
export type CardEffect = CardEffectTable[number];
|
||||||
|
|
|
||||||
|
|
@ -11,42 +11,40 @@
|
||||||
# itemUntilDiscard: 施加buff到周围物品,物品被弃掉后失效
|
# itemUntilDiscard: 施加buff到周围物品,物品被弃掉后失效
|
||||||
# itemPermanent: 施加buff到周围物品,持续整场冒险
|
# itemPermanent: 施加buff到周围物品,持续整场冒险
|
||||||
|
|
||||||
# type EffectLifecycle = 'instant' | 'temporary' | 'lingering' | 'permanent' | 'posture' | 'item' | 'itemTemporary' | 'itemUntilPlay' | 'itemUntilDiscard' | 'itemPermanent'
|
id, name, description, lifecycle
|
||||||
|
string, string, string, 'instant'|'temporary'|'lingering'|'permanent'|'posture'|'item'|'itemTemporary'|'itemUntilPlay'|'itemUntilDiscard'|'itemPermanent'
|
||||||
id, name, description, lifecycle, emoji
|
attack, 攻击, 对对手造成伤害, instant
|
||||||
string, string, string, EffectLifecycle, string
|
defend, 防御, 抵消下次行动前受到的伤害, temporary
|
||||||
attack, 攻击, 对对手造成伤害, instant, ⚔️
|
spike, 尖刺, 对攻击者造成X点伤害, permanent
|
||||||
defend, 防御, 抵消下次行动前受到的伤害, temporary, 🛡️
|
venom, 蛇毒, 同名状态牌/1费:打出时移除此牌。弃掉时受到3点伤害, instant
|
||||||
spike, 尖刺, 对攻击者造成X点伤害, permanent, 🌵
|
curse, 诅咒, 受攻击时物品攻击-1,直到弃掉一张该物品的牌, lingering
|
||||||
venom, 蛇毒, 同名状态牌/1费:打出时移除此牌。弃掉时受到3点伤害, instant, 🧪
|
aim, 瞄准, 造成双倍伤害,受伤时失去等量瞄准, posture
|
||||||
curse, 诅咒, 受攻击时物品攻击-1,直到弃掉一张该物品的牌, lingering, 💀
|
roll, 滚动, 攻击时每消耗10点滚动造成等量伤害, posture
|
||||||
aim, 瞄准, 造成双倍伤害,受伤时失去等量瞄准, posture, 🎯
|
rollDamage, 滚动攻击, 消耗滚动层数造成的伤害, instant
|
||||||
roll, 滚动, 攻击时每消耗10点滚动造成等量伤害, posture, 🎲
|
vultureEye, 秃鹫之眼, 抓到时获得3层暴露(临时debuff,受到的伤害+1/每层), instant
|
||||||
rollDamage, 滚动攻击, 消耗滚动层数造成的伤害, instant, 💥
|
tailSting, 尾刺, 攻击时,伤害提升X, posture
|
||||||
vultureEye, 秃鹫之眼, 抓到时获得3层暴露(临时debuff,受到的伤害+1/每层), instant, 👁️
|
energyDrain, 能量吸取, 受伤时,玩家失去X点能量, lingering
|
||||||
tailSting, 尾刺, 攻击时,伤害提升X, posture, 🦂
|
molt, 脱皮, 若脱皮达到生命上限则怪物逃跑, posture
|
||||||
energyDrain, 能量吸取, 受伤时,玩家失去X点能量, lingering, 🔋
|
discard, 劫掠, 回合开始时随机弃掉一张手牌, lingering
|
||||||
molt, 脱皮, 若脱皮达到生命上限则怪物逃跑, posture, 🐚
|
storm, 风暴, 攻击时给玩家塞入1张静电, permanent
|
||||||
discard, 劫掠, 回合开始时随机弃掉一张手牌, lingering, 🗑️
|
static, 静电, 在手里时受电击伤害+1, permanent
|
||||||
storm, 风暴, 攻击时给玩家塞入1张静电, permanent, ⚡
|
charge, 冲锋, 受到或造成的伤害翻倍并消耗等量冲锋, lingering
|
||||||
static, 静电, 在手里时受电击伤害+1, permanent, ⚡
|
summonMummy, 召唤木乃伊, 召唤1个木乃伊, instant
|
||||||
charge, 冲锋, 受到或造成的伤害翻倍并消耗等量冲锋, lingering, 🐎
|
summonSandwormLarva, 召唤幼沙虫, 召唤1个幼沙虫, instant
|
||||||
summonMummy, 召唤木乃伊, 召唤1个木乃伊, instant, 🧟
|
reviveMummy, 复活木乃伊, 复活1个已死亡的木乃伊, instant
|
||||||
summonSandwormLarva, 召唤幼沙虫, 召唤1个幼沙虫, instant, 🐛
|
draw, 抓牌, 抓X张牌, instant
|
||||||
reviveMummy, 复活木乃伊, 复活1个已死亡的木乃伊, instant, 🌅
|
crossbow, 十字弩连击, 对同一目标打出其他十字弩, instant
|
||||||
draw, 抓牌, 抓X张牌, instant, 🃏
|
defendNext, 下回合防御, 下回合开始时获得防御, temporary
|
||||||
crossbow, 十字弩连击, 对同一目标打出其他十字弩, instant, 🏹
|
damageReduce, 减伤, 本回合受到的伤害减少X, temporary
|
||||||
defendNext, 下回合防御, 下回合开始时获得防御, temporary, 🛡️
|
removeWound, 移除伤口, 从牌堆或弃牌堆移除X张伤口, instant
|
||||||
damageReduce, 减伤, 本回合受到的伤害减少X, temporary, 📉
|
attackBuff, 攻击增益, 周围物品的攻击+X, itemUntilPlay
|
||||||
removeWound, 移除伤口, 从牌堆或弃牌堆移除X张伤口, instant, 🩹
|
defendBuff, 防御增益, 周围物品的防御+X, itemUntilPlay
|
||||||
attackBuff, 攻击增益, 周围物品的攻击+X, itemUntilPlay, ⬆️
|
gainEnergy, 获得能量, 获得X点能量, instant
|
||||||
defendBuff, 防御增益, 周围物品的防御+X, itemUntilPlay, ⬆️
|
energyNext, 下回合获能量, 下回合开始时获得X点能量, temporary
|
||||||
gainEnergy, 获得能量, 获得X点能量, instant, ⚡
|
drawNext, 下回合抓牌, 下回合开始时抓X张牌, temporary
|
||||||
energyNext, 下回合获能量, 下回合开始时获得X点能量, temporary, ⚡
|
defendBuffUntilPlay, 防御增益直到打出, 周围物品的牌防御+X直到打出, itemUntilPlay
|
||||||
drawNext, 下回合抓牌, 下回合开始时抓X张牌, temporary, 🃏
|
drawChoice, 选择抓牌, 从牌堆周围物品的牌中选择一张加入手牌, instant
|
||||||
defendBuffUntilPlay, 防御增益直到打出, 周围物品的牌防御+X直到打出, itemUntilPlay, 🛡️
|
burnForEnergy, 消耗获能量, 打出周围物品的牌时消耗并获得X能量, itemUntilPlay
|
||||||
drawChoice, 选择抓牌, 从牌堆周围物品的牌中选择一张加入手牌, instant, 🔍
|
attackBuffUntilPlay, 攻击增益直到打出, 周围物品的牌攻击+X直到打出, itemUntilPlay
|
||||||
burnForEnergy, 消耗获能量, 打出周围物品的牌时消耗并获得X能量, itemUntilPlay, 🔥
|
transformRandom, 随机变牌, 选择一张牌随机变为周围物品的牌, instant
|
||||||
attackBuffUntilPlay, 攻击增益直到打出, 周围物品的牌攻击+X直到打出, itemUntilPlay, ⚔️
|
expose, 暴露, 受到的伤害+1/每层, temporary
|
||||||
transformRandom, 随机变牌, 选择一张牌随机变为周围物品的牌, instant, 🌀
|
|
||||||
expose, 暴露, 受到的伤害+1/每层, temporary, 👁️
|
|
||||||
|
|
|
||||||
|
Can't render this file because it has a wrong number of fields in line 14.
|
|
|
@ -1,11 +1,8 @@
|
||||||
export type EffectLifecycle = 'instant' | 'temporary' | 'lingering' | 'permanent' | 'posture' | 'item' | 'itemTemporary' | 'itemUntilPlay' | 'itemUntilDiscard' | 'itemPermanent';
|
|
||||||
|
|
||||||
type EffectTable = readonly {
|
type EffectTable = readonly {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly description: string;
|
readonly description: string;
|
||||||
readonly lifecycle: EffectLifecycle;
|
readonly lifecycle: "instant" | "temporary" | "lingering" | "permanent" | "posture" | "item" | "itemTemporary" | "itemUntilPlay" | "itemUntilDiscard" | "itemPermanent";
|
||||||
readonly emoji: string;
|
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
export type Effect = EffectTable[number];
|
export type Effect = EffectTable[number];
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,8 @@
|
||||||
# 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; initialHp; buffs[]]
|
||||||
|
|
||||||
# type EncounterType = 'minion' | 'elite' | 'event' | 'shop' | 'camp' | 'curio'
|
|
||||||
# type EnemyList = [data: @enemy; hp: int; effects: [effect: @effect; stacks: int][]][]
|
|
||||||
|
|
||||||
id,type,name,description,enemies,dialogue
|
id,type,name,description,enemies,dialogue
|
||||||
string,EncounterType,string,string,EnemyList,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点伤害。,[仙人掌怪;12;[]];[仙人掌怪;12;[]],
|
cactus_pair,minion,仙人掌怪,概念:防+强化。【尖刺X】:对攻击者造成X点伤害。,[仙人掌怪;12;[]];[仙人掌怪;12;[]],
|
||||||
snake_pair,minion,蛇,概念:攻+强化。给玩家塞入蛇毒牌(1费:打出时移除此牌。弃掉时受到3点伤害)。,[蛇;10;[]],
|
snake_pair,minion,蛇,概念:攻+强化。给玩家塞入蛇毒牌(1费:打出时移除此牌。弃掉时受到3点伤害)。,[蛇;10;[]],
|
||||||
mummy_cactus,minion,木乃伊,概念:攻+防。【诅咒】:受攻击时物品【攻击】-1,直到弃掉一张该物品的牌。,[木乃伊;14;[]];[仙人掌怪;12;[]],
|
mummy_cactus,minion,木乃伊,概念:攻+防。【诅咒】:受攻击时物品【攻击】-1,直到弃掉一张该物品的牌。,[木乃伊;14;[]];[仙人掌怪;12;[]],
|
||||||
|
|
|
||||||
|
Can't render this file because it has a wrong number of fields in line 12.
|
|
|
@ -1,15 +1,12 @@
|
||||||
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 EncounterType = 'minion' | 'elite' | 'event' | 'shop' | 'camp' | 'curio';
|
|
||||||
export type EnemyList = [data: Enemy, hp: int, effects: [effect: Effect, stacks: int][]][];
|
|
||||||
|
|
||||||
type EncounterTable = readonly {
|
type EncounterTable = readonly {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly type: EncounterType;
|
readonly type: "minion" | "elite" | "event" | "shop" | "camp" | "curio";
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly description: string;
|
readonly description: string;
|
||||||
readonly enemies: EnemyList;
|
readonly enemies: [data: Enemy, hp: number, effects: [effect: Effect, stacks: number][]][];
|
||||||
readonly dialogue: string;
|
readonly dialogue: string;
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,5 @@
|
||||||
import getItems, { Item } from "./item.csv";
|
import getItems, { Item } from "./item.csv";
|
||||||
|
|
||||||
export type * from "./card.csv";
|
|
||||||
export type * from "./cardEffect.csv";
|
|
||||||
export type * from "./effect.csv";
|
|
||||||
export type * from "./encounter.csv";
|
|
||||||
export type * from "./enemy.csv";
|
|
||||||
export type * from "./intent.csv";
|
|
||||||
export type * from "./item.csv";
|
|
||||||
|
|
||||||
export { default as getCards } from "./card.csv";
|
export { default as getCards } from "./card.csv";
|
||||||
export { default as getEffects } from "./effect.csv";
|
export { default as getEffects } from "./effect.csv";
|
||||||
export { default as getEncounters } from "./encounter.csv";
|
export { default as getEncounters } from "./encounter.csv";
|
||||||
|
|
|
||||||
|
|
@ -6,50 +6,46 @@
|
||||||
# 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 IntentEffect = [IntentEffectTarget;@effect;number]
|
|
||||||
# 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[],['self'|'player'|'team';@effect;number][]
|
||||||
仙人掌怪-boost,仙人掌怪,true,仙人掌怪-boost;仙人掌怪-defend,,[user;spike;1];[user;defend;4]
|
仙人掌怪-boost,仙人掌怪,true,仙人掌怪-boost;仙人掌怪-defend,,[self;spike;1];[self;defend;4]
|
||||||
仙人掌怪-defend,仙人掌怪,false,仙人掌怪-attack,,[user;defend;8]
|
仙人掌怪-defend,仙人掌怪,false,仙人掌怪-attack,,[self;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,,[self;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,,[self;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,,[self;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,[self;defend;5]
|
||||||
风卷草-boost,风卷草,true,风卷草-defend;风卷草-defend;风卷草-boost,,[user;roll;5];[user;defend;4]
|
风卷草-boost,风卷草,true,风卷草-defend;风卷草-defend;风卷草-boost,,[self;roll;5];[self;defend;4]
|
||||||
风卷草-defend,风卷草,false,风卷草-boost;风卷草-attack,,[user;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,,[user;defend;5]
|
秃鹫-defend,秃鹫,false,秃鹫-attack;秃鹫-attack,,[self;defend;5]
|
||||||
沙蝎-boost,沙蝎,true,沙蝎-attack;沙蝎-attack,,[user;tailSting;2]
|
沙蝎-boost,沙蝎,true,沙蝎-attack;沙蝎-attack,,[self;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,,[self;defend;6]
|
||||||
幼沙虫-boost,幼沙虫,false,幼沙虫-attack;幼沙虫-defend,,[user;energyDrain;1];[user;defend;4]
|
幼沙虫-boost,幼沙虫,false,幼沙虫-attack;幼沙虫-defend,,[self;energyDrain;1];[self;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,,[self;defend;6]
|
||||||
蜥蜴-molt,蜥蜴,false,蜥蜴-defend;蜥蜴-attack,,[user;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,,[user;storm;2];[user;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,,[user;defend;8]
|
风暴之灵-defend,风暴之灵,false,风暴之灵-storm;风暴之灵-attack,,[self;defend;8]
|
||||||
骑马枪手-charge,骑马枪手,true,骑马枪手-attack,,[user;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,[user;defend;5]
|
骑马枪手-defend,骑马枪手,false,骑马枪手-charge;骑马枪手-attack,骑马枪手-charge,[self;defend;5]
|
||||||
沙虫王-summon,沙虫王,true,沙虫王-attack;沙虫王-defend,,[user;summonSandwormLarva;18]
|
沙虫王-summon,沙虫王,true,沙虫王-attack;沙虫王-defend,,[self;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,,[self;defend;6]
|
||||||
沙漠守卫-summon,沙漠守卫,true,沙漠守卫-attack;沙漠守卫-defend,,[user;summonMummy;14]
|
沙漠守卫-summon,沙漠守卫,true,沙漠守卫-attack;沙漠守卫-defend,,[self;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,,[self;defend;8]
|
||||||
沙漠守卫-revive,沙漠守卫,false,沙漠守卫-attack;沙漠守卫-summon,,[user;reviveMummy;1]
|
沙漠守卫-revive,沙漠守卫,false,沙漠守卫-attack;沙漠守卫-summon,,[self;reviveMummy;1]
|
||||||
|
|
|
||||||
|
Can't render this file because it has a wrong number of fields in line 13.
|
|
|
@ -1,17 +1,13 @@
|
||||||
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 IntentEffect = [IntentEffectTarget, Effect, number];
|
|
||||||
export type IntentEffectList = IntentEffect[];
|
|
||||||
|
|
||||||
type IntentTable = readonly {
|
type IntentTable = readonly {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly enemy: Enemy;
|
readonly enemy: Enemy;
|
||||||
readonly initialIntent: boolean;
|
readonly initialIntent: boolean;
|
||||||
readonly nextIntents: Intent[];
|
readonly nextIntents: Intent[];
|
||||||
readonly brokenIntent: Intent[];
|
readonly brokenIntent: Intent[];
|
||||||
readonly effects: IntentEffectList;
|
readonly effects: ["self" | "player" | "team", Effect, number][];
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
export type Intent = IntentTable[number];
|
export type Intent = IntentTable[number];
|
||||||
|
|
|
||||||
|
|
@ -2,167 +2,157 @@ import { Triggers } from "@/samples/slay-the-spire-like/system/combat/triggers";
|
||||||
import { addEntityEffect } from "@/samples/slay-the-spire-like/system/combat/effects";
|
import { addEntityEffect } from "@/samples/slay-the-spire-like/system/combat/effects";
|
||||||
import { moveToRegion } from "@/core/region";
|
import { moveToRegion } from "@/core/region";
|
||||||
import { CombatGameContext } from "@/samples/slay-the-spire-like/system/combat/types";
|
import { CombatGameContext } from "@/samples/slay-the-spire-like/system/combat/types";
|
||||||
|
import { EffectData } from "@/samples/slay-the-spire-like/system/types";
|
||||||
import { GameCard } from "@/samples/slay-the-spire-like/system/deck";
|
import { GameCard } from "@/samples/slay-the-spire-like/system/deck";
|
||||||
|
import getEffects from "../effect.csv";
|
||||||
import getCards from "../card.csv";
|
import getCards from "../card.csv";
|
||||||
|
|
||||||
export function addInstantEffectTriggers(triggers: Triggers) {
|
export function addInstantEffectTriggers(triggers: Triggers) {
|
||||||
const cards = getCards();
|
const effects = getEffects();
|
||||||
|
const cards = getCards();
|
||||||
|
|
||||||
function findCard(id: string) {
|
function findEffect(id: string): EffectData {
|
||||||
return cards.find((c) => c.id === id);
|
const found = effects.find((e: EffectData) => e.id === id);
|
||||||
}
|
if (found) return found;
|
||||||
|
return { id, name: id, description: "", lifecycle: "instant" } as EffectData;
|
||||||
function createStatusCard(
|
|
||||||
draft: CombatGameContext["value"],
|
|
||||||
cardId: string,
|
|
||||||
): void {
|
|
||||||
const cardData = findCard(cardId);
|
|
||||||
if (!cardData) return;
|
|
||||||
|
|
||||||
const instanceId = `${cardId}-${draft.player.deck.regions.drawPile.childIds.length}-${draft.player.deck.regions.discardPile.childIds.length}`;
|
|
||||||
const card: GameCard = {
|
|
||||||
id: instanceId,
|
|
||||||
regionId: "",
|
|
||||||
position: [],
|
|
||||||
itemId: cardId,
|
|
||||||
cardData: {
|
|
||||||
id: cardData.id,
|
|
||||||
name: cardData.name,
|
|
||||||
desc: cardData.desc,
|
|
||||||
type: cardData.type,
|
|
||||||
costType: cardData.costType,
|
|
||||||
costCount: cardData.costCount,
|
|
||||||
targetType: cardData.targetType,
|
|
||||||
effects: cardData.effects,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
draft.player.deck.cards[instanceId] = card;
|
|
||||||
moveToRegion(card, null, draft.player.deck.regions.drawPile);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAllEnemyDataFromState(state: CombatGameContext["value"]) {
|
|
||||||
const seen = new Set<string>();
|
|
||||||
const result: (typeof state.enemies)[number]["enemy"][] = [];
|
|
||||||
for (const enemy of state.enemies) {
|
|
||||||
if (!seen.has(enemy.enemy.id)) {
|
|
||||||
seen.add(enemy.enemy.id);
|
|
||||||
result.push(enemy.enemy);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function summonEnemy(
|
function findCard(id: string) {
|
||||||
draft: CombatGameContext["value"],
|
return cards.find(c => c.id === id);
|
||||||
enemyId: string,
|
|
||||||
hp: number,
|
|
||||||
) {
|
|
||||||
for (const enemyData of getAllEnemyDataFromState(draft)) {
|
|
||||||
if (enemyData.id === enemyId) {
|
|
||||||
const existing = draft.enemies.find(
|
|
||||||
(e) => e.enemy.id === enemyId && !e.isAlive,
|
|
||||||
);
|
|
||||||
if (existing) {
|
|
||||||
existing.isAlive = true;
|
|
||||||
existing.hp = existing.maxHp;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const intent =
|
|
||||||
enemyData.intents.find((i) => i.initialIntent) ??
|
|
||||||
enemyData.intents[0];
|
|
||||||
const instanceId = `${enemyData.id}-${draft.enemies.length}`;
|
|
||||||
const intents: Record<string, typeof intent> = {};
|
|
||||||
for (const i of enemyData.intents) {
|
|
||||||
intents[i.id] = i;
|
|
||||||
}
|
|
||||||
draft.enemies.push({
|
|
||||||
id: instanceId,
|
|
||||||
enemy: enemyData,
|
|
||||||
hp,
|
|
||||||
maxHp: hp,
|
|
||||||
isAlive: true,
|
|
||||||
effects: {},
|
|
||||||
intents,
|
|
||||||
currentIntent: intent,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
triggers.onEffectApplied.use(async (ctx, next) => {
|
function createStatusCard(draft: CombatGameContext["value"], cardId: string): void {
|
||||||
if (ctx.effect.id === "attack") {
|
const cardData = findCard(cardId);
|
||||||
await triggers.onDamage.execute(ctx.game, {
|
if (!cardData) return;
|
||||||
entityKey: ctx.entityKey,
|
|
||||||
amount: ctx.stacks,
|
const instanceId = `${cardId}-${draft.player.deck.regions.drawPile.childIds.length}-${draft.player.deck.regions.discardPile.childIds.length}`;
|
||||||
sourceEntityKey:
|
const card: GameCard = {
|
||||||
(ctx.sourceEntityKey ?? ctx.entityKey === "player")
|
id: instanceId,
|
||||||
? undefined
|
regionId: "",
|
||||||
: "player",
|
position: [],
|
||||||
});
|
itemId: cardId,
|
||||||
} else if (ctx.effect.id === "draw") {
|
cardData: {
|
||||||
await triggers.onDraw.execute(ctx.game, { count: ctx.stacks });
|
id: cardData.id,
|
||||||
} else if (ctx.effect.id === "gainEnergy") {
|
name: cardData.name,
|
||||||
await ctx.game.produceAsync((draft) => {
|
desc: cardData.desc,
|
||||||
draft.player.energy += ctx.stacks;
|
type: cardData.type,
|
||||||
});
|
costType: cardData.costType,
|
||||||
} else if (ctx.effect.id === "removeWound") {
|
costCount: cardData.costCount,
|
||||||
await ctx.game.produceAsync((draft) => {
|
targetType: cardData.targetType,
|
||||||
const { cards, regions } = draft.player.deck;
|
effects: cardData.effects,
|
||||||
let removed = 0;
|
},
|
||||||
const allPileIds = [
|
};
|
||||||
...regions.drawPile.childIds,
|
draft.player.deck.cards[instanceId] = card;
|
||||||
...regions.discardPile.childIds,
|
moveToRegion(card, null, draft.player.deck.regions.drawPile);
|
||||||
];
|
|
||||||
for (const cardId of allPileIds) {
|
|
||||||
if (removed >= ctx.stacks) break;
|
|
||||||
const card = cards[cardId];
|
|
||||||
if (card && card.cardData.id === "wound") {
|
|
||||||
const sourceRegion =
|
|
||||||
card.regionId === "drawPile"
|
|
||||||
? regions.drawPile
|
|
||||||
: regions.discardPile;
|
|
||||||
moveToRegion(card, sourceRegion, null);
|
|
||||||
delete cards[cardId];
|
|
||||||
removed++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (ctx.effect.id === "venom") {
|
|
||||||
await ctx.game.produceAsync((draft) => {
|
|
||||||
createStatusCard(draft, "venom");
|
|
||||||
});
|
|
||||||
} else if (ctx.effect.id === "vultureEye") {
|
|
||||||
await ctx.game.produceAsync((draft) => {
|
|
||||||
createStatusCard(draft, "vultureEye");
|
|
||||||
});
|
|
||||||
} else if (ctx.effect.id === "static") {
|
|
||||||
await ctx.game.produceAsync((draft) => {
|
|
||||||
createStatusCard(draft, "static");
|
|
||||||
});
|
|
||||||
} else if (ctx.effect.id === "summonMummy") {
|
|
||||||
await ctx.game.produceAsync((draft) => {
|
|
||||||
summonEnemy(draft, "木乃伊", ctx.stacks);
|
|
||||||
});
|
|
||||||
} else if (ctx.effect.id === "summonSandwormLarva") {
|
|
||||||
await ctx.game.produceAsync((draft) => {
|
|
||||||
summonEnemy(draft, "幼沙虫", ctx.stacks);
|
|
||||||
});
|
|
||||||
} else if (ctx.effect.id === "reviveMummy") {
|
|
||||||
await ctx.game.produceAsync((draft) => {
|
|
||||||
const deadMummy = draft.enemies.find(
|
|
||||||
(e) => e.enemy.id === "木乃伊" && !e.isAlive,
|
|
||||||
);
|
|
||||||
if (deadMummy) {
|
|
||||||
deadMummy.isAlive = true;
|
|
||||||
deadMummy.hp = deadMummy.maxHp;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (ctx.effect.id === "curse") {
|
|
||||||
await ctx.game.produceAsync((draft) => {
|
|
||||||
addEntityEffect(draft.player, ctx.effect, ctx.stacks);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
await next();
|
|
||||||
});
|
function getAllEnemyDataFromState(state: CombatGameContext["value"]) {
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const result: typeof state.enemies[number]["enemy"][] = [];
|
||||||
|
for (const enemy of state.enemies) {
|
||||||
|
if (!seen.has(enemy.enemy.id)) {
|
||||||
|
seen.add(enemy.enemy.id);
|
||||||
|
result.push(enemy.enemy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function summonEnemy(draft: CombatGameContext["value"], enemyId: string, hp: number) {
|
||||||
|
for (const enemyData of getAllEnemyDataFromState(draft)) {
|
||||||
|
if (enemyData.id === enemyId) {
|
||||||
|
const existing = draft.enemies.find(e => e.enemy.id === enemyId && !e.isAlive);
|
||||||
|
if (existing) {
|
||||||
|
existing.isAlive = true;
|
||||||
|
existing.hp = existing.maxHp;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const intent = enemyData.intents.find(i => i.initialIntent) ?? enemyData.intents[0];
|
||||||
|
const instanceId = `${enemyData.id}-${draft.enemies.length}`;
|
||||||
|
const intents: Record<string, typeof intent> = {};
|
||||||
|
for (const i of enemyData.intents) {
|
||||||
|
intents[i.id] = i;
|
||||||
|
}
|
||||||
|
draft.enemies.push({
|
||||||
|
id: instanceId,
|
||||||
|
enemy: enemyData,
|
||||||
|
hp,
|
||||||
|
maxHp: hp,
|
||||||
|
isAlive: true,
|
||||||
|
effects: {},
|
||||||
|
intents,
|
||||||
|
currentIntent: intent,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
triggers.onEffectApplied.use(async (ctx, next) => {
|
||||||
|
if (ctx.effect.id === "attack") {
|
||||||
|
await triggers.onDamage.execute(ctx.game, {
|
||||||
|
entityKey: ctx.entityKey,
|
||||||
|
amount: ctx.stacks,
|
||||||
|
sourceEntityKey: ctx.sourceEntityKey ?? ctx.entityKey === "player" ? undefined : "player",
|
||||||
|
});
|
||||||
|
} else if (ctx.effect.id === "draw") {
|
||||||
|
await triggers.onDraw.execute(ctx.game, { count: ctx.stacks });
|
||||||
|
} else if (ctx.effect.id === "gainEnergy") {
|
||||||
|
await ctx.game.produceAsync(draft => {
|
||||||
|
draft.player.energy += ctx.stacks;
|
||||||
|
});
|
||||||
|
} else if (ctx.effect.id === "removeWound") {
|
||||||
|
await ctx.game.produceAsync(draft => {
|
||||||
|
const { cards, regions } = draft.player.deck;
|
||||||
|
let removed = 0;
|
||||||
|
const allPileIds = [
|
||||||
|
...regions.drawPile.childIds,
|
||||||
|
...regions.discardPile.childIds,
|
||||||
|
];
|
||||||
|
for (const cardId of allPileIds) {
|
||||||
|
if (removed >= ctx.stacks) break;
|
||||||
|
const card = cards[cardId];
|
||||||
|
if (card && card.cardData.id === "wound") {
|
||||||
|
const sourceRegion = card.regionId === "drawPile" ? regions.drawPile : regions.discardPile;
|
||||||
|
moveToRegion(card, sourceRegion, null);
|
||||||
|
delete cards[cardId];
|
||||||
|
removed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (ctx.effect.id === "venom") {
|
||||||
|
await ctx.game.produceAsync(draft => {
|
||||||
|
createStatusCard(draft, "venom");
|
||||||
|
});
|
||||||
|
} else if (ctx.effect.id === "vultureEye") {
|
||||||
|
await ctx.game.produceAsync(draft => {
|
||||||
|
createStatusCard(draft, "vultureEye");
|
||||||
|
});
|
||||||
|
} else if (ctx.effect.id === "static") {
|
||||||
|
await ctx.game.produceAsync(draft => {
|
||||||
|
createStatusCard(draft, "static");
|
||||||
|
});
|
||||||
|
} else if (ctx.effect.id === "summonMummy") {
|
||||||
|
await ctx.game.produceAsync(draft => {
|
||||||
|
summonEnemy(draft, "木乃伊", ctx.stacks);
|
||||||
|
});
|
||||||
|
} else if (ctx.effect.id === "summonSandwormLarva") {
|
||||||
|
await ctx.game.produceAsync(draft => {
|
||||||
|
summonEnemy(draft, "幼沙虫", ctx.stacks);
|
||||||
|
});
|
||||||
|
} else if (ctx.effect.id === "reviveMummy") {
|
||||||
|
await ctx.game.produceAsync(draft => {
|
||||||
|
const deadMummy = draft.enemies.find(e => e.enemy.id === "木乃伊" && !e.isAlive);
|
||||||
|
if (deadMummy) {
|
||||||
|
deadMummy.isAlive = true;
|
||||||
|
deadMummy.hp = deadMummy.maxHp;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (ctx.effect.id === "curse") {
|
||||||
|
await ctx.game.produceAsync(draft => {
|
||||||
|
addEntityEffect(draft.player, ctx.effect, ctx.stacks);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await next();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,22 @@
|
||||||
import { LoadResult as YarnDialogues } from "yarn-spinner-loader";
|
import { LoadResult as YarnDialogues } from "yarn-spinner-loader";
|
||||||
|
import {
|
||||||
|
CardData,
|
||||||
|
EffectData,
|
||||||
|
EncounterData,
|
||||||
|
EnemyData,
|
||||||
|
IntentData,
|
||||||
|
ItemData,
|
||||||
|
} from "../system/types";
|
||||||
import { Triggers } from "../system/combat/triggers";
|
import { Triggers } from "../system/combat/triggers";
|
||||||
|
|
||||||
import type * as desert from "./desert";
|
|
||||||
|
|
||||||
export type ContentModule = {
|
export type ContentModule = {
|
||||||
getCards: () => desert.Card[];
|
getCards: () => CardData[];
|
||||||
getEffects: () => desert.Effect[];
|
getEffects: () => EffectData[];
|
||||||
getEncounters: () => desert.Encounter[];
|
getEncounters: () => EncounterData[];
|
||||||
getEnemies: () => desert.Enemy[];
|
getEnemies: () => EnemyData[];
|
||||||
getIntents: () => desert.Intent[];
|
getIntents: () => IntentData[];
|
||||||
getItems: () => desert.Item[];
|
getItems: () => ItemData[];
|
||||||
getStartingItems: () => desert.Item[];
|
getStartingItems: () => ItemData[];
|
||||||
|
|
||||||
dialogues: YarnDialogues;
|
dialogues: YarnDialogues;
|
||||||
addTriggers: (triggers: Triggers) => void;
|
addTriggers: (triggers: Triggers) => void;
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,9 @@ import {
|
||||||
import {
|
import {
|
||||||
CardData,
|
CardData,
|
||||||
CardEffectTarget,
|
CardEffectTarget,
|
||||||
|
CardTargetType,
|
||||||
EffectData,
|
EffectData,
|
||||||
IntentEffectTarget,
|
EffectTarget,
|
||||||
} from "@/samples/slay-the-spire-like/system/types";
|
} from "@/samples/slay-the-spire-like/system/types";
|
||||||
|
|
||||||
export function addEffect(
|
export function addEffect(
|
||||||
|
|
@ -103,25 +104,25 @@ export function* getAliveEnemies(state: CombatState) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* getEffectTargets(
|
export function* getEffectTargets(
|
||||||
target: CardEffectTarget | IntentEffectTarget,
|
target: CardEffectTarget | EffectTarget,
|
||||||
game: CombatGameContext,
|
game: CombatGameContext,
|
||||||
targetId?: string,
|
targetId?: string,
|
||||||
sourceEntityKey: "player" | string = "player",
|
sourceEntityKey: "player" | string = "player",
|
||||||
) {
|
) {
|
||||||
if (target === "eachEnemy") {
|
if (target === "all" || target === "team") {
|
||||||
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 === "self") {
|
||||||
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") {
|
||||||
yield game.value.player;
|
yield game.value.player;
|
||||||
} else if (target === "eachTarget") {
|
} else if (target === "target") {
|
||||||
if (!targetId) return;
|
if (!targetId) return;
|
||||||
const entity = getCombatEntity(game.value, targetId);
|
const entity = getCombatEntity(game.value, targetId);
|
||||||
if (entity) yield entity;
|
if (entity) yield entity;
|
||||||
} else if (target === "randomEnemy") {
|
} else if (target === "random") {
|
||||||
const aliveEnemies = [...getAliveEnemies(game.value)];
|
const aliveEnemies = [...getAliveEnemies(game.value)];
|
||||||
if (aliveEnemies.length === 0) return;
|
if (aliveEnemies.length === 0) return;
|
||||||
const index = game.rng.nextInt(aliveEnemies.length);
|
const index = game.rng.nextInt(aliveEnemies.length);
|
||||||
|
|
|
||||||
|
|
@ -38,12 +38,12 @@ export async function promptMainAction(
|
||||||
}
|
}
|
||||||
|
|
||||||
const { targetType } = cardData;
|
const { targetType } = cardData;
|
||||||
if (targetType === "enemy") {
|
if (targetType === "single") {
|
||||||
if (!targetId) throw `请指定目标`;
|
if (!targetId) throw `请指定目标`;
|
||||||
const target = game.value.enemies.find((e) => e.id === targetId);
|
const target = game.value.enemies.find((e) => e.id === targetId);
|
||||||
if (!target) throw `目标"${targetId}"不存在`;
|
if (!target) throw `目标"${targetId}"不存在`;
|
||||||
if (!target.isAlive) throw `目标"${targetId}"已死亡`;
|
if (!target.isAlive) throw `目标"${targetId}"已死亡`;
|
||||||
} else if (targetType === "enemies" || targetType === "player") {
|
} else if (targetType === "none") {
|
||||||
if (targetId) throw `目标"${targetId}"无效`;
|
if (targetId) throw `目标"${targetId}"无效`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,89 @@
|
||||||
import {
|
export type EffectData = {
|
||||||
Card,
|
readonly id: string;
|
||||||
Effect,
|
readonly name: string;
|
||||||
Encounter,
|
readonly description: string;
|
||||||
EncounterType,
|
readonly lifecycle: EffectLifecycle;
|
||||||
Enemy,
|
};
|
||||||
Intent,
|
export type EffectLifecycle =
|
||||||
Item,
|
| "instant"
|
||||||
} from "../data/desert";
|
| "temporary"
|
||||||
|
| "lingering"
|
||||||
|
| "permanent"
|
||||||
|
| "posture"
|
||||||
|
| "item"
|
||||||
|
| "itemTemporary"
|
||||||
|
| "itemUntilPlay"
|
||||||
|
| "itemUntilDiscard"
|
||||||
|
| "itemPermanent";
|
||||||
|
|
||||||
export type CardData = Card;
|
export type EnemyData = {
|
||||||
export type ItemData = Item;
|
readonly id: string;
|
||||||
export type EffectData = Effect;
|
readonly name: string;
|
||||||
export type IntentData = Intent;
|
readonly intents: readonly IntentData[];
|
||||||
export type EnemyData = Enemy;
|
readonly description: string;
|
||||||
export type EncounterData<T extends EncounterType = EncounterType> =
|
};
|
||||||
Encounter & { type: T };
|
|
||||||
|
|
||||||
export {
|
export type CardType = "item" | "status";
|
||||||
CardTargetType,
|
export type CardCostType = "energy" | "uses" | "none";
|
||||||
CardEffectTarget,
|
export type CardTargetType = "single" | "none";
|
||||||
IntentEffectTarget,
|
export type EffectTarget = "self" | "player" | "team";
|
||||||
} from "../data/desert";
|
|
||||||
|
export type CardEffectTrigger = "onPlay" | "onDraw" | "onDiscard";
|
||||||
|
export type CardEffectTarget = "self" | "target" | "all" | "random";
|
||||||
|
|
||||||
|
export type CardEffect = {
|
||||||
|
readonly id: string;
|
||||||
|
readonly trigger: CardEffectTrigger;
|
||||||
|
readonly target: CardEffectTarget;
|
||||||
|
readonly effects: readonly [EffectData, number][];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CardData = {
|
||||||
|
readonly id: string;
|
||||||
|
readonly name: string;
|
||||||
|
readonly desc: string;
|
||||||
|
readonly type: CardType;
|
||||||
|
readonly costType: CardCostType;
|
||||||
|
readonly costCount: number;
|
||||||
|
readonly targetType: CardTargetType;
|
||||||
|
readonly effects: readonly CardEffect[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EncounterType =
|
||||||
|
| "minion"
|
||||||
|
| "elite"
|
||||||
|
| "event"
|
||||||
|
| "shop"
|
||||||
|
| "camp"
|
||||||
|
| "curio";
|
||||||
|
export type EncounterData<T extends EncounterType = EncounterType> = {
|
||||||
|
readonly id: string;
|
||||||
|
readonly type: T;
|
||||||
|
readonly name: string;
|
||||||
|
readonly description: string;
|
||||||
|
readonly enemies: readonly [
|
||||||
|
data: EnemyData,
|
||||||
|
hp: number,
|
||||||
|
effects: [EffectData, stacks: number][],
|
||||||
|
][];
|
||||||
|
readonly dialogue: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IntentData = {
|
||||||
|
readonly id: string;
|
||||||
|
readonly enemy: EnemyData;
|
||||||
|
readonly initialIntent: boolean;
|
||||||
|
readonly nextIntents: readonly IntentData[];
|
||||||
|
readonly brokenIntent: readonly IntentData[];
|
||||||
|
readonly effects: readonly [EffectTarget, EffectData, number][];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ItemData = {
|
||||||
|
readonly id: string;
|
||||||
|
readonly type: string;
|
||||||
|
readonly name: string;
|
||||||
|
readonly shape: string;
|
||||||
|
readonly card: CardData;
|
||||||
|
readonly price: number;
|
||||||
|
readonly description: string;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ import type {
|
||||||
import type { GameItemMeta } from "@/samples/slay-the-spire-like/system/grid-inventory/types";
|
import type { GameItemMeta } from "@/samples/slay-the-spire-like/system/grid-inventory/types";
|
||||||
import type { ParsedShape } from "@/samples/slay-the-spire-like/system/utils/parse-shape";
|
import type { ParsedShape } from "@/samples/slay-the-spire-like/system/utils/parse-shape";
|
||||||
import type { Transform2D } from "@/samples/slay-the-spire-like/system/utils/shape-collision";
|
import type { Transform2D } from "@/samples/slay-the-spire-like/system/utils/shape-collision";
|
||||||
import { CardEffect } from "@/samples/slay-the-spire-like/data/desert";
|
|
||||||
|
|
||||||
function createRunContext(
|
function createRunContext(
|
||||||
items: Map<string, InventoryItem<GameItemMeta>>,
|
items: Map<string, InventoryItem<GameItemMeta>>,
|
||||||
|
|
@ -64,7 +63,7 @@ function createEffect(
|
||||||
id: string,
|
id: string,
|
||||||
lifecycle: EffectData["lifecycle"],
|
lifecycle: EffectData["lifecycle"],
|
||||||
): EffectData {
|
): EffectData {
|
||||||
return { id, name: id, description: "", lifecycle, emoji: "" };
|
return { id, name: id, description: "", lifecycle };
|
||||||
}
|
}
|
||||||
|
|
||||||
function createCard(
|
function createCard(
|
||||||
|
|
@ -79,8 +78,8 @@ function createCard(
|
||||||
type: "item" as const,
|
type: "item" as const,
|
||||||
costType,
|
costType,
|
||||||
costCount,
|
costCount,
|
||||||
targetType: "player" as const,
|
targetType: "none" as const,
|
||||||
effects: [] as CardEffect[],
|
effects: [] as const,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import {
|
||||||
} from "@/core/game";
|
} from "@/core/game";
|
||||||
import { createRegion } from "@/core/region";
|
import { createRegion } from "@/core/region";
|
||||||
import {
|
import {
|
||||||
|
createStartWith,
|
||||||
createTriggers,
|
createTriggers,
|
||||||
Triggers,
|
Triggers,
|
||||||
} from "@/samples/slay-the-spire-like/system/combat/triggers";
|
} from "@/samples/slay-the-spire-like/system/combat/triggers";
|
||||||
|
|
@ -29,7 +30,6 @@ import { GameItemMeta } from "@/samples/slay-the-spire-like/system/grid-inventor
|
||||||
import { ParsedShape } from "@/samples/slay-the-spire-like/system/utils/parse-shape";
|
import { ParsedShape } from "@/samples/slay-the-spire-like/system/utils/parse-shape";
|
||||||
import { Transform2D } from "@/samples/slay-the-spire-like/system/utils/shape-collision";
|
import { Transform2D } from "@/samples/slay-the-spire-like/system/utils/shape-collision";
|
||||||
import {
|
import {
|
||||||
CardEffect,
|
|
||||||
getCards,
|
getCards,
|
||||||
getEffects,
|
getEffects,
|
||||||
getEncounters,
|
getEncounters,
|
||||||
|
|
@ -48,7 +48,7 @@ function createEffect(
|
||||||
): EffectData {
|
): EffectData {
|
||||||
const found = effects.find((e) => e.id === id);
|
const found = effects.find((e) => e.id === id);
|
||||||
if (found) return found;
|
if (found) return found;
|
||||||
return { id, name: id, description: "", lifecycle, emoji: "" };
|
return { id, name: id, description: "", lifecycle };
|
||||||
}
|
}
|
||||||
|
|
||||||
function createDeckRegions(): DeckRegions {
|
function createDeckRegions(): DeckRegions {
|
||||||
|
|
@ -73,8 +73,8 @@ function createCard(
|
||||||
type: "item" as const,
|
type: "item" as const,
|
||||||
costType,
|
costType,
|
||||||
costCount,
|
costCount,
|
||||||
targetType: "player" as const,
|
targetType: "none" as const,
|
||||||
effects: [] as CardEffect[],
|
effects: [],
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
|
|
@ -285,7 +285,7 @@ describe("desert triggers", () => {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const triggers = getTriggers();
|
const triggers = getTriggers();
|
||||||
const defendEffect = createEffect("defend", "temporary");
|
const defendEffect = createEffect("defend", "posture");
|
||||||
|
|
||||||
ctx._state.produce((draft) => {
|
ctx._state.produce((draft) => {
|
||||||
draft.player.effects.defend = { data: defendEffect, stacks: 5 };
|
draft.player.effects.defend = { data: defendEffect, stacks: 5 };
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ function createTestCardData(id: string, name: string, desc: string): CardData {
|
||||||
type: "item",
|
type: "item",
|
||||||
costType: "energy",
|
costType: "energy",
|
||||||
costCount: 1,
|
costCount: 1,
|
||||||
targetType: "enemy",
|
targetType: "single",
|
||||||
effects: [],
|
effects: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import {
|
||||||
getItemAtCell,
|
getItemAtCell,
|
||||||
getAdjacentItems,
|
getAdjacentItems,
|
||||||
validatePlacement,
|
validatePlacement,
|
||||||
|
type GridInventory,
|
||||||
type InventoryItem,
|
type InventoryItem,
|
||||||
} from "@/samples/slay-the-spire-like/system/grid-inventory";
|
} from "@/samples/slay-the-spire-like/system/grid-inventory";
|
||||||
import { createItemIn } from "@/samples/slay-the-spire-like/system/grid-inventory/factory";
|
import { createItemIn } from "@/samples/slay-the-spire-like/system/grid-inventory/factory";
|
||||||
|
|
@ -35,7 +36,7 @@ function createTestCardData(id: string, name: string, desc: string): CardData {
|
||||||
type: "item",
|
type: "item",
|
||||||
costType: "energy",
|
costType: "energy",
|
||||||
costCount: 1,
|
costCount: 1,
|
||||||
targetType: "enemy",
|
targetType: "single",
|
||||||
effects: [],
|
effects: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue