using CodeStage.AntiCheat.ObscuredTypes; using System; using System.Collections; using UnityEngine; using UnityEngine.AI; using UnityEngine.ResourceManagement.AsyncOperations; using Random = UnityEngine.Random; public class PCActor : MyActor { public Transform[] tfs_weapon; bool isUI; float t_RecoverHPTime = 10f, t_RecoverMPTime = 10f, t_autoskill = 0.5f; float t_HP = 0f, t_MP = 0f; ObscuredFloat[] arr_magicCoolTime = new ObscuredFloat[] { 0f, 0f, 0f, 0f, 0f, 0f }; ObscuredFloat[] arr_magicMaxCoolTime = new ObscuredFloat[] { 0f, 0f, 0f, 0f, 0f, 0f }; SkillListTableData[] arr_magicData = new SkillListTableData[6]; SkillCard[] arr_PCSkillCard; public int JoystickStatus = 0; // 0 조이스틱 x, 1 조이스틱 + run 애님, 2 조이스틱만 ObscuredFloat m_AvoidCoolTime; protected ClassConfigTableData m_ClassData; // ── PD #762 (2026-09-06) 공격 모션 전진 이동 ─────────────────────────────── // 원래 Actor.Move(float) 는 에디터 로그만 남기는 빈 스텁이었고, 클립의 루트 전진 // (Sword&Shield_Attack1~3 = +0.78 / +1.73 / +1.62 m 실측)은 Animator.applyRootMotion = 0 // 때문에 전부 버려져 "모션만 크고 위치는 제자리" 였다. // 여기서 클립이 넘겨 준 거리를 WL.Combat.AttackStepDriver 로 넘겨 NavMeshAgent 로 밀어 준다. // 설정 에셋(WLCombatMotionSettings)이 없거나 스위치를 끄면 즉시 예전 동작으로 돌아간다(C8). public override void Move(float b) { base.Move(b); if (b <= 0f || IsStop || IsDead() || IsCC()) return; WL.Combat.AttackRootMotion.Ensure(this); // PD #789 (모델 교체 등으로 빠졌을 때 재부착) WL.Combat.AttackStepDriver.Begin(this, b); } /// /// 전진을 계속해도 되는 상태인가. AttackStepDriver 가 매 프레임 묻는다. /// IsStop 이 Actor 의 protected 라 밖에서 못 읽으므로 여기서 한 번에 판정한다. /// public bool WL_CanAttackStep() { return !IsStop && !IsDead() && !IsCC() && Get_CurAnim() >= eAnim.Attack; } // ── PD #787 (2026-09-06) 대쉬 후 공격 ───────────────────────────────────── // WL.Combat.DashDriver 가 Actor 의 protected 멤버(IsStop · m_Stat · StopMove_Imm · // Play_Run · Play_Attack)에 접근할 수 없어, 필요한 최소 동작만 여기서 공개한다. // 값·스위치의 SOT 는 WLCombatMotionSettings 에셋이다(C45). /// 지금 대쉬해도 되는 상태인가. 조이스틱 입력이 있으면 플레이어 조작이 우선이라 막는다. public bool WL_CanDash() { return !IsStop && !IsDead() && !IsCC() && JoystickStatus == 0 && Get_CurAnim() < eAnim.Attack; } /// NavMesh 경로 이동을 끊는다(대쉬는 NavMeshAgent.Move 로 직접 민다 — 두 이동이 싸우지 않게). public void WL_StopNavMove() { StopMove_Imm(false); } /// 대쉬 도착 즉시 공격(대기 없음). 공격 속도는 기존 자동 교전과 같은 값을 쓴다. public void WL_PlayAttackNow() { Play_Attack(0, (float)m_Stat.Get_Stat(eStat.FinalAttackSpeed)); } /// 대쉬로 못 붙었을 때 기존 달리기 추적으로 돌려준다. public void WL_PlayRunNow() { Play_Run(); } protected override void OnDestroy() { base.OnDestroy(); if (!m_Enemy && !isUI) { MyValue.MyPC = null; if (!DSUtil.CheckNull(MyValue.m_RealCamera)) MyValue.m_RealCamera.Set_Target(null); } } protected override void Update() { if (!DSUtil.CheckNull(arr_PCSkillCard) && arr_PCSkillCard.Length > 0) { for (int i = 0; i < arr_magicCoolTime.Length; i++) { if (DSUtil.CheckNull(arr_magicData[i]) || arr_magicData[i].e_SkillType == eSkillType.Passive) continue; arr_magicCoolTime[i] -= Time.deltaTime; if (arr_magicCoolTime[i] <= 0f) arr_magicCoolTime[i] = 0f; arr_magicCoolTime[i].RandomizeCryptoKey(); if (!IsEnemy()) arr_PCSkillCard[i].Set_CoolTime(arr_magicCoolTime[i], arr_magicMaxCoolTime[i], i); if (!IsStop && m_Auto && !IsDead() && !DSUtil.CheckNull(arr_magicData[i]) && arr_magicCoolTime[i] <= 0f && t_autoskill <= 0f && !IsCC() && CurAnim < eAnim.Skill && isTarget && Vector3.Distance(Get_position(), m_Target.Get_position()) <= arr_magicData[i].f_SkillRange) { if (Use_Skill(arr_magicData[i], m_Auto, i)) { arr_magicCoolTime[i] = arr_magicMaxCoolTime[i]; t_autoskill = Random.Range(2f, 3f); } } } t_autoskill -= Time.deltaTime; } t_HP += Time.deltaTime; if (t_HP >= t_RecoverHPTime) { t_HP = 0f; if (!DSUtil.CheckNull(m_Stat)) { var hpper = (float)(m_Stat.Get_Stat(eStat.HP) / m_Stat.Get_Stat(eStat.MaxHP)); if (hpper <= m_Stat.Get_Stat(eStat.HeroBuffHP)) m_Stat.Set_Stat(true, eStat.HP, m_Stat.Get_Stat(eStat.FinalHPRegen) + m_Stat.Get_Stat(eStat.HP_REGEN) * m_Stat.Get_Stat(eStat.HeroBuff_HP_REGEN_Multiplier)); else m_Stat.Set_Stat(true, eStat.HP, m_Stat.Get_Stat(eStat.FinalHPRegen)); } } t_MP += Time.deltaTime; if (t_MP >= t_RecoverMPTime + (dic_ccData.ContainsKey(eCC.MPRecoverTime) && dic_ccData[eCC.MPRecoverTime].CCTime > 0 ? dic_ccData[eCC.MPRecoverTime].CCDmg : 0)) { t_MP = 0f; if (!DSUtil.CheckNull(m_Stat)) m_Stat.Set_Stat(true, eStat.MP, m_Stat.Get_Stat(eStat.FinalMPRegen)); } if (m_AvoidCoolTime > 0f) { m_AvoidCoolTime -= Time.deltaTime; m_AvoidCoolTime.RandomizeCryptoKey(); if (!DSUtil.CheckNull(NewGameUI.Ins)) NewGameUI.Ins.m_BattleUI.Set_Avoid(m_AvoidCoolTime); } Update_Move(); if (JoystickStatus > 0 || IsStop || IsDead()) return; base.Update(); // 2023. 02. 19. ?뺤씤?? ?ы깉李얜뒗 濡쒖쭅(?먮옒???ㅽ뀒?댁????덉뿀?붾뜲, 留덈???湲곗뼲?쇰줈 諛붽뺨????濡쒖쭅 ?꾩슂?놁뼱吏? //if (m_Target == null && m_AutoTarget && MobInfo.Ins.m_PortalMgr != null && MobInfo.Ins.m_PortalMgr.go_portal.activeInHierarchy && m_NavMeshAgent.stoppingDistance > 0f) //{ // Set_AutoBraking(false); // Set_Path(MobInfo.Ins.m_PortalMgr.go_portal.transform.position); //} } public override void Set(bool isEnemy, Actor owner, ServerData sdata, int _id, bool _isstop, bool _ui = false) { m_Role = eRole.PC; if (DSUtil.CheckNull(arr_PCSkillCard) && !DSUtil.CheckNull(NewGameUI.Ins)) arr_PCSkillCard = NewGameUI.Ins.m_BattleUI.skillCards; if (!DSUtil.CheckNull(arr_PCSkillCard)) { // ?ㅽ궗 ?ㅼ젙 for (int i = 0; i < arr_PCSkillCard.Length; i++) { var skillData = table_skilllist.Ins.Get_Data_orNull(sdata.Get_EquipSkillID(i)); arr_PCSkillCard[i].Set(skillData); arr_PCSkillCard[i].Set_CardType(eUICardType.Use); dic_MagicData.Add(i, new UseMagicInfo { m_uiIndex = i, m_SkillTableData = skillData, }); } } base.Set(isEnemy, owner, sdata, _id, _isstop, _ui); Change_Class(table_classconfig.Ins.Get_Data_orNull(_id), _ui); // #768 — 플레이어의 이동 차단(충돌) 범위. 값은 설정 에셋에서 읽는다(C45 · 프리팹 46개 개별 편집 금지). WL.Settings.WLCollisionTuning.ApplyPC(m_NavMeshAgent); WL.Combat.AttackRootMotion.Ensure(this); // PD #789 공격 클립 루트모션 재현 isSurviveTime = false; } public void Set_UIPC() { isUI = true; } public override void ReSet() { base.ReSet(); Set_Stat(true); // ?뚯뒪???ㅽ꺈 //m_Stat.Set_Stat(true, eStat.CRI, 1f); //m_Stat.Set_Stat(true, eStat.M_CRI, 1f); ////////////// t_autoskill = Random.Range(2f, 3f); m_Auto = m_ServerData.Get_Common(eCommon.Auto) == 1; for (int i = 0; i < arr_magicCoolTime.Length; i++) arr_magicCoolTime[i] = 0f; for (int i = 0; i < arr_magicData.Length; i++) arr_magicData[i] = table_skilllist.Ins.Get_Data_orNull(m_ServerData.Get_EquipSkillID(i)); for (int i = 0; i < arr_magicMaxCoolTime.Length; i++) arr_magicMaxCoolTime[i] = arr_magicData[i] != null ? arr_magicData[i].Get_CoolTime(m_Stat.Get_Stat(eStat.COOLTIME_Multiplier)) : 0f; t_HP = t_MP = 0f; m_bImmortal = m_bCanDodge = false; if (!m_Enemy) { MyValue.MyPC = this; //// 카메라 위치 잡기 //var curStageData = InGameInfo.Ins.Get_CurStageData(); //var viewcam = LoadMapMgr.Ins.Get_ViewCamera("ViewCamera"); //var maincam = Camera.main; //if (InGameInfo.Ins.IsGameMode(eGameMode.Lobby) && viewcam != null) //{ // InGameTouchInfo.Ins.m_RealCamera.enabled = false; // maincam.fieldOfView = viewcam.fieldOfView; // maincam.backgroundColor = viewcam.backgroundColor; // maincam.transform.position = viewcam.transform.position; // maincam.transform.rotation = viewcam.transform.rotation; //} #if UNITY_EDITOR if (UnityEngine.SceneManagement.SceneManager.GetActiveScene().name == "StatTest" || UnityEngine.SceneManagement.SceneManager.GetActiveScene().name == "Settings") return; #endif LoadMapMgr.Ins.Get_PortalPos(MyValue.MyChoiceMapData, Set_Warp); var dgData = MyValue.MyChoiceMapData.Get_DungeonMapData_orNull(); if (!DSUtil.CheckNull(dgData)) { if (dgData.e_DungeonType == eDungeon.d2_Maze) LoadMapMgr.Ins.Set_MazeMap(MyValue.MyChoiceMapData.n_PortalIndex); if (dgData.e_DungeonType == eDungeon.d2_Maze || dgData.e_DungeonType == eDungeon.d4_Wave) LoadMapMgr.Ins.Set_MazeNextPortal(MyValue.MyChoiceMapData.n_PortalIndex, false); } } else LoadMapMgr.Ins.Get_PortalPos(MyValue.MyChoiceMapData, Set_Warp); // TODO ?뺤씤??: PvP ??寃쎌슦 ???꾩튂 ?↔린 } public override void On_Regen(bool reincarnation = false, float healrate = 1f) { base.On_Regen(reincarnation, healrate); Del_Target(true); Del_Magic(); Play_Idle(); ActorInfo.Ins.All_Stop(false); OptionInfo.Ins.Set_PCMove(this, true); JoystickStatus = 0; } protected override bool IsAvoidCoolTime() { return m_AvoidCoolTime > 0f; } protected override void Set_AvoidCoolTime() { m_AvoidCoolTime = table_GlobalValue.Ins.Get_Int("f_AvoidCoolTime"); } public void StartCam(Action actGetPos) { Camera.main.fieldOfView = 60f; LoadMapMgr.Ins.Get_PortalPos(MyValue.MyChoiceMapData, portalpos => { var mzd = LoadMapMgr.Ins.Get_MapZoneData(MyValue.MyChoiceMapData); MyValue.m_RealCamera.Set_Target(transform, mzd.CamRot_Portal); actGetPos?.Invoke(portalpos); }); } float Get_RealCamRotAround(Vector3 portalpos) { //Vector3 targetPosition = portalpos; //Vector3 cameraPosition = portalpos - Vector3.forward; // ?꾩옱 移대찓?쇱쓽 ?꾩튂 //Vector3 relativePosition = targetPosition - cameraPosition; //return Mathf.Atan2(relativePosition.x, relativePosition.z) * Mathf.Rad2Deg; return 0f; } public override void Get_Damage(DamageInfo _dinfo) { //_dinfo.Damage = 1; // ?뚯뒪??: 留덈? ??二쎄쾶 WL.Combat.Survival.Survival.TakeDamage(this, _dinfo, base.Get_Damage); // WL #813i 무적 해제 게이트(원본 = base.Get_Damage(_dinfo) · SO off 면 그대로 base 호출) if (!IsEnemy() && _dinfo.Damage >= Get_MaxHP() * 0.5d && !isSurviveTime) StartCoroutine(Co_Survive(5f)); } bool isSurviveTime = false; IEnumerator Co_Survive(float _time) { isSurviveTime = true; yield return new WaitForSeconds(_time); if (!IsDead()) ServerInfo.Ins.m_ServerData.Add_Item(new ItemData(eItem.Title, 55, 1)); // 移?샇 : 吏€???몄썡??蹂댁씤 isSurviveTime = false; } public override void Set_MagicCoolTime(float _time, int _index) { arr_magicCoolTime[_index] = _time; arr_magicCoolTime[_index].RandomizeCryptoKey(); } public override void Set_SkillCoolTime(bool add, int skillid, float rate) { for (int i = 0; i < arr_magicData.Length; i++) { if (arr_magicData[i].n_SkillID == skillid) { if (add) arr_magicCoolTime[i] += arr_magicMaxCoolTime[i] * rate; else arr_magicCoolTime[i] -= arr_magicMaxCoolTime[i] * rate; break; } } } protected override bool Check_MagicCoolTime(int _index) { return arr_magicCoolTime[_index] <= 0f; } public void Init_UI(Transform _parent) { DSUtil.Set_Transform_withParent(transform, _parent); transform.localEulerAngles = Vector3.up * 180f; DSUtil.ChangeLayersRecursively(transform, "UI"); } public override void BossRaidCard_AddDmg(double _dmg) { } public override void BossRaidCard_HP() { } public void Set_Obj() { string animControllerPath; if (InGameInfo.Ins.IsGameMode(eGameMode.Lobby)) { transform.localScale = Vector3.one; animControllerPath = "Assets/Res_Addr/Animations/pcanim_lobby.controller"; } else { transform.localScale = m_ClassData.f_Scale * Vector3.one; animControllerPath = m_ClassData.s_AnimationController; } AddrResourceMgr.Ins.LoadObject(animControllerPath, handle => { m_animation.runtimeAnimatorController = handle.Result; }); if (!InGameInfo.Ins.IsGameMode(eGameMode.Lobby)) Change_Weapon(); } public void Change_Weapon() { for (int i = 0; i < tfs_weapon.Length; i++) { if (tfs_weapon[i].childCount > 0) { for (int j = 0; j < tfs_weapon[i].childCount; j++) Destroy(tfs_weapon[i].GetChild(j).gameObject); } } var wData = table_equipitemlist.Ins.Get_Data_orNull(m_ServerData.Get_EqiupmentEquip(eItem.WeaponCostume) > 0 ? m_ServerData.Get_EqiupmentEquip(eItem.WeaponCostume) : m_ServerData.Get_EqiupmentEquip(eItem.Weapon)); m_AttackType = DSUtil.CheckNull(wData) ? eAttackType.Physics : wData.e_EquipAttribute; for (int i = 0; i < 2; i++) { var prefab = i == 0 ? m_ClassData.s_WeaponLHPrefab : m_ClassData.s_WeaponRHPrefab; var index = i == 0 ? m_ClassData.n_WeaponLHIndex : m_ClassData.n_WeaponRHIndex; if (!string.IsNullOrEmpty(prefab)) { var fullpath = $"Assets/Res_Addr/Weapon/{prefab}.prefab"; Make_Weapon(fullpath, tfs_weapon[index], Vector3.zero, Vector3.one); } } } void Make_Weapon(string fullpath, Transform tfparent, Vector3 localpos, Vector3 scale, bool ui = false) { AddrResourceMgr.Ins.LoadObject(fullpath, handle => { if (handle.IsDone && handle.Status == AsyncOperationStatus.Succeeded) { var w = DSUtil.Get_Clone(handle.Result); AddrResourceMgr.Ins.Set_AddressableReleaseSelf(w); DSUtil.Set_Transform_withParent(w.transform, tfparent, localpos, scale); if (ui) DSUtil.ChangeLayersRecursively(w.transform, "UI"); } }); } public void Change_Class(ClassConfigTableData classdata, bool _ui) { m_ID = classdata.n_ClassID; m_ClassData = classdata; m_NavStopDistance = m_ClassData.f_AttackRange; Set_Obj(); if (!_ui) { ReSet(); Set_MoveSpeed(true, m_Stat.Get_Stat(eStat.FinalMoveSpeed)); } f_FindEnemyRange = table_GlobalValue.Ins.Get_Float("f_PC_FindEnemyRange") + (float)m_Stat.Get_Stat(eStat.AttackRange_Up); } protected override void Set_Script() { CameraShake.Ins.VibrateForTime(0.1f); } float move_dist = 0f; public override void Run_byJoystick(Vector3 _pos) { if (m_bCanDodge) { if (!IsDead() && CurAnim != eAnim.Dodge) { transform.LookAt(transform.position + _pos); Play_Dodge(); } } else { //if (IsDead() || CurAnim >= eAnim.Skill1 || IsCC()) if (CurAnim == eAnim.Dodge || IsDead() || IsCC()) return; if (JoystickStatus != 2) Play_RunAnim(); Del_Magic(); Del_Target(); transform.LookAt(Get_position() + _pos); if (m_NavMeshAgent.isOnNavMesh) { m_NavMeshAgent.ResetPath(); m_NavMeshAgent.stoppingDistance = 0f; var before = Get_position(); m_NavMeshAgent.speed = m_NavSpeed; var mov = Get_MoveSpeed() * Time.deltaTime * _pos; m_NavMeshAgent.Move(mov); var after = Get_position(); move_dist += Vector3.Distance(before, after); if (move_dist >= 1f) { move_dist -= 1f; m_ServerData.Add_TitleCondition(74, 75); // 移?샇 : 諛쒕줈 嫄룸뒗, 諛쒕줈 ?곕뒗 } } } } public override void Set_AutoBraking(bool _active) { if (CurAnim == eAnim.Dodge || IsDead() || IsCC()) return; if (_active) { IsStop = false; Play_Idle(); m_NavMeshAgent.stoppingDistance = m_NavStopDistance; m_NavMeshAgent.angularSpeed = 300f; } else { IsStop = true; Del_Target(); m_NavMeshAgent.ResetPath(); m_NavMeshAgent.stoppingDistance = 0f; } } public override void Set_JoystickStatus(int status) { if (IsMainPC()) { if (JoystickStatus == 2 && status < 3) return; JoystickStatus = status == 3 ? 0 : status; } } public bool Check_Path(Vector3 pos) { if (!IsCC()) { NavMeshPath path = new NavMeshPath(); if (m_NavMeshAgent.isOnNavMesh && m_NavMeshAgent.CalculatePath(pos, path)) return path.status == NavMeshPathStatus.PathComplete; } return false; } protected override void Set_Die() { base.Set_Die(); ActorInfo.Ins.All_Stop(true); //NewGameUI.Ins.m_GuideQuestUI.Add_GuideQuest(ePurpose.DieOrQuit); DungeonInfo.Ins.Sub_WaveLife(); MonsterSimpleInfo.Ins.Set_DelLockTarget(true); WL.Combat.Survival.DeathFlow.OnPCDied(this); // WL #813i 사망 연출 → 부활 요청 → 존 시작점 On_Regen(SO off 면 즉시 반환) } protected override IEnumerator Co_ScaleControl(bool returntoOriginalScale, float targetscale, Action actComplete = null) { if (returntoOriginalScale) targetscale = m_ClassData.f_Scale; Vector3 target = Vector3.one * targetscale; float t = 0f; while (t < 1f) { yield return null; t += Time.deltaTime; transform.localScale = Vector3.Lerp(transform.localScale, target, t); } transform.localScale = Vector3.one * targetscale; actComplete?.Invoke(); } protected override void Weapon_OnOff(bool active) { for (int i = 0; i < tfs_weapon.Length; i++) { if (tfs_weapon[i].childCount > 0) { for (int j = 0; j < tfs_weapon[i].childCount; j++) tfs_weapon[i].GetChild(j).gameObject.SetActive(active); } } } public override void Play_Attack(int _attack, float _as, Action actsuccess = null) { base.Play_Attack(_attack, _as, () => { // 애니메이션 Event ShowEffect 로 변경 (확장성 및 공격 속도 고려) //if (!string.IsNullOrEmpty(m_ClassData.s_SlashPrefab)) // Set_Coroutine(() => // { // InGameInfo.Ins.Show_Effect(m_ClassData.s_SlashPrefab, Get_position(), transform.eulerAngles); // }, m_ClassData.f_SlashStartDelay); }); } }