支持非指向性技能目标选取;删除战斗框架的全部异步;添加豁免机制和指向性扩散 (#145)

* 支持非指向性技能和指向性技能的扩散

* 添加豁免机制,优化非指向性寻路算法

* 删除战斗框架的全部异步;添加非指向性无目标阻止释放;添加直线宽度;修改扇形算法

* 添加了新的特效钩子;添加了决策点相关统计;添加伤害计算选项;开放新事件和 API
This commit is contained in:
milimoe 2026-01-09 09:06:49 +08:00 committed by GitHub
parent ae1135ce06
commit 310f672ed4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 2025 additions and 677 deletions

View File

@ -25,7 +25,7 @@ namespace Milimoe.FunGame.Core.Controller
/// <param name="selectableEnemys">场上能够选取的敌人</param>
/// <param name="selectableTeammates">场上能够选取的队友</param>
/// <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<Character> selectableEnemys, List<Character> selectableTeammates)
{
@ -86,31 +86,43 @@ namespace Milimoe.FunGame.Core.Controller
// 计算当前技能的可达格子
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))
.Distinct()];
List<Character> skillReachableTeammates = [.. allTeammatesInGame
List<Character> skillReachableTeammates = [.. allTeammatesInGame
.Where(c => skillReachableGrids.SelectMany(g => g.Characters).Contains(c) && selectableTeammates.Contains(c))
.Distinct()];
// 检查是否有可用的目标(敌人或队友,取决于技能类型)
if (skillReachableEnemys.Count > 0 || skillReachableTeammates.Count > 0)
{
// 将筛选后的目标列表传递给 SelectTargets
List<Character> targets = SelectTargets(character, skill, skillReachableEnemys, skillReachableTeammates);
if (targets.Count > 0)
// 检查是否有可用的目标(敌人或队友,取决于技能类型)
if (skillReachableEnemys.Count > 0 || skillReachableTeammates.Count > 0)
{
double currentScore = EvaluateSkill(character, skill, targets, cost) - movePenalty;
if (currentScore > bestDecision.Score)
// 将筛选后的目标列表传递给 SelectTargets
List<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,
TargetMoveGrid = potentialMoveGrid,
SkillToUse = skill,
Targets = targets,
Score = currentScore
};
bestDecision = new AIDecision
{
ActionType = CharacterActionType.PreCastSkill,
TargetMoveGrid = potentialMoveGrid,
SkillToUse = skill,
Targets = targets,
Score = currentScore
};
}
}
}
}
@ -126,32 +138,44 @@ namespace Milimoe.FunGame.Core.Controller
// 计算当前物品技能的可达格子
List<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))
.Distinct()];
List<Character> itemSkillReachableTeammates = [.. allTeammatesInGame
List<Character> itemSkillReachableTeammates = [.. allTeammatesInGame
.Where(c => itemSkillReachableGrids.SelectMany(g => g.Characters).Contains(c) && selectableTeammates.Contains(c))
.Distinct()];
// 检查是否有可用的目标
if (itemSkillReachableEnemys.Count > 0 || itemSkillReachableTeammates.Count > 0)
{
// 将筛选后的目标列表传递给 SelectTargets
List<Character> targetsForItem = SelectTargets(character, itemSkill, itemSkillReachableEnemys, itemSkillReachableTeammates);
if (targetsForItem.Count > 0)
// 检查是否有可用的目标
if (itemSkillReachableEnemys.Count > 0 || itemSkillReachableTeammates.Count > 0)
{
double currentScore = EvaluateItem(character, item, targetsForItem, cost) - movePenalty;
if (currentScore > bestDecision.Score)
// 将筛选后的目标列表传递给 SelectTargets
List<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,
TargetMoveGrid = potentialMoveGrid,
ItemToUse = item,
SkillToUse = itemSkill,
Targets = targetsForItem,
Score = currentScore
};
bestDecision = new AIDecision
{
ActionType = CharacterActionType.UseItem,
TargetMoveGrid = potentialMoveGrid,
ItemToUse = item,
SkillToUse = itemSkill,
Targets = targetsForItem,
Score = currentScore
};
}
}
}
}
@ -216,7 +240,7 @@ namespace Milimoe.FunGame.Core.Controller
}
}
return await Task.FromResult(bestDecision);
return bestDecision;
}
// --- AI 决策辅助方法 ---
@ -251,6 +275,7 @@ namespace Milimoe.FunGame.Core.Controller
(character.CharacterState != CharacterState.ActionRestricted || item.ItemType == ItemType.Consumable) && // 行动受限只能用消耗品
character.CharacterState != CharacterState.BattleRestricted;
}
/// <summary>
/// 选择技能的最佳目标
/// </summary>
@ -302,6 +327,51 @@ namespace Milimoe.FunGame.Core.Controller
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>

View File

