diff --git a/Controller/AIController.cs b/Controller/AIController.cs
index 35b77a0..ea453f2 100644
--- a/Controller/AIController.cs
+++ b/Controller/AIController.cs
@@ -21,9 +21,12 @@ namespace Milimoe.FunGame.Core.Controller
/// 角色所有可用的物品(已过滤CD和EP/MP)。
/// 场上所有敌人。
/// 场上所有队友。
+ /// 场上能够选取的敌人。
+ /// 场上能够选取的队友。
/// 包含最佳行动的AIDecision对象。
public async Task DecideAIActionAsync(Character character, Grid startGrid, List allPossibleMoveGrids,
- List availableSkills, List- availableItems, List allEnemysInGame, List allTeammatesInGame)
+ List availableSkills, List
- availableItems, List allEnemysInGame, List allTeammatesInGame,
+ List selectableEnemys, List selectableTeammates)
{
// 初始化一个默认的“结束回合”决策作为基准
AIDecision bestDecision = new()
@@ -47,10 +50,10 @@ namespace Milimoe.FunGame.Core.Controller
List normalAttackReachableGrids = _map.GetGridsByRange(potentialMoveGrid, character.ATR, true);
List normalAttackReachableEnemys = [.. allEnemysInGame
- .Where(c => normalAttackReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable)
+ .Where(c => normalAttackReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c))
.Distinct()];
List normalAttackReachableTeammates = [.. allTeammatesInGame
- .Where(c => normalAttackReachableGrids.SelectMany(g => g.Characters).Contains(c))
+ .Where(c => normalAttackReachableGrids.SelectMany(g => g.Characters).Contains(c) && selectableTeammates.Contains(c))
.Distinct()];
if (normalAttackReachableEnemys.Count > 0)
@@ -83,10 +86,10 @@ namespace Milimoe.FunGame.Core.Controller
List skillReachableGrids = _map.GetGridsByRange(potentialMoveGrid, skill.CastRange, true);
List skillReachableEnemys = [.. allEnemysInGame
- .Where(c => skillReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable)
+ .Where(c => skillReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c))
.Distinct()];
List skillReachableTeammates = [.. allTeammatesInGame
- .Where(c => skillReachableGrids.SelectMany(g => g.Characters).Contains(c))
+ .Where(c => skillReachableGrids.SelectMany(g => g.Characters).Contains(c) && selectableTeammates.Contains(c))
.Distinct()];
// 检查是否有可用的目标(敌人或队友,取决于技能类型)
@@ -123,10 +126,10 @@ namespace Milimoe.FunGame.Core.Controller
List itemSkillReachableGrids = _map.GetGridsByRange(potentialMoveGrid, itemSkill.CastRange, true);
List itemSkillReachableEnemys = [.. allEnemysInGame
- .Where(c => itemSkillReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable)
+ .Where(c => itemSkillReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c))
.Distinct()];
List itemSkillReachableTeammates = [.. allTeammatesInGame
- .Where(c => itemSkillReachableGrids.SelectMany(g => g.Characters).Contains(c))
+ .Where(c => itemSkillReachableGrids.SelectMany(g => g.Characters).Contains(c) && selectableTeammates.Contains(c))
.Distinct()];
// 检查是否有可用的目标
@@ -176,7 +179,7 @@ namespace Milimoe.FunGame.Core.Controller
}
List tempAllReachableGridsForPureMove = [.. tempAttackGridsForPureMove.Union(tempCastGridsForPureMove).Distinct()];
List tempCurrentReachableEnemysForPureMove = [.. allEnemysInGame
- .Where(c => tempAllReachableGridsForPureMove.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable)
+ .Where(c => tempAllReachableGridsForPureMove.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c))
.Distinct()];
// 如果当前位置无法攻击任何敌人,但地图上还有敌人,尝试向最近的敌人移动
diff --git a/Entity/Skill/Skill.cs b/Entity/Skill/Skill.cs
index 808bb39..da26940 100644
--- a/Entity/Skill/Skill.cs
+++ b/Entity/Skill/Skill.cs
@@ -93,7 +93,7 @@ namespace Milimoe.FunGame.Core.Entity
///
/// 是否无视施法距离(全图施法),魔法默认为 true,战技默认为 false
///
- public bool CastAnywhere { get; set; } = false;
+ public virtual bool CastAnywhere { get; set; } = false;
///
/// 施法距离 [ 单位:格 ]
diff --git a/Model/GamingQueue.cs b/Model/GamingQueue.cs
index 1747375..32a9cea 100644
--- a/Model/GamingQueue.cs
+++ b/Model/GamingQueue.cs
@@ -114,6 +114,29 @@ namespace Milimoe.FunGame.Core.Model
///
public Dictionary> RoundRewards => _roundRewards;
+ ///
+ /// 是否使用插队保护机制
+ ///
+ public bool UseQueueProtected { get; set; }
+
+ ///
+ /// 插队保护机制检查的最多被插队次数:-1 为默认,即队列长度,最少为 5;0 为不保护
+ ///
+ public int MaxCutQueueTimes
+ {
+ get
+ {
+ if (!UseQueueProtected || _maxCutQueueTimes == 0) return 0;
+ else if (_maxCutQueueTimes == -1) return Math.Max(5, _queue.Count);
+ else if (_maxCutQueueTimes < 5) return 5;
+ else return _maxCutQueueTimes;
+ }
+ set
+ {
+ _maxCutQueueTimes = value;
+ }
+ }
+
///
/// 自定义数据
///
@@ -238,6 +261,11 @@ namespace Milimoe.FunGame.Core.Model
///
protected Func> _factoryRoundRewardEffects = id => [];
+ ///
+ /// 最多被插队次数,-1 为默认,即队列长度,最少为 5
+ ///
+ protected int _maxCutQueueTimes = 5;
+
///
/// 游戏是否结束
///
@@ -472,49 +500,51 @@ namespace Milimoe.FunGame.Core.Model
if (isCheckProtected)
{
// 查找保护条件 被插队超过此次数便能获得插队补偿 即行动保护
- int countProtected = Math.Max(5, _queue.Count);
-
- // 查找队列中是否有满足插队补偿条件的角色(最后一个)
- var list = _queue
- .Select((c, index) => new { Character = c, Index = index })
- .Where(x => _cutCount.ContainsKey(x.Character) && _cutCount[x.Character] >= countProtected);
-
- // 如果没有找到满足条件的角色,返回 -1
- int protectIndex = list.Select(x => x.Index).LastOrDefault(-1);
-
- if (protectIndex != -1)
+ int countProtected = MaxCutQueueTimes;
+ if (countProtected > 0)
{
- // 获取最后一个符合条件的角色
- Character lastProtectedCharacter = list.Last().Character;
- double lastProtectedHardnessTime = _hardnessTimes[lastProtectedCharacter];
-
- // 查找与最后一个受保护角色相同硬直时间的其他角色
- var sameHardnessList = _queue
+ // 查找队列中是否有满足插队补偿条件的角色(最后一个)
+ var list = _queue
.Select((c, index) => new { Character = c, Index = index })
- .Where(x => _hardnessTimes[x.Character] == lastProtectedHardnessTime && x.Index > protectIndex);
+ .Where(x => _cutCount.ContainsKey(x.Character) && _cutCount[x.Character] >= countProtected);
- // 如果找到了相同硬直时间的角色,更新 protectIndex 为它们中最后一个的索引
- if (sameHardnessList.Any())
+ // 如果没有找到满足条件的角色,返回 -1
+ int protectIndex = list.Select(x => x.Index).LastOrDefault(-1);
+
+ if (protectIndex != -1)
{
- protectIndex = sameHardnessList.Select(x => x.Index).Last();
- }
+ // 获取最后一个符合条件的角色
+ Character lastProtectedCharacter = list.Last().Character;
+ double lastProtectedHardnessTime = _hardnessTimes[lastProtectedCharacter];
- // 判断是否需要插入到受保护角色的后面
- // 只有当插入位置在受保护角色之前或相同时才触发保护
- if (insertIndex != -1 && insertIndex <= protectIndex)
- {
- // 插入到受保护角色的后面一位
- insertIndex = protectIndex + 1;
+ // 查找与最后一个受保护角色相同硬直时间的其他角色
+ var sameHardnessList = _queue
+ .Select((c, index) => new { Character = c, Index = index })
+ .Where(x => _hardnessTimes[x.Character] == lastProtectedHardnessTime && x.Index > protectIndex);
- // 设置硬直时间为保护角色的硬直时间 + 0.01
- hardnessTime = lastProtectedHardnessTime + 0.01;
- hardnessTime = ResolveConflict(hardnessTime, character);
+ // 如果找到了相同硬直时间的角色,更新 protectIndex 为它们中最后一个的索引
+ if (sameHardnessList.Any())
+ {
+ protectIndex = sameHardnessList.Select(x => x.Index).Last();
+ }
- // 重新计算插入索引
- insertIndex = _queue.FindIndex(c => _hardnessTimes[c] > hardnessTime);
+ // 判断是否需要插入到受保护角色的后面
+ // 只有当插入位置在受保护角色之前或相同时才触发保护
+ if (insertIndex != -1 && insertIndex <= protectIndex)
+ {
+ // 插入到受保护角色的后面一位
+ insertIndex = protectIndex + 1;
- // 列出受保护角色的名单
- WriteLine($"由于 [ {string.Join(" ],[ ", list.Select(x => x.Character))} ] 受到行动保护,因此角色 [ {character} ] 将插入至顺序表第 {insertIndex + 1} 位。");
+ // 设置硬直时间为保护角色的硬直时间 + 0.01
+ hardnessTime = lastProtectedHardnessTime + 0.01;
+ hardnessTime = ResolveConflict(hardnessTime, character);
+
+ // 重新计算插入索引
+ insertIndex = _queue.FindIndex(c => _hardnessTimes[c] > hardnessTime);
+
+ // 列出受保护角色的名单
+ WriteLine($"由于 [ {string.Join(" ],[ ", list.Select(x => x.Character))} ] 受到行动保护,因此角色 [ {character} ] 将插入至顺序表第 {insertIndex + 1} 位。");
+ }
}
}
}
@@ -815,7 +845,7 @@ namespace Milimoe.FunGame.Core.Model
// 队友列表
List allTeammates = GetTeammates(character);
- List selecableTeammates = [.. allTeammates.Where(_queue.Contains)];
+ List selectableTeammates = [.. allTeammates.Where(_queue.Contains)];
// 敌人列表
List allEnemys = [.. _allCharacters.Where(c => c != character && !allTeammates.Contains(c))];
@@ -831,7 +861,7 @@ namespace Milimoe.FunGame.Core.Model
// 回合开始事件,允许事件返回 false 接管回合操作
// 如果事件全程接管回合操作,需要注意触发特效
- if (!await OnTurnStartAsync(character, selectableEnemys, selecableTeammates, skills, items))
+ if (!await OnTurnStartAsync(character, selectableEnemys, selectableTeammates, skills, items))
{
_isInRound = false;
return _isGameEnd;
@@ -839,13 +869,13 @@ namespace Milimoe.FunGame.Core.Model
foreach (Skill skillTurnStart in skills)
{
- skillTurnStart.OnTurnStart(character, selectableEnemys, selecableTeammates, skills, items);
+ skillTurnStart.OnTurnStart(character, selectableEnemys, selectableTeammates, skills, items);
}
List effects = [.. character.Effects.Where(e => e.IsInEffect)];
foreach (Effect effect in effects)
{
- effect.OnTurnStart(character, selectableEnemys, selecableTeammates, skills, items);
+ effect.OnTurnStart(character, selectableEnemys, selectableTeammates, skills, items);
}
// 此变量用于在取消选择时,能够重新行动
@@ -919,13 +949,13 @@ namespace Milimoe.FunGame.Core.Model
}
enemys = [.. selectableEnemys.Where(canAttackGrids.Union(canCastGrids).SelectMany(g => g.Characters).Contains)];
- teammates = [.. selecableTeammates.Where(canAttackGrids.Union(canCastGrids).SelectMany(g => g.Characters).Contains)];
+ teammates = [.. selectableTeammates.Where(canAttackGrids.Union(canCastGrids).SelectMany(g => g.Characters).Contains)];
willMoveGridWithSkill = [.. canMoveGrids.Where(g => canAttackGrids.Union(canCastGrids).Contains(g))];
}
else
{
enemys = selectableEnemys;
- teammates = selecableTeammates;
+ teammates = selectableTeammates;
}
// AI 决策结果(适用于启用战棋地图的情况)
@@ -1020,7 +1050,7 @@ namespace Milimoe.FunGame.Core.Model
}
else if (character.CharacterState == CharacterState.BattleRestricted)
{
- // 战斗不能,只能使用物品
+ // 战斗不能,只能对自己使用物品
enemys.Clear();
teammates.Clear();
skills.Clear();
@@ -1063,7 +1093,7 @@ namespace Milimoe.FunGame.Core.Model
// 启用战棋地图时的专属 AI 决策方法
if (isAI && ai != null && startGrid != null)
{
- aiDecision = await ai.DecideAIActionAsync(character, startGrid, canMoveGrids, skills, items, allEnemys, allTeammates);
+ aiDecision = await ai.DecideAIActionAsync(character, startGrid, canMoveGrids, skills, items, allEnemys, allTeammates, enemys, teammates);
type = aiDecision.ActionType;
}
else
@@ -1115,10 +1145,14 @@ namespace Milimoe.FunGame.Core.Model
}
moved = await CharacterMoveAsync(character, target, startGrid);
}
- if (isAI && aiDecision != null && cancelTimes == 0)
+ if (isAI && (aiDecision?.IsPureMove ?? false))
{
// 取消 AI 的移动
+ SetOnlyMoveHardnessTime(character, ref baseTime);
type = CharacterActionType.EndTurn;
+ decided = true;
+ WriteLine($"[ {character} ] 结束了回合!");
+ await OnCharacterDoNothingAsync(character);
}
}
else if (type == CharacterActionType.NormalAttack)
@@ -1439,14 +1473,7 @@ namespace Milimoe.FunGame.Core.Model
}
else if (type == CharacterActionType.EndTurn)
{
- baseTime += 3;
- if (character.CharacterState == CharacterState.NotActionable ||
- character.CharacterState == CharacterState.ActionRestricted ||
- character.CharacterState == CharacterState.BattleRestricted)
- {
- baseTime += 3;
- WriteLine($"[ {character} ] {CharacterSet.GetCharacterState(character.CharacterState)},放弃行动将额外获得 3 {GameplayEquilibriumConstant.InGameTime}硬直时间!");
- }
+ SetOnlyMoveHardnessTime(character, ref baseTime);
decided = true;
WriteLine($"[ {character} ] 结束了回合!");
await OnCharacterDoNothingAsync(character);
@@ -2937,6 +2964,23 @@ namespace Milimoe.FunGame.Core.Model
return dict;
}
+ ///
+ /// 对角色设置仅移动的硬直时间
+ ///
+ ///
+ ///
+ public void SetOnlyMoveHardnessTime(Character character, ref double baseTime)
+ {
+ baseTime += 3;
+ if (character.CharacterState == CharacterState.NotActionable ||
+ character.CharacterState == CharacterState.ActionRestricted ||
+ character.CharacterState == CharacterState.BattleRestricted)
+ {
+ baseTime += 3;
+ WriteLine($"[ {character} ] {CharacterSet.GetCharacterState(character.CharacterState)},放弃行动将额外获得 3 {GameplayEquilibriumConstant.InGameTime}硬直时间!");
+ }
+ }
+
#endregion
#region 回合奖励