2024-12-15 01:39:39 +00:00
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
public class MyActor : Actor
|
|
|
|
|
{
|
|
|
|
|
protected float f_FindEnemyRange;
|
|
|
|
|
protected Actor OwnerActor;
|
2025-05-06 22:41:40 +00:00
|
|
|
protected bool isFollowOwner;
|
2024-12-15 01:39:39 +00:00
|
|
|
|
2026-09-06 23:24:25 +00:00
|
|
|
#region PD 지시 #770 — 플레이어 타겟팅 규칙 (수치·스위치 SOT = WLTargetingSettings 에셋 · C45)
|
|
|
|
|
// 1. 공격 시작 시점에 자신과 가장 가까운 대상(바라보는 방향 우선)을 타겟팅
|
|
|
|
|
// 2. 콤보 중 적이 사망하면 그 시점에 가장 가까운 적으로 재타겟
|
|
|
|
|
// 3. 타겟을 바꿔도 이동부터 하지 않고 사거리 안이면 즉시 공격
|
|
|
|
|
// 4. 피격 어그로 금지 (자신을 때린 대상 우선 공격 금지)
|
|
|
|
|
// 에셋 = Assets/WL/Settings/Resources/WL/WLTargetingSettings.asset
|
|
|
|
|
|
|
|
|
|
/// <summary>#770 규칙 적용 대상인지. PC 만 적용하고 펫·소환수는 기존 동작(거리만·피격 어그로)을 유지한다.</summary>
|
|
|
|
|
protected bool UseWLTargeting() { return IsRole(eRole.PC); }
|
|
|
|
|
|
|
|
|
|
/// <summary>재타겟 탐색 거리. 설정이 0 이면 기존 f_FindEnemyRange(table_GlobalValue + AttackRange_Up)를 쓴다.</summary>
|
|
|
|
|
float Get_WLFindRange()
|
|
|
|
|
{
|
|
|
|
|
if (!UseWLTargeting()) return f_FindEnemyRange;
|
|
|
|
|
var r = WL.Settings.WLTargetingSettings.RetargetSearchRange;
|
|
|
|
|
return r > 0f ? r : f_FindEnemyRange;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>수평면 기준, 캐릭터 정면과 대상 방향 사이의 각도(도).</summary>
|
|
|
|
|
float Get_FacingAngle(Vector3 targetPos)
|
|
|
|
|
{
|
|
|
|
|
var dir = targetPos - Get_position(); dir.y = 0f;
|
|
|
|
|
if (dir.sqrMagnitude < 0.0001f) return 0f;
|
|
|
|
|
var fwd = transform.forward; fwd.y = 0f;
|
|
|
|
|
if (fwd.sqrMagnitude < 0.0001f) return 0f;
|
|
|
|
|
return Vector3.Angle(fwd, dir);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 지금 이 자리에서 대상을 때릴 수 있는 거리(m).
|
|
|
|
|
/// Update() 의 정지 거리 계산(m_NavStopDistance + AttackRange_Up · 상대 반경 보정 · 상대 크기 보정)과
|
|
|
|
|
/// 같은 식이며 설정의 여유(immediateAttackMargin · #770-3)를 더한다.
|
|
|
|
|
/// </summary>
|
|
|
|
|
float Get_AttackReach(Actor target)
|
|
|
|
|
{
|
|
|
|
|
float stop = m_NavStopDistance + (float)m_Stat.Get_Stat(eStat.AttackRange_Up);
|
|
|
|
|
var agent = target.Get_Agent();
|
|
|
|
|
if (!DSUtil.CheckNull(agent) && agent.radius > stop) stop = agent.radius + 0.5f;
|
|
|
|
|
return stop + target.Get_ScaleSizeDist() + WL.Settings.WLTargetingSettings.ImmediateAttackMargin;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// #770-1 정면 우선 + 최근접 선택.
|
|
|
|
|
/// 점수 = 거리 x (1 + facingWeight x (1 - cosθ)) 가 가장 낮은 후보를 고른다.
|
|
|
|
|
/// 정면 원뿔(frontConeDeg) 안에 후보가 하나라도 있으면 그 안에서만 고르고, 없으면 전체에서 고른다.
|
|
|
|
|
/// inReachOnly = true 면 "지금 이동 없이 때릴 수 있는" 후보만 본다(#770-1 공격 시작 시점 · #770-3).
|
|
|
|
|
/// </summary>
|
|
|
|
|
Actor Find_Target_Facing(float searchRange, bool inReachOnly)
|
|
|
|
|
{
|
|
|
|
|
if (DSUtil.CheckNull(ActorInfo.Ins)) return null;
|
|
|
|
|
|
|
|
|
|
var actors = ActorInfo.Ins.Get_EnemyActors(IsEnemy(), Get_position(), searchRange);
|
|
|
|
|
Actor best = null;
|
|
|
|
|
float bestScore = float.MaxValue;
|
|
|
|
|
float halfCone = WL.Settings.WLTargetingSettings.FrontConeDeg * 0.5f;
|
|
|
|
|
float weight = WL.Settings.WLTargetingSettings.FacingWeight;
|
|
|
|
|
bool useCone = halfCone > 0f && halfCone < 180f;
|
|
|
|
|
|
|
|
|
|
for (int pass = 0; pass < 2; pass++)
|
|
|
|
|
{ // pass 0 = 정면 원뿔 안에서만 · pass 1 = 전체(원뿔 안에 후보가 없을 때만 돈다)
|
|
|
|
|
for (int i = 0; i < actors.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
var a = actors[i];
|
|
|
|
|
if (a == null) continue;
|
|
|
|
|
|
|
|
|
|
float dist = Vector3.Distance(Get_position(), a.Get_position());
|
|
|
|
|
if (inReachOnly && dist > Get_AttackReach(a)) continue;
|
|
|
|
|
|
|
|
|
|
float angle = Get_FacingAngle(a.Get_position());
|
|
|
|
|
if (pass == 0 && useCone && angle > halfCone) continue;
|
|
|
|
|
|
|
|
|
|
float score = dist * (1f + weight * (1f - Mathf.Cos(angle * Mathf.Deg2Rad)));
|
|
|
|
|
if (score < bestScore) { bestScore = score; best = a; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (best != null) break; // 원뿔 안에서 찾았으면 끝
|
|
|
|
|
if (!useCone) break; // 원뿔 제한이 없으면 2회전은 같은 결과다
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ActorInfo.Ins.Release_Actors(actors);
|
|
|
|
|
return best;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>수평 회전만으로 대상을 바라본다(콤보를 끊지 않고 방향만 돌릴 때 · #770-2).</summary>
|
|
|
|
|
void LookAt_Horizontal(Vector3 pos)
|
|
|
|
|
{
|
|
|
|
|
var dir = pos - Get_position(); dir.y = 0f;
|
|
|
|
|
if (dir.sqrMagnitude < 0.0001f) return;
|
|
|
|
|
transform.rotation = Quaternion.LookRotation(dir);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// #770-1 "공격 시작 시점" 재타겟. 이동 없이 때릴 수 있는 거리 안에서만 다시 고르며,
|
|
|
|
|
/// 그 안에 아무도 없으면 기존 타겟을 그대로 둔다(달려가던 대상을 잃지 않게).
|
|
|
|
|
/// </summary>
|
|
|
|
|
void Retarget_InReach()
|
|
|
|
|
{
|
|
|
|
|
if (isTarget && m_Target.m_IsLockTarget && !m_Target.IsDead()) return; // 락 타겟 유지
|
|
|
|
|
|
|
|
|
|
var found = Find_Target_Facing(Get_WLFindRange(), true);
|
|
|
|
|
if (found == null || found == m_Target) return;
|
|
|
|
|
|
|
|
|
|
if (isTarget && IsMainPC()) m_Target.Set_Outline(false);
|
|
|
|
|
m_Target = found;
|
|
|
|
|
if (IsMainPC()) m_Target.Set_Outline(true);
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
2025-01-24 05:28:12 +00:00
|
|
|
public override void Set_Target(bool forcefind = false)
|
2024-12-15 01:39:39 +00:00
|
|
|
{
|
2025-02-08 22:22:31 +00:00
|
|
|
if (isTarget)
|
2024-12-15 01:39:39 +00:00
|
|
|
{
|
|
|
|
|
if (m_Target.m_IsLockTarget && !m_Target.IsDead()) return;
|
2025-05-06 07:05:38 +00:00
|
|
|
if (IsMainPC()) m_Target.Set_Outline(false);
|
2024-12-15 01:39:39 +00:00
|
|
|
}
|
|
|
|
|
|
2026-09-06 23:24:25 +00:00
|
|
|
// #770-1 : PC 는 "정면 우선 + 가장 가까운" 대상을 고른다. 펫·소환수는 기존(거리만) 그대로.
|
|
|
|
|
float range = forcefind ? float.MaxValue : Get_WLFindRange();
|
|
|
|
|
m_Target = UseWLTargeting()
|
|
|
|
|
? Find_Target_Facing(range, false)
|
|
|
|
|
: ActorInfo.Ins.Get_Nearest_EnemyActor_orNull(IsEnemy(), Get_position(), forcefind ? float.MaxValue : f_FindEnemyRange);
|
2024-12-15 01:39:39 +00:00
|
|
|
|
2025-02-08 22:22:31 +00:00
|
|
|
if (isTarget)
|
2024-12-15 01:39:39 +00:00
|
|
|
{
|
2025-05-06 07:05:38 +00:00
|
|
|
if (IsMainPC())
|
2024-12-15 01:39:39 +00:00
|
|
|
{
|
|
|
|
|
m_Target.Set_Outline(true);
|
|
|
|
|
//MonsterSimpleInfo.Ins.Set(m_Target);
|
|
|
|
|
//PCInfo.Ins.Get_PetsWithDie(m_Enemy).ForEach(f => f.Add_FindEnemy(m_Target.m_Role, m_Target.Get_Group()));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{ // 모두 하나의 적만 노리지 않게 타겟 주위의 몹을 타겟
|
|
|
|
|
//var enemies = DSUtil.Get_Enemies(IsEnemy(), m_Target.Get_position(), 3f, Get_EnemyList());
|
|
|
|
|
//if (enemies != null && enemies.Count > 0) m_Target = enemies[Random.Range(0, enemies.Count)];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public override void Change_Target(bool _ispc, Actor target, bool _ChangeTargetByHit = false)
|
|
|
|
|
{
|
|
|
|
|
base.Change_Target(_ispc, target, _ChangeTargetByHit);
|
|
|
|
|
//if (target) MonsterSimpleInfo.Ins.Set(target);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void Update()
|
|
|
|
|
{
|
|
|
|
|
if (IsDead()) return;
|
|
|
|
|
m_attackCoolTime -= Time.deltaTime;
|
|
|
|
|
if (m_HitTime > 0f)
|
|
|
|
|
{
|
|
|
|
|
m_HitTime -= Time.deltaTime;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (IsStop || IsCC() || (GameUI.Ins && GameUI.Ins.IngameUI.Active_SelectSkillUI())) return;
|
2025-05-06 22:41:40 +00:00
|
|
|
if (isFollowOwner)
|
|
|
|
|
{
|
|
|
|
|
if (Complete_Destination())
|
|
|
|
|
{
|
|
|
|
|
Play_Idle();
|
|
|
|
|
isFollowOwner = false;
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-12-15 01:39:39 +00:00
|
|
|
|
|
|
|
|
base.Update();
|
2026-09-06 23:24:25 +00:00
|
|
|
|
2024-12-15 01:39:39 +00:00
|
|
|
Check_Target();
|
|
|
|
|
|
2026-09-06 23:24:25 +00:00
|
|
|
// #770-2 : 타겟이 죽는 즉시(공격 애니메이션 재생 중이어도) 가장 가까운 적으로 다시 타겟팅한다.
|
|
|
|
|
// m_Auto 와 무관하다 — 공격 버튼만 쓰는 계정도 콤보가 끊기면 안 된다.
|
|
|
|
|
if (isTarget && m_Target.IsDead() && UseWLTargeting() && WL.Settings.WLTargetingSettings.RetargetOnKill)
|
|
|
|
|
{
|
|
|
|
|
Set_Target();
|
|
|
|
|
// 콤보 재생 중이면 애니메이션을 끊지 않고 몸만 돌린다 — 다음 타격 판정이 새 타겟 쪽으로 나간다.
|
|
|
|
|
if (isTarget && CurAnim >= eAnim.Attack && CurAnim <= eAnim.Attack4)
|
|
|
|
|
LookAt_Horizontal(m_Target.Get_position());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// #787 : 대쉬 중에는 접근/공격 판정을 건너뛴다. DashDriver 가 이동을 쥐고 있고
|
|
|
|
|
// 도착하면 스스로 Play_Attack 을 건다. 여기서 Play_Run 을 다시 부르면
|
|
|
|
|
// NavMesh 경로가 생겨 대쉬 이동과 싸운다(위 재타겟 처리는 그대로 돈다).
|
|
|
|
|
if (WL.Combat.DashDriver.IsDashingNow(this)) return;
|
|
|
|
|
|
2025-02-08 22:22:31 +00:00
|
|
|
if (isTarget)
|
2024-12-15 01:39:39 +00:00
|
|
|
{
|
|
|
|
|
if (m_Target.IsDead())
|
|
|
|
|
Del_Target();
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (IsNotPC() && Vector3.Distance(OwnerActor.Get_position(), Get_position()) > 7)
|
|
|
|
|
{
|
|
|
|
|
Del_Target();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-14 06:51:57 +00:00
|
|
|
if (CurAnim < eAnim.Attack)
|
|
|
|
|
{
|
|
|
|
|
if (DSUtil.CheckNull(m_Magic))
|
|
|
|
|
m_NavMeshAgent.stoppingDistance = m_NavStopDistance + (float)m_Stat.Get_Stat(eStat.AttackRange_Up);
|
|
|
|
|
else
|
|
|
|
|
m_NavMeshAgent.stoppingDistance = m_Magic.m_SkillTableData.f_SkillRange;
|
|
|
|
|
|
2025-08-09 13:10:17 +00:00
|
|
|
// 상대 크기 보정해서 공격할 수 있게 함
|
|
|
|
|
if (m_Target.Get_Agent().radius > m_NavMeshAgent.stoppingDistance)
|
|
|
|
|
m_NavMeshAgent.stoppingDistance = m_Target.Get_Agent().radius + 0.5f;
|
|
|
|
|
|
2026-09-06 23:24:25 +00:00
|
|
|
// #770-3 : 사거리(+여유) 안이면 접근하지 않고 즉시 공격한다.
|
|
|
|
|
// 정지 거리보다 실제 타격 박스가 조금 길어서 생기던 "한 걸음 다가갔다 때리기"를 없앤다.
|
|
|
|
|
float reach = m_NavMeshAgent.stoppingDistance + m_Target.Get_ScaleSizeDist();
|
|
|
|
|
if (UseWLTargeting() && DSUtil.CheckNull(m_Magic))
|
|
|
|
|
reach += WL.Settings.WLTargetingSettings.ImmediateAttackMargin;
|
|
|
|
|
|
|
|
|
|
float distToTarget = Vector3.Distance(Get_position(), m_Target.Get_position());
|
|
|
|
|
if (distToTarget > reach)
|
|
|
|
|
{
|
|
|
|
|
// #790 : 사정거리 안에 다른 적이 있으면 대쉬/달리기 대신 그 적을 즉시 공격한다(대쉬 남발 방지).
|
|
|
|
|
if (UseWLTargeting() && DSUtil.CheckNull(m_Magic) && WL.Combat.WLCombatMotionSettings.DashBlockIfEnemyInReach)
|
|
|
|
|
{
|
|
|
|
|
var nearInReach = Find_Target_Facing(Get_WLFindRange(), true);
|
|
|
|
|
if (nearInReach != null && nearInReach != m_Target)
|
|
|
|
|
{
|
|
|
|
|
if (IsMainPC()) m_Target.Set_Outline(false);
|
|
|
|
|
m_Target = nearInReach;
|
|
|
|
|
if (IsMainPC()) m_Target.Set_Outline(true);
|
|
|
|
|
if (CurAnim != eAnim.Skill && CurAnim != eAnim.Dodge)
|
|
|
|
|
Play_Attack(0, (float)m_Stat.Get_Stat(eStat.FinalAttackSpeed));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// #787 : 중간 거리 창([dashMin, dashMax]) 안이면 대쉬로 붙고 도착 즉시 공격한다.
|
|
|
|
|
// 창 밖(너무 멀다)이거나 대쉬 조건이 안 맞으면 기존 달리기 추적 그대로.
|
|
|
|
|
// 타겟은 여기서 이미 정해져 있다 — 대쉬는 타겟을 고르지 않는다(어그로 금지).
|
|
|
|
|
if (!(UseWLTargeting() && DSUtil.CheckNull(m_Magic) &&
|
|
|
|
|
WL.Combat.DashDriver.TryBegin(this as PCActor, m_Target, distToTarget, reach)))
|
|
|
|
|
Play_Run();
|
|
|
|
|
}
|
2025-01-14 06:51:57 +00:00
|
|
|
else if (!DSUtil.CheckNull(m_Magic))
|
2025-05-03 23:06:28 +00:00
|
|
|
{
|
|
|
|
|
if (!Use_Skill(m_Magic.m_SkillTableData, false, m_Magic.m_uiIndex))
|
|
|
|
|
Play_Idle();
|
|
|
|
|
}
|
2025-01-14 06:51:57 +00:00
|
|
|
//else if (CurAnim != eAnim.Attack && m_attackCoolTime > 0f)
|
|
|
|
|
// Play_Idle();
|
|
|
|
|
else if (CurAnim != eAnim.Skill && CurAnim != eAnim.Dodge)
|
|
|
|
|
Play_Attack(0, (float)m_Stat.Get_Stat(eStat.FinalAttackSpeed));
|
|
|
|
|
}
|
2024-12-15 01:39:39 +00:00
|
|
|
}
|
|
|
|
|
}
|
2025-02-11 00:15:02 +00:00
|
|
|
else if (CurAnim != eAnim.Run && CurAnim < eAnim.Dodge)
|
2025-01-27 04:24:29 +00:00
|
|
|
Play_Idle();
|
2024-12-15 01:39:39 +00:00
|
|
|
}
|
|
|
|
|
protected virtual void Update_Move()
|
|
|
|
|
{
|
|
|
|
|
if (m_fAttackMoveTime > 0f)
|
|
|
|
|
{
|
|
|
|
|
m_fAttackMoveTime -= Time.deltaTime;
|
|
|
|
|
m_NavMeshAgent.stoppingDistance = 0;
|
|
|
|
|
if (Complete_Destination() || m_fAttackMoveTime <= 0f)
|
|
|
|
|
{
|
|
|
|
|
m_fAttackMoveTime = 0f;
|
|
|
|
|
if (!DSUtil.CheckNull(act_MoveEnd))
|
|
|
|
|
{
|
|
|
|
|
m_bCanDodge = false;
|
|
|
|
|
Play_Run();
|
|
|
|
|
act_MoveEnd(true);
|
|
|
|
|
act_MoveEnd(false);
|
|
|
|
|
IsStop = false;
|
|
|
|
|
act_MoveEnd = null;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
m_NavMeshAgent.angularSpeed = 300f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
protected void Follow_PC()
|
|
|
|
|
{
|
2025-05-06 22:41:40 +00:00
|
|
|
if (isFollowOwner)
|
|
|
|
|
{
|
2026-09-06 23:24:25 +00:00
|
|
|
|
2025-05-06 22:41:40 +00:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
isFollowOwner = true;
|
|
|
|
|
Del_Target(true);
|
|
|
|
|
m_NavMeshAgent.stoppingDistance = 1f;
|
|
|
|
|
Play_Run();
|
|
|
|
|
Set_Path(OwnerActor.Get_position());
|
|
|
|
|
}
|
2024-12-15 01:39:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsNotPC() { return !IsRole(eRole.PC); }
|
|
|
|
|
|
|
|
|
|
public override void Get_Damage(DamageInfo _dinfo)
|
|
|
|
|
{
|
2026-09-06 23:24:25 +00:00
|
|
|
// PD 지시 #765 (임시) — 테스트 편의용 플레이어 무적. 값은 설정 에셋에서 읽는다(C45).
|
|
|
|
|
// 끄는 법: Assets/WL/Settings/Resources/WL/WLGameplaySettings.asset 의 playerInvincible 체크 해제.
|
|
|
|
|
// #767 (2026-09-06 회귀 수정): 무적은 '피해'만 건너뛴다. 아래 "피격 시 타겟 획득" 은 자동 공격의 시작점이라
|
|
|
|
|
// 무적이어도 그대로 돌아야 한다 — 가드가 함수 맨 앞에서 return 하던 동안 플레이어가 공격을 시작하지 못했다.
|
|
|
|
|
bool wlInvincible = IsRole(eRole.PC) && WL.Settings.WLGameplaySettings.PlayerInvincible;
|
|
|
|
|
if (!wlInvincible) base.Get_Damage(_dinfo);
|
2026-09-08 14:30:57 +00:00
|
|
|
WL.Combat.Core.CombatEvents.RaiseDamaged(this, _dinfo, wlInvincible);
|
2026-09-06 23:24:25 +00:00
|
|
|
|
|
|
|
|
if (_dinfo == null || DSUtil.CheckNull(_dinfo.Beater)) return;
|
|
|
|
|
|
|
|
|
|
// PD 지시 #770-4 (2026-09-06): 플레이어 자동 공격 AI 에 "나를 때린 대상을 우선 공격"(어그로) 개념이
|
|
|
|
|
// 있어서는 안 된다 → PC 는 피격으로 타겟을 잡지도, 바꾸지도 않는다. 펫·소환수는 기존 동작 유지.
|
|
|
|
|
// 🔴 귀결: Auto 가 꺼진 계정은 맞아도 스스로 공격을 시작하지 않는다(#767 이 이 경로를 시작점으로 쓰고 있었다).
|
|
|
|
|
// 공격 버튼(BattleUI.OnClick_Attack → Set_Target) 또는 Auto ON 이 타겟 획득 경로다.
|
|
|
|
|
// 되돌리는 법: WLTargetingSettings.asset 의 pcAggroOnHit 체크.
|
|
|
|
|
if (UseWLTargeting() && !WL.Settings.WLTargetingSettings.PcAggroOnHit) return;
|
|
|
|
|
|
2025-02-08 22:22:31 +00:00
|
|
|
if (!isTarget || m_Target.IsDead())
|
2024-12-15 01:39:39 +00:00
|
|
|
{
|
2025-05-06 22:41:40 +00:00
|
|
|
Change_Target(false, _dinfo.Beater, true);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var dist1 = DSUtil.Get_Distance(m_Target.Get_position(), Get_position());
|
|
|
|
|
var dist2 = DSUtil.Get_Distance(_dinfo.Beater.Get_position(), Get_position());
|
|
|
|
|
if (dist1 > dist2) Change_Target(false, _dinfo.Beater, true);
|
2024-12-15 01:39:39 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void Play_Run()
|
|
|
|
|
{
|
|
|
|
|
base.Play_Run();
|
2025-02-08 22:22:31 +00:00
|
|
|
if (isTarget) Set_Path(m_Target.Get_position());
|
|
|
|
|
//if (isTarget)
|
2025-01-14 06:51:57 +00:00
|
|
|
//{
|
|
|
|
|
// if (!Set_Path(m_Target.Get_position()))
|
|
|
|
|
// { // 적이 내비메시 위에 없다고 판단됨
|
|
|
|
|
// m_Target.ReCal_NaveMesh();
|
|
|
|
|
// }
|
|
|
|
|
//}
|
2024-12-15 01:39:39 +00:00
|
|
|
}
|
2026-09-06 23:24:25 +00:00
|
|
|
/// <summary>
|
|
|
|
|
/// #770-1 "공격 시작 시점" : 콤보를 시작하는 순간 이동 없이 때릴 수 있는 범위에서 타겟을 다시 고른다.
|
|
|
|
|
/// 자동 전투 루프(Update)와 공격 버튼(BattleUI.OnClick_Attack) 양쪽 모두 이 경로를 지난다.
|
|
|
|
|
/// base.Play_Attack 이 곧바로 새 타겟을 LookAt 하므로 방향 전환도 같은 프레임에 끝난다.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public override void Play_Attack(int _attack, float _as, System.Action actsuccess = null)
|
|
|
|
|
{
|
|
|
|
|
if (_attack == 0 && UseWLTargeting() && WL.Settings.WLTargetingSettings.AttackStartRetarget
|
|
|
|
|
&& !IsDead() && CurAnim != eAnim.Attack && CurAnim < eAnim.Skill)
|
|
|
|
|
Retarget_InReach();
|
|
|
|
|
|
|
|
|
|
base.Play_Attack(_attack, _as, actsuccess);
|
|
|
|
|
}
|
2024-12-15 01:39:39 +00:00
|
|
|
public void Check_Target(bool forcefind = false)
|
|
|
|
|
{
|
2026-09-06 23:24:25 +00:00
|
|
|
// #773 (2026-09-06 회귀 수정): 규칙 4 로 피격 어그로가 사라진 뒤 Auto OFF 계정은 교전 시작점이 없었다.
|
|
|
|
|
// → PC 는 설정(autoEngage · 기본 ON)이 켜져 있으면 계정 Auto 값과 무관하게 탐색 거리 안의 적을 자동 타겟한다.
|
|
|
|
|
bool autoEngage = UseWLTargeting() && WL.Settings.WLTargetingSettings.AutoEngage;
|
|
|
|
|
if (forcefind || m_Auto || autoEngage)
|
2024-12-15 01:39:39 +00:00
|
|
|
{
|
2025-02-08 22:22:31 +00:00
|
|
|
if (!isTarget)
|
2024-12-15 01:39:39 +00:00
|
|
|
Set_Target(forcefind);
|
|
|
|
|
else
|
|
|
|
|
{
|
2026-09-06 23:24:25 +00:00
|
|
|
// #770-2 : PC 는 공격/스킬 애니메이션 재생 중이어도 죽은 타겟을 즉시 교체한다(기존엔 애님이 끝날 때까지 보류).
|
|
|
|
|
bool waitForAnim = !(UseWLTargeting() && WL.Settings.WLTargetingSettings.RetargetOnKill)
|
|
|
|
|
&& isPlaying(eAnim.Attack, eAnim.Attack2, eAnim.Attack3, eAnim.Attack4, eAnim.Skill);
|
|
|
|
|
if (m_Target.IsDead() && !waitForAnim)
|
2024-12-15 01:39:39 +00:00
|
|
|
Set_Target(forcefind);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-10 05:35:41 +00:00
|
|
|
public override void Play_Hit()
|
|
|
|
|
{
|
|
|
|
|
base.Play_Hit();
|
|
|
|
|
if (CurAnim == eAnim.Run) return; // 달릴 때도 무시
|
|
|
|
|
if (IsDead() || IsCC() || CurAnim >= eAnim.Attack) return; // 공격, 스킬 쓸 때는 피격 애님 무시
|
|
|
|
|
|
|
|
|
|
if (IsRole(eRole.Mob)) m_HitTime = 0.5f;
|
|
|
|
|
var aniname = MyValue.Get_AnimName(eAnim.Hit);
|
|
|
|
|
m_animation.speed = 1f;
|
|
|
|
|
AnimationPlay(aniname);
|
|
|
|
|
}
|
2024-12-15 01:39:39 +00:00
|
|
|
|
2025-02-13 04:40:04 +00:00
|
|
|
public override void On_Regen(bool reincarnation = false, float healrate = 1f)
|
2024-12-15 01:39:39 +00:00
|
|
|
{
|
2025-02-13 04:40:04 +00:00
|
|
|
base.On_Regen(reincarnation, healrate);
|
2024-12-15 01:39:39 +00:00
|
|
|
Set_Warp(LoadMapMgr.Ins.Get_ReincanationPos(MyValue.MyChoiceMapData));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void Set_Stat_SelectSkill(SelectSkillTableData _sstd)
|
|
|
|
|
{
|
|
|
|
|
base.Set_Stat_SelectSkill(_sstd);
|
|
|
|
|
|
|
|
|
|
// TODO 정인호 : 3가지 카드에서 선택하는 스킬과 관련된 스탯 설정
|
|
|
|
|
//switch (_sstd.Stat)
|
|
|
|
|
//{
|
|
|
|
|
// case eStat.Attack:
|
|
|
|
|
// case eStat.CriDmg:
|
|
|
|
|
// if (IsRole(eRole.PC)) m_Stat.Set_Stat(true, _sstd.Stat, m_Stat.Get_Stat(_sstd.Stat) * _sstd.ValueBase);
|
|
|
|
|
// break;
|
|
|
|
|
// case eStat.HP:
|
|
|
|
|
// case eStat.MP:
|
|
|
|
|
// if (IsRole(eRole.PC)) m_Stat.Add_MaxHPMP(_sstd.Stat, _sstd.ValueBase);
|
|
|
|
|
// break;
|
|
|
|
|
// case eStat.MPRecover:
|
|
|
|
|
// break;
|
|
|
|
|
// case eStat.Defence_Melee:
|
|
|
|
|
// case eStat.Defence_Range:
|
|
|
|
|
// case eStat.Cri:
|
|
|
|
|
// if (IsRole(eRole.PC)) m_Stat.Set_Stat(true, _sstd.Stat, _sstd.ValueBase);
|
|
|
|
|
// break;
|
|
|
|
|
// case eStat.AttackSpeed:
|
|
|
|
|
// break;
|
|
|
|
|
// case eStat.AttackCoolTime:
|
|
|
|
|
// break;
|
|
|
|
|
// case eStat.PetAttack:
|
|
|
|
|
// if (IsRole(eRole.Pet)) m_Stat.Set_Stat(true, eStat.Attack, m_Stat.Get_Stat(eStat.Attack) * _sstd.ValueBase);
|
|
|
|
|
// break;
|
|
|
|
|
// case eStat.PetHP:
|
|
|
|
|
// if (IsRole(eRole.Pet)) m_Stat.Add_MaxHPMP(eStat.HP, _sstd.ValueBase);
|
|
|
|
|
// break;
|
|
|
|
|
// case eStat.PetDefence_Melee:
|
|
|
|
|
// if (IsRole(eRole.Pet)) m_Stat.Set_Stat(true, eStat.Defence_Melee, _sstd.ValueBase);
|
|
|
|
|
// break;
|
|
|
|
|
// case eStat.PetDefence_Range:
|
|
|
|
|
// if (IsRole(eRole.Pet)) m_Stat.Set_Stat(true, eStat.Defence_Range, _sstd.ValueBase);
|
|
|
|
|
// break;
|
|
|
|
|
// case eStat.PetBerserkAttack:
|
|
|
|
|
// if (IsRole(eRole.Pet) && IsBerserkPet()) m_Stat.Set_Stat(true, eStat.Attack, m_Stat.Get_Stat(eStat.Attack) * _sstd.ValueBase);
|
|
|
|
|
// break;
|
|
|
|
|
// case eStat.PetBerserkQuick:
|
|
|
|
|
// break;
|
|
|
|
|
// case eStat.PetBerserkTime:
|
|
|
|
|
// break;
|
|
|
|
|
//}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-24 03:25:01 +00:00
|
|
|
public override void Projectile(AnimationEvent s)
|
2024-12-15 01:39:39 +00:00
|
|
|
{
|
|
|
|
|
base.Projectile(s);
|
2025-06-24 03:25:01 +00:00
|
|
|
if (!string.IsNullOrEmpty(s.stringParameter))
|
2024-12-15 01:39:39 +00:00
|
|
|
{
|
2025-06-24 03:25:01 +00:00
|
|
|
var lifeTime = table_effectlist.Ins.Get_Data(s.stringParameter).f_ProjectileLifeTime;
|
|
|
|
|
Shoot_Projectile(s.stringParameter, s.floatParameter, lifeTime, Get_CenterPositionFoward());
|
2024-12-15 01:39:39 +00:00
|
|
|
}
|
|
|
|
|
}
|
2025-05-15 07:33:01 +00:00
|
|
|
|
|
|
|
|
public override Actor Get_Owner() { return OwnerActor; }
|
2024-12-15 01:39:39 +00:00
|
|
|
}
|