Files
JJBB/Assets/Project/Script/Player/AI/AI_PlayerCombat.cs

744 lines
32 KiB
C#
Raw Normal View History

2024-08-23 15:49:34 +08:00
/********************************************************************************
* 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<int> _killMonsters = new List<int>();
// 记录哪些技能出错后,需要锁定一段时间
private readonly List<MyTuple<int, float>> _skillFailList = new List<MyTuple<int, float>>();
// 上次释放的技能 - 防止单个技能出错,锁死自动战斗流程
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<Obj_MainPlayer>();
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<AutoCombatSkill>();
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<AutoCombatTarget>();
var healTargets = new List<AutoCombatTarget>();
var attackTargets = new List<AutoCombatTarget>();
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<AutoCombatTarget> 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<AutoCombatTarget> 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<int, float> 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<int, float>(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);
}
}
}
}
}
/// <summary>
/// 获得护送副本NPC
/// </summary>
private Obj_Character SelectEscortNpc()
{
Obj_Character result = null;
var targets = Singleton<ObjManager>.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;
}
/// <summary>
/// 获得目标相对于主角的选取优先度
/// </summary>
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;
}
/// <summary>
/// 自动战斗行为类型
/// </summary>
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);
}
}
}
}