From 2c39f46dd936dd4f176d0d9c339412b3701f00d7 Mon Sep 17 00:00:00 2001
From: milimoe <110188673+milimoe@users.noreply.github.com>
Date: Sat, 13 Sep 2025 15:10:11 +0800
Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=88=98=E6=A3=8B=E5=9C=B0?=
=?UTF-8?q?=E5=9B=BE=E7=8E=A9=E6=B3=95=20(#142)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Controller/AIController.cs | 322 +++++++++++++++++
Entity/Character/Character.cs | 104 +++++-
Entity/Character/Unit.cs | 24 +-
Entity/Item/Item.cs | 12 +-
Entity/Skill/Effect.cs | 19 +-
Entity/Skill/NormalAttack.cs | 6 +
Entity/Skill/OpenSkill.cs | 46 +++
Entity/Skill/Skill.cs | 33 +-
Entity/System/Team.cs | 14 +-
Interface/Base/IGamingQueue.cs | 27 +-
Interface/Entity/Typical/ISkill.cs | 6 +
.../Common/Addon/Example/ExampleGameModule.cs | 2 +-
Library/Common/Addon/GameMap.cs | 122 ++++++-
.../JsonConverter/CharacterConverter.cs | 16 +-
Library/Constant/TypeEnum.cs | 10 +
Model/AIDecision.cs | 18 +
Model/EquilibriumConstant.cs | 80 +++++
Model/GamingQueue.cs | 324 ++++++++++++++----
Model/TeamGamingQueue.cs | 6 +-
19 files changed, 1067 insertions(+), 124 deletions(-)
create mode 100644 Controller/AIController.cs
create mode 100644 Model/AIDecision.cs
diff --git a/Controller/AIController.cs b/Controller/AIController.cs
new file mode 100644
index 0000000..35b77a0
--- /dev/null
+++ b/Controller/AIController.cs
@@ -0,0 +1,322 @@
+using Milimoe.FunGame.Core.Entity;
+using Milimoe.FunGame.Core.Interface.Entity;
+using Milimoe.FunGame.Core.Library.Common.Addon;
+using Milimoe.FunGame.Core.Library.Constant;
+using Milimoe.FunGame.Core.Model;
+
+namespace Milimoe.FunGame.Core.Controller
+{
+ public class AIController(GamingQueue queue, GameMap map)
+ {
+ private readonly GamingQueue _queue = queue;
+ private readonly GameMap _map = map;
+
+ ///
+ /// AI的核心决策方法,根据当前游戏状态为角色选择最佳行动。
+ ///
+ /// 当前行动的AI角色。
+ /// 角色的起始格子。
+ /// 从起始格子可达的所有移动格子(包括起始格子本身)。
+ /// 角色所有可用的技能(已过滤CD和EP/MP)。
+ /// 角色所有可用的物品(已过滤CD和EP/MP)。
+ /// 场上所有敌人。
+ /// 场上所有队友。
+ /// 包含最佳行动的AIDecision对象。
+ public async Task DecideAIActionAsync(Character character, Grid startGrid, List allPossibleMoveGrids,
+ List availableSkills, List- availableItems, List allEnemysInGame, List allTeammatesInGame)
+ {
+ // 初始化一个默认的“结束回合”决策作为基准
+ AIDecision bestDecision = new()
+ {
+ ActionType = CharacterActionType.EndTurn,
+ TargetMoveGrid = startGrid,
+ Targets = [],
+ Score = -1000.0
+ };
+
+ // 遍历所有可能的移动目标格子 (包括起始格子本身)
+ foreach (Grid potentialMoveGrid in allPossibleMoveGrids)
+ {
+ // 计算移动到这个格子的代价(曼哈顿距离)
+ int moveDistance = GameMap.CalculateManhattanDistance(startGrid, potentialMoveGrid);
+ double movePenalty = moveDistance * 0.5; // 每移动一步扣0.5分
+
+ if (CanCharacterNormalAttack(character))
+ {
+ // 计算普通攻击的可达格子
+ List normalAttackReachableGrids = _map.GetGridsByRange(potentialMoveGrid, character.ATR, true);
+
+ List normalAttackReachableEnemys = [.. allEnemysInGame
+ .Where(c => normalAttackReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable)
+ .Distinct()];
+ List normalAttackReachableTeammates = [.. allTeammatesInGame
+ .Where(c => normalAttackReachableGrids.SelectMany(g => g.Characters).Contains(c))
+ .Distinct()];
+
+ if (normalAttackReachableEnemys.Count > 0)
+ {
+ // 将筛选后的目标列表传递给 SelectTargets
+ List targets = SelectTargets(character, character.NormalAttack, normalAttackReachableEnemys, normalAttackReachableTeammates);
+ if (targets.Count > 0)
+ {
+ double currentScore = EvaluateNormalAttack(character, targets) - movePenalty;
+ if (currentScore > bestDecision.Score)
+ {
+ bestDecision = new AIDecision
+ {
+ ActionType = CharacterActionType.NormalAttack,
+ TargetMoveGrid = potentialMoveGrid,
+ SkillToUse = character.NormalAttack,
+ Targets = targets,
+ Score = currentScore
+ };
+ }
+ }
+ }
+ }
+
+ foreach (Skill skill in availableSkills)
+ {
+ if (CanCharacterUseSkill(character) && _queue.CheckCanCast(character, skill, out double cost))
+ {
+ // 计算当前技能的可达格子
+ List skillReachableGrids = _map.GetGridsByRange(potentialMoveGrid, skill.CastRange, true);
+
+ List skillReachableEnemys = [.. allEnemysInGame
+ .Where(c => skillReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable)
+ .Distinct()];
+ List skillReachableTeammates = [.. allTeammatesInGame
+ .Where(c => skillReachableGrids.SelectMany(g => g.Characters).Contains(c))
+ .Distinct()];
+
+ // 检查是否有可用的目标(敌人或队友,取决于技能类型)
+ if (skillReachableEnemys.Count > 0 || skillReachableTeammates.Count > 0)
+ {
+ // 将筛选后的目标列表传递给 SelectTargets
+ List targets = SelectTargets(character, skill, skillReachableEnemys, skillReachableTeammates);
+ if (targets.Count > 0)
+ {
+ double currentScore = EvaluateSkill(character, skill, targets, cost) - movePenalty;
+ if (currentScore > bestDecision.Score)
+ {
+ bestDecision = new AIDecision
+ {
+ ActionType = CharacterActionType.PreCastSkill,
+ TargetMoveGrid = potentialMoveGrid,
+ SkillToUse = skill,
+ Targets = targets,
+ Score = currentScore
+ };
+ }
+ }
+ }
+ }
+ }
+
+ foreach (Item item in availableItems)
+ {
+ if (item.Skills.Active != null && CanCharacterUseItem(character, item) && _queue.CheckCanCast(character, item.Skills.Active, out double cost))
+ {
+ Skill itemSkill = item.Skills.Active;
+
+ // 计算当前物品技能的可达格子
+ List itemSkillReachableGrids = _map.GetGridsByRange(potentialMoveGrid, itemSkill.CastRange, true);
+
+ List itemSkillReachableEnemys = [.. allEnemysInGame
+ .Where(c => itemSkillReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable)
+ .Distinct()];
+ List itemSkillReachableTeammates = [.. allTeammatesInGame
+ .Where(c => itemSkillReachableGrids.SelectMany(g => g.Characters).Contains(c))
+ .Distinct()];
+
+ // 检查是否有可用的目标
+ if (itemSkillReachableEnemys.Count > 0 || itemSkillReachableTeammates.Count > 0)
+ {
+ // 将筛选后的目标列表传递给 SelectTargets
+ List targetsForItem = SelectTargets(character, itemSkill, itemSkillReachableEnemys, itemSkillReachableTeammates);
+ if (targetsForItem.Count > 0)
+ {
+ double currentScore = EvaluateItem(character, item, targetsForItem, cost) - movePenalty;
+ if (currentScore > bestDecision.Score)
+ {
+ bestDecision = new AIDecision
+ {
+ ActionType = CharacterActionType.UseItem,
+ TargetMoveGrid = potentialMoveGrid,
+ ItemToUse = item,
+ SkillToUse = itemSkill,
+ Targets = targetsForItem,
+ Score = currentScore
+ };
+ }
+ }
+ }
+ }
+ }
+
+ // 如果从该格子没有更好的行动,但移动本身有价值
+ // 只有当当前最佳决策是“结束回合”或分数很低时,才考虑纯粹的移动。
+ if (potentialMoveGrid != startGrid && bestDecision.Score < 0) // 如果当前最佳决策是负分(即什么都不做)
+ {
+ double pureMoveScore = -movePenalty; // 移动本身有代价
+
+ // 为纯粹移动逻辑重新计算综合可达敌人列表
+ List tempAttackGridsForPureMove = _map.GetGridsByRange(potentialMoveGrid, character.ATR, true);
+ List tempCastGridsForPureMove = [];
+ foreach (Skill skill in availableSkills)
+ {
+ tempCastGridsForPureMove.AddRange(_map.GetGridsByRange(potentialMoveGrid, skill.CastRange, true));
+ }
+ foreach (Item item in availableItems)
+ {
+ if (item.Skills.Active != null)
+ {
+ tempCastGridsForPureMove.AddRange(_map.GetGridsByRange(potentialMoveGrid, item.Skills.Active.CastRange, true));
+ }
+ }
+ List tempAllReachableGridsForPureMove = [.. tempAttackGridsForPureMove.Union(tempCastGridsForPureMove).Distinct()];
+ List tempCurrentReachableEnemysForPureMove = [.. allEnemysInGame
+ .Where(c => tempAllReachableGridsForPureMove.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable)
+ .Distinct()];
+
+ // 如果当前位置无法攻击任何敌人,但地图上还有敌人,尝试向最近的敌人移动
+ if (tempCurrentReachableEnemysForPureMove.Count == 0 && allEnemysInGame.Count > 0) // 使用新计算的列表
+ {
+ Character? target = allEnemysInGame
+ .OrderBy(e => GameMap.CalculateManhattanDistance(potentialMoveGrid, _map.GetCharacterCurrentGrid(e)!))
+ .FirstOrDefault();
+
+ if (target != null)
+ {
+ Grid? nearestEnemyGrid = _map.GetCharacterCurrentGrid(target);
+ if (nearestEnemyGrid != null)
+ {
+ // 奖励靠近敌人
+ pureMoveScore += (10 - GameMap.CalculateManhattanDistance(potentialMoveGrid, nearestEnemyGrid)) * 0.1;
+ }
+ }
+ }
+
+ // 如果纯粹移动比当前最佳(什么都不做)更好
+ if (pureMoveScore > bestDecision.Score)
+ {
+ bestDecision = new AIDecision
+ {
+ ActionType = CharacterActionType.Move,
+ TargetMoveGrid = potentialMoveGrid,
+ Targets = [],
+ Score = pureMoveScore,
+ IsPureMove = true
+ };
+ }
+ }
+ }
+
+ return await Task.FromResult(bestDecision);
+ }
+
+ // --- AI 决策辅助方法 ---
+
+ // 检查角色是否能进行普通攻击(基于状态)
+ private static bool CanCharacterNormalAttack(Character character)
+ {
+ return character.CharacterState != CharacterState.NotActionable &&
+ character.CharacterState != CharacterState.ActionRestricted &&
+ character.CharacterState != CharacterState.BattleRestricted &&
+ character.CharacterState != CharacterState.AttackRestricted;
+ }
+
+ // 检查角色是否能使用某个技能(基于状态)
+ private static bool CanCharacterUseSkill(Character character)
+ {
+ return character.CharacterState != CharacterState.NotActionable &&
+ character.CharacterState != CharacterState.ActionRestricted &&
+ character.CharacterState != CharacterState.BattleRestricted &&
+ character.CharacterState != CharacterState.SkillRestricted;
+ }
+
+ // 检查角色是否能使用某个物品(基于状态)
+ private static bool CanCharacterUseItem(Character character, Item item)
+ {
+ return character.CharacterState != CharacterState.NotActionable &&
+ (character.CharacterState != CharacterState.ActionRestricted || item.ItemType == ItemType.Consumable) && // 行动受限只能用消耗品
+ character.CharacterState != CharacterState.BattleRestricted;
+ }
+ ///
+ /// 选择技能的最佳目标
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static List SelectTargets(Character character, ISkill skill, List enemys, List teammates)
+ {
+ List targets = skill.GetSelectableTargets(character, enemys, teammates);
+ int count = skill.RealCanSelectTargetCount(enemys, teammates);
+ return [.. targets.OrderBy(o => Random.Shared.Next()).Take(count)];
+ }
+
+ ///
+ /// 评估普通攻击的价值
+ ///
+ ///
+ ///
+ ///
+ private static double EvaluateNormalAttack(Character character, List targets)
+ {
+ double score = 0;
+ foreach (Character target in targets)
+ {
+ double damage = character.NormalAttack.Damage * (1 - target.PDR);
+ score += damage;
+ if (target.HP <= damage) score += 100;
+ }
+ return score;
+ }
+
+ ///
+ /// 评估技能的价值
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static double EvaluateSkill(Character character, Skill skill, List targets, double cost)
+ {
+ double score = 0;
+ score += targets.Sum(t => CalculateTargetValue(t, skill));
+ //score -= cost * 5;
+ //score -= skill.RealCD * 2;
+ //score -= skill.HardnessTime * 2;
+ return score;
+ }
+
+ ///
+ /// 评估物品的价值
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static double EvaluateItem(Character character, Item item, List targets, double cost)
+ {
+ double score = Random.Shared.Next(1000);
+ return score;
+ }
+
+ ///
+ /// 辅助函数:计算单个目标在某个技能下的价值
+ ///
+ ///
+ ///
+ ///
+ private static double CalculateTargetValue(Character target, ISkill skill)
+ {
+ double value = Random.Shared.Next(1000);
+ return value;
+ }
+ }
+}
diff --git a/Entity/Character/Character.cs b/Entity/Character/Character.cs
index 139d405..64255c2 100644
--- a/Entity/Character/Character.cs
+++ b/Entity/Character/Character.cs
@@ -219,7 +219,7 @@ namespace Milimoe.FunGame.Core.Entity
///
/// 最大生命值 = 基础生命值 + 额外生命值 + 额外生命值2 + 额外生命值3
///
- public double MaxHP => BaseHP + ExHP + ExHP2 + ExHP3;
+ public double MaxHP => Math.Max(1, BaseHP + ExHP + ExHP2 + ExHP3);
///
/// 当前生命值 [ 战斗相关 ]
@@ -238,6 +238,12 @@ namespace Milimoe.FunGame.Core.Entity
}
}
+ ///
+ /// 是否有魔法值 [ 初始设定 ]
+ ///
+ [InitRequired]
+ public bool HasMP { get; set; } = true;
+
///
/// 初始魔法值 [ 初始设定 ]
///
@@ -272,7 +278,7 @@ namespace Milimoe.FunGame.Core.Entity
///
/// 最大魔法值 = 基础魔法值 + 额外魔法值 + 额外魔法值2 + 额外魔法值3
///
- public double MaxMP => BaseMP + ExMP + ExMP2 + ExMP3;
+ public double MaxMP => Math.Max(1, BaseMP + ExMP + ExMP2 + ExMP3);
///
/// 当前魔法值 [ 战斗相关 ]
@@ -749,16 +755,68 @@ namespace Milimoe.FunGame.Core.Entity
public double ExCDR { get; set; } = 0;
///
- /// 攻击距离 [ 与技能和物品相关 ] [ 单位:格(半径) ]
+ /// 攻击距离 [ 与武器相关 ] [ 单位:格(半径) ]
///
- [InitOptional]
- public int ATR { get; set; } = 1;
+ public int ATR
+ {
+ get
+ {
+ int baseATR = 1;
+ if (EquipSlot.Weapon != null)
+ {
+ baseATR = EquipSlot.Weapon.WeaponType switch
+ {
+ WeaponType.OneHandedSword => GameplayEquilibriumConstant.OneHandedSwordAttackRange,
+ WeaponType.TwoHandedSword => GameplayEquilibriumConstant.TwoHandedSwordAttackRange,
+ WeaponType.Bow => GameplayEquilibriumConstant.BowAttackRange,
+ WeaponType.Pistol => GameplayEquilibriumConstant.PistolAttackRange,
+ WeaponType.Rifle => GameplayEquilibriumConstant.RifleAttackRange,
+ WeaponType.DualDaggers => GameplayEquilibriumConstant.DualDaggersAttackRange,
+ WeaponType.Talisman => GameplayEquilibriumConstant.TalismanAttackRange,
+ WeaponType.Staff => GameplayEquilibriumConstant.StaffAttackRange,
+ WeaponType.Polearm => GameplayEquilibriumConstant.PolearmAttackRange,
+ WeaponType.Gauntlet => GameplayEquilibriumConstant.GauntletAttackRange,
+ WeaponType.HiddenWeapon => GameplayEquilibriumConstant.HiddenWeaponAttackRange,
+ _ => baseATR
+ };
+ }
+ return Math.Max(1, baseATR + ExATR);
+ }
+ }
+
+ ///
+ /// 额外攻击距离 [ 与技能和物品相关 ] [ 单位:格(半径) ]
+ ///
+ public int ExATR { get; set; } = 0;
+
+ ///
+ /// 行动力/可移动距离 [ 与第一定位相关 ] [ 单位:格(半径) ]
+ ///
+ public int MOV
+ {
+ get
+ {
+ int baseMOV = 3;
+ if (EquipSlot.Weapon != null)
+ {
+ baseMOV = FirstRoleType switch
+ {
+ RoleType.Core => GameplayEquilibriumConstant.RoleMOV_Core,
+ RoleType.Vanguard => GameplayEquilibriumConstant.RoleMOV_Vanguard,
+ RoleType.Guardian => GameplayEquilibriumConstant.RoleMOV_Guardian,
+ RoleType.Support => GameplayEquilibriumConstant.RoleMOV_Support,
+ RoleType.Medic => GameplayEquilibriumConstant.RoleMOV_Medic,
+ _ => baseMOV
+ };
+ }
+ return Math.Max(1, baseMOV + ExMOV);
+ }
+ }
///
/// 行动力/可移动距离 [ 与技能和物品相关 ] [ 单位:格(半径) ]
///
- [InitOptional]
- public int MOV { get; set; } = 5;
+ public int ExMOV { get; set; } = 0;
///
/// 暴击率(%) = [ 与敏捷相关 ] + 额外暴击率(%)
@@ -1388,7 +1446,7 @@ namespace Milimoe.FunGame.Core.Entity
/// 获取角色的详细信息
///
///
- public string GetInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false)
+ public string GetInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false, bool showMapRelated = false)
{
StringBuilder builder = new();
@@ -1434,6 +1492,12 @@ namespace Milimoe.FunGame.Core.Entity
builder.AppendLine($"魔法消耗减少:{INT * GameplayEquilibriumConstant.INTtoCastMPReduce * 100:0.##}%");
builder.AppendLine($"能量消耗减少:{INT * GameplayEquilibriumConstant.INTtoCastEPReduce * 100:0.##}%");
+ if (showMapRelated)
+ {
+ builder.AppendLine($"移动距离:{MOV}");
+ builder.AppendLine($"攻击距离:{ATR}");
+ }
+
GetStatusInfo(builder);
builder.AppendLine("== 普通攻击 ==");
@@ -1475,7 +1539,7 @@ namespace Milimoe.FunGame.Core.Entity
/// 获取角色的简略信息
///
///
- public string GetSimpleInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false, bool showBasicOnly = false)
+ public string GetSimpleInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false, bool showBasicOnly = false, bool showMapRelated = false)
{
StringBuilder builder = new();
@@ -1518,6 +1582,12 @@ namespace Milimoe.FunGame.Core.Entity
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.##}]" : ""));
+ if (showMapRelated)
+ {
+ builder.AppendLine($"移动距离:{MOV}");
+ builder.AppendLine($"攻击距离:{ATR}");
+ }
+
if (!showBasicOnly)
{
GetStatusInfo(builder);
@@ -1685,7 +1755,7 @@ namespace Milimoe.FunGame.Core.Entity
/// 获取角色的物品信息
///
///
- public string GetItemInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false)
+ public string GetItemInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false, bool showMapRelated = false)
{
StringBuilder builder = new();
@@ -1731,6 +1801,12 @@ namespace Milimoe.FunGame.Core.Entity
builder.AppendLine($"魔法消耗减少:{INT * GameplayEquilibriumConstant.INTtoCastMPReduce * 100:0.##}%");
builder.AppendLine($"能量消耗减少:{INT * GameplayEquilibriumConstant.INTtoCastEPReduce * 100:0.##}%");
+ if (showMapRelated)
+ {
+ builder.AppendLine($"移动距离:{MOV}");
+ builder.AppendLine($"攻击距离:{ATR}");
+ }
+
if (EquipSlot.Any())
{
builder.AppendLine(GetEquipSlotInfo().Trim());
@@ -2022,8 +2098,8 @@ namespace Milimoe.FunGame.Core.Entity
c.MDF = MDF.Copy();
c.Lifesteal = Lifesteal;
c.Shield = Shield.Copy();
- c.ATR = ATR;
- c.MOV = MOV;
+ c.ExATR = ExATR;
+ c.ExMOV = ExMOV;
c.MagicType = MagicType;
c.ImmuneType = ImmuneType;
}
@@ -2132,8 +2208,8 @@ namespace Milimoe.FunGame.Core.Entity
ExActionCoefficient = c.ExActionCoefficient;
ExAccelerationCoefficient = c.ExAccelerationCoefficient;
ExCDR = c.ExCDR;
- ATR = c.ATR;
- MOV = c.MOV;
+ ExATR = c.ExATR;
+ ExMOV = c.ExMOV;
ExCritRate = c.ExCritRate;
ExCritDMG = c.ExCritDMG;
ExEvadeRate = c.ExEvadeRate;
diff --git a/Entity/Character/Unit.cs b/Entity/Character/Unit.cs
index bd0cb18..08e75fa 100644
--- a/Entity/Character/Unit.cs
+++ b/Entity/Character/Unit.cs
@@ -47,7 +47,7 @@ namespace Milimoe.FunGame.Core.Entity
/// 获取单位的详细信息
///
///
- public new string GetInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false)
+ public new string GetInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false, bool showMapRelated = false)
{
StringBuilder builder = new();
@@ -78,6 +78,12 @@ namespace Milimoe.FunGame.Core.Entity
builder.AppendLine($"物理穿透:{PhysicalPenetration * 100:0.##}%");
builder.AppendLine($"魔法穿透:{MagicalPenetration * 100:0.##}%");
+ if (showMapRelated)
+ {
+ builder.AppendLine($"移动距离:{MOV}");
+ builder.AppendLine($"攻击距离:{ATR}");
+ }
+
if (CharacterState != CharacterState.Actionable)
{
builder.AppendLine(CharacterSet.GetCharacterState(CharacterState));
@@ -174,7 +180,7 @@ namespace Milimoe.FunGame.Core.Entity
/// 获取单位的简略信息
///
///
- public new string GetSimpleInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false, bool showBasicOnly = false)
+ public new string GetSimpleInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false, bool showBasicOnly = false, bool showMapRelated = false)
{
StringBuilder builder = new();
@@ -198,6 +204,12 @@ namespace Milimoe.FunGame.Core.Entity
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.##}]" : ""));
+ if (showMapRelated)
+ {
+ builder.AppendLine($"移动距离:{MOV}");
+ builder.AppendLine($"攻击距离:{ATR}");
+ }
+
if (!showBasicOnly)
{
if (CharacterState != CharacterState.Actionable)
@@ -408,7 +420,7 @@ namespace Milimoe.FunGame.Core.Entity
/// 获取单位的物品信息
///
///
- public new string GetItemInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false)
+ public new string GetItemInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false, bool showMapRelated = false)
{
StringBuilder builder = new();
@@ -439,6 +451,12 @@ namespace Milimoe.FunGame.Core.Entity
builder.AppendLine($"物理穿透:{PhysicalPenetration * 100:0.##}%");
builder.AppendLine($"魔法穿透:{MagicalPenetration * 100:0.##}%");
+ if (showMapRelated)
+ {
+ builder.AppendLine($"移动距离:{MOV}");
+ builder.AppendLine($"攻击距离:{ATR}");
+ }
+
if (EquipSlot.Any())
{
builder.AppendLine("== 装备栏 ==");
diff --git a/Entity/Item/Item.cs b/Entity/Item/Item.cs
index 7d0ca7b..5d3cfbb 100644
--- a/Entity/Item/Item.cs
+++ b/Entity/Item/Item.cs
@@ -1,7 +1,9 @@
-using System.Text;
+using System.Collections.Generic;
+using System.Text;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Interface.Entity;
+using Milimoe.FunGame.Core.Library.Common.Addon;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Entity
@@ -310,7 +312,13 @@ namespace Milimoe.FunGame.Core.Entity
}
if (result && Skills.Active != null)
{
- used = await queue.UseItemAsync(this, character, enemys, teammates);
+ List castRange = [];
+ if (Skills.Active.GamingQueue != null && Skills.Active.GamingQueue.Map != null)
+ {
+ 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, enemys, teammates, castRange);
}
if (used)
{
diff --git a/Entity/Skill/Effect.cs b/Entity/Skill/Effect.cs
index 3946ffc..4528c10 100644
--- a/Entity/Skill/Effect.cs
+++ b/Entity/Skill/Effect.cs
@@ -1,4 +1,5 @@
-using System.Text;
+using System.Collections.Generic;
+using System.Text;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Interface.Entity;
@@ -560,7 +561,8 @@ namespace Milimoe.FunGame.Core.Entity
///
///
///
- public virtual void BeforeSelectTargetGrid(Character character, List enemys, List teammates, GameMap map)
+ ///
+ public virtual void BeforeSelectTargetGrid(Character character, List enemys, List teammates, GameMap map, List moveRange)
{
}
@@ -1067,6 +1069,19 @@ namespace Milimoe.FunGame.Core.Entity
return GamingQueue?.IsCharacterInAIControlling(character) ?? false;
}
+ ///
+ /// 添加角色应用的特效类型到回合记录中
+ ///
+ ///
+ ///
+ public void RecordCharacterApplyEffects(Character character, params List types)
+ {
+ if (GamingQueue?.LastRound.ApplyEffects.TryAdd(character, types) ?? false)
+ {
+ GamingQueue?.LastRound.ApplyEffects[character].AddRange(types);
+ }
+ }
+
///
/// 返回特效详情
///
diff --git a/Entity/Skill/NormalAttack.cs b/Entity/Skill/NormalAttack.cs
index 25f3af3..6623f85 100644
--- a/Entity/Skill/NormalAttack.cs
+++ b/Entity/Skill/NormalAttack.cs
@@ -233,6 +233,11 @@ namespace Milimoe.FunGame.Core.Entity
///
public double CurrentCD => 0;
+ ///
+ /// 游戏中的行动顺序表实例,使用时需要判断其是否存在
+ ///
+ public IGamingQueue? GamingQueue { get; set; } = null;
+
///
/// 绑定到特效的普通攻击扩展。键为特效,值为对应的普攻扩展对象。
///
@@ -450,6 +455,7 @@ namespace Milimoe.FunGame.Core.Entity
builder.AppendLine($"{Name} - 等级 {Level}");
builder.AppendLine($"描述:{Description}");
+ if (GamingQueue?.Map != null) builder.AppendLine($"攻击距离:{Character.ATR}");
builder.AppendLine($"硬直时间:{RealHardnessTime:0.##}{(showOriginal && RealHardnessTime != HardnessTime ? $"(原始值:{HardnessTime})" : "")}");
return builder.ToString();
diff --git a/Entity/Skill/OpenSkill.cs b/Entity/Skill/OpenSkill.cs
index 1273413..e6f39f3 100644
--- a/Entity/Skill/OpenSkill.cs
+++ b/Entity/Skill/OpenSkill.cs
@@ -44,6 +44,21 @@ namespace Milimoe.FunGame.Core.Entity
CanSelectTeammate = teammate;
}
break;
+ case "allenemy":
+ case "allenemys":
+ case "allenemies":
+ if (bool.TryParse(args[str].ToString(), out bool allenemy))
+ {
+ SelectAllEnemies = allenemy;
+ }
+ break;
+ case "allteammate":
+ case "allteammates":
+ if (bool.TryParse(args[str].ToString(), out bool allteammate))
+ {
+ SelectAllTeammates = allteammate;
+ }
+ break;
case "count":
if (int.TryParse(args[str].ToString(), out int count) && count > 0)
{
@@ -56,6 +71,19 @@ namespace Milimoe.FunGame.Core.Entity
CanSelectTargetRange = range;
}
break;
+ case "nd":
+ case "nondirectional":
+ if (bool.TryParse(args[str].ToString(), out bool nondirectional))
+ {
+ IsNonDirectional = nondirectional;
+ }
+ break;
+ case "rangetype":
+ if (int.TryParse(args[str].ToString(), out int rangetype) && rangetype > 0)
+ {
+ SkillRangeType = (SkillRangeType)rangetype;
+ }
+ break;
case "mpcost":
if (double.TryParse(args[str].ToString(), out double mpcost) && mpcost > 0)
{
@@ -69,12 +97,14 @@ namespace Milimoe.FunGame.Core.Entity
}
break;
case "costall":
+ case "costallep":
if (bool.TryParse(args[str].ToString(), out bool costall) && costall)
{
CostAllEP = costall;
}
break;
case "mincost":
+ case "mincostep":
if (double.TryParse(args[str].ToString(), out double mincost) && mincost > 0)
{
MinCostEP = mincost;
@@ -87,12 +117,28 @@ namespace Milimoe.FunGame.Core.Entity
}
break;
case "cast":
+ case "casttime":
if (double.TryParse(args[str].ToString(), out double cast) && cast > 0)
{
CastTime = cast;
}
break;
+ case "cr":
+ case "castrange":
+ if (int.TryParse(args[str].ToString(), out int castrange) && castrange > 0)
+ {
+ CastRange = castrange;
+ }
+ break;
+ case "caw":
+ case "castanywhere":
+ if (bool.TryParse(args[str].ToString(), out bool castanywhere))
+ {
+ CastAnywhere = castanywhere;
+ }
+ break;
case "ht":
+ case "hardnesstime":
if (double.TryParse(args[str].ToString(), out double ht) && ht > 0)
{
HardnessTime = ht;
diff --git a/Entity/Skill/Skill.cs b/Entity/Skill/Skill.cs
index 65c315c..808bb39 100644
--- a/Entity/Skill/Skill.cs
+++ b/Entity/Skill/Skill.cs
@@ -99,7 +99,11 @@ namespace Milimoe.FunGame.Core.Entity
/// 施法距离 [ 单位:格 ]
///
[InitOptional]
- public int CastRange { get; set; } = 5;
+ public int CastRange
+ {
+ get => Math.Max(1, CastAnywhere ? (GamingQueue?.Map != null ? GamingQueue.Map.Grids.Count : 999) : _CastRange);
+ set => _CastRange = Math.Max(1, value);
+ }
///
/// 可选取自身
@@ -136,6 +140,24 @@ namespace Milimoe.FunGame.Core.Entity
///
public virtual int CanSelectTargetRange { get; set; } = 0;
+ ///
+ /// 如果为 true,表示非指向性技能,可以任意选取一个范围( = 0 时为单个格子)。
+ /// 如果为 false,表示必须选取一个角色作为目标,当 > 0 时,技能作用范围将根据目标位置覆盖 形状的区域;= 0 时正常选取目标。
+ ///
+ public virtual bool IsNonDirectional { get; set; } = false;
+
+ ///
+ /// 作用范围形状
+ /// - 菱形。默认的曼哈顿距离正方形
+ /// - 圆形。基于欧几里得距离的圆形
+ /// - 正方形
+ /// - 施法者与目标之前的直线
+ /// - 施法者与目标所在的直线,贯穿至地图边缘
+ /// - 扇形
+ /// 注意,该属性不影响选取目标的范围。选取目标的范围由 决定。
+ ///
+ public virtual SkillRangeType SkillRangeType { get; set; } = SkillRangeType.Diamond;
+
///
/// 选取角色的条件
///
@@ -534,6 +556,10 @@ namespace Milimoe.FunGame.Core.Entity
{
builder.AppendLine($"{DispelDescription}");
}
+ if (GamingQueue?.Map != null && SkillType != SkillType.Passive)
+ {
+ builder.AppendLine($"施法距离:{(CastAnywhere ? "全图" : CastRange)}");
+ }
if (IsActive && (Item?.IsInGameItem ?? true))
{
if (SkillType == SkillType.Item)
@@ -661,5 +687,10 @@ namespace Milimoe.FunGame.Core.Entity
/// 等级
///
private int _Level = 0;
+
+ ///
+ /// 施法距离
+ ///
+ private int _CastRange = 3;
}
}
diff --git a/Entity/System/Team.cs b/Entity/System/Team.cs
index 0fb82a0..af41865 100644
--- a/Entity/System/Team.cs
+++ b/Entity/System/Team.cs
@@ -1,19 +1,17 @@
-using Milimoe.FunGame.Core.Interface.Base;
-
-namespace Milimoe.FunGame.Core.Entity
+namespace Milimoe.FunGame.Core.Entity
{
public class Team(string name, IEnumerable charaters)
{
public Guid Id { get; set; } = Guid.Empty;
public string Name { get; set; } = name;
- public List Members { get; } = new(charaters);
+ public List Members { get; } = [.. charaters];
public int Score { get; set; } = 0;
public bool IsWinner { get; set; } = false;
public int Count => Members.Count;
- public List GetActiveCharacters(IGamingQueue queue)
+ public List GetActiveCharacters()
{
- return [.. Members.Where(queue.Queue.Contains)];
+ return [.. Members.Where(c => c.HP > 0)];
}
public List GetTeammates(Character character)
@@ -21,9 +19,9 @@ namespace Milimoe.FunGame.Core.Entity
return [.. Members.Where(c => c != character)];
}
- public List GetActiveTeammates(IGamingQueue queue, Character character)
+ public List GetActiveTeammates(Character character)
{
- return [.. Members.Where(c => queue.Queue.Contains(c) && c != character)];
+ return [.. Members.Where(c => c.HP > 0 && c != character)];
}
public bool IsOnThisTeam(Character character)
diff --git a/Interface/Base/IGamingQueue.cs b/Interface/Base/IGamingQueue.cs
index 695bf54..515f08c 100644
--- a/Interface/Base/IGamingQueue.cs
+++ b/Interface/Base/IGamingQueue.cs
@@ -25,6 +25,11 @@ namespace Milimoe.FunGame.Core.Interface.Base
///
public Dictionary Original { get; }
+ ///
+ /// 参与本次游戏的所有角色列表
+ ///
+ public List AllCharacters { get; }
+
///
/// 当前的行动顺序
///
@@ -151,8 +156,19 @@ namespace Milimoe.FunGame.Core.Interface.Base
///
///
///
+ ///
+ ///
///
- public Task UseItemAsync(Item item, Character caster, List enemys, List teammates);
+ public Task UseItemAsync(Item item, Character caster, List enemys, List teammates, List castRange, List? desiredTargets = null);
+
+ ///
+ /// 角色移动
+ ///
+ ///
+ ///
+ ///
+ ///
+ public Task CharacterMoveAsync(Character character, Grid target, Grid? startGrid);
///
/// 选取移动目标
@@ -161,8 +177,9 @@ namespace Milimoe.FunGame.Core.Interface.Base
///
///
///
+ ///
///
- public Task SelectTargetGridAsync(Character character, List enemys, List teammates, GameMap map);
+ public Task SelectTargetGridAsync(Character character, List enemys, List teammates, GameMap map, List moveRange);
///
/// 选取技能目标
@@ -171,8 +188,9 @@ namespace Milimoe.FunGame.Core.Interface.Base
///
///
///
+ ///
///
- public Task
> SelectTargetsAsync(Character caster, Skill skill, List enemys, List teammates);
+ public Task> SelectTargetsAsync(Character caster, Skill skill, List enemys, List teammates, List castRange);
///
/// 选取普通攻击目标
@@ -181,8 +199,9 @@ namespace Milimoe.FunGame.Core.Interface.Base
///
///
///
+ ///
///
- public Task> SelectTargetsAsync(Character character, NormalAttack attack, List enemys, List teammates);
+ public Task> SelectTargetsAsync(Character character, NormalAttack attack, List enemys, List teammates, List attackRange);
///
/// 判断目标对于某个角色是否是队友
diff --git a/Interface/Entity/Typical/ISkill.cs b/Interface/Entity/Typical/ISkill.cs
index 8d1ce21..cfea5ff 100644
--- a/Interface/Entity/Typical/ISkill.cs
+++ b/Interface/Entity/Typical/ISkill.cs
@@ -1,4 +1,5 @@
using Milimoe.FunGame.Core.Entity;
+using Milimoe.FunGame.Core.Interface.Base;
namespace Milimoe.FunGame.Core.Interface.Entity
{
@@ -7,6 +8,11 @@ namespace Milimoe.FunGame.Core.Interface.Entity
///
public interface ISkill : IBaseEntity
{
+ ///
+ /// 所属的行动顺序表实例
+ ///
+ public IGamingQueue? GamingQueue { get; }
+
///
/// 此技能所属的角色
///
diff --git a/Library/Common/Addon/Example/ExampleGameModule.cs b/Library/Common/Addon/Example/ExampleGameModule.cs
index 2ad54bd..5c959c4 100644
--- a/Library/Common/Addon/Example/ExampleGameModule.cs
+++ b/Library/Common/Addon/Example/ExampleGameModule.cs
@@ -344,7 +344,7 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
return map;
}
- private async Task Gq_SelectTargetGrid(GamingQueue queue, Character character, List enemys, List teammates, GameMap map)
+ private async Task Gq_SelectTargetGrid(GamingQueue queue, Character character, List enemys, List teammates, GameMap map, List canMoveGrids)
{
// 介入选择,假设这里更新界面,让玩家选择目的地
await Task.CompletedTask;
diff --git a/Library/Common/Addon/GameMap.cs b/Library/Common/Addon/GameMap.cs
index e1150e1..b054f40 100644
--- a/Library/Common/Addon/GameMap.cs
+++ b/Library/Common/Addon/GameMap.cs
@@ -240,6 +240,50 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
return grids;
}
+ ///
+ /// 获取以某个格子为中心,最远距离的格子(曼哈顿距离),只考虑同一平面的格子。
+ ///
+ ///
+ ///
+ ///
+ ///
+ public virtual List GetOuterGridsByRange(Grid grid, int range, bool includeCharacter = false)
+ {
+ List grids = [];
+
+ if (range < 0)
+ {
+ return grids;
+ }
+
+ // 遍历以中心格子为中心的方形区域
+ // dx和dy的范围从 -range 到 +range
+ for (int dx = -range; dx <= range; ++dx)
+ {
+ for (int dy = -range; dy <= range; ++dy)
+ {
+ // 只有当曼哈顿距离恰好等于 range 时,才认为是最远距离的格子
+ if (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;
+ }
+
///
/// 获取以某个格子为中心,一定半径内的格子(圆形范围,欧几里得距离),只考虑同一平面的格子。
///
@@ -254,7 +298,7 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
// 预计算半径的平方
int rangeSquared = range * range;
- // 遍历以中心格子为中心的方形区域
+ // 遍历以中心格子为中心的区域
// 范围从 -range 到 +range,覆盖所有可能的圆形区域内的格子
for (int dx = -range; dx <= range; ++dx)
{
@@ -281,6 +325,47 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
return grids;
}
+ ///
+ /// 获取以某个格子为中心,最远距离的格子(圆形范围,欧几里得距离),只考虑同一平面的格子。
+ ///
+ ///
+ ///
+ ///
+ ///
+ public virtual List GetOuterGridsByCircleRange(Grid grid, int range, bool includeCharacter = false)
+ {
+ List grids = [];
+
+ // 预计算半径的平方
+ int rangeSquared = range * range;
+
+ // 遍历以中心格子为中心的区域
+ // 范围从 -range 到 +range,覆盖所有可能的圆形区域内的格子
+ for (int dx = -range; dx <= range; ++dx)
+ {
+ for (int dy = -range; dy <= range; ++dy)
+ {
+ // 计算当前格子与中心格子的欧几里得距离的平方
+ if ((dx * dx) + (dy * dy) == rangeSquared)
+ {
+ 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;
+ }
+
///
/// 设置角色移动
///
@@ -296,9 +381,15 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
}
Grid? realGrid = GetCharacterCurrentGrid(character);
-
- if (current.Id == target.Id)
+ Grid startGrid = current;
+ if (realGrid != null && current.Id != realGrid.Id)
{
+ startGrid = realGrid;
+ }
+
+ if (startGrid.Id == target.Id)
+ {
+ SetCharacterCurrentGrid(character, startGrid);
return 0;
}
@@ -308,8 +399,8 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
HashSet visited = [];
// 将起始格子加入队列,步数为0,并标记为已访问
- queue.Enqueue((current, 0));
- visited.Add(current.Id);
+ queue.Enqueue((startGrid, 0));
+ visited.Add(startGrid.Id);
while (queue.Count > 0)
{
@@ -319,9 +410,7 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
if (currentGrid.Id == target.Id)
{
realGrid?.Characters.Remove(character);
- current.Characters.Remove(character);
- target.Characters.Add(character);
- Characters[character] = target;
+ SetCharacterCurrentGrid(character, target);
return currentSteps;
}
@@ -369,10 +458,15 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
}
Grid? realGrid = GetCharacterCurrentGrid(character);
-
- if (current.Id == target.Id)
+ Grid startGrid = current;
+ if (realGrid != null && current.Id != realGrid.Id)
{
- SetCharacterCurrentGrid(character, current);
+ startGrid = realGrid;
+ }
+
+ if (startGrid.Id == target.Id)
+ {
+ SetCharacterCurrentGrid(character, startGrid);
return 0;
}
@@ -383,11 +477,11 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
HashSet visited = [];
// 初始化 BFS 队列,将起始格子加入,步数为0
- queue.Enqueue((current, 0));
- visited.Add(current.Id);
+ queue.Enqueue((startGrid, 0));
+ visited.Add(startGrid.Id);
Grid? bestReachableGrid = current;
- int minDistanceToTarget = CalculateManhattanDistance(current, target);
+ int minDistanceToTarget = CalculateManhattanDistance(startGrid, target);
int stepsToBestReachable = 0;
// 定义平面移动的四个方向
diff --git a/Library/Common/JsonConverter/CharacterConverter.cs b/Library/Common/JsonConverter/CharacterConverter.cs
index abca358..ff6beff 100644
--- a/Library/Common/JsonConverter/CharacterConverter.cs
+++ b/Library/Common/JsonConverter/CharacterConverter.cs
@@ -83,6 +83,9 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
case nameof(Character.ExHPPercentage):
result.ExHPPercentage = reader.GetDouble();
break;
+ case nameof(Character.HasMP):
+ result.HasMP = reader.GetBoolean();
+ break;
case nameof(Character.InitialMP):
result.InitialMP = reader.GetDouble();
break;
@@ -197,11 +200,11 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
case nameof(Character.ExCDR):
result.ExCDR = reader.GetDouble();
break;
- case nameof(Character.ATR):
- result.ATR = reader.GetInt32();
+ case nameof(Character.ExATR):
+ result.ExATR = reader.GetInt32();
break;
- case nameof(Character.MOV):
- result.MOV = reader.GetInt32();
+ case nameof(Character.ExMOV):
+ result.ExMOV = reader.GetInt32();
break;
case nameof(Character.ExCritRate):
result.ExCritRate = reader.GetDouble();
@@ -273,6 +276,7 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
writer.WriteNumber(nameof(Character.InitialHP), value.InitialHP);
writer.WriteNumber(nameof(Character.ExHP2), value.ExHP2);
writer.WriteNumber(nameof(Character.ExHPPercentage), value.ExHPPercentage);
+ writer.WriteBoolean(nameof(Character.HasMP), value.HasMP);
writer.WriteNumber(nameof(Character.InitialMP), value.InitialMP);
writer.WriteNumber(nameof(Character.ExMP2), value.ExMP2);
writer.WriteNumber(nameof(Character.ExMPPercentage), value.ExMPPercentage);
@@ -311,8 +315,8 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
writer.WriteNumber(nameof(Character.ExActionCoefficient), value.ExActionCoefficient);
writer.WriteNumber(nameof(Character.ExAccelerationCoefficient), value.ExAccelerationCoefficient);
writer.WriteNumber(nameof(Character.ExCDR), value.ExCDR);
- writer.WriteNumber(nameof(Character.ATR), value.ATR);
- writer.WriteNumber(nameof(Character.MOV), value.MOV);
+ writer.WriteNumber(nameof(Character.ExATR), value.ExATR);
+ writer.WriteNumber(nameof(Character.ExMOV), value.ExMOV);
writer.WriteNumber(nameof(Character.ExCritRate), value.ExCritRate);
writer.WriteNumber(nameof(Character.ExCritDMG), value.ExCritDMG);
writer.WriteNumber(nameof(Character.ExEvadeRate), value.ExEvadeRate);
diff --git a/Library/Constant/TypeEnum.cs b/Library/Constant/TypeEnum.cs
index 8db44a1..b1fb6f3 100644
--- a/Library/Constant/TypeEnum.cs
+++ b/Library/Constant/TypeEnum.cs
@@ -1063,4 +1063,14 @@ namespace Milimoe.FunGame.Core.Library.Constant
Magical,
True
}
+
+ public enum SkillRangeType
+ {
+ Diamond,
+ Circle,
+ Square,
+ Line,
+ LinePass,
+ Sector
+ }
}
diff --git a/Model/AIDecision.cs b/Model/AIDecision.cs
new file mode 100644
index 0000000..1a188b2
--- /dev/null
+++ b/Model/AIDecision.cs
@@ -0,0 +1,18 @@
+using Milimoe.FunGame.Core.Entity;
+using Milimoe.FunGame.Core.Interface.Entity;
+using Milimoe.FunGame.Core.Library.Common.Addon;
+using Milimoe.FunGame.Core.Library.Constant;
+
+namespace Milimoe.FunGame.Core.Model
+{
+ public class AIDecision
+ {
+ public CharacterActionType ActionType { get; set; } = CharacterActionType.EndTurn;
+ public Grid? TargetMoveGrid { get; set; } = null;
+ public ISkill? SkillToUse { get; set; } = null;
+ public Item? ItemToUse { get; set; } = null;
+ public List Targets { get; set; } = [];
+ public double Score { get; set; } = 0;
+ public bool IsPureMove { get; set; } = false;
+ }
+}
diff --git a/Model/EquilibriumConstant.cs b/Model/EquilibriumConstant.cs
index 04c550e..e58db8b 100644
--- a/Model/EquilibriumConstant.cs
+++ b/Model/EquilibriumConstant.cs
@@ -460,6 +460,86 @@ namespace Milimoe.FunGame.Core.Model
///
public double HiddenWeaponHardness { get; set; } = 7;
+ ///
+ /// 单手剑的攻击距离
+ ///
+ public int OneHandedSwordAttackRange { get; set; } = 1;
+
+ ///
+ /// 双手剑的攻击距离
+ ///
+ public int TwoHandedSwordAttackRange { get; set; } = 2;
+
+ ///
+ /// 弓的攻击距离
+ ///
+ public int BowAttackRange { get; set; } = 4;
+
+ ///
+ /// 手枪的攻击距离
+ ///
+ public int PistolAttackRange { get; set; } = 3;
+
+ ///
+ /// 步枪的攻击距离
+ ///
+ public int RifleAttackRange { get; set; } = 5;
+
+ ///
+ /// 双持短刀的攻击距离
+ ///
+ public int DualDaggersAttackRange { get; set; } = 1;
+
+ ///
+ /// 法器的攻击距离
+ ///
+ public int TalismanAttackRange { get; set; } = 5;
+
+ ///
+ /// 法杖的攻击距离
+ ///
+ public int StaffAttackRange { get; set; } = 3;
+
+ ///
+ /// 长柄武器的攻击距离
+ ///
+ public int PolearmAttackRange { get; set; } = 2;
+
+ ///
+ /// 拳套的攻击距离
+ ///
+ public int GauntletAttackRange { get; set; } = 1;
+
+ ///
+ /// 暗器的攻击距离
+ ///
+ public int HiddenWeaponAttackRange { get; set; } = 4;
+
+ ///
+ /// 核心角色的移动距离
+ ///
+ public int RoleMOV_Core { get; set; } = 3;
+
+ ///
+ /// 先锋角色的移动距离
+ ///
+ public int RoleMOV_Vanguard { get; set; } = 6;
+
+ ///
+ /// 近卫角色的移动距离
+ ///
+ public int RoleMOV_Guardian { get; set; } = 5;
+
+ ///
+ /// 支援角色的移动距离
+ ///
+ public int RoleMOV_Support { get; set; } = 4;
+
+ ///
+ /// 治疗角色的移动距离
+ ///
+ public int RoleMOV_Medic { get; set; } = 3;
+
///
/// 应用此游戏平衡常数给实体
///
diff --git a/Model/GamingQueue.cs b/Model/GamingQueue.cs
index d8df0f2..1747375 100644
--- a/Model/GamingQueue.cs
+++ b/Model/GamingQueue.cs
@@ -1,4 +1,5 @@
using Milimoe.FunGame.Core.Api.Utility;
+using Milimoe.FunGame.Core.Controller;
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Interface.Entity;
@@ -753,6 +754,8 @@ namespace Milimoe.FunGame.Core.Model
}
}
}
+
+ _eliminated.Remove(character);
}
// 减少复活倒计时
@@ -812,11 +815,11 @@ namespace Milimoe.FunGame.Core.Model
// 队友列表
List allTeammates = GetTeammates(character);
- List teammates = [.. allTeammates.Where(_queue.Contains)];
+ List selecableTeammates = [.. allTeammates.Where(_queue.Contains)];
// 敌人列表
- List allEnemys = [.. _allCharacters.Where(c => c != character && !teammates.Contains(c))];
- List enemys = [.. allEnemys.Where(c => _queue.Contains(c) && !c.IsUnselectable)];
+ List allEnemys = [.. _allCharacters.Where(c => c != character && !allTeammates.Contains(c))];
+ List selectableEnemys = [.. allEnemys.Where(c => _queue.Contains(c) && !c.IsUnselectable)];
// 技能列表
List skills = [.. character.Skills.Where(s => s.Level > 0 && s.SkillType != SkillType.Passive && s.Enable && !s.IsInEffect && s.CurrentCD == 0 &&
@@ -828,7 +831,7 @@ namespace Milimoe.FunGame.Core.Model
// 回合开始事件,允许事件返回 false 接管回合操作
// 如果事件全程接管回合操作,需要注意触发特效
- if (!await OnTurnStartAsync(character, enemys, teammates, skills, items))
+ if (!await OnTurnStartAsync(character, selectableEnemys, selecableTeammates, skills, items))
{
_isInRound = false;
return _isGameEnd;
@@ -836,13 +839,13 @@ namespace Milimoe.FunGame.Core.Model
foreach (Skill skillTurnStart in skills)
{
- skillTurnStart.OnTurnStart(character, enemys, teammates, skills, items);
+ skillTurnStart.OnTurnStart(character, selectableEnemys, selecableTeammates, skills, items);
}
List effects = [.. character.Effects.Where(e => e.IsInEffect)];
foreach (Effect effect in effects)
{
- effect.OnTurnStart(character, enemys, teammates, skills, items);
+ effect.OnTurnStart(character, selectableEnemys, selecableTeammates, skills, items);
}
// 此变量用于在取消选择时,能够重新行动
@@ -852,24 +855,33 @@ namespace Milimoe.FunGame.Core.Model
// 此变量控制角色移动后可以继续选择其他的行动
bool moved = false;
- Grid? currentGrid = null;
+ // AI 决策控制器,适用于启用战棋地图的情况
+ AIController? ai = null;
+
+ // 角色的起始地点,确保角色该回合移动的范围不超过 MOV
+ Grid? startGrid = null;
+ List canMoveGrids = [];
+ // 并且要筛选最远可选取角色
+ List canAttackGridsByStartGrid = [];
+ List canCastGridsByStartGrid = [];
if (_map != null)
{
- currentGrid = _map.GetCharacterCurrentGrid(character);
- }
+ startGrid = _map.GetCharacterCurrentGrid(character);
- // 行动开始前,可以修改可选取的角色列表
- Dictionary continuousKillingTemp = new(_continuousKilling);
- Dictionary earnedMoneyTemp = new(_earnedMoney);
- effects = [.. character.Effects.Where(e => e.IsInEffect)];
- foreach (Effect effect in effects)
- {
- effect.AlterSelectListBeforeAction(character, enemys, teammates, skills, continuousKillingTemp, earnedMoneyTemp);
- }
+ if (startGrid != null)
+ {
+ canMoveGrids = _map.GetGridsByRange(startGrid, character.MOV, false);
+ canAttackGridsByStartGrid = _map.GetGridsByRange(startGrid, character.ATR, true);
+ Skill[] canCastSkills = [.. skills, .. items.Select(i => i.Skills.Active!)];
+ foreach (Skill skill in canCastSkills)
+ {
+ canCastGridsByStartGrid.AddRange(_map.GetGridsByRange(startGrid, skill.CastRange, true));
+ }
+ }
- // 这里筛掉重复角色
- enemys = [.. enemys.Distinct()];
- teammates = [.. teammates.Distinct()];
+ allEnemys = [.. allEnemys.Where(canAttackGridsByStartGrid.Union(canCastGridsByStartGrid).SelectMany(g => g.Characters).Contains)];
+ allTeammates = [.. allTeammates.Where(canAttackGridsByStartGrid.Union(canCastGridsByStartGrid).SelectMany(g => g.Characters).Contains)];
+ }
// 作出了什么行动
CharacterActionType type = CharacterActionType.None;
@@ -880,10 +892,62 @@ namespace Milimoe.FunGame.Core.Model
bool isAI = CharactersInAI.Contains(character);
while (!decided && (!isAI || cancelTimes > 0))
{
+ // 根据当前位置,更新可选取角色列表
+ Grid? realGrid = null;
+ List canAttackGrids = [];
+ List canCastGrids = [];
+ List willMoveGridWithSkill = [];
+ List enemys = [];
+ List teammates = [];
+ if (_map != null)
+ {
+ if (isAI)
+ {
+ ai ??= new(this, _map);
+ }
+
+ realGrid = _map.GetCharacterCurrentGrid(character);
+
+ if (realGrid != null)
+ {
+ canAttackGrids = _map.GetGridsByRange(realGrid, character.ATR, true);
+ Skill[] canCastSkills = [.. skills, .. items.Select(i => i.Skills.Active!)];
+ foreach (Skill skill in canCastSkills)
+ {
+ canCastGrids.AddRange(_map.GetGridsByRange(realGrid, skill.CastRange, true));
+ }
+ }
+
+ enemys = [.. selectableEnemys.Where(canAttackGrids.Union(canCastGrids).SelectMany(g => g.Characters).Contains)];
+ teammates = [.. selecableTeammates.Where(canAttackGrids.Union(canCastGrids).SelectMany(g => g.Characters).Contains)];
+ willMoveGridWithSkill = [.. canMoveGrids.Where(g => canAttackGrids.Union(canCastGrids).Contains(g))];
+ }
+ else
+ {
+ enemys = selectableEnemys;
+ teammates = selecableTeammates;
+ }
+
+ // AI 决策结果(适用于启用战棋地图的情况)
+ AIDecision? aiDecision = null;
+
+ // 行动开始前,可以修改可选取的角色列表
+ Dictionary continuousKillingTemp = new(_continuousKilling);
+ Dictionary earnedMoneyTemp = new(_earnedMoney);
+ effects = [.. character.Effects.Where(e => e.IsInEffect)];
+ foreach (Effect effect in effects)
+ {
+ effect.AlterSelectListBeforeAction(character, enemys, teammates, skills, continuousKillingTemp, earnedMoneyTemp);
+ }
+
+ // 这里筛掉重复角色
+ enemys = [.. enemys.Distinct()];
+ teammates = [.. teammates.Distinct()];
+
if (moved) moved = false;
else cancelTimes--;
type = CharacterActionType.None;
-
+
// 是否能使用物品和释放技能
bool canUseItem = items.Count > 0;
bool canCastSkill = skills.Count > 0;
@@ -996,8 +1060,17 @@ namespace Milimoe.FunGame.Core.Model
}
}
- // 模组可以通过此事件来决定角色的行动
- type = await OnDecideActionAsync(character, enemys, teammates, skills, items);
+ // 启用战棋地图时的专属 AI 决策方法
+ if (isAI && ai != null && startGrid != null)
+ {
+ aiDecision = await ai.DecideAIActionAsync(character, startGrid, canMoveGrids, skills, items, allEnemys, allTeammates);
+ type = aiDecision.ActionType;
+ }
+ else
+ {
+ // 模组可以通过此事件来决定角色的行动
+ type = await OnDecideActionAsync(character, enemys, teammates, skills, items);
+ }
// 若事件未完成决策,则将通过概率对角色进行自动化决策
if (type == CharacterActionType.None)
{
@@ -1021,7 +1094,34 @@ namespace Milimoe.FunGame.Core.Model
}
}
- if (type == CharacterActionType.NormalAttack)
+ if (aiDecision != null && aiDecision.ActionType != CharacterActionType.Move && aiDecision.TargetMoveGrid != null)
+ {
+ // 不是纯粹移动的情况,需要手动移动
+ moved = await CharacterMoveAsync(character, aiDecision.TargetMoveGrid, startGrid);
+ }
+
+ if (type == CharacterActionType.Move)
+ {
+ if (_map != null)
+ {
+ Grid target;
+ if (aiDecision != null && aiDecision.TargetMoveGrid != null)
+ {
+ target = aiDecision.TargetMoveGrid;
+ }
+ else
+ {
+ target = await SelectTargetGridAsync(character, enemys, teammates, _map, canMoveGrids);
+ }
+ moved = await CharacterMoveAsync(character, target, startGrid);
+ }
+ if (isAI && aiDecision != null && cancelTimes == 0)
+ {
+ // 取消 AI 的移动
+ type = CharacterActionType.EndTurn;
+ }
+ }
+ else if (type == CharacterActionType.NormalAttack)
{
if (!forceAction && (character.CharacterState == CharacterState.NotActionable ||
character.CharacterState == CharacterState.ActionRestricted ||
@@ -1033,7 +1133,22 @@ namespace Milimoe.FunGame.Core.Model
else
{
// 使用普通攻击逻辑
- List targets = await SelectTargetsAsync(character, character.NormalAttack, enemys, teammates);
+ List targets;
+ if (aiDecision != null)
+ {
+ targets = aiDecision.Targets;
+ }
+ else
+ {
+ List attackRange = [];
+ if (_map != null && realGrid != null)
+ {
+ attackRange = _map.GetGridsByRange(realGrid, character.ATR, true);
+ enemys = [.. enemys.Where(attackRange.SelectMany(g => g.Characters).Contains)];
+ teammates = [.. teammates.Where(attackRange.SelectMany(g => g.Characters).Contains)];
+ }
+ targets = await SelectTargetsAsync(character, character.NormalAttack, enemys, teammates, attackRange);
+ }
if (targets.Count > 0)
{
LastRound.Targets = [.. targets];
@@ -1063,7 +1178,15 @@ namespace Milimoe.FunGame.Core.Model
else
{
// 预使用技能,即开始吟唱逻辑
- Skill? skill = await OnSelectSkillAsync(character, skills);
+ Skill? skill;
+ if (aiDecision != null && aiDecision.SkillToUse is Skill s)
+ {
+ skill = s;
+ }
+ else
+ {
+ skill = await OnSelectSkillAsync(character, skills);
+ }
if (skill is null && CharactersInAI.Contains(character) && skills.Count > 0)
{
skill = skills[Random.Shared.Next(skills.Count)];
@@ -1073,7 +1196,22 @@ namespace Milimoe.FunGame.Core.Model
// 吟唱前需要先选取目标
if (skill.SkillType == SkillType.Magic)
{
- List targets = await SelectTargetsAsync(character, skill, enemys, teammates);
+ List targets;
+ if (aiDecision != null)
+ {
+ targets = aiDecision.Targets;
+ }
+ else
+ {
+ List castRange = [];
+ if (_map != null && realGrid != null)
+ {
+ castRange = _map.GetGridsByRange(realGrid, skill.CastRange, true);
+ enemys = [.. enemys.Where(castRange.SelectMany(g => g.Characters).Contains)];
+ teammates = [.. teammates.Where(castRange.SelectMany(g => g.Characters).Contains)];
+ }
+ targets = await SelectTargetsAsync(character, skill, enemys, teammates, castRange);
+ }
if (targets.Count > 0)
{
// 免疫检定
@@ -1100,7 +1238,22 @@ namespace Milimoe.FunGame.Core.Model
// 只有魔法需要吟唱,战技和爆发技直接释放
if (CheckCanCast(character, skill, out double cost))
{
- List targets = await SelectTargetsAsync(character, skill, enemys, teammates);
+ List targets;
+ if (aiDecision != null)
+ {
+ targets = aiDecision.Targets;
+ }
+ else
+ {
+ List castRange = [];
+ if (_map != null && realGrid != null)
+ {
+ castRange = _map.GetGridsByRange(realGrid, skill.CastRange, true);
+ enemys = [.. enemys.Where(castRange.SelectMany(g => g.Characters).Contains)];
+ teammates = [.. teammates.Where(castRange.SelectMany(g => g.Characters).Contains)];
+ }
+ targets = await SelectTargetsAsync(character, skill, enemys, teammates, castRange);
+ }
if (targets.Count > 0)
{
// 免疫检定
@@ -1211,7 +1364,8 @@ namespace Milimoe.FunGame.Core.Model
if (CheckCanCast(character, skill, out double cost))
{
// 预释放的爆发技不可取消
- List targets = await SelectTargetsAsync(character, skill, enemys, teammates);
+ List castRange = _map != null && realGrid != null ? _map.GetGridsByRange(realGrid, skill.CastRange, true) : [];
+ List targets = await SelectTargetsAsync(character, skill, enemys, teammates, castRange);
// 免疫检定
await CheckSkilledImmuneAsync(character, targets, skill);
LastRound.Targets = [.. targets];
@@ -1246,7 +1400,15 @@ namespace Milimoe.FunGame.Core.Model
else if (type == CharacterActionType.UseItem)
{
// 使用物品逻辑
- Item? item = await OnSelectItemAsync(character, items);
+ Item? item;
+ if (aiDecision != null && aiDecision.ItemToUse != null)
+ {
+ item = aiDecision.ItemToUse;
+ }
+ else
+ {
+ item = await OnSelectItemAsync(character, items);
+ }
if (item is null && CharactersInAI.Contains(character) && items.Count > 0)
{
// AI 控制下随机选取一个物品
@@ -1255,7 +1417,14 @@ namespace Milimoe.FunGame.Core.Model
if (item != null && item.Skills.Active != null)
{
Skill skill = item.Skills.Active;
- if (await UseItemAsync(item, character, enemys, teammates))
+ List castRange = [];
+ if (_map != null && realGrid != null)
+ {
+ castRange = _map.GetGridsByRange(realGrid, skill.CastRange, true);
+ enemys = [.. enemys.Where(castRange.SelectMany(g => g.Characters).Contains)];
+ teammates = [.. teammates.Where(castRange.SelectMany(g => g.Characters).Contains)];
+ }
+ if (await UseItemAsync(item, character, enemys, teammates, castRange, aiDecision?.Targets))
{
decided = true;
LastRound.Item = item;
@@ -1282,20 +1451,6 @@ namespace Milimoe.FunGame.Core.Model
WriteLine($"[ {character} ] 结束了回合!");
await OnCharacterDoNothingAsync(character);
}
- else if (type == CharacterActionType.Move)
- {
- if (_map != null)
- {
- Grid target = await SelectTargetGridAsync(character, enemys, teammates, _map);
- if (target.Id != -1)
- {
- int steps = _map.CharacterMove(character, currentGrid, target);
- moved = true;
- WriteLine($"[ {character} ] 移动了 {steps} 步!");
- await OnCharacterMoveAsync(character, target);
- }
- }
- }
else
{
if (baseTime == 0) baseTime += 8;
@@ -2213,8 +2368,10 @@ namespace Milimoe.FunGame.Core.Model
///
///
///
+ ///
+ ///
///
- public async Task UseItemAsync(Item item, Character character, List enemys, List teammates)
+ public async Task UseItemAsync(Item item, Character character, List enemys, List teammates, List castRange, List? desiredTargets = null)
{
if (CheckCanCast(character, item, out double costMP, out double costEP))
{
@@ -2222,7 +2379,15 @@ namespace Milimoe.FunGame.Core.Model
if (skill != null)
{
skill.GamingQueue = this;
- List targets = await SelectTargetsAsync(character, skill, enemys, teammates);
+ List targets;
+ if (desiredTargets != null)
+ {
+ targets = desiredTargets;
+ }
+ else
+ {
+ targets = await SelectTargetsAsync(character, skill, enemys, teammates, castRange);
+ }
if (targets.Count > 0)
{
// 免疫检定
@@ -2277,6 +2442,28 @@ namespace Milimoe.FunGame.Core.Model
return false;
}
+ ///
+ /// 角色移动实际逻辑
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task CharacterMoveAsync(Character character, Grid target, Grid? startGrid)
+ {
+ if (target.Id != -1)
+ {
+ int steps = _map?.CharacterMove(character, startGrid, target) ?? -1;
+ if (steps > 0)
+ {
+ WriteLine($"[ {character} ] 移动了 {steps} 步!");
+ await OnCharacterMoveAsync(character, target);
+ return true;
+ }
+ }
+ return false;
+ }
+
///
/// 通过概率计算角色要干嘛
///
@@ -2329,25 +2516,25 @@ namespace Milimoe.FunGame.Core.Model
///
///
///
+ ///
///
- public async Task SelectTargetGridAsync(Character character, List enemys, List teammates, GameMap map)
+ public async Task SelectTargetGridAsync(Character character, List enemys, List teammates, GameMap map, List moveRange)
{
List effects = [.. character.Effects.Where(e => e.IsInEffect)];
foreach (Effect effect in effects)
{
- effect.BeforeSelectTargetGrid(character, enemys, teammates, map);
+ effect.BeforeSelectTargetGrid(character, enemys, teammates, map, moveRange);
}
- Grid target = await OnSelectTargetGridAsync(character, enemys, teammates, map);
+ Grid target = await OnSelectTargetGridAsync(character, enemys, teammates, map, moveRange);
if (target.Id != -1)
{
return target;
}
else if (target.Id == -2 && map.Characters.TryGetValue(character, out Grid? current) && current != null)
{
- List grids = map.GetGridsByRange(current, character.MOV);
- if (grids.Count > 0)
+ if (moveRange.Count > 0)
{
- return grids[Random.Shared.Next(grids.Count)];
+ return moveRange[Random.Shared.Next(moveRange.Count)];
}
}
return Grid.Empty;
@@ -2360,15 +2547,16 @@ namespace Milimoe.FunGame.Core.Model
///
///
///
+ ///
///
- public async Task> SelectTargetsAsync(Character caster, Skill skill, List enemys, List teammates)
+ public async Task> SelectTargetsAsync(Character caster, Skill skill, List enemys, List teammates, List castRange)
{
List effects = [.. caster.Effects.Where(e => e.IsInEffect)];
foreach (Effect effect in effects)
{
effect.AlterSelectListBeforeSelection(caster, skill, enemys, teammates);
}
- List targets = await OnSelectSkillTargetsAsync(caster, skill, enemys, teammates);
+ List targets = await OnSelectSkillTargetsAsync(caster, skill, enemys, teammates, castRange);
if (targets.Count == 0 && CharactersInAI.Contains(caster))
{
targets = skill.SelectTargets(caster, enemys, teammates);
@@ -2383,15 +2571,16 @@ namespace Milimoe.FunGame.Core.Model
///
///
///
+ ///
///
- public async Task> SelectTargetsAsync(Character character, NormalAttack attack, List enemys, List teammates)
+ public async Task> SelectTargetsAsync(Character character, NormalAttack attack, List enemys, List teammates, List attackRange)
{
List effects = [.. character.Effects.Where(e => e.IsInEffect)];
foreach (Effect effect in effects)
{
effect.AlterSelectListBeforeSelection(character, attack, enemys, teammates);
}
- List targets = await OnSelectNormalAttackTargetsAsync(character, attack, enemys, teammates);
+ List targets = await OnSelectNormalAttackTargetsAsync(character, attack, enemys, teammates, attackRange);
if (targets.Count == 0 && CharactersInAI.Contains(character))
{
targets = character.NormalAttack.SelectTargets(character, enemys, teammates);
@@ -3359,7 +3548,7 @@ namespace Milimoe.FunGame.Core.Model
return await (SelectItem?.Invoke(this, character, items) ?? Task.FromResult- (null));
}
- public delegate Task SelectTargetGridEventHandler(GamingQueue queue, Character character, List enemys, List teammates, GameMap map);
+ public delegate Task SelectTargetGridEventHandler(GamingQueue queue, Character character, List enemys, List teammates, GameMap map, List moveRange);
///
/// 选取移动目标事件
///
@@ -3371,13 +3560,14 @@ namespace Milimoe.FunGame.Core.Model
///
///
///
+ ///
///
- protected async Task OnSelectTargetGridAsync(Character character, List enemys, List teammates, GameMap map)
+ protected async Task OnSelectTargetGridAsync(Character character, List enemys, List teammates, GameMap map, List moveRange)
{
- return await (SelectTargetGrid?.Invoke(this, character, enemys, teammates, map) ?? Task.FromResult(Grid.Empty));
+ return await (SelectTargetGrid?.Invoke(this, character, enemys, teammates, map, moveRange) ?? Task.FromResult(Grid.Empty));
}
- public delegate Task
> SelectSkillTargetsEventHandler(GamingQueue queue, Character caster, Skill skill, List enemys, List teammates);
+ public delegate Task> SelectSkillTargetsEventHandler(GamingQueue queue, Character caster, Skill skill, List enemys, List teammates, List castRange);
///
/// 选取技能目标事件
///
@@ -3389,13 +3579,14 @@ namespace Milimoe.FunGame.Core.Model
///
///
///
+ ///
///
- protected async Task> OnSelectSkillTargetsAsync(Character caster, Skill skill, List enemys, List teammates)
+ protected async Task> OnSelectSkillTargetsAsync(Character caster, Skill skill, List enemys, List teammates, List castRange)
{
- return await (SelectSkillTargets?.Invoke(this, caster, skill, enemys, teammates) ?? Task.FromResult(new List()));
+ return await (SelectSkillTargets?.Invoke(this, caster, skill, enemys, teammates, castRange) ?? Task.FromResult(new List()));
}
- public delegate Task> SelectNormalAttackTargetsEventHandler(GamingQueue queue, Character character, NormalAttack attack, List enemys, List teammates);
+ public delegate Task> SelectNormalAttackTargetsEventHandler(GamingQueue queue, Character character, NormalAttack attack, List enemys, List teammates, List attackRange);
///
/// 选取普通攻击目标事件
///
@@ -3407,10 +3598,11 @@ namespace Milimoe.FunGame.Core.Model
///
///
///
+ ///
///
- protected async Task> OnSelectNormalAttackTargetsAsync(Character character, NormalAttack attack, List enemys, List teammates)
+ protected async Task> OnSelectNormalAttackTargetsAsync(Character character, NormalAttack attack, List enemys, List teammates, List attackRange)
{
- return await (SelectNormalAttackTargets?.Invoke(this, character, attack, enemys, teammates) ?? Task.FromResult(new List()));
+ return await (SelectNormalAttackTargets?.Invoke(this, character, attack, enemys, teammates, attackRange) ?? Task.FromResult(new List()));
}
public delegate Task InterruptCastingEventHandler(GamingQueue queue, Character cast, Skill? skill, Character interrupter);
diff --git a/Model/TeamGamingQueue.cs b/Model/TeamGamingQueue.cs
index 1ee7079..17b0acd 100644
--- a/Model/TeamGamingQueue.cs
+++ b/Model/TeamGamingQueue.cs
@@ -147,7 +147,7 @@ namespace Milimoe.FunGame.Core.Model
{
string[] teamActive = [.. Teams.OrderByDescending(kv => kv.Value.Score).Select(kv =>
{
- int activeCount = kv.Value.GetActiveCharacters(this).Count;
+ int activeCount = kv.Value.GetActiveCharacters().Count;
if (kv.Value == killTeam)
{
activeCount += 1;
@@ -159,7 +159,7 @@ namespace Milimoe.FunGame.Core.Model
if (deathTeam != null)
{
- List remain = deathTeam.GetActiveCharacters(this);
+ List remain = deathTeam.GetActiveCharacters();
int remainCount = remain.Count;
if (remainCount == 0)
{
@@ -175,7 +175,7 @@ namespace Milimoe.FunGame.Core.Model
if (killTeam != null)
{
- List actives = killTeam.GetActiveCharacters(this);
+ List actives = killTeam.GetActiveCharacters();
int remainCount = actives.Count;
if (remainCount > 0 && MaxRespawnTimes == 0)
{