using System; using System.Collections.Generic; using System.Linq; using System.Text; using Cielonos.MainGame.Buffs.Character; using Cielonos.MainGame.Characters; using Cielonos.MainGame.Inventory; using Cielonos.MainGame.UI; using Lean.Pool; using Sirenix.OdinInspector; using SLSUtilities.General; using SLSUtilities.LeanPoolAssistance; using SLSUtilities.WwiseAssistance; using SLSUtilities.FunctionalAnimation; using UnityEngine; using Random = UnityEngine.Random; namespace Cielonos.MainGame { public abstract partial class AttackAreaBase : SerializedMonoBehaviour { private static Dictionary areaNameCountDictionary = new Dictionary(); [Title("References")] public CharacterBase creator; public ItemBase itemSource; public List targetFractions; public Transform topParent; public Collider areaCollider; public Dictionary functionalParts; [Title("Status")] public string areaName; public string spamGroupName; public bool isEnabling; public bool canTriggerHitEvent = true; public List tags = new List(); public Action updateAction; [Title("Submodules")] [HideInEditorMode] public TransformSubmodule transformSm; [HideInEditorMode] public AttackSubmodule attackSm; [HideInEditorMode] public TimeSubmodule timeSm; [HideInEditorMode] public HitSubmodule hitSm; [HideInEditorMode] public MoveSubmoduleBase moveSm; [HideInEditorMode] public RaycastSubmodule raycastSm; [HideInEditorMode] public ImpulseSubmodule impulseSm; [HideInEditorMode] public ReactionSubmodule reactionSm; public T Initialize(CharacterBase creator, params Fraction[] targetFractions) where T : AttackAreaBase { return Initialize(creator, null, targetFractions); } public virtual T Initialize(CharacterBase creator, ItemBase itemSource, params Fraction[] targetFractions) where T : AttackAreaBase { this.isEnabling = false; this.creator = creator; this.itemSource = itemSource; this.targetFractions = targetFractions.ToList(); this.canTriggerHitEvent = true; this.tags = new List(); attackSm = null; timeSm = null; hitSm = null; moveSm = null; raycastSm = null; impulseSm = null; reactionSm = null; areaCollider = GetComponent(); // 通过 VFXObject 组件确定 VFX 顶层节点,比遍历 parent 链更可靠。 VFXObject vfxObject = GetComponentInParent(); this.topParent = vfxObject != null ? vfxObject.transform : transform; if (!areaNameCountDictionary.TryAdd(topParent.name, 1)) { areaNameCountDictionary[topParent.name]++; } areaName = $"{topParent.name}_{areaNameCountDictionary[topParent.name]}"; spamGroupName = creator.name + (itemSource != null ? $"_{itemSource.name}" : ""); foreach (TrailRenderer trail in GetComponentsInChildren()) { trail.Clear(); } this.SetReactionSubmodule(); CombatManager.AttackAreaSm.Register(this); if (vfxObject != null) { vfxObject.onDespawnAction = () => { CombatManager.AttackAreaSm.Unregister(this); }; } this.isEnabling = true; return this as T; } protected virtual void Update() { transformSm?.Update(); raycastSm?.Update(); updateAction?.Invoke(); hitSm?.Update(); timeSm?.Update(); moveSm?.Update(); } } public partial class AttackAreaBase { #region TransformSubmodule public T SetTransformSubmodule(Transform target = null, float delay = 0) where T : AttackAreaBase { transformSm = new TransformSubmodule(this, target, delay); return this as T; } #endregion #region AttackSubmodule public T SetAttackSubmodule(AttackUnit attackUnit, GameObject hitVFX = null) where T : AttackAreaBase { if (attackUnit.useVFXDataHit) { hitVFX ??= itemSource != null ? itemSource.vfxData.Get(attackUnit.vfxUnitName).hitVFX : creator.vfxData.Get(attackUnit.vfxUnitName).hitVFX; } else { hitVFX ??= attackUnit.GetHitVFX(); } attackSm = new AttackSubmodule(this, attackUnit, 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 = 0.04f, Action enableAction = null, Action timeOutAction = null) where T : AttackAreaBase { 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 SetAdaptiveTraceMoveModule(CharacterBase target, float moveSpeed, float moveAcceleration, float angularSpeed, float angularAcceleration, Vector3 initialDirection, bool autoConnect = true, bool autoDisconnect = true, float detectRadius = 20f, bool stopWhenHit = true) where T : AttackAreaBase { moveSm = new TraceMoveSubmodule(this, target, moveSpeed, moveAcceleration, angularSpeed, angularAcceleration, initialDirection, autoConnect, autoDisconnect, detectRadius, stopWhenHit); return this as T; } /// /// 设置不可更改目标的追踪移动子模块 /// 永远追踪指定目标,不会断开 /// public T SetUnchangeableTraceMoveModule(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, false, false, 0, stopWhenHit); return this as T; } /// /// 设置可分离目标的追踪移动子模块 /// 一旦目标超出检测范围则断开追踪,断开后不再追踪其他目标 /// public T SeDetachableTraceMoveModule(CharacterBase target, float moveSpeed, float moveAcceleration, float angularSpeed, float angularAcceleration, Vector3 initialDirection, float detectRadius = 10f, bool stopWhenHit = true) where T : AttackAreaBase { moveSm = new TraceMoveSubmodule(this, target, moveSpeed, moveAcceleration, angularSpeed, angularAcceleration, initialDirection, false, true, detectRadius, 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 ImpulseSubmodule /// /// 创建脉冲子模块并返回实例,后续通过链式 With* 方法配置力模式和参数。 /// 注意:此方法返回 ImpulseSubmodule 而非 AttackAreaBase,因此应在主链末尾调用或单独使用。 /// public ImpulseSubmodule SetImpulseSubmodule(float duration = 0.5f, AnimationCurve curve = null) { impulseSm = new ImpulseSubmodule(this, duration, curve); return impulseSm; } #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, bool canBeReflected) where T : AttackAreaBase { reactionSm = new ReactionSubmodule(this); reactionSm.SetBlock(canBeBlocked, hasPerfectBlock, canBreakBlock); reactionSm.SetDodge(canBeDodged, hasPerfectDodge, canBreakDodge); reactionSm.SetReflection(canBeReflected); return this as T; } #endregion } public partial class AttackAreaBase { /// /// 检查目标角色是否为有效攻击目标:非创建者自身,且属于目标阵营。 /// public bool IsValidTarget(CharacterBase target) { if (target == null || target == creator) return false; return targetFractions.Contains(target.fraction); } public virtual void HitCharacter(Collider characterCollider, Vector3 hitPosition) { } public virtual void HitEnvironment(Collider other, Vector3 hitPosition) { } protected virtual void HitOnTarget(Collider hitCollider, Vector3 hitPosition, out Attack.Result attackResult) { CharacterBase target = hitCollider.GetComponentInParent(); // Phase 1: 构建上下文 var context = new Attack.Context(creator, target, spamGroupName, hitPosition); attackResult = new Attack.Result(context); AttackUnit attackUnit = attackSm!.attackUnit; if (target == null) { return; } if (moveSm is { stopWhenHit: true }) { moveSm.canMove = false; } attackResult.isBlocked = reactionSm?.CheckBlock(target, creator, hitPosition) ?? false; attackResult.isDodged = reactionSm?.CheckDodge(target) ?? false; attackResult.isReflected = reactionSm?.CheckReflection(target) ?? false; attackResult.isMissed = false; attackResult.isEvaded = false; attackResult.causedDeath = false; if (!attackResult.isBlocked && !attackResult.isDodged && !attackResult.isMissed && !attackResult.isEvaded) { // 被动闪避概率判定(独立于动作帧主动闪避) float evasionProbability = target.attributeSm[CharacterAttribute.EvasionProbability]; if (evasionProbability > 0f && Random.value < evasionProbability) { attackResult.isEvaded = true; } } if (!attackResult.isBlocked && !attackResult.isDodged && !attackResult.isMissed && !attackResult.isEvaded) { // 无论攻击是否 invalid,都需要填充 context.value 以供 breakthrough/disruption 读取 if (attackUnit.isIndependentPerHit) { attackSm.attackValue = attackSm.attackUnit.GetAttackValue(creator); } context.value = attackSm.attackValue.Clone(); if (!attackUnit.isInvalidAttack) { // Phase 2: 前置事件,订阅者通过 Context 修改攻击参数 creator.eventSm.onStartAttack.Invoke(this, target, context); target.eventSm.onBeforeGetAttacked.Invoke(this, context); for (var index = 0; index < target.buffSm.buffList.Count; index++) { var eventBuff = target.buffSm.buffList[index]; eventBuff.eventSubmodule?.onBeforeGetAttacked.Invoke(this, context); } if (context.isImmune || context.isForcedInterrupt) { return; // 被机制一票否决,直接短路 } } // 从 context.value 读取,因为前置事件可能已修改这些值 Breakthrough.Type breakthroughType = context.value.breakthroughType; DisruptionType disruptionType = context.value.disruptionType; Vector3 direction = (target.centerPoint.position - creator.centerPoint.position).normalized; //TODO: 后续制作更详细的打断和击退处理 bool canBreakthrough = target.CheckBreakthrough(breakthroughType); bool canDisrupt = target.CheckDisruption(disruptionType); if (target.buffSm.TryGetBuff(out BreakthroughResistanceModification buff)) { if (canBreakthrough && canDisrupt) { attackSm.breakthroughAction?.Invoke(target, hitPosition); target.eventSm.onGetBreakthrough?.Invoke(this); target.renderSc.SpawnShatters(breakthroughType, direction); } } bool disrupted = target.GetHit(breakthroughType, out float recoveryTime, disruptionType, direction); //受击事件 InvokeHitEvents(target, hitPosition); //特效 GenerateHitEffect(target, hitCollider, hitPosition); //音效 PlaySoundFX(hitPosition); if (!attackUnit.isInvalidAttack) { // Phase 3: 伤害结算 target.TakeDamage(attackResult, out _); // Phase 4: 后置事件,订阅者通过 Result 读取结算结果 // onHealthChanged 已由 AttributeSubmodule 值变更回调在 TakeDamage 内部自动触发 creator.eventSm.onFinishAttack.Invoke(this, target, attackResult); if (attackResult.finalDamage > 0) { target.eventSm.onAfterGetAttacked.Invoke(this, attackResult); for (var index = 0; index < target.buffSm.buffList.Count; index++) { var eventBuff = target.buffSm.buffList[index]; eventBuff.eventSubmodule?.onAfterGetAttacked.Invoke(this, attackResult); } } } } //应用额外力 if (!attackResult.causedDeath && !attackResult.isBlocked && !attackResult.isDodged && !attackResult.isMissed && !attackResult.isEvaded) { impulseSm?.ApplyImpulse(target); } } } public partial class AttackAreaBase { protected virtual void InvokeHitEvents(CharacterBase target, Vector3 hitPosition) { hitSm.InvokeAllHitEvents(target, hitPosition); if (target is Automata automata) { automata.behaviorSc.DispatchContextEvent("GetHit"); } target.eventSm.onGetHit.Invoke(this); } protected virtual GameObject GenerateHitEffect(CharacterBase target, Collider hitCollider, Vector3 hitPosition) { GameObject hitEffect = attackSm.SpawnHitVFX(creator, hitPosition); attackSm.modifyHitEffectAction?.Invoke(hitEffect, target); return hitEffect; } protected virtual GameObject GenerateHitEffect(Vector3 hitPosition) { GameObject hitEffect = attackSm.SpawnHitVFX(creator, hitPosition); attackSm.modifyHitEffectAction?.Invoke(hitEffect, null); return hitEffect; } protected virtual void PlaySoundFX(Vector3 hitPosition, uint soundID = AkUnitySoundEngine.AK_INVALID_PLAYING_ID) { if (soundID == AkUnitySoundEngine.AK_INVALID_PLAYING_ID) { if (hitSm.isAutoPlayHitSound) { hitSm.hitSoundList.ForEach(hitSound => { AudioManager.Post(hitSound, hitPosition); }); } } else { AudioManager.Post(soundID, hitPosition); } } } public class AttackAreaSubmoduleBase : SubmoduleBase { protected AttackAreaBase attackArea => owner; public bool isEnabling; public AttackAreaSubmoduleBase(AttackAreaBase owner) : base(owner) { isEnabling = true; } } }