Skip to content

自定义特效

基于官方示例 Library/Module/Example/ExampleSkill.csExampleItem.cs

v3.0 起统一上下文参数

所有特效钩子接收单一参数上下文对象(如 SkillCastContextDamageContext),主角色从 ctx.Actor 获取,原 ref 参数改为上下文可写属性,返回值语义不变。详见 HookContext 参数上下文族


伤害特效(带等级成长)

最常见的特效类型,数值随技能等级动态计算:

csharp
public class ExampleDamageBasedOnATKWithBasicDamage : Effect
{
    public override long Id => Skill.Id;
    public override string Name => Skill.Name;

    // 动态描述
    public override string Description =>
        $"对{Skill.TargetDescription()}造成 {BaseDamage:0.##} + " +
        $"{ATKCoefficient * 100:0.##}% 攻击力 [ {Damage:0.##} ] 点" +
        $"{CharacterSet.GetDamageTypeName(DamageType, MagicType)}。";

    // 数值随等级成长
    private double BaseDamage =>
        Skill.Level > 0
            ? BaseNumericDamage + BaseNumericDamageLevelGrowth * (Skill.Level - 1)
            : BaseNumericDamage;

    private double ATKCoefficient =>
        Skill.Level > 0
            ? BaseATKCoefficient + BaseATKCoefficientLevelGrowth * (Skill.Level - 1)
            : BaseATKCoefficient;

    private double Damage => BaseDamage + ATKCoefficient * (Skill.Character?.ATK ?? 0);

    // 可配置参数(通过构造函数传入)
    private double BaseNumericDamage { get; set; } = 100;
    private double BaseNumericDamageLevelGrowth { get; set; } = 50;
    private double BaseATKCoefficient { get; set; } = 0.2;
    private double BaseATKCoefficientLevelGrowth { get; set; } = 0.2;
    private DamageType DamageType { get; set; } = DamageType.Magical;

    public ExampleDamageBasedOnATKWithBasicDamage(
        Skill skill,
        double baseNumericDamage,
        double baseNumericDamageLevelGrowth,
        double baseATKCoefficient,
        double baseATKCoefficientLevelGrowth,
        DamageType damageType = DamageType.Magical,
        MagicType magicType = MagicType.None) : base(skill)
    {
        GamingQueue = skill.GamingQueue;
        BaseNumericDamage = baseNumericDamage;
        BaseNumericDamageLevelGrowth = baseNumericDamageLevelGrowth;
        BaseATKCoefficient = baseATKCoefficient;
        BaseATKCoefficientLevelGrowth = baseATKCoefficientLevelGrowth;
        DamageType = damageType;
        MagicType = magicType;
    }

    public override void OnSkillCasted(SkillCastContext ctx)
    {
        if (ctx.Actor is Character caster)
        {
            foreach (Character enemy in ctx.Targets)
            {
                DamageToEnemy(caster, enemy, DamageType, MagicType, Damage);
            }
        }
    }
}

打断施法特效(含豁免检定)

csharp
public class ExampleInterruptCastingEffect : Effect
{
    public override long Id => Skill.Id;
    public override string Name => Skill.Name;
    public override string Description =>
        $"对{Skill.TargetDescription()}施加打断施法效果:中断其正在进行的吟唱。";
    public override EffectType EffectType => EffectType.InterruptCasting;

    public ExampleInterruptCastingEffect(Skill skill) : base(skill)
    {
        GamingQueue = skill.GamingQueue;
    }

    public override void OnSkillCasted(SkillCastContext ctx)
    {
        if (ctx.Actor is Character caster)
        {
            foreach (Character target in ctx.Targets)
            {
                // 手动调用豁免检定——只对该特效有效
                if (!CheckExemption(caster, target, this))
                {
                    InterruptCasting(target, caster);
                }
            }
        }
    }
}

地图传送特效(非指向性技能用)

csharp
public class ExampleNonDirectionalSkill1Effect(Skill skill) : Effect(skill)
{
    public override long Id => Skill.Id;
    public override string Name => Skill.Name;
    public override string Description =>
        $"立即将角色传送到范围内的任意{Skill.TargetDescription()}。";

    public override void OnSkillCasted(SkillCastContext ctx)
    {
        // 只有地图模式才有效
        if (ctx.Actor is Character caster && GamingQueue?.Map is GameMap map && ctx.Grids.Count > 0)
        {
            map.CharacterMove(caster,
                map.GetCharacterCurrentGrid(caster), ctx.Grids[0]);
        }
    }
}

被动特效(普攻额外触发 + CD + 乘区钩子)

csharp
public class ExamplePassiveSkillEffect(Skill skill) : Effect(skill)
{
    public override long Id => Skill.Id;
    public override string Name => Skill.Name;
    public override string Description =>
        $"普通攻击硬直时间减少 20%。冷却 {CD:0.##} {GameplayEquilibriumConstant.InGameTime}。" +
        (CurrentCD > 0 ? $"(冷却剩余 {CurrentCD:0.##})" : "");