@ -482,7 +482,7 @@ namespace Milimoe.FunGame.Core.Entity
/// <summary>
/// 生命回复力 = [ 与初始设定相关 ] [ 与力量相关 ] + 额外生命回复力
/// </summary>
public double HR => InitialHR + STR * GameplayEquilibriumConstant.STRtoHRFactor + ExHR;
public double HR => Math.Max(0, InitialHR + STR * GameplayEquilibriumConstant.STRtoHRFactor + ExHR);
/// <summary>
/// 额外生命回复力 [ 与技能和物品相关 ]
@ -498,7 +498,7 @@ namespace Milimoe.FunGame.Core.Entity
/// <summary>
/// 魔法回复力 = [ 与初始设定相关 ] [ 与智力相关 ] + 额外魔法回复力
/// </summary>
public double MR => InitialMR + INT * GameplayEquilibriumConstant.INTtoMRFactor + ExMR;
public double MR => Math.Max(0, InitialMR + INT * GameplayEquilibriumConstant.INTtoMRFactor + ExMR);
/// <summary>
/// 额外魔法回复力 [ 与技能和物品相关 ]
@ -687,6 +687,21 @@ namespace Milimoe.FunGame.Core.Entity
[InitOptional]
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>
@ -809,7 +824,7 @@ namespace Milimoe.FunGame.Core.Entity
_ => baseMOV
};
}
return Math.Max(1, baseMOV + ExMOV);
return Math.Max(0, baseMOV + ExMOV);
}
}
@ -878,6 +893,16 @@ namespace Milimoe.FunGame.Core.Entity
/// </summary>
public Shield Shield { get; set; }
/// <summary>
/// 角色是否是单位 [ 初始设定 ]
/// </summary>
public virtual bool IsUnit { get; } = false;
/// <summary>
/// 角色所属的上级角色 [ 战斗相关 ]
/// </summary>
public Character? Master { get; set; } = null;
/// <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($"核心属性:{CharacterSet.GetPrimaryAttributeName(PrimaryAttribute)}");
double exSTR = ExSTR + ExSTR2;
builder.AppendLine($"力量:{STR:0.##}" + (exSTR != 0 ? $" [{BaseSTR:0.##} {(exSTR >= 0 ? "+" : "-")} {Math.Abs(exSTR):0.##}]" : "") + (showGrowth ? $"{(STRGrowth >= 0 ? "+" : "-")}{Math.Abs(STRGrowth)}/Lv" : ""));
builder.AppendLine($"力量:{STR:0.##}" + (exSTR != 0 ? $" [{BaseSTR:0.##} {(exSTR >= 0 ? "+" : "-")} {Math.Abs(exSTR):0.##}]" : "") + (showGrowth ? $"{(STRGrowth >= 0 ? "+" : "-")}{Math.Abs(STRGrowth)}/Lv" : "") + $"{STRExemption * 100:0.##}%");
double exAGI = ExAGI + ExAGI2;
builder.AppendLine($"敏捷:{AGI:0.##}" + (exAGI != 0 ? $" [{BaseAGI:0.##} {(exAGI >= 0 ? "+" : "-")} {Math.Abs(exAGI):0.##}]" : "") + (showGrowth ? $"{(AGIGrowth >= 0 ? "+" : "-")}{Math.Abs(AGIGrowth)}/Lv" : ""));
builder.AppendLine($"敏捷:{AGI:0.##}" + (exAGI != 0 ? $" [{BaseAGI:0.##} {(exAGI >= 0 ? "+" : "-")} {Math.Abs(exAGI):0.##}]" : "") + (showGrowth ? $"{(AGIGrowth >= 0 ? "+" : "-")}{Math.Abs(AGIGrowth)}/Lv" : "") + $"{AGIExemption * 100:0.##}%");
double exINT = ExINT + ExINT2;
builder.AppendLine($"智力:{INT:0.##}" + (exINT != 0 ? $" [{BaseINT:0.##} {(exINT >= 0 ? "+" : "-")} {Math.Abs(exINT):0.##}]" : "") + (showGrowth ? $"{(INTGrowth >= 0 ? "+" : "-")}{Math.Abs(INTGrowth)}/Lv" : ""));
builder.AppendLine($"智力:{INT:0.##}" + (exINT != 0 ? $" [{BaseINT:0.##} {(exINT >= 0 ? "+" : "-")} {Math.Abs(exINT):0.##}]" : "") + (showGrowth ? $"{(INTGrowth >= 0 ? "+" : "-")}{Math.Abs(INTGrowth)}/Lv" : "") + $"{INTExemption * 100:0.##}%");
builder.AppendLine($"生命回复:{HR:0.##}" + (ExHR != 0 ? $" [{InitialHR + STR * GameplayEquilibriumConstant.STRtoHRFactor:0.##} {(ExHR >= 0 ? "+" : "-")} {Math.Abs(ExHR):0.##}]" : ""));
builder.AppendLine($"魔法回复:{MR:0.##}" + (ExMR != 0 ? $" [{InitialMR + INT * GameplayEquilibriumConstant.INTtoMRFactor:0.##} {(ExMR >= 0 ? "+" : "-")} {Math.Abs(ExMR):0.##}]" : ""));
builder.AppendLine($"暴击率:{CritRate * 100:0.##}%");
@ -1474,6 +1499,7 @@ namespace Milimoe.FunGame.Core.Entity
builder.AppendLine("== 角色技能 ==");
foreach (Skill skill in Skills)
{
skill.Character = this;
builder.Append(skill.ToString());
}
}
@ -1539,11 +1565,11 @@ namespace Milimoe.FunGame.Core.Entity
builder.AppendLine($"行动速度:{SPD:0.##}" + (exSPD != 0 ? $" [{InitialSPD:0.##} {(exSPD >= 0 ? "+" : "-")} {Math.Abs(exSPD):0.##}]" : "") + $" ({ActionCoefficient * 100:0.##}%)");
builder.AppendLine($"核心属性:{CharacterSet.GetPrimaryAttributeName(PrimaryAttribute)}");
double exSTR = ExSTR + ExSTR2;
builder.AppendLine($"力量:{STR:0.##}" + (exSTR != 0 ? $" [{BaseSTR:0.##} {(exSTR >= 0 ? "+" : "-")} {Math.Abs(exSTR):0.##}]" : "") + (showGrowth ? $"{(STRGrowth >= 0 ? "+" : "-")}{Math.Abs(STRGrowth)}/Lv" : ""));
builder.AppendLine($"力量:{STR:0.##}" + (exSTR != 0 ? $" [{BaseSTR:0.##} {(exSTR >= 0 ? "+" : "-")} {Math.Abs(exSTR):0.##}]" : "") + (showGrowth ? $"{(STRGrowth >= 0 ? "+" : "-")}{Math.Abs(STRGrowth)}/Lv" : "") + $"{STRExemption * 100:0.##}%");
double exAGI = ExAGI + ExAGI2;
builder.AppendLine($"敏捷:{AGI:0.##}" + (exAGI != 0 ? $" [{BaseAGI:0.##} {(exAGI >= 0 ? "+" : "-")} {Math.Abs(exAGI):0.##}]" : "") + (showGrowth ? $"{(AGIGrowth >= 0 ? "+" : "-")}{Math.Abs(AGIGrowth)}/Lv" : ""));
builder.AppendLine($"敏捷:{AGI:0.##}" + (exAGI != 0 ? $" [{BaseAGI:0.##} {(exAGI >= 0 ? "+" : "-")} {Math.Abs(exAGI):0.##}]" : "") + (showGrowth ? $"{(AGIGrowth >= 0 ? "+" : "-")}{Math.Abs(AGIGrowth)}/Lv" : "") + $"{AGIExemption * 100:0.##}%");
double exINT = ExINT + ExINT2;
builder.AppendLine($"智力:{INT:0.##}" + (exINT != 0 ? $" [{BaseINT:0.##} {(exINT >= 0 ? "+" : "-")} {Math.Abs(exINT):0.##}]" : "") + (showGrowth ? $"{(INTGrowth >= 0 ? "+" : "-")}{Math.Abs(INTGrowth)}/Lv" : ""));
builder.AppendLine($"智力:{INT:0.##}" + (exINT != 0 ? $" [{BaseINT:0.##} {(exINT >= 0 ? "+" : "-")} {Math.Abs(exINT):0.##}]" : "") + (showGrowth ? $"{(INTGrowth >= 0 ? "+" : "-")}{Math.Abs(INTGrowth)}/Lv" : "") + $"{INTExemption * 100:0.##}%");
}
builder.AppendLine($"生命回复:{HR:0.##}" + (ExHR != 0 ? $" [{InitialHR + STR * GameplayEquilibriumConstant.STRtoHRFactor:0.##} {(ExHR >= 0 ? "+" : "-")} {Math.Abs(ExHR):0.##}]" : ""));
builder.AppendLine($"魔法回复:{MR:0.##}" + (ExMR != 0 ? $" [{InitialMR + INT * GameplayEquilibriumConstant.INTtoMRFactor:0.##} {(ExMR >= 0 ? "+" : "-")} {Math.Abs(ExMR):0.##}]" : ""));
@ -1749,11 +1775,11 @@ namespace Milimoe.FunGame.Core.Entity
builder.AppendLine($"行动速度:{SPD:0.##}" + (exSPD != 0 ? $" [{InitialSPD:0.##} {(exSPD >= 0 ? "+" : "-")} {Math.Abs(exSPD):0.##}]" : "") + $" ({ActionCoefficient * 100:0.##}%)");
builder.AppendLine($"核心属性:{CharacterSet.GetPrimaryAttributeName(PrimaryAttribute)}");
double exSTR = ExSTR + ExSTR2;
builder.AppendLine($"力量:{STR:0.##}" + (exSTR != 0 ? $" [{BaseSTR:0.##} {(exSTR >= 0 ? "+" : "-")} {Math.Abs(exSTR):0.##}]" : "") + (showGrowth ? $"{(STRGrowth >= 0 ? "+" : "-")}{Math.Abs(STRGrowth)}/Lv" : ""));
builder.AppendLine($"力量:{STR:0.##}" + (exSTR != 0 ? $" [{BaseSTR:0.##} {(exSTR >= 0 ? "+" : "-")} {Math.Abs(exSTR):0.##}]" : "") + (showGrowth ? $"{(STRGrowth >= 0 ? "+" : "-")}{Math.Abs(STRGrowth)}/Lv" : "") + $"{STRExemption * 100:0.##}%");
double exAGI = ExAGI + ExAGI2;
builder.AppendLine($"敏捷:{AGI:0.##}" + (exAGI != 0 ? $" [{BaseAGI:0.##} {(exAGI >= 0 ? "+" : "-")} {Math.Abs(exAGI):0.##}]" : "") + (showGrowth ? $"{(AGIGrowth >= 0 ? "+" : "-")}{Math.Abs(AGIGrowth)}/Lv" : ""));
builder.AppendLine($"敏捷:{AGI:0.##}" + (exAGI != 0 ? $" [{BaseAGI:0.##} {(exAGI >= 0 ? "+" : "-")} {Math.Abs(exAGI):0.##}]" : "") + (showGrowth ? $"{(AGIGrowth >= 0 ? "+" : "-")}{Math.Abs(AGIGrowth)}/Lv" : "") + $"{AGIExemption * 100:0.##}%");
double exINT = ExINT + ExINT2;
builder.AppendLine($"智力:{INT:0.##}" + (exINT != 0 ? $" [{BaseINT:0.##} {(exINT >= 0 ? "+" : "-")} {Math.Abs(exINT):0.##}]" : "") + (showGrowth ? $"{(INTGrowth >= 0 ? "+" : "-")}{Math.Abs(INTGrowth)}/Lv" : ""));
builder.AppendLine($"智力:{INT:0.##}" + (exINT != 0 ? $" [{BaseINT:0.##} {(exINT >= 0 ? "+" : "-")} {Math.Abs(exINT):0.##}]" : "") + (showGrowth ? $"{(INTGrowth >= 0 ? "+" : "-")}{Math.Abs(INTGrowth)}/Lv" : "") + $"{INTExemption * 100:0.##}%");
builder.AppendLine($"生命回复:{HR:0.##}" + (ExHR != 0 ? $" [{InitialHR + STR * GameplayEquilibriumConstant.STRtoHRFactor:0.##} {(ExHR >= 0 ? "+" : "-")} {Math.Abs(ExHR):0.##}]" : ""));
builder.AppendLine($"魔法回复:{MR:0.##}" + (ExMR != 0 ? $" [{InitialMR + INT * GameplayEquilibriumConstant.INTtoMRFactor:0.##} {(ExMR >= 0 ? "+" : "-")} {Math.Abs(ExMR):0.##}]" : ""));
builder.AppendLine($"暴击率:{CritRate * 100:0.##}%");

