/******************************************************************************** * 文件名: AI_PlayCombat.cs * 全路径: \Script\Player\AI\AI_PlayCombat.cs * 创建时间:2013-12-4 * * 功能说明:主角战斗AI * 修改记录: *********************************************************************************/ using System.Collections.Generic; using Games.GlobeDefine; using Games.ImpactModle; using Games.LogicObj; using Games.Mission; using Games.SkillModle; using GCGame.Table; using Module.Log; using UnityEngine; namespace Games.AI_Logic { public class AI_PlayerCombat : AI_BaseCombat { // 挂机技能延迟时间,防止操作过快 private const float _autoSkillDelay = 0.05f; // 挂机最小接近距离 public const float minCloseInDistance = 0.5f; // 跟随NPC开始距离 private const float _escortDistTolerance = 2f; // 进入地图锁定时间 private const float _startLockTime = 2f; // 无法索敌发送协议时间 private const float _requestEnemyInterval = 0.3f; // 最小设置移动到目标时间间隔 private const float _minMoveResetTime = 1f; // 使用技能失败后的锁定时间 private const float _skillFailLockTime = 0.5f; //记录杀怪任务有哪些怪可杀 private readonly List _killMonsters = new List(); // 记录哪些技能出错后,需要锁定一段时间 private readonly List> _skillFailList = new List>(); // 上次释放的技能 - 防止单个技能出错,锁死自动战斗流程 private Tab_SkillBase _lastSkillBase; // 下次设置移动目标的时间 private float _nextMoveToTargetTime; // 下次发送需求敌人位置协议的时间 private float _nextRequestEnemyTime; // 下次设置普攻的时间 private float _nextSimpleAttackTime; // 下次设置技能的时间 private float _nextSkillTime; // 主角引用 private Obj_MainPlayer _player; private float _unlockTime = float.PositiveInfinity; private void Start() { //装载AI到AIController,进行统一管理 LoadAI(); _player = gameObject.GetComponent(); if (_player != null && _player.Controller != null) { //如果已经设定了自动挂机 则开启自动挂机 if (_player.isAutoCombat) { if (GameManager.gameManager.ActiveScene.IsCopyScene() == false) _player.BreakAutoCombatState(); _player.Controller.EnterCombat(); } } else { var errorType = _player == null ? "Obj_MainPlayer" : "AIController"; LogModule.ErrorLog(string.Format("AI_PlayerCombat初始化失败,没有找到{0},也无法自修复!", errorType)); } } public void LockAction() { _unlockTime = Time.unscaledTime + _startLockTime; } //激活AI public override void OnActive() { base.OnActive(); RefreshKillMonsterId(); UpdateAI(); } public override void OnDeactive() { base.OnDeactive(); _killMonsters.Clear(); } // 基本构想 - 每次释放技能后,AI等待一个最小可接技能的时间,然后再试图扫描新的状态; public override void UpdateAI() { base.UpdateAI(); if (IsCanAuto()) if (_player.IsDisableState(DISABLESTATE.Disable_UseSkill) || _player.IsDisableState(DISABLESTATE.Disable_Select)) { } // 如果正在使用自动连续技能,暂时不修改当前状态 else if (_player.IsPredictSkill && _player.PredictSkillBase.SkillClass.ContainFlag((int) SKILLCLASS.AUTOREPEAT) || _player.IsUsingSkill && _player.SkillCore.CurrentSkillBase.SkillClass.ContainFlag((int) SKILLCLASS.AUTOREPEAT)) { } // 如果预测处于自动战斗技能使用状态,则跳过这一帧 else if (Time.time < _nextSkillTime) { } else { // 2019.01.31 自动战斗流程改成行为树流程,放弃之前不易维护的运算量优化 // 每一帧对可用技能执行排序,然后依次对目标监测当前技能 // 每一帧对可用目标执行排序,然后依次监测最适合目标 // 获得可用技能列表 var skillList = new List(); var skillBar = SkillBarLogic.Instance(); if (skillBar != null) for (var i = 0; i < skillBar.SkillButtons.Length; i++) { var skillButton = skillBar.SkillButtons[i]; if (skillButton.SkillIndex >= 0 && skillButton.SkillIndex < _player.OwnSkillInfo.Length) { var ownSkill = _player.OwnSkillInfo[skillButton.SkillIndex]; if (ValidOwnSkill(ownSkill) && // 不允许连续使用同一个技能 - 防止自动战斗因为单个技能故障锁死 ownSkill.ComboBaseTable != _lastSkillBase) { var actionMask = GetSkillActionPriority(ownSkill); skillList.Add(new AutoCombatSkill(ownSkill, actionMask)); } } } skillList.Sort(SkillComparison); // 获得可能目标列表 var resurrectTargets = new List(); var healTargets = new List(); var attackTargets = new List(); foreach (var keyValue in ObjManager.Instance.ObjPools) { var character = keyValue.Value as Obj_Character; if (character) { var actionMask = GetTargetActionMask(character); if (actionMask > 0) { var distance = _player.DistanceToAnotherEdge(character); if (actionMask.ContainFlag(AutoActionPriority.resurrect)) { var combatTarget = new AutoCombatTarget(character, actionMask, distance); resurrectTargets.Add(combatTarget); } else if (actionMask.ContainFlag(AutoActionPriority.heal)) { var combatTarget = new AutoCombatTarget(character, actionMask, distance); healTargets.Add(combatTarget); } else { var priority = GetAttackPriority(character); if (priority > 0) { var combatTarget = new AutoCombatTarget(character, actionMask, distance) {priority = priority}; // 额外优先位,暂时只有攻击类需要这个 attackTargets.Add(combatTarget); } } } } } OwnSkillData skill = null; Obj_Character target = null; for (var i = 0; i < skillList.Count; i++) { var checkSkill = skillList[i]; List targetList = null; var actionPriority = checkSkill.actionPriority; switch (actionPriority) { case AutoActionPriority.resurrect: targetList = resurrectTargets; break; case AutoActionPriority.heal: targetList = healTargets; break; case AutoActionPriority.attack: targetList = attackTargets; break; default: LogModule.ErrorLog("Cannot solve action mask for " + checkSkill.actionPriority + " of skillEx " + checkSkill.ownSkill.ComboExTableFinal.SkillExID); break; } // 在拥有当前敌对目标时,仅仅使用敌对目标 if (actionPriority == AutoActionPriority.attack && _player.enemyTarget != null) { target = _player.enemyTarget; if (ValidateSkillTarget(checkSkill, target)) { skill = checkSkill.ownSkill; } } else { if (targetList != null && targetList.Count > 0) { target = GetSkillTarget(targetList, checkSkill); if (target != null) skill = checkSkill.ownSkill; } } if (skill != null) break; } // 无法使用技能时,监视普攻技能 if (skill == null) { var nextAttack = _player.GetNextSimpleAttack(); if (nextAttack != null) { var actionMask = GetSkillActionPriority(nextAttack); var checkSkill = new AutoCombatSkill(nextAttack, actionMask); if (_player.enemyTarget != null) target = _player.enemyTarget; else target = GetSkillTarget(attackTargets, checkSkill); if (target != null) skill = checkSkill.ownSkill; } } // 根据选择情况重置当前目标和技能 if (skill == null) { // 如果确实没有可以使用的技能,获得最近的敌人作为目标 if (attackTargets.Count > 0) target = _player.enemyTarget ?? attackTargets[0].target; } else { // 如果不能释放,则取消技能选择,但保留目标 if (!_player.ValidSkillRangeOnTarget(skill.ComboExTableFinal, skill.ComboBaseTable, target)) skill = null; } // 无目标时,试图获得新的目标 if (target == null) { if (_player.MovementState == MoveState.Static && IsAutoSearchNpc()) { // 如果是护送副本,则试图获得护送NPC位置 var fubenData = TableManager.GetFubenByID( GameManager.gameManager.PlayerDataPool.EnterSceneCache.EnterCopySceneID, 0); var isEscortFuben = fubenData != null && fubenData.IsEscort > 0; if (isEscortFuben) { var escortNpc = SelectEscortNpc(); if (escortNpc == null) { if (Time.time > _nextRequestEnemyTime) { _nextRequestEnemyTime = Time.time + _requestEnemyInterval; // 护送任务,试图搜索护送NPC var send = (CG_REQ_NEED_FOLLOW_NPC_POS) PacketDistributed.CreatePacket( MessageID .PACKET_CG_REQ_NEED_FOLLOW_NPC_POS); send.SetNilparam(0); send.SendPacket(); } } else { MoveToEscortNpc(_player, escortNpc); } } else if (Time.time > _nextRequestEnemyTime) { _nextRequestEnemyTime = Time.time + _requestEnemyInterval; // 非护送任务,试图搜索敌对NPC var send = (CG_REQ_NEAREST_NPC_POS) PacketDistributed.CreatePacket(MessageID .PACKET_CG_REQ_NEAREST_NPC_POS); send.SetNilparam(0); send.SendPacket(); } } } else if (skill == null) { WalkToTarget(target); } else { CastAutoCombatSkill(skill, target); } } } private bool IsAutoSearchNpc() { Tab_Fuben fuben = TableManager.GetFubenByID(GameManager.gameManager.PlayerDataPool.EnterSceneCache.EnterCopySceneID, 0); if (fuben != null && (fuben.Type == 2 || fuben.AutoSearchNPC == 1)) return true; return false; } private Obj_Character GetSkillTarget(List targetList, AutoCombatSkill skill) { Obj_Character result = null; if (targetList != null && targetList.Count > 0) { for (var i = 0; i < targetList.Count; i++) targetList[i].UpdateWeightage(_player, skill); targetList.Sort(TargetComparison); for (var i = 0; i < targetList.Count; i++) { var checkTarget = targetList[i]; if (ValidateSkillTarget(skill, checkTarget.target)) { result = checkTarget.target; break; } } } return result; } private bool ValidateSkillTarget(AutoCombatSkill skill, Obj_Character target) { var result = true; // 治疗技能额外监视目标生命 if (skill.actionPriority == AutoActionPriority.heal && skill.ownSkill.ComboBaseTable.HealthAutoFight > 0) { var baseAttr = target.BaseAttr; if (baseAttr == null) result = false; else if (baseAttr.HP * 100 / baseAttr.MaxHP > skill.ownSkill.ComboBaseTable.HealthAutoFight) result = false; } if (result) if (!Obj_MainPlayer.ValidTargetTypeBySkillBase(target, skill.ownSkill.ComboBaseTable.PriorityTarget)) result = false; // 特殊处理冲锋类技能 if (result && skill.actionPriority == AutoActionPriority.attack) { var moveImpact = skill.ownSkill.ComboExTableFinal.GetMoveSkillImpact(); if (moveImpact != null && !_player.CanDashToTarget(moveImpact, target)) result = false; } return result; } private int SkillComparison(AutoCombatSkill a, AutoCombatSkill b) { var result = a.actionPriority.CompareTo(b.actionPriority); if (result == 0) result = a.ownSkill.PriorityAutoCombat.CompareTo(b.ownSkill.PriorityAutoCombat); // 高释放距离的技能优先 if (result == 0) result = a.ownSkill.ComboExTableFinal.Radius.CompareTo(b.ownSkill.ComboExTableFinal.Radius); return -result; } private int TargetComparison(AutoCombatTarget a, AutoCombatTarget b) { var result = a.priority.CompareTo(b.priority); if (result == 0) result = a.threat.CompareTo(b.threat); // 注:weightage 和 distance 均为越小越优先 if (result == 0) result = -a.weightage.CompareTo(b.weightage); if (result == 0) result = a.inRange.CompareTo(b.inRange); if (result == 0) result = -a.distance.CompareTo(b.distance); return -result; } private void CastAutoCombatSkill(OwnSkillData ownSkill, Obj_Character target) { var attackPoint = (target.Position - _player.Position).RemoveY(); var castRange = ownSkill.ComboExTableFinal.Radius; // 校正射程 - 由于距离计算时会用目标边缘,目标中心位置可能超过最大距离 if (attackPoint.sqrMagnitude > castRange.ToSquare()) attackPoint = attackPoint.normalized * castRange; // 对地技能不上传目标 var skillTarget = ownSkill.ComboExTableFinal.IsTargetGround() ? null : target; // 如果是对地面释放的技能,推送敌人目标位置到ActiveScene if (_player.TryStartTargetSkill(ownSkill, attackPoint, skillTarget, false) == SkillCastableCheckResult.Success) { _nextSimpleAttackTime = Time.time + _autoSkillDelay + Obj_Character.SkillToSimpleDelay(ownSkill.ComboExTableFinal); _nextSkillTime = Time.time + _autoSkillDelay + Obj_Character.SkillToSkillDelay(ownSkill.ComboExTableFinal); } var skillBase = ownSkill.ComboBaseTable; _lastSkillBase = skillBase.SkillClass.ContainFlag((int) SKILLCLASS.SIMPLEATTACK) ? null : skillBase; } private void WalkToTarget(Obj_Character target) { if (Time.time > _nextSimpleAttackTime + _autoSkillDelay && _player.MovementState == MoveState.Static) if (Time.time > _nextMoveToTargetTime) { _nextMoveToTargetTime = Time.time + _minMoveResetTime; // 注:自动战斗移动不作为指令发出,但是需要自行判断移动条件 if (_player.IsCanOperate_Move(false) && (_player.Position - target.Position).RemoveY().sqrMagnitude > minCloseInDistance.ToSquare()) _player.MainPlayMoveToTarget(target, new Obj_MainPlayer.MoveToTargetTillDistance(minCloseInDistance), isMoveOrder: false); } } public void AddSkillFailRecord(int skillBaseId, bool canBeUsed) { _nextSkillTime = 0f; _nextSimpleAttackTime = 0f; if (!canBeUsed) { MyTuple fail = null; for (var i = 0; i < _skillFailList.Count; i++) if (_skillFailList[i].first == skillBaseId) { fail = _skillFailList[i]; break; } if (fail == null) { fail = new MyTuple(skillBaseId, 0f); _skillFailList.Add(fail); } fail.second = Time.unscaledTime + _skillFailLockTime; } } private bool IsCanAuto() { return Time.unscaledTime > _unlockTime && _player != null && _player.Controller != null && !_player.IsDie() // 副本需要灵敏地远程寻路到目标,因此干掉这个标识 // && _player.Controller.CombatFlag && _player.isAutoCombat && !SceneMovieManager.Instance._PlayingMovie // 注:同IsDisableControl不同 && _player.FollowState != TeamFollowState.followMove && !_player.IsHaveChaosBuff() // 只能能够使用技能或者能够使用普攻,不然就不折腾了 && (GameManager.gameManager.PlayerDataPool.AllowSimpleInput || GameManager.gameManager.PlayerDataPool.AllowSkillInput); } public static void MoveToEscortNpc(Obj_MainPlayer mainPlayer, Obj_Character escortNpc) { var roleBase = TableManager.GetRoleBaseAttrByID(escortNpc.BaseAttr.RoleBaseID, 0); var minDist = roleBase == null ? 0 : roleBase.PlayerFollow; var maxDist = minDist + _escortDistTolerance; if ((mainPlayer.Position - escortNpc.Position).sqrMagnitude > maxDist.ToSquare()) mainPlayer.MainPlayMoveToTarget(escortNpc, new Obj_MainPlayer.MoveToTargetTillDistance(minDist)); } private bool ValidOwnSkill(OwnSkillData ownSkill) { return ownSkill.IsValid() && !ownSkill.ComboBaseTable.SkillClass.ContainFlag((int) SKILLCLASS.SIMPLEATTACK) // 可以使用复活类绝技,过滤其他类型 && !ownSkill.ComboBaseTable.SkillClass.ContainFlag((int) SKILLCLASS.XP) && ownSkill.PriorityAutoCombat > 0 && !IsInFailLock(ownSkill.SkillBaseTable.Id) && GetSkillActionPriority(ownSkill) > 0 && _player.CheckSkillCanBeCasted(ownSkill.SkillBaseTable,ownSkill.SkillExTable,ownSkill.IsCooldownFinish()) == SkillCastableCheckResult.Success; } private int GetSkillActionPriority(OwnSkillData ownSkill) { int autoActionMask; var targetType = ownSkill.ComboBaseTable.PriorityTarget; if (targetType.ContainFlag(CharacterDefine.SkillBaseTargetType .friend) || targetType.ContainFlag(CharacterDefine.SkillBaseTargetType.self)) autoActionMask = targetType.ContainFlag(CharacterDefine.SkillBaseTargetType.die) ? AutoActionPriority.resurrect : AutoActionPriority.heal; else if (targetType.ContainFlag(CharacterDefine.SkillBaseTargetType.enemy)) autoActionMask = AutoActionPriority.attack; else autoActionMask = 0; return autoActionMask; } private bool IsInFailLock(int skillBaseId) { var result = false; for (var i = 0; i < _skillFailList.Count; i++) if (_skillFailList[i].first == skillBaseId) result = _skillFailList[i].second > Time.unscaledTime; return result; } private int GetTargetActionMask(Obj_Character character) { var result = 0; if (!character.IsDisableState(DISABLESTATE.Disable_BeSelect)) { // 注:之后使用otherPlayer != null 判定是否允许执行友好行为 var otherPlayer = character as Obj_OtherPlayer; if (otherPlayer != null) if (otherPlayer != _player && !GameManager.gameManager.PlayerDataPool.IsTeamMem(otherPlayer.GUID)) otherPlayer = null; if (otherPlayer != null) if (otherPlayer.IsDie()) { if (otherPlayer != _player) result = AutoActionPriority.resurrect; } else { result = AutoActionPriority.heal; } else if (!character.IsDie()) result = AutoActionPriority.attack; } return result; } private void RefreshKillMonsterId() { _killMonsters.Clear(); foreach (var oldPair in GameManager.gameManager.MissionManager.MissionList.m_aMission) if (oldPair.Value != null && oldPair.Value.m_yStatus == (int) MissionState.Mission_Accepted) { var missInfo = CommonUtility.TryGetTable(oldPair.Key, a => TableManager.GetMissionBaseByID(a, 0)); if (missInfo != null) { var missionLogic = CommonUtility.TryGetTable(missInfo.LogicID, a => TableManager.GetMissionLogicByID(a, 0)); if (missionLogic != null) { var curMissionIndex = GameManager.gameManager.MissionManager.getCurMissionIndex(oldPair.Key); if (missionLogic.GetLogicTypebyIndex(curMissionIndex) == (int) TableType.Table_KillMonster) { var killMonster = TableManager.GetMissionKillMonsterByID( missionLogic.GetLogicIDbyIndex(curMissionIndex), 0); if (killMonster != null && !_killMonsters.Contains(killMonster.MonsterDataID)) _killMonsters.Add(killMonster.MonsterDataID); } } } } } /// /// 获得护送副本NPC /// private Obj_Character SelectEscortNpc() { Obj_Character result = null; var targets = Singleton.GetInstance().ObjPools.Values; foreach (var obj in targets) { var npc = obj as Obj_NPC; if (npc != null) { var roleBase = TableManager.GetRoleBaseAttrByID(npc.BaseAttr.RoleBaseID, 0); if (roleBase != null && roleBase.PlayerFollow > 0) { result = npc; break; } } } return result; } /// /// 获得目标相对于主角的选取优先度 /// public int GetAttackPriority(Obj_Character obj) { var priority = 0; if (obj.IsDie() || obj.ObjType == GameDefine_Globe.OBJ_TYPE.OBJ_MAIN_PLAYER) { } else { // 声望已经包含Pk模式判断,不需要额外检查 var reputation = Reputation.GetObjReputionType(obj); if (Reputation.CanAttack(reputation) && _player.ValidPkProtection(obj, false)) { // 可以攻击该目标 priority += TargetPriority.canAttack.ToFlag(); // 然后选择任务目标 if (_killMonsters.Contains(obj.BaseAttr.RoleBaseID)) priority += TargetPriority.mission.ToFlag(); if (obj is Obj_OtherPlayer) { priority += TargetPriority.otherPlayer.ToFlag(); } else { // 然后判断NPC类型 var npc = obj as Obj_NPC; if (npc != null) { var attrData = TableManager.GetRoleBaseAttrByID(npc.RoleBaseID, 0); if (attrData != null) if (attrData.NpcType == (int) GameDefine_Globe.NPC_TYPE.ELITE || attrData.NpcType == (int) GameDefine_Globe.NPC_TYPE.TOWER) priority += TargetPriority.eliteNpc.ToFlag(); else if (attrData.NpcType == (int) GameDefine_Globe.NPC_TYPE.BOSS) priority += TargetPriority.boss.ToFlag(); } } } // 如果可以攻击,则敌对目标最为优先 if (priority > 0 && reputation == CharacterDefine.REPUTATION_TYPE.REPUTATION_HOSTILE) priority += TargetPriority.hostile.ToFlag(); } return priority; } private static class TargetPriority { // 可以进行攻击 public const int canAttack = 0; // 精英怪物 public const int eliteNpc = 1; // 任务目标怪物 public const int mission = 2; // 可攻击其他玩家 public const int otherPlayer = 3; // Boss 类型,比其他玩家优先度高 public const int boss = 4; // 敌对类型目标 public const int hostile = 5; } /// /// 自动战斗行为类型 /// private static class AutoActionPriority { // 复活目标 public const int resurrect = 1 << 2; // 治疗目标 public const int heal = 1 << 1; // 攻击目标 public const int attack = 1 << 0; } private struct AutoCombatSkill { public readonly int actionPriority; public readonly OwnSkillData ownSkill; public AutoCombatSkill(OwnSkillData ownSkillData, int actionPriority) { ownSkill = ownSkillData; this.actionPriority = actionPriority; } } private struct AutoCombatTarget { public readonly int actionMask; public readonly Obj_Character target; public bool inRange; public float weightage; public readonly float distance; public int priority; public int threat; public AutoCombatTarget(Obj_Character target, int actionMask, float distance) { this.target = target; this.actionMask = actionMask; this.distance = distance; inRange = default(bool); weightage = default(int); priority = default(int); threat = default(int); } public void UpdateWeightage(Obj_MainPlayer mainPlayer, AutoCombatSkill skillData) { var skillEx = skillData.ownSkill.ComboExTableFinal; inRange = distance < skillEx.Radius; var targetOrder = Obj_MainPlayer.GetTargetSelectOrder(skillEx); weightage = mainPlayer.GetWeightByOrder(target, targetOrder); threat = mainPlayer.Controller.GetThreatValue(target); } } } }