using System; using System.Collections.Generic; using System.Linq; using Cielonos.MainGame.Characters; using Cielonos.MainGame.Inventory; using Sirenix.OdinInspector; 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; 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 enableTime, float enableDelay, Action enableAction = null, Action timeOutAction = null) where T : AttackAreaBase { timeSm = new TimeSubmodule(this, lifeTime, enableTime, enableDelay, 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, float timeScaleCoefficient = 1) where T : AttackAreaBase { moveSm = new LinearDirectionMoveSubmodule(this, direction, speed, acceleration, overrideRotation, timeScaleCoefficient); return this as T; } #endregion #region TraceMoveModule public T SetTraceMoveModule(CharacterBase target, float moveSpeed, float moveAcceleration, float angularSpeed, float angularAcceleration, Vector3 initialDirection) where T : AttackAreaBase { moveSm = new TraceMoveSubmodule(this, target, moveSpeed, moveAcceleration, angularSpeed, angularAcceleration, initialDirection); 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; bool isBlocked = reactionSm?.CheckBlock(target, 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); //最终伤害结算 float finalDamage = CalculateFinalDamage(target, out isDead); if (finalDamage > 0) { //player.eventController.GetHurtEvents.InvokeAllEvents(this, finalDamage); GenerateDamageNumber(finalDamage, hitPosition); } //特效 GenerateHitEffect(target, hitCollider, hitPosition); //音效 PlaySoundFX(hitPosition); //计算力的方向 //CalculateAdditionalForceDirection(player); //打断,击退,受击动画 //float disruptionBreakLevel = attackModule.modifiedAttackValue.baseDisruptionBreakLevel; //SetReaction(player, disruptionBreakLevel); target.GetHit(attackSm.modifiedAttackValue.breakthroughType); } //应用额外力 if (!isDead && !isDodged && !isMissed && !isEvaded) { forceSm?.ApplyForce(target); } } } public partial class AttackAreaBase { protected virtual void InvokeHitEvents(CharacterBase target, Vector3 hitPosition) { hitSm.InvokeAllHitEvents(target, hitPosition); if (target is Player player) { //player.eventController.GetHitEvents.InvokeAllEvents(this); } /*else if (target is EnemyController enemy) { //enemy.enemyEventController.GetHitEvents.InvokeAllEvents(this); }*/ } protected virtual float CalculateFinalDamage(CharacterBase target, out bool isDead) { if (attackSm == null) { isDead = false; return 0; } float finalDamage = target.DealAttack(attackSm.modifiedAttackValue, out isDead); return finalDamage; } 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; } } }