    public double CurrentCD { get; set; } = 0;
    public double CD { get; set; } = 10;
    private bool IsNested = false;

    // 乘区2钩子:调整伤害计算后的实际伤害
    public override double AlterActualDamageAfterCalculation(DamageContext ctx)
    {
        // 嵌套普攻伤害折半
        if (ctx.Actor == Skill.Character && IsNested && ctx.IsNormalAttack && ctx.Damage > 0)
            return -(ctx.Damage / 2);  // 返回负值即削减
        return 0;
    }

    // 伤害计算后钩子:额外发动一次普通攻击
    public override void AfterDamageCalculation(DamageContext ctx)
    {
        if (ctx.Actor is Character character && ctx.Enemy is Character enemy
            && character == Skill.Character && ctx.IsNormalAttack
            && CurrentCD == 0 && !IsNested && GamingQueue != null && enemy.HP > 0)
        {
            WriteLine($"[ {character} ] 发动了{Skill.Name}!额外进行一次普通攻击!");
            CurrentCD = CD;
            IsNested = true;
            character.NormalAttack.Attack(GamingQueue, character, null, enemy);
        }

        if (ctx.Actor == Skill.Character && IsNested)
            IsNested = false;
    }

    // 时间流逝时冷却
    public override void OnTimeElapsed(TimeLapseContext ctx)
    {
        if (CurrentCD > 0)
        {
            CurrentCD -= ctx.Elapsed;
            if (CurrentCD <= 0) CurrentCD = 0;
        }
    }

    // 普攻后硬直时间减免
    public override void AlterHardnessTimeAfterNormalAttack(HardnessContext ctx)
    {
        ctx.BaseHardnessTime *= 0.8;  // ref 变量 → 可写属性
    }
}

爆发技增益特效(叠加属性 + 联动技能 + AI 偏好)

csharp
public class ExampleSuperSkillEffect(Skill skill) : Effect(skill)
{
    public override long Id => Skill.Id;
    public override string Name => Skill.Name;
    public override string Description =>
        $"{Duration:0.##} {GameplayEquilibriumConstant.InGameTime}内,增加" +
        $"{ATKMultiplier * 100:0.##}% 攻击力、{PhysicalPenetrationBonus * 100:0.##}% 物理穿透、" +
        $"{EvadeRateBonus * 100:0.##}% 闪避率,普攻硬直时间额外减少 20%。";

    public override bool Durative => true;
    public override double Duration => 30;
    public override DispelledType DispelledType => DispelledType.CannotBeDispelled;

    private double Coefficient => 1.2 * (1 + 0.5 * (Skill.Level - 1));
    private double ATKMultiplier =>
        Skill.Level > 0 ? 0.15 + 0.03 * (Skill.Level - 1) : 0.15;
    private double ATKBonus => ATKMultiplier * (Skill.Character?.BaseATK ?? 0);
    private double PhysicalPenetrationBonus =>
        Skill.Level > 0 ? 0.1 + 0.03 * (Skill.Level - 1) : 0.1;
    private double EvadeRateBonus =>
        Skill.Level > 0 ? 0.1 + 0.02 * (Skill.Level - 1) : 0.1;

    // 保存实际加成值以便恢复
    private double ActualATKBonus = 0;
    private double ActualPhysicalPenetrationBonus = 0;
    private double ActualEvadeRateBonus = 0;

    // 特效施加:修改角色属性
    public override void OnEffectGained(HookContext ctx)
    {
        if (ctx.Actor is not Character character) return;
        ActualATKBonus = ATKBonus;
        ActualPhysicalPenetrationBonus = PhysicalPenetrationBonus;
        ActualEvadeRateBonus = EvadeRateBonus;
        character.ExATK2 += ActualATKBonus;
        character.PhysicalPenetration += ActualPhysicalPenetrationBonus;
        character.ExEvadeRate += ActualEvadeRateBonus;

        // 联动其他技能:修改 ExamplePassiveSkillEffect 的冷却
        if (character.Effects.FirstOrDefault(
            e => e is ExamplePassiveSkillEffect && e.Skill.Character == character)
            is ExamplePassiveSkillEffect e)
        {
            e.CD = 3;
            if (e.CurrentCD > e.CD) e.CurrentCD = e.CD;
        }
    }

    // 特效移除:恢复角色属性
    public override void OnEffectLost(HookContext ctx)
    {
        if (ctx.Actor is not Character character) return;
        character.ExATK2 -= ActualATKBonus;
        character.PhysicalPenetration -= ActualPhysicalPenetrationBonus;
        character.ExEvadeRate -= ActualEvadeRateBonus;

        if (character.Effects.FirstOrDefault(
            e => e is ExamplePassiveSkillEffect && e.Skill.Character == character)
            is ExamplePassiveSkillEffect e)
        {
            e.CD = 10;
        }
    }

    // AI 决策偏好:提高普攻积极性
    public override CharacterActionType AlterActionTypeBeforeAction(DecisionContext ctx)
    {
        ctx.PNormalAttack += 0.1;
        return CharacterActionType.None;
    }