View File

@ -15,6 +15,11 @@ namespace Milimoe.FunGame.Core.Entity
/// </summary>
public override string Name { get; set; } = "";
/// <summary>
/// 单位标识
/// </summary>
public override bool IsUnit => true;
/// <summary>
/// 获取单位名称以及所属玩家
/// </summary>

View File

@ -311,7 +311,7 @@ namespace Milimoe.FunGame.Core.Entity
/// 局内使用物品触发
/// </summary>
/// <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 used = false;
@ -328,7 +328,7 @@ namespace Milimoe.FunGame.Core.Entity
Grid? grid = Skills.Active.GamingQueue.Map.GetCharacterCurrentGrid(character);
castRange = grid is null ? [] : Skills.Active.GamingQueue.Map.GetGridsByRange(grid, Skills.Active.CastRange, true);
}
used = await queue.UseItemAsync(this, character, dp, enemys, teammates, castRange);
used = queue.UseItem(this, character, dp, enemys, teammates, castRange, allEnemys, allTeammates);
}
if (used)
{

View File

@ -137,6 +137,48 @@ namespace Milimoe.FunGame.Core.Entity
/// </summary>
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>
/// <param name="caster"></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>
@ -362,8 +417,9 @@ namespace Milimoe.FunGame.Core.Entity
/// </summary>
/// <param name="caster"></param>
/// <param name="targets"></param>
/// <param name="grids"></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>
@ -819,58 +887,72 @@ namespace Milimoe.FunGame.Core.Entity
}
/// <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>
/// <param name="actor"></param>
/// <param name="enemy"></param>
/// <param name="damageType"></param>
/// <param name="magicType"></param>
/// <param name="expectedDamage"></param>
/// <param name="options"></param>
/// <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;
int changeCount = 0;
DamageResult result = DamageResult.Normal;
double damage = expectedDamage;
if (damageType != DamageType.True)
options ??= new();
if (options.NeedCalculate && damageType != DamageType.True)
{
result = damageType == DamageType.Physical ? GamingQueue.CalculatePhysicalDamage(actor, enemy, false, expectedDamage, out damage, ref changeCount) : GamingQueue.CalculateMagicalDamage(actor, enemy, false, MagicType, expectedDamage, out damage, ref changeCount);
result = damageType == DamageType.Physical ? GamingQueue.CalculatePhysicalDamage(actor, enemy, false, expectedDamage, out damage, ref changeCount, options) : GamingQueue.CalculateMagicalDamage(actor, enemy, false, MagicType, expectedDamage, out damage, ref changeCount, options);
}
// 注意此方法在后台线程运行
GamingQueue.DamageToEnemyAsync(actor, enemy, damage, false, damageType, magicType, result);
GamingQueue.DamageToEnemy(actor, enemy, damage, false, damageType, magicType, result, options);
return result;
}
/// <summary>
/// 治疗一个目标 [ 强烈建议使用此方法而不是自行调用 <see cref="IGamingQueue.HealToTargetAsync"/> ]
/// 治疗一个目标 [ 强烈建议使用此方法而不是自行调用 <see cref="IGamingQueue.HealToTarget"/> ]
/// </summary>
/// <param name="actor"></param>
/// <param name="target"></param>
/// <param name="heal"></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>
/// 打断施法 [ 尽可能的调用此方法而不是直接调用 <see cref="IGamingQueue.InterruptCastingAsync(Character, Character)"/>,以防止中断性变更 ]
/// 打断施法 [ 尽可能的调用此方法而不是直接调用 <see cref="IGamingQueue.InterruptCasting(Character, Character)"/>,以防止中断性变更 ]
/// </summary>
/// <param name="caster"></param>
/// <param name="interrupter"></param>
public void InterruptCasting(Character caster, Character interrupter)
{
GamingQueue?.InterruptCastingAsync(caster, interrupter);
GamingQueue?.InterruptCasting(caster, interrupter);
}
/// <summary>
/// 打断施法 [ 用于使敌人目标丢失 ] [ 尽可能的调用此方法而不是直接调用 <see cref="IGamingQueue.InterruptCastingAsync(Character)"/>,以防止中断性变更 ]
/// 打断施法 [ 用于使敌人目标丢失 ] [ 尽可能的调用此方法而不是直接调用 <see cref="IGamingQueue.InterruptCasting(Character)"/>,以防止中断性变更 ]
/// </summary>
/// <param name="interrupter"></param>
public void InterruptCasting(Character interrupter)
{
GamingQueue?.InterruptCastingAsync(interrupter);
GamingQueue?.InterruptCasting(interrupter);
}
/// <summary>
@ -1048,7 +1130,7 @@ namespace Milimoe.FunGame.Core.Entity
{
return;
}
Effect[] effects = [.. target.Effects.Where(e => e.IsInEffect && e.ShowInStatusBar)];
Effect[] effects = [.. target.Effects.Where(e => e.ShowInStatusBar)];
foreach (Effect effect in effects)
{
if (effect.OnEffectIsBeingDispelled(dispeller, target, this, isEnemy))
@ -1058,6 +1140,35 @@ namespace Milimoe.FunGame.Core.Entity
}
}
/// <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>
@ -1091,6 +1202,18 @@ namespace Milimoe.FunGame.Core.Entity
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>
@ -1206,5 +1329,10 @@ namespace Milimoe.FunGame.Core.Entity
/// 驱散描述
/// </summary>
private string _dispelDescription = "";
/// <summary>
/// 豁免性
/// </summary>
private PrimaryAttribute? _exemptionType = null;
}
}

