From ad4a9a43469e0eeeb6d693ffc2ff79b57f765f9c Mon Sep 17 00:00:00 2001 From: milimoe Date: Fri, 26 Sep 2025 23:32:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=92=E9=98=9F=E6=9C=BA=E5=88=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=EF=BC=9BAI=E7=A7=BB=E5=8A=A8=E5=86=B3=E7=AD=96?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Controller/AIController.cs | 19 +++-- Entity/Skill/Skill.cs | 2 +- Model/GamingQueue.cs | 148 ++++++++++++++++++++++++------------- 3 files changed, 108 insertions(+), 61 deletions(-) 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 回合奖励