diff --git a/Controller/AIController.cs b/Controller/AIController.cs index 7588ba7..459c930 100644 --- a/Controller/AIController.cs +++ b/Controller/AIController.cs @@ -25,7 +25,7 @@ namespace Milimoe.FunGame.Core.Controller /// 场上能够选取的敌人 /// 场上能够选取的队友 /// 包含最佳行动的AIDecision对象 - public async Task DecideAIActionAsync(Character character, DecisionPoints dp, Grid startGrid, List allPossibleMoveGrids, + public AIDecision DecideAIAction(Character character, DecisionPoints dp, Grid startGrid, List allPossibleMoveGrids, List availableSkills, List availableItems, List allEnemysInGame, List allTeammatesInGame, List selectableEnemys, List selectableTeammates) { @@ -86,31 +86,43 @@ namespace Milimoe.FunGame.Core.Controller // 计算当前技能的可达格子 List skillReachableGrids = _map.GetGridsByRange(potentialMoveGrid, skill.CastRange, true); - List skillReachableEnemys = [.. allEnemysInGame + if (skill.IsNonDirectional) + { + AIDecision? nonDirDecision = EvaluateNonDirectionalSkill(character, skill, potentialMoveGrid, skillReachableGrids, allEnemysInGame, allTeammatesInGame, cost); + + if (nonDirDecision != null && nonDirDecision.Score > bestDecision.Score) + { + bestDecision = nonDirDecision; + } + } + else + { + List skillReachableEnemys = [.. allEnemysInGame .Where(c => skillReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c)) .Distinct()]; - List skillReachableTeammates = [.. allTeammatesInGame + List skillReachableTeammates = [.. allTeammatesInGame .Where(c => skillReachableGrids.SelectMany(g => g.Characters).Contains(c) && selectableTeammates.Contains(c)) .Distinct()]; - // 检查是否有可用的目标(敌人或队友,取决于技能类型) - if (skillReachableEnemys.Count > 0 || skillReachableTeammates.Count > 0) - { - // 将筛选后的目标列表传递给 SelectTargets - List targets = SelectTargets(character, skill, skillReachableEnemys, skillReachableTeammates); - if (targets.Count > 0) + // 检查是否有可用的目标(敌人或队友,取决于技能类型) + if (skillReachableEnemys.Count > 0 || skillReachableTeammates.Count > 0) { - double currentScore = EvaluateSkill(character, skill, targets, cost) - movePenalty; - if (currentScore > bestDecision.Score) + // 将筛选后的目标列表传递给 SelectTargets + List targets = SelectTargets(character, skill, skillReachableEnemys, skillReachableTeammates); + if (targets.Count > 0) { - bestDecision = new AIDecision + double currentScore = EvaluateSkill(character, skill, targets, cost) - movePenalty; + if (currentScore > bestDecision.Score) { - ActionType = CharacterActionType.PreCastSkill, - TargetMoveGrid = potentialMoveGrid, - SkillToUse = skill, - Targets = targets, - Score = currentScore - }; + bestDecision = new AIDecision + { + ActionType = CharacterActionType.PreCastSkill, + TargetMoveGrid = potentialMoveGrid, + SkillToUse = skill, + Targets = targets, + Score = currentScore + }; + } } } } @@ -126,32 +138,44 @@ namespace Milimoe.FunGame.Core.Controller // 计算当前物品技能的可达格子 List itemSkillReachableGrids = _map.GetGridsByRange(potentialMoveGrid, itemSkill.CastRange, true); - List itemSkillReachableEnemys = [.. allEnemysInGame + if (itemSkill.IsNonDirectional) + { + AIDecision? nonDirDecision = EvaluateNonDirectionalSkill(character, itemSkill, potentialMoveGrid, itemSkillReachableGrids, allEnemysInGame, allTeammatesInGame, cost); + + if (nonDirDecision != null && nonDirDecision.Score > bestDecision.Score) + { + bestDecision = nonDirDecision; + } + } + else + { + List itemSkillReachableEnemys = [.. allEnemysInGame .Where(c => itemSkillReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c)) .Distinct()]; - List itemSkillReachableTeammates = [.. allTeammatesInGame + List itemSkillReachableTeammates = [.. allTeammatesInGame .Where(c => itemSkillReachableGrids.SelectMany(g => g.Characters).Contains(c) && selectableTeammates.Contains(c)) .Distinct()]; - // 检查是否有可用的目标 - if (itemSkillReachableEnemys.Count > 0 || itemSkillReachableTeammates.Count > 0) - { - // 将筛选后的目标列表传递给 SelectTargets - List targetsForItem = SelectTargets(character, itemSkill, itemSkillReachableEnemys, itemSkillReachableTeammates); - if (targetsForItem.Count > 0) + // 检查是否有可用的目标 + if (itemSkillReachableEnemys.Count > 0 || itemSkillReachableTeammates.Count > 0) { - double currentScore = EvaluateItem(character, item, targetsForItem, cost) - movePenalty; - if (currentScore > bestDecision.Score) + // 将筛选后的目标列表传递给 SelectTargets + List targetsForItem = SelectTargets(character, itemSkill, itemSkillReachableEnemys, itemSkillReachableTeammates); + if (targetsForItem.Count > 0) { - bestDecision = new AIDecision + double currentScore = EvaluateItem(character, item, targetsForItem, cost) - movePenalty; + if (currentScore > bestDecision.Score) { - ActionType = CharacterActionType.UseItem, - TargetMoveGrid = potentialMoveGrid, - ItemToUse = item, - SkillToUse = itemSkill, - Targets = targetsForItem, - Score = currentScore - }; + bestDecision = new AIDecision + { + ActionType = CharacterActionType.UseItem, + TargetMoveGrid = potentialMoveGrid, + ItemToUse = item, + SkillToUse = itemSkill, + Targets = targetsForItem, + Score = currentScore + }; + } } } } @@ -216,7 +240,7 @@ namespace Milimoe.FunGame.Core.Controller } } - return await Task.FromResult(bestDecision); + return bestDecision; } // --- AI 决策辅助方法 --- @@ -251,6 +275,7 @@ namespace Milimoe.FunGame.Core.Controller (character.CharacterState != CharacterState.ActionRestricted || item.ItemType == ItemType.Consumable) && // 行动受限只能用消耗品 character.CharacterState != CharacterState.BattleRestricted; } + /// /// 选择技能的最佳目标 /// @@ -302,6 +327,51 @@ namespace Milimoe.FunGame.Core.Controller return score; } + // 非指向性技能的评估 + private AIDecision? EvaluateNonDirectionalSkill(Character character, Skill skill, Grid moveGrid, List castableGrids, List allEnemys, List allTeammates, double cost) + { + double bestSkillScore = double.NegativeInfinity; + List bestTargetGrids = []; + + // 枚举所有可施放的格子作为潜在中心 + foreach (Grid centerGrid in castableGrids) + { + // 计算该中心格子下的实际影响范围格子 + List effectGrids = skill.SelectNonDirectionalTargets(character, centerGrid, skill.SelectIncludeCharacterGrid); + + // 计算实际影响的角色 + List affected = skill.SelectTargetsByRange(character, allEnemys, allTeammates, [], effectGrids); + + if (affected.Count == 0) + continue; + + // 评估这些影响目标的价值 + double skillScore = affected.Sum(t => CalculateTargetValue(t, skill)); + + if (skillScore > bestSkillScore) + { + bestSkillScore = skillScore; + bestTargetGrids = effectGrids; + } + } + + if (bestSkillScore == double.NegativeInfinity) + return null; // 无有效格子 + + double movePenalty = GameMap.CalculateManhattanDistance(_map.GetCharacterCurrentGrid(character)!, moveGrid) * 0.5; + double finalScore = bestSkillScore - movePenalty; + + return new AIDecision + { + ActionType = CharacterActionType.PreCastSkill, + TargetMoveGrid = moveGrid, + SkillToUse = skill, + Targets = [], + TargetGrids = bestTargetGrids, + Score = finalScore + }; + } + /// /// 评估物品的价值 /// diff --git a/Entity/Character/Character.cs b/Entity/Character/Character.cs index 2eac9a6..f8800ee 100644 --- a/Entity/Character/Character.cs +++ b/Entity/Character/Character.cs @@ -482,7 +482,7 @@ namespace Milimoe.FunGame.Core.Entity /// /// 生命回复力 = [ 与初始设定相关 ] [ 与力量相关 ] + 额外生命回复力 /// - public double HR => InitialHR + STR * GameplayEquilibriumConstant.STRtoHRFactor + ExHR; + public double HR => Math.Max(0, InitialHR + STR * GameplayEquilibriumConstant.STRtoHRFactor + ExHR); /// /// 额外生命回复力 [ 与技能和物品相关 ] @@ -498,7 +498,7 @@ namespace Milimoe.FunGame.Core.Entity /// /// 魔法回复力 = [ 与初始设定相关 ] [ 与智力相关 ] + 额外魔法回复力 /// - public double MR => InitialMR + INT * GameplayEquilibriumConstant.INTtoMRFactor + ExMR; + public double MR => Math.Max(0, InitialMR + INT * GameplayEquilibriumConstant.INTtoMRFactor + ExMR); /// /// 额外魔法回复力 [ 与技能和物品相关 ] @@ -687,6 +687,21 @@ namespace Milimoe.FunGame.Core.Entity [InitOptional] public double INTGrowth { get; set; } = 0; + /// + /// 力量豁免 + /// + public double STRExemption => STR * GameplayEquilibriumConstant.STRtoExemptionRateMultiplier; + + /// + /// 敏捷豁免 + /// + public double AGIExemption => AGI * GameplayEquilibriumConstant.AGItoExemptionRateMultiplier; + + /// + /// 智力豁免 + /// + public double INTExemption => INT * GameplayEquilibriumConstant.INTtoExemptionRateMultiplier; + /// /// 行动速度 [ 初始设定 ] /// @@ -809,7 +824,7 @@ namespace Milimoe.FunGame.Core.Entity _ => baseMOV }; } - return Math.Max(1, baseMOV + ExMOV); + return Math.Max(0, baseMOV + ExMOV); } } @@ -878,6 +893,16 @@ namespace Milimoe.FunGame.Core.Entity /// public Shield Shield { get; set; } + /// + /// 角色是否是单位 [ 初始设定 ] + /// + public virtual bool IsUnit { get; } = false; + + /// + /// 角色所属的上级角色 [ 战斗相关 ] + /// + public Character? Master { get; set; } = null; + /// /// 普通攻击对象 /// @@ -1440,11 +1465,11 @@ namespace Milimoe.FunGame.Core.Entity builder.AppendLine($"行动速度:{SPD:0.##}" + (exSPD != 0 ? $" [{InitialSPD:0.##} {(exSPD >= 0 ? "+" : "-")} {Math.Abs(exSPD):0.##}]" : "") + $" ({ActionCoefficient * 100:0.##}%)"); builder.AppendLine($"核心属性:{CharacterSet.GetPrimaryAttributeName(PrimaryAttribute)}"); double exSTR = ExSTR + ExSTR2; - builder.AppendLine($"力量:{STR:0.##}" + (exSTR != 0 ? $" [{BaseSTR:0.##} {(exSTR >= 0 ? "+" : "-")} {Math.Abs(exSTR):0.##}]" : "") + (showGrowth ? $"({(STRGrowth >= 0 ? "+" : "-")}{Math.Abs(STRGrowth)}/Lv)" : "")); + builder.AppendLine($"力量:{STR:0.##}" + (exSTR != 0 ? $" [{BaseSTR:0.##} {(exSTR >= 0 ? "+" : "-")} {Math.Abs(exSTR):0.##}]" : "") + (showGrowth ? $"({(STRGrowth >= 0 ? "+" : "-")}{Math.Abs(STRGrowth)}/Lv)" : "") + $"({STRExemption * 100:0.##}%)"); double exAGI = ExAGI + ExAGI2; - builder.AppendLine($"敏捷:{AGI:0.##}" + (exAGI != 0 ? $" [{BaseAGI:0.##} {(exAGI >= 0 ? "+" : "-")} {Math.Abs(exAGI):0.##}]" : "") + (showGrowth ? $"({(AGIGrowth >= 0 ? "+" : "-")}{Math.Abs(AGIGrowth)}/Lv)" : "")); + builder.AppendLine($"敏捷:{AGI:0.##}" + (exAGI != 0 ? $" [{BaseAGI:0.##} {(exAGI >= 0 ? "+" : "-")} {Math.Abs(exAGI):0.##}]" : "") + (showGrowth ? $"({(AGIGrowth >= 0 ? "+" : "-")}{Math.Abs(AGIGrowth)}/Lv)" : "") + $"({AGIExemption * 100:0.##}%)"); double exINT = ExINT + ExINT2; - builder.AppendLine($"智力:{INT:0.##}" + (exINT != 0 ? $" [{BaseINT:0.##} {(exINT >= 0 ? "+" : "-")} {Math.Abs(exINT):0.##}]" : "") + (showGrowth ? $"({(INTGrowth >= 0 ? "+" : "-")}{Math.Abs(INTGrowth)}/Lv)" : "")); + builder.AppendLine($"智力:{INT:0.##}" + (exINT != 0 ? $" [{BaseINT:0.##} {(exINT >= 0 ? "+" : "-")} {Math.Abs(exINT):0.##}]" : "") + (showGrowth ? $"({(INTGrowth >= 0 ? "+" : "-")}{Math.Abs(INTGrowth)}/Lv)" : "") + $"({INTExemption * 100:0.##}%)"); builder.AppendLine($"生命回复:{HR:0.##}" + (ExHR != 0 ? $" [{InitialHR + STR * GameplayEquilibriumConstant.STRtoHRFactor:0.##} {(ExHR >= 0 ? "+" : "-")} {Math.Abs(ExHR):0.##}]" : "")); builder.AppendLine($"魔法回复:{MR:0.##}" + (ExMR != 0 ? $" [{InitialMR + INT * GameplayEquilibriumConstant.INTtoMRFactor:0.##} {(ExMR >= 0 ? "+" : "-")} {Math.Abs(ExMR):0.##}]" : "")); builder.AppendLine($"暴击率:{CritRate * 100:0.##}%"); @@ -1474,6 +1499,7 @@ namespace Milimoe.FunGame.Core.Entity builder.AppendLine("== 角色技能 =="); foreach (Skill skill in Skills) { + skill.Character = this; builder.Append(skill.ToString()); } } @@ -1539,11 +1565,11 @@ namespace Milimoe.FunGame.Core.Entity builder.AppendLine($"行动速度:{SPD:0.##}" + (exSPD != 0 ? $" [{InitialSPD:0.##} {(exSPD >= 0 ? "+" : "-")} {Math.Abs(exSPD):0.##}]" : "") + $" ({ActionCoefficient * 100:0.##}%)"); builder.AppendLine($"核心属性:{CharacterSet.GetPrimaryAttributeName(PrimaryAttribute)}"); double exSTR = ExSTR + ExSTR2; - builder.AppendLine($"力量:{STR:0.##}" + (exSTR != 0 ? $" [{BaseSTR:0.##} {(exSTR >= 0 ? "+" : "-")} {Math.Abs(exSTR):0.##}]" : "") + (showGrowth ? $"({(STRGrowth >= 0 ? "+" : "-")}{Math.Abs(STRGrowth)}/Lv)" : "")); + builder.AppendLine($"力量:{STR:0.##}" + (exSTR != 0 ? $" [{BaseSTR:0.##} {(exSTR >= 0 ? "+" : "-")} {Math.Abs(exSTR):0.##}]" : "") + (showGrowth ? $"({(STRGrowth >= 0 ? "+" : "-")}{Math.Abs(STRGrowth)}/Lv)" : "") + $"({STRExemption * 100:0.##}%)"); double exAGI = ExAGI + ExAGI2; - builder.AppendLine($"敏捷:{AGI:0.##}" + (exAGI != 0 ? $" [{BaseAGI:0.##} {(exAGI >= 0 ? "+" : "-")} {Math.Abs(exAGI):0.##}]" : "") + (showGrowth ? $"({(AGIGrowth >= 0 ? "+" : "-")}{Math.Abs(AGIGrowth)}/Lv)" : "")); + builder.AppendLine($"敏捷:{AGI:0.##}" + (exAGI != 0 ? $" [{BaseAGI:0.##} {(exAGI >= 0 ? "+" : "-")} {Math.Abs(exAGI):0.##}]" : "") + (showGrowth ? $"({(AGIGrowth >= 0 ? "+" : "-")}{Math.Abs(AGIGrowth)}/Lv)" : "") + $"({AGIExemption * 100:0.##}%)"); double exINT = ExINT + ExINT2; - builder.AppendLine($"智力:{INT:0.##}" + (exINT != 0 ? $" [{BaseINT:0.##} {(exINT >= 0 ? "+" : "-")} {Math.Abs(exINT):0.##}]" : "") + (showGrowth ? $"({(INTGrowth >= 0 ? "+" : "-")}{Math.Abs(INTGrowth)}/Lv)" : "")); + builder.AppendLine($"智力:{INT:0.##}" + (exINT != 0 ? $" [{BaseINT:0.##} {(exINT >= 0 ? "+" : "-")} {Math.Abs(exINT):0.##}]" : "") + (showGrowth ? $"({(INTGrowth >= 0 ? "+" : "-")}{Math.Abs(INTGrowth)}/Lv)" : "") + $"({INTExemption * 100:0.##}%)"); } builder.AppendLine($"生命回复:{HR:0.##}" + (ExHR != 0 ? $" [{InitialHR + STR * GameplayEquilibriumConstant.STRtoHRFactor:0.##} {(ExHR >= 0 ? "+" : "-")} {Math.Abs(ExHR):0.##}]" : "")); builder.AppendLine($"魔法回复:{MR:0.##}" + (ExMR != 0 ? $" [{InitialMR + INT * GameplayEquilibriumConstant.INTtoMRFactor:0.##} {(ExMR >= 0 ? "+" : "-")} {Math.Abs(ExMR):0.##}]" : "")); @@ -1749,11 +1775,11 @@ namespace Milimoe.FunGame.Core.Entity builder.AppendLine($"行动速度:{SPD:0.##}" + (exSPD != 0 ? $" [{InitialSPD:0.##} {(exSPD >= 0 ? "+" : "-")} {Math.Abs(exSPD):0.##}]" : "") + $" ({ActionCoefficient * 100:0.##}%)"); builder.AppendLine($"核心属性:{CharacterSet.GetPrimaryAttributeName(PrimaryAttribute)}"); double exSTR = ExSTR + ExSTR2; - builder.AppendLine($"力量:{STR:0.##}" + (exSTR != 0 ? $" [{BaseSTR:0.##} {(exSTR >= 0 ? "+" : "-")} {Math.Abs(exSTR):0.##}]" : "") + (showGrowth ? $"({(STRGrowth >= 0 ? "+" : "-")}{Math.Abs(STRGrowth)}/Lv)" : "")); + builder.AppendLine($"力量:{STR:0.##}" + (exSTR != 0 ? $" [{BaseSTR:0.##} {(exSTR >= 0 ? "+" : "-")} {Math.Abs(exSTR):0.##}]" : "") + (showGrowth ? $"({(STRGrowth >= 0 ? "+" : "-")}{Math.Abs(STRGrowth)}/Lv)" : "") + $"({STRExemption * 100:0.##}%)"); double exAGI = ExAGI + ExAGI2; - builder.AppendLine($"敏捷:{AGI:0.##}" + (exAGI != 0 ? $" [{BaseAGI:0.##} {(exAGI >= 0 ? "+" : "-")} {Math.Abs(exAGI):0.##}]" : "") + (showGrowth ? $"({(AGIGrowth >= 0 ? "+" : "-")}{Math.Abs(AGIGrowth)}/Lv)" : "")); + builder.AppendLine($"敏捷:{AGI:0.##}" + (exAGI != 0 ? $" [{BaseAGI:0.##} {(exAGI >= 0 ? "+" : "-")} {Math.Abs(exAGI):0.##}]" : "") + (showGrowth ? $"({(AGIGrowth >= 0 ? "+" : "-")}{Math.Abs(AGIGrowth)}/Lv)" : "") + $"({AGIExemption * 100:0.##}%)"); double exINT = ExINT + ExINT2; - builder.AppendLine($"智力:{INT:0.##}" + (exINT != 0 ? $" [{BaseINT:0.##} {(exINT >= 0 ? "+" : "-")} {Math.Abs(exINT):0.##}]" : "") + (showGrowth ? $"({(INTGrowth >= 0 ? "+" : "-")}{Math.Abs(INTGrowth)}/Lv)" : "")); + builder.AppendLine($"智力:{INT:0.##}" + (exINT != 0 ? $" [{BaseINT:0.##} {(exINT >= 0 ? "+" : "-")} {Math.Abs(exINT):0.##}]" : "") + (showGrowth ? $"({(INTGrowth >= 0 ? "+" : "-")}{Math.Abs(INTGrowth)}/Lv)" : "") + $"({INTExemption * 100:0.##}%)"); builder.AppendLine($"生命回复:{HR:0.##}" + (ExHR != 0 ? $" [{InitialHR + STR * GameplayEquilibriumConstant.STRtoHRFactor:0.##} {(ExHR >= 0 ? "+" : "-")} {Math.Abs(ExHR):0.##}]" : "")); builder.AppendLine($"魔法回复:{MR:0.##}" + (ExMR != 0 ? $" [{InitialMR + INT * GameplayEquilibriumConstant.INTtoMRFactor:0.##} {(ExMR >= 0 ? "+" : "-")} {Math.Abs(ExMR):0.##}]" : "")); builder.AppendLine($"暴击率:{CritRate * 100:0.##}%"); diff --git a/Entity/Character/Unit.cs b/Entity/Character/Unit.cs index 08e75fa..071c163 100644 --- a/Entity/Character/Unit.cs +++ b/Entity/Character/Unit.cs @@ -15,6 +15,11 @@ namespace Milimoe.FunGame.Core.Entity /// public override string Name { get; set; } = ""; + /// + /// 单位标识 + /// + public override bool IsUnit => true; + /// /// 获取单位名称以及所属玩家 /// diff --git a/Entity/Item/Item.cs b/Entity/Item/Item.cs index 932266f..8d5ce3c 100644 --- a/Entity/Item/Item.cs +++ b/Entity/Item/Item.cs @@ -311,7 +311,7 @@ namespace Milimoe.FunGame.Core.Entity /// 局内使用物品触发 /// /// - public async Task UseItem(IGamingQueue queue, Character character, DecisionPoints dp, List enemys, List teammates) + public bool UseItem(IGamingQueue queue, Character character, DecisionPoints dp, List enemys, List teammates, List allEnemys, List allTeammates) { bool cancel = false; bool used = false; @@ -328,7 +328,7 @@ namespace Milimoe.FunGame.Core.Entity Grid? grid = Skills.Active.GamingQueue.Map.GetCharacterCurrentGrid(character); castRange = grid is null ? [] : Skills.Active.GamingQueue.Map.GetGridsByRange(grid, Skills.Active.CastRange, true); } - used = await queue.UseItemAsync(this, character, dp, enemys, teammates, castRange); + used = queue.UseItem(this, character, dp, enemys, teammates, castRange, allEnemys, allTeammates); } if (used) { diff --git a/Entity/Skill/Effect.cs b/Entity/Skill/Effect.cs index b4f20a1..d6801bb 100644 --- a/Entity/Skill/Effect.cs +++ b/Entity/Skill/Effect.cs @@ -136,6 +136,48 @@ namespace Milimoe.FunGame.Core.Entity /// 无视免疫类型 /// public virtual ImmuneType IgnoreImmune { get; set; } = ImmuneType.None; + + /// + /// 豁免性的具体说明 + /// + public virtual string ExemptionDescription + { + get + { + StringBuilder builder = new(); + if (Exemptable) + { + builder.AppendLine($"豁免类型:{CharacterSet.GetPrimaryAttributeName(ExemptionType)}"); + builder.Append($"豁免持续时间:{(ExemptDuration ? "是" : "否")}"); + } + return builder.ToString(); + } + } + + /// + /// 是否可被属性豁免 + /// + public bool Exemptable => ExemptionType != PrimaryAttribute.None; + + /// + /// 豁免所需的属性类型 + /// + public virtual PrimaryAttribute ExemptionType + { + get + { + return _exemptionType ?? SkillSet.GetExemptionTypeByEffectType(EffectType); + } + set + { + _exemptionType = value; + } + } + + /// + /// 可豁免持续时间(每次减半/一回合) + /// + public virtual bool ExemptDuration { get; set; } = false; /// /// 效果描述 @@ -341,11 +383,24 @@ namespace Milimoe.FunGame.Core.Entity /// /// /// - public virtual void OnSkillCasting(Character caster, List targets) + /// + public virtual void OnSkillCasting(Character caster, List targets, List grids) { } + /// + /// 技能吟唱被打断前触发 + /// + /// + /// + /// + /// 返回 false 阻止打断 + public virtual bool BeforeSkillCastWillBeInterrupted(Character caster, Skill skill, Character interrupter) + { + return true; + } + /// /// 技能吟唱被打断时 /// @@ -362,8 +417,9 @@ namespace Milimoe.FunGame.Core.Entity /// /// /// + /// /// - public virtual void OnSkillCasted(Character caster, List targets, Dictionary others) + public virtual void OnSkillCasted(Character caster, List targets, List grids, Dictionary others) { } @@ -379,6 +435,18 @@ namespace Milimoe.FunGame.Core.Entity } + /// + /// 在时间流逝期间应用生命/魔法回复前修改 [ 允许取消回复 ] + /// + /// + /// + /// + /// 返回 true 取消回复 + public virtual bool BeforeApplyRecoveryAtTimeLapsing(Character character, ref double hr, ref double mr) + { + return false; + } + /// /// 时间流逝时 /// @@ -819,58 +887,72 @@ namespace Milimoe.FunGame.Core.Entity } /// - /// 对敌人造成技能伤害 [ 强烈建议使用此方法造成伤害而不是自行调用 ] + /// 在角色取得询问反应的答复时触发 + /// + /// + /// + /// + /// + public virtual void OnCharacterInquiry(Character character, string topic, Dictionary args, Dictionary response) + { + + } + + /// + /// 对敌人造成技能伤害 [ 强烈建议使用此方法造成伤害而不是自行调用 ] /// /// /// /// /// /// + /// /// - public DamageResult DamageToEnemy(Character actor, Character enemy, DamageType damageType, MagicType magicType, double expectedDamage) + public DamageResult DamageToEnemy(Character actor, Character enemy, DamageType damageType, MagicType magicType, double expectedDamage, DamageCalculationOptions? options = null) { if (GamingQueue is null) return DamageResult.Evaded; int changeCount = 0; DamageResult result = DamageResult.Normal; double damage = expectedDamage; - if (damageType != DamageType.True) + options ??= new(); + if (options.NeedCalculate && damageType != DamageType.True) { - result = damageType == DamageType.Physical ? GamingQueue.CalculatePhysicalDamage(actor, enemy, false, expectedDamage, out damage, ref changeCount) : GamingQueue.CalculateMagicalDamage(actor, enemy, false, MagicType, expectedDamage, out damage, ref changeCount); + result = damageType == DamageType.Physical ? GamingQueue.CalculatePhysicalDamage(actor, enemy, false, expectedDamage, out damage, ref changeCount, options) : GamingQueue.CalculateMagicalDamage(actor, enemy, false, MagicType, expectedDamage, out damage, ref changeCount, options); } - // 注意此方法在后台线程运行 - GamingQueue.DamageToEnemyAsync(actor, enemy, damage, false, damageType, magicType, result); + GamingQueue.DamageToEnemy(actor, enemy, damage, false, damageType, magicType, result, options); return result; } /// - /// 治疗一个目标 [ 强烈建议使用此方法而不是自行调用 ] + /// 治疗一个目标 [ 强烈建议使用此方法而不是自行调用 ] /// /// /// /// /// - public void HealToTarget(Character actor, Character target, double heal, bool canRespawn = false) + /// + public void HealToTarget(Character actor, Character target, double heal, bool canRespawn = false, bool triggerEffects = true) { - GamingQueue?.HealToTargetAsync(actor, target, heal, canRespawn); + GamingQueue?.HealToTarget(actor, target, heal, canRespawn, triggerEffects); } /// - /// 打断施法 [ 尽可能的调用此方法而不是直接调用 ,以防止中断性变更 ] + /// 打断施法 [ 尽可能的调用此方法而不是直接调用 ,以防止中断性变更 ] /// /// /// public void InterruptCasting(Character caster, Character interrupter) { - GamingQueue?.InterruptCastingAsync(caster, interrupter); + GamingQueue?.InterruptCasting(caster, interrupter); } /// - /// 打断施法 [ 用于使敌人目标丢失 ] [ 尽可能的调用此方法而不是直接调用 ,以防止中断性变更 ] + /// 打断施法 [ 用于使敌人目标丢失 ] [ 尽可能的调用此方法而不是直接调用 ,以防止中断性变更 ] /// /// public void InterruptCasting(Character interrupter) { - GamingQueue?.InterruptCastingAsync(interrupter); + GamingQueue?.InterruptCasting(interrupter); } /// @@ -1048,7 +1130,7 @@ namespace Milimoe.FunGame.Core.Entity { return; } - Effect[] effects = [.. target.Effects.Where(e => e.IsInEffect && e.ShowInStatusBar)]; + Effect[] effects = [.. target.Effects.Where(e => e.ShowInStatusBar)]; foreach (Effect effect in effects) { if (effect.OnEffectIsBeingDispelled(dispeller, target, this, isEnemy)) @@ -1058,6 +1140,35 @@ namespace Milimoe.FunGame.Core.Entity } } + /// + /// 免疫检定 [ 尽可能的调用此方法而不是自己实现 ] + /// 先进行检定,再施加状态效果 + /// + /// + /// + /// + /// + /// + public bool CheckSkilledImmune(Character character, Character target, Skill skill, Item? item = null) + { + if (GamingQueue is null) return false; + return GamingQueue.CheckSkilledImmune(target, character, skill, item); + } + + /// + /// 技能豁免检定 [ 尽可能的调用此方法而不是自己实现 ] + /// 先进行检定,再施加状态效果 + /// + /// + /// + /// + /// + public bool CheckExemption(Character character, Character target, Effect effect) + { + if (GamingQueue is null) return false; + return GamingQueue.CheckExemption(target, character, effect, true); + } + /// /// 修改角色的硬直时间 [ 尽可能的调用此方法而不是自己实现 ] /// @@ -1091,6 +1202,18 @@ namespace Milimoe.FunGame.Core.Entity return GamingQueue?.IsCharacterInAIControlling(character) ?? false; } + /// + /// 向角色发起询问反应事件 [ 尽可能的调用此方法而不是自己实现 ] + /// + /// + /// + /// + /// + public Dictionary Inquiry(Character character, string topic, Dictionary args) + { + return GamingQueue?.Inquiry(character, topic, args) ?? []; + } + /// /// 添加角色应用的特效类型到回合记录中 /// @@ -1206,5 +1329,10 @@ namespace Milimoe.FunGame.Core.Entity /// 驱散描述 /// private string _dispelDescription = ""; + + /// + /// 豁免性 + /// + private PrimaryAttribute? _exemptionType = null; } } diff --git a/Entity/Skill/NormalAttack.cs b/Entity/Skill/NormalAttack.cs index cea38e5..dae84a4 100644 --- a/Entity/Skill/NormalAttack.cs +++ b/Entity/Skill/NormalAttack.cs @@ -3,6 +3,7 @@ using Milimoe.FunGame.Core.Api.Utility; using Milimoe.FunGame.Core.Interface.Base; using Milimoe.FunGame.Core.Interface.Entity; using Milimoe.FunGame.Core.Library.Constant; +using Milimoe.FunGame.Core.Model; namespace Milimoe.FunGame.Core.Entity { @@ -336,8 +337,9 @@ namespace Milimoe.FunGame.Core.Entity /// /// /// + /// /// - public void Attack(IGamingQueue queue, Character attacker, params IEnumerable enemys) + public void Attack(IGamingQueue queue, Character attacker, DamageCalculationOptions? options, params IEnumerable enemys) { if (!Enable) { @@ -350,8 +352,8 @@ namespace Milimoe.FunGame.Core.Entity queue.WriteLine($"[ {Character} ] 对 [ {enemy} ] 发起了普通攻击!"); double expected = 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 ? DamageType.Magical : DamageType.Physical, MagicType, result); + DamageResult result = IsMagic ? queue.CalculateMagicalDamage(attacker, enemy, true, MagicType, expected, out double damage, ref changeCount, options) : queue.CalculatePhysicalDamage(attacker, enemy, true, expected, out damage, ref changeCount, options); + queue.DamageToEnemy(attacker, enemy, damage, true, IsMagic ? DamageType.Magical : DamageType.Physical, MagicType, result, options); } } } diff --git a/Entity/Skill/Skill.cs b/Entity/Skill/Skill.cs index 5d89b98..b389010 100644 --- a/Entity/Skill/Skill.cs +++ b/Entity/Skill/Skill.cs @@ -2,6 +2,7 @@ using Milimoe.FunGame.Core.Api.Utility; using Milimoe.FunGame.Core.Interface.Base; using Milimoe.FunGame.Core.Interface.Entity; +using Milimoe.FunGame.Core.Library.Common.Addon; using Milimoe.FunGame.Core.Library.Constant; namespace Milimoe.FunGame.Core.Entity @@ -37,6 +38,11 @@ namespace Milimoe.FunGame.Core.Entity /// public virtual string DispelDescription { get; set; } = ""; + /// + /// 豁免性的描述 + /// + public virtual string ExemptionDescription { get; set; } = ""; + /// /// 释放技能时的口号 /// @@ -146,18 +152,39 @@ namespace Milimoe.FunGame.Core.Entity /// public virtual bool IsNonDirectional { get; set; } = false; + /// + /// 在非指向性技能选取目标格子时,包括有角色的格子,默认为 true。仅 = true 时有效。 + /// 当此项为 false 时,必须设置 = true,否则实际施法时会被拒绝。 + /// + public virtual bool SelectIncludeCharacterGrid { get; set; } = true; + + /// + /// 是否可以选择没有被角色占据的空地,为 false 时会阻止施法。仅 = true 时有效。 + /// + public virtual bool AllowSelectNoCharacterGrid { get; set; } = false; + + /// + /// 是否可以选择已死亡的角色。仅 = true 时有效。 + /// + public virtual bool AllowSelectDead { get; set; } = false; + /// /// 作用范围形状 /// - 菱形。默认的曼哈顿距离正方形 /// - 圆形。基于欧几里得距离的圆形 /// - 正方形 - /// - 施法者与目标之间的直线 + /// - 施法者与目标之间的线段 /// - 施法者与目标所在的直线,贯穿至地图边缘 /// - 扇形 - /// 注意,该属性不影响选取目标的范围。选取目标的范围由 决定。 + /// 注意,该属性不影响选取目标的范围。选取目标的范围由 决定。 /// public virtual SkillRangeType SkillRangeType { get; set; } = SkillRangeType.Diamond; + /// + /// 扇形的角度。仅 时有效,默认值为 90 度。 + /// + public virtual double SectorAngle { get; set; } = 90; + /// /// 选取角色的条件 /// @@ -376,7 +403,7 @@ namespace Milimoe.FunGame.Core.Entity foreach (Character character in enemys) { - IEnumerable effects = character.Effects.Where(e => e.IsInEffect); + IEnumerable effects = Effects.Where(e => e.IsInEffect); if (CanSelectEnemy && ((character.ImmuneType & checkType) == ImmuneType.None || effects.Any(e => e.IgnoreImmune == ImmuneType.All || e.IgnoreImmune == ImmuneType.Skilled || (IsMagic && e.IgnoreImmune == ImmuneType.Magical)))) { @@ -386,7 +413,6 @@ namespace Milimoe.FunGame.Core.Entity foreach (Character character in teammates) { - IEnumerable effects = character.Effects.Where(e => e.IsInEffect); if (CanSelectTeammate) { selectable.Add(character); @@ -452,19 +478,146 @@ namespace Milimoe.FunGame.Core.Entity return [.. targets.Distinct()]; } + /// + /// 默认行为:在指向性技能中,当 > 0 时,会额外选取一些被扩散的目标 + /// + /// + /// + /// + /// + /// + /// + public virtual List SelectTargetsByCanSelectTargetRange(Character caster, List allEnemys, List allTeammates, IEnumerable selected, bool union = true) + { + List grids = []; + + if (IsNonDirectional || CanSelectTargetRange < 0 || GamingQueue?.Map is not GameMap map) + { + return []; + } + + foreach (Character selectedCharacter in selected) + { + Grid? centerGrid = map.GetCharacterCurrentGrid(selectedCharacter); + if (centerGrid == null || centerGrid == Grid.Empty) + continue; + + // 使用曼哈顿距离获取以主要目标为中心、范围内的所有格子(包括中心格子本身) + grids.AddRange(map.GetGridsByRange(centerGrid, CanSelectTargetRange, true)); + } + + return SelectTargetsByRange(caster, allEnemys, allTeammates, selected, grids, union); + } + + /// + /// 选取范围内的目标 + /// + /// + /// + /// + /// + /// + /// + /// + public virtual List SelectTargetsByRange(Character caster, List allEnemys, List allTeammates, IEnumerable selected, IEnumerable range, bool union = true) + { + List targets = []; + + foreach (Character character in range.SelectMany(g => g.Characters)) + { + if (CanSelectSelf && character == caster) + { + targets.Add(caster); + } + + ImmuneType checkType = ImmuneType.Skilled | ImmuneType.All; + if (IsMagic) + { + checkType |= ImmuneType.Magical; + } + + if (allEnemys.Contains(character)) + { + IEnumerable effects = Effects.Where(e => e.IsInEffect); + if (CanSelectEnemy && ((AllowSelectDead && character.HP == 0) || (!AllowSelectDead && character.HP > 0)) && + ((character.ImmuneType & checkType) == ImmuneType.None || effects.Any(e => e.IgnoreImmune == ImmuneType.All || e.IgnoreImmune == ImmuneType.Skilled || (IsMagic && e.IgnoreImmune == ImmuneType.Magical)))) + { + targets.Add(character); + } + } + + if (CanSelectTeammate && allTeammates.Contains(character) && ((AllowSelectDead && character.HP == 0) || (!AllowSelectDead && character.HP > 0))) + { + targets.Add(character); + } + } + + // 如果和已经选择的列表合并 + if (union) + { + return [.. targets.Where(c => SelectTargetPredicates.All(f => f(c))).Union(selected).Distinct()]; + } + + return [.. targets.Where(c => SelectTargetPredicates.All(f => f(c))).Distinct()]; + } + + /// + /// 选取非指向性目标 + /// + /// + /// + /// + /// + public virtual List SelectNonDirectionalTargets(Character caster, Grid targetGrid, bool includeCharacter = false) + { + int range = CanSelectTargetRange; + List targets = []; + + if (GamingQueue?.Map == null || targetGrid == Grid.Empty || range < 0) + { + return targets; + } + + GameMap map = GamingQueue.Map; + Grid currentGrid = map.GetCharacterCurrentGrid(caster) ?? Grid.Empty; + + // 范围等于 0 时只返回中心格子 + if (range == 0) + { + targets.Add(targetGrid); + return targets; + } + + List rangeGrids = SkillRangeType switch + { + SkillRangeType.Diamond => map.GetGridsByRange(targetGrid, range, includeCharacter), + SkillRangeType.Circle => map.GetGridsByCircleRange(targetGrid, range, includeCharacter), + SkillRangeType.Square => map.GetGridsBySquareRange(targetGrid, range, includeCharacter), + SkillRangeType.Line => map.GetGridsOnThickLine(currentGrid, targetGrid, range, false, includeCharacter), + SkillRangeType.LinePass => map.GetGridsOnThickLine(currentGrid, targetGrid, range, true, includeCharacter), + SkillRangeType.Sector => map.GetGridsInSector(currentGrid, targetGrid, range, SectorAngle, includeCharacter), + _ => map.GetGridsByRange(targetGrid, range, includeCharacter) + }; + + targets.AddRange(rangeGrids); + + return [.. targets.Distinct()]; + } + /// /// 技能开始吟唱时 [ 吟唱魔法、释放战技和爆发技、预释放爆发技均可触发 ] /// /// /// /// - public void OnSkillCasting(IGamingQueue queue, Character caster, List targets) + /// + public void OnSkillCasting(IGamingQueue queue, Character caster, List targets, List grids) { GamingQueue = queue; foreach (Effect e in Effects) { e.GamingQueue = GamingQueue; - e.OnSkillCasting(caster, targets); + e.OnSkillCasting(caster, targets, grids); } } @@ -483,13 +636,14 @@ namespace Milimoe.FunGame.Core.Entity /// /// /// - public void OnSkillCasted(IGamingQueue queue, Character caster, List targets) + /// + public void OnSkillCasted(IGamingQueue queue, Character caster, List targets, List grids) { GamingQueue = queue; foreach (Effect e in Effects) { e.GamingQueue = GamingQueue; - e.OnSkillCasted(caster, targets, Values); + e.OnSkillCasted(caster, targets, grids, Values); } } @@ -556,6 +710,10 @@ namespace Milimoe.FunGame.Core.Entity { builder.AppendLine($"{DispelDescription}"); } + if (ExemptionDescription != "") + { + builder.AppendLine($"{ExemptionDescription}"); + } if (GamingQueue?.Map != null && SkillType != SkillType.Passive) { builder.AppendLine($"施法距离:{(CastAnywhere ? "全图" : CastRange)}"); @@ -660,6 +818,7 @@ namespace Milimoe.FunGame.Core.Entity skill.Description = skillDefined.Description; skill.GeneralDescription = skillDefined.GeneralDescription; skill.DispelDescription = skillDefined.DispelDescription; + skill.ExemptionDescription = skillDefined.ExemptionDescription; skill.SkillType = skillDefined.SkillType; skill.MPCost = skillDefined.MPCost; skill.CastTime = skillDefined.CastTime; diff --git a/Entity/Skill/SkillTarget.cs b/Entity/Skill/SkillTarget.cs index 329233e..0f83d51 100644 --- a/Entity/Skill/SkillTarget.cs +++ b/Entity/Skill/SkillTarget.cs @@ -1,11 +1,14 @@ -namespace Milimoe.FunGame.Core.Entity +using Milimoe.FunGame.Core.Library.Common.Addon; + +namespace Milimoe.FunGame.Core.Entity { /// /// 技能和它的目标结构体 /// /// /// - public struct SkillTarget(Skill skill, List targets) + /// + public struct SkillTarget(Skill skill, List targets, List grids) { /// /// 技能实例 @@ -13,8 +16,13 @@ public Skill Skill { get; set; } = skill; /// - /// 技能的目标列表 + /// 指向性技能的目标列表 /// public List Targets { get; set; } = targets; + + /// + /// 非指向性技能的目标列表 + /// + public List TargetGrids { get; set; } = grids; } } diff --git a/Entity/Statistics/CharacterStatistics.cs b/Entity/Statistics/CharacterStatistics.cs index c23f67e..b0ff674 100644 --- a/Entity/Statistics/CharacterStatistics.cs +++ b/Entity/Statistics/CharacterStatistics.cs @@ -50,5 +50,9 @@ public double AvgRank { get; set; } = 0; public double Rating { get; set; } = 0; public int MVPs { get; set; } = 0; + public int UseDecisionPoints { get; set; } = 0; + public int TurnDecisions { get; set; } = 0; + public int AvgUseDecisionPoints { get; set; } = 0; + public int AvgTurnDecisions { get; set; } = 0; } } diff --git a/Interface/Base/IGamingQueue.cs b/Interface/Base/IGamingQueue.cs index c441786..dcae70f 100644 --- a/Interface/Base/IGamingQueue.cs +++ b/Interface/Base/IGamingQueue.cs @@ -86,7 +86,7 @@ namespace Milimoe.FunGame.Core.Interface.Base /// /// /// - public Task ProcessTurnAsync(Character character); + public bool ProcessTurn(Character character); /// /// 造成伤害 @@ -98,7 +98,8 @@ namespace Milimoe.FunGame.Core.Interface.Base /// /// /// - public Task DamageToEnemyAsync(Character actor, Character enemy, double damage, bool isNormalAttack, DamageType damageType = DamageType.Physical, MagicType magicType = MagicType.None, DamageResult damageResult = DamageResult.Normal); + /// + public void DamageToEnemy(Character actor, Character enemy, double damage, bool isNormalAttack, DamageType damageType = DamageType.Physical, MagicType magicType = MagicType.None, DamageResult damageResult = DamageResult.Normal, DamageCalculationOptions? options = null); /// /// 治疗一个目标 @@ -107,7 +108,8 @@ namespace Milimoe.FunGame.Core.Interface.Base /// /// /// - public Task HealToTargetAsync(Character actor, Character target, double heal, bool canRespawn = false); + /// + public void HealToTarget(Character actor, Character target, double heal, bool canRespawn = false, bool triggerEffects = true); /// /// 计算物理伤害 @@ -118,8 +120,9 @@ namespace Milimoe.FunGame.Core.Interface.Base /// /// /// + /// /// - public DamageResult CalculatePhysicalDamage(Character actor, Character enemy, bool isNormalAttack, double expectedDamage, out double finalDamage, ref int changeCount); + public DamageResult CalculatePhysicalDamage(Character actor, Character enemy, bool isNormalAttack, double expectedDamage, out double finalDamage, ref int changeCount, DamageCalculationOptions? options = null); /// /// 计算魔法伤害 @@ -131,28 +134,29 @@ namespace Milimoe.FunGame.Core.Interface.Base /// /// /// + /// /// - public DamageResult CalculateMagicalDamage(Character actor, Character enemy, bool isNormalAttack, MagicType magicType, double expectedDamage, out double finalDamage, ref int changeCount); + public DamageResult CalculateMagicalDamage(Character actor, Character enemy, bool isNormalAttack, MagicType magicType, double expectedDamage, out double finalDamage, ref int changeCount, DamageCalculationOptions? options = null); /// /// 死亡结算 /// /// /// - public Task DeathCalculationAsync(Character killer, Character death); + public void DeathCalculation(Character killer, Character death); /// /// 打断施法 /// /// /// - public Task InterruptCastingAsync(Character caster, Character interrupter); + public void InterruptCasting(Character caster, Character interrupter); /// /// 打断施法 [ 用于使敌人目标丢失 ] /// /// - public Task InterruptCastingAsync(Character interrupter); + public void InterruptCasting(Character interrupter); /// /// 使用物品 @@ -163,9 +167,11 @@ namespace Milimoe.FunGame.Core.Interface.Base /// /// /// - /// + /// + /// + /// /// - public Task UseItemAsync(Item item, Character character, DecisionPoints dp, List enemys, List teammates, List castRange, List? desiredTargets = null); + public bool UseItem(Item item, Character character, DecisionPoints dp, List enemys, List teammates, List castRange, List allEnemys, List allTeammates, AIDecision? aiDecision = null); /// /// 角色移动 @@ -175,7 +181,7 @@ namespace Milimoe.FunGame.Core.Interface.Base /// /// /// - public Task CharacterMoveAsync(Character character, DecisionPoints dp, Grid target, Grid? startGrid); + public bool CharacterMove(Character character, DecisionPoints dp, Grid target, Grid? startGrid); /// /// 选取移动目标 @@ -186,7 +192,7 @@ namespace Milimoe.FunGame.Core.Interface.Base /// /// /// - public Task SelectTargetGridAsync(Character character, List enemys, List teammates, GameMap map, List moveRange); + public Grid SelectTargetGrid(Character character, List enemys, List teammates, GameMap map, List moveRange); /// /// 选取技能目标 @@ -197,7 +203,7 @@ namespace Milimoe.FunGame.Core.Interface.Base /// /// /// - public Task> SelectTargetsAsync(Character caster, Skill skill, List enemys, List teammates, List castRange); + public List SelectTargets(Character caster, Skill skill, List enemys, List teammates, List castRange); /// /// 选取普通攻击目标 @@ -208,7 +214,21 @@ namespace Milimoe.FunGame.Core.Interface.Base /// /// /// - public Task> SelectTargetsAsync(Character character, NormalAttack attack, List enemys, List teammates, List attackRange); + public List SelectTargets(Character character, NormalAttack attack, List enemys, List teammates, List attackRange); + + /// + /// 获取某角色的敌人列表 + /// + /// + /// + public List GetEnemies(Character character); + + /// + /// 获取某角色的队友列表 + /// + /// + /// + public List GetTeammates(Character character); /// /// 判断目标对于某个角色是否是队友 @@ -256,5 +276,34 @@ namespace Milimoe.FunGame.Core.Interface.Base /// /// public void CalculateCharacterDamageStatistics(Character character, Character characterTaken, double damage, DamageType damageType, double takenDamage = -1); + + /// + /// 免疫检定 + /// + /// + /// + /// + /// + /// + public bool CheckSkilledImmune(Character character, Character target, Skill skill, Item? item = null); + + /// + /// 技能豁免检定 + /// + /// + /// + /// + /// true - 豁免成功等效于闪避 + /// + public bool CheckExemption(Character character, Character? source, Effect effect, bool isEvade); + + /// + /// 向角色(或控制该角色的玩家)进行询问并取得答复 + /// + /// + /// + /// + /// + public Dictionary Inquiry(Character character, string topic, Dictionary args); } } diff --git a/Library/Common/Addon/Example/ExampleGameModule.cs b/Library/Common/Addon/Example/ExampleGameModule.cs index 5c959c4..a92f724 100644 --- a/Library/Common/Addon/Example/ExampleGameModule.cs +++ b/Library/Common/Addon/Example/ExampleGameModule.cs @@ -338,16 +338,15 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example // 不使用框架的实现时,需要地图作者与游戏队列的作者做好适配 if (queue is GamingQueue gq) { - gq.SelectTargetGrid += Gq_SelectTargetGrid; + gq.SelectTargetGridEvent += Gq_SelectTargetGrid; } return map; } - private async Task Gq_SelectTargetGrid(GamingQueue queue, Character character, List enemys, List teammates, GameMap map, List canMoveGrids) + private Grid Gq_SelectTargetGrid(GamingQueue queue, Character character, List enemys, List teammates, GameMap map, List canMoveGrids) { // 介入选择,假设这里更新界面,让玩家选择目的地 - await Task.CompletedTask; return Grid.Empty; } } diff --git a/Library/Common/Addon/GameMap.cs b/Library/Common/Addon/GameMap.cs index e201f3c..6e95626 100644 --- a/Library/Common/Addon/GameMap.cs +++ b/Library/Common/Addon/GameMap.cs @@ -366,6 +366,305 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon return grids; } + /// + /// 获取以某个格子为中心,一定范围内的格子(正方形,切比雪夫距离),只考虑同一平面的格子。 + /// + /// + /// + /// + /// + public virtual List GetGridsBySquareRange(Grid grid, int range, bool includeCharacter = false) + { + List grids = []; + + if (range < 0) return grids; + + for (int dx = -range; dx <= range; dx++) + { + for (int dy = -range; dy <= range; dy++) + { + // 切比雪夫距离:max(|dx|, |dy|) <= range + if (Math.Max(Math.Abs(dx), Math.Abs(dy)) <= range) + { + int x = grid.X + dx; + int y = grid.Y + dy; + int z = grid.Z; + + if (GridsByCoordinate.TryGetValue((x, y, z), out Grid? select) && select != null) + { + if (includeCharacter || select.Characters.Count == 0) + { + grids.Add(select); + } + } + } + } + } + + return grids; + } + + /// + /// 获取以某个格子为中心,最远距离的格子(正方形,切比雪夫距离),只考虑同一平面的格子。 + /// + /// + /// + /// + /// + public virtual List GetOuterGridsBySquareRange(Grid grid, int range, bool includeCharacter = false) + { + List grids = []; + + if (range < 0) return grids; + + for (int dx = -range; dx <= range; dx++) + { + for (int dy = -range; dy <= range; dy++) + { + if (Math.Max(Math.Abs(dx), Math.Abs(dy)) == range) + { + int x = grid.X + dx; + int y = grid.Y + dy; + int z = grid.Z; + + if (GridsByCoordinate.TryGetValue((x, y, z), out Grid? select) && select != null) + { + if (includeCharacter || select.Characters.Count == 0) + { + grids.Add(select); + } + } + } + } + } + + return grids; + } + + /// + /// 使用布雷森汉姆直线算法获取从起点到终点的所有格子(包含起点和终点)。 + /// 若 passThrough 为 true,则继续向同一方向延伸直到地图边缘。只考虑同一平面的格子。 + /// + /// 施法者格子 + /// 目标格子 + /// 是否贯穿至地图边缘 + /// 是否包含有角色的格子 + /// 直线上的格子列表 + public virtual List GetGridsOnLine(Grid casterGrid, Grid targetGrid, bool passThrough = false, bool includeCharacter = false) + { + List grids = []; + + if (casterGrid == Grid.Empty || targetGrid == Grid.Empty || casterGrid.Z != targetGrid.Z) + { + if (targetGrid != Grid.Empty && (includeCharacter || targetGrid.Characters.Count == 0)) + grids.Add(targetGrid); + return grids; + } + + int x0 = casterGrid.X; + int y0 = casterGrid.Y; + int x1 = targetGrid.X; + int y1 = targetGrid.Y; + int z = casterGrid.Z; + + // 始终包含起点 + if (includeCharacter || casterGrid.Characters.Count == 0) + grids.Add(casterGrid); + + // 计算直线上的所有整数点 + List<(int x, int y)> points = GetLinePoints(x0, y0, x1, y1); + + // 添加中间点(不包括起点和终点) + for (int i = 1; i < points.Count - 1; i++) + { + (int x, int y) = points[i]; + Grid? current = this[x, y, z]; + if (current != null && (includeCharacter || current.Characters.Count == 0) && !grids.Contains(current)) + { + grids.Add(current); + } + } + + // 添加终点(如果与起点不同) + if (!(x0 == x1 && y0 == y1)) + { + Grid? target = this[x1, y1, z]; + if (target != null && (includeCharacter || target.Characters.Count == 0) && !grids.Contains(target)) + { + grids.Add(target); + } + } + + // 贯穿模式:继续向目标方向延伸直到地图边缘 + if (passThrough && points.Count >= 2) + { + // 获取方向向量(从最后第二个点到最后第一个点) + int lastIndex = points.Count - 1; + int dirX = points[lastIndex].x - points[lastIndex - 1].x; + int dirY = points[lastIndex].y - points[lastIndex - 1].y; + + // 规范化方向(保证每次移动一个单位) + if (Math.Abs(dirX) > 1 || Math.Abs(dirY) > 1) + { + int gcd = GCD(Math.Abs(dirX), Math.Abs(dirY)); + dirX /= gcd; + dirY /= gcd; + } + + int extendX = x1 + dirX; + int extendY = y1 + dirY; + + // 设置最大延伸步数,防止无限循环 + int maxSteps = Math.Max(Length, Width) * 2; + int steps = 0; + + while (steps < maxSteps) + { + // 检查坐标是否在地图边界内 + if (extendX < 0 || extendX >= Length || + extendY < 0 || extendY >= Width) + break; + + Grid? extendGrid = this[extendX, extendY, z]; + if (extendGrid == null) break; + + if ((includeCharacter || extendGrid.Characters.Count == 0) && !grids.Contains(extendGrid)) + { + grids.Add(extendGrid); + } + + extendX += dirX; + extendY += dirY; + steps++; + } + } + + return grids; + } + + /// + /// 使用布雷森汉姆直线算法获取从起点到终点的所有格子(包含起点和终点)并考虑宽度。 + /// 若 passThrough 为 true,则继续向同一方向延伸直到地图边缘。只考虑同一平面的格子。 + /// + /// + /// + /// + /// + /// + /// + public virtual List GetGridsOnThickLine(Grid start, Grid directionRef, int range, bool passThrough = false, bool includeChar = false) + { + List line = GetGridsOnLine(start, directionRef, passThrough, includeCharacter: true); + List result = []; + + foreach (Grid g in line) + { + List around = GetGridsBySquareRange(g, range / 2, includeCharacter: true); + foreach (Grid a in around) + { + if (!result.Contains(a) && (includeChar || a.Characters.Count == 0)) + { + result.Add(a); + } + } + } + return result; + } + + /// + /// 获取扇形范围内的格子 + /// 扇形以 casterGrid 为顶点,向 targetGrid 方向张开 + /// + /// 目标格子,即扇形顶点 + /// 施法者格子,用于确定朝向 + /// 最大半径 + /// 扇形角度,默认 90 + /// 是否包含有角色的格子 + /// + public virtual List GetGridsInSector(Grid casterGrid, Grid targetGrid, int range, double angleDegrees = 90, bool includeCharacter = false) + { + List grids = []; + + if (casterGrid == Grid.Empty || targetGrid == Grid.Empty || casterGrid.Z != targetGrid.Z) + return grids; + + if (range <= 0) + { + if (includeCharacter || casterGrid.Characters.Count == 0) + grids.Add(casterGrid); + return grids; + } + + int z = casterGrid.Z; + + // 计算朝向向量:从施法者指向目标点 + double dirX = targetGrid.X - casterGrid.X; + double dirY = targetGrid.Y - casterGrid.Y; + double dirLength = Math.Sqrt(dirX * dirX + dirY * dirY); + + // 如果目标和施法者重合,退化为圆形范围 + if (dirLength < 0.01) + { + return GetGridsByCircleRange(casterGrid, range - 1 > 0 ? range - 1 : 0, includeCharacter); + } + + // 单位朝向向量 + double unitDirX = dirX / dirLength; + double unitDirY = dirY / dirLength; + + // 半角(弧度) + double halfAngleRad = (angleDegrees / 2) * (Math.PI / 180.0); + + // 遍历以施法者为中心的一个足够大的区域(-range 到 +range) + for (int dx = -range; dx <= range; dx++) + { + for (int dy = -range; dy <= range; dy++) + { + int x = casterGrid.X + dx; + int y = casterGrid.Y + dy; + + Grid? candidate = this[x, y, z]; + if (candidate == null) continue; + + // 向量:从施法者到候选格子 + double vecX = dx; + double vecY = dy; + double vecLength = Math.Sqrt(vecX * vecX + vecY * vecY); + + // 必须在最大范围(半径)内 + if (vecLength > range + 0.01) continue; + + // 中心格子(施法者自己)始终包含 + if (vecLength < 0.01) + { + if (includeCharacter || candidate.Characters.Count == 0) + grids.Add(candidate); + continue; + } + + // 单位向量 + double unitVecX = vecX / vecLength; + double unitVecY = vecY / vecLength; + + // 计算与朝向的夹角 + double dot = unitDirX * unitVecX + unitDirY * unitVecY; + dot = Math.Clamp(dot, -1.0, 1.0); + double angleRad = Math.Acos(dot); + + // 夹角在半角范围内 → 在扇形内 + if (angleRad <= halfAngleRad) + { + if (includeCharacter || candidate.Characters.Count == 0) + { + grids.Add(candidate); + } + } + } + } + + return grids; + } + /// /// 设置角色移动 /// @@ -414,12 +713,16 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon return currentSteps; } - // 定义平面移动的四个方向 + // 定义平面移动的方向 (int dx, int dy)[] directions = [ (0, 1), // 上 (0, -1), // 下 (1, 0), // 右 - (-1, 0) // 左 + (-1, 0), // 左 + (1, 1), // 右上 + (1, -1), // 右下 + (-1, 1), // 左上 + (-1, -1) // 左下 ]; foreach (var (dx, dy) in directions) @@ -484,9 +787,10 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon int minDistanceToTarget = CalculateManhattanDistance(startGrid, target); int stepsToBestReachable = 0; - // 定义平面移动的四个方向 + // 定义平面移动的方向 (int dx, int dy)[] directions = [ - (0, 1), (0, -1), (1, 0), (-1, 0) + (0, 1), (0, -1), (1, 0), (-1, 0), + (1, 1), (1, -1), (-1, 1), (-1, -1) ]; while (queue.Count > 0) @@ -552,6 +856,101 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon return Math.Abs(g1.X - g2.X) + Math.Abs(g1.Y - g2.Y) + Math.Abs(g1.Z - g2.Z); } + /// + /// 计算两个整数的最大公约数(欧几里得算法) + /// + public static int GCD(int a, int b) + { + if (a == 0 && b == 0) return 1; // 避免除以零 + if (a == 0) return b; + if (b == 0) return a; + while (b != 0) + { + int temp = b; + b = a % b; + a = temp; + } + return a; + } + + /// + /// 获取两点之间直线上的所有整数点(包含起点和终点) + /// 使用改进的Bresenham算法,确保不遗漏任何点 + /// + public static List<(int x, int y)> GetLinePoints(int x0, int y0, int x1, int y1) + { + List<(int x, int y)> points = []; + + int dx = Math.Abs(x1 - x0); + int dy = Math.Abs(y1 - y0); + int sx = x0 < x1 ? 1 : -1; + int sy = y0 < y1 ? 1 : -1; + + // 如果直线是水平的 + if (dy == 0) + { + int x = x0; + while (x != x1 + sx) + { + points.Add((x, y0)); + x += sx; + } + return points; + } + + // 如果直线是垂直的 + if (dx == 0) + { + int y = y0; + while (y != y1 + sy) + { + points.Add((x0, y)); + y += sy; + } + return points; + } + + // 对于斜线,使用改进的Bresenham算法 + if (dx >= dy) + { + // 斜率小于1 + int err = 2 * dy - dx; + int y = y0; + + for (int x = x0; x != x1 + sx; x += sx) + { + points.Add((x, y)); + + if (err > 0) + { + y += sy; + err -= 2 * dx; + } + err += 2 * dy; + } + } + else + { + // 斜率大于1 + int err = 2 * dx - dy; + int x = x0; + + for (int y = y0; y != y1 + sy; y += sy) + { + points.Add((x, y)); + + if (err > 0) + { + x += sx; + err -= 2 * dy; + } + err += 2 * dx; + } + } + + return points; + } + /// /// 在事件流逝前处理 /// @@ -565,7 +964,7 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon /// 在事件流逝后处理 /// /// - protected virtual void AfterTimeElapsed(ref double timeToReduce) + protected virtual void AfterTimeElapsed(double timeToReduce) { } @@ -605,7 +1004,7 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon } } - AfterTimeElapsed(ref timeToReduce); + AfterTimeElapsed(timeToReduce); } } } diff --git a/Library/Constant/ConstantSet.cs b/Library/Constant/ConstantSet.cs index 0668c7e..53224d3 100644 --- a/Library/Constant/ConstantSet.cs +++ b/Library/Constant/ConstantSet.cs @@ -1,4 +1,5 @@ -using Milimoe.FunGame.Core.Model; +using System.Text; +using Milimoe.FunGame.Core.Model; /** * 此文件用于保存字符串常量(String Set) @@ -703,7 +704,7 @@ namespace Milimoe.FunGame.Core.Library.Constant EffectType.Item => "装备特效", EffectType.Mark => "标记", EffectType.Stun => "眩晕", - EffectType.Freeze => "冰冻", + EffectType.Freeze => "冻结", EffectType.Silence => "沉默", EffectType.Root => "定身", EffectType.Fear => "恐惧", @@ -751,6 +752,8 @@ namespace Milimoe.FunGame.Core.Library.Constant EffectType.Recovery => "恢复", EffectType.Vulnerable => "易伤", EffectType.Delay => "迟滞", + EffectType.Focusing => "专注", + EffectType.InterruptCasting => "打断施法", _ => "未知效果" }; } @@ -811,10 +814,49 @@ namespace Milimoe.FunGame.Core.Library.Constant EffectType.Recovery => DispelledType.Weak, EffectType.Vulnerable => DispelledType.Weak, EffectType.Delay => DispelledType.Weak, + EffectType.InterruptCasting => DispelledType.Weak, _ => DispelledType.Weak }; } + public static PrimaryAttribute GetExemptionTypeByEffectType(EffectType type) + { + return type switch + { + EffectType.Stun => PrimaryAttribute.STR, + EffectType.Freeze => PrimaryAttribute.STR, + EffectType.Silence => PrimaryAttribute.INT, + EffectType.Root => PrimaryAttribute.STR, + EffectType.Fear => PrimaryAttribute.INT, + EffectType.Sleep => PrimaryAttribute.STR, + EffectType.Knockback => PrimaryAttribute.STR, + EffectType.Knockdown => PrimaryAttribute.STR, + EffectType.Taunt => PrimaryAttribute.INT, + EffectType.Slow => PrimaryAttribute.AGI, + EffectType.Weaken => PrimaryAttribute.STR, + EffectType.Poison => PrimaryAttribute.AGI, + EffectType.Burn => PrimaryAttribute.AGI, + EffectType.Bleed => PrimaryAttribute.AGI, + EffectType.Blind => PrimaryAttribute.AGI, + EffectType.Cripple => PrimaryAttribute.STR, + EffectType.MagicResistBreak => PrimaryAttribute.INT, + EffectType.Curse => PrimaryAttribute.INT, + EffectType.Exhaustion => PrimaryAttribute.STR, + EffectType.ManaBurn => PrimaryAttribute.INT, + EffectType.Charm => PrimaryAttribute.INT, + EffectType.Disarm => PrimaryAttribute.STR, + EffectType.Confusion => PrimaryAttribute.INT, + EffectType.Petrify => PrimaryAttribute.STR, + EffectType.SilenceMagic => PrimaryAttribute.INT, + EffectType.Banish => PrimaryAttribute.INT, + EffectType.GrievousWound => PrimaryAttribute.STR, + EffectType.Vulnerable => PrimaryAttribute.INT, + EffectType.Delay => PrimaryAttribute.AGI, + EffectType.InterruptCasting => PrimaryAttribute.INT, + _ => PrimaryAttribute.None + }; + } + public static bool GetIsDebuffByEffectType(EffectType type) { return type switch @@ -871,6 +913,7 @@ namespace Milimoe.FunGame.Core.Library.Constant EffectType.Recovery => false, EffectType.Vulnerable => true, EffectType.Delay => true, + EffectType.InterruptCasting => true, _ => false }; } @@ -922,5 +965,23 @@ namespace Milimoe.FunGame.Core.Library.Constant _ => "" }; } + + public static string GetExemptionDescription(EffectType type, bool exemptDuration = true) + { + PrimaryAttribute pa = GetExemptionTypeByEffectType(type); + if (pa == PrimaryAttribute.None) + { + return ""; + } + return GetExemptionDescription(pa, exemptDuration); + } + + public static string GetExemptionDescription(PrimaryAttribute type, bool exemptDuration = true) + { + StringBuilder builder = new(); + builder.AppendLine($"豁免类型:{CharacterSet.GetPrimaryAttributeName(type)}"); + builder.Append($"豁免持续时间:{(exemptDuration ? "是" : "否")}"); + return builder.ToString(); + } } } diff --git a/Library/Constant/TypeEnum.cs b/Library/Constant/TypeEnum.cs index b1fb6f3..1ce0650 100644 --- a/Library/Constant/TypeEnum.cs +++ b/Library/Constant/TypeEnum.cs @@ -233,6 +233,7 @@ namespace Milimoe.FunGame.Core.Library.Constant /// /// /// + /// /// public enum EffectType { @@ -494,7 +495,17 @@ namespace Milimoe.FunGame.Core.Library.Constant /// /// 迟滞,硬直时间延长 /// - Delay + Delay, + + /// + /// 专注 + /// + Focusing, + + /// + /// 打断施法 + /// + InterruptCasting } public enum ItemType @@ -1066,11 +1077,34 @@ namespace Milimoe.FunGame.Core.Library.Constant public enum SkillRangeType { + /// + /// 菱形 + /// Diamond, + + /// + /// 圆形 + /// Circle, + + /// + /// 正方形 + /// Square, + + /// + /// 施法者与目标之间的线段 + /// Line, + + /// + /// 施法者与目标所在的直线,贯穿至地图边缘 + /// LinePass, + + /// + /// 扇形 + /// Sector } } diff --git a/Model/AIDecision.cs b/Model/AIDecision.cs index 1a188b2..2046e0a 100644 --- a/Model/AIDecision.cs +++ b/Model/AIDecision.cs @@ -12,6 +12,7 @@ namespace Milimoe.FunGame.Core.Model public ISkill? SkillToUse { get; set; } = null; public Item? ItemToUse { get; set; } = null; public List Targets { get; set; } = []; + public List TargetGrids { get; set; } = []; public double Score { get; set; } = 0; public bool IsPureMove { get; set; } = false; } diff --git a/Model/DamageCalculationOptions.cs b/Model/DamageCalculationOptions.cs new file mode 100644 index 0000000..7862dd5 --- /dev/null +++ b/Model/DamageCalculationOptions.cs @@ -0,0 +1,43 @@ +namespace Milimoe.FunGame.Core.Model +{ + /// + /// 精准的分步控制伤害计算 + /// + public class DamageCalculationOptions + { + /// + /// 完整计算伤害 + /// + public bool NeedCalculate { get; set; } = true; + + /// + /// 计算减伤 + /// + public bool CalculateReduction { get; set; } = true; + + /// + /// 计算暴击 + /// + public bool CalculateCritical { get; set; } = true; + + /// + /// 计算闪避 + /// + public bool CalculateEvade { get; set; } = true; + + /// + /// 计算护盾 + /// + public bool CalculateShield { get; set; } = true; + + /// + /// 触发特效 + /// + public bool TriggerEffects { get; set; } = true; + + /// + /// 无视免疫 + /// + public bool IgnoreImmune { get; set; } = false; + } +} diff --git a/Model/EquilibriumConstant.cs b/Model/EquilibriumConstant.cs index 7fb8646..57e0df7 100644 --- a/Model/EquilibriumConstant.cs +++ b/Model/EquilibriumConstant.cs @@ -230,6 +230,11 @@ namespace Milimoe.FunGame.Core.Model /// public double STRtoCritDMGMultiplier { get; set; } = 0.00575; + /// + /// 每 1 点力量增加力量豁免率 + /// + public double STRtoExemptionRateMultiplier { get; set; } = 0.001; + /// /// 每 1 点智力增加魔法值 /// @@ -260,6 +265,11 @@ namespace Milimoe.FunGame.Core.Model /// public double INTtoAccelerationCoefficientMultiplier { get; set; } = 0.00125; + /// + /// 每 1 点智力增加智力豁免率 + /// + public double INTtoExemptionRateMultiplier { get; set; } = 0.001; + /// /// 每 1 点敏捷增加行动速度 /// @@ -275,6 +285,11 @@ namespace Milimoe.FunGame.Core.Model /// public double AGItoEvadeRateMultiplier { get; set; } = 0.00175; + /// + /// 每 1 点敏捷增加敏捷豁免率 + /// + public double AGItoExemptionRateMultiplier { get; set; } = 0.001; + /// /// 造成伤害获得能量值因子 /// diff --git a/Model/GamingQueue.cs b/Model/GamingQueue.cs index f5cfb94..16f6ccc 100644 --- a/Model/GamingQueue.cs +++ b/Model/GamingQueue.cs @@ -623,7 +623,7 @@ namespace Milimoe.FunGame.Core.Model /// 从行动顺序表取出第一个角色 /// /// - public async Task NextCharacterAsync() + public Character? NextCharacter() { if (_queue.Count == 0) return null; @@ -649,8 +649,8 @@ namespace Milimoe.FunGame.Core.Model } else { - await TimeLapse(); - return await NextCharacterAsync(); + TimeLapse(); + return NextCharacter(); } } @@ -658,7 +658,7 @@ namespace Milimoe.FunGame.Core.Model /// 时间进行流逝,减少硬直时间,减少技能冷却时间,角色也会因此回复状态 /// /// 流逝的时间 - public async Task TimeLapse() + public double TimeLapse() { if (_queue.Count == 0) return 0; @@ -675,7 +675,13 @@ namespace Milimoe.FunGame.Core.Model } TotalTime = Calculation.Round2Digits(TotalTime + timeToReduce); - WriteLine("时间流逝:" + timeToReduce); + WriteLine($"时间流逝:{timeToReduce}"); + + if (IsDebug) + { + // 记录行动顺序表 + WriteLine(string.Join("\r\n", _queue.Select(c => c + ": " + _hardnessTimes[c]))); + } Character[] characters = [.. _queue]; foreach (Character character in characters) @@ -703,23 +709,36 @@ namespace Milimoe.FunGame.Core.Model double needMP = character.MaxMP - character.MP; double reallyReHP = needHP >= recoveryHP ? recoveryHP : needHP; double reallyReMP = needMP >= recoveryMP ? recoveryMP : needMP; - if (reallyReHP > 0 && reallyReMP > 0) + bool allowRecovery = true; + Effect[] effects = [.. character.Effects]; + foreach (Effect effect in effects) { - character.HP += reallyReHP; - character.MP += reallyReMP; - if (IsDebug) WriteLine($"角色 {character.Name} 回血:{recoveryHP:0.##} [{character.HP:0.##} / {character.MaxHP:0.##}] / 回蓝:{recoveryMP:0.##} [{character.MP:0.##} / {character.MaxMP:0.##}] / 当前能量:{character.EP:0.##}"); + if (effect.BeforeApplyRecoveryAtTimeLapsing(character, ref reallyReHP, ref reallyReMP)) + { + allowRecovery = false; + } } - else + + if (allowRecovery) { - if (reallyReHP > 0) + if (reallyReHP > 0 && reallyReMP > 0) { character.HP += reallyReHP; - if (IsDebug) WriteLine($"角色 {character.Name} 回血:{recoveryHP:0.##} [{character.HP:0.##} / {character.MaxHP:0.##}] / 当前能量:{character.EP:0.##}"); - } - if (reallyReMP > 0) - { character.MP += reallyReMP; - if (IsDebug) WriteLine($"角色 {character.Name} 回蓝:{recoveryMP:0.##} [{character.MP:0.##} / {character.MaxMP:0.##}] / 当前能量:{character.EP:0.##}"); + if (IsDebug) WriteLine($"角色 {character.Name} 回血:{recoveryHP:0.##} [{character.HP:0.##} / {character.MaxHP:0.##}] / 回蓝:{recoveryMP:0.##} [{character.MP:0.##} / {character.MaxMP:0.##}] / 当前能量:{character.EP:0.##}"); + } + else + { + if (reallyReHP > 0) + { + character.HP += reallyReHP; + if (IsDebug) WriteLine($"角色 {character.Name} 回血:{recoveryHP:0.##} [{character.HP:0.##} / {character.MaxHP:0.##}] / 当前能量:{character.EP:0.##}"); + } + if (reallyReMP > 0) + { + character.MP += reallyReMP; + if (IsDebug) WriteLine($"角色 {character.Name} 回蓝:{recoveryMP:0.##} [{character.MP:0.##} / {character.MaxMP:0.##}] / 当前能量:{character.EP:0.##}"); + } } } @@ -738,7 +757,7 @@ namespace Milimoe.FunGame.Core.Model _map?.OnTimeElapsed(timeToReduce); // 移除到时间的特效 - List effects = [.. character.Effects]; + effects = [.. character.Effects]; foreach (Effect effect in effects) { if (!character.Shield.ShieldOfEffects.ContainsKey(effect)) @@ -758,6 +777,12 @@ namespace Milimoe.FunGame.Core.Model effect.OnTimeElapsed(character, timeToReduce); } + // 进行持续时间豁免 + if (effect.Exemptable && effect.ExemptDuration && (effect.RemainDuration > 0 || effect.RemainDurationTurn > 0)) + { + CheckExemption(character, effect.Source, effect, false); + } + if (effect.IsBeingTemporaryDispelled) { effect.IsBeingTemporaryDispelled = false; @@ -803,6 +828,12 @@ namespace Milimoe.FunGame.Core.Model effect.Dispel(effect.Source, character, !IsTeammate(character, effect.Source) && character != effect.Source); } + // 还原临时驱散后的吟唱状态 + if (character.CharacterState == CharacterState.Actionable && _castingSkills.ContainsKey(character)) + { + character.CharacterState = CharacterState.Casting; + } + _eliminated.Remove(character); } @@ -812,7 +843,7 @@ namespace Milimoe.FunGame.Core.Model _respawnCountdown[character] = Calculation.Round2Digits(_respawnCountdown[character] - timeToReduce); if (_respawnCountdown[character] <= 0) { - await SetCharacterRespawn(character); + SetCharacterRespawn(character); } } @@ -842,13 +873,13 @@ namespace Milimoe.FunGame.Core.Model /// /// /// 是否结束游戏 - public async Task ProcessTurnAsync(Character character) + public bool ProcessTurn(Character character) { _isInRound = true; LastRound.Actor = character; _roundDeaths.Clear(); - if (!await BeforeTurnAsync(character)) + if (!BeforeTurn(character)) { _isInRound = false; return _isGameEnd; @@ -875,7 +906,7 @@ namespace Milimoe.FunGame.Core.Model // 回合开始事件,允许事件返回 false 接管回合操作 // 如果事件全程接管回合操作,需要注意触发特效 - if (!await OnTurnStartAsync(character, dp, selectableEnemys, selectableTeammates, skills, items)) + if (!OnTurnStartEvent(character, dp, selectableEnemys, selectableTeammates, skills, items)) { _isInRound = false; return _isGameEnd; @@ -929,8 +960,6 @@ namespace Milimoe.FunGame.Core.Model { canCastGridsByStartGrid.AddRange(_map.GetGridsByRange(startGrid, skill.CastRange, true)); } - allEnemys = [.. allEnemys.Where(canAttackGridsByStartGrid.Union(canCastGridsByStartGrid).SelectMany(g => g.Characters).Contains)]; - allTeammates = [.. allTeammates.Where(canAttackGridsByStartGrid.Union(canCastGridsByStartGrid).SelectMany(g => g.Characters).Contains)]; } // 此变量用于在取消选择时,能够重新行动 @@ -1128,13 +1157,16 @@ namespace Milimoe.FunGame.Core.Model // 启用战棋地图时的专属 AI 决策方法 if (isAI && ai != null && startGrid != null) { - aiDecision = await ai.DecideAIActionAsync(character, dp, startGrid, canMoveGrids, skills, items, allEnemys, allTeammates, enemys, teammates); + List allEnemysInGame = [.. allEnemys.Where(canAttackGridsByStartGrid.Union(canCastGridsByStartGrid).SelectMany(g => g.Characters).Contains)]; + List allTeammatesInGame = [.. allTeammates.Where(canAttackGridsByStartGrid.Union(canCastGridsByStartGrid).SelectMany(g => g.Characters).Contains)]; + + aiDecision = ai.DecideAIAction(character, dp, startGrid, canMoveGrids, skills, items, allEnemys, allTeammates, enemys, teammates); type = aiDecision.ActionType; } else { // 模组可以通过此事件来决定角色的行动 - type = await OnDecideActionAsync(character, dp, enemys, teammates, skills, items); + type = OnDecideActionEvent(character, dp, enemys, teammates, skills, items); } // 若事件未完成决策,则将通过概率对角色进行自动化决策 if (type == CharacterActionType.None) @@ -1162,7 +1194,7 @@ namespace Milimoe.FunGame.Core.Model if (aiDecision != null && aiDecision.ActionType != CharacterActionType.Move && aiDecision.TargetMoveGrid != null) { // 不是纯粹移动的情况,需要手动移动 - moved = await CharacterMoveAsync(character, dp, aiDecision.TargetMoveGrid, startGrid); + moved = CharacterMove(character, dp, aiDecision.TargetMoveGrid, startGrid); } int costDP = dp.GetActionPointCost(type); @@ -1178,9 +1210,9 @@ namespace Milimoe.FunGame.Core.Model } else { - target = await SelectTargetGridAsync(character, enemys, teammates, _map, canMoveGrids); + target = SelectTargetGrid(character, enemys, teammates, _map, canMoveGrids); } - moved = await CharacterMoveAsync(character, dp, target, startGrid); + moved = CharacterMove(character, dp, target, startGrid); } if (isAI && (aiDecision?.IsPureMove ?? false)) { @@ -1190,7 +1222,7 @@ namespace Milimoe.FunGame.Core.Model decided = true; endTurn = true; WriteLine($"[ {character} ] 结束了回合!"); - await OnCharacterDoNothingAsync(character, dp); + OnCharacterDoNothingEvent(character, dp); } } else if (type == CharacterActionType.NormalAttack) @@ -1227,19 +1259,21 @@ namespace Milimoe.FunGame.Core.Model enemys = [.. enemys.Where(attackRange.SelectMany(g => g.Characters).Contains)]; teammates = [.. teammates.Where(attackRange.SelectMany(g => g.Characters).Contains)]; } - targets = await SelectTargetsAsync(character, character.NormalAttack, enemys, teammates, attackRange); + targets = SelectTargets(character, character.NormalAttack, enemys, teammates, attackRange); } if (targets.Count > 0) { LastRound.Targets[CharacterActionType.NormalAttack] = [.. targets]; LastRound.ActionTypes.Add(CharacterActionType.NormalAttack); + _stats[character].UseDecisionPoints += costDP; + _stats[character].TurnDecisions++; dp.AddActionType(CharacterActionType.NormalAttack); dp.CurrentDecisionPoints -= costDP; decided = true; - await OnCharacterNormalAttackAsync(character, dp, targets); + OnCharacterNormalAttackEvent(character, dp, targets); - character.NormalAttack.Attack(this, character, targets); + character.NormalAttack.Attack(this, character, null, targets); baseTime += character.NormalAttack.RealHardnessTime; effects = [.. character.Effects.Where(e => e.IsInEffect)]; foreach (Effect effect in effects) @@ -1268,7 +1302,7 @@ namespace Milimoe.FunGame.Core.Model } else { - skill = await OnSelectSkillAsync(character, skills); + skill = OnSelectSkillEvent(character, skills); } if (skill is null && CharactersInAI.Contains(character) && skills.Count > 0) { @@ -1276,6 +1310,9 @@ namespace Milimoe.FunGame.Core.Model } if (skill != null) { + skill.GamingQueue = this; + List targets = []; + List grids = []; costDP = dp.GetActionPointCost(type, skill); if (dp.CurrentDecisionPoints < costDP) { @@ -1284,46 +1321,45 @@ namespace Milimoe.FunGame.Core.Model else if (skill.SkillType == SkillType.Magic) { // 吟唱前需要先选取目标 - List targets; - if (aiDecision != null) + List castRange = []; + if (_map != null && realGrid != null) { - targets = aiDecision.Targets; - } - else - { - List castRange = []; - if (_map != null && realGrid != null) - { - castRange = _map.GetGridsByRange(realGrid, skill.CastRange, true); - enemys = [.. enemys.Where(castRange.SelectMany(g => g.Characters).Contains)]; - teammates = [.. teammates.Where(castRange.SelectMany(g => g.Characters).Contains)]; - } - targets = await SelectTargetsAsync(character, skill, enemys, teammates, castRange); + castRange = _map.GetGridsByRange(realGrid, skill.CastRange, true); + enemys = [.. enemys.Where(castRange.SelectMany(g => g.Characters).Contains)]; + teammates = [.. teammates.Where(castRange.SelectMany(g => g.Characters).Contains)]; } + (targets, grids) = GetSelectedSkillTargetsList(character, skill, enemys, teammates, castRange, allEnemys, allTeammates, aiDecision); + if (targets.Count > 0) { // 免疫检定 - await CheckSkilledImmuneAsync(character, targets, skill); + CheckSkilledImmune(character, targets, skill); + } + bool hasTarget = targets.Count > 0 || (skill.IsNonDirectional && grids.Count > 0 && (skill.AllowSelectNoCharacterGrid || !skill.AllowSelectNoCharacterGrid && targets.Count > 0)); + if (hasTarget) + { + LastRound.Skills[CharacterActionType.PreCastSkill] = skill; + LastRound.Targets[CharacterActionType.PreCastSkill] = [.. targets]; + LastRound.ActionTypes.Add(CharacterActionType.PreCastSkill); + _stats[character].UseDecisionPoints += costDP; + _stats[character].TurnDecisions++; + dp.AddActionType(CharacterActionType.PreCastSkill); + dp.CurrentDecisionPoints -= costDP; + decided = true; + endTurn = true; - if (targets.Count > 0) - { - LastRound.Skills[CharacterActionType.PreCastSkill] = skill; - LastRound.Targets[CharacterActionType.PreCastSkill] = [.. targets]; - LastRound.ActionTypes.Add(CharacterActionType.PreCastSkill); - dp.AddActionType(CharacterActionType.PreCastSkill); - dp.CurrentDecisionPoints -= costDP; - decided = true; - endTurn = true; + character.CharacterState = CharacterState.Casting; + SkillTarget skillTarget = new(skill, targets, grids); + OnCharacterPreCastSkillEvent(character, dp, skillTarget); - character.CharacterState = CharacterState.Casting; - SkillTarget skillTarget = new(skill, targets); - await OnCharacterPreCastSkillAsync(character, dp, skillTarget); - - _castingSkills[character] = skillTarget; - baseTime += skill.RealCastTime; - isCheckProtected = false; - skill.OnSkillCasting(this, character, targets); - } + _castingSkills[character] = skillTarget; + baseTime += skill.RealCastTime; + isCheckProtected = false; + skill.OnSkillCasting(this, character, targets, grids); + } + else + { + if (IsDebug) WriteLine($"[ {character} ] 想要吟唱 [ {skill.Name} ],但是没有目标!"); } } else if (skill is CourageCommandSkill && dp.CourageCommandSkill) @@ -1343,67 +1379,66 @@ namespace Milimoe.FunGame.Core.Model // 只有魔法需要吟唱,战技和爆发技直接释放 if (CheckCanCast(character, skill, out double cost)) { - List targets; - if (aiDecision != null) + List castRange = []; + if (_map != null && realGrid != null) { - targets = aiDecision.Targets; - } - else - { - List castRange = []; - if (_map != null && realGrid != null) - { - castRange = _map.GetGridsByRange(realGrid, skill.CastRange, true); - enemys = [.. enemys.Where(castRange.SelectMany(g => g.Characters).Contains)]; - teammates = [.. teammates.Where(castRange.SelectMany(g => g.Characters).Contains)]; - } - targets = await SelectTargetsAsync(character, skill, enemys, teammates, castRange); + castRange = _map.GetGridsByRange(realGrid, skill.CastRange, true); + enemys = [.. enemys.Where(castRange.SelectMany(g => g.Characters).Contains)]; + teammates = [.. teammates.Where(castRange.SelectMany(g => g.Characters).Contains)]; } + (targets, grids) = GetSelectedSkillTargetsList(character, skill, enemys, teammates, castRange, allEnemys, allTeammates, aiDecision); + if (targets.Count > 0) { // 免疫检定 - await CheckSkilledImmuneAsync(character, targets, skill); - - if (targets.Count > 0) + CheckSkilledImmune(character, targets, skill); + } + bool hasTarget = targets.Count > 0 || (skill.IsNonDirectional && grids.Count > 0 && (skill.AllowSelectNoCharacterGrid || !skill.AllowSelectNoCharacterGrid && targets.Count > 0)); + if (hasTarget) + { + CharacterActionType skillType = skill.SkillType == SkillType.SuperSkill ? CharacterActionType.CastSuperSkill : CharacterActionType.CastSkill; + LastRound.Skills[skillType] = skill; + LastRound.Targets[skillType] = [.. targets]; + LastRound.ActionTypes.Add(skillType); + if (skill is not CourageCommandSkill) { - CharacterActionType skillType = skill.SkillType == SkillType.SuperSkill ? CharacterActionType.CastSuperSkill : CharacterActionType.CastSkill; - LastRound.Skills[skillType] = skill; - LastRound.Targets[skillType] = [.. targets]; - LastRound.ActionTypes.Add(skillType); - if (skill is not CourageCommandSkill) - { - dp.AddActionType(skillType); - dp.CurrentDecisionPoints -= costDP; - } - else - { - // 勇气指令不消耗决策点,但是有标记 - dp.CourageCommandSkill = true; - } - decided = true; - - SkillTarget skillTarget = new(skill, targets); - await OnCharacterPreCastSkillAsync(character, dp, skillTarget); - - skill.OnSkillCasting(this, character, targets); - skill.BeforeSkillCasted(); - - character.EP -= cost; - baseTime += skill.RealHardnessTime; - skill.CurrentCD = skill.RealCD; - skill.Enable = false; - LastRound.SkillsCost[skill] = $"{-cost:0.##} EP"; - WriteLine($"[ {character} ] 消耗了 {cost:0.##} 点能量,释放了{(skill.IsSuperSkill ? "爆发技" : "战技")} [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}"); - - await OnCharacterCastSkillAsync(character, dp, skillTarget, cost); - - skill.OnSkillCasted(this, character, targets); - effects = [.. character.Effects.Where(e => e.IsInEffect)]; - foreach (Effect effect in effects) - { - effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected); - } + _stats[character].UseDecisionPoints += costDP; + _stats[character].TurnDecisions++; + dp.AddActionType(skillType); + dp.CurrentDecisionPoints -= costDP; } + else + { + // 勇气指令不消耗决策点,但是有标记 + dp.CourageCommandSkill = true; + } + decided = true; + + SkillTarget skillTarget = new(skill, targets, grids); + OnCharacterPreCastSkillEvent(character, dp, skillTarget); + + skill.OnSkillCasting(this, character, targets, grids); + skill.BeforeSkillCasted(); + + character.EP -= cost; + baseTime += skill.RealHardnessTime; + skill.CurrentCD = skill.RealCD; + skill.Enable = false; + LastRound.SkillsCost[skill] = $"{-cost:0.##} EP"; + WriteLine($"[ {character} ] 消耗了 {cost:0.##} 点能量,释放了{(skill.IsSuperSkill ? "爆发技" : "战技")} [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}"); + + OnCharacterCastSkillEvent(character, dp, skillTarget, cost); + + skill.OnSkillCasted(this, character, targets, grids); + effects = [.. character.Effects.Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) + { + effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected); + } + } + else + { + if (IsDebug) WriteLine($"[ {character} ] 想要释放 [ {skill.Name} ],但是没有目标!"); } } } @@ -1418,48 +1453,70 @@ namespace Milimoe.FunGame.Core.Model character.CharacterState = CharacterState.Actionable; character.UpdateCharacterState(); Skill skill = skillTarget.Skill; - List targets = [.. skillTarget.Targets.Where(c => c == character || !c.IsUnselectable)]; + List targets = []; + List grids = []; + if (skill.IsNonDirectional && _map != null) + { + grids = skillTarget.TargetGrids; + targets = skill.SelectTargetsByRange(character, allEnemys, allTeammates, targets, grids); + } + else + { + targets = [.. skillTarget.Targets.Where(c => c == character || !c.IsUnselectable)]; + if (skill.CanSelectTargetRange > 0) + { + targets = skill.SelectTargetsByCanSelectTargetRange(character, allEnemys, allTeammates, targets); + } + } - // 判断是否能够释放技能 - if (targets.Count > 0 && CheckCanCast(character, skill, out double cost)) + if (targets.Count > 0) { // 免疫检定 - await CheckSkilledImmuneAsync(character, targets, skill); + CheckSkilledImmune(character, targets, skill); + } - if (targets.Count > 0) + // 判断是否能够释放技能 + bool hasTarget = targets.Count > 0 || (skill.IsNonDirectional && grids.Count > 0 && (skill.AllowSelectNoCharacterGrid || !skill.AllowSelectNoCharacterGrid && targets.Count > 0)); + + if (hasTarget && CheckCanCast(character, skill, out double cost)) + { + decided = true; + endTurn = true; + LastRound.Targets[CharacterActionType.CastSkill] = [.. targets]; + LastRound.Skills[CharacterActionType.CastSkill] = skill; + LastRound.ActionTypes.Add(CharacterActionType.CastSkill); + _castingSkills.Remove(character); + + skill.BeforeSkillCasted(); + + character.MP -= cost; + baseTime += skill.RealHardnessTime; + skill.CurrentCD = skill.RealCD; + skill.Enable = false; + LastRound.SkillsCost[skill] = $"{-cost:0.##} MP"; + WriteLine($"[ {character} ] 消耗了 {cost:0.##} 点魔法值,释放了魔法 [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}"); + + OnCharacterCastSkillEvent(character, dp, skillTarget, cost); + + skill.OnSkillCasted(this, character, targets, grids); + + effects = [.. character.Effects.Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) { - decided = true; - endTurn = true; - LastRound.Targets[CharacterActionType.CastSkill] = [.. targets]; - LastRound.Skills[CharacterActionType.CastSkill] = skill; - LastRound.ActionTypes.Add(CharacterActionType.CastSkill); - _castingSkills.Remove(character); - - skill.BeforeSkillCasted(); - - character.MP -= cost; - baseTime += skill.RealHardnessTime; - skill.CurrentCD = skill.RealCD; - skill.Enable = false; - LastRound.SkillsCost[skill] = $"{-cost:0.##} MP"; - WriteLine($"[ {character} ] 消耗了 {cost:0.##} 点魔法值,释放了魔法 [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}"); - - await OnCharacterCastSkillAsync(character, dp, skillTarget, cost); - - skill.OnSkillCasted(this, character, targets); + effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected); } } else { + if (!hasTarget) + { + WriteLine($"[ {character} ] 想要释放 [ {skill.Name} ],但是没有目标!"); + } WriteLine($"[ {character} ] 放弃释放技能!"); // 放弃释放技能会获得3的硬直时间 if (baseTime == 0) baseTime = 3; - } - - effects = [.. character.Effects.Where(e => e.IsInEffect)]; - foreach (Effect effect in effects) - { - effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected); + decided = true; + endTurn = true; } } else @@ -1471,6 +1528,7 @@ namespace Milimoe.FunGame.Core.Model } else if (type == CharacterActionType.CastSuperSkill) { + _stats[character].TurnDecisions++; dp.AddActionType(CharacterActionType.CastSuperSkill); LastRound.ActionTypes.Add(CharacterActionType.CastSuperSkill); decided = true; @@ -1487,9 +1545,9 @@ namespace Milimoe.FunGame.Core.Model { // 预释放的爆发技不可取消 List castRange = _map != null && realGrid != null ? _map.GetGridsByRange(realGrid, skill.CastRange, true) : []; - List targets = await SelectTargetsAsync(character, skill, enemys, teammates, castRange); + (List targets, List grids) = GetSelectedSkillTargetsList(character, skill, enemys, teammates, castRange, allEnemys, allTeammates, aiDecision); // 免疫检定 - await CheckSkilledImmuneAsync(character, targets, skill); + CheckSkilledImmune(character, targets, skill); LastRound.Targets[CharacterActionType.CastSuperSkill] = [.. targets]; skill.BeforeSkillCasted(); @@ -1501,22 +1559,24 @@ namespace Milimoe.FunGame.Core.Model LastRound.SkillsCost[skill] = $"{-cost:0.##} EP"; WriteLine($"[ {character} ] 消耗了 {cost:0.##} 点能量值,释放了爆发技 [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}"); - SkillTarget skillTarget = new(skill, targets); - await OnCharacterCastSkillAsync(character, dp, skillTarget, cost); + SkillTarget skillTarget = new(skill, targets, grids); + OnCharacterCastSkillEvent(character, dp, skillTarget, cost); - skill.OnSkillCasted(this, character, targets); + skill.OnSkillCasted(this, character, targets, grids); + + effects = [.. character.Effects.Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) + { + effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected); + } } else { WriteLine($"[ {character} ] 因能量不足放弃释放爆发技!"); // 放弃释放技能会获得3的硬直时间 if (baseTime == 0) baseTime = 3; - } - - effects = [.. character.Effects.Where(e => e.IsInEffect)]; - foreach (Effect effect in effects) - { - effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected); + decided = true; + endTurn = true; } } else if (type == CharacterActionType.UseItem) @@ -1529,7 +1589,7 @@ namespace Milimoe.FunGame.Core.Model } else { - item = await OnSelectItemAsync(character, items); + item = OnSelectItemEvent(character, items); } if (item is null && CharactersInAI.Contains(character) && items.Count > 0) { @@ -1554,8 +1614,10 @@ namespace Milimoe.FunGame.Core.Model { if (IsDebug) WriteLine($"角色 [ {character} ] 该回合使用物品的次数已超过决策点配额,无法再使用物品!"); } - else if (await UseItemAsync(item, character, dp, enemys, teammates, castRange, aiDecision?.Targets)) + else if (UseItem(item, character, dp, enemys, teammates, castRange, allEnemys, allTeammates, aiDecision)) { + _stats[character].UseDecisionPoints += costDP; + _stats[character].TurnDecisions++; dp.AddActionType(CharacterActionType.UseItem); dp.CurrentDecisionPoints -= costDP; LastRound.ActionTypes.Add(CharacterActionType.UseItem); @@ -1572,11 +1634,12 @@ namespace Milimoe.FunGame.Core.Model } else if (type == CharacterActionType.EndTurn) { + _stats[character].TurnDecisions++; SetOnlyMoveHardnessTime(character, dp, ref baseTime); decided = true; endTurn = true; WriteLine($"[ {character} ] 结束了回合!"); - await OnCharacterDoNothingAsync(character, dp); + OnCharacterDoNothingEvent(character, dp); } else { @@ -1603,12 +1666,12 @@ namespace Milimoe.FunGame.Core.Model { endTurn = true; WriteLine($"[ {character} ] 放弃了行动!"); - await OnCharacterGiveUpAsync(character, dp); + OnCharacterGiveUpEvent(character, dp); } if (character.CharacterState != CharacterState.Casting) dp.ActionsHardnessTime.Add(baseTime); - await OnCharacterActionTakenAsync(character, dp, type, LastRound); + OnCharacterActionTakenEvent(character, dp, type, LastRound); effects = [.. character.Effects.Where(e => e.IsInEffect)]; foreach (Effect effect in effects) @@ -1616,7 +1679,7 @@ namespace Milimoe.FunGame.Core.Model effect.OnCharacterActionTaken(character, dp, type); } - if (!await AfterCharacterAction(character, type)) + if (!AfterCharacterAction(character, type)) { endTurn = true; } @@ -1629,8 +1692,8 @@ namespace Milimoe.FunGame.Core.Model _stats[character].ActionTurn += 1; - await AfterCharacterDecision(character, dp); - await OnCharacterDecisionCompletedAsync(character, dp, LastRound); + AfterCharacterDecision(character, dp); + OnCharacterDecisionCompletedEvent(character, dp, LastRound); effects = [.. character.Effects.Where(e => e.IsInEffect)]; foreach (Effect effect in effects) { @@ -1638,7 +1701,7 @@ namespace Milimoe.FunGame.Core.Model } // 统一在回合结束时处理角色的死亡 - await ProcessCharacterDeathAsync(character); + ProcessCharacterDeath(character); // 移除回合奖励 RemoveRoundRewards(character, rewards); @@ -1646,9 +1709,9 @@ namespace Milimoe.FunGame.Core.Model if (_isGameEnd) { // 回合结束事件 - await OnTurnEndAsync(character, dp); + OnTurnEndEvent(character, dp); - await AfterTurnAsync(character); + AfterTurn(character); _isInRound = false; return _isGameEnd; @@ -1669,7 +1732,7 @@ namespace Milimoe.FunGame.Core.Model } AddCharacter(character, newHardnessTime, isCheckProtected); LastRound.HardnessTime = newHardnessTime; - await OnQueueUpdatedAsync(_queue, character, dp, newHardnessTime, QueueUpdatedReason.Action, "设置角色行动后的硬直时间。"); + OnQueueUpdatedEvent(_queue, character, dp, newHardnessTime, QueueUpdatedReason.Action, "设置角色行动后的硬直时间。"); effects = [.. character.Effects]; foreach (Effect effect in effects) @@ -1703,12 +1766,12 @@ namespace Milimoe.FunGame.Core.Model dp.ClearTempActionQuota(); // 有人想要插队吗? - await WillPreCastSuperSkill(); + WillPreCastSuperSkill(); // 回合结束事件 - await OnTurnEndAsync(character, dp); + OnTurnEndEvent(character, dp); - await AfterTurnAsync(character); + AfterTurn(character); WriteLine(""); _isInRound = false; @@ -1719,11 +1782,11 @@ namespace Milimoe.FunGame.Core.Model /// 处理角色死亡 /// /// - protected async Task ProcessCharacterDeathAsync(Character character) + protected void ProcessCharacterDeath(Character character) { foreach (Character death in _roundDeaths) { - if (!await OnCharacterDeathAsync(character, death)) + if (!OnCharacterDeathEvent(character, death)) { continue; } @@ -1737,27 +1800,17 @@ namespace Milimoe.FunGame.Core.Model // 将死者移出队列 _queue.Remove(death); - await AfterDeathCalculation(death, character); + AfterDeathCalculation(death, character); } } - /// - /// 获取某角色的团队成员 - /// - /// - /// - protected virtual List GetTeammates(Character character) - { - return []; - } - /// /// 角色行动后触发 /// /// /// /// 返回 false 结束回合 - protected virtual async Task AfterCharacterAction(Character character, CharacterActionType type) + protected virtual bool AfterCharacterAction(Character character, CharacterActionType type) { List allTeammates = GetTeammates(character); Character[] allEnemys = [.. _allCharacters.Where(c => c != character && !allTeammates.Contains(c) && !_eliminated.Contains(c))]; @@ -1773,9 +1826,9 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected virtual async Task AfterCharacterDecision(Character character, DecisionPoints dp) + protected virtual void AfterCharacterDecision(Character character, DecisionPoints dp) { - await Task.CompletedTask; + } /// @@ -1783,9 +1836,9 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected virtual async Task OnDeathCalculation(Character death, Character killer) + protected virtual void OnDeathCalculation(Character death, Character killer) { - await Task.CompletedTask; + } /// @@ -1793,7 +1846,7 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected virtual async Task AfterDeathCalculation(Character death, Character killer) + protected virtual void AfterDeathCalculation(Character death, Character killer) { if (!_queue.Where(c => c != killer).Any()) { @@ -1802,7 +1855,7 @@ namespace Milimoe.FunGame.Core.Model _queue.Remove(killer); _eliminated.Add(killer); _isGameEnd = true; - await OnGameEndAsync(killer); + OnGameEndEvent(killer); } } @@ -1822,18 +1875,18 @@ namespace Milimoe.FunGame.Core.Model /// 回合开始前触发 /// /// - protected virtual async Task BeforeTurnAsync(Character character) + protected virtual bool BeforeTurn(Character character) { - return await Task.FromResult(true); + return true; } /// /// 回合结束后触发 /// /// - protected virtual async Task AfterTurnAsync(Character character) + protected virtual void AfterTurn(Character character) { - await Task.CompletedTask; + } #endregion @@ -1850,7 +1903,8 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - public async Task DamageToEnemyAsync(Character actor, Character enemy, double damage, bool isNormalAttack, DamageType damageType = DamageType.Physical, MagicType magicType = MagicType.None, DamageResult damageResult = DamageResult.Normal) + /// + public void DamageToEnemy(Character actor, Character enemy, double damage, bool isNormalAttack, DamageType damageType = DamageType.Physical, MagicType magicType = MagicType.None, DamageResult damageResult = DamageResult.Normal, DamageCalculationOptions? options = null) { // 如果敌人在结算伤害之前就已经死亡,将不会继续下去 if (enemy.HP <= 0) @@ -1868,31 +1922,35 @@ namespace Milimoe.FunGame.Core.Model List characters = [actor, enemy]; bool isEvaded = damageResult == DamageResult.Evaded; List effects = []; + options ??= new(); - // 真实伤害跳过伤害加成区间 - if (damageType != DamageType.True) + if (options.TriggerEffects) { - Dictionary totalDamageBonus = []; - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) + // 真实伤害跳过伤害加成区间 + if (damageType != DamageType.True) { - double damageBonus = effect.AlterActualDamageAfterCalculation(actor, enemy, damage, isNormalAttack, damageType, magicType, damageResult, ref isEvaded, totalDamageBonus); - totalDamageBonus[effect] = damageBonus; - if (isEvaded) + Dictionary totalDamageBonus = []; + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) { - damageResult = DamageResult.Evaded; + double damageBonus = effect.AlterActualDamageAfterCalculation(actor, enemy, damage, isNormalAttack, damageType, magicType, damageResult, ref isEvaded, totalDamageBonus); + totalDamageBonus[effect] = damageBonus; + if (isEvaded) + { + damageResult = DamageResult.Evaded; + } } + damage += totalDamageBonus.Sum(kv => kv.Value); } - damage += totalDamageBonus.Sum(kv => kv.Value); - } - else - { - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) + else { - if (effect.BeforeApplyTrueDamage(actor, enemy, damage, isNormalAttack, damageResult)) + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) { - damageResult = DamageResult.Evaded; + if (effect.BeforeApplyTrueDamage(actor, enemy, damage, isNormalAttack, damageResult)) + { + damageResult = DamageResult.Evaded; + } } } } @@ -1903,8 +1961,8 @@ namespace Milimoe.FunGame.Core.Model { // 开始计算伤害免疫 bool isImmune = false; - // 真实伤害跳过免疫 - if (damageType != DamageType.True) + // 真实伤害或者指定无视免疫则跳过免疫检定 + if (damageType != DamageType.True || !options.IgnoreImmune) { // 此变量为是否无视免疫 bool ignore = false; @@ -1958,8 +2016,8 @@ namespace Milimoe.FunGame.Core.Model string damageTypeString = CharacterSet.GetDamageTypeName(damageType, magicType); string shieldMsg = ""; - // 真实伤害跳过护盾结算 - if (damageType != DamageType.True) + // 真实伤害或指定跳过护盾结算则跳过护盾结算 + if (damageType != DamageType.True || !options.CalculateShield) { // 在护盾结算前,特效可以有自己的逻辑 bool change = false; @@ -2132,7 +2190,7 @@ namespace Milimoe.FunGame.Core.Model // 生命偷取 double steal = actualDamage * actor.Lifesteal; - await HealToTargetAsync(actor, actor, steal, false); + HealToTarget(actor, actor, steal, false, true); effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; foreach (Effect effect in effects) { @@ -2177,19 +2235,22 @@ namespace Milimoe.FunGame.Core.Model actualDamage = 0; } - await OnDamageToEnemyAsync(actor, enemy, damage, actualDamage, isNormalAttack, damageType, magicType, damageResult); + OnDamageToEnemyEvent(actor, enemy, damage, actualDamage, isNormalAttack, damageType, magicType, damageResult); - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) + if (options.TriggerEffects) { - effect.AfterDamageCalculation(actor, enemy, damage, actualDamage, isNormalAttack, damageType, magicType, damageResult); + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) + { + effect.AfterDamageCalculation(actor, enemy, damage, actualDamage, isNormalAttack, damageType, magicType, damageResult); + } } if (enemy.HP <= 0 && !_eliminated.Contains(enemy) && !_respawnCountdown.ContainsKey(enemy)) { LastRound.HasKill = true; _roundDeaths.Add(enemy); - await DeathCalculationAsync(actor, enemy); + DeathCalculation(actor, enemy); } } @@ -2198,15 +2259,15 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - public async Task DeathCalculationAsync(Character killer, Character death) + public void DeathCalculation(Character killer, Character death) { if (IsTeammate(killer, death)) { - await DeathCalculationByTeammateAsync(killer, death); + DeathCalculationByTeammate(killer, death); return; } - if (!await OnDeathCalculationAsync(killer, death)) + if (!OnDeathCalculationEvent(killer, death)) { return; } @@ -2215,7 +2276,7 @@ namespace Milimoe.FunGame.Core.Model { _stats[death].Deaths += 1; WriteLine($"[ {death} ] 自杀了!"); - await DealWithCharacterDied(killer, death); + DealWithCharacterDied(killer, death); return; } @@ -2366,7 +2427,7 @@ namespace Milimoe.FunGame.Core.Model WriteLine(actorContinuousKilling); } - await DealWithCharacterDied(killer, death); + DealWithCharacterDied(killer, death); } /// @@ -2375,9 +2436,9 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - public async Task DeathCalculationByTeammateAsync(Character killer, Character death) + public void DeathCalculationByTeammate(Character killer, Character death) { - if (!await OnDeathCalculationByTeammateAsync(killer, death)) + if (!OnDeathCalculationByTeammateEvent(killer, death)) { return; } @@ -2395,7 +2456,7 @@ namespace Milimoe.FunGame.Core.Model LastRound.DeathContinuousKilling.Add(msg); WriteLine(msg); - await DealWithCharacterDied(killer, death); + DealWithCharacterDied(killer, death); } /// @@ -2405,7 +2466,8 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - public async Task HealToTargetAsync(Character actor, Character target, double heal, bool canRespawn = false) + /// + public void HealToTarget(Character actor, Character target, double heal, bool canRespawn = false, bool triggerEffects = true) { if (target.HP == target.MaxHP) { @@ -2414,22 +2476,20 @@ namespace Milimoe.FunGame.Core.Model bool isDead = target.HP <= 0; - Dictionary totalHealBonus = []; - List effects = [.. actor.Effects.Union(target.Effects).Distinct().Where(e => e.IsInEffect)]; - foreach (Effect effect in effects) + if (triggerEffects) { - bool changeCanRespawn = false; - double healBonus = effect.AlterHealValueBeforeHealToTarget(actor, target, heal, ref changeCanRespawn, totalHealBonus); - if (changeCanRespawn && !canRespawn) + Dictionary totalHealBonus = []; + List effects = [.. actor.Effects.Union(target.Effects).Distinct().Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) { - canRespawn = true; + 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; + heal += totalHealBonus.Sum(kv => kv.Value); } if (target.HP > 0 || (isDead && canRespawn)) @@ -2446,6 +2506,11 @@ namespace Milimoe.FunGame.Core.Model } } + if (heal <= 0) + { + return; + } + bool isRespawn = isDead && canRespawn; if (isRespawn) { @@ -2459,7 +2524,7 @@ namespace Milimoe.FunGame.Core.Model } double hp = target.HP; double mp = target.MP; - await SetCharacterRespawn(target); + SetCharacterRespawn(target); target.HP = hp; target.MP = mp; } @@ -2477,7 +2542,7 @@ namespace Milimoe.FunGame.Core.Model stats.TotalHeal += heal; } - await OnHealToTargetAsync(actor, target, heal, isRespawn); + OnHealToTargetEvent(actor, target, heal, isRespawn); } #endregion @@ -2507,15 +2572,60 @@ namespace Milimoe.FunGame.Core.Model return (selectableTeammates, selectableEnemys, skills, items); } + /// + /// 同时考虑指向性和非指向性技能的目标选取方法 + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public (List, List) GetSelectedSkillTargetsList(Character character, Skill skill, List enemys, List teammates, List castRange, List allEnemys, List allTeammates, AIDecision? aiDecision) + { + List targets = []; + List grids = []; + // 对于非战棋模式,我们会把它退回到指向性目标选择器 + if (skill.IsNonDirectional && _map != null) + { + if (aiDecision != null) grids = aiDecision.TargetGrids; + if (grids.Count == 0) + { + grids = SelectNonDirectionalSkillTargetGrid(character, skill, enemys, teammates, castRange); + } + if (grids.Count > 0) + { + targets = skill.SelectTargetsByRange(character, allEnemys, allTeammates, targets, grids); + } + } + else + { + if (aiDecision != null) targets = aiDecision.Targets; + if (targets.Count == 0) + { + targets = SelectTargets(character, skill, enemys, teammates, castRange); + } + if (skill.CanSelectTargetRange > 0) + { + // 扩散目标 + targets = skill.SelectTargetsByCanSelectTargetRange(character, allEnemys, allTeammates, targets); + } + } + return (targets, grids); + } + /// /// 需要处理复活和解除施法等 /// /// /// /// - public async Task DealWithCharacterDied(Character killer, Character death) + public void DealWithCharacterDied(Character killer, Character death) { - await OnDeathCalculation(death, killer); + OnDeathCalculation(death, killer); death.EP = 0; @@ -2579,9 +2689,11 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - /// + /// + /// + /// /// - public async Task UseItemAsync(Item item, Character character, DecisionPoints dp, List enemys, List teammates, List castRange, List? desiredTargets = null) + public bool UseItem(Item item, Character character, DecisionPoints dp, List enemys, List teammates, List castRange, List allEnemys, List allTeammates, AIDecision? aiDecision = null) { if (CheckCanCast(character, item, out double costMP, out double costEP)) { @@ -2589,63 +2701,55 @@ namespace Milimoe.FunGame.Core.Model if (skill != null) { skill.GamingQueue = this; - List targets; - if (desiredTargets != null) - { - targets = desiredTargets; - } - else - { - targets = await SelectTargetsAsync(character, skill, enemys, teammates, castRange); - } + (List targets, List grids) = GetSelectedSkillTargetsList(character, skill, enemys, teammates, castRange, allEnemys, allTeammates, aiDecision); + if (targets.Count > 0) { // 免疫检定 - await CheckSkilledImmuneAsync(character, targets, skill, item); + CheckSkilledImmune(character, targets, skill, item); + } + if (targets.Count > 0 && CheckCanCast(character, skill, out double cost)) + { + LastRound.Targets[CharacterActionType.UseItem] = [.. targets]; - if (targets.Count > 0) + WriteLine($"[ {character} ] 使用了物品 [ {item.Name} ]!"); + item.ReduceTimesAndRemove(); + if (item.IsReduceTimesAfterUse && item.RemainUseTimes == 0) { - LastRound.Targets[CharacterActionType.UseItem] = [.. targets]; - - WriteLine($"[ {character} ] 使用了物品 [ {item.Name} ]!"); - item.ReduceTimesAndRemove(); - if (item.IsReduceTimesAfterUse && item.RemainUseTimes == 0) - { - character.Items.Remove(item); - } - await OnCharacterUseItemAsync(character, dp, item, targets); - - skill.OnSkillCasting(this, character, targets); - skill.BeforeSkillCasted(); - - skill.CurrentCD = skill.RealCD; - skill.Enable = false; - - string line = $"[ {character} ] "; - if (costMP > 0) - { - character.MP -= costMP; - LastRound.ItemsCost[item] = $"{-costMP:0.##} MP"; - line += $"消耗了 {costMP:0.##} 点魔法值,"; - } - - if (costEP > 0) - { - character.EP -= costEP; - if (LastRound.ItemsCost[item] != "") LastRound.ItemsCost[item] += " / "; - LastRound.ItemsCost[item] += $"{-costEP:0.##} EP"; - line += $"消耗了 {costEP:0.##} 点能量,"; - } - - line += $"释放了物品技能 [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}"; - WriteLine(line); - - SkillTarget skillTarget = new(skill, targets); - await OnCharacterCastItemSkillAsync(character, dp, item, skillTarget, costMP, costEP); - - skill.OnSkillCasted(this, character, targets); - return true; + character.Items.Remove(item); } + OnCharacterUseItemEvent(character, dp, item, targets); + + skill.OnSkillCasting(this, character, targets, grids); + skill.BeforeSkillCasted(); + + skill.CurrentCD = skill.RealCD; + skill.Enable = false; + + string line = $"[ {character} ] "; + if (costMP > 0) + { + character.MP -= costMP; + LastRound.ItemsCost[item] = $"{-costMP:0.##} MP"; + line += $"消耗了 {costMP:0.##} 点魔法值,"; + } + + if (costEP > 0) + { + character.EP -= costEP; + if (LastRound.ItemsCost[item] != "") LastRound.ItemsCost[item] += " / "; + LastRound.ItemsCost[item] += $"{-costEP:0.##} EP"; + line += $"消耗了 {costEP:0.##} 点能量,"; + } + + line += $"释放了物品技能 [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}"; + WriteLine(line); + + SkillTarget skillTarget = new(skill, targets, grids); + OnCharacterCastItemSkillEvent(character, dp, item, skillTarget, costMP, costEP); + + skill.OnSkillCasted(this, character, targets, grids); + return true; } } } @@ -2660,7 +2764,7 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - public async Task CharacterMoveAsync(Character character, DecisionPoints dp, Grid target, Grid? startGrid) + public bool CharacterMove(Character character, DecisionPoints dp, Grid target, Grid? startGrid) { if (target.Id != -1) { @@ -2668,7 +2772,7 @@ namespace Milimoe.FunGame.Core.Model if (steps > 0) { WriteLine($"[ {character} ] 移动了 {steps} 步!"); - await OnCharacterMoveAsync(character, dp, target); + OnCharacterMoveEvent(character, dp, target); return true; } } @@ -2747,14 +2851,14 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - public async Task SelectTargetGridAsync(Character character, List enemys, List teammates, GameMap map, List moveRange) + public Grid SelectTargetGrid(Character character, List enemys, List teammates, GameMap map, List moveRange) { List effects = [.. character.Effects.Where(e => e.IsInEffect)]; foreach (Effect effect in effects) { effect.BeforeSelectTargetGrid(character, enemys, teammates, map, moveRange); } - Grid target = await OnSelectTargetGridAsync(character, enemys, teammates, map, moveRange); + Grid target = OnSelectTargetGridEvent(character, enemys, teammates, map, moveRange); if (target.Id != -1) { return target; @@ -2778,14 +2882,14 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - public async Task> SelectTargetsAsync(Character caster, Skill skill, List enemys, List teammates, List castRange) + public List SelectTargets(Character caster, Skill skill, List enemys, List teammates, List castRange) { List effects = [.. caster.Effects.Where(e => e.IsInEffect)]; foreach (Effect effect in effects) { effect.AlterSelectListBeforeSelection(caster, skill, enemys, teammates); } - List targets = await OnSelectSkillTargetsAsync(caster, skill, enemys, teammates, castRange); + List targets = OnSelectSkillTargetsEvent(caster, skill, enemys, teammates, castRange); if (targets.Count == 0 && CharactersInAI.Contains(caster)) { targets = skill.SelectTargets(caster, enemys, teammates); @@ -2793,6 +2897,25 @@ namespace Milimoe.FunGame.Core.Model return targets; } + /// + /// 选取非指向性技能目标 + /// + /// + /// + /// + /// + /// + /// + public List SelectNonDirectionalSkillTargetGrid(Character caster, Skill skill, List enemys, List teammates, List castRange) + { + List targets = OnSelectNonDirectionalSkillTargetsEvent(caster, skill, enemys, teammates, castRange); + if (targets.Count == 0 && CharactersInAI.Contains(caster) && castRange.Count > 0) + { + targets = skill.SelectNonDirectionalTargets(caster, castRange.OrderBy(r => Random.Shared.Next()).FirstOrDefault(r => r.Characters.Count > 0) ?? castRange.First(), skill.SelectIncludeCharacterGrid); + } + return targets; + } + /// /// 选取普通攻击目标 /// @@ -2802,14 +2925,14 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - public async Task> SelectTargetsAsync(Character character, NormalAttack attack, List enemys, List teammates, List attackRange) + public List SelectTargets(Character character, NormalAttack attack, List enemys, List teammates, List attackRange) { List effects = [.. character.Effects.Where(e => e.IsInEffect)]; foreach (Effect effect in effects) { effect.AlterSelectListBeforeSelection(character, attack, enemys, teammates); } - List targets = await OnSelectNormalAttackTargetsAsync(character, attack, enemys, teammates, attackRange); + List targets = OnSelectNormalAttackTargetsEvent(character, attack, enemys, teammates, attackRange); if (targets.Count == 0 && CharactersInAI.Contains(character)) { targets = character.NormalAttack.SelectTargets(character, enemys, teammates); @@ -2903,40 +3026,45 @@ namespace Milimoe.FunGame.Core.Model /// /// /// + /// /// - public DamageResult CalculatePhysicalDamage(Character actor, Character enemy, bool isNormalAttack, double expectedDamage, out double finalDamage, ref int changeCount) + public DamageResult CalculatePhysicalDamage(Character actor, Character enemy, bool isNormalAttack, double expectedDamage, out double finalDamage, ref int changeCount, DamageCalculationOptions? options = null) { + options ??= new(); List characters = [actor, enemy]; DamageType damageType = DamageType.Physical; MagicType magicType = MagicType.None; List effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - if (changeCount < 3) + if (options.TriggerEffects) { + if (changeCount < 3) + { + foreach (Effect effect in effects) + { + effect.AlterDamageTypeBeforeCalculation(actor, enemy, ref isNormalAttack, ref damageType, ref magicType); + } + if (damageType == DamageType.Magical) + { + changeCount++; + return CalculateMagicalDamage(actor, enemy, isNormalAttack, magicType, expectedDamage, out finalDamage, ref changeCount, options); + } + } + + Dictionary totalDamageBonus = []; + effects = [.. actor.Effects.Union(enemy.Effects).Distinct().Where(e => e.IsInEffect)]; foreach (Effect effect in effects) { - effect.AlterDamageTypeBeforeCalculation(actor, enemy, ref isNormalAttack, ref damageType, ref magicType); - } - if (damageType == DamageType.Magical) - { - changeCount++; - return CalculateMagicalDamage(actor, enemy, isNormalAttack, magicType, expectedDamage, out finalDamage, ref changeCount); + double damageBonus = effect.AlterExpectedDamageBeforeCalculation(actor, enemy, expectedDamage, isNormalAttack, DamageType.Physical, MagicType.None, totalDamageBonus); + totalDamageBonus[effect] = damageBonus; } + expectedDamage += totalDamageBonus.Sum(kv => kv.Value); } - Dictionary totalDamageBonus = []; - effects = [.. actor.Effects.Union(enemy.Effects).Distinct().Where(e => e.IsInEffect)]; - foreach (Effect effect in effects) - { - double damageBonus = effect.AlterExpectedDamageBeforeCalculation(actor, enemy, expectedDamage, isNormalAttack, DamageType.Physical, MagicType.None, totalDamageBonus); - totalDamageBonus[effect] = damageBonus; - } - expectedDamage += totalDamageBonus.Sum(kv => kv.Value); - double dice = Random.Shared.NextDouble(); double throwingBonus = 0; bool checkEvade = true; bool checkCritical = true; - if (isNormalAttack) + if (isNormalAttack && options.CalculateEvade) { effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; foreach (Effect effect in effects) @@ -2952,7 +3080,6 @@ namespace Milimoe.FunGame.Core.Model // 闪避检定 if (dice < (enemy.EvadeRate + throwingBonus)) { - finalDamage = 0; bool isAlterEvaded = false; effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; foreach (Effect effect in effects) @@ -2964,6 +3091,7 @@ namespace Milimoe.FunGame.Core.Model } if (!isAlterEvaded) { + finalDamage = 0; WriteLine("此物理攻击被完美闪避了!"); return DamageResult.Evaded; } @@ -2972,37 +3100,44 @@ namespace Milimoe.FunGame.Core.Model } // 物理穿透后的护甲 - double penetratedDEF = (1 - actor.PhysicalPenetration) * enemy.DEF; - + double penetratedDEF = 0; // 物理伤害减免 - double physicalDamageReduction = penetratedDEF / (penetratedDEF + GameplayEquilibriumConstant.DEFReductionFactor); - + double physicalDamageReduction = 0; // 最终的物理伤害 - finalDamage = expectedDamage * (1 - Calculation.PercentageCheck(physicalDamageReduction + enemy.ExPDR)); + finalDamage = expectedDamage; - // 暴击检定 - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) + if (options.CalculateReduction) { - if (!effect.BeforeCriticalCheck(actor, enemy, ref throwingBonus)) - { - checkCritical = false; - } + penetratedDEF = (1 - actor.PhysicalPenetration) * enemy.DEF; + physicalDamageReduction = penetratedDEF / (penetratedDEF + GameplayEquilibriumConstant.DEFReductionFactor); + finalDamage = expectedDamage * (1 - Calculation.PercentageCheck(physicalDamageReduction + enemy.ExPDR)); } - if (checkCritical) - { - dice = Random.Shared.NextDouble(); - if (dice < (actor.CritRate + throwingBonus)) + if (options.CalculateCritical) + { // 暴击检定 + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) { - finalDamage *= actor.CritDMG; // 暴击伤害倍率加成 - WriteLine("暴击生效!!"); - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) + if (!effect.BeforeCriticalCheck(actor, enemy, ref throwingBonus)) { - effect.OnCriticalDamageTriggered(actor, enemy, dice); + checkCritical = false; + } + } + + if (checkCritical) + { + dice = Random.Shared.NextDouble(); + if (dice < (actor.CritRate + throwingBonus)) + { + finalDamage *= actor.CritDMG; // 暴击伤害倍率加成 + WriteLine("暴击生效!!"); + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) + { + effect.OnCriticalDamageTriggered(actor, enemy, dice); + } + return DamageResult.Critical; } - return DamageResult.Critical; } } @@ -3020,39 +3155,44 @@ namespace Milimoe.FunGame.Core.Model /// /// /// + /// /// - public DamageResult CalculateMagicalDamage(Character actor, Character enemy, bool isNormalAttack, MagicType magicType, double expectedDamage, out double finalDamage, ref int changeCount) + public DamageResult CalculateMagicalDamage(Character actor, Character enemy, bool isNormalAttack, MagicType magicType, double expectedDamage, out double finalDamage, ref int changeCount, DamageCalculationOptions? options = null) { + options ??= new(); List characters = [actor, enemy]; DamageType damageType = DamageType.Magical; List effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - if (changeCount < 3) + if (options.TriggerEffects) { + if (changeCount < 3) + { + foreach (Effect effect in effects) + { + effect.AlterDamageTypeBeforeCalculation(actor, enemy, ref isNormalAttack, ref damageType, ref magicType); + } + if (damageType == DamageType.Physical) + { + changeCount++; + return CalculatePhysicalDamage(actor, enemy, isNormalAttack, expectedDamage, out finalDamage, ref changeCount, options); + } + } + + Dictionary totalDamageBonus = []; + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; foreach (Effect effect in effects) { - effect.AlterDamageTypeBeforeCalculation(actor, enemy, ref isNormalAttack, ref damageType, ref magicType); - } - if (damageType == DamageType.Physical) - { - changeCount++; - return CalculatePhysicalDamage(actor, enemy, isNormalAttack, expectedDamage, out finalDamage, ref changeCount); + double damageBonus = effect.AlterExpectedDamageBeforeCalculation(actor, enemy, expectedDamage, isNormalAttack, DamageType.Magical, magicType, totalDamageBonus); + totalDamageBonus[effect] = damageBonus; } + expectedDamage += totalDamageBonus.Sum(kv => kv.Value); } - Dictionary totalDamageBonus = []; - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) - { - double damageBonus = effect.AlterExpectedDamageBeforeCalculation(actor, enemy, expectedDamage, isNormalAttack, DamageType.Magical, magicType, totalDamageBonus); - totalDamageBonus[effect] = damageBonus; - } - expectedDamage += totalDamageBonus.Sum(kv => kv.Value); - double dice = Random.Shared.NextDouble(); double throwingBonus = 0; bool checkEvade = true; bool checkCritical = true; - if (isNormalAttack) + if (isNormalAttack && options.CalculateEvade) { effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; foreach (Effect effect in effects) @@ -3068,7 +3208,6 @@ namespace Milimoe.FunGame.Core.Model // 闪避检定 if (dice < (enemy.EvadeRate + throwingBonus)) { - finalDamage = 0; bool isAlterEvaded = false; effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; foreach (Effect effect in effects) @@ -3080,6 +3219,7 @@ namespace Milimoe.FunGame.Core.Model } if (!isAlterEvaded) { + finalDamage = 0; WriteLine("此魔法攻击被完美闪避了!"); return DamageResult.Evaded; } @@ -3088,36 +3228,43 @@ namespace Milimoe.FunGame.Core.Model } double MDF = enemy.MDF[magicType]; + finalDamage = 0; - // 魔法穿透后的魔法抗性 - MDF = (1 - actor.MagicalPenetration) * MDF; - - // 最终的魔法伤害 - finalDamage = expectedDamage * (1 - MDF); - - // 暴击检定 - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) + if (options.CalculateReduction) { - if (!effect.BeforeCriticalCheck(actor, enemy, ref throwingBonus)) - { - checkCritical = false; - } + // 魔法穿透后的魔法抗性 + MDF = (1 - actor.MagicalPenetration) * MDF; + + // 最终的魔法伤害 + finalDamage = expectedDamage * (1 - MDF); } - if (checkCritical) + if (options.CalculateCritical) { - dice = Random.Shared.NextDouble(); - if (dice < (actor.CritRate + throwingBonus)) + // 暴击检定 + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) { - finalDamage *= actor.CritDMG; // 暴击伤害倍率加成 - WriteLine("暴击生效!!"); - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) + if (!effect.BeforeCriticalCheck(actor, enemy, ref throwingBonus)) { - effect.OnCriticalDamageTriggered(actor, enemy, dice); + checkCritical = false; + } + } + + if (checkCritical) + { + dice = Random.Shared.NextDouble(); + if (dice < (actor.CritRate + throwingBonus)) + { + finalDamage *= actor.CritDMG; // 暴击伤害倍率加成 + WriteLine("暴击生效!!"); + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) + { + effect.OnCriticalDamageTriggered(actor, enemy, dice); + } + return DamageResult.Critical; } - return DamageResult.Critical; } } @@ -3136,6 +3283,27 @@ namespace Milimoe.FunGame.Core.Model return Math.Min((a + Random.Shared.Next(30)) * b, max); } + /// + /// 获取某角色的敌对角色 + /// + /// + /// + public List GetEnemies(Character character) + { + List teammates = GetTeammates(character); + return [.. _allCharacters.Where(c => c != character && !teammates.Contains(c))]; + } + + /// + /// 获取某角色的团队成员 + /// + /// + /// + public virtual List GetTeammates(Character character) + { + return []; + } + /// /// 判断目标对于某个角色是否是队友(不包括自己) /// @@ -3315,7 +3483,7 @@ namespace Milimoe.FunGame.Core.Model WriteLine($"[ {character} ] 获得了回合奖励!{skill.Description}".Trim()); if (skill.IsActive) { - skill.OnSkillCasted(this, character, [character]); + skill.OnSkillCasted(this, character, [character], []); } else { @@ -3353,12 +3521,12 @@ namespace Milimoe.FunGame.Core.Model /// 是否在回合外释放爆发技插队(仅自动化,手动设置请调用:) /// /// - protected async Task WillPreCastSuperSkill() + protected void WillPreCastSuperSkill() { // 选取所有 AI 控制角色 foreach (Character other in _queue.Where(c => c.CharacterState == CharacterState.Actionable && CharactersInAI.Contains(c)).ToList()) { - if (_decisionPoints.TryGetValue(other, out DecisionPoints? dp) && dp != null && dp.CurrentDecisionPoints < dp.GetActionPointCost(CharacterActionType.CastSuperSkill)) + if (!_decisionPoints.TryGetValue(other, out DecisionPoints? dp) || dp is null || dp.CurrentDecisionPoints < dp.GetActionPointCost(CharacterActionType.CastSuperSkill)) { continue; } @@ -3370,7 +3538,7 @@ namespace Milimoe.FunGame.Core.Model if (skills.Count > 0) { Skill skill = skills[Random.Shared.Next(skills.Count)]; - await SetCharacterPreCastSuperSkill(other, skill); + SetCharacterPreCastSuperSkill(other, skill); } } } @@ -3385,13 +3553,12 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - public async Task InterruptCastingAsync(Character caster, Character interrupter) + public void InterruptCasting(Character caster, Character interrupter) { Skill? skill = null; if (_castingSkills.TryGetValue(caster, out SkillTarget target)) { skill = target.Skill; - _castingSkills.Remove(caster); } if (skill is null && caster.CharacterState == CharacterState.PreCastSuperSkill) { @@ -3399,13 +3566,26 @@ namespace Milimoe.FunGame.Core.Model } if (skill != null) { - WriteLine($"[ {caster} ] 的施法被 [ {interrupter} ] 打断了!!"); + bool interruption = true; List effects = [.. caster.Effects.Union(interrupter.Effects).Distinct().Where(e => e.IsInEffect)]; - foreach (Effect effect in effects) + foreach (Effect e in effects) { - effect.OnSkillCastInterrupted(caster, skill, interrupter); + if (!e.BeforeSkillCastWillBeInterrupted(caster, skill, interrupter)) + { + interruption = false; + } + } + if (interruption) + { + _castingSkills.Remove(caster); + WriteLine($"[ {caster} ] 的施法被 [ {interrupter} ] 打断了!!"); + effects = [.. caster.Effects.Union(interrupter.Effects).Distinct().Where(e => e.IsInEffect)]; + foreach (Effect e in effects) + { + e.OnSkillCastInterrupted(caster, skill, interrupter); + } + OnInterruptCastingEvent(caster, skill, interrupter); } - await OnInterruptCastingAsync(caster, skill, interrupter); } } @@ -3413,7 +3593,7 @@ namespace Milimoe.FunGame.Core.Model /// 打断施法 [ 用于使敌人目标丢失 ] /// /// - public async Task InterruptCastingAsync(Character interrupter) + public void InterruptCasting(Character interrupter) { foreach (Character caster in _castingSkills.Keys) { @@ -3421,13 +3601,13 @@ namespace Milimoe.FunGame.Core.Model if (skillTarget.Targets.Contains(interrupter)) { Skill skill = skillTarget.Skill; - WriteLine($"[ {interrupter} ] 打断了 [ {caster} ] 的施法!!"); + WriteLine($"[ {interrupter} ] 人间蒸发了。[ {caster} ] 丢失了施法目标!!"); List effects = [.. caster.Effects.Union(interrupter.Effects).Distinct().Where(e => e.IsInEffect)]; foreach (Effect effect in effects) { effect.OnSkillCastInterrupted(caster, skill, interrupter); } - await OnInterruptCastingAsync(caster, skill, interrupter); + OnInterruptCastingEvent(caster, skill, interrupter); } } } @@ -3436,7 +3616,7 @@ namespace Milimoe.FunGame.Core.Model /// 设置角色复活 /// /// - public async Task SetCharacterRespawn(Character character) + public void SetCharacterRespawn(Character character) { double hardnessTime = 5; character.Respawn(_original[character.Guid]); @@ -3454,7 +3634,7 @@ namespace Milimoe.FunGame.Core.Model dp = new(); _decisionPoints[character] = dp; } - await OnQueueUpdatedAsync(_queue, character, dp, hardnessTime, QueueUpdatedReason.Respawn, "设置角色复活后的硬直时间。"); + OnQueueUpdatedEvent(_queue, character, dp, hardnessTime, QueueUpdatedReason.Respawn, "设置角色复活后的硬直时间。"); } /// @@ -3462,13 +3642,13 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - public async Task SetCharacterPreCastSuperSkill(Character character, Skill skill) + public void SetCharacterPreCastSuperSkill(Character character, Skill skill) { if (_decisionPoints.TryGetValue(character, out DecisionPoints? dp) && dp != null) { - if (dp.CurrentDecisionPoints < 3) + if (dp.CurrentDecisionPoints < GameplayEquilibriumConstant.DecisionPointsCostSuperSkillOutOfTurn) { - WriteLine("[ " + character + " ] 决策点不足,无法预释放爆发技。"); + WriteLine($"[ {character} ] 决策点不足,无法预释放爆发技。决策点剩余:{dp.CurrentDecisionPoints} / {dp.MaxDecisionPoints}"); return; } } @@ -3486,11 +3666,14 @@ namespace Milimoe.FunGame.Core.Model } if (character.CharacterState == CharacterState.Actionable) { + dp.CurrentDecisionPoints -= GameplayEquilibriumConstant.DecisionPointsCostSuperSkillOutOfTurn; + _stats[character].UseDecisionPoints += GameplayEquilibriumConstant.DecisionPointsCostSuperSkillOutOfTurn; + _stats[character].TurnDecisions++; _castingSuperSkills[character] = skill; character.CharacterState = CharacterState.PreCastSuperSkill; _queue.Remove(character); _cutCount.Remove(character); - WriteLine("[ " + character + " ] 预释放了爆发技!!"); + WriteLine($"[ {character} ] 预释放了爆发技!!决策点剩余:{dp.CurrentDecisionPoints} / {dp.MaxDecisionPoints}"); int preCastSSCount = 0; double maxPreCastTime = 0; // 当前最大预释放时间 @@ -3521,8 +3704,8 @@ namespace Milimoe.FunGame.Core.Model double newHardnessTime = preCastSSCount > 0 ? Calculation.Round2Digits(maxPreCastTime + 0.01) : 0; AddCharacter(character, newHardnessTime, false); - skill.OnSkillCasting(this, character, []); - await OnQueueUpdatedAsync(_queue, character, dp, 0, QueueUpdatedReason.PreCastSuperSkill, "设置角色预释放爆发技的硬直时间。"); + skill.OnSkillCasting(this, character, [], []); + OnQueueUpdatedEvent(_queue, character, dp, 0, QueueUpdatedReason.PreCastSuperSkill, "设置角色预释放爆发技的硬直时间。"); } } @@ -3549,12 +3732,16 @@ namespace Milimoe.FunGame.Core.Model /// 是否使用插队保护机制 public void ChangeCharacterHardnessTime(Character character, double addValue, bool isPercentage, bool isCheckProtected) { + if (!_queue.Contains(character)) + { + return; + } double hardnessTime = _hardnessTimes[character]; if (isPercentage) { addValue = hardnessTime * addValue; } - hardnessTime += addValue; + hardnessTime = Calculation.Round2Digits(hardnessTime + addValue); if (hardnessTime <= 0) hardnessTime = 0; AddCharacter(character, hardnessTime, isCheckProtected); } @@ -3650,40 +3837,121 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected async Task CheckSkilledImmuneAsync(Character character, List targets, Skill skill, Item? item = null) + public void CheckSkilledImmune(Character character, List targets, Skill skill, Item? item = null) { Character[] loop = [.. targets]; foreach (Character target in loop) { - bool ignore = false; - bool isImmune = (skill.IsMagic && (target.ImmuneType & ImmuneType.Magical) == ImmuneType.Magical) || - (target.ImmuneType & ImmuneType.Skilled) == ImmuneType.Skilled || (target.ImmuneType & ImmuneType.All) == ImmuneType.All; - if (isImmune) + if (CheckSkilledImmune(character, target, skill, item)) + { + targets.Remove(target); + } + } + } + + /// + /// 免疫检定 + /// + /// + /// + /// + /// + /// + public bool CheckSkilledImmune(Character character, Character target, Skill skill, Item? item = null) + { + bool ignore = false; + bool isImmune = (skill.IsMagic && (target.ImmuneType & ImmuneType.Magical) == ImmuneType.Magical) || + (target.ImmuneType & ImmuneType.Skilled) == ImmuneType.Skilled || (target.ImmuneType & ImmuneType.All) == ImmuneType.All; + if (isImmune) + { + Effect[] effects = [.. skill.Effects.Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) + { + // 自带无视免疫 + if (effect.IgnoreImmune == ImmuneType.All || effect.IgnoreImmune == ImmuneType.Skilled || (skill.IsMagic && effect.IgnoreImmune == ImmuneType.Magical)) + { + ignore = true; + } + } + if (!ignore) { Character[] characters = [character, target]; - Effect[] effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).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)) + // 特效免疫检定不通过可无视免疫 + if (!effect.OnImmuneCheck(character, target, skill, item)) { ignore = true; } } } - if (ignore) - { - isImmune = false; - } - if (isImmune) - { - targets.Remove(target); - WriteLine($"[ {character} ] 想要对 [ {target} ] 释放技能 [ {skill.Name} ],但是被 [ {target} ] 免疫了!"); - await OnCharacterImmunedAsync(character, target, skill, item); - } } - await Task.CompletedTask; - return; + if (ignore) + { + isImmune = false; + } + if (isImmune) + { + WriteLine($"[ {character} ] 想要对 [ {target} ] 释放技能 [ {skill.Name} ],但是被 [ {target} ] 免疫了!"); + OnCharacterImmunedEvent(character, target, skill, item); + } + return isImmune; + } + + /// + /// 特效豁免检定 + /// + /// + /// + /// + /// true - 豁免成功等效于闪避 + /// + public bool CheckExemption(Character character, Character? source, Effect effect, bool isEvade) + { + double exemption = effect.ExemptionType switch + { + PrimaryAttribute.STR => character.STRExemption, + PrimaryAttribute.AGI => character.AGIExemption, + PrimaryAttribute.INT => character.INTExemption, + _ => 0 + }; + double dice = Random.Shared.NextDouble(); + if (dice < exemption) + { + if (isEvade) + { + WriteLine($"[ {source} ] 想要对 [ {character} ] 施加 [ {effect.Name} ],但 [ {character} ] 的{CharacterSet.GetPrimaryAttributeName(effect.ExemptionType)}豁免检定通过,免疫了该效果!"); + } + else + { + string description = ""; + if (effect.Durative && effect.RemainDuration > 0) + { + // 随机减小 20% 至 50% + double reduce = Random.Shared.Next(2, 6) * 10; + reduce = effect.RemainDuration * (reduce / 100); + effect.RemainDuration -= reduce; + description = $"[ {effect.Name} ] 的持续时间减少了 {reduce:0.##} {GameplayEquilibriumConstant.InGameTime}!"; + } + else if (effect.RemainDurationTurn > 0) + { + effect.RemainDurationTurn--; + description = $"[ {effect.Name} ] 的持续时间减少了 1 回合!"; + if (effect.RemainDurationTurn <= 0) + { + effect.RemainDurationTurn = 0; + character.Effects.Remove(effect); + effect.OnEffectLost(character); + description += $"\r\n[ {character} ] 失去了 [ {effect.Name} ] 效果。"; + } + } + WriteLine($"[ {character} ] 的{CharacterSet.GetPrimaryAttributeName(effect.ExemptionType)}豁免检定通过!{description}"); + } + OnCharacterExemptionEvent(character, source, effect.Skill, effect.Skill.Item, isEvade); + return true; + } + return false; } #endregion @@ -3787,15 +4055,38 @@ namespace Milimoe.FunGame.Core.Model return item; } + /// + /// 向角色(或控制该角色的玩家)进行询问并取得答复 + /// + /// + /// + /// + /// + public Dictionary Inquiry(Character character, string topic, Dictionary args) + { + if (!_decisionPoints.TryGetValue(character, out DecisionPoints? dp) || dp is null) + { + dp = new(); + _decisionPoints[character] = dp; + } + Dictionary response = OnCharacterInquiryEvent(character, dp, topic, args); + Effect[] effects = [.. character.Effects.Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) + { + effect.OnCharacterInquiry(character, topic, args, response); + } + return response; + } + #endregion #region 事件 - public delegate Task TurnStartEventHandler(GamingQueue queue, Character character, DecisionPoints dp, List enemys, List teammates, List skills, List items); + public delegate bool TurnStartEventHandler(GamingQueue queue, Character character, DecisionPoints dp, List enemys, List teammates, List skills, List items); /// /// 回合开始事件 /// - public event TurnStartEventHandler? TurnStart; + public event TurnStartEventHandler? TurnStartEvent; /// /// 回合开始事件 /// @@ -3806,32 +4097,32 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected async Task OnTurnStartAsync(Character character, DecisionPoints dp, List enemys, List teammates, List skills, List items) + protected bool OnTurnStartEvent(Character character, DecisionPoints dp, List enemys, List teammates, List skills, List items) { - return await (TurnStart?.Invoke(this, character, dp, enemys, teammates, skills, items) ?? Task.FromResult(true)); + return TurnStartEvent?.Invoke(this, character, dp, enemys, teammates, skills, items) ?? true; } - public delegate Task TurnEndEventHandler(GamingQueue queue, Character character, DecisionPoints dp); + public delegate void TurnEndEventHandler(GamingQueue queue, Character character, DecisionPoints dp); /// /// 回合结束事件 /// - public event TurnEndEventHandler? TurnEnd; + public event TurnEndEventHandler? TurnEndEvent; /// /// 回合结束事件 /// /// /// /// - protected async Task OnTurnEndAsync(Character character, DecisionPoints dp) + protected void OnTurnEndEvent(Character character, DecisionPoints dp) { - await (TurnEnd?.Invoke(this, character, dp) ?? Task.CompletedTask); + TurnEndEvent?.Invoke(this, character, dp); } - public delegate Task DecideActionEventHandler(GamingQueue queue, Character character, DecisionPoints dp, List enemys, List teammates, List skills, List items); + public delegate CharacterActionType DecideActionEventHandler(GamingQueue queue, Character character, DecisionPoints dp, List enemys, List teammates, List skills, List items); /// /// 决定角色的行动事件 /// - public event DecideActionEventHandler? DecideAction; + public event DecideActionEventHandler? DecideActionEvent; /// /// 决定角色的行动事件 /// @@ -3842,48 +4133,48 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected async Task OnDecideActionAsync(Character character, DecisionPoints dp, List enemys, List teammates, List skills, List items) + protected CharacterActionType OnDecideActionEvent(Character character, DecisionPoints dp, List enemys, List teammates, List skills, List items) { - return await (DecideAction?.Invoke(this, character, dp, enemys, teammates, skills, items) ?? Task.FromResult(CharacterActionType.None)); + return DecideActionEvent?.Invoke(this, character, dp, enemys, teammates, skills, items) ?? CharacterActionType.None; } - public delegate Task SelectSkillEventHandler(GamingQueue queue, Character character, List skills); + public delegate Skill? SelectSkillEventHandler(GamingQueue queue, Character character, List skills); /// /// 角色需要选择一个技能 /// - public event SelectSkillEventHandler? SelectSkill; + public event SelectSkillEventHandler? SelectSkillEvent; /// /// 角色需要选择一个技能 /// /// /// /// - protected async Task OnSelectSkillAsync(Character character, List skills) + protected Skill? OnSelectSkillEvent(Character character, List skills) { - return await (SelectSkill?.Invoke(this, character, skills) ?? Task.FromResult(null)); + return SelectSkillEvent?.Invoke(this, character, skills) ?? null; } - public delegate Task SelectItemEventHandler(GamingQueue queue, Character character, List items); + public delegate Item? SelectItemEventHandler(GamingQueue queue, Character character, List items); /// /// 角色需要选择一个物品 /// - public event SelectItemEventHandler? SelectItem; + public event SelectItemEventHandler? SelectItemEvent; /// /// 角色需要选择一个物品 /// /// /// /// - protected async Task OnSelectItemAsync(Character character, List items) + protected Item? OnSelectItemEvent(Character character, List items) { - return await (SelectItem?.Invoke(this, character, items) ?? Task.FromResult(null)); + return SelectItemEvent?.Invoke(this, character, items) ?? null; } - public delegate Task SelectTargetGridEventHandler(GamingQueue queue, Character character, List enemys, List teammates, GameMap map, List moveRange); + public delegate Grid SelectTargetGridEventHandler(GamingQueue queue, Character character, List enemys, List teammates, GameMap map, List moveRange); /// /// 选取移动目标事件 /// - public event SelectTargetGridEventHandler? SelectTargetGrid; + public event SelectTargetGridEventHandler? SelectTargetGridEvent; /// /// 选取移动目标事件 /// @@ -3893,16 +4184,16 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected async Task OnSelectTargetGridAsync(Character character, List enemys, List teammates, GameMap map, List moveRange) + protected Grid OnSelectTargetGridEvent(Character character, List enemys, List teammates, GameMap map, List moveRange) { - return await (SelectTargetGrid?.Invoke(this, character, enemys, teammates, map, moveRange) ?? Task.FromResult(Grid.Empty)); + return SelectTargetGridEvent?.Invoke(this, character, enemys, teammates, map, moveRange) ?? Grid.Empty; } - public delegate Task> SelectSkillTargetsEventHandler(GamingQueue queue, Character caster, Skill skill, List enemys, List teammates, List castRange); + public delegate List SelectSkillTargetsEventHandler(GamingQueue queue, Character caster, Skill skill, List enemys, List teammates, List castRange); /// /// 选取技能目标事件 /// - public event SelectSkillTargetsEventHandler? SelectSkillTargets; + public event SelectSkillTargetsEventHandler? SelectSkillTargetsEvent; /// /// 选取技能目标事件 /// @@ -3912,16 +4203,35 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected async Task> OnSelectSkillTargetsAsync(Character caster, Skill skill, List enemys, List teammates, List castRange) + protected List OnSelectSkillTargetsEvent(Character caster, Skill skill, List enemys, List teammates, List castRange) { - return await (SelectSkillTargets?.Invoke(this, caster, skill, enemys, teammates, castRange) ?? Task.FromResult(new List())); + return SelectSkillTargetsEvent?.Invoke(this, caster, skill, enemys, teammates, castRange) ?? []; } - public delegate Task> SelectNormalAttackTargetsEventHandler(GamingQueue queue, Character character, NormalAttack attack, List enemys, List teammates, List attackRange); + public delegate List SelectNonDirectionalSkillTargetsEventHandler(GamingQueue queue, Character caster, Skill skill, List enemys, List teammates, List castRange); + /// + /// 选取非指向性技能目标事件 + /// + public event SelectNonDirectionalSkillTargetsEventHandler? SelectNonDirectionalSkillTargetsEvent; + /// + /// 选取非指向性技能目标事件 + /// + /// + /// + /// + /// + /// + /// + protected List OnSelectNonDirectionalSkillTargetsEvent(Character caster, Skill skill, List enemys, List teammates, List castRange) + { + return SelectNonDirectionalSkillTargetsEvent?.Invoke(this, caster, skill, enemys, teammates, castRange) ?? []; + } + + public delegate List SelectNormalAttackTargetsEventHandler(GamingQueue queue, Character character, NormalAttack attack, List enemys, List teammates, List attackRange); /// /// 选取普通攻击目标事件 /// - public event SelectNormalAttackTargetsEventHandler? SelectNormalAttackTargets; + public event SelectNormalAttackTargetsEventHandler? SelectNormalAttackTargetsEvent; /// /// 选取普通攻击目标事件 /// @@ -3931,16 +4241,16 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected async Task> OnSelectNormalAttackTargetsAsync(Character character, NormalAttack attack, List enemys, List teammates, List attackRange) + protected List OnSelectNormalAttackTargetsEvent(Character character, NormalAttack attack, List enemys, List teammates, List attackRange) { - return await (SelectNormalAttackTargets?.Invoke(this, character, attack, enemys, teammates, attackRange) ?? Task.FromResult(new List())); + return SelectNormalAttackTargetsEvent?.Invoke(this, character, attack, enemys, teammates, attackRange) ?? []; } - public delegate Task InterruptCastingEventHandler(GamingQueue queue, Character cast, Skill? skill, Character interrupter); + public delegate void InterruptCastingEventHandler(GamingQueue queue, Character cast, Skill? skill, Character interrupter); /// /// 打断施法事件 /// - public event InterruptCastingEventHandler? InterruptCasting; + public event InterruptCastingEventHandler? InterruptCastingEvent; /// /// 打断施法事件 /// @@ -3948,64 +4258,64 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected async Task OnInterruptCastingAsync(Character cast, Skill skill, Character interrupter) + protected void OnInterruptCastingEvent(Character cast, Skill skill, Character interrupter) { - await (InterruptCasting?.Invoke(this, cast, skill, interrupter) ?? Task.CompletedTask); + InterruptCastingEvent?.Invoke(this, cast, skill, interrupter); } - public delegate Task DeathCalculationEventHandler(GamingQueue queue, Character killer, Character death); + public delegate bool DeathCalculationEventHandler(GamingQueue queue, Character killer, Character death); /// /// 死亡结算事件 /// - public event DeathCalculationEventHandler? DeathCalculation; + public event DeathCalculationEventHandler? DeathCalculationEvent; /// /// 死亡结算事件 /// /// /// /// - protected async Task OnDeathCalculationAsync(Character killer, Character death) + protected bool OnDeathCalculationEvent(Character killer, Character death) { - return await (DeathCalculation?.Invoke(this, killer, death) ?? Task.FromResult(true)); + return DeathCalculationEvent?.Invoke(this, killer, death) ?? true; } - public delegate Task DeathCalculationByTeammateEventHandler(GamingQueue queue, Character killer, Character death); + public delegate bool DeathCalculationByTeammateEventHandler(GamingQueue queue, Character killer, Character death); /// /// 死亡结算(击杀队友)事件 /// - public event DeathCalculationEventHandler? DeathCalculationByTeammate; + public event DeathCalculationEventHandler? DeathCalculationByTeammateEvent; /// /// 死亡结算(击杀队友)事件 /// /// /// /// - protected async Task OnDeathCalculationByTeammateAsync(Character killer, Character death) + protected bool OnDeathCalculationByTeammateEvent(Character killer, Character death) { - return await (DeathCalculationByTeammate?.Invoke(this, killer, death) ?? Task.FromResult(true)); + return DeathCalculationByTeammateEvent?.Invoke(this, killer, death) ?? true; } - public delegate Task CharacterDeathEventHandler(GamingQueue queue, Character current, Character death); + public delegate bool CharacterDeathEventHandler(GamingQueue queue, Character current, Character death); /// /// 角色死亡事件,此事件位于 之后 /// - public event CharacterDeathEventHandler? CharacterDeath; + public event CharacterDeathEventHandler? CharacterDeathEvent; /// /// 角色死亡事件,此事件位于 之后 /// /// /// /// - protected async Task OnCharacterDeathAsync(Character current, Character death) + protected bool OnCharacterDeathEvent(Character current, Character death) { - return await (CharacterDeath?.Invoke(this, current, death) ?? Task.FromResult(true)); + return CharacterDeathEvent?.Invoke(this, current, death) ?? true; } - public delegate Task HealToTargetEventHandler(GamingQueue queue, Character actor, Character target, double heal, bool isRespawn); + public delegate void HealToTargetEventHandler(GamingQueue queue, Character actor, Character target, double heal, bool isRespawn); /// /// 治疗事件 /// - public event HealToTargetEventHandler? HealToTarget; + public event HealToTargetEventHandler? HealToTargetEvent; /// /// 治疗事件 /// @@ -4014,16 +4324,16 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected async Task OnHealToTargetAsync(Character actor, Character target, double heal, bool isRespawn) + protected void OnHealToTargetEvent(Character actor, Character target, double heal, bool isRespawn) { - await (HealToTarget?.Invoke(this, actor, target, heal, isRespawn) ?? Task.CompletedTask); + HealToTargetEvent?.Invoke(this, actor, target, heal, isRespawn); } - public delegate Task DamageToEnemyEventHandler(GamingQueue queue, Character actor, Character enemy, double damage, double actualDamage, bool isNormalAttack, DamageType damageType, MagicType magicType, DamageResult damageResult); + public delegate void DamageToEnemyEventHandler(GamingQueue queue, Character actor, Character enemy, double damage, double actualDamage, bool isNormalAttack, DamageType damageType, MagicType magicType, DamageResult damageResult); /// /// 造成伤害事件 /// - public event DamageToEnemyEventHandler? DamageToEnemy; + public event DamageToEnemyEventHandler? DamageToEnemyEvent; /// /// 造成伤害事件 /// @@ -4036,16 +4346,16 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected async Task OnDamageToEnemyAsync(Character actor, Character enemy, double damage, double actualDamage, bool isNormalAttack, DamageType damageType, MagicType magicType, DamageResult damageResult) + protected void OnDamageToEnemyEvent(Character actor, Character enemy, double damage, double actualDamage, bool isNormalAttack, DamageType damageType, MagicType magicType, DamageResult damageResult) { - await (DamageToEnemy?.Invoke(this, actor, enemy, damage, actualDamage, isNormalAttack, damageType, magicType, damageResult) ?? Task.CompletedTask); + DamageToEnemyEvent?.Invoke(this, actor, enemy, damage, actualDamage, isNormalAttack, damageType, magicType, damageResult); } - public delegate Task CharacterNormalAttackEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, List targets); + public delegate void CharacterNormalAttackEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, List targets); /// /// 角色普通攻击事件 /// - public event CharacterNormalAttackEventHandler? CharacterNormalAttack; + public event CharacterNormalAttackEventHandler? CharacterNormalAttackEvent; /// /// 角色普通攻击事件 /// @@ -4053,16 +4363,16 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected async Task OnCharacterNormalAttackAsync(Character actor, DecisionPoints dp, List targets) + protected void OnCharacterNormalAttackEvent(Character actor, DecisionPoints dp, List targets) { - await (CharacterNormalAttack?.Invoke(this, actor, dp, targets) ?? Task.CompletedTask); + CharacterNormalAttackEvent?.Invoke(this, actor, dp, targets); } - public delegate Task CharacterPreCastSkillEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, SkillTarget skillTarget); + public delegate void CharacterPreCastSkillEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, SkillTarget skillTarget); /// /// 角色吟唱技能事件(包括直接释放战技) /// - public event CharacterPreCastSkillEventHandler? CharacterPreCastSkill; + public event CharacterPreCastSkillEventHandler? CharacterPreCastSkillEvent; /// /// 角色吟唱技能事件(包括直接释放战技) /// @@ -4070,16 +4380,16 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected async Task OnCharacterPreCastSkillAsync(Character actor, DecisionPoints dp, SkillTarget skillTarget) + protected void OnCharacterPreCastSkillEvent(Character actor, DecisionPoints dp, SkillTarget skillTarget) { - await (CharacterPreCastSkill?.Invoke(this, actor, dp, skillTarget) ?? Task.CompletedTask); + CharacterPreCastSkillEvent?.Invoke(this, actor, dp, skillTarget); } - public delegate Task CharacterCastSkillEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, SkillTarget skillTarget, double cost); + public delegate void CharacterCastSkillEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, SkillTarget skillTarget, double cost); /// /// 角色释放技能事件 /// - public event CharacterCastSkillEventHandler? CharacterCastSkill; + public event CharacterCastSkillEventHandler? CharacterCastSkillEvent; /// /// 角色释放技能事件 /// @@ -4088,16 +4398,16 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected async Task OnCharacterCastSkillAsync(Character actor, DecisionPoints dp, SkillTarget skillTarget, double cost) + protected void OnCharacterCastSkillEvent(Character actor, DecisionPoints dp, SkillTarget skillTarget, double cost) { - await (CharacterCastSkill?.Invoke(this, actor, dp, skillTarget, cost) ?? Task.CompletedTask); + CharacterCastSkillEvent?.Invoke(this, actor, dp, skillTarget, cost); } - public delegate Task CharacterUseItemEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, Item item, List targets); + public delegate void CharacterUseItemEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, Item item, List targets); /// /// 角色使用物品事件 /// - public event CharacterUseItemEventHandler? CharacterUseItem; + public event CharacterUseItemEventHandler? CharacterUseItemEvent; /// /// 角色使用物品事件 /// @@ -4106,16 +4416,16 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected async Task OnCharacterUseItemAsync(Character actor, DecisionPoints dp, Item item, List targets) + protected void OnCharacterUseItemEvent(Character actor, DecisionPoints dp, Item item, List targets) { - await (CharacterUseItem?.Invoke(this, actor, dp, item, targets) ?? Task.CompletedTask); + CharacterUseItemEvent?.Invoke(this, actor, dp, item, targets); } - public delegate Task CharacterCastItemSkillEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, Item item, SkillTarget skillTarget, double costMP, double costEP); + public delegate void CharacterCastItemSkillEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, Item item, SkillTarget skillTarget, double costMP, double costEP); /// /// 角色释放物品的技能事件 /// - public event CharacterCastItemSkillEventHandler? CharacterCastItemSkill; + public event CharacterCastItemSkillEventHandler? CharacterCastItemSkillEvent; /// /// 角色释放物品的技能事件 /// @@ -4126,16 +4436,16 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected async Task OnCharacterCastItemSkillAsync(Character actor, DecisionPoints dp, Item item, SkillTarget skillTarget, double costMP, double costEP) + protected void OnCharacterCastItemSkillEvent(Character actor, DecisionPoints dp, Item item, SkillTarget skillTarget, double costMP, double costEP) { - await (CharacterCastItemSkill?.Invoke(this, actor, dp, item, skillTarget, costMP, costEP) ?? Task.CompletedTask); + CharacterCastItemSkillEvent?.Invoke(this, actor, dp, item, skillTarget, costMP, costEP); } - public delegate Task CharacterImmunedEventHandler(GamingQueue queue, Character character, Character immune, ISkill skill, Item? item = null); + public delegate void CharacterImmunedEventHandler(GamingQueue queue, Character character, Character immune, ISkill skill, Item? item = null); /// /// 角色免疫事件 /// - public event CharacterImmunedEventHandler? CharacterImmuned; + public event CharacterImmunedEventHandler? CharacterImmunedEvent; /// /// 角色免疫事件 /// @@ -4144,48 +4454,67 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected async Task OnCharacterImmunedAsync(Character character, Character immune, ISkill skill, Item? item = null) + protected void OnCharacterImmunedEvent(Character character, Character immune, ISkill skill, Item? item = null) { - await (CharacterImmuned?.Invoke(this, character, immune, skill, item) ?? Task.CompletedTask); + CharacterImmunedEvent?.Invoke(this, character, immune, skill, item); } - public delegate Task CharacterDoNothingEventHandler(GamingQueue queue, Character actor, DecisionPoints dp); + public delegate void CharacterExemptionEventHandler(GamingQueue queue, Character character, Character? source, ISkill skill, Item? item = null, bool isEvade = false); + /// + /// 角色豁免事件 + /// + public event CharacterExemptionEventHandler? CharacterExemptionEvent; + /// + /// 角色豁免事件 + /// + /// + /// + /// + /// + /// + /// + protected void OnCharacterExemptionEvent(Character character, Character? source, ISkill skill, Item? item = null, bool isEvade = false) + { + CharacterExemptionEvent?.Invoke(this, character, source, skill, item, isEvade); + } + + public delegate void CharacterDoNothingEventHandler(GamingQueue queue, Character actor, DecisionPoints dp); /// /// 角色主动结束回合事件(区别于放弃行动,这个是主动的) /// - public event CharacterDoNothingEventHandler? CharacterDoNothing; + public event CharacterDoNothingEventHandler? CharacterDoNothingEvent; /// /// 角色主动结束回合事件(区别于放弃行动,这个是主动的) /// /// /// /// - protected async Task OnCharacterDoNothingAsync(Character actor, DecisionPoints dp) + protected void OnCharacterDoNothingEvent(Character actor, DecisionPoints dp) { - await (CharacterDoNothing?.Invoke(this, actor, dp) ?? Task.CompletedTask); + CharacterDoNothingEvent?.Invoke(this, actor, dp); } - public delegate Task CharacterGiveUpEventHandler(GamingQueue queue, Character actor, DecisionPoints dp); + public delegate void CharacterGiveUpEventHandler(GamingQueue queue, Character actor, DecisionPoints dp); /// /// 角色放弃行动事件 /// - public event CharacterGiveUpEventHandler? CharacterGiveUp; + public event CharacterGiveUpEventHandler? CharacterGiveUpEvent; /// /// 角色放弃行动事件 /// /// /// /// - protected async Task OnCharacterGiveUpAsync(Character actor, DecisionPoints dp) + protected void OnCharacterGiveUpEvent(Character actor, DecisionPoints dp) { - await (CharacterGiveUp?.Invoke(this, actor, dp) ?? Task.CompletedTask); + CharacterGiveUpEvent?.Invoke(this, actor, dp); } - public delegate Task CharacterMoveEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, Grid grid); + public delegate void CharacterMoveEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, Grid grid); /// /// 角色移动事件 /// - public event CharacterMoveEventHandler? CharacterMove; + public event CharacterMoveEventHandler? CharacterMoveEvent; /// /// 角色移动事件 /// @@ -4193,31 +4522,31 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected async Task OnCharacterMoveAsync(Character actor, DecisionPoints dp, Grid grid) + protected void OnCharacterMoveEvent(Character actor, DecisionPoints dp, Grid grid) { - await (CharacterMove?.Invoke(this, actor, dp, grid) ?? Task.CompletedTask); + CharacterMoveEvent?.Invoke(this, actor, dp, grid); } - public delegate Task GameEndEventHandler(GamingQueue queue, Character winner); + public delegate bool GameEndEventHandler(GamingQueue queue, Character winner); /// /// 游戏结束事件 /// - public event GameEndEventHandler? GameEnd; + public event GameEndEventHandler? GameEndEvent; /// /// 游戏结束事件 /// /// /// - protected async Task OnGameEndAsync(Character winner) + protected bool OnGameEndEvent(Character winner) { - return await (GameEnd?.Invoke(this, winner) ?? Task.FromResult(true)); + return GameEndEvent?.Invoke(this, winner) ?? true; } - public delegate Task QueueUpdatedEventHandler(GamingQueue queue, List characters, Character character, DecisionPoints dp, double hardnessTime, QueueUpdatedReason reason, string msg); + public delegate void QueueUpdatedEventHandler(GamingQueue queue, List characters, Character character, DecisionPoints dp, double hardnessTime, QueueUpdatedReason reason, string msg); /// /// 行动顺序表更新事件 /// - public event QueueUpdatedEventHandler? QueueUpdated; + public event QueueUpdatedEventHandler? QueueUpdatedEvent; /// /// 行动顺序表更新事件 /// @@ -4228,16 +4557,16 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected async Task OnQueueUpdatedAsync(List characters, Character character, DecisionPoints dp, double hardnessTime, QueueUpdatedReason reason, string msg = "") + protected void OnQueueUpdatedEvent(List characters, Character character, DecisionPoints dp, double hardnessTime, QueueUpdatedReason reason, string msg = "") { - await (QueueUpdated?.Invoke(this, characters, character, dp, hardnessTime, reason, msg) ?? Task.CompletedTask); + QueueUpdatedEvent?.Invoke(this, characters, character, dp, hardnessTime, reason, msg); } - public delegate Task CharacterActionTakenEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, CharacterActionType type, RoundRecord record); + public delegate void CharacterActionTakenEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, CharacterActionType type, RoundRecord record); /// /// 角色完成行动事件 /// - public event CharacterActionTakenEventHandler? CharacterActionTaken; + public event CharacterActionTakenEventHandler? CharacterActionTakenEvent; /// /// 角色完成行动事件 /// @@ -4246,16 +4575,16 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected async Task OnCharacterActionTakenAsync(Character actor, DecisionPoints dp, CharacterActionType type, RoundRecord record) + protected void OnCharacterActionTakenEvent(Character actor, DecisionPoints dp, CharacterActionType type, RoundRecord record) { - await (CharacterActionTaken?.Invoke(this, actor, dp, type, record) ?? Task.CompletedTask); + CharacterActionTakenEvent?.Invoke(this, actor, dp, type, record); } - public delegate Task CharacterDecisionCompletedEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, RoundRecord record); + public delegate void CharacterDecisionCompletedEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, RoundRecord record); /// /// 角色完成决策事件 /// - public event CharacterDecisionCompletedEventHandler? CharacterDecisionCompleted; + public event CharacterDecisionCompletedEventHandler? CharacterDecisionCompletedEvent; /// /// 角色完成决策事件 /// @@ -4263,9 +4592,27 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected async Task OnCharacterDecisionCompletedAsync(Character actor, DecisionPoints dp, RoundRecord record) + protected void OnCharacterDecisionCompletedEvent(Character actor, DecisionPoints dp, RoundRecord record) { - await (CharacterDecisionCompleted?.Invoke(this, actor, dp, record) ?? Task.CompletedTask); + CharacterDecisionCompletedEvent?.Invoke(this, actor, dp, record); + } + + public delegate Dictionary CharacterInquiryEventHandler(GamingQueue character, Character actor, DecisionPoints dp, string topic, Dictionary args); + /// + /// 角色询问反应事件 + /// + public event CharacterInquiryEventHandler? CharacterInquiryEvent; + /// + /// 角色询问反应事件 + /// + /// + /// + /// + /// + /// + protected Dictionary OnCharacterInquiryEvent(Character character, DecisionPoints dp, string topic, Dictionary args) + { + return CharacterInquiryEvent?.Invoke(this, character, dp, topic, args) ?? []; } #endregion diff --git a/Model/MixGamingQueue.cs b/Model/MixGamingQueue.cs index 294b740..0add851 100644 --- a/Model/MixGamingQueue.cs +++ b/Model/MixGamingQueue.cs @@ -14,7 +14,7 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected override async Task AfterDeathCalculation(Character death, Character killer) + protected override void AfterDeathCalculation(Character death, Character killer) { if (MaxRespawnTimes != 0 && MaxScoreToWin > 0) { @@ -25,12 +25,12 @@ namespace Milimoe.FunGame.Core.Model if (!_queue.Where(c => c != killer).Any()) { // 没有其他的角色了,游戏结束 - await EndGameInfo(killer); + EndGameInfo(killer); } if (MaxScoreToWin > 0 && _stats[killer].Kills >= MaxScoreToWin) { - await EndGameInfo(killer); + EndGameInfo(killer); return; } } @@ -41,9 +41,9 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected override async Task AfterCharacterAction(Character character, CharacterActionType type) + protected override bool AfterCharacterAction(Character character, CharacterActionType type) { - bool result = await base.AfterCharacterAction(character, type); + bool result = base.AfterCharacterAction(character, type); if (result) { if (MaxRespawnTimes != 0 && MaxScoreToWin > 0 && _stats[character].Kills >= MaxScoreToWin) @@ -57,7 +57,7 @@ namespace Milimoe.FunGame.Core.Model /// /// 游戏结束信息 /// - public async Task EndGameInfo(Character winner) + public void EndGameInfo(Character winner) { WriteLine("[ " + winner + " ] 是胜利者。"); foreach (Character character in _stats.OrderBy(kv => kv.Value.Kills) @@ -73,7 +73,7 @@ namespace Milimoe.FunGame.Core.Model _queue.Clear(); _isGameEnd = true; - if (!await OnGameEndAsync(winner)) + if (!OnGameEndEvent(winner)) { return; } diff --git a/Model/TeamGamingQueue.cs b/Model/TeamGamingQueue.cs index a2cbd1a..0225534 100644 --- a/Model/TeamGamingQueue.cs +++ b/Model/TeamGamingQueue.cs @@ -77,7 +77,7 @@ namespace Milimoe.FunGame.Core.Model /// 获取某角色的团队成员 /// /// - protected override List GetTeammates(Character character) + public override List GetTeammates(Character character) { foreach (string team in _teams.Keys) { @@ -95,7 +95,7 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected override async Task AfterCharacterDecision(Character character, DecisionPoints dp) + protected override void AfterCharacterDecision(Character character, DecisionPoints dp) { // 如果目标都是队友,会考虑非伤害型助攻 Team? team = GetTeam(character); @@ -103,7 +103,6 @@ namespace Milimoe.FunGame.Core.Model { SetNotDamageAssistTime(character, LastRound.Targets.Values.SelectMany(c => c).Where(team.IsOnThisTeam)); } - else await Task.CompletedTask; } /// @@ -112,9 +111,9 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected override async Task AfterCharacterAction(Character character, CharacterActionType type) + protected override bool AfterCharacterAction(Character character, CharacterActionType type) { - bool result = await base.AfterCharacterAction(character, type); + bool result = base.AfterCharacterAction(character, type); if (result) { Team? team = GetTeam(character); @@ -132,7 +131,7 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected override async Task OnDeathCalculation(Character death, Character killer) + protected override void OnDeathCalculation(Character death, Character killer) { if (killer == death) { @@ -149,7 +148,6 @@ namespace Milimoe.FunGame.Core.Model } else team.Score++; } - else await Task.CompletedTask; } /// @@ -158,7 +156,7 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected override async Task AfterDeathCalculation(Character death, Character killer) + protected override void AfterDeathCalculation(Character death, Character killer) { Team? killTeam = GetTeam(killer); Team? deathTeam = GetTeam(death); @@ -200,7 +198,7 @@ namespace Milimoe.FunGame.Core.Model if (!_teams.Keys.Where(str => str != killTeam.Name).Any()) { // 没有其他的团队了,游戏结束 - await EndGameInfo(killTeam); + EndGameInfo(killTeam); return; } if (MaxScoreToWin > 0 && killTeam.Score >= MaxScoreToWin) @@ -209,7 +207,7 @@ namespace Milimoe.FunGame.Core.Model combinedTeams.Remove(killTeam); _eliminatedTeams.Clear(); _eliminatedTeams.AddRange(combinedTeams.OrderByDescending(t => t.Score)); - await EndGameInfo(killTeam); + EndGameInfo(killTeam); return; } } @@ -218,12 +216,12 @@ namespace Milimoe.FunGame.Core.Model /// /// 游戏结束信息 [ 团队版 ] /// - public async Task EndGameInfo(Team winner) + public void EndGameInfo(Team winner) { winner.IsWinner = true; WriteLine("[ " + winner + " ] 是胜利者。"); - if (!await OnGameEndTeamAsync(winner)) + if (!OnGameEndTeamEvent(winner)) { return; } @@ -322,19 +320,19 @@ namespace Milimoe.FunGame.Core.Model } - public delegate Task GameEndTeamEventHandler(TeamGamingQueue queue, Team winner); + public delegate bool GameEndTeamEventHandler(TeamGamingQueue queue, Team winner); /// /// 游戏结束事件(团队版) /// - public event GameEndTeamEventHandler? GameEndTeam; + public event GameEndTeamEventHandler? GameEndTeamEvent; /// /// 游戏结束事件(团队版) /// /// /// - protected async Task OnGameEndTeamAsync(Team winner) + protected bool OnGameEndTeamEvent(Team winner) { - return await (GameEndTeam?.Invoke(this, winner) ?? Task.FromResult(true)); + return GameEndTeamEvent?.Invoke(this, winner) ?? true; } } }