View File

@ -3,6 +3,7 @@ using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Interface.Entity;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Core.Model;
namespace Milimoe.FunGame.Core.Entity
{
@ -336,8 +337,9 @@ namespace Milimoe.FunGame.Core.Entity
/// </summary>
/// <param name="queue"></param>
/// <param name="attacker"></param>
/// <param name="options"></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)
{
@ -350,8 +352,8 @@ namespace Milimoe.FunGame.Core.Entity
queue.WriteLine($"[ {Character} ] 对 [ {enemy} ] 发起了普通攻击!");
double expected = Damage;
int changeCount = 0;
DamageResult result = IsMagic ? queue.CalculateMagicalDamage(attacker, enemy, true, MagicType, expected, out double damage, ref changeCount) : queue.CalculatePhysicalDamage(attacker, enemy, true, expected, out damage, ref changeCount);
queue.DamageToEnemyAsync(attacker, enemy, damage, true, IsMagic ? DamageType.Magical : DamageType.Physical, MagicType, result);
DamageResult result = IsMagic ? queue.CalculateMagicalDamage(attacker, enemy, true, MagicType, expected, out double damage, ref changeCount, options) : queue.CalculatePhysicalDamage(attacker, enemy, true, expected, out damage, ref changeCount, options);
queue.DamageToEnemy(attacker, enemy, damage, true, IsMagic ? DamageType.Magical : DamageType.Physical, MagicType, result, options);
}
}
}

