mirror of
https://github.com/project-redbud/FunGame-Core.git
synced 2026-01-19 14:08:22 +00:00
支持非指向性技能目标选取;删除战斗框架的全部异步;添加豁免机制和指向性扩散 (#145)
* 支持非指向性技能和指向性技能的扩散 * 添加豁免机制,优化非指向性寻路算法 * 删除战斗框架的全部异步;添加非指向性无目标阻止释放;添加直线宽度;修改扇形算法 * 添加了新的特效钩子;添加了决策点相关统计;添加伤害计算选项;开放新事件和 API
This commit is contained in:
parent
ae1135ce06
commit
310f672ed4
@ -25,7 +25,7 @@ namespace Milimoe.FunGame.Core.Controller
|
|||||||
/// <param name="selectableEnemys">场上能够选取的敌人</param>
|
/// <param name="selectableEnemys">场上能够选取的敌人</param>
|
||||||
/// <param name="selectableTeammates">场上能够选取的队友</param>
|
/// <param name="selectableTeammates">场上能够选取的队友</param>
|
||||||
/// <returns>包含最佳行动的AIDecision对象</returns>
|
/// <returns>包含最佳行动的AIDecision对象</returns>
|
||||||
public async Task<AIDecision> DecideAIActionAsync(Character character, DecisionPoints dp, Grid startGrid, List<Grid> allPossibleMoveGrids,
|
public AIDecision DecideAIAction(Character character, DecisionPoints dp, Grid startGrid, List<Grid> allPossibleMoveGrids,
|
||||||
List<Skill> availableSkills, List<Item> availableItems, List<Character> allEnemysInGame, List<Character> allTeammatesInGame,
|
List<Skill> availableSkills, List<Item> availableItems, List<Character> allEnemysInGame, List<Character> allTeammatesInGame,
|
||||||
List<Character> selectableEnemys, List<Character> selectableTeammates)
|
List<Character> selectableEnemys, List<Character> selectableTeammates)
|
||||||
{
|
{
|
||||||
@ -86,31 +86,43 @@ namespace Milimoe.FunGame.Core.Controller
|
|||||||
// 计算当前技能的可达格子
|
// 计算当前技能的可达格子
|
||||||
List<Grid> skillReachableGrids = _map.GetGridsByRange(potentialMoveGrid, skill.CastRange, true);
|
List<Grid> skillReachableGrids = _map.GetGridsByRange(potentialMoveGrid, skill.CastRange, true);
|
||||||
|
|
||||||
List<Character> 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<Character> skillReachableEnemys = [.. allEnemysInGame
|
||||||
.Where(c => skillReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c))
|
.Where(c => skillReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c))
|
||||||
.Distinct()];
|
.Distinct()];
|
||||||
List<Character> skillReachableTeammates = [.. allTeammatesInGame
|
List<Character> skillReachableTeammates = [.. allTeammatesInGame
|
||||||
.Where(c => skillReachableGrids.SelectMany(g => g.Characters).Contains(c) && selectableTeammates.Contains(c))
|
.Where(c => skillReachableGrids.SelectMany(g => g.Characters).Contains(c) && selectableTeammates.Contains(c))
|
||||||
.Distinct()];
|
.Distinct()];
|
||||||
|
|
||||||
// 检查是否有可用的目标(敌人或队友,取决于技能类型)
|
// 检查是否有可用的目标(敌人或队友,取决于技能类型)
|
||||||
if (skillReachableEnemys.Count > 0 || skillReachableTeammates.Count > 0)
|
if (skillReachableEnemys.Count > 0 || skillReachableTeammates.Count > 0)
|
||||||
{
|
|
||||||
// 将筛选后的目标列表传递给 SelectTargets
|
|
||||||
List<Character> targets = SelectTargets(character, skill, skillReachableEnemys, skillReachableTeammates);
|
|
||||||
if (targets.Count > 0)
|
|
||||||
{
|
{
|
||||||
double currentScore = EvaluateSkill(character, skill, targets, cost) - movePenalty;
|
// 将筛选后的目标列表传递给 SelectTargets
|
||||||
if (currentScore > bestDecision.Score)
|
List<Character> 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,
|
bestDecision = new AIDecision
|
||||||
TargetMoveGrid = potentialMoveGrid,
|
{
|
||||||
SkillToUse = skill,
|
ActionType = CharacterActionType.PreCastSkill,
|
||||||
Targets = targets,
|
TargetMoveGrid = potentialMoveGrid,
|
||||||
Score = currentScore
|
SkillToUse = skill,
|
||||||
};
|
Targets = targets,
|
||||||
|
Score = currentScore
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -126,32 +138,44 @@ namespace Milimoe.FunGame.Core.Controller
|
|||||||
// 计算当前物品技能的可达格子
|
// 计算当前物品技能的可达格子
|
||||||
List<Grid> itemSkillReachableGrids = _map.GetGridsByRange(potentialMoveGrid, itemSkill.CastRange, true);
|
List<Grid> itemSkillReachableGrids = _map.GetGridsByRange(potentialMoveGrid, itemSkill.CastRange, true);
|
||||||
|
|
||||||
List<Character> 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<Character> itemSkillReachableEnemys = [.. allEnemysInGame
|
||||||
.Where(c => itemSkillReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c))
|
.Where(c => itemSkillReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c))
|
||||||
.Distinct()];
|
.Distinct()];
|
||||||
List<Character> itemSkillReachableTeammates = [.. allTeammatesInGame
|
List<Character> itemSkillReachableTeammates = [.. allTeammatesInGame
|
||||||
.Where(c => itemSkillReachableGrids.SelectMany(g => g.Characters).Contains(c) && selectableTeammates.Contains(c))
|
.Where(c => itemSkillReachableGrids.SelectMany(g => g.Characters).Contains(c) && selectableTeammates.Contains(c))
|
||||||
.Distinct()];
|
.Distinct()];
|
||||||
|
|
||||||
// 检查是否有可用的目标
|
// 检查是否有可用的目标
|
||||||
if (itemSkillReachableEnemys.Count > 0 || itemSkillReachableTeammates.Count > 0)
|
if (itemSkillReachableEnemys.Count > 0 || itemSkillReachableTeammates.Count > 0)
|
||||||
{
|
|
||||||
// 将筛选后的目标列表传递给 SelectTargets
|
|
||||||
List<Character> targetsForItem = SelectTargets(character, itemSkill, itemSkillReachableEnemys, itemSkillReachableTeammates);
|
|
||||||
if (targetsForItem.Count > 0)
|
|
||||||
{
|
{
|
||||||
double currentScore = EvaluateItem(character, item, targetsForItem, cost) - movePenalty;
|
// 将筛选后的目标列表传递给 SelectTargets
|
||||||
if (currentScore > bestDecision.Score)
|
List<Character> 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,
|
bestDecision = new AIDecision
|
||||||
TargetMoveGrid = potentialMoveGrid,
|
{
|
||||||
ItemToUse = item,
|
ActionType = CharacterActionType.UseItem,
|
||||||
SkillToUse = itemSkill,
|
TargetMoveGrid = potentialMoveGrid,
|
||||||
Targets = targetsForItem,
|
ItemToUse = item,
|
||||||
Score = currentScore
|
SkillToUse = itemSkill,
|
||||||
};
|
Targets = targetsForItem,
|
||||||
|
Score = currentScore
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -216,7 +240,7 @@ namespace Milimoe.FunGame.Core.Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await Task.FromResult(bestDecision);
|
return bestDecision;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- AI 决策辅助方法 ---
|
// --- AI 决策辅助方法 ---
|
||||||
@ -251,6 +275,7 @@ namespace Milimoe.FunGame.Core.Controller
|
|||||||
(character.CharacterState != CharacterState.ActionRestricted || item.ItemType == ItemType.Consumable) && // 行动受限只能用消耗品
|
(character.CharacterState != CharacterState.ActionRestricted || item.ItemType == ItemType.Consumable) && // 行动受限只能用消耗品
|
||||||
character.CharacterState != CharacterState.BattleRestricted;
|
character.CharacterState != CharacterState.BattleRestricted;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 选择技能的最佳目标
|
/// 选择技能的最佳目标
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -302,6 +327,51 @@ namespace Milimoe.FunGame.Core.Controller
|
|||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 非指向性技能的评估
|
||||||
|
private AIDecision? EvaluateNonDirectionalSkill(Character character, Skill skill, Grid moveGrid, List<Grid> castableGrids, List<Character> allEnemys, List<Character> allTeammates, double cost)
|
||||||
|
{
|
||||||
|
double bestSkillScore = double.NegativeInfinity;
|
||||||
|
List<Grid> bestTargetGrids = [];
|
||||||
|
|
||||||
|
// 枚举所有可施放的格子作为潜在中心
|
||||||
|
foreach (Grid centerGrid in castableGrids)
|
||||||
|
{
|
||||||
|
// 计算该中心格子下的实际影响范围格子
|
||||||
|
List<Grid> effectGrids = skill.SelectNonDirectionalTargets(character, centerGrid, skill.SelectIncludeCharacterGrid);
|
||||||
|
|
||||||
|
// 计算实际影响的角色
|
||||||
|
List<Character> 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 评估物品的价值
|
/// 评估物品的价值
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -482,7 +482,7 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 生命回复力 = [ 与初始设定相关 ] [ 与力量相关 ] + 额外生命回复力
|
/// 生命回复力 = [ 与初始设定相关 ] [ 与力量相关 ] + 额外生命回复力
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double HR => InitialHR + STR * GameplayEquilibriumConstant.STRtoHRFactor + ExHR;
|
public double HR => Math.Max(0, InitialHR + STR * GameplayEquilibriumConstant.STRtoHRFactor + ExHR);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 额外生命回复力 [ 与技能和物品相关 ]
|
/// 额外生命回复力 [ 与技能和物品相关 ]
|
||||||
@ -498,7 +498,7 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 魔法回复力 = [ 与初始设定相关 ] [ 与智力相关 ] + 额外魔法回复力
|
/// 魔法回复力 = [ 与初始设定相关 ] [ 与智力相关 ] + 额外魔法回复力
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double MR => InitialMR + INT * GameplayEquilibriumConstant.INTtoMRFactor + ExMR;
|
public double MR => Math.Max(0, InitialMR + INT * GameplayEquilibriumConstant.INTtoMRFactor + ExMR);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 额外魔法回复力 [ 与技能和物品相关 ]
|
/// 额外魔法回复力 [ 与技能和物品相关 ]
|
||||||
@ -687,6 +687,21 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
[InitOptional]
|
[InitOptional]
|
||||||
public double INTGrowth { get; set; } = 0;
|
public double INTGrowth { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 力量豁免
|
||||||
|
/// </summary>
|
||||||
|
public double STRExemption => STR * GameplayEquilibriumConstant.STRtoExemptionRateMultiplier;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 敏捷豁免
|
||||||
|
/// </summary>
|
||||||
|
public double AGIExemption => AGI * GameplayEquilibriumConstant.AGItoExemptionRateMultiplier;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 智力豁免
|
||||||
|
/// </summary>
|
||||||
|
public double INTExemption => INT * GameplayEquilibriumConstant.INTtoExemptionRateMultiplier;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 行动速度 [ 初始设定 ]
|
/// 行动速度 [ 初始设定 ]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -809,7 +824,7 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
_ => baseMOV
|
_ => baseMOV
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return Math.Max(1, baseMOV + ExMOV);
|
return Math.Max(0, baseMOV + ExMOV);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -878,6 +893,16 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Shield Shield { get; set; }
|
public Shield Shield { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色是否是单位 [ 初始设定 ]
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool IsUnit { get; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 角色所属的上级角色 [ 战斗相关 ]
|
||||||
|
/// </summary>
|
||||||
|
public Character? Master { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 普通攻击对象
|
/// 普通攻击对象
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -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($"行动速度:{SPD:0.##}" + (exSPD != 0 ? $" [{InitialSPD:0.##} {(exSPD >= 0 ? "+" : "-")} {Math.Abs(exSPD):0.##}]" : "") + $" ({ActionCoefficient * 100:0.##}%)");
|
||||||
builder.AppendLine($"核心属性:{CharacterSet.GetPrimaryAttributeName(PrimaryAttribute)}");
|
builder.AppendLine($"核心属性:{CharacterSet.GetPrimaryAttributeName(PrimaryAttribute)}");
|
||||||
double exSTR = ExSTR + ExSTR2;
|
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;
|
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;
|
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($"生命回复:{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($"魔法回复:{MR:0.##}" + (ExMR != 0 ? $" [{InitialMR + INT * GameplayEquilibriumConstant.INTtoMRFactor:0.##} {(ExMR >= 0 ? "+" : "-")} {Math.Abs(ExMR):0.##}]" : ""));
|
||||||
builder.AppendLine($"暴击率:{CritRate * 100:0.##}%");
|
builder.AppendLine($"暴击率:{CritRate * 100:0.##}%");
|
||||||
@ -1474,6 +1499,7 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
builder.AppendLine("== 角色技能 ==");
|
builder.AppendLine("== 角色技能 ==");
|
||||||
foreach (Skill skill in Skills)
|
foreach (Skill skill in Skills)
|
||||||
{
|
{
|
||||||
|
skill.Character = this;
|
||||||
builder.Append(skill.ToString());
|
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($"行动速度:{SPD:0.##}" + (exSPD != 0 ? $" [{InitialSPD:0.##} {(exSPD >= 0 ? "+" : "-")} {Math.Abs(exSPD):0.##}]" : "") + $" ({ActionCoefficient * 100:0.##}%)");
|
||||||
builder.AppendLine($"核心属性:{CharacterSet.GetPrimaryAttributeName(PrimaryAttribute)}");
|
builder.AppendLine($"核心属性:{CharacterSet.GetPrimaryAttributeName(PrimaryAttribute)}");
|
||||||
double exSTR = ExSTR + ExSTR2;
|
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;
|
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;
|
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($"生命回复:{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($"魔法回复:{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($"行动速度:{SPD:0.##}" + (exSPD != 0 ? $" [{InitialSPD:0.##} {(exSPD >= 0 ? "+" : "-")} {Math.Abs(exSPD):0.##}]" : "") + $" ({ActionCoefficient * 100:0.##}%)");
|
||||||
builder.AppendLine($"核心属性:{CharacterSet.GetPrimaryAttributeName(PrimaryAttribute)}");
|
builder.AppendLine($"核心属性:{CharacterSet.GetPrimaryAttributeName(PrimaryAttribute)}");
|
||||||
double exSTR = ExSTR + ExSTR2;
|
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;
|
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;
|
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($"生命回复:{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($"魔法回复:{MR:0.##}" + (ExMR != 0 ? $" [{InitialMR + INT * GameplayEquilibriumConstant.INTtoMRFactor:0.##} {(ExMR >= 0 ? "+" : "-")} {Math.Abs(ExMR):0.##}]" : ""));
|
||||||
builder.AppendLine($"暴击率:{CritRate * 100:0.##}%");
|
builder.AppendLine($"暴击率:{CritRate * 100:0.##}%");
|
||||||
|
|||||||
@ -15,6 +15,11 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public override string Name { get; set; } = "";
|
public override string Name { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 单位标识
|
||||||
|
/// </summary>
|
||||||
|
public override bool IsUnit => true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取单位名称以及所属玩家
|
/// 获取单位名称以及所属玩家
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -311,7 +311,7 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
/// 局内使用物品触发
|
/// 局内使用物品触发
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<bool> UseItem(IGamingQueue queue, Character character, DecisionPoints dp, List<Character> enemys, List<Character> teammates)
|
public bool UseItem(IGamingQueue queue, Character character, DecisionPoints dp, List<Character> enemys, List<Character> teammates, List<Character> allEnemys, List<Character> allTeammates)
|
||||||
{
|
{
|
||||||
bool cancel = false;
|
bool cancel = false;
|
||||||
bool used = false;
|
bool used = false;
|
||||||
@ -328,7 +328,7 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
Grid? grid = Skills.Active.GamingQueue.Map.GetCharacterCurrentGrid(character);
|
Grid? grid = Skills.Active.GamingQueue.Map.GetCharacterCurrentGrid(character);
|
||||||
castRange = grid is null ? [] : Skills.Active.GamingQueue.Map.GetGridsByRange(grid, Skills.Active.CastRange, true);
|
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)
|
if (used)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -136,6 +136,48 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
/// 无视免疫类型
|
/// 无视免疫类型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual ImmuneType IgnoreImmune { get; set; } = ImmuneType.None;
|
public virtual ImmuneType IgnoreImmune { get; set; } = ImmuneType.None;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 豁免性的具体说明
|
||||||
|
/// </summary>
|
||||||
|
public virtual string ExemptionDescription
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
StringBuilder builder = new();
|
||||||
|
if (Exemptable)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"豁免类型:{CharacterSet.GetPrimaryAttributeName(ExemptionType)}");
|
||||||
|
builder.Append($"豁免持续时间:{(ExemptDuration ? "是" : "否")}");
|
||||||
|
}
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否可被属性豁免
|
||||||
|
/// </summary>
|
||||||
|
public bool Exemptable => ExemptionType != PrimaryAttribute.None;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 豁免所需的属性类型
|
||||||
|
/// </summary>
|
||||||
|
public virtual PrimaryAttribute ExemptionType
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _exemptionType ?? SkillSet.GetExemptionTypeByEffectType(EffectType);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_exemptionType = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 可豁免持续时间(每次减半/一回合)
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool ExemptDuration { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 效果描述
|
/// 效果描述
|
||||||
@ -341,11 +383,24 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="caster"></param>
|
/// <param name="caster"></param>
|
||||||
/// <param name="targets"></param>
|
/// <param name="targets"></param>
|
||||||
public virtual void OnSkillCasting(Character caster, List<Character> targets)
|
/// <param name="grids"></param>
|
||||||
|
public virtual void OnSkillCasting(Character caster, List<Character> targets, List<Grid> grids)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 技能吟唱被打断前触发
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="caster"></param>
|
||||||
|
/// <param name="skill"></param>
|
||||||
|
/// <param name="interrupter"></param>
|
||||||
|
/// <returns>返回 false 阻止打断</returns>
|
||||||
|
public virtual bool BeforeSkillCastWillBeInterrupted(Character caster, Skill skill, Character interrupter)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 技能吟唱被打断时
|
/// 技能吟唱被打断时
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -362,8 +417,9 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="caster"></param>
|
/// <param name="caster"></param>
|
||||||
/// <param name="targets"></param>
|
/// <param name="targets"></param>
|
||||||
|
/// <param name="grids"></param>
|
||||||
/// <param name="others"></param>
|
/// <param name="others"></param>
|
||||||
public virtual void OnSkillCasted(Character caster, List<Character> targets, Dictionary<string, object> others)
|
public virtual void OnSkillCasted(Character caster, List<Character> targets, List<Grid> grids, Dictionary<string, object> others)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -379,6 +435,18 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 在时间流逝期间应用生命/魔法回复前修改 [ 允许取消回复 ]
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <param name="hr"></param>
|
||||||
|
/// <param name="mr"></param>
|
||||||
|
/// <returns>返回 true 取消回复</returns>
|
||||||
|
public virtual bool BeforeApplyRecoveryAtTimeLapsing(Character character, ref double hr, ref double mr)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 时间流逝时
|
/// 时间流逝时
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -819,58 +887,72 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 对敌人造成技能伤害 [ 强烈建议使用此方法造成伤害而不是自行调用 <see cref="IGamingQueue.DamageToEnemyAsync"/> ]
|
/// 在角色取得询问反应的答复时触发
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <param name="topic"></param>
|
||||||
|
/// <param name="args"></param>
|
||||||
|
/// <param name="response"></param>
|
||||||
|
public virtual void OnCharacterInquiry(Character character, string topic, Dictionary<string, object> args, Dictionary<string, object> response)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 对敌人造成技能伤害 [ 强烈建议使用此方法造成伤害而不是自行调用 <see cref="IGamingQueue.DamageToEnemy"/> ]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="actor"></param>
|
/// <param name="actor"></param>
|
||||||
/// <param name="enemy"></param>
|
/// <param name="enemy"></param>
|
||||||
/// <param name="damageType"></param>
|
/// <param name="damageType"></param>
|
||||||
/// <param name="magicType"></param>
|
/// <param name="magicType"></param>
|
||||||
/// <param name="expectedDamage"></param>
|
/// <param name="expectedDamage"></param>
|
||||||
|
/// <param name="options"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
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;
|
if (GamingQueue is null) return DamageResult.Evaded;
|
||||||
int changeCount = 0;
|
int changeCount = 0;
|
||||||
DamageResult result = DamageResult.Normal;
|
DamageResult result = DamageResult.Normal;
|
||||||
double damage = expectedDamage;
|
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.DamageToEnemy(actor, enemy, damage, false, damageType, magicType, result, options);
|
||||||
GamingQueue.DamageToEnemyAsync(actor, enemy, damage, false, damageType, magicType, result);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 治疗一个目标 [ 强烈建议使用此方法而不是自行调用 <see cref="IGamingQueue.HealToTargetAsync"/> ]
|
/// 治疗一个目标 [ 强烈建议使用此方法而不是自行调用 <see cref="IGamingQueue.HealToTarget"/> ]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="actor"></param>
|
/// <param name="actor"></param>
|
||||||
/// <param name="target"></param>
|
/// <param name="target"></param>
|
||||||
/// <param name="heal"></param>
|
/// <param name="heal"></param>
|
||||||
/// <param name="canRespawn"></param>
|
/// <param name="canRespawn"></param>
|
||||||
public void HealToTarget(Character actor, Character target, double heal, bool canRespawn = false)
|
/// <param name="triggerEffects"></param>
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 打断施法 [ 尽可能的调用此方法而不是直接调用 <see cref="IGamingQueue.InterruptCastingAsync(Character, Character)"/>,以防止中断性变更 ]
|
/// 打断施法 [ 尽可能的调用此方法而不是直接调用 <see cref="IGamingQueue.InterruptCasting(Character, Character)"/>,以防止中断性变更 ]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="caster"></param>
|
/// <param name="caster"></param>
|
||||||
/// <param name="interrupter"></param>
|
/// <param name="interrupter"></param>
|
||||||
public void InterruptCasting(Character caster, Character interrupter)
|
public void InterruptCasting(Character caster, Character interrupter)
|
||||||
{
|
{
|
||||||
GamingQueue?.InterruptCastingAsync(caster, interrupter);
|
GamingQueue?.InterruptCasting(caster, interrupter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 打断施法 [ 用于使敌人目标丢失 ] [ 尽可能的调用此方法而不是直接调用 <see cref="IGamingQueue.InterruptCastingAsync(Character)"/>,以防止中断性变更 ]
|
/// 打断施法 [ 用于使敌人目标丢失 ] [ 尽可能的调用此方法而不是直接调用 <see cref="IGamingQueue.InterruptCasting(Character)"/>,以防止中断性变更 ]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="interrupter"></param>
|
/// <param name="interrupter"></param>
|
||||||
public void InterruptCasting(Character interrupter)
|
public void InterruptCasting(Character interrupter)
|
||||||
{
|
{
|
||||||
GamingQueue?.InterruptCastingAsync(interrupter);
|
GamingQueue?.InterruptCasting(interrupter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -1048,7 +1130,7 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Effect[] effects = [.. target.Effects.Where(e => e.IsInEffect && e.ShowInStatusBar)];
|
Effect[] effects = [.. target.Effects.Where(e => e.ShowInStatusBar)];
|
||||||
foreach (Effect effect in effects)
|
foreach (Effect effect in effects)
|
||||||
{
|
{
|
||||||
if (effect.OnEffectIsBeingDispelled(dispeller, target, this, isEnemy))
|
if (effect.OnEffectIsBeingDispelled(dispeller, target, this, isEnemy))
|
||||||
@ -1058,6 +1140,35 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 免疫检定 [ 尽可能的调用此方法而不是自己实现 ]
|
||||||
|
/// 先进行检定,再施加状态效果
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <param name="target"></param>
|
||||||
|
/// <param name="skill"></param>
|
||||||
|
/// <param name="item"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 技能豁免检定 [ 尽可能的调用此方法而不是自己实现 ]
|
||||||
|
/// 先进行检定,再施加状态效果
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <param name="target"></param>
|
||||||
|
/// <param name="effect"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool CheckExemption(Character character, Character target, Effect effect)
|
||||||
|
{
|
||||||
|
if (GamingQueue is null) return false;
|
||||||
|
return GamingQueue.CheckExemption(target, character, effect, true);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 修改角色的硬直时间 [ 尽可能的调用此方法而不是自己实现 ]
|
/// 修改角色的硬直时间 [ 尽可能的调用此方法而不是自己实现 ]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -1091,6 +1202,18 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
return GamingQueue?.IsCharacterInAIControlling(character) ?? false;
|
return GamingQueue?.IsCharacterInAIControlling(character) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 向角色发起询问反应事件 [ 尽可能的调用此方法而不是自己实现 ]
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <param name="topic"></param>
|
||||||
|
/// <param name="args"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Dictionary<string, object> Inquiry(Character character, string topic, Dictionary<string, object> args)
|
||||||
|
{
|
||||||
|
return GamingQueue?.Inquiry(character, topic, args) ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 添加角色应用的特效类型到回合记录中
|
/// 添加角色应用的特效类型到回合记录中
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -1206,5 +1329,10 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
/// 驱散描述
|
/// 驱散描述
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private string _dispelDescription = "";
|
private string _dispelDescription = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 豁免性
|
||||||
|
/// </summary>
|
||||||
|
private PrimaryAttribute? _exemptionType = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ using Milimoe.FunGame.Core.Api.Utility;
|
|||||||
using Milimoe.FunGame.Core.Interface.Base;
|
using Milimoe.FunGame.Core.Interface.Base;
|
||||||
using Milimoe.FunGame.Core.Interface.Entity;
|
using Milimoe.FunGame.Core.Interface.Entity;
|
||||||
using Milimoe.FunGame.Core.Library.Constant;
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
using Milimoe.FunGame.Core.Model;
|
||||||
|
|
||||||
namespace Milimoe.FunGame.Core.Entity
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
{
|
{
|
||||||
@ -336,8 +337,9 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="queue"></param>
|
/// <param name="queue"></param>
|
||||||
/// <param name="attacker"></param>
|
/// <param name="attacker"></param>
|
||||||
|
/// <param name="options"></param>
|
||||||
/// <param name="enemys"></param>
|
/// <param name="enemys"></param>
|
||||||
public void Attack(IGamingQueue queue, Character attacker, params IEnumerable<Character> enemys)
|
public void Attack(IGamingQueue queue, Character attacker, DamageCalculationOptions? options, params IEnumerable<Character> enemys)
|
||||||
{
|
{
|
||||||
if (!Enable)
|
if (!Enable)
|
||||||
{
|
{
|
||||||
@ -350,8 +352,8 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
queue.WriteLine($"[ {Character} ] 对 [ {enemy} ] 发起了普通攻击!");
|
queue.WriteLine($"[ {Character} ] 对 [ {enemy} ] 发起了普通攻击!");
|
||||||
double expected = Damage;
|
double expected = Damage;
|
||||||
int changeCount = 0;
|
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);
|
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.DamageToEnemyAsync(attacker, enemy, damage, true, IsMagic ? DamageType.Magical : DamageType.Physical, MagicType, result);
|
queue.DamageToEnemy(attacker, enemy, damage, true, IsMagic ? DamageType.Magical : DamageType.Physical, MagicType, result, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
using Milimoe.FunGame.Core.Api.Utility;
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
using Milimoe.FunGame.Core.Interface.Base;
|
using Milimoe.FunGame.Core.Interface.Base;
|
||||||
using Milimoe.FunGame.Core.Interface.Entity;
|
using Milimoe.FunGame.Core.Interface.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Library.Common.Addon;
|
||||||
using Milimoe.FunGame.Core.Library.Constant;
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
namespace Milimoe.FunGame.Core.Entity
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
@ -37,6 +38,11 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual string DispelDescription { get; set; } = "";
|
public virtual string DispelDescription { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 豁免性的描述
|
||||||
|
/// </summary>
|
||||||
|
public virtual string ExemptionDescription { get; set; } = "";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 释放技能时的口号
|
/// 释放技能时的口号
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -146,18 +152,39 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual bool IsNonDirectional { get; set; } = false;
|
public virtual bool IsNonDirectional { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 在非指向性技能选取目标格子时,包括有角色的格子,默认为 true。仅 <see cref="IsNonDirectional"/> = true 时有效。<para/>
|
||||||
|
/// 当此项为 false 时,必须设置 <see cref="AllowSelectNoCharacterGrid"/> = true,否则实际施法时会被拒绝。
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool SelectIncludeCharacterGrid { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否可以选择没有被角色占据的空地,为 false 时会阻止施法。仅 <see cref="IsNonDirectional"/> = true 时有效。<para/>
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool AllowSelectNoCharacterGrid { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否可以选择已死亡的角色。仅 <see cref="IsNonDirectional"/> = true 时有效。
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool AllowSelectDead { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 作用范围形状<para/>
|
/// 作用范围形状<para/>
|
||||||
/// <see cref="SkillRangeType.Diamond"/> - 菱形。默认的曼哈顿距离正方形<para/>
|
/// <see cref="SkillRangeType.Diamond"/> - 菱形。默认的曼哈顿距离正方形<para/>
|
||||||
/// <see cref="SkillRangeType.Circle"/> - 圆形。基于欧几里得距离的圆形<para/>
|
/// <see cref="SkillRangeType.Circle"/> - 圆形。基于欧几里得距离的圆形<para/>
|
||||||
/// <see cref="SkillRangeType.Square"/> - 正方形<para/>
|
/// <see cref="SkillRangeType.Square"/> - 正方形<para/>
|
||||||
/// <see cref="SkillRangeType.Line"/> - 施法者与目标之间的直线<para/>
|
/// <see cref="SkillRangeType.Line"/> - 施法者与目标之间的线段<para/>
|
||||||
/// <see cref="SkillRangeType.LinePass"/> - 施法者与目标所在的直线,贯穿至地图边缘<para/>
|
/// <see cref="SkillRangeType.LinePass"/> - 施法者与目标所在的直线,贯穿至地图边缘<para/>
|
||||||
/// <see cref="SkillRangeType.Sector"/> - 扇形<para/>
|
/// <see cref="SkillRangeType.Sector"/> - 扇形<para/>
|
||||||
/// 注意,该属性不影响选取目标的范围。选取目标的范围由 <see cref="Library.Common.Addon.GameMap"/> 决定。
|
/// 注意,该属性不影响选取目标的范围。选取目标的范围由 <see cref="GameMap"/> 决定。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual SkillRangeType SkillRangeType { get; set; } = SkillRangeType.Diamond;
|
public virtual SkillRangeType SkillRangeType { get; set; } = SkillRangeType.Diamond;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 扇形的角度。仅 <see cref="SkillRangeType"/> 为 <see cref="SkillRangeType.Sector"/> 时有效,默认值为 90 度。
|
||||||
|
/// </summary>
|
||||||
|
public virtual double SectorAngle { get; set; } = 90;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 选取角色的条件
|
/// 选取角色的条件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -376,7 +403,7 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
|
|
||||||
foreach (Character character in enemys)
|
foreach (Character character in enemys)
|
||||||
{
|
{
|
||||||
IEnumerable<Effect> effects = character.Effects.Where(e => e.IsInEffect);
|
IEnumerable<Effect> effects = Effects.Where(e => e.IsInEffect);
|
||||||
if (CanSelectEnemy && ((character.ImmuneType & checkType) == ImmuneType.None ||
|
if (CanSelectEnemy && ((character.ImmuneType & checkType) == ImmuneType.None ||
|
||||||
effects.Any(e => e.IgnoreImmune == ImmuneType.All || e.IgnoreImmune == ImmuneType.Skilled || (IsMagic && e.IgnoreImmune == ImmuneType.Magical))))
|
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)
|
foreach (Character character in teammates)
|
||||||
{
|
{
|
||||||
IEnumerable<Effect> effects = character.Effects.Where(e => e.IsInEffect);
|
|
||||||
if (CanSelectTeammate)
|
if (CanSelectTeammate)
|
||||||
{
|
{
|
||||||
selectable.Add(character);
|
selectable.Add(character);
|
||||||
@ -452,19 +478,146 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
return [.. targets.Distinct()];
|
return [.. targets.Distinct()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 默认行为:在指向性技能中,当 <see cref="CanSelectTargetRange"/> > 0 时,会额外选取一些被扩散的目标
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="caster"></param>
|
||||||
|
/// <param name="allEnemys"></param>
|
||||||
|
/// <param name="allTeammates"></param>
|
||||||
|
/// <param name="selected"></param>
|
||||||
|
/// <param name="union"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual List<Character> SelectTargetsByCanSelectTargetRange(Character caster, List<Character> allEnemys, List<Character> allTeammates, IEnumerable<Character> selected, bool union = true)
|
||||||
|
{
|
||||||
|
List<Grid> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 选取范围内的目标
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="caster"></param>
|
||||||
|
/// <param name="allEnemys"></param>
|
||||||
|
/// <param name="allTeammates"></param>
|
||||||
|
/// <param name="selected"></param>
|
||||||
|
/// <param name="range"></param>
|
||||||
|
/// <param name="union"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual List<Character> SelectTargetsByRange(Character caster, List<Character> allEnemys, List<Character> allTeammates, IEnumerable<Character> selected, IEnumerable<Grid> range, bool union = true)
|
||||||
|
{
|
||||||
|
List<Character> 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<Effect> 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()];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 选取非指向性目标
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="caster"></param>
|
||||||
|
/// <param name="targetGrid"></param>
|
||||||
|
/// <param name="includeCharacter"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual List<Grid> SelectNonDirectionalTargets(Character caster, Grid targetGrid, bool includeCharacter = false)
|
||||||
|
{
|
||||||
|
int range = CanSelectTargetRange;
|
||||||
|
List<Grid> 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<Grid> 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()];
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 技能开始吟唱时 [ 吟唱魔法、释放战技和爆发技、预释放爆发技均可触发 ]
|
/// 技能开始吟唱时 [ 吟唱魔法、释放战技和爆发技、预释放爆发技均可触发 ]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="queue"></param>
|
/// <param name="queue"></param>
|
||||||
/// <param name="caster"></param>
|
/// <param name="caster"></param>
|
||||||
/// <param name="targets"></param>
|
/// <param name="targets"></param>
|
||||||
public void OnSkillCasting(IGamingQueue queue, Character caster, List<Character> targets)
|
/// <param name="grids"></param>
|
||||||
|
public void OnSkillCasting(IGamingQueue queue, Character caster, List<Character> targets, List<Grid> grids)
|
||||||
{
|
{
|
||||||
GamingQueue = queue;
|
GamingQueue = queue;
|
||||||
foreach (Effect e in Effects)
|
foreach (Effect e in Effects)
|
||||||
{
|
{
|
||||||
e.GamingQueue = GamingQueue;
|
e.GamingQueue = GamingQueue;
|
||||||
e.OnSkillCasting(caster, targets);
|
e.OnSkillCasting(caster, targets, grids);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -483,13 +636,14 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
/// <param name="queue"></param>
|
/// <param name="queue"></param>
|
||||||
/// <param name="caster"></param>
|
/// <param name="caster"></param>
|
||||||
/// <param name="targets"></param>
|
/// <param name="targets"></param>
|
||||||
public void OnSkillCasted(IGamingQueue queue, Character caster, List<Character> targets)
|
/// <param name="grids"></param>
|
||||||
|
public void OnSkillCasted(IGamingQueue queue, Character caster, List<Character> targets, List<Grid> grids)
|
||||||
{
|
{
|
||||||
GamingQueue = queue;
|
GamingQueue = queue;
|
||||||
foreach (Effect e in Effects)
|
foreach (Effect e in Effects)
|
||||||
{
|
{
|
||||||
e.GamingQueue = GamingQueue;
|
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}");
|
builder.AppendLine($"{DispelDescription}");
|
||||||
}
|
}
|
||||||
|
if (ExemptionDescription != "")
|
||||||
|
{
|
||||||
|
builder.AppendLine($"{ExemptionDescription}");
|
||||||
|
}
|
||||||
if (GamingQueue?.Map != null && SkillType != SkillType.Passive)
|
if (GamingQueue?.Map != null && SkillType != SkillType.Passive)
|
||||||
{
|
{
|
||||||
builder.AppendLine($"施法距离:{(CastAnywhere ? "全图" : CastRange)}");
|
builder.AppendLine($"施法距离:{(CastAnywhere ? "全图" : CastRange)}");
|
||||||
@ -660,6 +818,7 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
skill.Description = skillDefined.Description;
|
skill.Description = skillDefined.Description;
|
||||||
skill.GeneralDescription = skillDefined.GeneralDescription;
|
skill.GeneralDescription = skillDefined.GeneralDescription;
|
||||||
skill.DispelDescription = skillDefined.DispelDescription;
|
skill.DispelDescription = skillDefined.DispelDescription;
|
||||||
|
skill.ExemptionDescription = skillDefined.ExemptionDescription;
|
||||||
skill.SkillType = skillDefined.SkillType;
|
skill.SkillType = skillDefined.SkillType;
|
||||||
skill.MPCost = skillDefined.MPCost;
|
skill.MPCost = skillDefined.MPCost;
|
||||||
skill.CastTime = skillDefined.CastTime;
|
skill.CastTime = skillDefined.CastTime;
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
namespace Milimoe.FunGame.Core.Entity
|
using Milimoe.FunGame.Core.Library.Common.Addon;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 技能和它的目标结构体
|
/// 技能和它的目标结构体
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="skill"></param>
|
/// <param name="skill"></param>
|
||||||
/// <param name="targets"></param>
|
/// <param name="targets"></param>
|
||||||
public struct SkillTarget(Skill skill, List<Character> targets)
|
/// <param name="grids"></param>
|
||||||
|
public struct SkillTarget(Skill skill, List<Character> targets, List<Grid> grids)
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 技能实例
|
/// 技能实例
|
||||||
@ -13,8 +16,13 @@
|
|||||||
public Skill Skill { get; set; } = skill;
|
public Skill Skill { get; set; } = skill;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 技能的目标列表
|
/// 指向性技能的目标列表
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<Character> Targets { get; set; } = targets;
|
public List<Character> Targets { get; set; } = targets;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 非指向性技能的目标列表
|
||||||
|
/// </summary>
|
||||||
|
public List<Grid> TargetGrids { get; set; } = grids;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,5 +50,9 @@
|
|||||||
public double AvgRank { get; set; } = 0;
|
public double AvgRank { get; set; } = 0;
|
||||||
public double Rating { get; set; } = 0;
|
public double Rating { get; set; } = 0;
|
||||||
public int MVPs { 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -86,7 +86,7 @@ namespace Milimoe.FunGame.Core.Interface.Base
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="character"></param>
|
/// <param name="character"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public Task<bool> ProcessTurnAsync(Character character);
|
public bool ProcessTurn(Character character);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 造成伤害
|
/// 造成伤害
|
||||||
@ -98,7 +98,8 @@ namespace Milimoe.FunGame.Core.Interface.Base
|
|||||||
/// <param name="damageType"></param>
|
/// <param name="damageType"></param>
|
||||||
/// <param name="magicType"></param>
|
/// <param name="magicType"></param>
|
||||||
/// <param name="damageResult"></param>
|
/// <param name="damageResult"></param>
|
||||||
public Task DamageToEnemyAsync(Character actor, Character enemy, double damage, bool isNormalAttack, DamageType damageType = DamageType.Physical, MagicType magicType = MagicType.None, DamageResult damageResult = DamageResult.Normal);
|
/// <param name="options"></param>
|
||||||
|
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);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 治疗一个目标
|
/// 治疗一个目标
|
||||||
@ -107,7 +108,8 @@ namespace Milimoe.FunGame.Core.Interface.Base
|
|||||||
/// <param name="target"></param>
|
/// <param name="target"></param>
|
||||||
/// <param name="heal"></param>
|
/// <param name="heal"></param>
|
||||||
/// <param name="canRespawn"></param>
|
/// <param name="canRespawn"></param>
|
||||||
public Task HealToTargetAsync(Character actor, Character target, double heal, bool canRespawn = false);
|
/// <param name="triggerEffects"></param>
|
||||||
|
public void HealToTarget(Character actor, Character target, double heal, bool canRespawn = false, bool triggerEffects = true);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 计算物理伤害
|
/// 计算物理伤害
|
||||||
@ -118,8 +120,9 @@ namespace Milimoe.FunGame.Core.Interface.Base
|
|||||||
/// <param name="expectedDamage"></param>
|
/// <param name="expectedDamage"></param>
|
||||||
/// <param name="finalDamage"></param>
|
/// <param name="finalDamage"></param>
|
||||||
/// <param name="changeCount"></param>
|
/// <param name="changeCount"></param>
|
||||||
|
/// <param name="options"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
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);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 计算魔法伤害
|
/// 计算魔法伤害
|
||||||
@ -131,28 +134,29 @@ namespace Milimoe.FunGame.Core.Interface.Base
|
|||||||
/// <param name="expectedDamage"></param>
|
/// <param name="expectedDamage"></param>
|
||||||
/// <param name="finalDamage"></param>
|
/// <param name="finalDamage"></param>
|
||||||
/// <param name="changeCount"></param>
|
/// <param name="changeCount"></param>
|
||||||
|
/// <param name="options"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
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);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 死亡结算
|
/// 死亡结算
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="killer"></param>
|
/// <param name="killer"></param>
|
||||||
/// <param name="death"></param>
|
/// <param name="death"></param>
|
||||||
public Task DeathCalculationAsync(Character killer, Character death);
|
public void DeathCalculation(Character killer, Character death);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 打断施法
|
/// 打断施法
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="caster"></param>
|
/// <param name="caster"></param>
|
||||||
/// <param name="interrupter"></param>
|
/// <param name="interrupter"></param>
|
||||||
public Task InterruptCastingAsync(Character caster, Character interrupter);
|
public void InterruptCasting(Character caster, Character interrupter);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 打断施法 [ 用于使敌人目标丢失 ]
|
/// 打断施法 [ 用于使敌人目标丢失 ]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="interrupter"></param>
|
/// <param name="interrupter"></param>
|
||||||
public Task InterruptCastingAsync(Character interrupter);
|
public void InterruptCasting(Character interrupter);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 使用物品
|
/// 使用物品
|
||||||
@ -163,9 +167,11 @@ namespace Milimoe.FunGame.Core.Interface.Base
|
|||||||
/// <param name="enemys"></param>
|
/// <param name="enemys"></param>
|
||||||
/// <param name="teammates"></param>
|
/// <param name="teammates"></param>
|
||||||
/// <param name="castRange"></param>
|
/// <param name="castRange"></param>
|
||||||
/// <param name="desiredTargets"></param>
|
/// <param name="allEnemys"></param>
|
||||||
|
/// <param name="allTeammates"></param>
|
||||||
|
/// <param name="aiDecision"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public Task<bool> UseItemAsync(Item item, Character character, DecisionPoints dp, List<Character> enemys, List<Character> teammates, List<Grid> castRange, List<Character>? desiredTargets = null);
|
public bool UseItem(Item item, Character character, DecisionPoints dp, List<Character> enemys, List<Character> teammates, List<Grid> castRange, List<Character> allEnemys, List<Character> allTeammates, AIDecision? aiDecision = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 角色移动
|
/// 角色移动
|
||||||
@ -175,7 +181,7 @@ namespace Milimoe.FunGame.Core.Interface.Base
|
|||||||
/// <param name="target"></param>
|
/// <param name="target"></param>
|
||||||
/// <param name="startGrid"></param>
|
/// <param name="startGrid"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public Task<bool> CharacterMoveAsync(Character character, DecisionPoints dp, Grid target, Grid? startGrid);
|
public bool CharacterMove(Character character, DecisionPoints dp, Grid target, Grid? startGrid);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 选取移动目标
|
/// 选取移动目标
|
||||||
@ -186,7 +192,7 @@ namespace Milimoe.FunGame.Core.Interface.Base
|
|||||||
/// <param name="map"></param>
|
/// <param name="map"></param>
|
||||||
/// <param name="moveRange"></param>
|
/// <param name="moveRange"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public Task<Grid> SelectTargetGridAsync(Character character, List<Character> enemys, List<Character> teammates, GameMap map, List<Grid> moveRange);
|
public Grid SelectTargetGrid(Character character, List<Character> enemys, List<Character> teammates, GameMap map, List<Grid> moveRange);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 选取技能目标
|
/// 选取技能目标
|
||||||
@ -197,7 +203,7 @@ namespace Milimoe.FunGame.Core.Interface.Base
|
|||||||
/// <param name="teammates"></param>
|
/// <param name="teammates"></param>
|
||||||
/// <param name="castRange"></param>
|
/// <param name="castRange"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public Task<List<Character>> SelectTargetsAsync(Character caster, Skill skill, List<Character> enemys, List<Character> teammates, List<Grid> castRange);
|
public List<Character> SelectTargets(Character caster, Skill skill, List<Character> enemys, List<Character> teammates, List<Grid> castRange);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 选取普通攻击目标
|
/// 选取普通攻击目标
|
||||||
@ -208,7 +214,21 @@ namespace Milimoe.FunGame.Core.Interface.Base
|
|||||||
/// <param name="teammates"></param>
|
/// <param name="teammates"></param>
|
||||||
/// <param name="attackRange"></param>
|
/// <param name="attackRange"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public Task<List<Character>> SelectTargetsAsync(Character character, NormalAttack attack, List<Character> enemys, List<Character> teammates, List<Grid> attackRange);
|
public List<Character> SelectTargets(Character character, NormalAttack attack, List<Character> enemys, List<Character> teammates, List<Grid> attackRange);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取某角色的敌人列表
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public List<Character> GetEnemies(Character character);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取某角色的队友列表
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public List<Character> GetTeammates(Character character);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 判断目标对于某个角色是否是队友
|
/// 判断目标对于某个角色是否是队友
|
||||||
@ -256,5 +276,34 @@ namespace Milimoe.FunGame.Core.Interface.Base
|
|||||||
/// <param name="damageType"></param>
|
/// <param name="damageType"></param>
|
||||||
/// <param name="takenDamage"></param>
|
/// <param name="takenDamage"></param>
|
||||||
public void CalculateCharacterDamageStatistics(Character character, Character characterTaken, double damage, DamageType damageType, double takenDamage = -1);
|
public void CalculateCharacterDamageStatistics(Character character, Character characterTaken, double damage, DamageType damageType, double takenDamage = -1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 免疫检定
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <param name="target"></param>
|
||||||
|
/// <param name="skill"></param>
|
||||||
|
/// <param name="item"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool CheckSkilledImmune(Character character, Character target, Skill skill, Item? item = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 技能豁免检定
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <param name="source"></param>
|
||||||
|
/// <param name="effect"></param>
|
||||||
|
/// <param name="isEvade">true - 豁免成功等效于闪避</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool CheckExemption(Character character, Character? source, Effect effect, bool isEvade);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 向角色(或控制该角色的玩家)进行询问并取得答复
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <param name="topic"></param>
|
||||||
|
/// <param name="args"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Dictionary<string, object> Inquiry(Character character, string topic, Dictionary<string, object> args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -338,16 +338,15 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
|
|||||||
// 不使用框架的实现时,需要地图作者与游戏队列的作者做好适配
|
// 不使用框架的实现时,需要地图作者与游戏队列的作者做好适配
|
||||||
if (queue is GamingQueue gq)
|
if (queue is GamingQueue gq)
|
||||||
{
|
{
|
||||||
gq.SelectTargetGrid += Gq_SelectTargetGrid;
|
gq.SelectTargetGridEvent += Gq_SelectTargetGrid;
|
||||||
}
|
}
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Grid> Gq_SelectTargetGrid(GamingQueue queue, Character character, List<Character> enemys, List<Character> teammates, GameMap map, List<Grid> canMoveGrids)
|
private Grid Gq_SelectTargetGrid(GamingQueue queue, Character character, List<Character> enemys, List<Character> teammates, GameMap map, List<Grid> canMoveGrids)
|
||||||
{
|
{
|
||||||
// 介入选择,假设这里更新界面,让玩家选择目的地
|
// 介入选择,假设这里更新界面,让玩家选择目的地
|
||||||
await Task.CompletedTask;
|
|
||||||
return Grid.Empty;
|
return Grid.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -366,6 +366,305 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
return grids;
|
return grids;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取以某个格子为中心,一定范围内的格子(正方形,切比雪夫距离),只考虑同一平面的格子。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="grid"></param>
|
||||||
|
/// <param name="range"></param>
|
||||||
|
/// <param name="includeCharacter"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual List<Grid> GetGridsBySquareRange(Grid grid, int range, bool includeCharacter = false)
|
||||||
|
{
|
||||||
|
List<Grid> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取以某个格子为中心,最远距离的格子(正方形,切比雪夫距离),只考虑同一平面的格子。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="grid"></param>
|
||||||
|
/// <param name="range"></param>
|
||||||
|
/// <param name="includeCharacter"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual List<Grid> GetOuterGridsBySquareRange(Grid grid, int range, bool includeCharacter = false)
|
||||||
|
{
|
||||||
|
List<Grid> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用布雷森汉姆直线算法获取从起点到终点的所有格子(包含起点和终点)。
|
||||||
|
/// 若 passThrough 为 true,则继续向同一方向延伸直到地图边缘。只考虑同一平面的格子。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="casterGrid">施法者格子</param>
|
||||||
|
/// <param name="targetGrid">目标格子</param>
|
||||||
|
/// <param name="passThrough">是否贯穿至地图边缘</param>
|
||||||
|
/// <param name="includeCharacter">是否包含有角色的格子</param>
|
||||||
|
/// <returns>直线上的格子列表</returns>
|
||||||
|
public virtual List<Grid> GetGridsOnLine(Grid casterGrid, Grid targetGrid, bool passThrough = false, bool includeCharacter = false)
|
||||||
|
{
|
||||||
|
List<Grid> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用布雷森汉姆直线算法获取从起点到终点的所有格子(包含起点和终点)并考虑宽度。
|
||||||
|
/// 若 passThrough 为 true,则继续向同一方向延伸直到地图边缘。只考虑同一平面的格子。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="start"></param>
|
||||||
|
/// <param name="directionRef"></param>
|
||||||
|
/// <param name="range"></param>
|
||||||
|
/// <param name="passThrough"></param>
|
||||||
|
/// <param name="includeChar"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual List<Grid> GetGridsOnThickLine(Grid start, Grid directionRef, int range, bool passThrough = false, bool includeChar = false)
|
||||||
|
{
|
||||||
|
List<Grid> line = GetGridsOnLine(start, directionRef, passThrough, includeCharacter: true);
|
||||||
|
List<Grid> result = [];
|
||||||
|
|
||||||
|
foreach (Grid g in line)
|
||||||
|
{
|
||||||
|
List<Grid> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取扇形范围内的格子
|
||||||
|
/// 扇形以 casterGrid 为顶点,向 targetGrid 方向张开
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="targetGrid">目标格子,即扇形顶点</param>
|
||||||
|
/// <param name="casterGrid">施法者格子,用于确定朝向</param>
|
||||||
|
/// <param name="range">最大半径</param>
|
||||||
|
/// <param name="angleDegrees">扇形角度,默认 90</param>
|
||||||
|
/// <param name="includeCharacter">是否包含有角色的格子</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual List<Grid> GetGridsInSector(Grid casterGrid, Grid targetGrid, int range, double angleDegrees = 90, bool includeCharacter = false)
|
||||||
|
{
|
||||||
|
List<Grid> 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;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置角色移动
|
/// 设置角色移动
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -414,12 +713,16 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
return currentSteps;
|
return currentSteps;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义平面移动的四个方向
|
// 定义平面移动的方向
|
||||||
(int dx, int dy)[] directions = [
|
(int dx, int dy)[] directions = [
|
||||||
(0, 1), // 上
|
(0, 1), // 上
|
||||||
(0, -1), // 下
|
(0, -1), // 下
|
||||||
(1, 0), // 右
|
(1, 0), // 右
|
||||||
(-1, 0) // 左
|
(-1, 0), // 左
|
||||||
|
(1, 1), // 右上
|
||||||
|
(1, -1), // 右下
|
||||||
|
(-1, 1), // 左上
|
||||||
|
(-1, -1) // 左下
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach (var (dx, dy) in directions)
|
foreach (var (dx, dy) in directions)
|
||||||
@ -484,9 +787,10 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
int minDistanceToTarget = CalculateManhattanDistance(startGrid, target);
|
int minDistanceToTarget = CalculateManhattanDistance(startGrid, target);
|
||||||
int stepsToBestReachable = 0;
|
int stepsToBestReachable = 0;
|
||||||
|
|
||||||
// 定义平面移动的四个方向
|
// 定义平面移动的方向
|
||||||
(int dx, int dy)[] directions = [
|
(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)
|
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);
|
return Math.Abs(g1.X - g2.X) + Math.Abs(g1.Y - g2.Y) + Math.Abs(g1.Z - g2.Z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算两个整数的最大公约数(欧几里得算法)
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取两点之间直线上的所有整数点(包含起点和终点)
|
||||||
|
/// 使用改进的Bresenham算法,确保不遗漏任何点
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 在事件流逝前处理
|
/// 在事件流逝前处理
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -565,7 +964,7 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
/// 在事件流逝后处理
|
/// 在事件流逝后处理
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="timeToReduce"></param>
|
/// <param name="timeToReduce"></param>
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using Milimoe.FunGame.Core.Model;
|
using System.Text;
|
||||||
|
using Milimoe.FunGame.Core.Model;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 此文件用于保存字符串常量(String Set)
|
* 此文件用于保存字符串常量(String Set)
|
||||||
@ -703,7 +704,7 @@ namespace Milimoe.FunGame.Core.Library.Constant
|
|||||||
EffectType.Item => "装备特效",
|
EffectType.Item => "装备特效",
|
||||||
EffectType.Mark => "标记",
|
EffectType.Mark => "标记",
|
||||||
EffectType.Stun => "眩晕",
|
EffectType.Stun => "眩晕",
|
||||||
EffectType.Freeze => "冰冻",
|
EffectType.Freeze => "冻结",
|
||||||
EffectType.Silence => "沉默",
|
EffectType.Silence => "沉默",
|
||||||
EffectType.Root => "定身",
|
EffectType.Root => "定身",
|
||||||
EffectType.Fear => "恐惧",
|
EffectType.Fear => "恐惧",
|
||||||
@ -751,6 +752,8 @@ namespace Milimoe.FunGame.Core.Library.Constant
|
|||||||
EffectType.Recovery => "恢复",
|
EffectType.Recovery => "恢复",
|
||||||
EffectType.Vulnerable => "易伤",
|
EffectType.Vulnerable => "易伤",
|
||||||
EffectType.Delay => "迟滞",
|
EffectType.Delay => "迟滞",
|
||||||
|
EffectType.Focusing => "专注",
|
||||||
|
EffectType.InterruptCasting => "打断施法",
|
||||||
_ => "未知效果"
|
_ => "未知效果"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -811,10 +814,49 @@ namespace Milimoe.FunGame.Core.Library.Constant
|
|||||||
EffectType.Recovery => DispelledType.Weak,
|
EffectType.Recovery => DispelledType.Weak,
|
||||||
EffectType.Vulnerable => DispelledType.Weak,
|
EffectType.Vulnerable => DispelledType.Weak,
|
||||||
EffectType.Delay => DispelledType.Weak,
|
EffectType.Delay => DispelledType.Weak,
|
||||||
|
EffectType.InterruptCasting => DispelledType.Weak,
|
||||||
_ => 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)
|
public static bool GetIsDebuffByEffectType(EffectType type)
|
||||||
{
|
{
|
||||||
return type switch
|
return type switch
|
||||||
@ -871,6 +913,7 @@ namespace Milimoe.FunGame.Core.Library.Constant
|
|||||||
EffectType.Recovery => false,
|
EffectType.Recovery => false,
|
||||||
EffectType.Vulnerable => true,
|
EffectType.Vulnerable => true,
|
||||||
EffectType.Delay => true,
|
EffectType.Delay => true,
|
||||||
|
EffectType.InterruptCasting => true,
|
||||||
_ => false
|
_ => 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -233,6 +233,7 @@ namespace Milimoe.FunGame.Core.Library.Constant
|
|||||||
/// <para><see cref="SkillSet.GetDispelledTypeByEffectType(EffectType)"/></para>
|
/// <para><see cref="SkillSet.GetDispelledTypeByEffectType(EffectType)"/></para>
|
||||||
/// <para><see cref="SkillSet.GetEffectTypeName(EffectType)"/></para>
|
/// <para><see cref="SkillSet.GetEffectTypeName(EffectType)"/></para>
|
||||||
/// <para><see cref="SkillSet.GetIsDebuffByEffectType(EffectType)"/></para>
|
/// <para><see cref="SkillSet.GetIsDebuffByEffectType(EffectType)"/></para>
|
||||||
|
/// <para><see cref="SkillSet.GetExemptionTypeByEffectType(EffectType)"/></para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum EffectType
|
public enum EffectType
|
||||||
{
|
{
|
||||||
@ -494,7 +495,17 @@ namespace Milimoe.FunGame.Core.Library.Constant
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 迟滞,硬直时间延长
|
/// 迟滞,硬直时间延长
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Delay
|
Delay,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 专注
|
||||||
|
/// </summary>
|
||||||
|
Focusing,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 打断施法
|
||||||
|
/// </summary>
|
||||||
|
InterruptCasting
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ItemType
|
public enum ItemType
|
||||||
@ -1066,11 +1077,34 @@ namespace Milimoe.FunGame.Core.Library.Constant
|
|||||||
|
|
||||||
public enum SkillRangeType
|
public enum SkillRangeType
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 菱形
|
||||||
|
/// </summary>
|
||||||
Diamond,
|
Diamond,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 圆形
|
||||||
|
/// </summary>
|
||||||
Circle,
|
Circle,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 正方形
|
||||||
|
/// </summary>
|
||||||
Square,
|
Square,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 施法者与目标之间的线段
|
||||||
|
/// </summary>
|
||||||
Line,
|
Line,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 施法者与目标所在的直线,贯穿至地图边缘
|
||||||
|
/// </summary>
|
||||||
LinePass,
|
LinePass,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 扇形
|
||||||
|
/// </summary>
|
||||||
Sector
|
Sector
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
public ISkill? SkillToUse { get; set; } = null;
|
public ISkill? SkillToUse { get; set; } = null;
|
||||||
public Item? ItemToUse { get; set; } = null;
|
public Item? ItemToUse { get; set; } = null;
|
||||||
public List<Character> Targets { get; set; } = [];
|
public List<Character> Targets { get; set; } = [];
|
||||||
|
public List<Grid> TargetGrids { get; set; } = [];
|
||||||
public double Score { get; set; } = 0;
|
public double Score { get; set; } = 0;
|
||||||
public bool IsPureMove { get; set; } = false;
|
public bool IsPureMove { get; set; } = false;
|
||||||
}
|
}
|
||||||
|
|||||||
43
Model/DamageCalculationOptions.cs
Normal file
43
Model/DamageCalculationOptions.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
namespace Milimoe.FunGame.Core.Model
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 精准的分步控制伤害计算
|
||||||
|
/// </summary>
|
||||||
|
public class DamageCalculationOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 完整计算伤害
|
||||||
|
/// </summary>
|
||||||
|
public bool NeedCalculate { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算减伤
|
||||||
|
/// </summary>
|
||||||
|
public bool CalculateReduction { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算暴击
|
||||||
|
/// </summary>
|
||||||
|
public bool CalculateCritical { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算闪避
|
||||||
|
/// </summary>
|
||||||
|
public bool CalculateEvade { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算护盾
|
||||||
|
/// </summary>
|
||||||
|
public bool CalculateShield { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 触发特效
|
||||||
|
/// </summary>
|
||||||
|
public bool TriggerEffects { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 无视免疫
|
||||||
|
/// </summary>
|
||||||
|
public bool IgnoreImmune { get; set; } = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -230,6 +230,11 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public double STRtoCritDMGMultiplier { get; set; } = 0.00575;
|
public double STRtoCritDMGMultiplier { get; set; } = 0.00575;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 每 1 点力量增加力量豁免率
|
||||||
|
/// </summary>
|
||||||
|
public double STRtoExemptionRateMultiplier { get; set; } = 0.001;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 每 1 点智力增加魔法值
|
/// 每 1 点智力增加魔法值
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -260,6 +265,11 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public double INTtoAccelerationCoefficientMultiplier { get; set; } = 0.00125;
|
public double INTtoAccelerationCoefficientMultiplier { get; set; } = 0.00125;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 每 1 点智力增加智力豁免率
|
||||||
|
/// </summary>
|
||||||
|
public double INTtoExemptionRateMultiplier { get; set; } = 0.001;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 每 1 点敏捷增加行动速度
|
/// 每 1 点敏捷增加行动速度
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -275,6 +285,11 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public double AGItoEvadeRateMultiplier { get; set; } = 0.00175;
|
public double AGItoEvadeRateMultiplier { get; set; } = 0.00175;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 每 1 点敏捷增加敏捷豁免率
|
||||||
|
/// </summary>
|
||||||
|
public double AGItoExemptionRateMultiplier { get; set; } = 0.001;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 造成伤害获得能量值因子
|
/// 造成伤害获得能量值因子
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
1441
Model/GamingQueue.cs
1441
Model/GamingQueue.cs
File diff suppressed because it is too large
Load Diff
@ -14,7 +14,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
/// <param name="death"></param>
|
/// <param name="death"></param>
|
||||||
/// <param name="killer"></param>
|
/// <param name="killer"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected override async Task AfterDeathCalculation(Character death, Character killer)
|
protected override void AfterDeathCalculation(Character death, Character killer)
|
||||||
{
|
{
|
||||||
if (MaxRespawnTimes != 0 && MaxScoreToWin > 0)
|
if (MaxRespawnTimes != 0 && MaxScoreToWin > 0)
|
||||||
{
|
{
|
||||||
@ -25,12 +25,12 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
if (!_queue.Where(c => c != killer).Any())
|
if (!_queue.Where(c => c != killer).Any())
|
||||||
{
|
{
|
||||||
// 没有其他的角色了,游戏结束
|
// 没有其他的角色了,游戏结束
|
||||||
await EndGameInfo(killer);
|
EndGameInfo(killer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MaxScoreToWin > 0 && _stats[killer].Kills >= MaxScoreToWin)
|
if (MaxScoreToWin > 0 && _stats[killer].Kills >= MaxScoreToWin)
|
||||||
{
|
{
|
||||||
await EndGameInfo(killer);
|
EndGameInfo(killer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -41,9 +41,9 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
/// <param name="character"></param>
|
/// <param name="character"></param>
|
||||||
/// <param name="type"></param>
|
/// <param name="type"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected override async Task<bool> 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 (result)
|
||||||
{
|
{
|
||||||
if (MaxRespawnTimes != 0 && MaxScoreToWin > 0 && _stats[character].Kills >= MaxScoreToWin)
|
if (MaxRespawnTimes != 0 && MaxScoreToWin > 0 && _stats[character].Kills >= MaxScoreToWin)
|
||||||
@ -57,7 +57,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 游戏结束信息
|
/// 游戏结束信息
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task EndGameInfo(Character winner)
|
public void EndGameInfo(Character winner)
|
||||||
{
|
{
|
||||||
WriteLine("[ " + winner + " ] 是胜利者。");
|
WriteLine("[ " + winner + " ] 是胜利者。");
|
||||||
foreach (Character character in _stats.OrderBy(kv => kv.Value.Kills)
|
foreach (Character character in _stats.OrderBy(kv => kv.Value.Kills)
|
||||||
@ -73,7 +73,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
_queue.Clear();
|
_queue.Clear();
|
||||||
_isGameEnd = true;
|
_isGameEnd = true;
|
||||||
|
|
||||||
if (!await OnGameEndAsync(winner))
|
if (!OnGameEndEvent(winner))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -77,7 +77,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
/// 获取某角色的团队成员
|
/// 获取某角色的团队成员
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="character"></param>
|
/// <param name="character"></param>
|
||||||
protected override List<Character> GetTeammates(Character character)
|
public override List<Character> GetTeammates(Character character)
|
||||||
{
|
{
|
||||||
foreach (string team in _teams.Keys)
|
foreach (string team in _teams.Keys)
|
||||||
{
|
{
|
||||||
@ -95,7 +95,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
/// <param name="character"></param>
|
/// <param name="character"></param>
|
||||||
/// <param name="dp"></param>
|
/// <param name="dp"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected override async Task AfterCharacterDecision(Character character, DecisionPoints dp)
|
protected override void AfterCharacterDecision(Character character, DecisionPoints dp)
|
||||||
{
|
{
|
||||||
// 如果目标都是队友,会考虑非伤害型助攻
|
// 如果目标都是队友,会考虑非伤害型助攻
|
||||||
Team? team = GetTeam(character);
|
Team? team = GetTeam(character);
|
||||||
@ -103,7 +103,6 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
{
|
{
|
||||||
SetNotDamageAssistTime(character, LastRound.Targets.Values.SelectMany(c => c).Where(team.IsOnThisTeam));
|
SetNotDamageAssistTime(character, LastRound.Targets.Values.SelectMany(c => c).Where(team.IsOnThisTeam));
|
||||||
}
|
}
|
||||||
else await Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -112,9 +111,9 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
/// <param name="character"></param>
|
/// <param name="character"></param>
|
||||||
/// <param name="type"></param>
|
/// <param name="type"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected override async Task<bool> 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 (result)
|
||||||
{
|
{
|
||||||
Team? team = GetTeam(character);
|
Team? team = GetTeam(character);
|
||||||
@ -132,7 +131,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
/// <param name="death"></param>
|
/// <param name="death"></param>
|
||||||
/// <param name="killer"></param>
|
/// <param name="killer"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected override async Task OnDeathCalculation(Character death, Character killer)
|
protected override void OnDeathCalculation(Character death, Character killer)
|
||||||
{
|
{
|
||||||
if (killer == death)
|
if (killer == death)
|
||||||
{
|
{
|
||||||
@ -149,7 +148,6 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
}
|
}
|
||||||
else team.Score++;
|
else team.Score++;
|
||||||
}
|
}
|
||||||
else await Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -158,7 +156,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
/// <param name="death"></param>
|
/// <param name="death"></param>
|
||||||
/// <param name="killer"></param>
|
/// <param name="killer"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected override async Task AfterDeathCalculation(Character death, Character killer)
|
protected override void AfterDeathCalculation(Character death, Character killer)
|
||||||
{
|
{
|
||||||
Team? killTeam = GetTeam(killer);
|
Team? killTeam = GetTeam(killer);
|
||||||
Team? deathTeam = GetTeam(death);
|
Team? deathTeam = GetTeam(death);
|
||||||
@ -200,7 +198,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
if (!_teams.Keys.Where(str => str != killTeam.Name).Any())
|
if (!_teams.Keys.Where(str => str != killTeam.Name).Any())
|
||||||
{
|
{
|
||||||
// 没有其他的团队了,游戏结束
|
// 没有其他的团队了,游戏结束
|
||||||
await EndGameInfo(killTeam);
|
EndGameInfo(killTeam);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (MaxScoreToWin > 0 && killTeam.Score >= MaxScoreToWin)
|
if (MaxScoreToWin > 0 && killTeam.Score >= MaxScoreToWin)
|
||||||
@ -209,7 +207,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
combinedTeams.Remove(killTeam);
|
combinedTeams.Remove(killTeam);
|
||||||
_eliminatedTeams.Clear();
|
_eliminatedTeams.Clear();
|
||||||
_eliminatedTeams.AddRange(combinedTeams.OrderByDescending(t => t.Score));
|
_eliminatedTeams.AddRange(combinedTeams.OrderByDescending(t => t.Score));
|
||||||
await EndGameInfo(killTeam);
|
EndGameInfo(killTeam);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,12 +216,12 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 游戏结束信息 [ 团队版 ]
|
/// 游戏结束信息 [ 团队版 ]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task EndGameInfo(Team winner)
|
public void EndGameInfo(Team winner)
|
||||||
{
|
{
|
||||||
winner.IsWinner = true;
|
winner.IsWinner = true;
|
||||||
WriteLine("[ " + winner + " ] 是胜利者。");
|
WriteLine("[ " + winner + " ] 是胜利者。");
|
||||||
|
|
||||||
if (!await OnGameEndTeamAsync(winner))
|
if (!OnGameEndTeamEvent(winner))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -322,19 +320,19 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public delegate Task<bool> GameEndTeamEventHandler(TeamGamingQueue queue, Team winner);
|
public delegate bool GameEndTeamEventHandler(TeamGamingQueue queue, Team winner);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 游戏结束事件(团队版)
|
/// 游戏结束事件(团队版)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event GameEndTeamEventHandler? GameEndTeam;
|
public event GameEndTeamEventHandler? GameEndTeamEvent;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 游戏结束事件(团队版)
|
/// 游戏结束事件(团队版)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="winner"></param>
|
/// <param name="winner"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected async Task<bool> OnGameEndTeamAsync(Team winner)
|
protected bool OnGameEndTeamEvent(Team winner)
|
||||||
{
|
{
|
||||||
return await (GameEndTeam?.Invoke(this, winner) ?? Task.FromResult(true));
|
return GameEndTeamEvent?.Invoke(this, winner) ?? true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user