行为
在塔一中,我们将它称作GameAction,不过塔二现在通过异步(async)和等待(await)实现,无需再自定义一系列GameAction。
伤害类
构造
常用于攻击牌,核心命令为DamageCmd
1 2 3 4 5 6 7 8 9
| protected override async Task OnPlay(PlayerChoiceContext choiceContext, CardPlay cardPlay) { await DamageCmd.Attack(DynamicVars.Damage.BaseValue) .FromCard(this) .Targeting(cardPlay.Target) .Execute(choiceContext); }
|
PlayerChoiceContext是一个上下文类,用于存储一系列已发生的逻辑,无需深入理解(其实我也不懂)。
DamageCmd.Attack是固定返回AttackCommand的搭配,需要传入伤害值。
让我们看看AttackCommand的每一个方法和优先级:
来源
1 2 3 4 5 6 7 8 9
| public AttackCommand FromCard(CardModel card){...}
public AttackCommand FromOsty(Creature osty, CardModel card){...}
public AttackCommand FromMonster(MonsterModel monster){...}
|
目标
1 2 3 4 5 6 7 8 9
| public AttackCommand Targeting(Creature creature){...}
public AttackCommand TargetingAllOpponents(CombatState combatState){...}
public AttackCommand TargetingRandomOpponents(CombatState combatState, bool allowDuplicates=true){...}
|
拓展(非必要)
固定数值
1 2 3
| public AttackCommand Unpowered(){...}
|
(实际上这只是一个行为规范,以力量为例)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
public override decimal ModifyDamageAdditive(Creature? target, decimal amount, ValueProp props, Creature? dealer, CardModel? cardSource) { if (base.Owner != dealer) { return 0m; } if (!props.IsPoweredAttack()) { return 0m; } return base.Amount; }
public static bool IsPoweredAttack(this ValueProp props) { if (props.HasFlag(ValueProp.Move)) { return !props.HasFlag(ValueProp.Unpowered); } return false; }
|
攻击次数
1 2 3
| public AttackCommand WithHitCount(int hitCount){...}
|
动画/特效设置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public AttackCommand WithAttackerAnim(string? animName, float delay, Creature? visualAttacker = null){...}
public AttackCommand WithNoAttackerAnim(){...}
public AttackCommand WithAttackerFx(string? vfx = null, string? sfx = null, string? tmpSfx = null){...}
public AttackCommand WithWaitBeforeHit(float fastSeconds, float standardSeconds){...}
public AttackCommand WithHitFx(string? vfx = null, string? sfx = null, string? tmpSfx = null){...}
public AttackCommand SpawningHitVfxOnEachCreature(){...}
public AttackCommand WithHitVfxSpawnedAtBase(){...}
public AttackCommand OnlyPlayAnimOnce(){...}
|
关联Task
1 2 3 4 5 6 7 8 9
| public AttackCommand AfterAttackerAnim(Func<Task> afterAttackerAnim){...}
public AttackCommand BeforeDamage(Func<Task> beforeDamage){...}
public static async Task<AttackContext> CreateContextAsync(CombatState combatState, CardModel cardSource){...}
|
执行
在执行前,可以任意搭配属性/时机方法,执行必须放在最后一位
1 2 3
| public async Task<AttackCommand> Execute(PlayerChoiceContext? choiceContext){...}
|
以旋风斩为例(多段对群)
1 2 3 4 5 6 7
| await DamageCmd.Attack(DynamicVars.Damage.BaseValue) .WithHitCount(num) .FromCard(this) .TargetingAllOpponents(CombatState) .WithHitFx("vfx/vfx_giant_horizontal_slash") .Execute(choiceContext);
|
特殊-直接伤害
不使用DamageCmd,而是直接调用生物的Damage方法:
以锁镰为例(随机对单,不受Power影响)
1 2 3 4 5 6 7 8 9
| Creature creature = Owner.RunState.Rng.CombatTargets.NextItem(Owner.Creature.CombatState.HittableEnemies); if (creature != null) { TaskHelper.RunSafely(DoActivateVisuals()); await CreatureCmd.Damage(context, creature, DynamicVars.Damage, Owner.Creature); }
|
卡牌类
牌堆操作(CardPileCmd)
牌组类型(PileType)有这几种:
- Hand(手牌)
- Deck(牌组)
- Draw(抽牌堆)
- Exhaust(消耗堆)
- Discard(弃牌堆)
- Play(执行堆)
抽牌
1 2 3
| await CardPileCmd.Draw(choiceContext, DynamicVars.Cards.BaseValue, Owner);
|
获得牌堆及其所有卡牌
1 2 3
| IEnumerable<CardModel> cards = PileType.Hand.GetPile(Owner).Cards;
|
其他
自行查看CardPileCmd即可,这一部分和塔一比较类似
卡牌选择(CardSelectCmd)
选择参数
选择参数(CardSelectorPrefs):它的构造函数需要传入提示&限制
- 提示:(有以下几种内置,也可以自定义)
- TransformSelectionPrompt
- ExhaustSelectionPrompt
- RemoveSelectionPrompt
- EnchantSelectionPrompt
- DiscardSelectionPrompt
- UpgradeSelectionPrompt
- 限制:(倾斜部分不可定义)
- MinSelect 最小选牌数
- MaxSelect 最大选牌数
- RequireManualConfirmation 是否需要手动确认选择
- Cancelable 是否可取消选择
- UnpoweredPreviews 预览时是否置为未数值修正状态
- PretendCardsCanBePlayed 是否假定选中的卡牌可以被打出
- ShouldGlowGold 是否高亮(因为是Func类型,可以为每张卡牌单独定义)
范围定义
定义选择范围,形如:CardSelectCmd.FromHand
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public static async Task<CardModel?> FromChooseACardScreen (PlayerChoiceContext context, IReadOnlyList<CardModel> cards, Player player, bool canSkip = false){...}
public static async Task<IEnumerable<CardModel>> FromSimpleGrid (PlayerChoiceContext context, IReadOnlyList<CardModel> cardsIn, Player player, CardSelectorPrefs prefs){...}
public static async Task<IEnumerable<CardModel>> FromDeckForUpgrade (Player player, CardSelectorPrefs prefs){...}
public static async Task<IEnumerable<CardModel>> FromDeckForEnchantment (Player player, EnchantmentModel enchantment, int amount, CardSelectorPrefs prefs){...}
public static async Task<IEnumerable<CardModel>> FromHand (PlayerChoiceContext context, Player player, CardSelectorPrefs prefs, Func<CardModel, bool>? filter, AbstractModel source){...}
public static async Task<CardModel?> FromHandForUpgrade (PlayerChoiceContext context, Player player, AbstractModel source){...}
|
多结果处理(IEnumerable<CardModel>)
注意到范围定义中有的方法返回的是IEnumerable<CardModel>,而不是CardModel。这是为了支持多选。
给出几个常见的用法:(如果需要更多请自查IEnumerable<>的方法)
- 取出第一张:FirstOrDefault() -> 返回第一张选中的卡牌,如果为空则返回null
- 取出最后一张:LastOrDefault() -> 返回最后一张选中的卡牌,如果为空则返回null
- 取出所有:ToList() -> 返回一个List<CardModel>,包含所有选中的卡牌
卡牌处理
在这里默认你已经得到了一张卡牌,现在是对它的处理部分,命令类为CardCmd
注意,这一部分含有大量示例
自动打出(AutoPlay)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public static async Task AutoPlay( PlayerChoiceContext choiceContext, CardModel card, Creature? target, AutoPlayType type = AutoPlayType.Default, bool skipXCapture = false, bool skipCardPileVisuals = false){...}
for (int i = 0; i < DynamicVars.Repeat.IntValue; i++) { await CardCmd.AutoPlay(choiceContext, card, null); }
|
弃牌(Discard/DiscardAndDraw)
1 2 3 4 5 6 7 8 9 10 11
| public static async Task DiscardAndDraw( PlayerChoiceContext choiceContext, IEnumerable<CardModel> cardsToDiscard, int cardsToDraw){...}
IEnumerable<CardModel> cards = PileType.Hand.GetPile(Owner).Cards; int cardsToDraw = cards.Count(); await CardCmd.DiscardAndDraw(choiceContext, cards, cardsToDraw);
|
消耗(Exhaust)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public static async Task Exhaust( PlayerChoiceContext choiceContext, CardModel card, bool causedByEthereal = false, bool skipVisuals = false){...}
CardPile pile = PileType.Hand.GetPile(Owner); CardModel cardModel2 = Owner.RunState.Rng.CombatCardSelection.NextItem(pile.Cards); if (cardModel2 != null) { await CardCmd.Exhaust(choiceContext, cardModel2); }
|
升级/降级(Upgrade/Downgrade)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public static void Upgrade( IEnumerable<CardModel> cards, CardPreviewStyle style){...}
public static void Downgrade(CardModel card){...}
CardModel cardModel = await CardSelectCmd.FromHandForUpgrade(choiceContext, base.Owner, this); if (cardModel != null) { CardCmd.Upgrade(cardModel); }
|
变化(Transform)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public static async Task<IEnumerable<CardPileAddResult>> Transform( IEnumerable<CardTransformation> transformations, Rng? rng, CardPreviewStyle style = CardPreviewStyle.HorizontalLayout){...}
List<CardModel> cardsIn = ( from c in PileType.Draw.GetPile(base.Owner).Cards orderby c.Rarity, c.Id select c).ToList(); List<CardModel> list = (await CardSelectCmd.FromSimpleGrid(choiceContext, cardsIn, base.Owner, new CardSelectorPrefs(CardSelectorPrefs.TransformSelectionPrompt, base.DynamicVars.Cards.IntValue))).ToList(); foreach (CardModel item in list) { CardPileAddResult? cardPileAddResult = await CardCmd.TransformTo<Soul>(item); if (base.IsUpgraded && cardPileAddResult.HasValue) { CardCmd.Upgrade(cardPileAddResult.Value.cardAdded); } }
|
附魔(Enchant)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public static EnchantmentModel? Enchant( EnchantmentModel enchantment, CardModel card, decimal amount){...}
CardSelectorPrefs prefs = new CardSelectorPrefs(CardSelectorPrefs.EnchantSelectionPrompt, 1); Imbued canonicalMomentum = ModelDb.Enchantment<Imbued>(); foreach (CardModel item in await CardSelectCmd.FromDeckForEnchantment(Owner, canonicalMomentum, 1, prefs)) { CardCmd.Enchant(canonicalMomentum.ToMutable(), item, 1m); CardCmd.Preview(item); }
|
写到这里只介绍了一半的常见卡牌处理方法,还有半部分需要你在使用过程中自己探索了(
作业(这个习惯来自于塔一著名的Patch教程):
请实现:
选择弃牌堆的1张牌,将它变化为同稀有度的随机能力牌后洗入抽牌堆。
Tip1:你的检索列表应该只包含UNCOMMON/RARE的稀有度(因为没有COMMON的能力牌)
Tip2:你可以参考降灵/飞溅/头槌
[查看答案]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| List<CardModel> cardsIn = ( from c in PileType.Discard.GetPile(Owner).Cards orderby c.Rarity, c.Id select c ) .Where(c => c.Rarity == CardRarity.Uncommon || c.Rarity == CardRarity.Rare) .ToList();
List<CardModel> list = (await CardSelectCmd.FromSimpleGrid( choiceContext, cardsIn, Owner, new CardSelectorPrefs(CardSelectorPrefs.TransformSelectionPrompt,DynamicVars.Cards.IntValue) )).ToList();
foreach(CardModel model in list) { IEnumerable<CardPoolModel> pools = Owner.UnlockState.CharacterCardPools.Where(pool => pool.AllCardIds.Contains(model.Id)); if (pools.Any()) { CardRarity rarity = model.Rarity; IEnumerable<CardModel> powerCards = pools.First().AllCards.Where(c => c.Type == CardType.Power && c.Rarity == rarity); if (powerCards.Any()) { CardModel transformTo = Owner.RunState.Rng.CombatCardGeneration.NextItem(powerCards); CardPileAddResult? cardPileAddResult = await CardCmd.Transform(model,transformTo,CardPreviewStyle.HorizontalLayout); if (cardPileAddResult.HasValue) { await CardPileCmd.Add(cardPileAddResult.Value.cardAdded, PileType.Draw, CardPilePosition.Random); } } } }
|