View File

@ -2,6 +2,7 @@
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Interface.Entity;
using Milimoe.FunGame.Core.Library.Common.Addon;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Entity
@ -37,6 +38,11 @@ namespace Milimoe.FunGame.Core.Entity
/// </summary>
public virtual string DispelDescription { get; set; } = "";
/// <summary>
/// 豁免性的描述
/// </summary>
public virtual string ExemptionDescription { get; set; } = "";
/// <summary>
/// 释放技能时的口号
/// </summary>
@ -146,18 +152,39 @@ namespace Milimoe.FunGame.Core.Entity
/// </summary>
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>
/// 作用范围形状<para/>
/// <see cref="SkillRangeType.Diamond"/> - 菱形。默认的曼哈顿距离正方形<para/>
/// <see cref="SkillRangeType.Circle"/> - 圆形。基于欧几里得距离的圆形<para/>
/// <see cref="SkillRangeType.Square"/> - 正方形<para/>
/// <see cref="SkillRangeType.Line"/> - 施法者与目标之间的直线<para/>
/// <see cref="SkillRangeType.Line"/> - 施法者与目标之间的线<para/>
/// <see cref="SkillRangeType.LinePass"/> - 施法者与目标所在的直线,贯穿至地图边缘<para/>
/// <see cref="SkillRangeType.Sector"/> - 扇形<para/>
/// 注意,该属性不影响选取目标的范围。选取目标的范围由 <see cref="Library.Common.Addon.GameMap"/> 决定。
/// 注意,该属性不影响选取目标的范围。选取目标的范围由 <see cref="GameMap"/> 决定。
/// </summary>
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>
@ -376,7 +403,7 @@ namespace Milimoe.FunGame.Core.Entity
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 ||
effects.Any(e => e.IgnoreImmune == ImmuneType.All || e.IgnoreImmune == ImmuneType.Skilled || (IsMagic && e.IgnoreImmune == ImmuneType.Magical))))
{
@ -386,7 +413,6 @@ namespace Milimoe.FunGame.Core.Entity
foreach (Character character in teammates)
{
IEnumerable<Effect> effects = character.Effects.Where(e => e.IsInEffect);
if (CanSelectTeammate)
{
selectable.Add(character);
@ -452,19 +478,146 @@ namespace Milimoe.FunGame.Core.Entity
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>
/// <param name="queue"></param>
/// <param name="caster"></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;
foreach (Effect e in Effects)
{
e.GamingQueue = GamingQueue;
e.OnSkillCasting(caster, targets);
e.OnSkillCasting(caster, targets, grids);
}
}
@ -483,13 +636,14 @@ namespace Milimoe.FunGame.Core.Entity
/// <param name="queue"></param>
/// <param name="caster"></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;
foreach (Effect e in Effects)
{
e.GamingQueue = GamingQueue;
e.OnSkillCasted(caster, targets, Values);
e.OnSkillCasted(caster, targets, grids, Values);
}
}
@ -556,6 +710,10 @@ namespace Milimoe.FunGame.Core.Entity
{
builder.AppendLine($"{DispelDescription}");
}
if (ExemptionDescription != "")
{
builder.AppendLine($"{ExemptionDescription}");
}
if (GamingQueue?.Map != null && SkillType != SkillType.Passive)
{
builder.AppendLine($"施法距离:{(CastAnywhere ? "" : CastRange)}");
@ -660,6 +818,7 @@ namespace Milimoe.FunGame.Core.Entity
skill.Description = skillDefined.Description;
skill.GeneralDescription = skillDefined.GeneralDescription;
skill.DispelDescription = skillDefined.DispelDescription;
skill.ExemptionDescription = skillDefined.ExemptionDescription;
skill.SkillType = skillDefined.SkillType;
skill.MPCost = skillDefined.MPCost;
skill.CastTime = skillDefined.CastTime;

View File

@ -1,11 +1,14 @@
namespace Milimoe.FunGame.Core.Entity
using Milimoe.FunGame.Core.Library.Common.Addon;
namespace Milimoe.FunGame.Core.Entity
{
/// <summary>
/// 技能和它的目标结构体
/// </summary>
/// <param name="skill"></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>
/// 技能实例
@ -13,8 +16,13 @@
public Skill Skill { get; set; } = skill;
/// <summary>
/// 技能的目标列表
/// 指向性技能的目标列表
/// </summary>
public List<Character> Targets { get; set; } = targets;
/// <summary>
/// 非指向性技能的目标列表
/// </summary>
public List<Grid> TargetGrids { get; set; } = grids;
}
}

View File

@ -50,5 +50,9 @@
public double AvgRank { get; set; } = 0;
public double Rating { get; set; } = 0;
public int MVPs { get; set; } = 0;
public int UseDecisionPoints { get; set; } = 0;
public int TurnDecisions { get; set; } = 0;
public int AvgUseDecisionPoints { get; set; } = 0;
public int AvgTurnDecisions { get; set; } = 0;
}
}

View File

