using Games.AI_Logic;
using Games.GlobeDefine;
using Games.ImpactModle;
using Games.SkillModle;
using GCGame.Table;
using Module.Log;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
namespace Games.LogicObj
{
///
/// 主角普攻和技能预判断模块
///
public partial class Obj_MainPlayer
{
// 自动寻路并且使用技能时,释放距离减少的长度
public const float skillCastOverwalk = skillCastReduce + 0.2f;
// 客户端释放技能判定减少数值,注:普攻AoE类型技能无法用服务器补偿距离,因此统一客户端减损距离
public const float skillCastReduce = 0.5f;
public readonly List simpleAttackSkills = new List();
// 缓存跑商时禁用的技能表
private int[] _skillsBannedByEscort;
public int NextAttackIndex { get; private set; }
//按顺序存储所有普攻 策划需求普攻技能ID顺序不固定
private void SetSimpleAttackId()
{
if (simpleAttackSkills.Count < 1)
{
var simpleSkills = new List();
for (var i = 0; i < OwnSkillInfo.Length; i++)
{
var skillInfo = OwnSkillInfo[i];
var skillBase = skillInfo.SkillBaseTable;
if (skillBase != null &&
skillBase.SkillClass.ContainFlag((int)SKILLCLASS.SIMPLEATTACK | (int)SKILLCLASS.ACTIVE))
{
var skillEx =
CommonUtility.TryGetTable(skillInfo.SkillId, a => TableManager.GetSkillExByID(a, 0));
if (skillEx != null)
simpleSkills.Add(skillInfo);
}
}
simpleAttackSkills.Clear();
if (simpleSkills.Count == 1)
{
simpleAttackSkills.Add(simpleSkills[0]);
}
else if (simpleSkills.Count > 0)
{
// 对SimpleSkills进行链表排序,从最后一段开始反向选择
var previousSkill = simpleSkills.Find(a => a.SkillExTable.NextSkillId <= 0);
if (previousSkill == null)
LogModule.ErrorLog(string.Format("职业{0}普攻配置是无限循环!", Profession));
else
while (previousSkill != null)
{
simpleAttackSkills.Add(previousSkill);
previousSkill = simpleSkills.Find(a =>
a.ComboExTable.NextSkillId == previousSkill.ComboExTable.SkillExID);
}
simpleAttackSkills.Reverse();
}
}
}
///
/// 试图使用一个普攻技能
///
public SkillCastableCheckResult TryStartSimpleAttack(bool breakAutoCombat = true)
{
var nextAttack = GetNextSimpleAttack();
if (nextAttack == null)
return SkillCastableCheckResult.FailOther;
return TryStartTargetSkill(nextAttack, breakAutoCombat: breakAutoCombat);
}
public override bool UseSkill(Tab_SkillEx skillEx, Tab_SkillBase skillBase, int firstSkillId, int targetId,
Vector3 facePoint, bool skipWindup = false)
{
// 优先更新技能冷却时间,以服务器为准
UpdateSkillCooldown(firstSkillId);
var result = base.UseSkill(skillEx, skillBase, firstSkillId, targetId, facePoint, skipWindup);
// 无需预测连段的技能,就在释放成功后立刻解除锁定
//if (skillBase.IsPredict < 1)
//RemovePredictSkill(skillEx.SkillExID);
return result;
}
///
/// 使用失败的技能,是否可以立刻被再次使用
///
public bool CanFailedSkillBeUsed(SKILLUSEFAILTYPE failType)
{
return failType == SKILLUSEFAILTYPE.CE_IN_DEATH ||
failType == SKILLUSEFAILTYPE.CE_BACKSWINGCD1_NOT_COOL ||
failType == SKILLUSEFAILTYPE.CE_BACKSWINGCD2_NOT_COOL ||
failType == SKILLUSEFAILTYPE.CE_CUR_SKILL_CANNOT_BREAK ||
failType == SKILLUSEFAILTYPE.CE_MOUNT_FREEZE ||
failType == SKILLUSEFAILTYPE.CE_TARGET_IN_HIDING ||
failType == SKILLUSEFAILTYPE.CE_TARGET_ERROR;
}
///
/// 使用一个技能失败的接口
///
public void FailSkill(int skillExId, int firstSkillId, SKILLUSEFAILTYPE failType)
{
var skillEx = TableManager.GetSkillExByID(firstSkillId, 0);
if (skillEx != null)
{
var canBeUsed = CanFailedSkillBeUsed(failType);
// 如果是普攻失败,回退普攻连段
if (!canBeUsed)
{
for (var i = 0; i < simpleAttackSkills.Count; i++)
if (simpleAttackSkills[i].IsValid() &&
simpleAttackSkills[i].SkillBaseTable.Id == skillEx.BaseId)
NextAttackIndex = 0;
}
if (skillExId == firstSkillId)
{
// 多段技能中,仅仅第一段技能会回退连段
// 注:三段以上技能出错后会混乱,但是当前逻辑可以保证两段技能无误
var ownSkill = OwnSkillInfo.Find(a => a.IsValid() && a.SkillBaseTable.Id == skillEx.BaseId);
if (ownSkill != null)
{
if (!canBeUsed)
ownSkill.CleanComboState();
if (!ownSkill.SkillBaseTable.SkillClass.ContainFlag((int)SKILLCLASS.SIMPLEATTACK))
{
// 记录使用技能出错的情况
var aiPlayerCombat = GetComponent();
if (aiPlayerCombat != null)
aiPlayerCombat.AddSkillFailRecord(skillEx.BaseId, canBeUsed);
}
}
RemovePredictByBaseId(skillEx.BaseId);
}
}
}
///
/// 试图使用一个目标指向技能
///
/// 技能数据
/// 技能目标位置
/// 是否打断自动战斗
/// 攻击目标
/// 技能是否使用成功
public SkillCastableCheckResult TryStartTargetSkill(OwnSkillData ownSkill,
Vector2 attackPoint = default(Vector2),
Obj_Character target = null,
bool breakAutoCombat = true)
{
var castable = SkillCastableCheckResult.FailCasterDeath;
if (!IsDie())
{
castable = SkillCastableCheckResult.FailNoData;
// 已经预判当前技能,等待上一个包收到回复;非自动连续技普攻会自动升段,防止降段后一段时间无法使用普攻
if (IsPredictSkill && PredictSkillBase.Id == ownSkill.ComboBaseTable.Id &&
(!PredictSkillBase.SkillClass.ContainFlag((int)SKILLCLASS.SIMPLEATTACK) ||
PredictSkillBase.SkillClass.ContainFlag((int)SKILLCLASS.AUTOREPEAT)))
{
castable = SkillCastableCheckResult.FailCurrent;
}
// 如果角色正试图使用该技能,则忽略这次输入
else if (ownSkill.ComboExTable != PendingSkillEx)
{
// 无论技能是否释放,使用技能都是玩家主动操作,都应该解除自动战斗
if (breakAutoCombat)
BreakAutoCombatState();
// 检查特殊技能流程是否允许技能的使用
castable = SkillCastableCheckResult.Success;
if (castable == SkillCastableCheckResult.Success)
castable = CheckSkillCastable(ownSkill.SkillBaseTable, ownSkill.SkillExTable, ownSkill.IsCooldownFinish());
if (castable == SkillCastableCheckResult.Success)
{
// 非向目标释放技能,试图获得最适合目标
if (IsDisableState(DISABLESTATE.Disable_Select))
target = null;
else if (target == null && !ownSkill.ComboExTableFinal.IsTargetGround())
target = GetSkillTarget(ownSkill.ComboBaseTable, ownSkill.ComboExTableFinal);
if (target == null)
{
if (ownSkill.ComboBaseTable.IsReleaseWithoutTarget < 1)
{
// 技能不允许无目标释放,并且当前无目标时
GUIData.AddNotifyData(StrDictionary.GetClientDictionaryString("#{1246}"));
castable = SkillCastableCheckResult.FailNoTarget;
}
}
else if (target == enemyTarget
&& !ValidSkillPkCondition(ownSkill.ComboExTableFinal, ownSkill.ComboBaseTable, target))
{
castable = SkillCastableCheckResult.FailPkProtection;
}
if (castable == SkillCastableCheckResult.Success)
ProcessSkill(ownSkill, target, attackPoint);
}
}
}
return castable;
}
///
/// 检查一个技能是否可以被释放;该检查无视角色是否可以释放技能;
///
public SkillCastableCheckResult CheckSkillCanBeCasted(Tab_SkillBase skillBase, Tab_SkillEx skillEx, bool isCDFinish)
{
if (skillBase == null || skillEx == null)
return SkillCastableCheckResult.FailNoData;
var result = SkillCastableCheckResult.Success;
// 任务类型判定条件
if (IsInPaoShang())
{
// 构造数据表缓存
if (_skillsBannedByEscort == null)
{
// Escort表是零散数据,应该是只有一列;
var escortData = TableManager.GetEscortByID(1, 0);
_skillsBannedByEscort = new int[escortData.getSkillLimitBaseIDCount()];
for (var i = 0; i < _skillsBannedByEscort.Length; i++)
_skillsBannedByEscort[i] = escortData.GetSkillLimitBaseIDbyIndex(i);
}
if (_skillsBannedByEscort.Contain(skillBase.Id))
result = SkillCastableCheckResult.FailMission;
}
if (result == SkillCastableCheckResult.Success)
{
var sceneTable = CommonUtility.TryGetTable(GameManager.gameManager.RunningScene,
a => TableManager.GetSceneClassByID(a, 0));
if (sceneTable != null && sceneTable.IsBanUseSkill > 0)
result = SkillCastableCheckResult.FailScene;
}
if (result == SkillCastableCheckResult.Success)
if (skillBase.SkillClass.ContainFlag((int)SKILLCLASS.AUTOREPEAT))
if (IsPredictSkill && IsSkillSequence(skillEx, PredictSkill) ||
IsUsingSkill && IsSkillSequence(skillEx, SkillCore.CurrentSkill))
result = SkillCastableCheckResult.FailAutoRepeat;
if (result == SkillCastableCheckResult.Success)
if (skillBase.SkillClass.ContainFlag((int)SKILLCLASS.SIMPLEATTACK))
{
if (!SkillAllowSimpleAttack)
result = SkillCastableCheckResult.FailOtherSkill;
}
// 技能判定条件
else
{
if (!SkillAllowSkill)
result = SkillCastableCheckResult.FailOtherSkill;
}
if (result == SkillCastableCheckResult.Success)
{
// 检测技能冷却时间
if (!isCDFinish)
result = SkillCastableCheckResult.FailCooldown;
}
// 检测消耗类型
if (result == SkillCastableCheckResult.Success)
result = CheckSkillCost(skillEx, 0);
if (result == SkillCastableCheckResult.Success)
result = CheckSkillCost(skillEx, 1);
// 检测单体技能情况
if (result == SkillCastableCheckResult.Success
&& skillBase.IsReleaseWithoutTarget < 1)
if (IsDisableState(DISABLESTATE.Disable_Select))
result = SkillCastableCheckResult.FailSelect;
return result;
}
///
/// 检查角色是否处于能够释放技能的状态;该检查无视技能本身是否可以释放;
///
public SkillCastableCheckResult CheckCanCastSkill()
{
var result = SkillCastableCheckResult.Success;
if (MovementState > MoveState.MainPlayMove)
result = SkillCastableCheckResult.FailMovement;
else if (IsDisableState(DISABLESTATE.Disable_UseSkill))
result = SkillCastableCheckResult.FailDisable;
else if (SkillCore == null)
result = SkillCastableCheckResult.FailNoCore;
return result;
}
///
/// 检查角色是否可以释放某个技能;相当于CheckCanCastSkill + CheckSkillCanBeCasted;
///
public SkillCastableCheckResult CheckSkillCastable(Tab_SkillBase skillBase, Tab_SkillEx skillEx, bool isCDFinish,
bool showWarning = true)
{
var result = CheckCanCastSkill();
if (result == SkillCastableCheckResult.Success)
result = CheckSkillCanBeCasted(skillBase, skillEx, isCDFinish);
if (showWarning)
switch (result)
{
case SkillCastableCheckResult.FailScene:
GUIData.AddNotifyData(StrDictionary.GetClientDictionaryString("#{2997}"));
break;
case SkillCastableCheckResult.FailMission:
GUIData.AddNotifyData(StrDictionary.GetClientDictionaryString("#{34001}"));
break;
case SkillCastableCheckResult.FailNoTarget:
GUIData.AddNotifyData(StrDictionary.GetClientDictionaryString("#{1246}"));
break;
case SkillCastableCheckResult.FailCostHp:
case SkillCastableCheckResult.FailCostHpRate:
SendNoticMsg(false, "#{1247}");
break;
case SkillCastableCheckResult.FailCostMp:
case SkillCastableCheckResult.FailCostMpRate:
SendNoticMsg(false, "#{1248}");
break;
case SkillCastableCheckResult.FailCostXp:
case SkillCastableCheckResult.FailCostXpRate:
SendNoticMsg(false, "#{1244}");
break;
case SkillCastableCheckResult.FailDisable:
GUIData.AddNotifyData(StrDictionary.GetClientDictionaryString("#{1249}"));
break;
}
return result;
}
///
/// 更新技能CD数值
///
public void UpdateSkillCooldown(int skillId)
{
var skillEx = TableManager.GetSkillExByID(skillId, 0);
if (skillEx != null)
{
var ownSkill = OwnSkillInfo.Find(a => a.IsValid() && a.SkillBaseTable.Id == skillEx.BaseId);
if (ownSkill != null)
{
if (ownSkill.ComboBaseTable.UseType.ContainFlag((int)SKILLUSETYPE.YINCHANG))
{
}
// 连段技能除最后一段外,不增加CD
else if (ownSkill.FullCombo)
{
ownSkill.ResetCooldown();
//var nextSkill = OwnSkillInfo.Find(a => a.IsValid() && a.ComboBaseTable.Id == skillEx.BaseId);
//if (nextSkill != null)
// nextSkill.ResetCooldown();
}
}
}
}
///
/// 前技能连段技能中,是否拥有后技能
///
private bool IsSkillSequence(Tab_SkillEx a, Tab_SkillEx b)
{
var result = false;
if (a.BaseId == b.BaseId)
result = true;
else
while (a.NextSkillId > 0)
{
var skillEx = TableManager.GetSkillExByID(a.NextSkillId, 0);
if (skillEx == null)
break;
else if (skillEx.BaseId == b.BaseId)
{
result = true;
break;
}
else
a = skillEx;
}
return result;
}
private SkillCastableCheckResult CheckSkillCost(Tab_SkillEx skillEx, int typeIndex)
{
var costType = (SKILLDELANDGAINTYPE)skillEx.GetDelTypebyIndex(typeIndex);
var costValue = skillEx.GetDelNumbyIndex(typeIndex);
return CheckForUseSkillNeed(costType, costValue);
}
private Obj_Character GetSkillTarget(Tab_SkillBase skillBase, Tab_SkillEx skillEx, int overrideTargetType = -1)
{
var targetType = overrideTargetType < 0 ? skillBase.PriorityTarget : overrideTargetType;
var result = ValidClientTarget(targetType);
if (result == null
|| result.IsDisableState(DISABLESTATE.Disable_BeSelect))
{
result = SearchSkillTarget(skillBase, skillEx, targetType);
}
//// 如果是方向攻击技能,需要额外检查是否有更符合方向选择的目标
//else if (IsDirectionalAttack(skillBase) && !IsDirectionalAttackTarget(skillBase, result))
//{
// var candidate = SearchSkillTarget(skillBase, skillEx, targetType);
// if (candidate != null && IsDirectionalAttackTarget(skillBase, candidate))
// result = candidate;
//}
return result;
}
///
/// 检测一个技能是否是方向索敌技能
///
private static bool IsDirectionalAttack(Tab_SkillBase skillBase)
{
return skillBase.SearchEnemyAngle > 0f;
}
///
/// 检测目标是否满足方向条件
///
private bool IsDirectionalAttackTarget(Tab_SkillBase skillBase, Obj_Character target)
{
var result = true;
if (IsDirectionalAttack(skillBase))
{
var targetDirection = target.Position - Position;
targetDirection.y = 0f;
if (targetDirection != Vector3.zero)
result = Vector3.Angle(GetCurrentInputDirectional(), targetDirection) < skillBase.SearchEnemyAngle;
}
return result;
}
private Vector3 GetCurrentInputDirectional()
{
// 获得控制器输入方向
var direction = ProcessInput.Instance == null
? Vector3.zero
: ProcessInput.Instance.GetCurrentInputDirectionInWorld();
// 控制器无输入方向时,使用默认角色方向
if (direction == Vector3.zero)
direction = m_ObjTransform.forward;
return direction;
}
///
/// 开始使用技能
///
public bool ProcessSkill(OwnSkillData ownSkill, Obj_Character target, Vector2 attackPoint = default(Vector2))
{
CleanPendingSkill();
// 配置目标为当前选定目标;
if (target != null)
SetSelectTarget(target);
// 是否可以立刻释放技能
var success = true;
if (target != null)
{
success = ValidSkillRangeForNonDash(ownSkill.ComboExTableFinal, ownSkill.ComboBaseTable, target);
if (success)
{
// 冲锋类技能检查阻挡,检查失败时,会直接执行空放
var moveImpact = ownSkill.ComboExTableFinal.GetMoveSkillImpact();
if (moveImpact != null)
{
if (!CanDashToTarget(moveImpact, target))
{
attackPoint = target.Position.RemoveY() - Position.RemoveY();
target = null;
}
}
}
}
if (success)
StartSkillAction(ownSkill, target, attackPoint);
else
CreatePendingSkill(ownSkill, target);
return success;
}
private bool StartSkillAction(OwnSkillData ownSkill, Obj_Character target,
Vector2 attackPoint = default(Vector2))
{
var skipTime = false;
var skillEx = ownSkill.ComboExTableFinal;
var skillBase = ownSkill.ComboBaseTable;
// 普攻特殊处理连段和跳过前摇条件
if (skillBase.SkillClass.ContainFlag((int)SKILLCLASS.SIMPLEATTACK))
{
// 提高普攻连段数目
NextAttackIndex++;
if (NextAttackIndex > simpleAttackSkills.Count - 1)
NextAttackIndex = 0;
skipTime = Time.time < SkillCore.CurrentSkillTime +
skillEx.ConnectCradleTime.ToClientTime();
}
// 特殊技能自行处理特殊运作规则
SetPredictSkill(ownSkill, skipTime);
// 不允许移动的技能,提前停止角色移动
if (skillBase.IsMove < 1)
{
StopMove();
// Hack:2018.08.17前 - 使用技能后,直接跳过停止帧,阻止Stop协议发出
// 然后在发送技能使用之前发送Stop协议
ResetNavAgentState();
// 2018.08.17按服务器需求,修改为使用技能跳过Stop包
//SendStopToServer();
}
// 特殊:可以移动切移动打断的技能,锁定操作一段时间
else if (skillBase.IsMoveBreak >= 1)
{
LockJoyStick();
}
SendUseSkillPacket(ownSkill, target, attackPoint);
// 必须发送协议后再提升,否则会发送下一段的SkillId
ownSkill.AddCombo();
return true;
}
///
/// 构造一个保证可以检定通过的TargetType
///
///
/// 检查目标是否处于Pk保护状态
///
public bool ValidSkillPkCondition(Tab_SkillEx skillEx, Tab_SkillBase skillBase, Obj_Character target,
bool showWarning = true)
{
return !skillBase.PriorityTarget.ContainFlag(CharacterDefine.SkillBaseTargetType.enemy) ||
ValidPkProtection(target, showWarning);
}
public bool ValidPkProtection(Obj_Character target, bool showWarning = true)
{
var result = true;
if (target.ObjType == GameDefine_Globe.OBJ_TYPE.OBJ_OTHER_PLAYER)
{
var sceneData = TableManager.GetSceneClassByID(GameManager.gameManager.RunningScene, 0);
if (sceneData != null)
{
var pvpRule = TableManager.GetPVPRuleByID(sceneData.PVPRule, 0);
if (pvpRule != null)
if (target.BaseAttr.Level < pvpRule.ProtectLevel)
{
result = false;
if (showWarning)
SendNoticMsg(false, "#{1112}");
}
}
}
return result;
}
///
/// 检查目标是否处于技能射程之内
///
public bool ValidSkillRangeOnTarget(Tab_SkillEx skillEx, Tab_SkillBase skillBase, Obj_Character target,
bool isSearch = false, float rangeReduce = -1f)
{
bool result;
// 搜索范围不考虑寻路模型限制
if (isSearch)
{
if (rangeReduce < 0f)
rangeReduce = 0f;
var distance = DistanceToAnotherEdge(target);
result = distance < skillEx.SearchRadius - rangeReduce;
}
else
{
result = ValidSkillRangeForNonDash(skillEx, skillBase, target, rangeReduce);
if (result)
{
var moveImpact = skillEx.GetMoveSkillImpact();
if (moveImpact != null)
result = CanDashToTarget(moveImpact, target);
}
}
return result;
}
///
/// 检查非冲锋类技能是否可以攻击目标,不作冲锋阻挡判定
///
///
public bool ValidSkillRangeForNonDash(Tab_SkillEx skillEx, Tab_SkillBase skillBase, Obj_Character target, float rangeReduce = -1f)
{
var distance = DistanceToAnotherEdge(target);
if (rangeReduce < 0f)
rangeReduce = skillCastReduce;
// 攻击距离不允许低于1f
return distance < Mathf.Max(1f, skillEx.Radius - rangeReduce);
}
///
/// 检查地形是否允许冲锋类型技能到达目标
///
public bool CanDashToTarget(Tab_Impact moveImpact, Obj_Character target)
{
var result = true;
NavMeshHit hit;
if (NavMesh.Raycast(Position, target.Position, out hit, NavMesh.AllAreas))
{
// 冲锋逻辑额外增加一个冲锋攻击距离
var attackRange = moveImpact.LogicID == GlobeVar.chargeSkillLogicId
? moveImpact.GetBaseParamValuebyIndex(2) * 0.01f
: 0f;
// 如果碰撞位置处于目标半径内,视为允许攻击
result = target.BaseAttr != null && target.BaseAttr.RoleData != null
&& (hit.position - target.Position).RemoveY().sqrMagnitude <
(target.BaseAttr.RoleData.ModelRadius + attackRange).ToSquare();
}
return result;
}
///
/// 按照SkillBase里面的TargetType定义处理角色关系
///
public static bool ValidTargetTypeBySkillBase(Obj_Character character, int targetType)
{
// 非激活角色不能作为目标
// 主角不是宠物,不可能有主人
var result = character.gameObject.activeSelf &&
!targetType.ContainFlag(CharacterDefine.SkillBaseTargetType.master);
// 对齐角色死亡标签
if (result)
result = targetType.ContainFlag(CharacterDefine.SkillBaseTargetType.die) == character.IsDie();
if (result)
if (character.ObjType == GameDefine_Globe.OBJ_TYPE.OBJ_MAIN_PLAYER)
{
result = targetType.ContainFlag(CharacterDefine.SkillBaseTargetType.self);
}
else
{
// 队友标签
result = targetType.ContainFlag(CharacterDefine.SkillBaseTargetType.team) &&
GameManager.gameManager.PlayerDataPool.IsTeamMem(character.GUID);
// 如果不是队友,才进行阵营判定
if (!result)
result = targetType.ContainFlag(CharacterDefine.SkillBaseTargetType.friend) &&
Reputation.IsFriend(character) ||
targetType.ContainFlag(CharacterDefine.SkillBaseTargetType.enemy) &&
Reputation.CanAttack(character);
}
return result;
}
public static TargetSelectOrder GetTargetSelectOrder(Tab_SkillEx skillEx)
{
TargetSelectOrder result;
var selector = skillEx.GetSkillSelector();
if (selector == null)
result = TargetSelectOrder.Distance;
else
{
switch (selector.GetRangParambyIndex(2))
{
case 0:
result = TargetSelectOrder.Distance;
break;
case 1:
result = TargetSelectOrder.Angle;
break;
case 2:
result = TargetSelectOrder.HealthRatio;
break;
case 3:
result = TargetSelectOrder.Health;
break;
default:
result = TargetSelectOrder.Distance;
break;
}
}
return result;
}
public float GetWeightByOrder(Obj_Character character, TargetSelectOrder selectOrder)
{
float result;
switch (selectOrder)
{
case TargetSelectOrder.Distance:
result = DistanceToAnotherEdge(character);
break;
case TargetSelectOrder.Angle:
result = Vector2.Angle(m_ObjTransform.forward.RemoveY(), (character.Position - Position).RemoveY());
break;
case TargetSelectOrder.HealthRatio:
{
var baseAttr = character.BaseAttr;
if (baseAttr == null || baseAttr.CurMaxHP <= 0)
result = float.MaxValue;
else
result = (float)baseAttr.HP / baseAttr.CurMaxHP;
}
break;
case TargetSelectOrder.Health:
{
var baseAttr = character.BaseAttr;
result = baseAttr == null ? float.MaxValue : baseAttr.HP;
}
break;
default:
result = 0f;
break;
}
return result;
}
public Obj_Character SearchSkillTarget(Tab_SkillBase skillBase, Tab_SkillEx skillEx,
int overrideTargetType = -1)
{
Obj_Character result = null;
var targetType = overrideTargetType < 0 ? skillBase.PriorityTarget : overrideTargetType;
var candidates = new List();
if (ValidTargetTypeBySkillBase(this, targetType))
candidates.Add(this);
var targets = Singleton.GetInstance().ObjPools;
foreach (var keyValue in targets)
{
var obj = keyValue.Value as Obj_Character;
if (obj != null && obj != this &&
obj.ServerID != ServerID && !obj.isInvisible &&
!obj.IsDisableState(DISABLESTATE.Disable_BeSelect) &&
ValidTargetTypeBySkillBase(obj, targetType) &&
ValidSkillRangeOnTarget(skillEx, skillBase, obj, true))
candidates.Add(obj);
}
var targetOrder = GetTargetSelectOrder(skillEx);
// 上一个目标权重值
var lastWeight = float.PositiveInfinity;
// 上一个目标是否处于直接距离
var lastInRange = false;
for (var i = 0; i < candidates.Count; i++)
{
var obj = candidates[i];
var inRange = ValidSkillRangeOnTarget(skillEx, skillBase, candidates[i]);
// 优先选择可以原地释放的目标
if (!lastInRange || inRange)
{
var weight = GetWeightByOrder(obj, targetOrder);
if (weight < lastWeight)
{
result = obj;
lastWeight = weight;
lastInRange = inRange;
}
}
}
return result;
}
///
/// 获得普攻技能的Ex
///
public OwnSkillData GetFirstSimpleAttack()
{
OwnSkillData result = null;
SetSimpleAttackId();
if (simpleAttackSkills.Count > 0)
result = simpleAttackSkills[0];
return result;
}
///
/// 获得下一次普攻的技能Ex
///
public OwnSkillData GetNextSimpleAttack()
{
// 注:应该只需要在这个位置初始化普攻Id
// 无论任何逻辑,都不应该在不知道下一刀普攻技能的情况下调用普攻
SetSimpleAttackId();
OwnSkillData result = null;
if (simpleAttackSkills.Count > 0)
{
// 如果是自动连续技,则直接选中第一个普攻
if (simpleAttackSkills[0].SkillBaseTable.SkillClass.ContainFlag((int)SKILLCLASS.AUTOREPEAT))
NextAttackIndex = 0;
// 循环一圈回到第一段
else if (NextAttackIndex >= simpleAttackSkills.Count)
NextAttackIndex = 0;
// 如果是连段攻击,则检查是否满足连段条件
if (NextAttackIndex > 0)
if (IsPredictSkill
&& PredictSkillBase.SkillClass.ContainFlag((int)SKILLCLASS.SIMPLEATTACK)
&& PredictSkill.NextSkillId == simpleAttackSkills[NextAttackIndex].SkillId)
{
// 普攻可连段时间超过
// 超过平砍连接时间
if (Time.time > PredictSkillTime +
PredictSkill.ConnectNextSkillTime.ToClientTime())
NextAttackIndex = 0;
}
// 上次技能不是普攻,重置
else
{
NextAttackIndex = 0;
}
result = simpleAttackSkills[NextAttackIndex];
}
return result;
}
public override void EndSkillByBaseId(int baseId = -1)
{
base.EndSkillByBaseId(baseId);
// 非需要保持预测的技能,就直接关闭
if (PredictSkillBase != null && PredictSkillBase.IsPredict < 1)
RemovePredictByBaseId(PredictSkillBase.Id);
}
private void SendUseSkillPacket(OwnSkillData ownSkill, Obj_Character target, Vector2 attackPoint)
{
//发送技能使用包
// 之前逻辑是在发送技能使用数据包时,计算对应的数据,先保留这个结构
var useSkill = (CG_SKILL_USE)PacketDistributed.CreatePacket(MessageID.PACKET_CG_SKILL_USE);
useSkill.SetSkillId(ownSkill.ComboExTable.SkillExID);
//if (ownSkill.IsComboSkill())
// useSkill.SetFirststagekillid(ownSkill.SkillId);
var skillEx = ownSkill.ComboExTableFinal;
var skillBase = ownSkill.ComboBaseTable;
int dirRadian;
Vector2 worldAttackPoint;
// 在有目标的情况下,攻击位置以目标位置为准
if (target != null)
{
// 目标等于自己时,面朝方向配置为当前面向
if (target == this)
{
worldAttackPoint = Vector2.zero;
dirRadian = CommonUtility.DirToServerRadian(m_ObjTransform.forward.RemoveY());
}
// 目标不等于自己时,配置攻击方向为目标位置
else
{
worldAttackPoint = target.Position.RemoveY();
var attackDir = worldAttackPoint - Position.RemoveY();
dirRadian = CommonUtility.DirToServerRadian(attackDir);
}
}
else
{
// 校正普攻空放和冲锋技能
var isChargeForward = skillEx.MaxMoveDistance > 0 || skillEx.IsMoveSkill();
var isTargetGround = skillEx.IsTargetGround();
// 冲锋类和对地攻击类技能使用目标位置作为攻击点
if (isTargetGround || isChargeForward)
{
var selector = skillEx.GetSkillSelector();
var isLine = selector != null && selector.RangType == 3;
// 直线或者冲锋会补正AttackPoint到最大值
if (isLine || isChargeForward)
{
if (attackPoint == default(Vector2))
attackPoint = IsDirectionalAttack(skillBase)
? GetCurrentInputDirectional().RemoveY()
: ObjTransform.forward.RemoveY();
worldAttackPoint = Position.RemoveY();
// 陷阱类带冲锋属性直接使用落地位置;非陷阱类补正到最大攻击距离;
if (skillEx.MaxMoveDistance > 0)
worldAttackPoint += attackPoint * skillEx.MaxMoveDistance * 0.01f;
else if (selector != null && selector.RangCenter >= 1)
worldAttackPoint += attackPoint;
else
worldAttackPoint += attackPoint.normalized * skillEx.Radius;
// 冲锋额外使用行走区域碰撞进行补正
if (isChargeForward)
{
NavMeshHit hit;
if (NavMesh.Raycast(Position, worldAttackPoint.InsertY(Position.y), out hit,
NavMesh.AllAreas))
worldAttackPoint = hit.position.RemoveY();
}
}
else
worldAttackPoint = Position.RemoveY() + attackPoint;
var dir = worldAttackPoint - Position.RemoveY();
if (dir == default(Vector2))
dir = attackPoint;
dirRadian = CommonUtility.DirToServerRadian(dir);
}
// 非陷阱非冲锋不上传攻击目标点
else
{
worldAttackPoint = Vector2.zero;
if (attackPoint == default(Vector2))
attackPoint = IsDirectionalAttack(skillBase)
? GetCurrentInputDirectional().RemoveY()
: ObjTransform.forward.RemoveY();
dirRadian = CommonUtility.DirToServerRadian(attackPoint);
}
}
// 快速处理一个关于治疗链的问题留下的坑。
// 当前目标为自己时,不发送目标
if (target == this)
target = null;
useSkill.SetDesposx(Mathf.FloorToInt(worldAttackPoint.x * 100f));
useSkill.SetDespoxz(Mathf.FloorToInt(worldAttackPoint.y * 100f));
useSkill.SetFacedir(dirRadian);
useSkill.SetTargetId(target == null ? 0 : target.ServerID);
useSkill.SetRoleposx(Position.x.ToServerPos());
useSkill.SetRoleposy(Position.y.ToServerPos());
useSkill.SetRolepoxz(Position.z.ToServerPos());
useSkill.SendPacket();
}
///
/// 计算两个角色之间的距离,会减去对方体型半径,不减自己半径
///
// 该方法仅主角可以使用,检查两个非主角应该都是用ServerPos
public float DistanceToAnotherEdge(Obj_Character character)
{
var result = Vector2.Distance(character.Position.RemoveY(), Position.RemoveY());
if (character.BaseAttr != null && character.BaseAttr.RoleData != null)
result = Mathf.Max(0f, result - character.BaseAttr.RoleData.ModelRadius);
return result;
}
#region 寻路到目标再释放技能
private bool CreatePendingSkill(OwnSkillData ownSkill, Obj_Character target)
{
var result = false;
// 自动战斗期间,不允许叠加其他自动逻辑
// 原始结构修改需要改一些架构,暂时用这个标志位检查处理
if (!isAutoCombat)
{
// 特殊处理 - 如果是普攻技能,降低到第一段普攻的距离标准
if (ownSkill.SkillBaseTable.SkillClass.ContainFlag((int)SKILLCLASS.SIMPLEATTACK))
ownSkill = GetFirstSimpleAttack();
if (MainPlayMoveToTarget(target, new MoveToTargetTillSkill(ownSkill),
new MoveSuccessToUseSkill(this, ownSkill)))
{
result = true;
PendingSkillEx = ownSkill.ComboExTable;
}
else
{
CleanPendingSkill();
}
}
return result;
}
// 注:PendingSkill使用不修正的SkillEx
public Tab_SkillEx PendingSkillEx { get; private set; }
// 注:打断行为的操作,应该同时覆盖行为
private void CleanPendingSkill()
{
PendingSkillEx = null;
}
#endregion
// 注:主角除了SkillCore以外,自身会额外预判技能使用条件
// 自身预判依照技能协议被发出时机为准,然后使用协议返回数值予以校正
// 因此玩家连招输入频率不受到稳定ping值的影响
// 预判断仅仅影响玩家输入指令,不影响角色对指令的响应
#region 主角预判断技能状态记录
// 预判断使用的技能
// 预判使用等级修正后技能
public Tab_SkillEx PredictSkill { get; private set; }
// 预判断使用的技能BaseInfo
public Tab_SkillBase PredictSkillBase { get; private set; }
// 预判断技能开始时间
public float PredictSkillTime { get; private set; }
// 是否预判断使用技能
public bool IsPredictSkill
{
get
{
return PredictSkill != null &&
PredictSkillTime + SkillDuration(PredictSkill, PredictSkillBase) > Time.time;
}
}
private void SetPredictSkill(OwnSkillData ownSkill, bool skipWindup)
{
PredictSkillTime = Time.time - (skipWindup ? ownSkill.ComboExTableFinal.CradleTime.ToClientTime() : 0f);
PredictSkill = ownSkill.ComboExTableFinal;
PredictSkillBase = ownSkill.ComboBaseTable;
}
private void RemovePredictByBaseId(int baseId = -1)
{
if (PredictSkill != null && (baseId < 0 || baseId == PredictSkill.BaseId))
{
PredictSkill = null;
PredictSkillBase = null;
}
}
///
/// 技能状态是否允许移动
///
// 技能衔接移动的条件,由SkillCore为准;Predict数值仅保证协议返回前不被主角打断
public override bool SkillAllowMove
{
get
{
if (FollowState == TeamFollowState.followMove)
{
return base.SkillAllowMove;
}
var result = !IsUsingSkill || SkillCore.CurrentSkillBase.IsMove > 0 || Time.time >
SkillToSimpleDelay(SkillCore.CurrentSkill, SkillCore.CurrentSkillBase) +
SkillCore.CurrentSkillTime;
if (result)
result = PredictSkill == null || PredictSkillBase.IsMove > 0 || Time.time >
SkillToSimpleDelay(PredictSkill, PredictSkillBase) + PredictSkillTime;
return result;
}
}
///
/// 技能状态是否允许角色进行普攻
///
public bool SkillAllowSimpleAttack
{
get
{
if (IsPredictSkill)
if (PredictSkillBase.IsMove > 0)
return false;
else
return Time.time > SkillToSimpleDelay(PredictSkill, PredictSkillBase) + PredictSkillTime;
return true;
}
}
///
/// 技能状态是否允许角色释放技能
///
public bool SkillAllowSkill
{
get
{
if (IsPredictSkill)
return Time.time > SkillToSkillDelay(PredictSkill, PredictSkillBase) + PredictSkillTime;
return true;
}
}
#endregion
}
///
/// 技能是否可以释放检查结果
///
public enum SkillCastableCheckResult
{
Success, // 可以释放
FailCasterDeath, // 技能释放者已死亡
FailCurrent, // 已经预测当前技能
FailNoData, // 技能数据无法找到
FailCooldown, // 技能处于CD中
FailDisable, // 角色不能释放技能
FailSelect, // 角色不能选定目标
FailOtherSkill, // 其他技能阻止释放
FailNoCore, // 没有SkillCore
FailNoOwnSkill, // 没有服务器返回的技能注册
FailMission, // 任务期间不允许使用该技能
FailNoTarget, // 单体选定技能没有目标的情况
FailAutoRepeat, // 自动连续技能不允许打断自己的后续
FailPkProtection, // 不允许攻击PK保护的目标
FailCostHp, // 技能生命消耗不足
FailCostHpRate, // 技能生命百分比消耗不足
FailCostMp, // 技能法力消耗不足
FailCostMpRate, // 技能法力百分比消耗不足
FailCostXp, // 技能Xp消耗不足
FailCostXpRate, // 技能Xp百分比消耗不足
FailMovement, // 特殊移动状态不允许使用技能
FailScene, // 当前场景不允许释放技能
FailOther // 其他情况
}
///
/// 技能目标选择顺序
///
public enum TargetSelectOrder
{
Distance, // 最小距离优先
Angle, // 最小角度优先
HealthRatio, // 生命百分比最低优先
Health, // 生命绝对值最低优先
}
}