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;
}
}
}