@ -86,7 +86,7 @@ namespace Milimoe.FunGame.Core.Interface.Base
/// </summary>
/// <param name="character"></param>
/// <returns></returns>
public Task<bool> ProcessTurnAsync(Character character);
public bool ProcessTurn(Character character);
/// <summary>
/// 造成伤害
@ -98,7 +98,8 @@ namespace Milimoe.FunGame.Core.Interface.Base
/// <param name="damageType"></param>
/// <param name="magicType"></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>
/// 治疗一个目标
@ -107,7 +108,8 @@ namespace Milimoe.FunGame.Core.Interface.Base
/// <param name="target"></param>
/// <param name="heal"></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>
/// 计算物理伤害
@ -118,8 +120,9 @@ namespace Milimoe.FunGame.Core.Interface.Base
/// <param name="expectedDamage"></param>
/// <param name="finalDamage"></param>
/// <param name="changeCount"></param>
/// <param name="options"></param>
/// <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>
/// 计算魔法伤害
@ -131,28 +134,29 @@ namespace Milimoe.FunGame.Core.Interface.Base
/// <param name="expectedDamage"></param>
/// <param name="finalDamage"></param>
/// <param name="changeCount"></param>
/// <param name="options"></param>
/// <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>
/// <param name="killer"></param>
/// <param name="death"></param>
public Task DeathCalculationAsync(Character killer, Character death);
public void DeathCalculation(Character killer, Character death);
/// <summary>
/// 打断施法
/// </summary>
/// <param name="caster"></param>
/// <param name="interrupter"></param>
public Task InterruptCastingAsync(Character caster, Character interrupter);
public void InterruptCasting(Character caster, Character interrupter);
/// <summary>
/// 打断施法 [ 用于使敌人目标丢失 ]
/// </summary>
/// <param name="interrupter"></param>
public Task InterruptCastingAsync(Character interrupter);
public void InterruptCasting(Character interrupter);
/// <summary>
/// 使用物品
@ -163,9 +167,11 @@ namespace Milimoe.FunGame.Core.Interface.Base
/// <param name="enemys"></param>
/// <param name="teammates"></param>
/// <param name="castRange"></param>
/// <param name="desiredTargets"></param>
/// <param name="allEnemys"></param>
/// <param name="allTeammates"></param>
/// <param name="aiDecision"></param>
/// <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>
/// 角色移动
@ -175,7 +181,7 @@ namespace Milimoe.FunGame.Core.Interface.Base
/// <param name="target"></param>
/// <param name="startGrid"></param>
/// <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>
/// 选取移动目标
@ -186,7 +192,7 @@ namespace Milimoe.FunGame.Core.Interface.Base
/// <param name="map"></param>
/// <param name="moveRange"></param>
/// <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>
/// 选取技能目标
@ -197,7 +203,7 @@ namespace Milimoe.FunGame.Core.Interface.Base
/// <param name="teammates"></param>
/// <param name="castRange"></param>
/// <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>
/// 选取普通攻击目标
@ -208,7 +214,21 @@ namespace Milimoe.FunGame.Core.Interface.Base
/// <param name="teammates"></param>
/// <param name="attackRange"></param>
/// <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>
/// 判断目标对于某个角色是否是队友
@ -256,5 +276,34 @@ namespace Milimoe.FunGame.Core.Interface.Base
/// <param name="damageType"></param>
/// <param name="takenDamage"></param>
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);
}
}

View File

@ -338,16 +338,15 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
// 不使用框架的实现时,需要地图作者与游戏队列的作者做好适配
if (queue is GamingQueue gq)
{
gq.SelectTargetGrid += Gq_SelectTargetGrid;
gq.SelectTargetGridEvent += Gq_SelectTargetGrid;
}
return map;
}
private async Task<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;
}
}

View File

@ -366,6 +366,305 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
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>
@ -414,12 +713,16 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
return currentSteps;
}
// 定义平面移动的四个方向
// 定义平面移动的方向
(int dx, int dy)[] directions = [
(0, 1), // 上
(0, -1), // 下
(1, 0), // 右
(-1, 0) // 左
(-1, 0), // 左
(1, 1), // 右上
(1, -1), // 右下
(-1, 1), // 左上
(-1, -1) // 左下
];
foreach (var (dx, dy) in directions)
@ -484,9 +787,10 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
int minDistanceToTarget = CalculateManhattanDistance(startGrid, target);
int stepsToBestReachable = 0;
// 定义平面移动的四个方向
// 定义平面移动的方向
(int dx, int dy)[] directions = [
(0, 1), (0, -1), (1, 0), (-1, 0)
(0, 1), (0, -1), (1, 0), (-1, 0),
(1, 1), (1, -1), (-1, 1), (-1, -1)
];
while (queue.Count > 0)
@ -552,6 +856,101 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
return Math.Abs(g1.X - g2.X) + Math.Abs(g1.Y - g2.Y) + Math.Abs(g1.Z - g2.Z);
}
/// <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>
@ -565,7 +964,7 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
/// 在事件流逝后处理
/// </summary>
/// <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);
}
}
}

View File

