using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Sirenix.OdinInspector; using UnityEngine; using Random = UnityEngine.Random; // ReSharper disable once CheckNamespace namespace BlueWaterProject { public abstract class PirateAi : CombatAi, IDamageable { #region Properties and variables [Title("Skin")] [Tooltip("SkinnedMeshRenderer, MeshRenderer의 Material을 모두 담고 있는 리스트")] [SerializeField] protected List skinMaterialList = new(10); [Tooltip("캐릭터 외곽선의 기본 색상")] [SerializeField] protected Color defaultSkinColor = Color.black; [Tooltip("캐릭터에 마우스 커서가 올라가 있을 때 색상")] [SerializeField] protected Color mouseEnterHighlightSkinColor = Color.white; [Tooltip("캐릭터가 선택되었을 때 색상")] [SerializeField] protected Color selectedSkinColor = Color.red; [field: SerializeField] public PirateStat PirateStat { get; set; } private PirateUnit pirateUnit; private PirateUnit mouseEnterPirateUnit; private UnitSelection unitSelection; [SerializeField] protected bool isAttackCoroutine; [SerializeField] private bool isCommanded; #endregion #region Unit Built-in methods protected virtual void OnDrawGizmosSelected() { if (!isDrawGizmosInFieldOfView) return; if (PirateStat.AttackerType == EAttackerType.OFFENSE) { if (!targetTransform) return; Gizmos.color = Color.red; Gizmos.DrawLine(transform.position, targetTransform.position); } else if (PirateStat.AttackerType == EAttackerType.DEFENSE) { if (PirateStat.DefenseType == EDefenseType.DEFENDER) { Gizmos.color = Color.red; Gizmos.DrawWireSphere(defensePos, PirateStat.DefenseRange); } Gizmos.color = Color.blue; Gizmos.DrawWireSphere(transform.position, PirateStat.ViewRange); if (!targetTransform) return; Gizmos.color = Color.red; Gizmos.DrawLine(transform.position, targetTransform.position); } } private void OnMouseEnter() { if (!unitSelection || !unitSelection.IsSelectable) return; mouseEnterPirateUnit = gameObject.GetComponentInParent(); if (mouseEnterPirateUnit == unitSelection.SelectedPirateUnit) return; foreach (var pirateAi in mouseEnterPirateUnit.pirateUnitStat.PirateAiList) { pirateAi.MouseEnterHighlight(); } } private void OnMouseExit() { if (!unitSelection || !unitSelection.IsSelectable || !mouseEnterPirateUnit || mouseEnterPirateUnit == unitSelection.SelectedPirateUnit) return; foreach (var pirateAi in mouseEnterPirateUnit.pirateUnitStat.PirateAiList) { pirateAi.ResetHighlight(); } mouseEnterPirateUnit = null; } private void Start() { InitStart(); } #endregion #region IDamageable interface public void TakeDamage(float attackerPower, Vector3? attackPos = null) { if (attackPos != null && combatAgent.enabled && PirateStat.AttackerType == EAttackerType.DEFENSE && !targetTransform) { // BeAttackedMovement((Vector3)attackPos); } // 회피 성공 체크 if (Random.Range(0, 100) < PirateStat.AvoidanceRate) { // TODO : 회피 처리 return; } var finalDamage = 0f; if (PirateStat.UsingShield) { // var penetrationChance = attackerShieldPenetrationRate - // (attackerShieldPenetrationRate * PirateStat.PenetrationResistivity * 0.01f); // // // 방패를 관통했다면, // if (Random.Range(0, 100) < penetrationChance) // { // finalDamage = attackerPower - PirateStat.Def; // finalDamage = Mathf.Max(finalDamage, 0); // } // else // { // finalDamage = 0f; // } } finalDamage = attackerPower - PirateStat.Def; finalDamage = Mathf.Max(finalDamage, 0); // 방패 막기 체크 if (finalDamage == 0f) { combatAnimator.SetTrigger(ShieldHash); return; } var changeHp = Mathf.Max(PirateStat.CurrentHp - finalDamage, 0); SetCurrentHp(changeHp, true); // 죽었는지 체크 if (changeHp == 0f) return; combatAnimator.SetTrigger(DamageHash); } #endregion #region Custom methods protected override void InitComponent() { base.InitComponent(); pirateUnit = Utils.GetComponentAndAssert(transform.parent); unitSelection = Utils.GetComponentAndAssert(GameObject.Find("UnitManager").transform); } protected override void SetLayer() { gameObject.layer = LayerMask.NameToLayer("Pirate"); var hitBoxObj = hitBoxCollider.gameObject; hitBoxObj.layer = LayerMask.NameToLayer("HitBox"); hitBoxObj.tag = "Pirate"; targetLayer = LayerMask.GetMask("Enemy"); if (PirateStat.AttackerType == EAttackerType.OFFENSE) { targetLayer |= LayerMask.GetMask("Props"); } } protected virtual void InitStart() { var pirateViewData = DataManager.Inst.GetPirateViewDictionaryFromKey(PirateStat.ViewIdx); InitViewModel(pirateViewData); FindMaterial(); SetBehaviorTree(UnitManager.Inst.PirateBehaviorTree); SetCurrentHp(PirateStat.MaxHp, true); SetMoveSpeed(PirateStat.MoveSpd); if (PirateStat.AttackerType == EAttackerType.DEFENSE) { defensePos = transform.position; } } private void InitViewModel(PirateView pirateView) { SetActiveViewModel(backpackContainer, pirateView.Backpack); SetActiveViewModel(leftWeaponContainer, pirateView.LeftWeapon); SetActiveViewModel(leftShieldContainer, pirateView.LeftShield); SetActiveViewModel(headContainer, pirateView.Head); SetActiveViewModel(rightWeaponContainer, pirateView.RightWeapon); SetActiveViewModel(bodyContainer, pirateView.Body); SetActiveViewModel(flagContainer, pirateView.Flag); } private void FindMaterial() { var skinnedMeshRenderers = GetComponentsInChildren(); var meshRenderers = GetComponentsInChildren(); foreach (var skin in skinnedMeshRenderers) { if (!skin.gameObject.activeSelf) continue; skinMaterialList.Add(skin.material); } foreach (var skin in meshRenderers) { if (!skin.gameObject.activeSelf) continue; skinMaterialList.Add(skin.material); } } public void CommandMoveTarget(Vector3 movePos) { StartCoroutine(CommandMoveCoroutine(movePos)); } private IEnumerator CommandMoveCoroutine(Vector3 movePos) { while (isAttacking) { yield return null; } combatAgent.stoppingDistance = GlobalValue.MAXIMUM_STOP_DISTANCE; combatAgent.SetDestination(movePos); SetIsCommanded(true, true); while (combatAgent.pathPending || combatAgent.remainingDistance > combatAgent.stoppingDistance) { yield return null; } SetIsCommanded(false, true); } public override void FindTarget() { switch (PirateStat.AttackerType) { case EAttackerType.NONE: print("PirateStat.AttackerType == NONE Error"); break; case EAttackerType.OFFENSE: FindTargetInOffense(); break; case EAttackerType.DEFENSE: FindTargetInDefense(); break; default: throw new ArgumentOutOfRangeException(); } } public override bool CanAttack() { if (!targetTransform) return false; var attackInRange = Vector3.Distance(transform.position, targetTransform.position) <= PirateStat.AtkRange; return attackInRange; } public override void Attack() { isAttackCoroutine = true; StartCoroutine(nameof(AttackAnimation)); } protected abstract IEnumerator AttackAnimation(); private void FindTargetInOffense() { if (!attackingIslandInfo) { print("attackingIslandInfo == null error"); return; } switch (PirateStat.OffenseType) { case EOffenseType.NONE: print("AiStat.OffenseType == NONE Error"); break; case EOffenseType.NORMAL: if (attackingIslandInfo.ExceptHouseList.Count > 0) { FindNearestTargetInList(attackingIslandInfo.ExceptHouseList); } else if (attackingIslandInfo.HouseList.Count > 0) { FindNearestTargetInList(attackingIslandInfo.HouseList); } break; case EOffenseType.ONLY_HOUSE: if (attackingIslandInfo.HouseList.Count > 0) { FindNearestTargetInList(attackingIslandInfo.HouseList); } else if (attackingIslandInfo.ExceptHouseList.Count > 0) { FindNearestTargetInList(attackingIslandInfo.ExceptHouseList); } break; default: throw new ArgumentOutOfRangeException(); } } protected virtual void FindNearestTargetInList(List targetList) { if (targetList.Count <= 0) return; var nearestTarget = targetList.OrderBy(t => t ? Vector3.Distance(transform.position, t.position) : float.MaxValue).FirstOrDefault(); if (nearestTarget == null) return; SetTargetTransform(nearestTarget, true); } private void FindTargetInDefense() { switch (PirateStat.DefenseType) { case EDefenseType.NONE: print("EnemyStat.DefenseType == NONE Error"); break; case EDefenseType.STRIKER: FindNearestTargetInRange(transform.position, PirateStat.ViewRange); break; case EDefenseType.MIDFIELDER: FindNearestTargetInRange(transform.position, PirateStat.ViewRange); break; case EDefenseType.DEFENDER: FindNearestTargetInRange(defensePos, PirateStat.DefenseRange); break; case EDefenseType.KEEPER: FindNearestTargetInRange(transform.position, PirateStat.ViewRange); break; default: throw new ArgumentOutOfRangeException(); } } protected virtual void FindNearestTargetInRange(Vector3 centerPos, float range) { Array.Clear(colliderWithinRange, 0, TARGET_MAX_SIZE); var maxColliderCount = Physics.OverlapSphereNonAlloc(centerPos, range, colliderWithinRange, targetLayer, QueryTriggerInteraction.Collide); if (maxColliderCount <= 0) { SetTargetTransform(null, true); return; } var nearestDistance = Mathf.Infinity; Transform nearestTargetTransform = null; for (var i = 0; i < maxColliderCount; i++) { var distanceToTarget = Vector3.Distance(transform.position, colliderWithinRange[i].transform.position); if (distanceToTarget >= nearestDistance) continue; nearestDistance = distanceToTarget; nearestTargetTransform = colliderWithinRange[i].transform; } SetTargetTransform(nearestTargetTransform, true); } private void SetOutlineColor(Color color) { foreach (var skin in skinMaterialList) { skin.SetColor(OutlineColorHash, color); } } public void SetIsCommanded(bool value, bool useBehaviorTreeVariable = false) { isCommanded = value; if (!useBehaviorTreeVariable) return; Utils.SetBehaviorVariable(behaviorTree, "IsCommanded", value); } protected override void SetCurrentHp(float value, bool useBehaviorTreeVariable = false) { PirateStat.CurrentHp = value; if (!useBehaviorTreeVariable) return; Utils.SetBehaviorVariable(behaviorTree, "CurrentHp", value); } protected override void RemoveAiListElement() { if (pirateUnit.pirateUnitStat.PirateAiList.Contains(this)) { pirateUnit.pirateUnitStat.PirateAiList.Remove(this); } } public bool GetIsAttackCoroutine() => isAttackCoroutine; public bool GetIsCommanded() => isCommanded; public void SetAttackerType(EAttackerType type) => PirateStat.AttackerType = type; public void SetOffenseType(EOffenseType type) => PirateStat.OffenseType = type; public void SetDefenseType(EDefenseType type) => PirateStat.DefenseType = type; public void ResetHighlight() => SetOutlineColor(defaultSkinColor); public void MouseEnterHighlight() => SetOutlineColor(mouseEnterHighlightSkinColor); public void SelectedHighlight() => SetOutlineColor(selectedSkinColor); #endregion } }