Files
JJBB/Assets/Project/Script/Player/AI/AI_PlayerCombat.cs
2024-08-23 15:49:34 +08:00

744 lines
32 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/********************************************************************************
* 文件名: 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);
}
}
}
}