DragonWind

杀戮尖塔源码分析之Power

所谓的Power,也就是我们常说的Buff。我们着重讲解生命周期。

Power可以挂在人或者怪物身上,实际上,二者都为Creature,之后文章会有介绍。

Power如何添加

public static class PowerCmd
{
	public static async Task Apply(PowerModel power, Creature target, decimal amount, Creature? applier, CardModel? cardSource, bool silent = false)
{
	if (CombatManager.Instance.IsEnding || amount == 0m || !target.CanReceivePowers)
	{
		return;
	}
	CombatState combatState = target.CombatState;
	if (combatState == null)
	{
		return;
	}
	if (!power.IsInstanced && target.HasPower(power.Id))
	{
		PowerModel power2 = target.GetPower(power.Id);
		if (power2 == null)
		{
			throw new InvalidOperationException("Creature missing expected power.");
		}
		await ModifyAmount(power2, amount, applier, cardSource);
		return;
	}
	power.AssertMutable();
	power.Applier = applier;
	await Hook.BeforePowerAmountChanged(combatState, power, amount, target, applier, cardSource);
	decimal modifiedAmount = amount;
	IEnumerable<AbstractModel> givenModifiers = null;
	if (applier != null && combatState.ContainsCreature(applier))
	{
		modifiedAmount = Hook.ModifyPowerAmountGiven(combatState, power, applier, modifiedAmount, target, cardSource, out givenModifiers);
	}
	modifiedAmount = Hook.ModifyPowerAmountReceived(combatState, power, target, modifiedAmount, applier, out IEnumerable<AbstractModel> receivedModifiers);
	await power.BeforeApplied(target, modifiedAmount, applier, cardSource);
	if (modifiedAmount != 0m)
	{
		CombatManager.Instance.History.PowerReceived(combatState, power, modifiedAmount, applier);
	}
	power.ApplyInternal(target, modifiedAmount, silent);
	if (power.IsVisible && CombatManager.Instance.IsInProgress)
	{
		await Cmd.CustomScaledWait(0.1f, 0.25f);
	}
	if (target.Side == CombatSide.Player && power.Type == PowerType.Debuff)
	{
		power.SkipNextDurationTick = true;
	}
	if (givenModifiers != null)
	{
		await Hook.AfterModifyingPowerAmountGiven(combatState, givenModifiers, power);
	}
	await Hook.AfterModifyingPowerAmountReceived(combatState, receivedModifiers, power);
	if (modifiedAmount != 0m)
	{
		await power.AfterApplied(applier, cardSource);
		await Hook.AfterPowerAmountChanged(combatState, power, modifiedAmount, applier, cardSource);
	}
}
}

代码其实清晰明了,如果有同名Power则叠层,否则新增。中间有很多Hook,此处按下不表。

我们看具体的Apply函数

public abstract class PowerModel:AbstractModel
{
	public void ApplyInternal(Creature owner, decimal amount, bool silent = false)
	{
        if (!(amount == 0m))
        {
            AssertMutable();
            Owner = owner;
            SetAmount((int)amount, silent);
            Owner.ApplyPowerInternal(this);
        }
	}
}

很好理解,设好OwnerAmount,然后通知Owner把自己加进去,此处OwnerCreature

public class Creature
{
	public void ApplyPowerInternal(PowerModel power)
	{
        if (power.Owner != this)
        {
            throw new InvalidOperationException("ONLY CALL THIS FROM PowerModel.ApplyInternal!");
        }
        if (!power.IsInstanced && _powers.Any((PowerModel p) => p.GetType() == power.GetType()))
        {
            throw new InvalidOperationException("Trying to add multiple instances of a non-instanced power to a creature.");
        }
        _powers.Add(power);
        this.PowerApplied?.Invoke(power);
	}
}

同样很好理解,先进行有效性判断,Owner要一致;非IsInstancedPower为叠层,不可存在多个。

Power加到列表中,发送一个事件,值得一提的是,在杀戮尖塔2中,类似的事件大部分用来作为GUI显示逻辑,后面文章会详细论述。

Power如何生效

这个就简单了,在上文Hook中提到的,每个Power重写需要的时机并进行对应的行为。在Hook中遍历CreaturePower列表即可。

这里举个简单例子:

Dexterity

Dexterity improves Block gained from cards.

重写了ModifyBlockAdditive函数,返回对应层数的护甲。

Power如何删除

一般地,PowerCmd提供了Remove接口

public static class PowerCmd
{
	public static async Task Remove(PowerModel? power)
	{
        if (power != null)
        {
            power.RemoveInternal();
            await Cmd.CustomScaledWait(0.2f, 0.4f);
            await power.AfterRemoved(power.Owner);
        }
	}
}
public abstract class PowerModel : AbstractModel
{
	public void RemoveInternal()
	{
        AssertMutable();
        this.Removed?.Invoke();
        Owner.RemovePowerInternal(this);
	}
}
public class Creature
{
	public void RemovePowerInternal(PowerModel power)
	{
        if (power.Owner != this)
        {
            throw new InvalidOperationException("ONLY CALL THIS FROM PowerModel.RemoveInternal!");
        }
        _powers.Remove(power);
        this.PowerRemoved?.Invoke(power);
	}
}

就是把Power从列表中删除,触发事件通知订阅者。

在杀戮尖塔2中,有几种自动删除类型的Power

第一种,每回合Power层数自动-1,当Power层数为0时,删除。

这种实现可以在AfterTurnEnd时候,手动PowerCmd.Decrement,让层数-1,然后判断层数为0时是否要删除。

小结

总体来说,Power的实现比较简单,注意叠层逻辑即可。