1115 lines
48 KiB
C#
1115 lines
48 KiB
C#
|
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
|
|||
|
{
|
|||
|
/// <summary>
|
|||
|
/// 主角普攻和技能预判断模块
|
|||
|
/// </summary>
|
|||
|
public partial class Obj_MainPlayer
|
|||
|
{
|
|||
|
// 自动寻路并且使用技能时,释放距离减少的长度
|
|||
|
public const float skillCastOverwalk = skillCastReduce + 0.2f;
|
|||
|
|
|||
|
// 客户端释放技能判定减少数值,注:普攻AoE类型技能无法用服务器补偿距离,因此统一客户端减损距离
|
|||
|
public const float skillCastReduce = 0.5f;
|
|||
|
|
|||
|
public readonly List<OwnSkillData> simpleAttackSkills = new List<OwnSkillData>();
|
|||
|
|
|||
|
// 缓存跑商时禁用的技能表
|
|||
|
private int[] _skillsBannedByEscort;
|
|||
|
|
|||
|
public int NextAttackIndex { get; private set; }
|
|||
|
|
|||
|
//按顺序存储所有普攻 策划需求普攻技能ID顺序不固定
|
|||
|
private void SetSimpleAttackId()
|
|||
|
{
|
|||
|
if (simpleAttackSkills.Count < 1)
|
|||
|
{
|
|||
|
var simpleSkills = new List<OwnSkillData>();
|
|||
|
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();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 试图使用一个普攻技能
|
|||
|
/// </summary>
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 使用失败的技能,是否可以立刻被再次使用
|
|||
|
/// </summary>
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 使用一个技能失败的接口
|
|||
|
/// </summary>
|
|||
|
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<AI_PlayerCombat>();
|
|||
|
if (aiPlayerCombat != null)
|
|||
|
aiPlayerCombat.AddSkillFailRecord(skillEx.BaseId, canBeUsed);
|
|||
|
}
|
|||
|
}
|
|||
|
RemovePredictByBaseId(skillEx.BaseId);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 试图使用一个目标指向技能
|
|||
|
/// </summary>
|
|||
|
/// <param name="ownSkill">技能数据</param>
|
|||
|
/// <param name="attackPoint">技能目标位置</param>
|
|||
|
/// <param name="breakAutoCombat">是否打断自动战斗</param>
|
|||
|
/// <param name="target">攻击目标</param>
|
|||
|
/// <returns>技能是否使用成功</returns>
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 检查一个技能是否可以被释放;该检查无视角色是否可以释放技能;
|
|||
|
/// </summary>
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 检查角色是否处于能够释放技能的状态;该检查无视技能本身是否可以释放;
|
|||
|
/// </summary>
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 检查角色是否可以释放某个技能;相当于CheckCanCastSkill + CheckSkillCanBeCasted;
|
|||
|
/// </summary>
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 更新技能CD数值
|
|||
|
/// </summary>
|
|||
|
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();
|
|||
|
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 前技能连段技能中,是否拥有后技能
|
|||
|
/// </summary>
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 检测一个技能是否是方向索敌技能
|
|||
|
/// </summary>
|
|||
|
private static bool IsDirectionalAttack(Tab_SkillBase skillBase)
|
|||
|
{
|
|||
|
return skillBase.SearchEnemyAngle > 0f;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 检测目标是否满足方向条件
|
|||
|
/// </summary>
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 开始使用技能
|
|||
|
/// </summary>
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 构造一个保证可以检定通过的TargetType
|
|||
|
/// </summary>
|
|||
|
/// <summary>
|
|||
|
/// 检查目标是否处于Pk保护状态
|
|||
|
/// </summary>
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 检查目标是否处于技能射程之内
|
|||
|
/// </summary>
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 检查非冲锋类技能是否可以攻击目标,不作冲锋阻挡判定
|
|||
|
/// </summary>
|
|||
|
/// <returns></returns>
|
|||
|
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);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 检查地形是否允许冲锋类型技能到达目标
|
|||
|
/// </summary>
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 按照SkillBase里面的TargetType定义处理角色关系
|
|||
|
/// </summary>
|
|||
|
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<Obj_Character>();
|
|||
|
if (ValidTargetTypeBySkillBase(this, targetType))
|
|||
|
candidates.Add(this);
|
|||
|
var targets = Singleton<ObjManager>.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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 获得普攻技能的Ex
|
|||
|
/// </summary>
|
|||
|
public OwnSkillData GetFirstSimpleAttack()
|
|||
|
{
|
|||
|
OwnSkillData result = null;
|
|||
|
SetSimpleAttackId();
|
|||
|
if (simpleAttackSkills.Count > 0)
|
|||
|
result = simpleAttackSkills[0];
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 获得下一次普攻的技能Ex
|
|||
|
/// </summary>
|
|||
|
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();
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 计算两个角色之间的距离,会减去对方体型半径,不减自己半径
|
|||
|
/// </summary>
|
|||
|
// 该方法仅主角可以使用,检查两个非主角应该都是用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;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 技能状态是否允许移动
|
|||
|
/// </summary>
|
|||
|
// 技能衔接移动的条件,由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;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 技能状态是否允许角色进行普攻
|
|||
|
/// </summary>
|
|||
|
public bool SkillAllowSimpleAttack
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
if (IsPredictSkill)
|
|||
|
if (PredictSkillBase.IsMove > 0)
|
|||
|
return false;
|
|||
|
else
|
|||
|
return Time.time > SkillToSimpleDelay(PredictSkill, PredictSkillBase) + PredictSkillTime;
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 技能状态是否允许角色释放技能
|
|||
|
/// </summary>
|
|||
|
public bool SkillAllowSkill
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
if (IsPredictSkill)
|
|||
|
return Time.time > SkillToSkillDelay(PredictSkill, PredictSkillBase) + PredictSkillTime;
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 技能是否可以释放检查结果
|
|||
|
/// </summary>
|
|||
|
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 // 其他情况
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// 技能目标选择顺序
|
|||
|
/// </summary>
|
|||
|
public enum TargetSelectOrder
|
|||
|
{
|
|||
|
Distance, // 最小距离优先
|
|||
|
Angle, // 最小角度优先
|
|||
|
HealthRatio, // 生命百分比最低优先
|
|||
|
Health, // 生命绝对值最低优先
|
|||
|
}
|
|||
|
}
|