    // 乘区1钩子:普攻伤害加成(基于敏捷)
    public override double AlterExpectedDamageBeforeCalculation(DamageContext ctx)
    {
        if (ctx.Actor == Skill.Character && ctx.IsNormalAttack)
            return Coefficient * (Skill.Character?.AGI ?? 0);
        return 0;
    }

    // 普攻后硬直时间减免
    public override void AlterHardnessTimeAfterNormalAttack(HardnessContext ctx)
    {
        ctx.BaseHardnessTime *= 0.8;
    }

    // 技能释放时:不叠加效果刷新持续时间
    public override void OnSkillCasted(SkillCastContext ctx)
    {
        if (ctx.Actor is not Character caster) return;
        ActualATKBonus = 0;
        ActualPhysicalPenetrationBonus = 0;
        ActualEvadeRateBonus = 0;
        RemainDuration = Duration;
        if (!caster.Effects.Contains(this))
        {
            caster.Effects.Add(this);
            OnEffectGained(new HookContext(GamingQueue, caster));
        }
        RecordCharacterApplyEffects(caster, EffectType.DamageBoost, EffectType.PenetrationBoost);
    }
}

开放特效(从 Dictionary 读取参数,支持动态创建)

csharp
public class ExampleOpenEffectExATK2 : Effect
{
    public override long Id => 1001;
    public override string Name { get; set; } = "攻击力加成";
    public override string Description =>
        $"{(ActualBonus >= 0 ? "增加" : "减少")}角色 " +
        $"{Math.Abs(BonusFactor) * 100:0.##}% " +
        $"[ {(ActualBonus == 0 ? "基于基础攻击力" : $"{Math.Abs(ActualBonus):0.##}")} ] 点攻击力。";

    private readonly double BonusFactor = 0;
    private double ActualBonus = 0;

    // 特效施加
    public override void OnEffectGained(HookContext ctx)
    {
        if (ctx.Actor is not Character character) return;
        if (Durative && RemainDuration == 0)
            RemainDuration = Duration;
        else if (RemainDurationTurn == 0)
            RemainDurationTurn = DurationTurn;

        ActualBonus = character.BaseATK * BonusFactor;
        character.ExATKPercentage += BonusFactor;
    }

    // 特效移除
    public override void OnEffectLost(HookContext ctx)
    {
        if (ctx.Actor is not Character character) return;
        character.ExATKPercentage -= BonusFactor;
    }

    // 属性变化时刷新(等级提升、装备更换等引起的基础属性变化)
    public override void OnAttributeChanged(HookContext ctx)
    {
        OnEffectLost(ctx);
        OnEffectGained(ctx);
    }

    // 从 Dictionary 读取参数
    public ExampleOpenEffectExATK2(Skill skill, Dictionary<string, object> args,
        Character? source = null) : base(skill, args)
    {
        EffectType = EffectType.Item;
        GamingQueue = skill.GamingQueue;
        Source = source;
        if (Values.Count > 0)
        {
            string key = Values.Keys.FirstOrDefault(
                s => s.Equals("exatk", StringComparison.CurrentCultureIgnoreCase)) ?? "";
            if (key.Length > 0 && double.TryParse(Values[key].ToString(), out double v))
                BonusFactor = v;
        }
    }
}

真实 Effect 方法速查

以下方法均来自官方示例的实际 override,没有虚构:

方法签名用途来自
OnEffectGained(HookContext)特效施加到角色ExampleSuperSkillEffect, ExATK2
OnEffectLost(HookContext)特效从角色移除ExampleSuperSkillEffect, ExATK2
OnSkillCasted(SkillCastContext)技能释放时执行所有技能特效
OnTimeElapsed(TimeLapseContext)时间流逝ExamplePassiveSkillEffect
OnAttributeChanged(HookContext)属性变化时刷新ExATK2
AfterDamageCalculation(DamageContext)伤害计算后ExamplePassiveSkillEffect
AlterActualDamageAfterCalculation(DamageContext)乘区2调整(返回加值)ExamplePassiveSkillEffect
AlterExpectedDamageBeforeCalculation(DamageContext)乘区1调整(返回加值)ExampleSuperSkillEffect
AlterHardnessTimeAfterNormalAttack(HardnessContext)普攻后调整硬直(ctx.BaseHardnessTime 可写)ExamplePassiveSkillEffect, ExampleSuperSkillEffect
AlterActionTypeBeforeAction(DecisionContext)AI 决策偏好调整ExampleSuperSkillEffect
BeforeSkillCasted(SkillCastContext)技能释放前(读取 ctx.MPCost/ctx.EPCostSoulboundEffect
AfterSkillCasted(SkillCastContext)技能释放后SoulboundEffect

特效内部可用方法

方法说明
DamageToEnemy(caster, enemy, DamageType, MagicType, damage)造成伤害
CheckExemption(caster, target, this)豁免检定(返回 true = 豁免成功)
InterruptCasting(target, interrupter)打断目标施法
RecordCharacterApplyEffects(caster, params EffectType[])记录特效施加到回合日志
WriteLine(string)输出日志

基于 LGPL-3.0 许可证发布