From 769d0e4281e0f5013605bd8203a47a32ff8d90a8 Mon Sep 17 00:00:00 2001 From: milimoe <110188673+milimoe@users.noreply.github.com> Date: Sat, 26 Apr 2025 03:07:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=85=8D=E7=96=AB=E3=80=81?= =?UTF-8?q?=E9=A9=B1=E6=95=A3=EF=BC=9B=E9=A1=BA=E5=BA=8F=E8=A1=A8=E3=80=81?= =?UTF-8?q?=E7=88=86=E5=8F=91=E6=8A=80=E3=80=81=E5=8A=A9=E6=94=BB=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=20(#129)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 特效底层支持直接修改硬直时间;添加驱散类型 * 添加 debuff * 明确了驱散定义;添加助攻窗口期;修改预释放爆发技为不可驱散;预释放爆发技一定是最先行动;修复复活时导致硬直时间变成负数的问题 * 调整驱散描述 * 实现驱散系统;修复角色百分比公式错误;添加非伤害类助攻;添加辅助数据统计;修改一些文本显示 * 添加免疫、吸血、护盾机制 * 继续完善免疫和驱散、护盾和特效钩子等 * 添加新特效类型 --- Controller/RunTimeController.cs | 48 +- Entity/Character/AssistDetail.cs | 59 +- Entity/Character/Character.cs | 173 ++-- Entity/Character/MagicResistance.cs | 112 ++- Entity/Character/Shield.cs | 160 ++++ Entity/Item/Item.cs | 2 +- Entity/Skill/Effect.cs | 480 ++++++++++- Entity/Skill/NormalAttack.cs | 38 +- Entity/Skill/OpenSkill.cs | 6 - Entity/Skill/Skill.cs | 55 +- Entity/Statistics/CharacterStatistics.cs | 4 + Entity/System/Inventory.cs | 1 - Entity/System/RoundRecord.cs | 21 +- Interface/Base/IGamingQueue.cs | 16 +- .../JsonConverter/CharacterConverter.cs | 13 + .../JsonConverter/NormalAttackConverter.cs | 4 + .../Common/JsonConverter/ShieldConverter.cs | 69 ++ Library/Constant/ConstantSet.cs | 167 +++- Library/Constant/StateEnum.cs | 18 +- Library/Constant/TypeEnum.cs | 126 ++- Model/ActionQueue.cs | 755 +++++++++++++----- Service/JsonManager.cs | 2 +- 22 files changed, 1944 insertions(+), 385 deletions(-) create mode 100644 Entity/Character/Shield.cs create mode 100644 Library/Common/JsonConverter/ShieldConverter.cs diff --git a/Controller/RunTimeController.cs b/Controller/RunTimeController.cs index 4a2422e..cedd9d2 100644 --- a/Controller/RunTimeController.cs +++ b/Controller/RunTimeController.cs @@ -552,7 +552,7 @@ namespace Milimoe.FunGame.Core.Controller /// 获取服务器已发送的信息为SocketObject数组 [ Socket Only ] /// /// - protected SocketObject[] GetServerMessage() + protected SocketObject[] GetServerMessages() { if (_Socket != null && _Socket.Connected) { @@ -571,7 +571,7 @@ namespace Milimoe.FunGame.Core.Controller SocketMessageType result = SocketMessageType.Unknown; try { - SocketObject[] messages = GetServerMessage(); + SocketObject[] messages = GetServerMessages(); foreach (SocketObject obj in messages) { @@ -660,14 +660,14 @@ namespace Milimoe.FunGame.Core.Controller /// /// 客户端接收服务器断开连接的通知 /// - /// - protected abstract void SocketHandler_Disconnect(SocketObject ServerMessage); + /// + protected abstract void SocketHandler_Disconnect(SocketObject obj); /// /// 客户端接收并处理服务器系统消息 /// - /// - protected virtual void SocketHandler_System(SocketObject ServerMessage) + /// + protected virtual void SocketHandler_System(SocketObject obj) { } @@ -675,8 +675,8 @@ namespace Milimoe.FunGame.Core.Controller /// /// 客户端接收并处理服务器心跳 /// - /// - protected virtual void SocketHandler_HeartBeat(SocketObject ServerMessage) + /// + protected virtual void SocketHandler_HeartBeat(SocketObject obj) { } @@ -684,8 +684,8 @@ namespace Milimoe.FunGame.Core.Controller /// /// 客户端接收强制退出登录的通知 /// - /// - protected virtual void SocketHandler_ForceLogout(SocketObject ServerMessage) + /// + protected virtual void SocketHandler_ForceLogout(SocketObject obj) { } @@ -693,8 +693,8 @@ namespace Milimoe.FunGame.Core.Controller /// /// 客户端接收并处理聊天信息 /// - /// - protected virtual void SocketHandler_Chat(SocketObject ServerMessage) + /// + protected virtual void SocketHandler_Chat(SocketObject obj) { } @@ -702,8 +702,8 @@ namespace Milimoe.FunGame.Core.Controller /// /// 客户端接收并处理更换房主信息 /// - /// - protected virtual void SocketHandler_UpdateRoomMaster(SocketObject ServerMessage) + /// + protected virtual void SocketHandler_UpdateRoomMaster(SocketObject obj) { } @@ -711,8 +711,8 @@ namespace Milimoe.FunGame.Core.Controller /// /// 客户端接收并处理匹配房间成功信息 /// - /// - protected virtual void SocketHandler_MatchRoom(SocketObject ServerMessage) + /// + protected virtual void SocketHandler_MatchRoom(SocketObject obj) { } @@ -720,8 +720,8 @@ namespace Milimoe.FunGame.Core.Controller /// /// 客户端接收并处理开始游戏信息 /// - /// - protected virtual void SocketHandler_StartGame(SocketObject ServerMessage) + /// + protected virtual void SocketHandler_StartGame(SocketObject obj) { } @@ -729,8 +729,8 @@ namespace Milimoe.FunGame.Core.Controller /// /// 客户端接收并处理游戏结束信息 /// - /// - protected virtual void SocketHandler_EndGame(SocketObject ServerMessage) + /// + protected virtual void SocketHandler_EndGame(SocketObject obj) { } @@ -738,8 +738,8 @@ namespace Milimoe.FunGame.Core.Controller /// /// 客户端接收并处理局内消息 /// - /// - protected virtual void SocketHandler_Gaming(SocketObject ServerMessage) + /// + protected virtual void SocketHandler_Gaming(SocketObject obj) { } @@ -747,8 +747,8 @@ namespace Milimoe.FunGame.Core.Controller /// /// 客户端接收并处理匿名服务器的消息 /// - /// - protected virtual void SocketHandler_AnonymousGameServer(SocketObject ServerMessage) + /// + protected virtual void SocketHandler_AnonymousGameServer(SocketObject obj) { } diff --git a/Entity/Character/AssistDetail.cs b/Entity/Character/AssistDetail.cs index 2226d2b..74490db 100644 --- a/Entity/Character/AssistDetail.cs +++ b/Entity/Character/AssistDetail.cs @@ -5,13 +5,28 @@ namespace Milimoe.FunGame.Core.Entity /// /// 用于记录对哪个角色造成了多少伤害 /// - public class AssistDetail : Dictionary + public class AssistDetail { /// /// 此详情类属于哪个角色 /// public Character Character { get; } + /// + /// 对敌人造成的伤害 + /// + public Dictionary Damages { get; } = []; + + /// + /// 最后一次造成伤害的时间 + /// + public Dictionary DamageLastTime { get; } = []; + + /// + /// 对某角色最后一次友方非伤害辅助的时间 + /// + public Dictionary NotDamageAssistLastTime { get; } = []; + /// /// 初始化一个助攻详情类 /// @@ -27,21 +42,23 @@ namespace Milimoe.FunGame.Core.Entity } /// - /// 获取和设置对 的伤害 + /// 获取和设置对 的伤害,并设置时间 /// /// + /// /// - public new double this[Character enemy] + public double this[Character enemy, double? time = null] { get { - return base[enemy]; + return Damages[enemy]; } set { - if (!base.TryAdd(enemy, Calculation.Round2Digits(value))) + Damages[enemy] = Calculation.Round2Digits(value); + if (time.HasValue) { - base[enemy] = Calculation.Round2Digits(value); + DamageLastTime[enemy] = time.Value; } } } @@ -53,7 +70,35 @@ namespace Milimoe.FunGame.Core.Entity /// 目标的 的百分比形式 public double GetPercentage(Character enemy) { - return Calculation.Round2Digits(base[enemy] / enemy.MaxHP); + return Calculation.Round2Digits(Damages[enemy] / enemy.MaxHP); + } + + /// + /// 获取对 造成伤害的最后时间 + /// + /// + /// -1 意味着没有时间 + public double GetLastTime(Character enemy) + { + if (DamageLastTime.TryGetValue(enemy, out double time)) + { + return time; + } + return -1; + } + + /// + /// 获取对某角色友方非伤害辅助的最后时间 + /// + /// + /// -1 意味着没有时间 + public double GetNotDamageAssistLastTime(Character character) + { + if (NotDamageAssistLastTime.TryGetValue(character, out double time)) + { + return time; + } + return -1; } } } diff --git a/Entity/Character/Character.cs b/Entity/Character/Character.cs index bcf8963..d64e9fa 100644 --- a/Entity/Character/Character.cs +++ b/Entity/Character/Character.cs @@ -161,6 +161,11 @@ namespace Milimoe.FunGame.Core.Entity /// public Dictionary> CharacterEffectTypes { get; } = []; + /// + /// 角色目前被特效施加的免疫状态 [ 战斗相关 ] + /// + public Dictionary> CharacterImmuneTypes { get; } = []; + /// /// 角色是否是中立的 [ 战斗相关 ] /// @@ -171,6 +176,11 @@ namespace Milimoe.FunGame.Core.Entity /// public bool IsUnselectable { get; set; } = false; + /// + /// 角色是否具备免疫状态 [ 战斗相关 ] + /// + public ImmuneType ImmuneType { get; set; } = ImmuneType.None; + /// /// 初始生命值 [ 初始设定 ] /// @@ -195,7 +205,7 @@ namespace Milimoe.FunGame.Core.Entity /// /// 额外生命值3 [ 额外生命值% ] /// - public double ExHP3 => (BaseHP + ExHP + ExHP2) * ExHPPercentage; + public double ExHP3 => BaseHP * ExHPPercentage; /// /// 额外生命值% [ 与技能和物品相关 ] @@ -248,7 +258,7 @@ namespace Milimoe.FunGame.Core.Entity /// /// 额外魔法值3 [ 额外魔法值% ] /// - public double ExMP3 => (BaseMP + ExMP + ExMP2) * ExMPPercentage; + public double ExMP3 => BaseMP * ExMPPercentage; /// /// 额外魔法值% [ 与技能和物品相关 ] @@ -353,7 +363,7 @@ namespace Milimoe.FunGame.Core.Entity /// /// 额外攻击力3 [ 额外攻击力% ] /// - public double ExATK3 => (BaseATK + ExATK + ExATK2) * ExATKPercentage; + public double ExATK3 => BaseATK * ExATKPercentage; /// /// 额外攻击力% [ 与技能和物品相关 ] @@ -389,7 +399,7 @@ namespace Milimoe.FunGame.Core.Entity /// /// 额外物理护甲3 [ 额外物理护甲% ] /// - public double ExDEF3 => (BaseDEF + ExDEF + ExDEF2) * ExDEFPercentage; + public double ExDEF3 => BaseDEF * ExDEFPercentage; /// /// 额外物理护甲% [ 与技能和物品相关 ] @@ -607,17 +617,17 @@ namespace Milimoe.FunGame.Core.Entity /// /// 额外力量2 [ 额外力量% ] /// - public double ExSTR2 => (BaseSTR + ExSTR) * ExSTRPercentage; + public double ExSTR2 => BaseSTR * ExSTRPercentage; /// /// 额外敏捷2 [ 额外敏捷% ] /// - public double ExAGI2 => (BaseAGI + ExAGI) * ExAGIPercentage; + public double ExAGI2 => BaseAGI * ExAGIPercentage; /// /// 额外智力2 [ 额外智力% ] /// - public double ExINT2 => (BaseINT + ExINT) * ExINTPercentage; + public double ExINT2 => BaseINT * ExINTPercentage; /// /// 额外力量% [ 与技能和物品相关 ] @@ -787,6 +797,16 @@ namespace Milimoe.FunGame.Core.Entity /// public double ExEvadeRate { get; set; } = 0; + /// + /// 生命偷取 [ 与技能和物品相关 ] + /// + public double Lifesteal { get; set; } = 0; + + /// + /// 护盾值 [ 与技能和物品相关 ] + /// + public Shield Shield { get; set; } + /// /// 普通攻击对象 /// @@ -851,6 +871,7 @@ namespace Milimoe.FunGame.Core.Entity InitialDEF = GameplayEquilibriumConstant.InitialDEF; EquipSlot = new(); MDF = new(); + Shield = new(); NormalAttack = new(this); } @@ -1187,7 +1208,7 @@ namespace Milimoe.FunGame.Core.Entity /// public void OnAttributeChanged() { - List effects = [.. Effects.Where(e => e.Level > 0)]; + List effects = [.. Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)]; foreach (Effect effect in effects) { effect.OnAttributeChanged(this); @@ -1322,7 +1343,10 @@ namespace Milimoe.FunGame.Core.Entity builder.AppendLine($"经验值:{EXP}{(Level != GameplayEquilibriumConstant.MaxLevel && GameplayEquilibriumConstant.EXPUpperLimit.TryGetValue(Level, out double need) ? " / " + need : "")}"); } double exHP = ExHP + ExHP2 + ExHP3; - builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : "")); + List shield = []; + if (Shield.TotalPhysical > 0) shield.Add($"物理:{Shield.TotalPhysical:0.##}"); + if (Shield.TotalMagicial > 0) shield.Add($"魔法:{Shield.TotalMagicial:0.##}"); + builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : "") + (shield.Count > 0 ? $"({string.Join(",", shield)})" : "")); double exMP = ExMP + ExMP2 + ExMP3; builder.AppendLine($"魔法值:{MP:0.##} / {MaxMP:0.##}" + (exMP != 0 ? $" [{BaseMP:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exMP):0.##}]" : "")); builder.AppendLine($"能量值:{EP:0.##} / {GameplayEquilibriumConstant.MaxEP:0.##}"); @@ -1330,10 +1354,7 @@ namespace Milimoe.FunGame.Core.Entity builder.AppendLine($"攻击力:{ATK:0.##}" + (exATK != 0 ? $" [{BaseATK:0.##} {(exATK >= 0 ? "+" : "-")} {Math.Abs(exATK):0.##}]" : "")); double exDEF = ExDEF + ExDEF2 + ExDEF3; builder.AppendLine($"物理护甲:{DEF:0.##}" + (exDEF != 0 ? $" [{BaseDEF:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exDEF):0.##}]" : "") + $" ({PDR * 100:0.##}%)"); - double mdf = Calculation.Round4Digits((MDF.None + MDF.Starmark + MDF.PurityNatural + MDF.PurityContemporary + - MDF.Bright + MDF.Shadow + MDF.Element + MDF.Fleabane + MDF.Particle) / 9) * 100; - if (Calculation.IsApproximatelyZero(mdf)) mdf = 0; - builder.AppendLine($"魔法抗性:{mdf:0.##}%(平均)"); + builder.AppendLine($"魔法抗性:{MDF.Avg:0.##}%(平均)"); double exSPD = AGI * GameplayEquilibriumConstant.AGItoSPDMultiplier + ExSPD; builder.AppendLine($"行动速度:{SPD:0.##}" + (exSPD != 0 ? $" [{InitialSPD:0.##} {(exSPD >= 0 ? "+" : "-")} {Math.Abs(exSPD):0.##}]" : "") + $" ({ActionCoefficient * 100:0.##}%)"); builder.AppendLine($"核心属性:{CharacterSet.GetPrimaryAttributeName(PrimaryAttribute)}"); @@ -1348,6 +1369,7 @@ namespace Milimoe.FunGame.Core.Entity builder.AppendLine($"暴击率:{CritRate * 100:0.##}%"); builder.AppendLine($"暴击伤害:{CritDMG * 100:0.##}%"); builder.AppendLine($"闪避率:{EvadeRate * 100:0.##}%"); + builder.AppendLine($"生命偷取:{Lifesteal * 100:0.##}%"); builder.AppendLine($"冷却缩减:{CDR * 100:0.##}%"); builder.AppendLine($"加速系数:{AccelerationCoefficient * 100:0.##}%"); builder.AppendLine($"物理穿透:{PhysicalPenetration * 100:0.##}%"); @@ -1453,7 +1475,10 @@ namespace Milimoe.FunGame.Core.Entity builder.AppendLine($"经验值:{EXP}{(Level != GameplayEquilibriumConstant.MaxLevel && GameplayEquilibriumConstant.EXPUpperLimit.TryGetValue(Level, out double need) ? " / " + need : "")}"); } double exHP = ExHP + ExHP2 + ExHP3; - builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : "")); + List shield = []; + if (Shield.TotalPhysical > 0) shield.Add($"物理:{Shield.TotalPhysical:0.##}"); + if (Shield.TotalMagicial > 0) shield.Add($"魔法:{Shield.TotalMagicial:0.##}"); + builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : "") + (shield.Count > 0 ? $"({string.Join(",", shield)})" : "")); double exMP = ExMP + ExMP2 + ExMP3; builder.AppendLine($"魔法值:{MP:0.##} / {MaxMP:0.##}" + (exMP != 0 ? $" [{BaseMP:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exMP):0.##}]" : "")); builder.AppendLine($"能量值:{EP:0.##} / {GameplayEquilibriumConstant.MaxEP:0.##}"); @@ -1461,10 +1486,7 @@ namespace Milimoe.FunGame.Core.Entity builder.AppendLine($"攻击力:{ATK:0.##}" + (exATK != 0 ? $" [{BaseATK:0.##} {(exATK >= 0 ? "+" : "-")} {Math.Abs(exATK):0.##}]" : "")); double exDEF = ExDEF + ExDEF2 + ExDEF3; builder.AppendLine($"物理护甲:{DEF:0.##}" + (exDEF != 0 ? $" [{BaseDEF:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exDEF):0.##}]" : "") + $" ({PDR * 100:0.##}%)"); - double mdf = Calculation.Round4Digits((MDF.None + MDF.Starmark + MDF.PurityNatural + MDF.PurityContemporary + - MDF.Bright + MDF.Shadow + MDF.Element + MDF.Fleabane + MDF.Particle) / 9) * 100; - if (Calculation.IsApproximatelyZero(mdf)) mdf = 0; - builder.AppendLine($"魔法抗性:{mdf:0.##}%(平均)"); + builder.AppendLine($"魔法抗性:{MDF.Avg:0.##}%(平均)"); if (showBasicOnly) { builder.AppendLine($"核心属性:{PrimaryAttributeValue:0.##}({CharacterSet.GetPrimaryAttributeName(PrimaryAttribute)})"); @@ -1559,7 +1581,10 @@ namespace Milimoe.FunGame.Core.Entity builder.AppendLine(ToStringWithLevel()); double exHP = ExHP + ExHP2 + ExHP3; - builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : "")); + List shield = []; + if (Shield.TotalPhysical > 0) shield.Add($"物理:{Shield.TotalPhysical:0.##}"); + if (Shield.TotalMagicial > 0) shield.Add($"魔法:{Shield.TotalMagicial:0.##}"); + builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : "") + (shield.Count > 0 ? $"({string.Join(",", shield)})" : "")); double exMP = ExMP + ExMP2 + ExMP3; builder.AppendLine($"魔法值:{MP:0.##} / {MaxMP:0.##}" + (exMP != 0 ? $" [{BaseMP:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exMP):0.##}]" : "")); builder.AppendLine($"能量值:{EP:0.##} / {GameplayEquilibriumConstant.MaxEP:0.##}"); @@ -1607,7 +1632,10 @@ namespace Milimoe.FunGame.Core.Entity builder.AppendLine(ToStringWithLevel()); double exHP = ExHP + ExHP2 + ExHP3; - builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : "")); + List shield = []; + if (Shield.TotalPhysical > 0) shield.Add($"物理:{Shield.TotalPhysical:0.##}"); + if (Shield.TotalMagicial > 0) shield.Add($"魔法:{Shield.TotalMagicial:0.##}"); + builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : "") + (shield.Count > 0 ? $"({string.Join(",", shield)})" : "")); double exMP = ExMP + ExMP2 + ExMP3; builder.AppendLine($"魔法值:{MP:0.##} / {MaxMP:0.##}" + (exMP != 0 ? $" [{BaseMP:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exMP):0.##}]" : "")); builder.AppendLine($"能量值:{EP:0.##} / {GameplayEquilibriumConstant.MaxEP:0.##}"); @@ -1689,7 +1717,10 @@ namespace Milimoe.FunGame.Core.Entity builder.AppendLine($"经验值:{EXP}{(Level != GameplayEquilibriumConstant.MaxLevel && GameplayEquilibriumConstant.EXPUpperLimit.TryGetValue(Level, out double need) ? " / " + need : "")}"); } double exHP = ExHP + ExHP2 + ExHP3; - builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : "")); + List shield = []; + if (Shield.TotalPhysical > 0) shield.Add($"物理:{Shield.TotalPhysical:0.##}"); + if (Shield.TotalMagicial > 0) shield.Add($"魔法:{Shield.TotalMagicial:0.##}"); + builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : "") + (shield.Count > 0 ? $"({string.Join(",", shield)})" : "")); double exMP = ExMP + ExMP2 + ExMP3; builder.AppendLine($"魔法值:{MP:0.##} / {MaxMP:0.##}" + (exMP != 0 ? $" [{BaseMP:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exMP):0.##}]" : "")); builder.AppendLine($"能量值:{EP:0.##} / {GameplayEquilibriumConstant.MaxEP:0.##}"); @@ -1697,10 +1728,7 @@ namespace Milimoe.FunGame.Core.Entity builder.AppendLine($"攻击力:{ATK:0.##}" + (exATK != 0 ? $" [{BaseATK:0.##} {(exATK >= 0 ? "+" : "-")} {Math.Abs(exATK):0.##}]" : "")); double exDEF = ExDEF + ExDEF2 + ExDEF3; builder.AppendLine($"物理护甲:{DEF:0.##}" + (exDEF != 0 ? $" [{BaseDEF:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exDEF):0.##}]" : "") + $" ({PDR * 100:0.##}%)"); - double mdf = Calculation.Round4Digits((MDF.None + MDF.Starmark + MDF.PurityNatural + MDF.PurityContemporary + - MDF.Bright + MDF.Shadow + MDF.Element + MDF.Fleabane + MDF.Particle) / 9) * 100; - if (Calculation.IsApproximatelyZero(mdf)) mdf = 0; - builder.AppendLine($"魔法抗性:{mdf:0.##}%(平均)"); + builder.AppendLine($"魔法抗性:{MDF.Avg:0.##}%(平均)"); double exSPD = AGI * GameplayEquilibriumConstant.AGItoSPDMultiplier + ExSPD; builder.AppendLine($"行动速度:{SPD:0.##}" + (exSPD != 0 ? $" [{InitialSPD:0.##} {(exSPD >= 0 ? "+" : "-")} {Math.Abs(exSPD):0.##}]" : "") + $" ({ActionCoefficient * 100:0.##}%)"); builder.AppendLine($"核心属性:{CharacterSet.GetPrimaryAttributeName(PrimaryAttribute)}"); @@ -1715,6 +1743,7 @@ namespace Milimoe.FunGame.Core.Entity builder.AppendLine($"暴击率:{CritRate * 100:0.##}%"); builder.AppendLine($"暴击伤害:{CritDMG * 100:0.##}%"); builder.AppendLine($"闪避率:{EvadeRate * 100:0.##}%"); + builder.AppendLine($"生命偷取:{Lifesteal * 100:0.##}%"); builder.AppendLine($"冷却缩减:{CDR * 100:0.##}%"); builder.AppendLine($"加速系数:{AccelerationCoefficient * 100:0.##}%"); builder.AppendLine($"物理穿透:{PhysicalPenetration * 100:0.##}%"); @@ -1775,52 +1804,77 @@ namespace Milimoe.FunGame.Core.Entity /// public CharacterState UpdateCharacterState() { - bool isNotActionable = false; - bool isActionRestricted = false; - bool isBattleRestricted = false; - bool isSkillRestricted = false; - bool isAttackRestricted = false; - IEnumerable states = CharacterEffectStates.Values.SelectMany(list => list); // 根据持有的特效判断角色所处的状态 - isNotActionable = states.Any(state => state == CharacterState.NotActionable); - isActionRestricted = states.Any(state => state == CharacterState.ActionRestricted); - isBattleRestricted = states.Any(state => state == CharacterState.BattleRestricted); - isSkillRestricted = states.Any(state => state == CharacterState.SkillRestricted); - isAttackRestricted = states.Any(state => state == CharacterState.AttackRestricted); + bool isNotActionable = states.Any(state => state == CharacterState.NotActionable); + bool isActionRestricted = states.Any(state => state == CharacterState.ActionRestricted); + bool isBattleRestricted = states.Any(state => state == CharacterState.BattleRestricted); + bool isSkillRestricted = states.Any(state => state == CharacterState.SkillRestricted); + bool isAttackRestricted = states.Any(state => state == CharacterState.AttackRestricted); IEnumerable types = CharacterEffectTypes.Values.SelectMany(list => list); // 判断角色的控制效果 IsUnselectable = types.Any(type => type == EffectType.Unselectable); + IEnumerable immunes = CharacterImmuneTypes.Values.SelectMany(list => list); + // 判断角色的免疫状态 + bool isAllImmune = immunes.Any(type => type == ImmuneType.All); + bool isPhysicalImmune = immunes.Any(type => type == ImmuneType.Physical); + bool isMagicalImmune = immunes.Any(type => type == ImmuneType.Magical); + bool isSkilledImmune = immunes.Any(type => type == ImmuneType.Skilled); + if (isAllImmune) + { + ImmuneType = ImmuneType.All; + } + else if (isPhysicalImmune) + { + ImmuneType = ImmuneType.Physical; + } + else if (isMagicalImmune) + { + ImmuneType = ImmuneType.Magical; + } + else if (isSkilledImmune) + { + ImmuneType = ImmuneType.Skilled; + } + else + { + ImmuneType = ImmuneType.None; + } + bool isControl = isNotActionable || isActionRestricted || isBattleRestricted || isSkillRestricted || isAttackRestricted; bool isCasting = CharacterState == CharacterState.Casting; bool isPreCastSuperSkill = CharacterState == CharacterState.PreCastSuperSkill; - if (isNotActionable) + // 预释放爆发技不可驱散,保持原状态 + if (!isPreCastSuperSkill) { - CharacterState = CharacterState.NotActionable; - } - else if (isActionRestricted) - { - CharacterState = CharacterState.ActionRestricted; - } - else if (isBattleRestricted || (isSkillRestricted && isAttackRestricted)) - { - CharacterState = CharacterState.BattleRestricted; - } - else if (isSkillRestricted) - { - CharacterState = CharacterState.SkillRestricted; - } - else if (isAttackRestricted) - { - CharacterState = CharacterState.AttackRestricted; - } + if (isNotActionable) + { + CharacterState = CharacterState.NotActionable; + } + else if (isActionRestricted) + { + CharacterState = CharacterState.ActionRestricted; + } + else if (isBattleRestricted || (isSkillRestricted && isAttackRestricted)) + { + CharacterState = CharacterState.BattleRestricted; + } + else if (isSkillRestricted) + { + CharacterState = CharacterState.SkillRestricted; + } + else if (isAttackRestricted) + { + CharacterState = CharacterState.AttackRestricted; + } - if (!isControl && !isCasting && !isPreCastSuperSkill) - { - CharacterState = CharacterState.Actionable; + if (!isControl && !isCasting) + { + CharacterState = CharacterState.Actionable; + } } return CharacterState; @@ -1847,6 +1901,7 @@ namespace Milimoe.FunGame.Core.Entity ThirdRoleType = ThirdRoleType, Promotion = Promotion, PrimaryAttribute = PrimaryAttribute, + ImmuneType = ImmuneType, Level = Level, LevelBreak = LevelBreak, EXP = EXP, @@ -1869,6 +1924,8 @@ namespace Milimoe.FunGame.Core.Entity INTGrowth = INTGrowth, InitialSPD = InitialSPD, ATR = ATR, + Lifesteal = Lifesteal, + Shield = Shield.Copy() }; if (copyEx) { diff --git a/Entity/Character/MagicResistance.cs b/Entity/Character/MagicResistance.cs index 4c10770..ff23bbe 100644 --- a/Entity/Character/MagicResistance.cs +++ b/Entity/Character/MagicResistance.cs @@ -1,20 +1,128 @@ -namespace Milimoe.FunGame.Core.Entity +using Milimoe.FunGame.Core.Api.Utility; +using Milimoe.FunGame.Core.Library.Constant; + +namespace Milimoe.FunGame.Core.Entity { /// /// 角色的魔法抗性,对不同的魔法类型有不同抗性 /// - public class MagicResistance() + public class MagicResistance { + /// + /// 无属性魔法抗性 + /// public double None { get; set; } = 0; + + /// + /// 星痕魔法抗性 + /// public double Starmark { get; set; } = 0; + + /// + /// 纯粹结晶魔法抗性 + /// public double PurityNatural { get; set; } = 0; + + /// + /// 纯现代结晶魔法抗性 + /// public double PurityContemporary { get; set; } = 0; + + /// + /// 光魔法抗性 + /// public double Bright { get; set; } = 0; + + /// + /// 影魔法抗性 + /// public double Shadow { get; set; } = 0; + + /// + /// 元素魔法抗性 + /// public double Element { get; set; } = 0; + + /// + /// 紫宛魔法抗性 + /// public double Fleabane { get; set; } = 0; + + /// + /// 时空魔法抗性 + /// public double Particle { get; set; } = 0; + /// + /// 平均魔法抗性 + /// + public double Avg + { + get + { + double mdf = Calculation.Round4Digits((None + Starmark + PurityNatural + PurityContemporary + Bright + Shadow + Element + Fleabane + Particle) / 9) * 100; + if (Calculation.IsApproximatelyZero(mdf)) mdf = 0; + return mdf; + } + } + + /// + /// 获取或设置抗性值 + /// + /// + /// + public double this[MagicType type] + { + get + { + return type switch + { + MagicType.Starmark => Starmark, + MagicType.PurityNatural => PurityNatural, + MagicType.PurityContemporary => PurityContemporary, + MagicType.Bright => Bright, + MagicType.Shadow => Shadow, + MagicType.Element => Element, + MagicType.Fleabane => Fleabane, + MagicType.Particle => Particle, + _ => None + }; + } + set + { + switch (type) + { + case MagicType.Starmark: + Starmark = value; + break; + case MagicType.PurityNatural: + PurityNatural = value; + break; + case MagicType.PurityContemporary: + PurityContemporary = value; + break; + case MagicType.Bright: + Bright = value; + break; + case MagicType.Shadow: + Shadow = value; + break; + case MagicType.Element: + Element = value; + break; + case MagicType.Fleabane: + Fleabane = value; + break; + case MagicType.Particle: + Particle = value; + break; + default: + None = value; + break; + } + } + } + /// /// 对所有抗性赋值 /// diff --git a/Entity/Character/Shield.cs b/Entity/Character/Shield.cs new file mode 100644 index 0000000..5fcfe44 --- /dev/null +++ b/Entity/Character/Shield.cs @@ -0,0 +1,160 @@ +using Milimoe.FunGame.Core.Library.Constant; + +namespace Milimoe.FunGame.Core.Entity +{ + /// + /// 角色的护盾,对不同的魔法类型有不同值 + /// + public class Shield + { + /// + /// 物理护盾 + /// + public double Physical { get; set; } = 0; + + /// + /// 无属性魔法护盾 + /// + public double None { get; set; } = 0; + + /// + /// 星痕魔法护盾 + /// + public double Starmark { get; set; } = 0; + + /// + /// 纯粹结晶护盾 + /// + public double PurityNatural { get; set; } = 0; + + /// + /// 纯现代结晶护盾 + /// + public double PurityContemporary { get; set; } = 0; + + /// + /// 光护盾 + /// + public double Bright { get; set; } = 0; + + /// + /// 影护盾 + /// + public double Shadow { get; set; } = 0; + + /// + /// 元素护盾 + /// + public double Element { get; set; } = 0; + + /// + /// 紫宛护盾 + /// + public double Fleabane { get; set; } = 0; + + /// + /// 时空护盾 + /// + public double Particle { get; set; } = 0; + + /// + /// 总计物理护盾 + /// + public double TotalPhysical => Physical; + + /// + /// 总计魔法护盾 + /// + public double TotalMagicial => None + Starmark + PurityNatural + PurityContemporary + Bright + Shadow + Element + Fleabane + Particle; + + /// + /// 获取或设置护盾值 + /// + /// + /// + /// + public double this[bool isMagic = false, MagicType type = MagicType.None] + { + get + { + if (isMagic) + { + return type switch + { + MagicType.Starmark => Starmark, + MagicType.PurityNatural => PurityNatural, + MagicType.PurityContemporary => PurityContemporary, + MagicType.Bright => Bright, + MagicType.Shadow => Shadow, + MagicType.Element => Element, + MagicType.Fleabane => Fleabane, + MagicType.Particle => Particle, + _ => None + }; + } + return Physical; + } + set + { + if (isMagic) + { + switch (type) + { + case MagicType.Starmark: + Starmark = value; + break; + case MagicType.PurityNatural: + PurityNatural = value; + break; + case MagicType.PurityContemporary: + PurityContemporary = value; + break; + case MagicType.Bright: + Bright = value; + break; + case MagicType.Shadow: + Shadow = value; + break; + case MagicType.Element: + Element = value; + break; + case MagicType.Fleabane: + Fleabane = value; + break; + case MagicType.Particle: + Particle = value; + break; + default: + None = value; + break; + } + } + else + { + Physical = value; + } + } + } + + /// + /// 复制一个护盾对象 + /// + /// + public Shield Copy() + { + return new() + { + Physical = Physical, + None = None, + Starmark = Starmark, + PurityNatural = PurityNatural, + PurityContemporary = PurityContemporary, + Bright = Bright, + Shadow = Shadow, + Element = Element, + Fleabane = Fleabane, + Particle = Particle + }; + } + } +} diff --git a/Entity/Item/Item.cs b/Entity/Item/Item.cs index 20d1e47..d63cdab 100644 --- a/Entity/Item/Item.cs +++ b/Entity/Item/Item.cs @@ -248,7 +248,7 @@ namespace Milimoe.FunGame.Core.Entity { foreach (Skill skill in Skills.Passives) { - List effects = [.. Character.Effects.Where(e => e.Skill == skill && e.Level > 0)]; + List effects = [.. Character.Effects.Where(e => e.Skill == skill && e.Level > 0 && !e.IsBeingTemporaryDispelled)]; foreach (Effect e in effects) { Character.Effects.Remove(e); diff --git a/Entity/Skill/Effect.cs b/Entity/Skill/Effect.cs index ca2b770..d79d2de 100644 --- a/Entity/Skill/Effect.cs +++ b/Entity/Skill/Effect.cs @@ -50,11 +50,66 @@ namespace Milimoe.FunGame.Core.Entity /// public int RemainDurationTurn { get; set; } = 0; + /// + /// 是否是没有具体持续时间的持续性特效 + /// + public virtual bool DurativeWithoutDuration { get; set; } = false; + /// /// 魔法类型 /// public virtual MagicType MagicType { get; set; } = MagicType.None; + /// + /// 驱散性 [ 能驱散什么特效,默认无驱散 ] + /// + public virtual DispelType DispelType { get; set; } = DispelType.None; + + /// + /// 被驱散性 [ 能被什么驱散类型驱散,默认弱驱散 ] + /// + public virtual DispelledType DispelledType { get; set; } = DispelledType.Weak; + + /// + /// 是否是负面效果 + /// + public virtual bool IsDebuff { get; set; } = false; + + /// + /// 驱散性和被驱散性的具体说明 + /// + public virtual string DispelDescription + { + get => GetDispelDescription("\r\n"); + set => _dispelDescription = value; + } + + /// + /// 是否具备弱驱散功能(强驱散包含在内) + /// + public bool CanWeakDispel => DispelType == DispelType.Weak || DispelType == DispelType.DurativeWeak || DispelType == DispelType.TemporaryWeak || CanStrongDispel; + + /// + /// 是否具备强驱散功能 + /// + public bool CanStrongDispel => DispelType == DispelType.Strong || DispelType == DispelType.DurativeStrong || DispelType == DispelType.TemporaryStrong; + + /// + /// 是否是临时驱散 [ 需注意持续性驱散是在持续时间内将特效无效化而不是移除,适用临时驱散机制 ] + /// + public bool IsTemporaryDispel => DispelType == DispelType.DurativeWeak || DispelType == DispelType.TemporaryWeak || DispelType == DispelType.DurativeStrong || DispelType == DispelType.TemporaryStrong; + + /// + /// 是否处于临时被驱散状态 [ 如果使用后不手动恢复为 false,那么行动顺序表会在时间流逝时恢复它 ] + /// 注意看标准实现,需要配合 使用 + /// + public bool IsBeingTemporaryDispelled { get; set; } = false; + + /// + /// 无视免疫类型 + /// + public virtual ImmuneType IgnoreImmune { get; set; } = ImmuneType.None; + /// /// 效果描述 /// @@ -92,6 +147,11 @@ namespace Milimoe.FunGame.Core.Entity } } + /// + /// Values 构造动态特效参考这个构造函数 + /// + /// + /// protected Effect(Skill skill, Dictionary? args = null) { Skill = skill; @@ -284,6 +344,20 @@ namespace Milimoe.FunGame.Core.Entity } + /// + /// 在治疗结算前修改治疗值 + /// + /// + /// + /// + /// + /// + /// 返回治疗增减值 + public virtual double AlterHealValueBeforeHealToTarget(Character actor, Character target, double heal, ref bool canRespawn, Dictionary totalHealBonus) + { + return 0; + } + /// /// 在特效持有者的回合开始前 /// @@ -438,6 +512,178 @@ namespace Milimoe.FunGame.Core.Entity return CharacterActionType.None; } + /// + /// 可重写对某个特效的驱散实现,适用于特殊驱散类型 + /// + /// + /// + /// + /// + public virtual void OnDispellingEffect(Character dispeller, Character target, Effect effect, bool isEnemy) + { + bool isDispel = false; + // 先看特效整体是不是能被驱散的 + switch (effect.DispelledType) + { + case DispelledType.Weak: + if (CanWeakDispel) + { + isDispel = true; + } + break; + case DispelledType.Strong: + if (CanStrongDispel) + { + isDispel = true; + } + break; + default: + break; + } + if (isDispel) + { + bool removeEffectTypes = false; + bool removeEffectStates = false; + // 接下来再看看特效给角色施加的特效类型和改变状态是不是能被驱散的 + // 检查特效持续性 + if (effect.DurativeWithoutDuration || (effect.Durative && effect.Duration > 0) || effect.DurationTurn > 0) + { + // 先从角色身上移除特效类型 + if (target.CharacterEffectTypes.TryGetValue(effect, out List? types) && types != null) + { + RemoveEffectTypesByDispel(types, isEnemy); + if (types.Count == 0) + { + target.CharacterEffectTypes.Remove(effect); + removeEffectTypes = true; + } + } + else + { + removeEffectTypes = true; + } + // 友方移除控制状态 + if (!isEnemy && effect.IsDebuff) + { + if (target.CharacterEffectStates.TryGetValue(effect, out List? states) && states != null) + { + RemoveEffectStatesByDispel(states); + if (states.Count == 0) + { + target.CharacterEffectStates.Remove(effect); + removeEffectStates = true; + } + } + else + { + removeEffectStates = true; + } + } + target.UpdateCharacterState(); + } + // 移除整个特效 + if (removeEffectTypes && removeEffectStates) + { + if (IsTemporaryDispel) + { + effect.IsBeingTemporaryDispelled = true; + } + else + { + effect.RemainDuration = 0; + effect.RemainDurationTurn = 0; + target.Effects.Remove(effect); + } + effect.OnEffectLost(target); + } + } + } + + /// + /// 当特效被驱散时的 + /// + /// + /// + /// + /// + /// 返回 false 可以阻止驱散 + public virtual bool OnEffectIsBeingDispelled(Character dispeller, Character target, Effect dispellerEffect, bool isEnemy) + { + return true; + } + + /// + /// 当角色触发生命偷取后 + /// + /// + /// + /// + /// + public virtual void AfterLifesteal(Character character, Character enemy, double damage, double steal) + { + + } + + /// + /// 在角色护盾结算前触发 + /// + /// + /// + /// + /// + /// + /// + /// + /// 返回 false 可以阻止后续扣除角色护盾 + public virtual bool BeforeShieldCalculation(Character character, Character attacker, bool isMagic, MagicType magicType, double damage, double shield, ref string message) + { + return true; + } + + /// + /// 当角色护盾破碎时 + /// + /// + /// + /// + /// + /// + /// + /// + /// 返回 false 可以阻止后续扣除角色生命值 + public virtual bool OnShieldBroken(Character character, Character attacker, bool isMagic, MagicType magicType, double damage, double shield, double overFlowing) + { + return true; + } + + /// + /// 在免疫检定时 + /// + /// + /// + /// + /// + /// false:免疫检定不通过 + public virtual bool OnImmuneCheck(Character actor, Character enemy, ISkill skill, Item? item = null) + { + return true; + } + + /// + /// 在伤害免疫检定时 + /// + /// + /// + /// + /// + /// + /// + /// false:免疫检定不通过 + public virtual bool OnDamageImmuneCheck(Character actor, Character enemy, bool isNormalAttack, bool isMagic, MagicType magicType, double damage) + { + return true; + } + /// /// 对敌人造成技能伤害 [ 强烈建议使用此方法造成伤害而不是自行调用 ] /// @@ -450,7 +696,8 @@ namespace Milimoe.FunGame.Core.Entity public DamageResult DamageToEnemy(Character actor, Character enemy, bool isMagic, MagicType magicType, double expectedDamage) { if (GamingQueue is null) return DamageResult.Evaded; - DamageResult result = !isMagic ? GamingQueue.CalculatePhysicalDamage(actor, enemy, false, expectedDamage, out double damage) : GamingQueue.CalculateMagicalDamage(actor, enemy, false, MagicType, expectedDamage, out damage); + int changeCount = 0; + DamageResult result = !isMagic ? GamingQueue.CalculatePhysicalDamage(actor, enemy, false, expectedDamage, out double damage, ref changeCount) : GamingQueue.CalculateMagicalDamage(actor, enemy, false, MagicType, expectedDamage, out damage, ref changeCount); GamingQueue.DamageToEnemyAsync(actor, enemy, damage, false, isMagic, magicType, result); return result; } @@ -493,10 +740,53 @@ namespace Milimoe.FunGame.Core.Entity /// public void AddEffectStatesToCharacter(Character character, List states) { - character.CharacterEffectStates.Add(this, states); + if (character.CharacterEffectStates.TryGetValue(this, out List? value) && value != null) + { + states.AddRange(value); + } + states = [.. states.Distinct()]; + character.CharacterEffectStates[this] = states; character.UpdateCharacterState(); } - + + /// + /// 将特效控制效果设置到角色上 [ 尽可能的调用此方法而不是自己实现 ] + /// 施加 EffectType 的同时也会施加 CharacterState,参见 + /// + /// + /// + public void AddEffectTypeToCharacter(Character character, List types) + { + if (character.CharacterEffectTypes.TryGetValue(this, out List? value) && value != null) + { + types.AddRange(value); + } + types = [.. types.Distinct()]; + character.CharacterEffectTypes[this] = types; + List states = []; + foreach (EffectType type in types) + { + states.Add(SkillSet.GetCharacterStateByEffectType(type)); + } + AddEffectStatesToCharacter(character, states); + } + + /// + /// 将免疫状态设置到角色上 [ 尽可能的调用此方法而不是自己实现 ] + /// + /// + /// + public void AddImmuneTypesToCharacter(Character character, List types) + { + if (character.CharacterImmuneTypes.TryGetValue(this, out List? value) && value != null) + { + types.AddRange(value); + } + types = [.. types.Distinct()]; + character.CharacterImmuneTypes[this] = types; + character.UpdateCharacterState(); + } + /// /// 将特效状态从角色身上移除 [ 尽可能的调用此方法而不是自己实现 ] /// @@ -506,17 +796,6 @@ namespace Milimoe.FunGame.Core.Entity character.CharacterEffectStates.Remove(this); character.UpdateCharacterState(); } - - /// - /// 将特效控制效果设置到角色上 [ 尽可能的调用此方法而不是自己实现 ] - /// - /// - /// - public void AddEffectTypeToCharacter(Character character, List types) - { - character.CharacterEffectTypes.Add(this, types); - character.UpdateCharacterState(); - } /// /// 将特效控制效果从角色身上移除 [ 尽可能的调用此方法而不是自己实现 ] @@ -527,6 +806,128 @@ namespace Milimoe.FunGame.Core.Entity character.CharacterEffectTypes.Remove(this); character.UpdateCharacterState(); } + + /// + /// 将免疫状态从角色身上移除 [ 尽可能的调用此方法而不是自己实现 ] + /// + /// + public void RemoveImmuneTypesFromCharacter(Character character) + { + character.CharacterImmuneTypes.Remove(this); + character.UpdateCharacterState(); + } + + /// + /// 从角色身上消除特效类型 [ 如果重写了 ,则尽可能的调用此方法而不是自己实现 ] + /// + /// + /// + public void RemoveEffectTypesByDispel(List types, bool isEnemy) + { + EffectType[] loop = [.. types]; + foreach (EffectType type in loop) + { + bool isDebuff = SkillSet.GetIsDebuffByEffectType(type); + if (isEnemy == isDebuff) + { + // 简单判断,敌方不考虑 debuff,友方只考虑 debuff + continue; + } + DispelledType dispelledType = SkillSet.GetDispelledTypeByEffectType(type); + bool canDispel = false; + switch (dispelledType) + { + case DispelledType.Weak: + if (CanWeakDispel) canDispel = true; + break; + case DispelledType.Strong: + if (CanStrongDispel) canDispel = true; + break; + default: + break; + } + if (canDispel) + { + types.Remove(type); + } + } + } + + /// + /// 从角色身上消除状态类型 [ 如果重写了 ,则尽可能的调用此方法而不是自己实现 ] + /// + /// + public void RemoveEffectStatesByDispel(List states) + { + CharacterState[] loop = [.. states]; + foreach (CharacterState state in loop) + { + DispelledType dispelledType = DispelledType.Weak; + switch (state) + { + case CharacterState.NotActionable: + case CharacterState.ActionRestricted: + case CharacterState.BattleRestricted: + dispelledType = DispelledType.Strong; + break; + case CharacterState.SkillRestricted: + case CharacterState.AttackRestricted: + break; + default: + break; + } + bool canDispel = false; + switch (dispelledType) + { + case DispelledType.Weak: + if (CanWeakDispel) canDispel = true; + break; + case DispelledType.Strong: + if (CanStrongDispel) canDispel = true; + break; + default: + break; + } + if (canDispel) + { + states.Remove(state); + } + } + } + + /// + /// 驱散目标 [ 尽可能的调用此方法而不是自己实现 ] + /// 此方法会触发 + /// + /// + /// + /// + public void Dispel(Character dispeller, Character target, bool isEnemy) + { + if (DispelType == DispelType.None) + { + return; + } + Effect[] effects = [.. target.Effects.Where(e => e.Level > 0 && EffectType != EffectType.Item && !e.IsBeingTemporaryDispelled)]; + foreach (Effect effect in effects) + { + if (effect.OnEffectIsBeingDispelled(dispeller, target, this, isEnemy)) + { + OnDispellingEffect(dispeller, target, effect, isEnemy); + } + } + } + + /// + /// 修改角色的硬直时间 [ 尽可能的调用此方法而不是自己实现 ] + /// + /// 角色 + /// 加值 + /// 是否使用插队保护机制 + public void ChangeCharacterHardnessTime(Character character, double addValue, bool isCheckProtected) + { + GamingQueue?.ChangeCharacterHardnessTime(character, addValue, isCheckProtected); + } /// /// 检查角色是否在 AI 控制状态 @@ -549,13 +950,20 @@ namespace Milimoe.FunGame.Core.Entity string isDurative = ""; if (Durative) { - isDurative = $"(剩余:{RemainDuration:0.##} 时间)"; + isDurative = $"(剩余:{RemainDuration:0.##} {GameplayEquilibriumConstant.InGameTime})"; } else if (DurationTurn > 0) { - isDurative = "(剩余:" + RemainDurationTurn + " 回合)"; + isDurative = $"(剩余:{RemainDurationTurn} 回合)"; + } + + builder.Append($"【{Name} - 等级 {Level}】{Description}{isDurative}"); + + string dispels = GetDispelDescription(","); + if (dispels != "") + { + builder.Append($"({dispels})"); } - builder.AppendLine("【" + Name + " - 等级 " + Level + "】" + Description + isDurative); return builder.ToString(); } @@ -575,7 +983,13 @@ namespace Milimoe.FunGame.Core.Entity copy.Id = Id; copy.Name = Name; copy.Description = Description; + copy.DispelDescription = DispelDescription; copy.EffectType = EffectType; + copy.DispelType = DispelType; + copy.DispelledType = DispelledType; + copy.IsDebuff = IsDebuff; + copy.IgnoreImmune = IgnoreImmune; + copy.DurativeWithoutDuration = DurativeWithoutDuration; copy.Durative = Durative; copy.Duration = Duration; copy.DurationTurn = DurationTurn; @@ -593,5 +1007,37 @@ namespace Milimoe.FunGame.Core.Entity { return other is Effect c && c.Id + "." + Name == Id + "." + Name; } + + /// + /// 获取驱散描述 + /// + /// + /// + private string GetDispelDescription(string separator) + { + if (_dispelDescription.Trim() != "") + { + return _dispelDescription; + } + else if (DispelType != DispelType.None || DispelledType != DispelledType.Weak) + { + List dispels = []; + if (DispelType != DispelType.None) + { + dispels.Add($"驱散性:{SkillSet.GetDispelType(DispelType)}"); + } + if (DispelledType != DispelledType.Weak) + { + dispels.Add($"被驱散性:{SkillSet.GetDispelledType(DispelledType)}"); + } + return string.Join(separator, dispels); + } + return ""; + } + + /// + /// 驱散描述 + /// + private string _dispelDescription = ""; } } diff --git a/Entity/Skill/NormalAttack.cs b/Entity/Skill/NormalAttack.cs index 275a1d7..db5c023 100644 --- a/Entity/Skill/NormalAttack.cs +++ b/Entity/Skill/NormalAttack.cs @@ -1,4 +1,5 @@ using System.Text; +using Milimoe.FunGame.Core.Api.Utility; using Milimoe.FunGame.Core.Interface.Base; using Milimoe.FunGame.Core.Interface.Entity; using Milimoe.FunGame.Core.Library.Constant; @@ -52,11 +53,21 @@ namespace Milimoe.FunGame.Core.Entity /// public MagicType MagicType => _MagicType; + /// + /// 无视免疫类型 + /// + public ImmuneType IgnoreImmune { get; set; } = ImmuneType.None; + /// /// 硬直时间 /// public double HardnessTime { get; set; } = 10; + /// + /// 实际硬直时间 + /// + public double RealHardnessTime => Math.Max(0, HardnessTime * (1 - Calculation.PercentageCheck(Character?.ActionCoefficient ?? 0))); + /// /// 可选取自身 /// @@ -124,7 +135,8 @@ namespace Milimoe.FunGame.Core.Entity { queue.WriteLine("[ " + Character + $" ] 对 [ {enemy} ] 发起了普通攻击!"); double expected = Damage; - DamageResult result = IsMagic ? queue.CalculateMagicalDamage(attacker, enemy, true, MagicType, expected, out double damage) : queue.CalculatePhysicalDamage(attacker, enemy, true, expected, out damage); + int changeCount = 0; + DamageResult result = IsMagic ? queue.CalculateMagicalDamage(attacker, enemy, true, MagicType, expected, out double damage, ref changeCount) : queue.CalculatePhysicalDamage(attacker, enemy, true, expected, out damage, ref changeCount); queue.DamageToEnemyAsync(attacker, enemy, damage, true, IsMagic, MagicType, result); } } @@ -141,22 +153,38 @@ namespace Milimoe.FunGame.Core.Entity _MagicType = magicType; } + /// + /// 比较两个普攻对象 + /// + /// + /// public override bool Equals(IBaseEntity? other) { return other is NormalAttack c && c.Name == Name; } - public override string ToString() + /// + /// 输出信息 + /// + /// + /// + public string GetInfo(bool showOriginal = false) { StringBuilder builder = new(); - builder.AppendLine(Name + " - 等级 " + Level); - builder.AppendLine("描述:" + Description); - builder.AppendLine("硬直时间:" + HardnessTime); + builder.AppendLine($"{Name} - 等级 {Level}"); + builder.AppendLine($"描述:{Description}"); + builder.AppendLine($"硬直时间:{RealHardnessTime:0.##}{(showOriginal && RealHardnessTime != HardnessTime ? $"(原始值:{HardnessTime})" : "")}"); return builder.ToString(); } + /// + /// 输出信息 + /// + /// + public override string ToString() => GetInfo(true); + /// /// 等级 /// diff --git a/Entity/Skill/OpenSkill.cs b/Entity/Skill/OpenSkill.cs index 22ae70b..1273413 100644 --- a/Entity/Skill/OpenSkill.cs +++ b/Entity/Skill/OpenSkill.cs @@ -26,12 +26,6 @@ namespace Milimoe.FunGame.Core.Entity SkillType = SkillType.Item; } break; - case "debuff": - if (bool.TryParse(args[str].ToString(), out bool isDebuff) && isDebuff) - { - IsDebuff = isDebuff; - } - break; case "self": if (bool.TryParse(args[str].ToString(), out bool self)) { diff --git a/Entity/Skill/Skill.cs b/Entity/Skill/Skill.cs index bcdb8e3..1803abc 100644 --- a/Entity/Skill/Skill.cs +++ b/Entity/Skill/Skill.cs @@ -32,6 +32,11 @@ namespace Milimoe.FunGame.Core.Entity /// public virtual string GeneralDescription { get; set; } = ""; + /// + /// 驱散性和被驱散性的描述 + /// + public virtual string DispelDescription { get; set; } = ""; + /// /// 释放技能时的口号 /// @@ -88,11 +93,6 @@ namespace Milimoe.FunGame.Core.Entity [InitRequired] public bool IsMagic => SkillType == SkillType.Magic; - /// - /// 是否属于 Debuff - /// - public bool IsDebuff { get; set; } = false; - /// /// 可选取自身 /// @@ -134,17 +134,17 @@ namespace Milimoe.FunGame.Core.Entity [InitOptional] public virtual double MPCost { get; set; } = 0; - /// - /// 实际吟唱时间 [ 魔法 ] - /// - public double RealCastTime => Math.Max(0, CastTime * (1 - Calculation.PercentageCheck(Character?.AccelerationCoefficient ?? 0))); - /// /// 吟唱时间 [ 魔法 ] /// [InitOptional] public virtual double CastTime { get; set; } = 0; + /// + /// 实际吟唱时间 [ 魔法 ] + /// + public double RealCastTime => Math.Max(0, CastTime * (1 - Calculation.PercentageCheck(Character?.AccelerationCoefficient ?? 0))); + /// /// 实际能量消耗 [ 战技 ] /// @@ -198,6 +198,11 @@ namespace Milimoe.FunGame.Core.Entity [InitRequired] public virtual double HardnessTime { get; set; } = 0; + /// + /// 实际硬直时间 + /// + public double RealHardnessTime => Math.Max(0, HardnessTime * (1 - Calculation.PercentageCheck(Character?.ActionCoefficient ?? 0))); + /// /// 效果列表 /// @@ -420,8 +425,9 @@ namespace Milimoe.FunGame.Core.Entity /// /// 返回技能的详细说明 /// + /// /// - public override string ToString() + public string GetInfo(bool showOriginal = false) { StringBuilder builder = new(); @@ -441,45 +447,55 @@ namespace Milimoe.FunGame.Core.Entity { builder.AppendLine("效果结束前不可用"); } + if (DispelDescription != "") + { + builder.AppendLine($"{DispelDescription}"); + } if (IsActive && (Item?.IsInGameItem ?? true)) { if (SkillType == SkillType.Item) { if (RealMPCost > 0) { - builder.AppendLine($"魔法消耗:{RealMPCost:0.##}"); + builder.AppendLine($"魔法消耗:{RealMPCost:0.##}{(showOriginal && RealMPCost != MPCost ? $"(原始值:{MPCost})" : "")}"); } if (RealEPCost > 0) { - builder.AppendLine($"能量消耗:{RealEPCost:0.##}"); + builder.AppendLine($"能量消耗:{RealEPCost:0.##}{(showOriginal && RealEPCost != EPCost ? $"(原始值:{EPCost})" : "")}"); } } else { if (IsSuperSkill) { - builder.AppendLine($"能量消耗:{RealEPCost:0.##}"); + builder.AppendLine($"能量消耗:{RealEPCost:0.##}{(showOriginal && RealEPCost != EPCost ? $"(原始值:{EPCost})" : "")}"); } else { if (IsMagic) { - builder.AppendLine($"魔法消耗:{RealMPCost:0.##}"); - builder.AppendLine($"吟唱时间:{RealCastTime:0.##}"); + builder.AppendLine($"魔法消耗:{RealMPCost:0.##}{(showOriginal && RealMPCost != MPCost ? $"(原始值:{MPCost})" : "")}"); + builder.AppendLine($"吟唱时间:{RealCastTime:0.##}{(showOriginal && RealCastTime != CastTime ? $"(原始值:{CastTime})" : "")}"); } else { - builder.AppendLine($"能量消耗:{RealEPCost:0.##}"); + builder.AppendLine($"能量消耗:{RealEPCost:0.##}{(showOriginal && RealEPCost != EPCost ? $"(原始值:{EPCost})" : "")}"); } } } - builder.AppendLine($"冷却时间:{RealCD:0.##}"); - builder.AppendLine($"硬直时间:{HardnessTime:0.##}"); + builder.AppendLine($"冷却时间:{RealCD:0.##}{(showOriginal && RealCD != CD ? $"(原始值:{CD})" : "")}"); + builder.AppendLine($"硬直时间:{RealHardnessTime:0.##}{(showOriginal && RealHardnessTime != HardnessTime ? $"(原始值:{HardnessTime})" : "")}"); } return builder.ToString(); } + /// + /// 返回技能的详细说明 + /// + /// + public override string ToString() => GetInfo(true); + /// /// 判断两个技能是否相同 检查Id.Name /// @@ -516,6 +532,7 @@ namespace Milimoe.FunGame.Core.Entity skill.Name = skillDefined.Name; skill.Description = skillDefined.Description; skill.GeneralDescription = skillDefined.GeneralDescription; + skill.DispelDescription = skillDefined.DispelDescription; skill.SkillType = skillDefined.SkillType; skill.MPCost = skillDefined.MPCost; skill.CastTime = skillDefined.CastTime; diff --git a/Entity/Statistics/CharacterStatistics.cs b/Entity/Statistics/CharacterStatistics.cs index 52b3628..d13f023 100644 --- a/Entity/Statistics/CharacterStatistics.cs +++ b/Entity/Statistics/CharacterStatistics.cs @@ -18,12 +18,16 @@ public double AvgTakenPhysicalDamage { get; set; } = 0; public double AvgTakenMagicDamage { get; set; } = 0; public double AvgTakenRealDamage { get; set; } = 0; + public double TotalHeal { get; set; } = 0; + public double AvgHeal { get; set; } = 0; public int LiveRound { get; set; } = 0; public int AvgLiveRound { get; set; } = 0; public int ActionTurn { get; set; } = 0; public int AvgActionTurn { get; set; } = 0; public double LiveTime { get; set; } = 0; public double AvgLiveTime { get; set; } = 0; + public double ControlTime { get; set; } = 0; + public double AvgControlTime { get; set; } = 0; public double DamagePerRound { get; set; } = 0; public double DamagePerTurn { get; set; } = 0; public double DamagePerSecond { get; set; } = 0; diff --git a/Entity/System/Inventory.cs b/Entity/System/Inventory.cs index 7de60f0..a3de576 100644 --- a/Entity/System/Inventory.cs +++ b/Entity/System/Inventory.cs @@ -93,7 +93,6 @@ namespace Milimoe.FunGame.Core.Entity internal Inventory(User user) { User = user; - Name = user.Username + "的库存"; } public override string ToString() diff --git a/Entity/System/RoundRecord.cs b/Entity/System/RoundRecord.cs index 9141fb3..0bf9707 100644 --- a/Entity/System/RoundRecord.cs +++ b/Entity/System/RoundRecord.cs @@ -16,8 +16,10 @@ namespace Milimoe.FunGame.Core.Entity public bool HasKill { get; set; } = false; public Dictionary Damages { get; set; } = []; public Dictionary IsCritical { get; set; } = []; + public Dictionary IsEvaded { get; set; } = []; + public Dictionary IsImmune { get; set; } = []; public Dictionary Heals { get; set; } = []; - public Dictionary Effects { get; set; } = []; + public Dictionary> Effects { get; set; } = []; public List ActorContinuousKilling { get; set; } = []; public List DeathContinuousKilling { get; set; } = []; public double CastTime { get; set; } = 0; @@ -108,15 +110,22 @@ namespace Milimoe.FunGame.Core.Entity { hasHeal = $"治疗:{heals:0.##}"; } - if (Effects.TryGetValue(target, out EffectType effectType)) + if (Effects.TryGetValue(target, out List? effectTypes) && effectTypes != null) { - hasEffect = $"施加:{SkillSet.GetEffectTypeName(effectType)}"; + hasEffect = $"施加:{string.Join(" + ", effectTypes.Select(SkillSet.GetEffectTypeName))}"; } - if (ActionType == CharacterActionType.NormalAttack && hasDamage == "") + if (IsEvaded.ContainsKey(target)) { - hasDamage = "完美闪避"; + if (ActionType == CharacterActionType.NormalAttack) + { + hasDamage = "完美闪避"; + } + else if ((ActionType == CharacterActionType.PreCastSkill || ActionType == CharacterActionType.CastSkill || ActionType == CharacterActionType.CastSuperSkill)) + { + hasDamage = "技能免疫"; + } } - if ((ActionType == CharacterActionType.PreCastSkill || ActionType == CharacterActionType.CastSkill || ActionType == CharacterActionType.CastSuperSkill) && hasDamage == "" && target != Actor) + if (IsImmune.ContainsKey(target) && hasDamage != "" && target != Actor) { hasDamage = "免疫"; } diff --git a/Interface/Base/IGamingQueue.cs b/Interface/Base/IGamingQueue.cs index 7ea52d6..d963a53 100644 --- a/Interface/Base/IGamingQueue.cs +++ b/Interface/Base/IGamingQueue.cs @@ -92,7 +92,7 @@ namespace Milimoe.FunGame.Core.Interface.Base /// /// public Task HealToTargetAsync(Character actor, Character target, double heal, bool canRespawn = false); - + /// /// 计算物理伤害 /// @@ -101,8 +101,9 @@ namespace Milimoe.FunGame.Core.Interface.Base /// /// /// + /// /// - public DamageResult CalculatePhysicalDamage(Character actor, Character enemy, bool isNormalAttack, double expectedDamage, out double finalDamage); + public DamageResult CalculatePhysicalDamage(Character actor, Character enemy, bool isNormalAttack, double expectedDamage, out double finalDamage, ref int changeCount); /// /// 计算魔法伤害 @@ -113,8 +114,9 @@ namespace Milimoe.FunGame.Core.Interface.Base /// /// /// + /// /// - public DamageResult CalculateMagicalDamage(Character actor, Character enemy, bool isNormalAttack, MagicType magicType, double expectedDamage, out double finalDamage); + public DamageResult CalculateMagicalDamage(Character actor, Character enemy, bool isNormalAttack, MagicType magicType, double expectedDamage, out double finalDamage, ref int changeCount); /// /// 死亡结算 @@ -171,5 +173,13 @@ namespace Milimoe.FunGame.Core.Interface.Base /// /// public bool IsCharacterInAIControlling(Character character); + + /// + /// 修改角色的硬直时间 + /// + /// 角色 + /// 加值 + /// 是否使用插队保护机制 + public void ChangeCharacterHardnessTime(Character character, double addValue, bool isCheckProtected); } } diff --git a/Library/Common/JsonConverter/CharacterConverter.cs b/Library/Common/JsonConverter/CharacterConverter.cs index eb3ada5..ae0c545 100644 --- a/Library/Common/JsonConverter/CharacterConverter.cs +++ b/Library/Common/JsonConverter/CharacterConverter.cs @@ -71,6 +71,9 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter case nameof(Character.IsUnselectable): result.IsUnselectable = reader.GetBoolean(); break; + case nameof(Character.ImmuneType): + result.ImmuneType = (ImmuneType)reader.GetInt32(); + break; case nameof(Character.InitialHP): result.InitialHP = reader.GetDouble(); break; @@ -206,6 +209,12 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter case nameof(Character.ExEvadeRate): result.ExEvadeRate = reader.GetDouble(); break; + case nameof(Character.Lifesteal): + result.Lifesteal = reader.GetDouble(); + break; + case nameof(Character.Shield): + result.Shield = NetworkUtility.JsonDeserialize(ref reader, options) ?? new(); + break; case nameof(Character.NormalAttack): NormalAttack normalAttack = NetworkUtility.JsonDeserialize(ref reader, options) ?? new NormalAttack(result); result.NormalAttack.Level = normalAttack.Level; @@ -253,6 +262,7 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter writer.WriteNumber(nameof(Character.EXP), value.EXP); writer.WriteBoolean(nameof(Character.IsNeutral), value.IsNeutral); writer.WriteBoolean(nameof(Character.IsUnselectable), value.IsUnselectable); + writer.WriteNumber(nameof(Character.ImmuneType), (int)value.ImmuneType); writer.WriteNumber(nameof(Character.CharacterState), (int)value.CharacterState); writer.WriteNumber(nameof(Character.InitialHP), value.InitialHP); writer.WriteNumber(nameof(Character.ExHP2), value.ExHP2); @@ -299,6 +309,9 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter writer.WriteNumber(nameof(Character.ExCritRate), value.ExCritRate); writer.WriteNumber(nameof(Character.ExCritDMG), value.ExCritDMG); writer.WriteNumber(nameof(Character.ExEvadeRate), value.ExEvadeRate); + writer.WriteNumber(nameof(Character.Lifesteal), value.Lifesteal); + writer.WritePropertyName(nameof(Character.Shield)); + JsonSerializer.Serialize(writer, value.Shield, options); writer.WritePropertyName(nameof(Character.NormalAttack)); JsonSerializer.Serialize(writer, value.NormalAttack, options); writer.WritePropertyName(nameof(Character.Skills)); diff --git a/Library/Common/JsonConverter/NormalAttackConverter.cs b/Library/Common/JsonConverter/NormalAttackConverter.cs index e94eb1c..7446b02 100644 --- a/Library/Common/JsonConverter/NormalAttackConverter.cs +++ b/Library/Common/JsonConverter/NormalAttackConverter.cs @@ -29,6 +29,9 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter case nameof(NormalAttack.MagicType): result.SetMagicType(result.IsMagic, (MagicType)reader.GetInt32()); break; + case nameof(NormalAttack.IgnoreImmune): + result.IgnoreImmune = (ImmuneType)reader.GetInt32(); + break; } } @@ -40,6 +43,7 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter writer.WriteNumber(nameof(NormalAttack.HardnessTime), value.HardnessTime); writer.WriteBoolean(nameof(NormalAttack.IsMagic), value.IsMagic); writer.WriteNumber(nameof(NormalAttack.MagicType), (int)value.MagicType); + writer.WriteNumber(nameof(NormalAttack.IgnoreImmune), (int)value.IgnoreImmune); writer.WriteEndObject(); } diff --git a/Library/Common/JsonConverter/ShieldConverter.cs b/Library/Common/JsonConverter/ShieldConverter.cs new file mode 100644 index 0000000..5ce24b3 --- /dev/null +++ b/Library/Common/JsonConverter/ShieldConverter.cs @@ -0,0 +1,69 @@ +using System.Text.Json; +using Milimoe.FunGame.Core.Entity; +using Milimoe.FunGame.Core.Library.Common.Architecture; + +namespace Milimoe.FunGame.Core.Library.Common.JsonConverter +{ + public class ShieldConverter : BaseEntityConverter + { + public override Shield NewInstance() + { + return new(); + } + + public override void ReadPropertyName(ref Utf8JsonReader reader, string propertyName, JsonSerializerOptions options, ref Shield result, Dictionary convertingContext) + { + switch (propertyName) + { + case nameof(Shield.Physical): + result.Physical = reader.GetDouble(); + break; + case nameof(Shield.None): + result.None = reader.GetDouble(); + break; + case nameof(Shield.Starmark): + result.Starmark = reader.GetDouble(); + break; + case nameof(Shield.PurityNatural): + result.PurityNatural = reader.GetDouble(); + break; + case nameof(Shield.PurityContemporary): + result.PurityContemporary = reader.GetDouble(); + break; + case nameof(Shield.Bright): + result.Bright = reader.GetDouble(); + break; + case nameof(Shield.Shadow): + result.Shadow = reader.GetDouble(); + break; + case nameof(Shield.Element): + result.Element = reader.GetDouble(); + break; + case nameof(Shield.Fleabane): + result.Fleabane = reader.GetDouble(); + break; + case nameof(Shield.Particle): + result.Particle = reader.GetDouble(); + break; + } + } + + public override void Write(Utf8JsonWriter writer, Shield value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + + writer.WriteNumber(nameof(Shield.Physical), value.Physical); + writer.WriteNumber(nameof(Shield.None), value.None); + writer.WriteNumber(nameof(Shield.Starmark), value.Starmark); + writer.WriteNumber(nameof(Shield.PurityNatural), value.PurityNatural); + writer.WriteNumber(nameof(Shield.PurityContemporary), value.PurityContemporary); + writer.WriteNumber(nameof(Shield.Bright), value.Bright); + writer.WriteNumber(nameof(Shield.Shadow), value.Shadow); + writer.WriteNumber(nameof(Shield.Element), value.Element); + writer.WriteNumber(nameof(Shield.Fleabane), value.Fleabane); + writer.WriteNumber(nameof(Shield.Particle), value.Particle); + + writer.WriteEndObject(); + } + } +} diff --git a/Library/Constant/ConstantSet.cs b/Library/Constant/ConstantSet.cs index bb97b20..ed3631a 100644 --- a/Library/Constant/ConstantSet.cs +++ b/Library/Constant/ConstantSet.cs @@ -664,8 +664,8 @@ namespace Milimoe.FunGame.Core.Library.Constant EffectType.DamageBoost => "伤害提升", EffectType.DefenseBoost => "防御提升", EffectType.CritBoost => "暴击提升", - EffectType.ManaRegen => "魔法恢复", - EffectType.ArmorBreak => "破甲", + EffectType.MPRegen => "魔法恢复", + EffectType.PenetrationBoost => "穿透提升", EffectType.MagicResistBreak => "降低魔抗", EffectType.Curse => "诅咒", EffectType.Exhaustion => "疲劳", @@ -677,8 +677,171 @@ namespace Milimoe.FunGame.Core.Library.Constant EffectType.SilenceMagic => "法术沉默", EffectType.Banish => "放逐", EffectType.Doom => "毁灭", + EffectType.PhysicalImmune => "物理免疫", + EffectType.MagicalImmune => "魔法免疫", + EffectType.SkilledImmune => "技能免疫", + EffectType.AllImmune => "完全免疫", + EffectType.EvadeBoost => "闪避提升", _ => "未知效果" }; } + + public static DispelledType GetDispelledTypeByEffectType(EffectType type) + { + return type switch + { + EffectType.None => DispelledType.CannotBeDispelled, + EffectType.Item => DispelledType.CannotBeDispelled, + EffectType.Knockback => DispelledType.CannotBeDispelled, + EffectType.Unselectable => DispelledType.CannotBeDispelled, + EffectType.Doom => DispelledType.CannotBeDispelled, + EffectType.Stun => DispelledType.Strong, + EffectType.Freeze => DispelledType.Strong, + EffectType.Silence => DispelledType.Strong, + EffectType.Root => DispelledType.Strong, + EffectType.Fear => DispelledType.Strong, + EffectType.Sleep => DispelledType.Strong, + EffectType.Knockdown => DispelledType.Strong, + EffectType.Taunt => DispelledType.Strong, + EffectType.Invulnerable => DispelledType.Strong, + EffectType.Charm => DispelledType.Strong, + EffectType.Disarm => DispelledType.Strong, + EffectType.Confusion => DispelledType.Strong, + EffectType.Petrify => DispelledType.Strong, + EffectType.SilenceMagic => DispelledType.Strong, + EffectType.Banish => DispelledType.Strong, + EffectType.Mark => DispelledType.Weak, + EffectType.Slow => DispelledType.Weak, + EffectType.Weaken => DispelledType.Weak, + EffectType.Poison => DispelledType.Weak, + EffectType.Burn => DispelledType.Weak, + EffectType.Bleed => DispelledType.Weak, + EffectType.Blind => DispelledType.Weak, + EffectType.Cripple => DispelledType.Weak, + EffectType.Shield => DispelledType.Weak, + EffectType.HealOverTime => DispelledType.Weak, + EffectType.Haste => DispelledType.Weak, + EffectType.DamageBoost => DispelledType.Weak, + EffectType.DefenseBoost => DispelledType.Weak, + EffectType.CritBoost => DispelledType.Weak, + EffectType.MPRegen => DispelledType.Weak, + EffectType.PenetrationBoost => DispelledType.Weak, + EffectType.MagicResistBreak => DispelledType.Weak, + EffectType.Curse => DispelledType.Weak, + EffectType.Exhaustion => DispelledType.Weak, + EffectType.ManaBurn => DispelledType.Weak, + EffectType.PhysicalImmune => DispelledType.Weak, + EffectType.MagicalImmune => DispelledType.Weak, + EffectType.SkilledImmune => DispelledType.Weak, + EffectType.AllImmune => DispelledType.Strong, + EffectType.EvadeBoost => DispelledType.Weak, + EffectType.Lifesteal => DispelledType.Weak, + EffectType.GrievousWound => DispelledType.Weak, + _ => DispelledType.Weak + }; + } + + public static bool GetIsDebuffByEffectType(EffectType type) + { + return type switch + { + EffectType.None => false, + EffectType.Item => false, + EffectType.Knockback => true, + EffectType.Unselectable => false, + EffectType.Doom => true, + EffectType.Stun => true, + EffectType.Freeze => true, + EffectType.Silence => true, + EffectType.Root => true, + EffectType.Fear => true, + EffectType.Sleep => true, + EffectType.Knockdown => true, + EffectType.Taunt => true, + EffectType.Invulnerable => false, + EffectType.Charm => true, + EffectType.Disarm => true, + EffectType.Confusion => true, + EffectType.Petrify => true, + EffectType.SilenceMagic => true, + EffectType.Banish => true, + EffectType.Mark => true, + EffectType.Slow => true, + EffectType.Weaken => true, + EffectType.Poison => true, + EffectType.Burn => true, + EffectType.Bleed => true, + EffectType.Blind => true, + EffectType.Cripple => true, + EffectType.Shield => false, + EffectType.HealOverTime => false, + EffectType.Haste => false, + EffectType.DamageBoost => false, + EffectType.DefenseBoost => false, + EffectType.CritBoost => false, + EffectType.MPRegen => false, + EffectType.PenetrationBoost => true, + EffectType.MagicResistBreak => true, + EffectType.Curse => true, + EffectType.Exhaustion => true, + EffectType.ManaBurn => true, + EffectType.PhysicalImmune => false, + EffectType.MagicalImmune => false, + EffectType.SkilledImmune => false, + EffectType.AllImmune => false, + EffectType.EvadeBoost => false, + EffectType.Lifesteal => false, + EffectType.GrievousWound => false, + _ => false + }; + } + + public static CharacterState GetCharacterStateByEffectType(EffectType type) + { + return type switch + { + EffectType.Stun => CharacterState.NotActionable, + EffectType.Freeze => CharacterState.NotActionable, + EffectType.Sleep => CharacterState.NotActionable, + EffectType.Knockdown => CharacterState.NotActionable, + EffectType.Petrify => CharacterState.NotActionable, + EffectType.Banish => CharacterState.NotActionable, + EffectType.Root => CharacterState.ActionRestricted, + EffectType.Fear => CharacterState.ActionRestricted, + EffectType.Taunt => CharacterState.ActionRestricted, + EffectType.Charm => CharacterState.ActionRestricted, + EffectType.Confusion => CharacterState.ActionRestricted, + EffectType.Silence => CharacterState.SkillRestricted, + EffectType.SilenceMagic => CharacterState.SkillRestricted, + EffectType.Disarm => CharacterState.AttackRestricted, + _ => CharacterState.Actionable, + }; + } + + public static string GetDispelType(DispelType type) + { + return type switch + { + DispelType.Weak => "弱驱散", + DispelType.DurativeWeak => "持续性弱驱散", + DispelType.TemporaryWeak => "临时弱驱散", + DispelType.Strong => "强驱散", + DispelType.DurativeStrong => "持续性强驱散", + DispelType.TemporaryStrong => "临时强驱散", + DispelType.Special => "特殊驱散", + _ => "" + }; + } + + public static string GetDispelledType(DispelledType type) + { + return type switch + { + DispelledType.Strong => "需强驱散", + DispelledType.Special => "需特殊驱散", + DispelledType.CannotBeDispelled => "不可驱散", + _ => "" + }; + } } } diff --git a/Library/Constant/StateEnum.cs b/Library/Constant/StateEnum.cs index 182bee6..e962d6f 100644 --- a/Library/Constant/StateEnum.cs +++ b/Library/Constant/StateEnum.cs @@ -34,42 +34,42 @@ namespace Milimoe.FunGame.Core.Library.Constant public enum CharacterState { /// - /// 可以行动 [ 战斗相关 ] + /// 可以行动 [ 战斗相关 ] [ 常态 ] /// Actionable, /// - /// 完全行动不能 [ 战斗相关 ] + /// 完全行动不能 [ 战斗相关 ] [ 控制态 ] /// NotActionable, /// - /// 行动受限 [ 战斗相关 ] + /// 行动受限 [ 战斗相关 ] [ 控制态 ] /// ActionRestricted, /// - /// 战斗不能 [ 战斗相关 ] + /// 战斗不能 [ 战斗相关 ] [ 控制态 ] /// BattleRestricted, /// - /// 技能受限 [ 战斗相关 ] + /// 技能受限 [ 战斗相关 ] [ 控制态 ] /// SkillRestricted, - + /// - /// 攻击受限 [ 战斗相关 ] + /// 攻击受限 [ 战斗相关 ] [ 控制态 ] /// AttackRestricted, /// - /// 处于吟唱中 [ 战斗相关 ] [ 技能相关 ] + /// 处于吟唱中 [ 战斗相关 ] [ 技能相关 ] [ 吟唱态 ] /// Casting, /// - /// 预释放爆发技(插队) [ 战斗相关 ] [ 技能相关 ] + /// 预释放爆发技(插队) [ 战斗相关 ] [ 技能相关 ] [ 吟唱态 ] /// PreCastSuperSkill } diff --git a/Library/Constant/TypeEnum.cs b/Library/Constant/TypeEnum.cs index a76f67f..3e192ad 100644 --- a/Library/Constant/TypeEnum.cs +++ b/Library/Constant/TypeEnum.cs @@ -337,7 +337,7 @@ namespace Milimoe.FunGame.Core.Library.Constant HealOverTime, /// - /// 加速,提升行动速度和攻击频率 + /// 加速,提升行动速度 /// Haste, @@ -357,7 +357,7 @@ namespace Milimoe.FunGame.Core.Library.Constant DamageBoost, /// - /// 防御提升,减少所受伤害 + /// 物理护甲/魔法抗性提升,减少所受伤害 /// DefenseBoost, @@ -369,12 +369,12 @@ namespace Milimoe.FunGame.Core.Library.Constant /// /// 魔法恢复,增加魔法值回复速度 /// - ManaRegen, + MPRegen, /// - /// 破甲,降低目标的防御值 + /// 破甲,提高物理/魔法穿透 /// - ArmorBreak, + PenetrationBoost, /// /// 降低魔法抗性,目标更容易受到魔法伤害 @@ -429,7 +429,42 @@ namespace Milimoe.FunGame.Core.Library.Constant /// /// 毁灭,目标在倒计时结束后受到大量伤害或死亡 /// - Doom + Doom, + + /// + /// 物理免疫 + /// + PhysicalImmune, + + /// + /// 魔法免疫 + /// + MagicalImmune, + + /// + /// 技能免疫 + /// + SkilledImmune, + + /// + /// 完全免疫:物理免疫 + 技能免疫 + /// + AllImmune, + + /// + /// 闪避提升 + /// + EvadeBoost, + + /// + /// 生命偷取 + /// + Lifesteal, + + /// + /// 重伤,目标受到的治疗效果降低 + /// + GrievousWound } public enum ItemType @@ -901,4 +936,83 @@ namespace Milimoe.FunGame.Core.Library.Constant PreCastSuperSkill, Respawn } + + public enum DispelType + { + /// + /// 无驱散 [ 默认,不能驱散其他特效 ] + /// + None, + + /// + /// 弱驱散 + /// + Weak, + + /// + /// 持续性弱驱散 + /// + DurativeWeak, + + /// + /// 临时弱驱散 + /// + TemporaryWeak, + + /// + /// 强驱散 + /// + Strong, + + /// + /// 持续性强驱散 + /// + DurativeStrong, + + /// + /// 临时强驱散 + /// + TemporaryStrong, + + /// + /// 特殊驱散 + /// + Special + } + + public enum DispelledType + { + /// + /// 可弱驱散 [ 默认 ] + /// + Weak, + + /// + /// 需强驱散 + /// + Strong, + + /// + /// 需特殊驱散 + /// + Special, + + /// + /// 不可驱散 [ 最高优先级 ] + /// + CannotBeDispelled + } + + /// + /// 标准实现的免疫类型 + /// + public enum ImmuneType + { + None, + Physical, + Magical, + Skilled, + All, + Special + } } diff --git a/Model/ActionQueue.cs b/Model/ActionQueue.cs index bfee9cf..bcb4a62 100644 --- a/Model/ActionQueue.cs +++ b/Model/ActionQueue.cs @@ -1,6 +1,7 @@ using Milimoe.FunGame.Core.Api.Utility; using Milimoe.FunGame.Core.Entity; using Milimoe.FunGame.Core.Interface.Base; +using Milimoe.FunGame.Core.Interface.Entity; using Milimoe.FunGame.Core.Library.Constant; namespace Milimoe.FunGame.Core.Model @@ -186,7 +187,7 @@ namespace Milimoe.FunGame.Core.Model /// /// 助攻伤害 /// - protected readonly Dictionary _assistDamage = []; + protected readonly Dictionary _assistDetail = []; /// /// 角色数据 @@ -294,7 +295,7 @@ namespace Milimoe.FunGame.Core.Model // 如果只有一个角色,直接加入队列 Character character = group.First(); AddCharacter(character, Calculation.Round2Digits(_queue.Count * 0.1), false); - _assistDamage.Add(character, new AssistDetail(character, characters.Where(c => c != character))); + _assistDetail.Add(character, new AssistDetail(character, characters.Where(c => c != character))); _stats.Add(character, new()); // 初始化技能 foreach (Skill skill in character.Skills) @@ -348,7 +349,7 @@ namespace Milimoe.FunGame.Core.Model if (selectedCharacter != null) { AddCharacter(selectedCharacter, Calculation.Round2Digits(_queue.Count * 0.1), false); - _assistDamage.Add(selectedCharacter, new AssistDetail(selectedCharacter, characters.Where(c => c != selectedCharacter))); + _assistDetail.Add(selectedCharacter, new AssistDetail(selectedCharacter, characters.Where(c => c != selectedCharacter))); _stats.Add(selectedCharacter, new()); // 初始化技能 foreach (Skill skill in selectedCharacter.Skills) @@ -373,7 +374,7 @@ namespace Milimoe.FunGame.Core.Model _original.Clear(); _queue.Clear(); _hardnessTimes.Clear(); - _assistDamage.Clear(); + _assistDetail.Clear(); _stats.Clear(); _cutCount.Clear(); _castingSkills.Clear(); @@ -530,12 +531,26 @@ namespace Milimoe.FunGame.Core.Model /// 从行动顺序表取出第一个角色 /// /// - public Character? NextCharacter() + public async Task NextCharacterAsync() { if (_queue.Count == 0) return null; - // 硬直时间为0的角色将执行行动 - Character? character = _queue.FirstOrDefault(c => _hardnessTimes[c] == 0); + // 硬直时间为 0 的角色或预释放爆发技的角色先行动,取第一个 + int couynt = _queue.Count(c => c.CharacterState == CharacterState.PreCastSuperSkill); + Character? character = _queue.FirstOrDefault(c => c.CharacterState == CharacterState.PreCastSuperSkill); + if (character is null) + { + Character temp = _queue[0]; + if (_hardnessTimes[temp] == 0) + { + character = temp; + } + } + else + { + _hardnessTimes[character] = 0; + } + if (character != null) { _queue.Remove(character); @@ -548,8 +563,11 @@ namespace Milimoe.FunGame.Core.Model return character; } - - return null; + else + { + await TimeLapse(); + return await NextCharacterAsync(); + } } /// @@ -621,7 +639,7 @@ namespace Milimoe.FunGame.Core.Model skillTurnStart.OnTurnStart(character, enemys, teammates, skills, items); } - List effects = [.. character.Effects.Where(e => e.Level > 0)]; + List effects = [.. character.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)]; foreach (Effect effect in effects) { effect.OnTurnStart(character, enemys, teammates, skills, items); @@ -657,7 +675,7 @@ namespace Milimoe.FunGame.Core.Model if (character.CharacterState != CharacterState.Casting && character.CharacterState != CharacterState.PreCastSuperSkill) { CharacterActionType actionTypeTemp = CharacterActionType.None; - effects = [.. character.Effects.Where(e => e.Level > 0)]; + effects = [.. character.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)]; foreach (Effect effect in effects) { actionTypeTemp = effect.AlterActionTypeBeforeAction(character, character.CharacterState, ref canUseItem, ref canCastSkill, ref pUseItem, ref pCastSkill, ref pNormalAttack); @@ -780,7 +798,7 @@ namespace Milimoe.FunGame.Core.Model Dictionary continuousKillingTemp = new(_continuousKilling); Dictionary earnedMoneyTemp = new(_earnedMoney); - effects = [.. character.Effects.Where(e => e.Level > 0)]; + effects = [.. character.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)]; foreach (Effect effect in effects) { effect.AlterSelectListBeforeAction(character, enemys, teammates, skills, continuousKillingTemp, earnedMoneyTemp); @@ -815,8 +833,8 @@ namespace Milimoe.FunGame.Core.Model await OnCharacterNormalAttackAsync(character, targets); character.NormalAttack.Attack(this, character, targets); - baseTime = character.NormalAttack.HardnessTime; - effects = [.. character.Effects.Where(e => e.Level > 0)]; + baseTime = character.NormalAttack.RealHardnessTime; + effects = [.. character.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)]; foreach (Effect effect in effects) { effect.AlterHardnessTimeAfterNormalAttack(character, ref baseTime, ref isCheckProtected); @@ -845,16 +863,22 @@ namespace Milimoe.FunGame.Core.Model } if (targets.Count > 0) { - LastRound.Targets = [.. targets]; - decided = true; + // 免疫检定 + await CheckSkilledImmuneAsync(character, targets, skill); - character.CharacterState = CharacterState.Casting; - SkillTarget skillTarget = new(skill, targets); - await OnCharacterPreCastSkillAsync(character, skillTarget); + if (targets.Count > 0) + { + LastRound.Targets = [.. targets]; + decided = true; - _castingSkills[character] = skillTarget; - baseTime = skill.CastTime; - skill.OnSkillCasting(this, character, targets); + character.CharacterState = CharacterState.Casting; + SkillTarget skillTarget = new(skill, targets); + await OnCharacterPreCastSkillAsync(character, skillTarget); + + _castingSkills[character] = skillTarget; + baseTime = skill.RealCastTime; + skill.OnSkillCasting(this, character, targets); + } } } else @@ -870,29 +894,35 @@ namespace Milimoe.FunGame.Core.Model } if (targets.Count > 0) { - LastRound.Targets = [.. targets]; - decided = true; + // 免疫检定 + await CheckSkilledImmuneAsync(character, targets, skill); - SkillTarget skillTarget = new(skill, targets); - await OnCharacterPreCastSkillAsync(character, skillTarget); - - skill.OnSkillCasting(this, character, targets); - skill.BeforeSkillCasted(); - - character.EP -= cost; - baseTime = skill.HardnessTime; - skill.CurrentCD = skill.RealCD; - skill.Enable = false; - LastRound.SkillCost = $"{-cost:0.##} EP"; - WriteLine($"[ {character} ] 消耗了 {cost:0.##} 点能量,释放了{(skill.IsSuperSkill ? "爆发技" : "战技")} [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}"); - - await OnCharacterCastSkillAsync(character, skillTarget, cost); - - skill.OnSkillCasted(this, character, targets); - effects = [.. character.Effects.Where(e => e.Level > 0)]; - foreach (Effect effect in effects) + if (targets.Count > 0) { - effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected); + LastRound.Targets = [.. targets]; + decided = true; + + SkillTarget skillTarget = new(skill, targets); + await OnCharacterPreCastSkillAsync(character, skillTarget); + + skill.OnSkillCasting(this, character, targets); + skill.BeforeSkillCasted(); + + character.EP -= cost; + baseTime = skill.RealHardnessTime; + skill.CurrentCD = skill.RealCD; + skill.Enable = false; + LastRound.SkillCost = $"{-cost:0.##} EP"; + WriteLine($"[ {character} ] 消耗了 {cost:0.##} 点能量,释放了{(skill.IsSuperSkill ? "爆发技" : "战技")} [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}"); + + await OnCharacterCastSkillAsync(character, skillTarget, cost); + + skill.OnSkillCasted(this, character, targets); + effects = [.. character.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)]; + foreach (Effect effect in effects) + { + effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected); + } } } } @@ -906,29 +936,36 @@ namespace Milimoe.FunGame.Core.Model { // 使用技能逻辑,结束吟唱状态 character.CharacterState = CharacterState.Actionable; + character.UpdateCharacterState(); Skill skill = skillTarget.Skill; List targets = [.. skillTarget.Targets.Where(c => !c.IsUnselectable)]; // 判断是否能够释放技能 if (targets.Count > 0 && CheckCanCast(character, skill, out double cost)) { - decided = true; - LastRound.Targets = [.. targets]; - LastRound.Skill = skill; - _castingSkills.Remove(character); + // 免疫检定 + await CheckSkilledImmuneAsync(character, targets, skill); - skill.BeforeSkillCasted(); + if (targets.Count > 0) + { + decided = true; + LastRound.Targets = [.. targets]; + LastRound.Skill = skill; + _castingSkills.Remove(character); - character.MP -= cost; - baseTime = skill.HardnessTime; - skill.CurrentCD = skill.RealCD; - skill.Enable = false; - LastRound.SkillCost = $"{-cost:0.##} MP"; - WriteLine($"[ {character} ] 消耗了 {cost:0.##} 点魔法值,释放了魔法 [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}"); + skill.BeforeSkillCasted(); - await OnCharacterCastSkillAsync(character, skillTarget, cost); + character.MP -= cost; + baseTime = skill.RealHardnessTime; + skill.CurrentCD = skill.RealCD; + skill.Enable = false; + LastRound.SkillCost = $"{-cost:0.##} MP"; + WriteLine($"[ {character} ] 消耗了 {cost:0.##} 点魔法值,释放了魔法 [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}"); - skill.OnSkillCasted(this, character, targets); + await OnCharacterCastSkillAsync(character, skillTarget, cost); + + skill.OnSkillCasted(this, character, targets); + } } else { @@ -937,7 +974,7 @@ namespace Milimoe.FunGame.Core.Model baseTime = 3; } - effects = [.. character.Effects.Where(e => e.Level > 0)]; + effects = [.. character.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)]; foreach (Effect effect in effects) { effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected); @@ -947,6 +984,7 @@ namespace Milimoe.FunGame.Core.Model { // 原吟唱的技能丢失(被打断或者被取消),允许角色再次决策 character.CharacterState = CharacterState.Actionable; + character.UpdateCharacterState(); } } else if (type == CharacterActionType.CastSuperSkill) @@ -954,6 +992,7 @@ namespace Milimoe.FunGame.Core.Model decided = true; // 结束预释放爆发技的状态 character.CharacterState = CharacterState.Actionable; + character.UpdateCharacterState(); Skill skill = _castingSuperSkills[character]; LastRound.Skill = skill; _castingSuperSkills.Remove(character); @@ -963,12 +1002,14 @@ namespace Milimoe.FunGame.Core.Model { // 预释放的爆发技不可取消 List targets = await SelectTargetsAsync(character, skill, enemys, teammates); + // 免疫检定 + await CheckSkilledImmuneAsync(character, targets, skill); LastRound.Targets = [.. targets]; skill.BeforeSkillCasted(); character.EP -= cost; - baseTime = skill.HardnessTime; + baseTime = skill.RealHardnessTime; skill.CurrentCD = skill.RealCD; skill.Enable = false; LastRound.SkillCost = $"{-cost:0.##} EP"; @@ -986,7 +1027,7 @@ namespace Milimoe.FunGame.Core.Model baseTime = 3; } - effects = [.. character.Effects.Where(e => e.Level > 0)]; + effects = [.. character.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)]; foreach (Effect effect in effects) { effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected); @@ -1008,8 +1049,8 @@ namespace Milimoe.FunGame.Core.Model { decided = true; LastRound.Item = item; - baseTime = skill.HardnessTime; - effects = [.. character.Effects.Where(e => e.Level > 0)]; + baseTime = skill.RealHardnessTime; + effects = [.. character.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)]; foreach (Effect effect in effects) { effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected); @@ -1038,6 +1079,13 @@ namespace Milimoe.FunGame.Core.Model LastRound.ActionType = type; + // 如果目标都是队友,会考虑非伤害型助攻 + Team? team = GetTeam(character); + if (team != null) + { + SetNotDamageAssistTime(character, LastRound.Targets.Where(team.IsOnThisTeam)); + } + // 统一在回合结束时处理角色的死亡 await ProcessCharacterDeathAsync(character); @@ -1058,12 +1106,12 @@ namespace Milimoe.FunGame.Core.Model double newHardnessTime = baseTime; if (character.CharacterState != CharacterState.Casting) { - newHardnessTime = Math.Max(0, Calculation.Round2Digits(baseTime * (1 - character.ActionCoefficient))); + newHardnessTime = Calculation.Round2Digits(baseTime); WriteLine($"[ {character} ] 回合结束,获得硬直时间:{newHardnessTime} {GameplayEquilibriumConstant.InGameTime}"); } else { - newHardnessTime = Math.Max(0, Calculation.Round2Digits(baseTime * (1 - character.AccelerationCoefficient))); + newHardnessTime = Calculation.Round2Digits(baseTime); WriteLine($"[ {character} ] 进行吟唱,持续时间:{newHardnessTime} {GameplayEquilibriumConstant.InGameTime}"); LastRound.CastTime = newHardnessTime; } @@ -1071,7 +1119,7 @@ namespace Milimoe.FunGame.Core.Model await OnQueueUpdatedAsync(_queue, character, newHardnessTime, QueueUpdatedReason.Action, "设置角色行动后的硬直时间。"); LastRound.HardnessTime = newHardnessTime; - effects = [.. character.Effects.Where(e => e.Level > 0)]; + effects = [.. character.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)]; foreach (Effect effect in effects) { effect.OnTurnEnd(character); @@ -1140,27 +1188,19 @@ namespace Milimoe.FunGame.Core.Model TotalTime = Calculation.Round2Digits(TotalTime + timeToReduce); WriteLine("时间流逝:" + timeToReduce); - // 减少复活倒计时 - foreach (Character character in _respawnCountdown.Keys) - { - _respawnCountdown[character] = Calculation.Round2Digits(_respawnCountdown[character] - timeToReduce); - if (_respawnCountdown[character] <= 0) - { - await SetCharacterRespawn(character); - } - } - foreach (Character character in _queue) { // 减少所有角色的硬直时间 + double h = _hardnessTimes[character]; + double d = _hardnessTimes[character] - timeToReduce; _hardnessTimes[character] = Calculation.Round2Digits(_hardnessTimes[character] - timeToReduce); // 统计 _stats[character].LiveRound += 1; - _stats[character].LiveTime = Calculation.Round2Digits(_stats[character].LiveTime + timeToReduce); - _stats[character].DamagePerRound = Calculation.Round2Digits(_stats[character].TotalDamage / _stats[character].LiveRound); - _stats[character].DamagePerTurn = Calculation.Round2Digits(_stats[character].TotalDamage / _stats[character].ActionTurn); - _stats[character].DamagePerSecond = Calculation.Round2Digits(_stats[character].TotalDamage / _stats[character].LiveTime); + _stats[character].LiveTime += timeToReduce; + _stats[character].DamagePerRound = _stats[character].TotalDamage / _stats[character].LiveRound; + _stats[character].DamagePerTurn = _stats[character].TotalDamage / _stats[character].ActionTurn; + _stats[character].DamagePerSecond = _stats[character].TotalDamage / _stats[character].LiveTime; // 回血回蓝 double recoveryHP = character.HR * timeToReduce; @@ -1201,16 +1241,26 @@ namespace Milimoe.FunGame.Core.Model } // 移除到时间的特效 - List effects = [.. character.Effects.Where(e => e.Level > 0)]; + List effects = [.. character.Effects]; foreach (Effect effect in effects) { + if (effect.IsBeingTemporaryDispelled) + { + effect.IsBeingTemporaryDispelled = false; + effect.OnEffectGained(character); + } + if (effect.Level == 0) { character.Effects.Remove(effect); continue; } - effect.OnTimeElapsed(character, timeToReduce); + if (!effect.Durative) + { + // 防止特效在时间流逝后,持续时间已结束还能继续生效的情况 + effect.OnTimeElapsed(character, timeToReduce); + } // 自身被动不会考虑 if (effect.EffectType == EffectType.None && effect.Skill.SkillType == SkillType.Passive) @@ -1218,6 +1268,13 @@ namespace Milimoe.FunGame.Core.Model continue; } + // 统计控制时长 + if (effect.Source != null && SkillSet.GetCharacterStateByEffectType(effect.EffectType) != CharacterState.Actionable) + { + _stats[effect.Source].ControlTime += timeToReduce; + SetNotDamageAssistTime(effect.Source, character); + } + if (effect.Durative) { effect.RemainDuration -= timeToReduce; @@ -1227,10 +1284,24 @@ namespace Milimoe.FunGame.Core.Model character.Effects.Remove(effect); effect.OnEffectLost(character); } + else + { + effect.OnTimeElapsed(character, timeToReduce); + } } } } + // 减少复活倒计时 + foreach (Character character in _respawnCountdown.Keys) + { + _respawnCountdown[character] = Calculation.Round2Digits(_respawnCountdown[character] - timeToReduce); + if (_respawnCountdown[character] <= 0) + { + await SetCharacterRespawn(character); + } + } + WriteLine("\r\n"); return timeToReduce; @@ -1259,9 +1330,10 @@ namespace Milimoe.FunGame.Core.Model LastRound.IsCritical[enemy] = true; } + List characters = [actor, enemy]; bool isEvaded = damageResult == DamageResult.Evaded; Dictionary totalDamageBonus = []; - List effects = [.. actor.Effects.Union(enemy.Effects).Where(e => e.Level > 0)]; + List effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)).Distinct()]; foreach (Effect effect in effects) { double damageBonus = effect.AlterActualDamageAfterCalculation(actor, enemy, damage, isNormalAttack, isMagicDamage, magicType, damageResult, ref isEvaded, totalDamageBonus); @@ -1274,43 +1346,159 @@ namespace Milimoe.FunGame.Core.Model damage += totalDamageBonus.Sum(kv => kv.Value); // 闪避了就没伤害了 - if (!isEvaded) + if (damageResult != DamageResult.Evaded) { - if (damage < 0) damage = 0; - if (isMagicDamage) + // 计算伤害免疫 + bool ignore = false; + // 技能免疫无法免疫普通攻击,但是魔法免疫和物理免疫可以 + bool isImmune = (isNormalAttack && (enemy.ImmuneType == ImmuneType.All || enemy.ImmuneType == ImmuneType.Physical || enemy.ImmuneType == ImmuneType.Magical)) || + (!isNormalAttack && (enemy.ImmuneType == ImmuneType.All || enemy.ImmuneType == ImmuneType.Physical || enemy.ImmuneType == ImmuneType.Magical || enemy.ImmuneType == ImmuneType.Skilled)); + if (isImmune) { - string dmgType = CharacterSet.GetMagicDamageName(magicType); - WriteLine("[ " + enemy + $" ] 受到了 {damage:0.##} 点{dmgType}!"); + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)).Distinct()]; + foreach (Effect effect in effects) + { + if (isNormalAttack) + { + if (actor.NormalAttack.IgnoreImmune == ImmuneType.All || + (!isMagicDamage && actor.NormalAttack.IgnoreImmune == ImmuneType.Physical) || + (isMagicDamage && actor.NormalAttack.IgnoreImmune == ImmuneType.Magical) || + !effect.OnDamageImmuneCheck(actor, enemy, isNormalAttack, isMagicDamage, magicType, damage)) + { + ignore = true; + } + } + else + { + if (!effect.OnDamageImmuneCheck(actor, enemy, isNormalAttack, isMagicDamage, magicType, damage)) + { + ignore = true; + } + } + } } - else WriteLine("[ " + enemy + $" ] 受到了 {damage:0.##} 点物理伤害!"); - enemy.HP -= damage; - // 统计伤害 - CalculateCharacterDamageStatistics(actor, enemy, damage, isMagicDamage); - - // 计算助攻 - _assistDamage[actor][enemy] += damage; - - // 造成伤害和受伤都可以获得能量 - double ep = GetEP(damage, GameplayEquilibriumConstant.DamageGetEPFactor, GameplayEquilibriumConstant.DamageGetEPMax); - effects = [.. actor.Effects]; - foreach (Effect effect in effects) + if (ignore) { - effect.AlterEPAfterDamage(actor, ref ep); + // 无视免疫 + isImmune = false; } - actor.EP += ep; - ep = GetEP(damage, GameplayEquilibriumConstant.TakenDamageGetEPFactor, GameplayEquilibriumConstant.TakenDamageGetEPMax); - effects = [.. enemy.Effects.Where(e => e.Level > 0)]; - foreach (Effect effect in effects) + + if (isImmune) { - effect.AlterEPAfterGetDamage(enemy, ref ep); + // 免疫 + LastRound.IsImmune[enemy] = true; + WriteLine($"[ {enemy} ] 免疫了此伤害!"); } - enemy.EP += ep; + else + { + if (damage < 0) damage = 0; + + // 检查护盾 + double shield = enemy.Shield[isMagicDamage, magicType]; + string shieldMsg = ""; + if (shield > 0) + { + bool change = false; + + // 看特效有没有特殊护盾逻辑 + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)).Distinct()]; + foreach (Effect effect in effects) + { + if (!effect.BeforeShieldCalculation(actor, enemy, isMagicDamage, magicType, damage, shield, ref shieldMsg)) + { + change = true; + } + } + + if (!change) + { + double remain = shield - damage; + if (remain < 0) + { + remain = Math.Abs(remain); + enemy.Shield[isMagicDamage, magicType] = 0; + + change = false; + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)).Distinct()]; + foreach (Effect effect in effects) + { + if (!effect.OnShieldBroken(actor, enemy, isMagicDamage, magicType, damage, shield, remain)) + { + change = true; + } + } + + if (!change) + { + enemy.HP -= remain; + shieldMsg = $"(护盾抵消了 {shield:0.##} 点并破碎,角色承受了 {remain:0.##} 点)"; + } + else + { + shieldMsg = $"(护盾抵消了 {shield:0.##} 点并破碎,角色没有承受伤害)"; + } + } + else + { + enemy.Shield[isMagicDamage, magicType] = remain; + shieldMsg = $"(护盾抵消了 {damage:0.##} 点,剩余可用 {remain:0.##} 点)"; + } + } + else if (shieldMsg.Trim() == "") + { + shieldMsg = $"(护盾已使其无效化)"; + } + } + else enemy.HP -= damage; + + if (isMagicDamage) + { + string dmgType = CharacterSet.GetMagicDamageName(magicType); + WriteLine($"[ {enemy} ] 受到了 {damage:0.##} 点{dmgType}!{shieldMsg}"); + } + else WriteLine($"[ {enemy} ] 受到了 {damage:0.##} 点物理伤害!{shieldMsg}"); + + // 生命偷取 + double steal = damage * actor.Lifesteal; + await HealToTargetAsync(actor, actor, steal, false); + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)).Distinct()]; + foreach (Effect effect in effects) + { + effect.AfterLifesteal(actor, enemy, damage, steal); + } + + // 造成伤害和受伤都可以获得能量 + double ep = GetEP(damage, GameplayEquilibriumConstant.DamageGetEPFactor, GameplayEquilibriumConstant.DamageGetEPMax); + effects = [.. actor.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)]; + foreach (Effect effect in effects) + { + effect.AlterEPAfterDamage(actor, ref ep); + } + actor.EP += ep; + ep = GetEP(damage, GameplayEquilibriumConstant.TakenDamageGetEPFactor, GameplayEquilibriumConstant.TakenDamageGetEPMax); + effects = [.. enemy.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)]; + foreach (Effect effect in effects) + { + effect.AlterEPAfterGetDamage(enemy, ref ep); + } + enemy.EP += ep; + + // 统计伤害 + CalculateCharacterDamageStatistics(actor, enemy, damage, isMagicDamage); + + // 计算助攻 + _assistDetail[actor][enemy, TotalTime] += damage; + } + } + else + { + LastRound.IsEvaded[enemy] = true; } await OnDamageToEnemyAsync(actor, enemy, damage, isNormalAttack, isMagicDamage, magicType, damageResult); - effects = [.. actor.Effects.Union(enemy.Effects).Where(e => e.Level > 0)]; + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)).Distinct()]; foreach (Effect effect in effects) { effect.AfterDamageCalculation(actor, enemy, damage, isNormalAttack, isMagicDamage, magicType, damageResult); @@ -1340,7 +1528,24 @@ namespace Milimoe.FunGame.Core.Model bool isDead = target.HP <= 0; - if (heal < 0) heal = 0; + Dictionary totalHealBonus = []; + List effects = [.. actor.Effects.Union(target.Effects).Distinct().Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)]; + foreach (Effect effect in effects) + { + bool changeCanRespawn = false; + double healBonus = effect.AlterHealValueBeforeHealToTarget(actor, target, heal, ref changeCanRespawn, totalHealBonus); + if (changeCanRespawn && !canRespawn) + { + canRespawn = true; + } + } + heal += totalHealBonus.Sum(kv => kv.Value); + + if (heal <= 0) + { + return; + } + if (target.HP > 0 || (isDead && canRespawn)) { target.HP += heal; @@ -1361,13 +1566,22 @@ namespace Milimoe.FunGame.Core.Model { WriteLine($"[ {target} ] 复苏了,并回复了 {heal:0.##} 点生命值!!"); } - await SetCharacterRespawn(target); + await SetCharacterRespawn(target); } else { WriteLine($"[ {target} ] 回复了 {heal:0.##} 点生命值!"); } + // 添加助攻 + SetNotDamageAssistTime(actor, target); + + // 统计数据 + if (_stats.TryGetValue(actor, out CharacterStatistics? stats) && stats != null) + { + stats.TotalHeal += heal; + } + await OnHealToTargetAsync(actor, target, heal, isRespawn); } @@ -1390,24 +1604,29 @@ namespace Milimoe.FunGame.Core.Model /// /// /// + /// /// - public DamageResult CalculatePhysicalDamage(Character actor, Character enemy, bool isNormalAttack, double expectedDamage, out double finalDamage) + public DamageResult CalculatePhysicalDamage(Character actor, Character enemy, bool isNormalAttack, double expectedDamage, out double finalDamage, ref int changeCount) { List characters = [actor, enemy]; bool isMagic = false; MagicType magicType = MagicType.None; - List effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0))]; - foreach (Effect effect in effects) + List effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)).Distinct()]; + if (changeCount < 3) { - effect.AlterDamageTypeBeforeCalculation(actor, enemy, ref isNormalAttack, ref isMagic, ref magicType); - } - if (isMagic) - { - return CalculateMagicalDamage(actor, enemy, isNormalAttack, magicType, expectedDamage, out finalDamage); + foreach (Effect effect in effects) + { + effect.AlterDamageTypeBeforeCalculation(actor, enemy, ref isNormalAttack, ref isMagic, ref magicType); + } + if (isMagic) + { + changeCount++; + return CalculateMagicalDamage(actor, enemy, isNormalAttack, magicType, expectedDamage, out finalDamage, ref changeCount); + } } Dictionary totalDamageBonus = []; - effects = [.. actor.Effects.Union(enemy.Effects).Where(e => e.Level > 0)]; + effects = [.. actor.Effects.Union(enemy.Effects).Distinct().Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)]; foreach (Effect effect in effects) { double damageBonus = effect.AlterExpectedDamageBeforeCalculation(actor, enemy, expectedDamage, isNormalAttack, false, MagicType.None, totalDamageBonus); @@ -1421,7 +1640,7 @@ namespace Milimoe.FunGame.Core.Model bool checkCritical = true; if (isNormalAttack) { - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0))]; + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)).Distinct()]; foreach (Effect effect in effects) { checkEvade = effect.BeforeEvadeCheck(actor, enemy, ref throwingBonus); @@ -1429,12 +1648,12 @@ namespace Milimoe.FunGame.Core.Model if (checkEvade) { - // 闪避判定 + // 闪避检定 if (dice < (enemy.EvadeRate + throwingBonus)) { finalDamage = 0; bool isAlterEvaded = false; - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0))]; + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)).Distinct()]; foreach (Effect effect in effects) { if (effect.OnEvadedTriggered(actor, enemy, dice)) @@ -1460,8 +1679,8 @@ namespace Milimoe.FunGame.Core.Model // 最终的物理伤害 finalDamage = expectedDamage * (1 - Calculation.PercentageCheck(physicalDamageReduction + enemy.ExPDR)); - // 暴击判定 - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0))]; + // 暴击检定 + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)).Distinct()]; foreach (Effect effect in effects) { checkCritical = effect.BeforeCriticalCheck(actor, enemy, ref throwingBonus); @@ -1474,7 +1693,7 @@ namespace Milimoe.FunGame.Core.Model { finalDamage *= actor.CritDMG; // 暴击伤害倍率加成 WriteLine("暴击生效!!"); - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0))]; + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)).Distinct()]; foreach (Effect effect in effects) { effect.OnCriticalDamageTriggered(actor, enemy, dice); @@ -1496,23 +1715,28 @@ namespace Milimoe.FunGame.Core.Model /// /// /// + /// /// - public DamageResult CalculateMagicalDamage(Character actor, Character enemy, bool isNormalAttack, MagicType magicType, double expectedDamage, out double finalDamage) + public DamageResult CalculateMagicalDamage(Character actor, Character enemy, bool isNormalAttack, MagicType magicType, double expectedDamage, out double finalDamage, ref int changeCount) { List characters = [actor, enemy]; bool isMagic = true; - List effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0))]; - foreach (Effect effect in effects) + List effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)).Distinct()]; + if (changeCount < 3) { - effect.AlterDamageTypeBeforeCalculation(actor, enemy, ref isNormalAttack, ref isMagic, ref magicType); - } - if (!isMagic) - { - return CalculatePhysicalDamage(actor, enemy, isNormalAttack, expectedDamage, out finalDamage); + foreach (Effect effect in effects) + { + effect.AlterDamageTypeBeforeCalculation(actor, enemy, ref isNormalAttack, ref isMagic, ref magicType); + } + if (!isMagic) + { + changeCount++; + return CalculatePhysicalDamage(actor, enemy, isNormalAttack, expectedDamage, out finalDamage, ref changeCount); + } } Dictionary totalDamageBonus = []; - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0))]; + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)).Distinct()]; foreach (Effect effect in effects) { double damageBonus = effect.AlterExpectedDamageBeforeCalculation(actor, enemy, expectedDamage, isNormalAttack, true, magicType, totalDamageBonus); @@ -1526,7 +1750,7 @@ namespace Milimoe.FunGame.Core.Model bool checkCritical = true; if (isNormalAttack) { - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0))]; + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)).Distinct()]; foreach (Effect effect in effects) { checkEvade = effect.BeforeEvadeCheck(actor, enemy, ref throwingBonus); @@ -1534,12 +1758,12 @@ namespace Milimoe.FunGame.Core.Model if (checkEvade) { - // 闪避判定 + // 闪避检定 if (dice < (enemy.EvadeRate + throwingBonus)) { finalDamage = 0; bool isAlterEvaded = false; - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0))]; + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)).Distinct()]; foreach (Effect effect in effects) { if (effect.OnEvadedTriggered(actor, enemy, dice)) @@ -1556,18 +1780,7 @@ namespace Milimoe.FunGame.Core.Model } } - double MDF = magicType switch - { - MagicType.Starmark => enemy.MDF.Starmark, - MagicType.PurityNatural => enemy.MDF.PurityNatural, - MagicType.PurityContemporary => enemy.MDF.PurityContemporary, - MagicType.Bright => enemy.MDF.Bright, - MagicType.Shadow => enemy.MDF.Shadow, - MagicType.Element => enemy.MDF.Element, - MagicType.Fleabane => enemy.MDF.Fleabane, - MagicType.Particle => enemy.MDF.Particle, - _ => enemy.MDF.None - }; + double MDF = enemy.MDF[magicType]; // 魔法穿透后的魔法抗性 MDF = (1 - actor.MagicalPenetration) * MDF; @@ -1575,8 +1788,8 @@ namespace Milimoe.FunGame.Core.Model // 最终的魔法伤害 finalDamage = expectedDamage * (1 - MDF); - // 暴击判定 - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0))]; + // 暴击检定 + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)).Distinct()]; foreach (Effect effect in effects) { checkCritical = effect.BeforeCriticalCheck(actor, enemy, ref throwingBonus); @@ -1589,7 +1802,7 @@ namespace Milimoe.FunGame.Core.Model { finalDamage *= actor.CritDMG; // 暴击伤害倍率加成 WriteLine("暴击生效!!"); - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0))]; + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)).Distinct()]; foreach (Effect effect in effects) { effect.OnCriticalDamageTriggered(actor, enemy, dice); @@ -1610,13 +1823,13 @@ namespace Milimoe.FunGame.Core.Model { foreach (Character death in _roundDeaths) { - if(!await OnCharacterDeathAsync(character, death)) + if (!await OnCharacterDeathAsync(character, death)) { continue; } // 给所有角色的特效广播角色死亡结算 - List effects = [.. _queue.SelectMany(c => c.Effects.Where(e => e.Level > 0))]; + List effects = [.. _queue.SelectMany(c => c.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled))]; foreach (Effect effect in effects) { effect.AfterDeathCalculation(death, character, _continuousKilling, _earnedMoney); @@ -1716,14 +1929,17 @@ namespace Milimoe.FunGame.Core.Model _stats[death].Deaths += 1; int money = Random.Shared.Next(250, 350); - Character[] assists = [.. _assistDamage.Keys.Where(c => c != death && _assistDamage[c].GetPercentage(death) > 0.10)]; - double totalDamagePercentage = _assistDamage.Keys.Where(assists.Contains).Select(c => _assistDamage[c].GetPercentage(death)).Sum(); + // 按伤害比分配金钱 只有造成 10% 伤害以上并且是在 30 秒内造成的伤害才能参与 + // 现在 20 秒内的非伤害类型辅助也能参与助攻了 + Character[] assists = [.. _assistDetail.Keys.Where(c => c != death && _assistDetail[c].GetPercentage(death) > 0.10 && + (_assistDetail[c].GetLastTime(death) - TotalTime <= 30 || _assistDetail[c].GetNotDamageAssistLastTime(killer) - TotalTime <= 20))]; + double totalDamagePercentage = _assistDetail.Keys.Where(assists.Contains).Select(c => _assistDetail[c].GetPercentage(death)).Sum(); int totalMoney = Math.Min(Convert.ToInt32(money * totalDamagePercentage), 425); // 防止刷伤害设置金钱上限 - // 按伤害比分配金钱 只有造成10%伤害以上才能参与 + // 分配金钱和累计助攻 foreach (Character assist in assists) { - int cmoney = Convert.ToInt32(_assistDamage[assist].GetPercentage(death) / totalDamagePercentage * totalMoney); + int cmoney = Convert.ToInt32(_assistDetail[assist].GetPercentage(death) / totalDamagePercentage * totalMoney); if (assist != killer) { if (!_earnedMoney.TryAdd(assist, cmoney)) _earnedMoney[assist] += cmoney; @@ -1765,7 +1981,9 @@ namespace Milimoe.FunGame.Core.Model _stats[killer].FirstKills += 1; _stats[death].FirstDeaths += 1; money += 200; - WriteLine($"[ {killer} ] 拿下了第一滴血!额外奖励 200 {GameplayEquilibriumConstant.InGameCurrency}!!"); + string firstKill = $"[ {killer} ] 拿下了第一滴血!额外奖励 200 {GameplayEquilibriumConstant.InGameCurrency}!!"; + WriteLine(firstKill); + LastRound.ActorContinuousKilling.Add(firstKill); } int kills = _continuousKilling[killer]; @@ -1807,10 +2025,10 @@ namespace Milimoe.FunGame.Core.Model death.EP = 0; // 清除对死者的助攻数据 - List ads = [.. _assistDamage.Values.Where(ad => ad.Character != death)]; + List ads = [.. _assistDetail.Values.Where(ad => ad.Character != death)]; foreach (AssistDetail ad in ads) { - ad[death] = 0; + ad[death, 0] = 0; } _continuousKilling.Remove(death); @@ -1826,7 +2044,7 @@ namespace Milimoe.FunGame.Core.Model else { // 进入复活倒计时 - double respawnTime = Calculation.Round2Digits(Math.Min(90, death.Level * 0.15 + times * 2.77 + coefficient * Random.Shared.Next(1, 3))); + double respawnTime = Calculation.Round2Digits(Math.Min(30, death.Level * 0.15 + times * 0.87 + coefficient)); _respawnCountdown.TryAdd(death, respawnTime); LastRound.RespawnCountdowns.TryAdd(death, respawnTime); WriteLine($"[ {death} ] 进入复活倒计时:{respawnTime:0.##} {GameplayEquilibriumConstant.InGameTime}!"); @@ -2037,7 +2255,7 @@ namespace Milimoe.FunGame.Core.Model } return false; } - + /// /// 检查是否可以释放技能(物品版) /// @@ -2114,14 +2332,14 @@ namespace Milimoe.FunGame.Core.Model skill = target.Skill; _castingSkills.Remove(caster); } - else if (_castingSuperSkills.TryGetValue(caster, out skill)) + if (skill is null && caster.CharacterState == CharacterState.PreCastSuperSkill) { - _castingSuperSkills.Remove(caster); + WriteLine($"因 [ {caster} ] 的预释放爆发技状态不可驱散,[ {interrupter} ] 打断失败!!"); } if (skill != null) { - WriteLine($"[ {caster} ] 的{(skill.IsSuperSkill ? "预释放爆发技" : "施法")}被 [ {interrupter} ] 打断了!!"); - List effects = [.. caster.Effects.Union(interrupter.Effects).Where(e => e.Level > 0)]; + WriteLine($"[ {caster} ] 的施法被 [ {interrupter} ] 打断了!!"); + List effects = [.. caster.Effects.Union(interrupter.Effects).Distinct().Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)]; foreach (Effect effect in effects) { effect.OnSkillCastInterrupted(caster, skill, interrupter); @@ -2129,7 +2347,7 @@ namespace Milimoe.FunGame.Core.Model await OnInterruptCastingAsync(caster, skill, interrupter); } } - + /// /// 打断施法 [ 用于使敌人目标丢失 ] /// @@ -2143,7 +2361,7 @@ namespace Milimoe.FunGame.Core.Model { Skill skill = skillTarget.Skill; WriteLine($"[ {interrupter} ] 打断了 [ {caster} ] 的施法!!"); - List effects = [.. caster.Effects.Union(interrupter.Effects).Where(e => e.Level > 0)]; + List effects = [.. caster.Effects.Union(interrupter.Effects).Distinct().Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)]; foreach (Effect effect in effects) { effect.OnSkillCastInterrupted(caster, skill, interrupter); @@ -2207,13 +2425,13 @@ namespace Milimoe.FunGame.Core.Model { if (isMagic) { - stats.TotalMagicDamage = Calculation.Round2Digits(stats.TotalMagicDamage + damage); + stats.TotalMagicDamage += damage; } else { - stats.TotalPhysicalDamage = Calculation.Round2Digits(stats.TotalPhysicalDamage + damage); + stats.TotalPhysicalDamage += damage; } - stats.TotalDamage = Calculation.Round2Digits(stats.TotalDamage + damage); + stats.TotalDamage += damage; } if (_stats.TryGetValue(characterTaken, out CharacterStatistics? statsTaken) && statsTaken != null) { @@ -2305,41 +2523,47 @@ namespace Milimoe.FunGame.Core.Model } if (targets.Count > 0) { - LastRound.Targets = [.. targets]; + // 免疫检定 + await CheckSkilledImmuneAsync(character, targets, skill, item); - await OnCharacterUseItemAsync(character, item, targets); - - string line = $"[ {character} ] 使用了物品 [ {item.Name} ]!\r\n[ {character} ] "; - - skill.OnSkillCasting(this, character, targets); - skill.BeforeSkillCasted(); - - skill.CurrentCD = skill.RealCD; - skill.Enable = false; - - if (costMP > 0) + if (targets.Count > 0) { - character.MP -= costMP; - LastRound.SkillCost = $"{-costMP:0.##} MP"; - line += $"消耗了 {costMP:0.##} 点魔法值,"; + LastRound.Targets = [.. targets]; + + await OnCharacterUseItemAsync(character, item, targets); + + string line = $"[ {character} ] 使用了物品 [ {item.Name} ]!\r\n[ {character} ] "; + + skill.OnSkillCasting(this, character, targets); + skill.BeforeSkillCasted(); + + skill.CurrentCD = skill.RealCD; + skill.Enable = false; + + if (costMP > 0) + { + character.MP -= costMP; + LastRound.SkillCost = $"{-costMP:0.##} MP"; + line += $"消耗了 {costMP:0.##} 点魔法值,"; + } + + if (costEP > 0) + { + character.EP -= costEP; + if (LastRound.SkillCost != "") LastRound.SkillCost += " / "; + LastRound.SkillCost += $"{-costEP:0.##} EP"; + line += $"消耗了 {costEP:0.##} 点能量,"; + } + + line += $"释放了物品技能 [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}"; + WriteLine(line); + + SkillTarget skillTarget = new(skill, targets); + await OnCharacterCastItemSkillAsync(character, item, skillTarget, costMP, costEP); + + skill.OnSkillCasted(this, character, targets); + return true; } - - if (costEP > 0) - { - character.EP -= costEP; - if (LastRound.SkillCost != "") LastRound.SkillCost += " / "; - LastRound.SkillCost += $"{-costEP:0.##} EP"; - line += $"消耗了 {costEP:0.##} 点能量,"; - } - - line += $"释放了物品技能 [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}"; - WriteLine(line); - - SkillTarget skillTarget = new(skill, targets); - await OnCharacterCastItemSkillAsync(character, item, skillTarget, costMP, costEP); - - skill.OnSkillCasted(this, character, targets); - return true; } } } @@ -2378,16 +2602,26 @@ namespace Milimoe.FunGame.Core.Model character.CharacterState = CharacterState.PreCastSuperSkill; _queue.Remove(character); _cutCount.Remove(character); - AddCharacter(character, 0, false); - await OnQueueUpdatedAsync(_queue, character, 0, QueueUpdatedReason.PreCastSuperSkill, "设置角色预释放爆发技的硬直时间。"); WriteLine("[ " + character + " ] 预释放了爆发技!!"); + int preCastSSCount = 0; + double baseHardnessTime = 0; foreach (Character c in _hardnessTimes.Keys) { - if (_hardnessTimes[c] != 0) + if (c.CharacterState != CharacterState.PreCastSuperSkill) { _hardnessTimes[c] = Calculation.Round2Digits(_hardnessTimes[c] + 0.01); } + else if (c != character) + { + if (preCastSSCount == 0) + { + baseHardnessTime = _hardnessTimes[c]; + } + preCastSSCount++; + } } + AddCharacter(character, Calculation.Round2Digits(baseHardnessTime + preCastSSCount * 0.01), false); + await OnQueueUpdatedAsync(_queue, character, 0, QueueUpdatedReason.PreCastSuperSkill, "设置角色预释放爆发技的硬直时间。"); skill.OnSkillCasting(this, character, []); } } @@ -2486,10 +2720,7 @@ namespace Milimoe.FunGame.Core.Model skill.GamingQueue = this; skill.Character = character; skill.Level = 1; - Skill record = skill.Copy(); - record.Character = character; - record.Level = skill.Level; - LastRound.RoundRewards.Add(record); + LastRound.RoundRewards.Add(skill); WriteLine($"[ {character} ] 获得了回合奖励!{skill.Description}".Trim()); if (skill.IsActive) { @@ -2522,10 +2753,24 @@ namespace Milimoe.FunGame.Core.Model character.Effects.Remove(e); } character.Skills.Remove(skill); - skill.Character = null; } } + /// + /// 修改角色的硬直时间 + /// + /// 角色 + /// 加值 + /// 是否使用插队保护机制 + public void ChangeCharacterHardnessTime(Character character, double addValue, bool isCheckProtected) + { + double hardnessTime = _hardnessTimes[character]; + hardnessTime += addValue; + if (hardnessTime <= 0) hardnessTime = 0; + _queue.Remove(character); + AddCharacter(character, hardnessTime, isCheckProtected); + } + /// /// 选取技能目标 /// @@ -2568,6 +2813,62 @@ namespace Milimoe.FunGame.Core.Model return targets; } + /// + /// 设置角色对目标们的非伤害辅助时间 + /// + /// + /// + public void SetNotDamageAssistTime(Character character, params IEnumerable targets) + { + foreach (Character target in targets) + { + _assistDetail[character].NotDamageAssistLastTime[target] = TotalTime; + } + } + + /// + /// 免疫检定 + /// + /// + /// + /// + /// + /// + public virtual async Task CheckSkilledImmuneAsync(Character character, List targets, Skill skill, Item? item = null) + { + Character[] loop = [.. targets]; + foreach (Character target in loop) + { + bool ignore = false; + bool isImmune = target.ImmuneType == ImmuneType.Magical || target.ImmuneType == ImmuneType.Skilled || target.ImmuneType == ImmuneType.All; + if (isImmune) + { + Character[] characters = [character, target]; + Effect[] effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0 && !e.IsBeingTemporaryDispelled)).Distinct()]; + foreach (Effect effect in effects) + { + // 自带无视免疫或者特效免疫检定不通过可无视免疫 + if (effect.IgnoreImmune == ImmuneType.All || effect.IgnoreImmune == ImmuneType.Skilled || (skill.IsMagic && effect.IgnoreImmune == ImmuneType.Magical) || !effect.OnImmuneCheck(character, target, skill, item)) + { + ignore = true; + } + } + } + if (ignore) + { + isImmune = false; + targets.Remove(target); + } + if (isImmune) + { + WriteLine($"[ {target} ] 免疫了此技能!"); + await OnCharacterImmunedAsync(character, target, skill, item); + } + } + await Task.CompletedTask; + return; + } + #region 事件 public delegate Task TurnStartEventHandler(ActionQueue queue, Character character, List enemys, List teammates, List skills, List items); @@ -2723,7 +3024,7 @@ namespace Milimoe.FunGame.Core.Model { return await (DeathCalculation?.Invoke(this, killer, death) ?? Task.FromResult(true)); } - + public delegate Task CharacterDeathEventHandler(ActionQueue queue, Character current, Character death); /// /// 角色死亡事件,此事件位于 之后 @@ -2794,7 +3095,7 @@ namespace Milimoe.FunGame.Core.Model { await (CharacterNormalAttack?.Invoke(this, actor, targets) ?? Task.CompletedTask); } - + public delegate Task CharacterPreCastSkillEventHandler(ActionQueue queue, Character actor, SkillTarget skillTarget); /// /// 角色吟唱技能事件(包括直接释放战技) @@ -2810,7 +3111,7 @@ namespace Milimoe.FunGame.Core.Model { await (CharacterPreCastSkill?.Invoke(this, actor, skillTarget) ?? Task.CompletedTask); } - + public delegate Task CharacterCastSkillEventHandler(ActionQueue queue, Character actor, SkillTarget skillTarget, double cost); /// /// 角色释放技能事件 @@ -2827,7 +3128,7 @@ namespace Milimoe.FunGame.Core.Model { await (CharacterCastSkill?.Invoke(this, actor, skillTarget, cost) ?? Task.CompletedTask); } - + public delegate Task CharacterUseItemEventHandler(ActionQueue queue, Character actor, Item item, List targets); /// /// 角色使用物品事件 @@ -2844,7 +3145,7 @@ namespace Milimoe.FunGame.Core.Model { await (CharacterUseItem?.Invoke(this, actor, item, targets) ?? Task.CompletedTask); } - + public delegate Task CharacterCastItemSkillEventHandler(ActionQueue queue, Character actor, Item item, SkillTarget skillTarget, double costMP, double costEP); /// /// 角色释放物品的技能事件 @@ -2863,7 +3164,25 @@ namespace Milimoe.FunGame.Core.Model { await (CharacterCastItemSkill?.Invoke(this, actor, item, skillTarget, costMP, costEP) ?? Task.CompletedTask); } - + + public delegate Task CharacterImmunedEventHandler(ActionQueue queue, Character character, Character immune, ISkill skill, Item? item = null); + /// + /// 角色免疫事件 + /// + public event CharacterImmunedEventHandler? CharacterImmuned; + /// + /// 角色免疫事件 + /// + /// + /// + /// + /// + /// + protected async Task OnCharacterImmunedAsync(Character character, Character immune, ISkill skill, Item? item = null) + { + await (CharacterImmuned?.Invoke(this, character, immune, skill, item) ?? Task.CompletedTask); + } + public delegate Task CharacterDoNothingEventHandler(ActionQueue queue, Character actor); /// /// 角色主动结束回合事件(区别于放弃行动,这个是主动的) @@ -2878,7 +3197,7 @@ namespace Milimoe.FunGame.Core.Model { await (CharacterDoNothing?.Invoke(this, actor) ?? Task.CompletedTask); } - + public delegate Task CharacterGiveUpEventHandler(ActionQueue queue, Character actor); /// /// 角色放弃行动事件 @@ -2908,7 +3227,7 @@ namespace Milimoe.FunGame.Core.Model { return await (GameEnd?.Invoke(this, winner) ?? Task.FromResult(true)); } - + public delegate Task GameEndTeamEventHandler(ActionQueue queue, Team winner); /// /// 游戏结束事件(团队版) diff --git a/Service/JsonManager.cs b/Service/JsonManager.cs index 4a00757..3143c03 100644 --- a/Service/JsonManager.cs +++ b/Service/JsonManager.cs @@ -22,7 +22,7 @@ namespace Milimoe.FunGame.Core.Service Converters = { new DateTimeConverter(), new DataTableConverter(), new DataSetConverter(), new UserConverter(), new RoomConverter(), new CharacterConverter(), new MagicResistanceConverter(), new EquipSlotConverter(), new SkillConverter(), new EffectConverter(), new ItemConverter(), new InventoryConverter(), new NormalAttackConverter(), new ClubConverter(), new GoodsConverter(), new StoreConverter(), - new NovelOptionConverter(), new NovelNodeConverter() + new NovelOptionConverter(), new NovelNodeConverter(), new ShieldConverter() } };