using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Core.Model;
namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
{
///
/// 非指向性技能示例1:迷踪步
/// 立即将角色传送到范围内的任意一个未被角色占据的地点
/// 类型:战技
///
public class ExampleNonDirectionalSkill1 : Skill
{
public override long Id => 1;
public override string Name => "迷踪步";
public override string Description => string.Join("", Effects.Select(e => e.Description));
public override string DispelDescription => string.Join("", Effects.Select(e => e.DispelDescription));
public override double EPCost => 25;
public override double CD => 35 - 1.5 * Level;
public override double HardnessTime { get; set; } = 3;
public override bool IsNonDirectional => true;
public override bool CanSelectSelf => true;
public override bool CanSelectEnemy => false;
public override bool CanSelectTeammate => false;
public override int CanSelectTargetRange => 0;
public override bool SelectIncludeCharacterGrid => false;
public override bool AllowSelectNoCharacterGrid => true;
public ExampleNonDirectionalSkill1(Character? character = null) : base(SkillType.Skill, character)
{
CastRange = 9;
// 所有的技能特效,如果能直接 new,建议就直接 new,提高性能和可读性(工厂效率低且不好调试,工厂更偏向于动态创建技能,而对于编码实现的技能来说,怎么简单怎么来)
Effects.Add(new ExampleNonDirectionalSkill1Effect(this));
}
}
///
/// 注意:特效包含于技能之中,多个特效组合成一个技能
///
///
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 string DispelDescription => "";
public override void OnSkillCasted(Character caster, List targets, List grids, Dictionary others)
{
// 只有开启了地图模式才有效
if (GamingQueue?.Map is GameMap map && grids.Count > 0)
{
map.CharacterMove(caster, map.GetCharacterCurrentGrid(caster), grids[0]);
}
}
}
///
/// 主动技能特效示例:基于攻击力的伤害(带基础伤害)
///
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(Character caster, List targets, List grids, Dictionary others)
{
foreach (Character enemy in targets)
{
DamageToEnemy(caster, enemy, DamageType, MagicType, Damage);
}
// 或者:
//double damage = Damage;
//foreach (Character enemy in targets)
//{
// DamageToEnemy(caster, enemy, DamageType, MagicType, damage);
//}
}
}
///
/// 非指向性技能示例2:钻石星尘
/// 对半径为 2 格的圆形区域造成魔法伤害
/// 类型:魔法
///
public class ExampleNonDirectionalSkill2 : Skill
{
public override long Id => 2;
public override string Name => "钻石星尘";
public override string Description => string.Join("", Effects.Select(e => e.Description));
public override double MPCost => Level > 0 ? 80 + (75 * (Level - 1)) : 80;
public override double CD => Level > 0 ? 35 + (2 * (Level - 1)) : 35;
public override double CastTime => 9;
public override double HardnessTime { get; set; } = 6;
public override int CanSelectTargetCount
{
get
{
return Level switch
{
4 or 5 or 6 => 2,
7 or 8 => 3,
_ => 1
};
}
}
public override bool IsNonDirectional => true;
public override SkillRangeType SkillRangeType => SkillRangeType.Circle;
public override int CanSelectTargetRange => 2;
public override double MagicBottleneck => 35 + 24 * (Level - 1);
public ExampleNonDirectionalSkill2(Character? character = null) : base(SkillType.Magic, character)
{
Effects.Add(new ExampleDamageBasedOnATKWithBasicDamage(this, 20, 20, 0.03, 0.02, DamageType.Magical));
}
}
///
/// 指向性技能示例:全力一击
/// 对目标造成物理伤害并打断施法
/// 类型:战技
///
public class ExampleSkill : Skill
{
public override long Id => 3;
public override string Name => "全力一击";
public override string Description => string.Join("", Effects.Select(e => e.Description));
public override string DispelDescription => string.Join("", Effects.Select(e => e.Description));
public override string ExemptionDescription => Effects.Count > 0 ? Effects.First(e => e is ExampleInterruptCastingEffect).ExemptionDescription : "";
public override double EPCost => 60;
public override double CD => 20;
public override double HardnessTime { get; set; } = 8;
// 豁免检定有两种方式,通过直接设置技能的属性可自动触发豁免,但是豁免成功让整个技能都失效,包括伤害(或其他 Effects),另一种方式比较安全,但需要手动调用方法,看下面
public override Effect? EffectForExemptionCheck => Effects.FirstOrDefault(e => e is ExampleInterruptCastingEffect);
public ExampleSkill(Character? character = null) : base(SkillType.Skill, character)
{
Effects.Add(new ExampleDamageBasedOnATKWithBasicDamage(this, 65, 65, 0.09, 0.04, DamageType.Physical));
Effects.Add(new ExampleInterruptCastingEffect(this));
}
}
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(Character caster, List targets, List grids, Dictionary others)
{
foreach (Character target in targets)
{
// 这是另一种豁免检定方式,在技能实现时,自行调用 CheckExemption,只对该特效有效
if (!CheckExemption(caster, target, this))
{
InterruptCasting(target, caster);
}
}
}
}
///
/// 被动技能示例:心灵之弦
///
public class ExamplePassiveSkill : Skill
{
public override long Id => 4;
public override string Name => "心灵之弦";
public override string Description => Effects.Count > 0 ? Effects.First().Description : "";
public ExamplePassiveSkill(Character? character = null) : base(SkillType.Passive, character)
{
Effects.Add(new ExamplePassiveSkillEffect(this));
}
///
/// 特别注意:被动技能必须重写此方法,否则它不会自动添加到角色身上
///
///
public override IEnumerable AddPassiveEffectToCharacter()
{
return Effects;
}
}
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.##} {GameplayEquilibriumConstant.InGameTime})" : "");
///
/// 被动技能的冷却时间可以借用技能的冷却时间( 等属性)实现,也可以内部实现,看喜好
///
public double CurrentCD { get; set; } = 0;
public double CD { get; set; } = 10;
private bool IsNested = false;
// 该钩子属于伤害计算流程的特效乘区2
public override double AlterActualDamageAfterCalculation(Character character, Character enemy, double damage, bool isNormalAttack, DamageType damageType, MagicType magicType, DamageResult damageResult, ref bool isEvaded, Dictionary totalDamageBonus)
{
if (character == Skill.Character && IsNested && isNormalAttack && damage > 0)
{
// 此方法返回的是加值
return -(damage / 2);
}
return 0;
}
public override void AfterDamageCalculation(Character character, Character enemy, double damage, double actualDamage, bool isNormalAttack, DamageType damageType, MagicType magicType, DamageResult damageResult)
{
if (character == Skill.Character && 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 (character == Skill.Character && IsNested)
{
IsNested = false;
}
}
public override void OnTimeElapsed(Character character, double elapsed)
{
// 时间流逝时,手动减少CD。如果借用了技能的冷却时间属性,就不需要写了
if (CurrentCD > 0)
{
CurrentCD -= elapsed;
if (CurrentCD <= 0)
{
CurrentCD = 0;
}
}
}
public override void AlterHardnessTimeAfterNormalAttack(Character character, ref double baseHardnessTime, ref bool isCheckProtected)
{
// 普攻后调整硬直时间。ref 变量直接修改
baseHardnessTime *= 0.8;
}
}
///
/// 爆发技示例:千羽瞬华
/// 给自己加属性,并联动其他技能
///
public class ExampleSuperSkill : Skill
{
public override long Id => 5;
public override string Name => "千羽瞬华";
public override string Description => Effects.Count > 0 ? Effects.First().Description : "";
public override string DispelDescription => Effects.Count > 0 ? Effects.First().DispelDescription : "";
public override double EPCost => 100;
public override double CD => 60;
public override double HardnessTime { get; set; } = 10;
public override bool CanSelectSelf => true;
public override bool CanSelectEnemy => false;
public ExampleSuperSkill(Character? character = null) : base(SkillType.SuperSkill, character)
{
Effects.Add(new ExampleSuperSkillEffect(this));
}
}
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}内,增加{Skill.SkillOwner()} {ATKMultiplier * 100:0.##}% 攻击力 [ {ATKBonus:0.##} ]、{PhysicalPenetrationBonus * 100:0.##}% 物理穿透和 {EvadeRateBonus * 100:0.##}% 闪避率(不可叠加),普通攻击硬直时间额外减少 20%,基于 {Coefficient * 100:0.##}% 敏捷 [ {DamageBonus:0.##} ] 强化普通攻击的伤害。在持续时间内,[ 心灵之弦 ] 的冷却时间降低至 3 {GameplayEquilibriumConstant.InGameTime}。";
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 DamageBonus => Coefficient * Skill.Character?.AGI ?? 0;
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(Character character)
{
// 记录状态并修改属性
ActualATKBonus = ATKBonus;
ActualPhysicalPenetrationBonus = PhysicalPenetrationBonus;
ActualEvadeRateBonus = EvadeRateBonus;
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 = 3;
if (e.CurrentCD > e.CD) e.CurrentCD = e.CD;
}
}
public override void OnEffectLost(Character character)
{
// 从记录的状态中恢复
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;
}
}
public override CharacterActionType AlterActionTypeBeforeAction(Character character, DecisionPoints dp, CharacterState state, ref bool canUseItem, ref bool canCastSkill, ref double pUseItem, ref double pCastSkill, ref double pNormalAttack, ref bool forceAction)
{
// 对于 AI,可以提高角色的普攻积极性,调整决策偏好,这样可以充分利用技能效果
pNormalAttack += 0.1;
return CharacterActionType.None;
}
public override double AlterExpectedDamageBeforeCalculation(Character character, Character enemy, double damage, bool isNormalAttack, DamageType damageType, MagicType magicType, Dictionary totalDamageBonus)
{
if (character == Skill.Character && isNormalAttack)
{
return DamageBonus;
}
return 0;
}
public override void AlterHardnessTimeAfterNormalAttack(Character character, ref double baseHardnessTime, ref bool isCheckProtected)
{
// 可以和上面的心灵之弦叠加,最终硬直时间=硬直时间*0.8*0.8
baseHardnessTime *= 0.8;
}
public override void OnSkillCasted(Character caster, List targets, List grids, Dictionary others)
{
ActualATKBonus = 0;
ActualPhysicalPenetrationBonus = 0;
ActualEvadeRateBonus = 0;
// 不叠加的效果通常只刷新持续时间
RemainDuration = Duration;
// 通常,this(Effect本身)在整局战斗中都是唯一的,需要只需要判断 this 就行
if (!caster.Effects.Contains(this))
{
// 加也是加 this
caster.Effects.Add(this);
OnEffectGained(caster);
}
// 施加状态记录到回合日志中
RecordCharacterApplyEffects(caster, EffectType.DamageBoost, EffectType.PenetrationBoost);
}
}
}