@ -1,4 +1,5 @@
using Milimoe.FunGame.Core.Model;
using System.Text;
using Milimoe.FunGame.Core.Model;
/**
* String Set
@ -703,7 +704,7 @@ namespace Milimoe.FunGame.Core.Library.Constant
EffectType.Item => "装备特效",
EffectType.Mark => "标记",
EffectType.Stun => "眩晕",
EffectType.Freeze => "冻",
EffectType.Freeze => "",
EffectType.Silence => "沉默",
EffectType.Root => "定身",
EffectType.Fear => "恐惧",
@ -751,6 +752,8 @@ namespace Milimoe.FunGame.Core.Library.Constant
EffectType.Recovery => "恢复",
EffectType.Vulnerable => "易伤",
EffectType.Delay => "迟滞",
EffectType.Focusing => "专注",
EffectType.InterruptCasting => "打断施法",
_ => "未知效果"
};
}
@ -811,10 +814,49 @@ namespace Milimoe.FunGame.Core.Library.Constant
EffectType.Recovery => DispelledType.Weak,
EffectType.Vulnerable => DispelledType.Weak,
EffectType.Delay => DispelledType.Weak,
EffectType.InterruptCasting => DispelledType.Weak,
_ => DispelledType.Weak
};
}
public static PrimaryAttribute GetExemptionTypeByEffectType(EffectType type)
{
return type switch
{
EffectType.Stun => PrimaryAttribute.STR,
EffectType.Freeze => PrimaryAttribute.STR,
EffectType.Silence => PrimaryAttribute.INT,
EffectType.Root => PrimaryAttribute.STR,
EffectType.Fear => PrimaryAttribute.INT,
EffectType.Sleep => PrimaryAttribute.STR,
EffectType.Knockback => PrimaryAttribute.STR,
EffectType.Knockdown => PrimaryAttribute.STR,
EffectType.Taunt => PrimaryAttribute.INT,
EffectType.Slow => PrimaryAttribute.AGI,
EffectType.Weaken => PrimaryAttribute.STR,
EffectType.Poison => PrimaryAttribute.AGI,
EffectType.Burn => PrimaryAttribute.AGI,
EffectType.Bleed => PrimaryAttribute.AGI,
EffectType.Blind => PrimaryAttribute.AGI,
EffectType.Cripple => PrimaryAttribute.STR,
EffectType.MagicResistBreak => PrimaryAttribute.INT,
EffectType.Curse => PrimaryAttribute.INT,
EffectType.Exhaustion => PrimaryAttribute.STR,
EffectType.ManaBurn => PrimaryAttribute.INT,
EffectType.Charm => PrimaryAttribute.INT,
EffectType.Disarm => PrimaryAttribute.STR,
EffectType.Confusion => PrimaryAttribute.INT,
EffectType.Petrify => PrimaryAttribute.STR,
EffectType.SilenceMagic => PrimaryAttribute.INT,
EffectType.Banish => PrimaryAttribute.INT,
EffectType.GrievousWound => PrimaryAttribute.STR,
EffectType.Vulnerable => PrimaryAttribute.INT,
EffectType.Delay => PrimaryAttribute.AGI,
EffectType.InterruptCasting => PrimaryAttribute.INT,
_ => PrimaryAttribute.None
};
}
public static bool GetIsDebuffByEffectType(EffectType type)
{
return type switch
@ -871,6 +913,7 @@ namespace Milimoe.FunGame.Core.Library.Constant
EffectType.Recovery => false,
EffectType.Vulnerable => true,
EffectType.Delay => true,
EffectType.InterruptCasting => true,
_ => false
};
}
@ -922,5 +965,23 @@ namespace Milimoe.FunGame.Core.Library.Constant
_ => ""
};
}
public static string GetExemptionDescription(EffectType type, bool exemptDuration = true)
{
PrimaryAttribute pa = GetExemptionTypeByEffectType(type);
if (pa == PrimaryAttribute.None)
{
return "";
}
return GetExemptionDescription(pa, exemptDuration);
}
public static string GetExemptionDescription(PrimaryAttribute type, bool exemptDuration = true)
{
StringBuilder builder = new();
builder.AppendLine($"豁免类型:{CharacterSet.GetPrimaryAttributeName(type)}");
builder.Append($"豁免持续时间:{(exemptDuration ? "" : "")}");
return builder.ToString();
}
}
}

View File

@ -233,6 +233,7 @@ namespace Milimoe.FunGame.Core.Library.Constant
/// <para><see cref="SkillSet.GetDispelledTypeByEffectType(EffectType)"/></para>
/// <para><see cref="SkillSet.GetEffectTypeName(EffectType)"/></para>
/// <para><see cref="SkillSet.GetIsDebuffByEffectType(EffectType)"/></para>
/// <para><see cref="SkillSet.GetExemptionTypeByEffectType(EffectType)"/></para>
/// </summary>
public enum EffectType
{
@ -494,7 +495,17 @@ namespace Milimoe.FunGame.Core.Library.Constant
/// <summary>
/// 迟滞,硬直时间延长
/// </summary>
Delay
Delay,
/// <summary>
/// 专注
/// </summary>
Focusing,
/// <summary>
/// 打断施法
/// </summary>
InterruptCasting
}
public enum ItemType
@ -1066,11 +1077,34 @@ namespace Milimoe.FunGame.Core.Library.Constant
public enum SkillRangeType
{
/// <summary>
/// 菱形
/// </summary>
Diamond,
/// <summary>
/// 圆形
/// </summary>
Circle,
/// <summary>
/// 正方形
/// </summary>
Square,
/// <summary>
/// 施法者与目标之间的线段
/// </summary>
Line,
/// <summary>
/// 施法者与目标所在的直线,贯穿至地图边缘
/// </summary>
LinePass,
/// <summary>
/// 扇形
/// </summary>
Sector
}
}

View File

@ -12,6 +12,7 @@ namespace Milimoe.FunGame.Core.Model
public ISkill? SkillToUse { get; set; } = null;
public Item? ItemToUse { get; set; } = null;
public List<Character> Targets { get; set; } = [];
public List<Grid> TargetGrids { get; set; } = [];
public double Score { get; set; } = 0;
public bool IsPureMove { get; set; } = false;
}

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

View File

@ -230,6 +230,11 @@ namespace Milimoe.FunGame.Core.Model
/// </summary>
public double STRtoCritDMGMultiplier { get; set; } = 0.00575;
/// <summary>
/// 每 1 点力量增加力量豁免率
/// </summary>
public double STRtoExemptionRateMultiplier { get; set; } = 0.001;
/// <summary>
/// 每 1 点智力增加魔法值
/// </summary>
@ -260,6 +265,11 @@ namespace Milimoe.FunGame.Core.Model
/// </summary>
public double INTtoAccelerationCoefficientMultiplier { get; set; } = 0.00125;
/// <summary>
/// 每 1 点智力增加智力豁免率
/// </summary>
public double INTtoExemptionRateMultiplier { get; set; } = 0.001;
/// <summary>
/// 每 1 点敏捷增加行动速度
/// </summary>
@ -275,6 +285,11 @@ namespace Milimoe.FunGame.Core.Model
/// </summary>
public double AGItoEvadeRateMultiplier { get; set; } = 0.00175;
/// <summary>
/// 每 1 点敏捷增加敏捷豁免率
/// </summary>
public double AGItoExemptionRateMultiplier { get; set; } = 0.001;
/// <summary>
/// 造成伤害获得能量值因子
/// </summary>

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@ namespace Milimoe.FunGame.Core.Model
/// <param name="death"></param>
/// <param name="killer"></param>
/// <returns></returns>
protected override async Task AfterDeathCalculation(Character death, Character killer)
protected override void AfterDeathCalculation(Character death, Character killer)
{
if (MaxRespawnTimes != 0 && MaxScoreToWin > 0)
{
@ -25,12 +25,12 @@ namespace Milimoe.FunGame.Core.Model
if (!_queue.Where(c => c != killer).Any())
{
// 没有其他的角色了,游戏结束
await EndGameInfo(killer);
EndGameInfo(killer);
}
if (MaxScoreToWin > 0 && _stats[killer].Kills >= MaxScoreToWin)
{
await EndGameInfo(killer);
EndGameInfo(killer);
return;
}
}
@ -41,9 +41,9 @@ namespace Milimoe.FunGame.Core.Model
/// <param name="character"></param>
/// <param name="type"></param>
/// <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 (MaxRespawnTimes != 0 && MaxScoreToWin > 0 && _stats[character].Kills >= MaxScoreToWin)
@ -57,7 +57,7 @@ namespace Milimoe.FunGame.Core.Model
/// <summary>
/// 游戏结束信息
/// </summary>
public async Task EndGameInfo(Character winner)
public void EndGameInfo(Character winner)
{
WriteLine("[ " + winner + " ] 是胜利者。");
foreach (Character character in _stats.OrderBy(kv => kv.Value.Kills)
@ -73,7 +73,7 @@ namespace Milimoe.FunGame.Core.Model
_queue.Clear();
_isGameEnd = true;
if (!await OnGameEndAsync(winner))
if (!OnGameEndEvent(winner))
{
return;
}

View File

@ -77,7 +77,7 @@ namespace Milimoe.FunGame.Core.Model
/// 获取某角色的团队成员
/// </summary>
/// <param name="character"></param>
protected override List<Character> GetTeammates(Character character)
public override List<Character> GetTeammates(Character character)
{
foreach (string team in _teams.Keys)
{
@ -95,7 +95,7 @@ namespace Milimoe.FunGame.Core.Model
/// <param name="character"></param>
/// <param name="dp"></param>
/// <returns></returns>
protected override async Task AfterCharacterDecision(Character character, DecisionPoints dp)
protected override void AfterCharacterDecision(Character character, DecisionPoints dp)
{
// 如果目标都是队友,会考虑非伤害型助攻
Team? team = GetTeam(character);
@ -103,7 +103,6 @@ namespace Milimoe.FunGame.Core.Model
{
SetNotDamageAssistTime(character, LastRound.Targets.Values.SelectMany(c => c).Where(team.IsOnThisTeam));
}
else await Task.CompletedTask;
}
/// <summary>
@ -112,9 +111,9 @@ namespace Milimoe.FunGame.Core.Model
/// <param name="character"></param>
/// <param name="type"></param>
/// <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)
{
Team? team = GetTeam(character);
@ -132,7 +131,7 @@ namespace Milimoe.FunGame.Core.Model
/// <param name="death"></param>
/// <param name="killer"></param>
/// <returns></returns>
protected override async Task OnDeathCalculation(Character death, Character killer)
protected override void OnDeathCalculation(Character death, Character killer)
{
if (killer == death)
{
@ -149,7 +148,6 @@ namespace Milimoe.FunGame.Core.Model
}
else team.Score++;
}
else await Task.CompletedTask;
}
/// <summary>
@ -158,7 +156,7 @@ namespace Milimoe.FunGame.Core.Model
/// <param name="death"></param>
/// <param name="killer"></param>
/// <returns></returns>
protected override async Task AfterDeathCalculation(Character death, Character killer)
protected override void AfterDeathCalculation(Character death, Character killer)
{
Team? killTeam = GetTeam(killer);
Team? deathTeam = GetTeam(death);
@ -200,7 +198,7 @@ namespace Milimoe.FunGame.Core.Model
if (!_teams.Keys.Where(str => str != killTeam.Name).Any())
{
// 没有其他的团队了,游戏结束
await EndGameInfo(killTeam);
EndGameInfo(killTeam);
return;
}
if (MaxScoreToWin > 0 && killTeam.Score >= MaxScoreToWin)
@ -209,7 +207,7 @@ namespace Milimoe.FunGame.Core.Model
combinedTeams.Remove(killTeam);
_eliminatedTeams.Clear();
_eliminatedTeams.AddRange(combinedTeams.OrderByDescending(t => t.Score));
await EndGameInfo(killTeam);
EndGameInfo(killTeam);
return;
}
}
@ -218,12 +216,12 @@ namespace Milimoe.FunGame.Core.Model
/// <summary>
/// 游戏结束信息 [ 团队版 ]
/// </summary>
public async Task EndGameInfo(Team winner)
public void EndGameInfo(Team winner)
{
winner.IsWinner = true;
WriteLine("[ " + winner + " ] 是胜利者。");
if (!await OnGameEndTeamAsync(winner))
if (!OnGameEndTeamEvent(winner))
{
return;
}
@ -322,19 +320,19 @@ namespace Milimoe.FunGame.Core.Model
}
public delegate Task<bool> GameEndTeamEventHandler(TeamGamingQueue queue, Team winner);
public delegate bool GameEndTeamEventHandler(TeamGamingQueue queue, Team winner);
/// <summary>
/// 游戏结束事件(团队版)
/// </summary>
public event GameEndTeamEventHandler? GameEndTeam;
public event GameEndTeamEventHandler? GameEndTeamEvent;
/// <summary>
/// 游戏结束事件(团队版)
/// </summary>
/// <param name="winner"></param>
/// <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;
}
}
}