using System; using UnityEngine; using UnityEngine.SceneManagement; public enum eMobStatus { PatrolWait, PatrolRot, Patroling, Battle, BattleStop, Attack, Chase, } public class MobActor : Actor { public int MagicID, HealCount; public float HealRate = 0.1f; protected int m_SpawnerId; protected eMobStatus MobStatus; Vector3 OriginalPos, TargetPatrolPos; float m_PatrolDelay, m_DieTime; bool b_IsFirstAttack, NoUpdate_byTool; protected MonsterAppearData m_MonsterAppearData; protected MonsterTableData m_MonsterTableData; Action m_actDie; protected MobMark m_MobMark; protected override void Start() { base.Start(); GetComponent().isKinematic = true; } public override void Set(bool isEnemy, Actor owner, ServerData sdata, int _id, bool _stop, bool _ui = false) { base.Set(isEnemy, owner, sdata, _id, _stop); if (MagicID > 0) m_Magic = new UseMagicInfo { m_SkillTableData = table_skilllist.Ins.Get_Data_orNull(MagicID), m_uiIndex = -1 }; m_SpawnerId = _id; m_MonsterAppearData = table_monsterappear.Ins.Get_Data_orNull(m_SpawnerId); //var chapter = InGameInfo.Ins.Get_CurStageData().Chapter; if (transform.localScale.x > 1f) { m_NavMeshAgent.stoppingDistance = m_NavStopDistance * transform.localScale.x; if (m_AttackType == eAttackType.Physics && m_NavMeshAgent.stoppingDistance > 7f) m_NavMeshAgent.stoppingDistance = 7f; //else m_AttackType == eAttackType.Range && m_NavMeshAgent.stoppingDistance > 20f) m_NavMeshAgent.stoppingDistance = 20f; } if (m_SubRole == eSubRol.Dummy) IsStop = true; #if UNITY_EDITOR if (SceneManager.GetActiveScene().name == "StatTest") return; #endif Set_RunSmoke(false); Set_Warp(Get_position()); InGameInfo.Ins.Show_Effect("Effect_MonsterSpawn", Get_position()); } public void Set(eRole role, MonsterTableData mobtable, Vector3 pos, Action actDie, int order) { m_Role = role; m_actDie = actDie; bool IsBoss = IsRole(eRole.Boss); m_MonsterTableData = mobtable; var scale = m_MonsterTableData.f_DefaultScale; if (!DSUtil.CheckNull(m_MonsterTableData)) transform.localScale = (IsBoss ? 1.3f : 1f) * scale * Vector3.one; if (!IsBoss) IsBoss = scale > 2f; m_AttackType = m_MonsterTableData.e_AttackType; b_IsFirstAttack = m_MonsterTableData.b_IsFirstAttack; //var chapter = InGameInfo.Ins.Get_CurStageData().Chapter; //if (IsBoss && chapter < 101) { GameUI.Ins.IngameUI.m_BossInfoUI.Set(m_MonsterTableData.Get_Name()); } // 순찰 모드로 전환 transform.SetPositionAndRotation(pos, Quaternion.identity); OriginalPos = pos; Change_MobStatus(eMobStatus.PatrolWait); // 몹 스탯 설정 m_Stat = new ActorStatInfo(m_Role, m_MonsterTableData, m_MonsterAppearData); Reset_AttackCoolTime(); m_NavMeshAgent.speed = (float)m_Stat.Get_Stat(eStat.FinalMoveSpeed); m_NavMeshAgent.avoidancePriority = 51 + order; #if UNITY_EDITOR if (SceneManager.GetActiveScene().name == "StatTest") { transform.localPosition = Vector3.zero; return; } #endif //GameUI.Ins.IngameUI.m_ChapterQuest.Add_PurposeCount(new ChapterQuestAddData { purpose = ePurpose.MeetMob, mobid = m_MonsterTableData.n_MonsterID }); } public override void On_Regen() { base.On_Regen(); Set_Warp(OriginalPos); Change_MobStatus(eMobStatus.PatrolWait); InGameInfo.Ins.Show_Effect("Effect_MonsterSpawn", Get_position()); } protected override void Awake() { base.Awake(); m_NavMeshAgent.avoidancePriority = 51; tag = "Mob"; } protected override void Update() { if (NoUpdate_byTool || IsStop || IsDead() || IsCC()) return; Update_ETC(); // 순찰 중 switch (MobStatus) { case eMobStatus.PatrolWait: if (m_PatrolDelay > 0f) m_PatrolDelay -= Time.deltaTime; else Change_MobStatus(eMobStatus.PatrolRot); Check_Battle(); break; case eMobStatus.PatrolRot: // TargetPatrolPos 위치를 바라봄 Vector3 directionToTarget = (TargetPatrolPos - transform.position).normalized; Quaternion lookRotation = Quaternion.LookRotation(new Vector3(directionToTarget.x, 0, directionToTarget.z)); transform.rotation = Quaternion.Slerp(transform.rotation, lookRotation, Time.deltaTime * 5f); if (Quaternion.Angle(transform.rotation, lookRotation) < 5f) Change_MobStatus(eMobStatus.Patroling); //Check_Battle(); break; case eMobStatus.Patroling: // TargetPatrolPos 위치로 이동함 if (Complete_Destination()) Change_MobStatus(eMobStatus.PatrolWait); //Check_Battle(); break; case eMobStatus.BattleStop: Change_MobStatus(eMobStatus.PatrolRot); Heal(1f); break; case eMobStatus.Battle: if (!DSUtil.CheckNull(m_Target)) { if (CurAnim < eAnim.Attack && Vector3.Distance(Get_position(), m_Target.Get_position()) > m_NavMeshAgent.stoppingDistance) { if (!isPlaying(eAnim.Attack, eAnim.Attack2, eAnim.Attack3)) Change_MobStatus(eMobStatus.Chase); } else Change_MobStatus(eMobStatus.Attack); } else Change_MobStatus(eMobStatus.BattleStop); Check_Patrol(); break; case eMobStatus.Attack: if (!DSUtil.CheckNull(m_Target)) { if (m_Target.IsDead()) { Check_Battle(); if (MobStatus == eMobStatus.Attack) // 다음 타겟 못 찾았음 Change_MobStatus(eMobStatus.PatrolWait); } else { if (!isPlaying(eAnim.Attack, eAnim.Attack2, eAnim.Attack3) && Vector3.Distance(m_Target.Get_position(), Get_position()) > m_MonsterTableData.f_BaseATKRange + 0.5f) Change_MobStatus(eMobStatus.Chase); else if (m_attackCoolTime <= 0f) { m_attackCoolTime = 1f; Play_Attack(0, m_MonsterTableData.f_BaseATKSpeed); } } } else Change_MobStatus(eMobStatus.BattleStop); break; case eMobStatus.Chase: if (!DSUtil.CheckNull(m_Target)) { Set_Path(m_Target.Get_position()); if (Vector3.Distance(Get_position(), m_Target.Get_position()) <= m_NavMeshAgent.stoppingDistance) Change_MobStatus(eMobStatus.Attack); } Check_Patrol(); break; } { if (m_attackCoolTime > 0f) m_attackCoolTime -= Time.deltaTime; if (m_HitTime > 0f) { m_HitTime -= Time.deltaTime; if (m_attackCoolTime > 0f) return; } if (GoToTargetTime > 0f && IsAutoAIMob()) GoToTargetTime -= Time.deltaTime; //// 일정 시간이 지나면 가장 가까운 적을 다시 찾아봄 //if (FindOtherTargetTime > 0f) //{ // FindOtherTargetTime -= Time.deltaTime; // if (FindOtherTargetTime <= 0f) // { // FindOtherTargetTime = 7.5f; // FindNextTarget(m_FindDist); // } //} } } protected void Change_MobStatus(eMobStatus mobStatus) { #if UNITY_EDITOR if (SceneManager.GetActiveScene().name == "StatTest") return; #endif MobStatus = mobStatus; switch (mobStatus) { case eMobStatus.PatrolWait: Del_Target(); Play_Idle(); m_PatrolDelay = m_MonsterTableData.f_PatrolWaitTime; if (m_NavMeshAgent.isOnNavMesh && !IsCC()) m_NavMeshAgent.isStopped = false; m_NavMeshAgent.stoppingDistance = 0.1f; break; case eMobStatus.PatrolRot: Del_Target(); if (m_NavMeshAgent.isOnNavMesh && !IsCC()) m_NavMeshAgent.isStopped = false; TargetPatrolPos = DSUtil.Get_RandomPos_onNavMesh(OriginalPos, m_MonsterAppearData.f_PatrolRange - 1f, m_MonsterAppearData.f_PatrolRange); break; case eMobStatus.Patroling: Del_Target(); Play_Run(); Set_Path(TargetPatrolPos); m_NavMeshAgent.stoppingDistance = 0.1f; break; case eMobStatus.Battle: m_NavMeshAgent.stoppingDistance = m_MonsterTableData.f_BaseATKRange; FindNextTarget(10000f); break; case eMobStatus.BattleStop: Del_Target(); Play_Idle(); StopMove_Imm(); break; case eMobStatus.Attack: break; case eMobStatus.Chase: Play_Run(); break; } } void Check_Battle() { if (b_IsFirstAttack) { // 선공이고, 플레이어가 추적 범위 안에 들어오면 전투 상태로 전환 var enemies = ActorInfo.Ins.Get_EnemyActors(IsEnemy(), Get_position(), m_MonsterTableData.f_BaseChaseRange); if (enemies.Count > 0) Change_MobStatus(eMobStatus.Battle); } } void Check_Patrol() { // 플레이어가 추적 범위 밖으로 나가면 순찰 상태로 전환 if (!isPlaying(eAnim.Attack, eAnim.Attack2, eAnim.Attack3) && m_Target == null || Vector3.Distance(OriginalPos, Get_position()) > m_MonsterTableData.f_BaseChaseRange) Change_MobStatus(eMobStatus.BattleStop); } //protected override void Check_Attack() //{ // // 현재 애니메이션 상태 정보를 가져옴 // AnimatorStateInfo stateInfo = m_animation.GetCurrentAnimatorStateInfo(0); // // 애니메이션의 특정 구간 (예: 30%에서 40% 사이) 확인 // if (stateInfo.normalizedTime >= 0.3f && stateInfo.normalizedTime <= 0.4f && stateInfo.IsName("attack")) // { // // 데미지를 한 번만 적용 // if (!hasAppliedDamage) // { // switch (CurAnim) // { // case eAnim.Attack: // case eAnim.Attack2: // case eAnim.Attack3: // case eAnim.Attack4: // switch (m_AttackType) // { // // 근접 // case eAttackType.MeleePhysics: // case eAttackType.MeleeMagic: // case eAttackType.MeleePure: // m_Target.Get_Damage(Get_DamageInfo()); // break; // // 원거리 // case eAttackType.RangePhysics: // case eAttackType.RangeMagic: // case eAttackType.RangePure: // ProjectileInfo.Ins.Shoot_Projectile(m_MonsterTableData.s_Porjectile, this, m_Target, 1f, tfs_ProjectileStart[0].position); // break; // } // break; // case eAnim.Skill1: // case eAnim.Skill2: // break; // } // hasAppliedDamage = true; // 데미지가 적용되었음을 표시 // } // } // else // { // // 구간을 벗어나면 다시 데미지 적용 가능하도록 설정 // hasAppliedDamage = false; // } //} bool IsAutoAIMob() { return IsRole(eRole.Mob) || IsRole(eRole.Boss); } public void Set_Target(Actor _target) { m_Target = _target; } void FindNextTarget(float _dist) { switch (m_SubRole) { case eSubRol.Healer: var allies = ActorInfo.Ins.Get_AlliesActors(IsEnemy(), Get_position()); var hurts = allies.FindAll(f => f.Get_HP() < f.Get_MaxHP()); m_Target = hurts.Count > 0 ? hurts[0] : null; break; case eSubRol.Dummy: break; case eSubRol.None: case eSubRol.Ore: case eSubRol.Witch: m_Target = ActorInfo.Ins.Get_Nearest_EnemyActor_orNull(IsEnemy(), Get_position()); // 내가 타게팅했으니 너도 날 타게팅 할 수 있어야 한다. if (m_Target != null) { //ActorInfo.Ins.Add_FindTarget_AllPC(m_Target.IsEnemy(), m_Role, m_SpawnerId); if (IsRole(eRole.QuestMob)) GoToTargetTime = 0f; } break; } } protected override void Kill_OnePunch(DamageInfo dinfo) { base.Kill_OnePunch(dinfo); if (!DSUtil.CheckNull(dinfo.Beater_pd) && !DSUtil.CheckNull(dinfo.Beater_pd.m_SkillListTableData) && dinfo.Beater_pd.m_SkillListTableData.e_Skill == eEffect.Skill_FireBolt) ServerInfo.Ins.m_ServerData.Add_TitleCondition(59); // 칭호 : 두방은 사치 } protected override void VeryStrongDamage(double dmg) { base.VeryStrongDamage(dmg); if (dmg > 1000000000000d) ServerInfo.Ins.m_ServerData.Add_Item(new ItemData(eItem.Title, 68, 1)); // 칭호 : 한방있는 } protected override void Set_Die() { var sdata = ServerInfo.Ins.m_ServerData; m_actDie?.Invoke(m_MonsterTableData.n_MonsterID); sdata.Add_CollectionMobKill(m_MonsterTableData.n_MonsterID); base.Set_Die(); if (IsRole(eRole.QuestMob)) GameUI.Ins.IngameUI.m_ChapterQuest.Add_PurposeCount(new ChapterQuestAddData { purpose = ePurpose.SaveNPC }); else { m_DieTime = m_MonsterAppearData.f_SpawnerDelay; //m_MonsterAppearData.Cal_DropItems(this); if (IsRole(eRole.Boss)) { Time.timeScale = 0.3f; Set_Coroutine(() => { Time.timeScale = MyValue.Get_GameSpeed(); if (MobInfo.Ins.Get_CurStage() >= 10) { Set_Coroutine(() => { if (MyValue.MyPC.IsDead()) sdata.Add_Item(new ItemData(eItem.Title, 69, 1)); // 칭호 : 뒤끝있는 }, 0.5f); } }, 0.4f); sdata.Add_MissionCount(MyValue.arr_Mission[0], 3); sdata.Add_MissionCount(MyValue.arr_Mission[1], 2); if (dic_ccData[eCC.Freeze].CCTime > 0) sdata.Add_TitleCondition(57); // 칭호 : 얼음땡 if (dic_ccData[eCC.KnockedAirborne].CCTime > 0) sdata.Add_TitleCondition(58); // 칭호 : 비행기 태우는 //GameUI.Ins.IngameUI.m_ChapterQuest.Add_PurposeCount(new ChapterQuestAddData { purpose = ePurpose.BossKill, mobid = m_MonsterTableData.n_MonsterID }); } else { #if UNITY_EDITOR if (SceneManager.GetActiveScene().name == "StatTest") return; #endif sdata.Add_MissionCount(MyValue.arr_Mission[0], 2); sdata.Add_MissionCount(MyValue.arr_Mission[1], 1); //GameUI.Ins.IngameUI.m_ChapterQuest.Add_PurposeCount(new ChapterQuestAddData { purpose = ePurpose.MobKill, mobid = m_MonsterTableData.n_MonsterID }); } } } public override void Shoot_Skill() { base.Shoot_Skill(); if (IsRole(eRole.MemoryMob) && m_SubRole == eSubRol.Witch) { } else Reset_AttackCoolTime(); } public override void Get_Damage(DamageInfo _dinfo) { //_dinfo.Damage = 1f; // 테스트 : 몹 안 죽게 base.Get_Damage(_dinfo); switch (m_Role) { case eRole.QuestMob: case eRole.MemoryMob: if (m_SubRole == eSubRol.Dummy) { if (!GameUI.Ins.IngameUI.m_StageTimeUI.Get_Start()) GameUI.Ins.IngameUI.m_StageTimeUI.Set_Start(); // 젬 드랍 if (DSUtil.RandomTrue(0.1f)) InGameInfo.Ins.Shoot_Effect(new ProjectileData { shooter = this, m_Skill = eEffect.DropItem_Gem }); return; // 더미는 타겟 안 잡음 } break; } // 멀리서 맞았으면 맞은 몹을 타겟으로 잡고 주변 몹들도 같이 간다. if (m_Target == null || IsRole(eRole.QuestMob)) { if (m_Target == null) Change_Target(false, _dinfo.Beater, true); // TODO 정인호 : 몬스터 주변 몬스터 부르기 //var allies = ActorInfo.Ins.Get_EnemyActors(m_Role, transform, m_byHitDist); //for (int i = 0; i < allies.Count; i++) // if (allies[i].Get_Target() == null) // allies[i].Change_Target(false, m_Target, true); } } public override void Change_Target(bool _ispc, Actor target, bool _ChangeTargetByHit = false) { base.Change_Target(_ispc, target, _ChangeTargetByHit); if (m_Target != null) b_IsFirstAttack = true; if (b_IsFirstAttack) Change_MobStatus(eMobStatus.Battle); } protected override void Heal_To_Target() { base.Heal_To_Target(); if (m_Target != null) m_Target.Heal(HealRate); if (HealCount > 0) { var allies = ActorInfo.Ins.Get_AlliesActors(IsEnemy(), Get_position()); var hurts = allies.FindAll(f => f != m_Target && f.Get_HP() < f.Get_MaxHP()); for (int i = 0; i < hurts.Count; i++) { hurts[i].Heal(HealRate); if (i + 1 == HealCount - 1) break; } } } public override MonsterAppearData Get_MonsterStatTableData() { return m_MonsterAppearData; } public override int Get_Group() { return m_SpawnerId; } protected override void Set_MobMark(bool active) { if (m_MobMark == null) { m_MobMark = InGameInfo.Ins.Get_Obj(eObj.MobMark).GetComponent(); m_MobMark.transform.localScale = Vector3.one * 0.2f; } m_MobMark.Set(active, tf_HUD_Dmg); } public override void Projectile(string s) { base.Projectile(s); if (ProjectileInfo.Ins) ProjectileInfo.Ins.Shoot_Projectile(m_MonsterTableData.s_Porjectile1, this, m_Target, 1f, Get_ProjectileStartPos(), m_MonsterTableData.f_ProjectileLifeTime1); } public void Set_NoUpdate_byTool(MonsterTableData mobdata) { NoUpdate_byTool = true; m_MonsterTableData = mobdata; } }