using System; using System.Collections.Generic; using System.Linq; using Cielonos.MainGame.Characters; using Cielonos.MainGame.Inventory; using Cielonos.UI; using Sirenix.OdinInspector; using SLSFramework.General; using SLSFramework.LeanPoolAssistance; using SLSFramework.WwiseAssistance; using SLSUtilities.FunctionalAnimation; using UnityEngine; namespace Cielonos.MainGame { public abstract partial class AttackAreaBase : PooledObject { [Title("References")] public CharacterBase creator; public bool isGeneratedByPlayer => creator == MainGameManager.Instance.player; public ItemBase itemSource; public List targetFractions; public Transform topParent; public AudioContainer audioContainer; public Collider areaCollider; public Dictionary functionalParts; [Title("Status")] public bool isEnabling; public bool canTriggerHitEvent = true; public Action updateAction; [Title("Submodules")] [HideInEditorMode] public AttackSubmodule attackSm; [HideInEditorMode] public TimeSubmodule timeSm; [HideInEditorMode] public HitSubmodule hitSm; [HideInEditorMode] public MoveSubmoduleBase moveSm; [HideInEditorMode] public RaycastSubmodule raycastSm; [HideInEditorMode] public ForceSubmodule forceSm; [HideInEditorMode] public ReactionSubmodule reactionSm; #if UNITY_EDITOR private void Reset() { isAutoDespawn = false; } #endif public T Initialize(CharacterBase creator, params Fraction[] targetFractions) where T : AttackAreaBase { return Initialize(creator, null, targetFractions); } public T Initialize(CharacterBase creator, ItemBase itemSource, params Fraction[] targetFractions) where T : AttackAreaBase { this.isEnabling = true; this.creator = creator; this.itemSource = itemSource; this.targetFractions = targetFractions.ToList(); this.topParent = transform; this.canTriggerHitEvent = true; attackSm = null; timeSm = null; hitSm = null; moveSm = null; raycastSm = null; forceSm = null; reactionSm = null; areaCollider = GetComponent(); if (areaCollider != null) { //areaCollider.excludeLayers = LayerMask.GetMask("AttackAreaVFX", "DecoVFX", "Ignore Raycast"); } audioContainer = GetComponent(); if (audioContainer != null) { audioContainer.soundEventDictionary = new Dictionary(); } while (topParent.parent != null && //topParent.parent != creator.flexibleCenterPoint && //topParent.parent != creator.staticCenterPoint && topParent.parent != creator.transform) { topParent = topParent.parent; } foreach (TrailRenderer trail in GetComponentsInChildren()) { trail.Clear(); } this.SetReactionSubmodule(); return this as T; } protected virtual void Update() { raycastSm?.Update(); updateAction?.Invoke(); hitSm?.Update(); timeSm?.Update(); moveSm?.Update(); } } public partial class AttackAreaBase { #region AttackSubmodule public T SetAttackSubmodule(AttackUnit attackUnit, GameObject hitVFX = null) where T : AttackAreaBase { attackSm = new AttackSubmodule(this, attackUnit.GetAttackValue(), hitVFX == null ? attackUnit.GetHitVFX() : hitVFX); return this as T; } #endregion #region TimeSubmodule public T SetTimeSubmodule(float lifeTime) where T : AttackAreaBase { timeSm = new TimeSubmodule(this, lifeTime); return this as T; } public T SetTimeSubmodule(float lifeTime, float delayTime, float enableTime = -1f, Action enableAction = null, Action timeOutAction = null) where T : AttackAreaBase { enableTime = enableTime < 0 ? lifeTime * 0.1f : enableTime; timeSm = new TimeSubmodule(this, lifeTime, delayTime, enableTime, enableAction, timeOutAction); return this as T; } #endregion #region HitSubmodule public T SetHitSubmodule() where T : AttackAreaBase { hitSm = new HitSubmodule(this); return this as T; } public T SetHitSubmodule(float hitInterval, int hitCount) where T : AttackAreaBase { hitSm = new HitSubmodule(this, hitInterval, hitCount); return this as T; } #endregion #region LinearDirectionMoveModule public T SetLinearDirectionMoveModule(Vector3 direction, float speed, float acceleration = 0, bool overrideRotation = true, bool disableNegative = true, bool stopWhenHit = true, float timeScaleCoefficient = 1) where T : AttackAreaBase { moveSm = new LinearDirectionMoveSubmodule(this, direction, speed, acceleration, overrideRotation, disableNegative, stopWhenHit, timeScaleCoefficient); return this as T; } #endregion #region TraceMoveModule public T SetTraceMoveModule(CharacterBase target, float moveSpeed, float moveAcceleration, float angularSpeed, float angularAcceleration, Vector3 initialDirection, bool stopWhenHit = true) where T : AttackAreaBase { moveSm = new TraceMoveSubmodule(this, target, moveSpeed, moveAcceleration, angularSpeed, angularAcceleration, initialDirection, stopWhenHit); return this as T; } #endregion #region RaycastSubmodule /// /// 设置射线检测子模块 /// /// 射线方向 /// 球形射线半径,若小于等于0则为直线射线 /// 射线长度,若小于0则为动态长度(与移动速度相等) public T SetRaycastSubmodule(Vector3 direction = default, float rayRadius = -1f, float rayLength = -1f) where T : AttackAreaBase { raycastSm = new RaycastSubmodule(this, direction, rayLength, rayRadius); raycastSm.Update(); return this as T; } #endregion #region ForceSubmodule public T SetForceSubmodule(float dynamicForce) where T : AttackAreaBase { forceSm = new ForceSubmodule(this, dynamicForce); return this as T; } public T SetForceSubmodule(Vector3 customForce) where T : AttackAreaBase { forceSm = new ForceSubmodule(this, customForce); return this as T; } public T SetForceSubmodule(float strengthXZ, bool isRepulsion, float strengthY = 0) where T : AttackAreaBase { forceSm = new ForceSubmodule(this, strengthXZ, isRepulsion, strengthY); return this as T; } #endregion #region ReactionSubmodule public T SetReactionSubmodule() where T : AttackAreaBase { reactionSm = new ReactionSubmodule(this); return this as T; } public T SetReactionSubmodule(bool canBeBlocked, bool hasPerfectBlock, bool canBreakBlock, bool canBeDodged, bool hasPerfectDodge, bool canBreakDodge) where T : AttackAreaBase { reactionSm = new ReactionSubmodule(this); reactionSm.SetBlock(canBeBlocked, hasPerfectBlock, canBreakBlock); reactionSm.SetDodge(canBeDodged, hasPerfectDodge, canBreakDodge); return this as T; } #endregion } public partial class AttackAreaBase { public virtual void HitCharacter(Collider characterCollider, Vector3 hitPosition) { } public virtual void HitEnvironment(Collider other, Vector3 hitPosition) { } protected virtual void HitOnTarget(Collider hitCollider, Vector3 hitPosition) { CharacterBase target = hitCollider.GetComponentInParent(); if (target == null) return; if (moveSm is { stopWhenHit: true }) { moveSm.canMove = false; } bool isBlocked = reactionSm?.CheckBlock(target, creator, hitPosition) ?? false; bool isDodged = reactionSm?.CheckDodge(target) ?? false; bool isMissed = false; // reactionModule?.SetMiss(creator.attributeModule.currentAttributes.GetValue("AttackMissProbability", 0)) ?? false; bool isEvaded = false; // reactionModule?.SetEvasion(player.attributeModule.currentAttributes.GetValue("EvasionProbability", 0)) ?? false; bool isDead = false; if (!isBlocked && !isDodged && !isMissed && !isEvaded) { //受击事件 InvokeHitEvents(target, hitPosition); //最终伤害结算 AttackResult attackResult = CalculateFinalDamage(target, out isDead); if (attackResult.finalDamage > 0) { target.eventSm.onGetAttacked.Invoke(this, attackResult); GenerateDamageNumber(attackResult.finalDamage, hitPosition); } //特效 GenerateHitEffect(target, hitCollider, hitPosition); //音效 PlaySoundFX(hitPosition); //计算力的方向 //CalculateAdditionalForceDirection(player); //打断,击退,受击动画 //float disruptionBreakLevel = attackModule.modifiedAttackValue.baseDisruptionBreakLevel; //SetReaction(player, disruptionBreakLevel); target.GetHit(attackSm.modifiedAttackValue.breakthroughType, attackSm.modifiedAttackValue.disruptionType); } //应用额外力 if (!isDead && !isBlocked && !isDodged && !isMissed && !isEvaded) { forceSm?.ApplyForce(target); } } } public partial class AttackAreaBase { protected virtual void InvokeHitEvents(CharacterBase target, Vector3 hitPosition) { hitSm.InvokeAllHitEvents(target, hitPosition); target.eventSm.onGetHit.Invoke(this); } protected virtual AttackResult CalculateFinalDamage(CharacterBase target, out bool isDead) { if (attackSm == null) { isDead = false; return new AttackResult(0); } return target.DealAttack(attackSm.modifiedAttackValue, out isDead); } protected virtual void GenerateDamageNumber(float finalDamage, Vector3 hitPosition) { bool isCritical = attackSm.modifiedAttackValue.isCritical; attackSm.SpawnDamageNumber(hitPosition, isCritical, finalDamage); } protected virtual GameObject GenerateHitEffect(CharacterBase target, Collider hitCollider, Vector3 hitPosition) { GameObject hitEffect = attackSm.SpawnHitVFX(hitPosition); attackSm.modifyHitEffectAction?.Invoke(hitEffect, target); return hitEffect; } protected virtual GameObject GenerateHitEffect(Vector3 hitPosition) { GameObject hitEffect = attackSm.SpawnHitVFX(hitPosition); attackSm.modifyHitEffectAction?.Invoke(hitEffect, null); return hitEffect; } protected virtual void PlaySoundFX(Vector3 hitPosition, string soundKey = "") { if (string.IsNullOrEmpty(soundKey)) { if (hitSm.isAutoPlayHitSound) { hitSm.hitSoundList.ForEach(hitSound => audioContainer.PlaySoundFX(hitSound, hitPosition)); } } else { audioContainer.PlaySoundFX(soundKey, hitPosition); } } } public class AttackAreaSubmoduleBase : SubmoduleBase { protected AttackAreaBase attackArea => owner; public bool isEnabling; public AttackAreaSubmoduleBase(AttackAreaBase owner) : base(owner) { isEnabling = true; } } public class AttackResult { public float startDamage; public float blockedDamage; public float finalDamage; public AttackResult(float startDamage) { this.startDamage = startDamage; this.blockedDamage = 0; this.finalDamage = 0; } } }