2025-11-25 08:19:33 -05:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
2026-02-13 09:22:11 -05:00
|
|
|
|
using System.Text;
|
2026-03-20 12:07:44 -04:00
|
|
|
|
using Cielonos.MainGame.Buffs.Character;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
using Cielonos.MainGame.Characters;
|
2026-03-20 12:07:44 -04:00
|
|
|
|
using Cielonos.MainGame.Characters.Inventory;
|
2026-02-13 09:22:11 -05:00
|
|
|
|
using Cielonos.MainGame.UI;
|
|
|
|
|
|
using Lean.Pool;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
using Sirenix.OdinInspector;
|
2026-02-13 09:22:11 -05:00
|
|
|
|
using SLSUtilities.General;
|
|
|
|
|
|
using SLSUtilities.LeanPoolAssistance;
|
|
|
|
|
|
using SLSUtilities.WwiseAssistance;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
using SLSUtilities.FunctionalAnimation;
|
|
|
|
|
|
using UnityEngine;
|
2025-12-22 18:36:29 -05:00
|
|
|
|
using Random = UnityEngine.Random;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
|
|
|
|
|
|
namespace Cielonos.MainGame
|
|
|
|
|
|
{
|
2025-12-24 16:58:51 -05:00
|
|
|
|
public abstract partial class AttackAreaBase : SerializedMonoBehaviour
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
2025-12-22 18:36:29 -05:00
|
|
|
|
private static Dictionary<string, int> areaNameCountDictionary = new Dictionary<string, int>();
|
|
|
|
|
|
|
2025-11-25 08:19:33 -05:00
|
|
|
|
[Title("References")]
|
|
|
|
|
|
public CharacterBase creator;
|
|
|
|
|
|
public ItemBase itemSource;
|
|
|
|
|
|
public List<Fraction> targetFractions;
|
|
|
|
|
|
public Transform topParent;
|
|
|
|
|
|
public AudioContainer audioContainer;
|
|
|
|
|
|
public Collider areaCollider;
|
|
|
|
|
|
public Dictionary<string, GameObject> functionalParts;
|
|
|
|
|
|
|
|
|
|
|
|
[Title("Status")]
|
2025-12-22 18:36:29 -05:00
|
|
|
|
public string areaName;
|
2026-02-13 09:22:11 -05:00
|
|
|
|
public string spamGroupName;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
public bool isEnabling;
|
|
|
|
|
|
public bool canTriggerHitEvent = true;
|
|
|
|
|
|
public Action updateAction;
|
|
|
|
|
|
|
|
|
|
|
|
[Title("Submodules")]
|
2025-12-24 16:58:51 -05:00
|
|
|
|
[HideInEditorMode] public TransformSubmodule transformSm;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
[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;
|
|
|
|
|
|
|
|
|
|
|
|
public T Initialize<T>(CharacterBase creator, params Fraction[] targetFractions) where T : AttackAreaBase
|
|
|
|
|
|
{
|
|
|
|
|
|
return Initialize<T>(creator, null, targetFractions);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-18 13:57:19 -04:00
|
|
|
|
public virtual T Initialize<T>(CharacterBase creator, ItemBase itemSource, params Fraction[] targetFractions) where T : AttackAreaBase
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
|
|
|
|
|
this.isEnabling = true;
|
|
|
|
|
|
this.creator = creator;
|
|
|
|
|
|
this.itemSource = itemSource;
|
|
|
|
|
|
this.targetFractions = targetFractions.ToList();
|
|
|
|
|
|
this.topParent = transform;
|
|
|
|
|
|
this.canTriggerHitEvent = true;
|
|
|
|
|
|
|
2025-12-08 05:27:53 -05:00
|
|
|
|
attackSm = null;
|
|
|
|
|
|
timeSm = null;
|
|
|
|
|
|
hitSm = null;
|
|
|
|
|
|
moveSm = null;
|
|
|
|
|
|
raycastSm = null;
|
|
|
|
|
|
forceSm = null;
|
|
|
|
|
|
reactionSm = null;
|
|
|
|
|
|
|
2025-11-25 08:19:33 -05:00
|
|
|
|
areaCollider = GetComponent<Collider>();
|
|
|
|
|
|
if (areaCollider != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
//areaCollider.excludeLayers = LayerMask.GetMask("AttackAreaVFX", "DecoVFX", "Ignore Raycast");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
audioContainer = GetComponent<AudioContainer>();
|
|
|
|
|
|
if (audioContainer != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
audioContainer.soundEventDictionary = new Dictionary<string, AK.Wwise.Event>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
while (topParent.parent != null &&
|
|
|
|
|
|
//topParent.parent != creator.flexibleCenterPoint &&
|
|
|
|
|
|
//topParent.parent != creator.staticCenterPoint &&
|
|
|
|
|
|
topParent.parent != creator.transform)
|
|
|
|
|
|
{
|
|
|
|
|
|
topParent = topParent.parent;
|
|
|
|
|
|
}
|
2025-12-22 18:36:29 -05:00
|
|
|
|
|
|
|
|
|
|
if (!areaNameCountDictionary.TryAdd(topParent.name, 1))
|
|
|
|
|
|
{
|
|
|
|
|
|
areaNameCountDictionary[topParent.name]++;
|
|
|
|
|
|
}
|
|
|
|
|
|
areaName = $"{topParent.name}_{areaNameCountDictionary[topParent.name]}";
|
2026-02-13 09:22:11 -05:00
|
|
|
|
spamGroupName = creator.name + (itemSource != null ? $"_{itemSource.name}" : "");
|
2025-11-25 08:19:33 -05:00
|
|
|
|
|
|
|
|
|
|
foreach (TrailRenderer trail in GetComponentsInChildren<TrailRenderer>())
|
|
|
|
|
|
{
|
|
|
|
|
|
trail.Clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.SetReactionSubmodule<T>();
|
2025-12-24 16:58:51 -05:00
|
|
|
|
|
|
|
|
|
|
BattleManager.AttackAreaSm.Register(this);
|
|
|
|
|
|
topParent.GetComponent<VFXObject>().onDespawnAction = () =>
|
|
|
|
|
|
{
|
|
|
|
|
|
BattleManager.AttackAreaSm.Unregister(this);
|
|
|
|
|
|
};
|
2025-11-25 08:19:33 -05:00
|
|
|
|
|
|
|
|
|
|
return this as T;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual void Update()
|
|
|
|
|
|
{
|
2025-12-24 16:58:51 -05:00
|
|
|
|
transformSm?.Update();
|
2025-11-25 08:19:33 -05:00
|
|
|
|
raycastSm?.Update();
|
|
|
|
|
|
updateAction?.Invoke();
|
|
|
|
|
|
hitSm?.Update();
|
|
|
|
|
|
timeSm?.Update();
|
|
|
|
|
|
moveSm?.Update();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public partial class AttackAreaBase
|
|
|
|
|
|
{
|
2025-12-24 16:58:51 -05:00
|
|
|
|
#region TransformSubmodule
|
|
|
|
|
|
|
|
|
|
|
|
public T SetTransformSubmodule<T>(Transform target = null, float delay = 0) where T : AttackAreaBase
|
|
|
|
|
|
{
|
|
|
|
|
|
transformSm = new TransformSubmodule(this, target, delay);
|
|
|
|
|
|
return this as T;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
2025-11-25 08:19:33 -05:00
|
|
|
|
#region AttackSubmodule
|
2026-01-03 18:19:39 -05:00
|
|
|
|
|
2025-11-25 08:19:33 -05:00
|
|
|
|
public T SetAttackSubmodule<T>(AttackUnit attackUnit, GameObject hitVFX = null) where T : AttackAreaBase
|
2026-01-03 18:19:39 -05:00
|
|
|
|
{
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-25 08:19:33 -05:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region TimeSubmodule
|
|
|
|
|
|
public T SetTimeSubmodule<T>(float lifeTime) where T : AttackAreaBase
|
|
|
|
|
|
{
|
|
|
|
|
|
timeSm = new TimeSubmodule(this, lifeTime);
|
|
|
|
|
|
return this as T;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 09:22:11 -05:00
|
|
|
|
public T SetTimeSubmodule<T>(float lifeTime, float delayTime, float enableTime = 0.06f,
|
2025-11-25 08:19:33 -05:00
|
|
|
|
Action enableAction = null, Action timeOutAction = null) where T : AttackAreaBase
|
|
|
|
|
|
{
|
2025-12-08 05:27:53 -05:00
|
|
|
|
timeSm = new TimeSubmodule(this, lifeTime, delayTime, enableTime, enableAction, timeOutAction);
|
2025-11-25 08:19:33 -05:00
|
|
|
|
return this as T;
|
|
|
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region HitSubmodule
|
|
|
|
|
|
public T SetHitSubmodule<T>() where T : AttackAreaBase
|
|
|
|
|
|
{
|
|
|
|
|
|
hitSm = new HitSubmodule(this);
|
|
|
|
|
|
return this as T;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public T SetHitSubmodule<T>(float hitInterval, int hitCount) where T : AttackAreaBase
|
|
|
|
|
|
{
|
|
|
|
|
|
hitSm = new HitSubmodule(this, hitInterval, hitCount);
|
|
|
|
|
|
return this as T;
|
|
|
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region LinearDirectionMoveModule
|
|
|
|
|
|
public T SetLinearDirectionMoveModule<T>(Vector3 direction, float speed,
|
2025-12-08 05:27:53 -05:00
|
|
|
|
float acceleration = 0, bool overrideRotation = true, bool disableNegative = true, bool stopWhenHit = true,
|
|
|
|
|
|
float timeScaleCoefficient = 1) where T : AttackAreaBase
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
2025-12-08 05:27:53 -05:00
|
|
|
|
moveSm = new LinearDirectionMoveSubmodule(this, direction, speed, acceleration,
|
|
|
|
|
|
overrideRotation, disableNegative, stopWhenHit, timeScaleCoefficient);
|
2025-11-25 08:19:33 -05:00
|
|
|
|
return this as T;
|
|
|
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region TraceMoveModule
|
|
|
|
|
|
|
2025-12-24 16:58:51 -05:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 设置自适应追踪移动子模块,根据和目标的距离自动连接和断开追踪
|
|
|
|
|
|
/// 断开追踪后,自动选择范围内最近的目标进行追踪
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public T SetAdaptiveTraceMoveModule<T>(CharacterBase target, float moveSpeed, float moveAcceleration,
|
|
|
|
|
|
float angularSpeed, float angularAcceleration, Vector3 initialDirection,
|
|
|
|
|
|
bool autoConnect = true, bool autoDisconnect = true, float detectRadius = 10f, bool stopWhenHit = true) where T : AttackAreaBase
|
|
|
|
|
|
{
|
|
|
|
|
|
moveSm = new TraceMoveSubmodule(this, target, moveSpeed, moveAcceleration, angularSpeed,
|
|
|
|
|
|
angularAcceleration, initialDirection, autoConnect, autoDisconnect, detectRadius, stopWhenHit);
|
|
|
|
|
|
return this as T;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 设置不可更改目标的追踪移动子模块
|
|
|
|
|
|
/// 永远追踪指定目标,不会断开
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public T SetUnchangeableTraceMoveModule<T>(CharacterBase target, float moveSpeed, float moveAcceleration,
|
2025-12-08 05:27:53 -05:00
|
|
|
|
float angularSpeed, float angularAcceleration, Vector3 initialDirection, bool stopWhenHit = true) where T : AttackAreaBase
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
2025-12-24 16:58:51 -05:00
|
|
|
|
moveSm = new TraceMoveSubmodule(this, target, moveSpeed, moveAcceleration, angularSpeed,
|
|
|
|
|
|
angularAcceleration, initialDirection, false, false, 0, stopWhenHit);
|
|
|
|
|
|
return this as T;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 设置可分离目标的追踪移动子模块
|
|
|
|
|
|
/// 一旦目标超出检测范围则断开追踪,断开后不再追踪其他目标
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public T SeDetachableTraceMoveModule<T>(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);
|
2025-11-25 08:19:33 -05:00
|
|
|
|
return this as T;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region RaycastSubmodule
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 设置射线检测子模块
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="direction">射线方向</param>
|
|
|
|
|
|
/// <param name="rayRadius">球形射线半径,若小于等于0则为直线射线</param>
|
|
|
|
|
|
/// <param name="rayLength">射线长度,若小于0则为动态长度(与移动速度相等)</param>
|
|
|
|
|
|
public T SetRaycastSubmodule<T>(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<T>(float dynamicForce) where T : AttackAreaBase
|
|
|
|
|
|
{
|
|
|
|
|
|
forceSm = new ForceSubmodule(this, dynamicForce);
|
|
|
|
|
|
return this as T;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public T SetForceSubmodule<T>(Vector3 customForce) where T : AttackAreaBase
|
|
|
|
|
|
{
|
|
|
|
|
|
forceSm = new ForceSubmodule(this, customForce);
|
|
|
|
|
|
return this as T;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public T SetForceSubmodule<T>(float strengthXZ, bool isRepulsion, float strengthY = 0) where T : AttackAreaBase
|
|
|
|
|
|
{
|
|
|
|
|
|
forceSm = new ForceSubmodule(this, strengthXZ, isRepulsion, strengthY);
|
|
|
|
|
|
return this as T;
|
|
|
|
|
|
}
|
2026-02-13 09:22:11 -05:00
|
|
|
|
|
|
|
|
|
|
public T SetForceSubmodule<T>(float strengthXZ, bool isRepulsion, bool isLaunch,
|
|
|
|
|
|
float strengthY = 0, float stasisDuration = 0f) where T : AttackAreaBase
|
|
|
|
|
|
{
|
|
|
|
|
|
forceSm = new ForceSubmodule(this, strengthXZ, isRepulsion, isLaunch, strengthY, stasisDuration);
|
|
|
|
|
|
return this as T;
|
|
|
|
|
|
}
|
2025-11-25 08:19:33 -05:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region ReactionSubmodule
|
|
|
|
|
|
public T SetReactionSubmodule<T>() where T : AttackAreaBase
|
|
|
|
|
|
{
|
|
|
|
|
|
reactionSm = new ReactionSubmodule(this);
|
|
|
|
|
|
return this as T;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public T SetReactionSubmodule<T>(bool canBeBlocked, bool hasPerfectBlock, bool canBreakBlock,
|
2026-02-13 09:22:11 -05:00
|
|
|
|
bool canBeDodged, bool hasPerfectDodge, bool canBreakDodge, bool canBeReflected) where T : AttackAreaBase
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
|
|
|
|
|
reactionSm = new ReactionSubmodule(this);
|
|
|
|
|
|
reactionSm.SetBlock(canBeBlocked, hasPerfectBlock, canBreakBlock);
|
|
|
|
|
|
reactionSm.SetDodge(canBeDodged, hasPerfectDodge, canBreakDodge);
|
2026-02-13 09:22:11 -05:00
|
|
|
|
reactionSm.SetReflection(canBeReflected);
|
2025-11-25 08:19:33 -05:00
|
|
|
|
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)
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 09:22:11 -05:00
|
|
|
|
protected virtual void HitOnTarget(Collider hitCollider, Vector3 hitPosition, out AttackResult attackResult)
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
|
|
|
|
|
CharacterBase target = hitCollider.GetComponentInParent<CharacterBase>();
|
2026-02-13 09:22:11 -05:00
|
|
|
|
|
|
|
|
|
|
attackResult = new AttackResult(creator, target, spamGroupName, hitPosition);
|
|
|
|
|
|
|
|
|
|
|
|
AttackUnit attackUnit = attackSm!.attackUnit;
|
|
|
|
|
|
|
|
|
|
|
|
if (!attackUnit.isInvalidAttack)
|
|
|
|
|
|
{
|
2026-03-20 12:07:44 -04:00
|
|
|
|
// We will invoke onStartAttack down below with the AttackResult cloned.
|
2026-02-13 09:22:11 -05:00
|
|
|
|
}
|
2025-11-25 08:19:33 -05:00
|
|
|
|
|
2026-02-13 09:22:11 -05:00
|
|
|
|
if (target == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-11-25 08:19:33 -05:00
|
|
|
|
|
2025-12-08 05:27:53 -05:00
|
|
|
|
if (moveSm is { stopWhenHit: true })
|
|
|
|
|
|
{
|
|
|
|
|
|
moveSm.canMove = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 09:22:11 -05:00
|
|
|
|
attackResult.isBlocked = reactionSm?.CheckBlock(target, creator, hitPosition) ?? false;
|
|
|
|
|
|
attackResult.isDodged = reactionSm?.CheckDodge(target) ?? false;
|
|
|
|
|
|
attackResult.isReflected = reactionSm?.CheckReflection(target) ?? false;
|
|
|
|
|
|
attackResult.isMissed = false; // reactionModule?.SetMiss(creator.attributeModule.currentAttributes.GetValue("AttackMissProbability", 0)) ?? false;
|
|
|
|
|
|
attackResult.isEvaded = false; // reactionModule?.SetEvasion(player.attributeModule.currentAttributes.GetValue("EvasionProbability", 0)) ?? false;
|
|
|
|
|
|
attackResult.causedDeath = false;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
|
2026-02-13 09:22:11 -05:00
|
|
|
|
if (!attackResult.isBlocked && !attackResult.isDodged &&
|
|
|
|
|
|
!attackResult.isMissed && !attackResult.isEvaded)
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
2026-02-13 09:22:11 -05:00
|
|
|
|
if (!attackUnit.isInvalidAttack)
|
2026-01-03 18:19:39 -05:00
|
|
|
|
{
|
2026-02-13 09:22:11 -05:00
|
|
|
|
if (attackUnit.isIndependentPerHit)
|
|
|
|
|
|
{
|
|
|
|
|
|
attackSm.attackValue = attackSm.attackUnit.GetAttackValue(creator);
|
|
|
|
|
|
}
|
2026-03-20 12:07:44 -04:00
|
|
|
|
|
|
|
|
|
|
attackResult.attackValue = attackSm.attackValue.Clone();
|
|
|
|
|
|
|
|
|
|
|
|
creator.eventSm.onStartAttack.Invoke(this, target, attackResult);
|
|
|
|
|
|
target.eventSm.onBeforeGetAttacked.Invoke(this, attackResult);
|
|
|
|
|
|
|
|
|
|
|
|
if (attackResult.isImmune || attackResult.isForceInterrupt)
|
|
|
|
|
|
{
|
|
|
|
|
|
return; // 被机制一票否决,直接短路
|
|
|
|
|
|
}
|
2026-01-03 18:19:39 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-20 12:07:44 -04:00
|
|
|
|
BreakthroughType breakthroughType = attackSm.attackValue.breakthroughType;
|
|
|
|
|
|
DisruptionType disruptionType = attackSm.attackValue.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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
bool disrupted = target.GetHit(breakthroughType, out float recoveryTime, disruptionType, direction);
|
|
|
|
|
|
|
2025-11-25 08:19:33 -05:00
|
|
|
|
//受击事件
|
|
|
|
|
|
InvokeHitEvents(target, hitPosition);
|
2026-03-20 12:07:44 -04:00
|
|
|
|
|
|
|
|
|
|
//特效
|
|
|
|
|
|
GenerateHitEffect(target, hitCollider, hitPosition);
|
|
|
|
|
|
|
|
|
|
|
|
//音效
|
|
|
|
|
|
PlaySoundFX(hitPosition);
|
|
|
|
|
|
|
2026-02-13 09:22:11 -05:00
|
|
|
|
if (!attackUnit.isInvalidAttack)
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
2026-02-13 09:22:11 -05:00
|
|
|
|
//最终伤害结算
|
2026-03-20 12:07:44 -04:00
|
|
|
|
target.TakeDamage(ref attackResult);
|
2026-04-18 13:57:19 -04:00
|
|
|
|
|
|
|
|
|
|
Attack.AttackType attackType = attackResult.attackValue.attackType;
|
|
|
|
|
|
bool isCritical = attackResult.attackValue.isCritical;
|
|
|
|
|
|
MainGameBaseCollection.Instance.DamageNumber(attackType, isCritical)
|
|
|
|
|
|
.Spawn(attackResult.hitPosition, attackResult.finalDamage, transform)
|
|
|
|
|
|
.SetSpamGroup(attackResult.spamGroupID);
|
|
|
|
|
|
|
2026-02-13 09:22:11 -05:00
|
|
|
|
creator.eventSm.onFinishAttack.Invoke(this, target, attackResult);
|
|
|
|
|
|
if (attackResult.finalDamage > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
target.eventSm.onAfterGetAttacked.Invoke(this, attackResult);
|
|
|
|
|
|
target.eventSm.onHealthChanged.Invoke(-attackResult.finalDamage);
|
|
|
|
|
|
}
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-13 09:22:11 -05:00
|
|
|
|
|
2025-11-25 08:19:33 -05:00
|
|
|
|
//应用额外力
|
2026-02-13 09:22:11 -05:00
|
|
|
|
if (!attackResult.causedDeath &&
|
|
|
|
|
|
!attackResult.isBlocked && !attackResult.isDodged &&
|
|
|
|
|
|
!attackResult.isMissed && !attackResult.isEvaded)
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
|
|
|
|
|
forceSm?.ApplyForce(target);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public partial class AttackAreaBase
|
|
|
|
|
|
{
|
|
|
|
|
|
protected virtual void InvokeHitEvents(CharacterBase target, Vector3 hitPosition)
|
|
|
|
|
|
{
|
|
|
|
|
|
hitSm.InvokeAllHitEvents(target, hitPosition);
|
2026-03-20 12:07:44 -04:00
|
|
|
|
if (target is Automata automata)
|
|
|
|
|
|
{
|
|
|
|
|
|
automata.behaviorSc.DispatchContextEvent("GetHit");
|
|
|
|
|
|
}
|
2025-12-08 05:27:53 -05:00
|
|
|
|
target.eventSm.onGetHit.Invoke(this);
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual GameObject GenerateHitEffect(CharacterBase target, Collider hitCollider, Vector3 hitPosition)
|
|
|
|
|
|
{
|
2025-12-17 04:19:38 -05:00
|
|
|
|
GameObject hitEffect = attackSm.SpawnHitVFX(creator, hitPosition);
|
2025-11-25 08:19:33 -05:00
|
|
|
|
attackSm.modifyHitEffectAction?.Invoke(hitEffect, target);
|
|
|
|
|
|
return hitEffect;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual GameObject GenerateHitEffect(Vector3 hitPosition)
|
|
|
|
|
|
{
|
2025-12-17 04:19:38 -05:00
|
|
|
|
GameObject hitEffect = attackSm.SpawnHitVFX(creator, hitPosition);
|
2025-11-25 08:19:33 -05:00
|
|
|
|
attackSm.modifyHitEffectAction?.Invoke(hitEffect, null);
|
|
|
|
|
|
return hitEffect;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-20 12:07:44 -04:00
|
|
|
|
protected virtual void PlaySoundFX(Vector3 hitPosition, uint soundID = AkUnitySoundEngine.AK_INVALID_PLAYING_ID)
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
2026-03-20 12:07:44 -04:00
|
|
|
|
if (soundID == AkUnitySoundEngine.AK_INVALID_PLAYING_ID)
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
|
|
|
|
|
if (hitSm.isAutoPlayHitSound)
|
|
|
|
|
|
{
|
2026-03-20 12:07:44 -04:00
|
|
|
|
hitSm.hitSoundList.ForEach(hitSound =>
|
|
|
|
|
|
{
|
|
|
|
|
|
AudioManager.Post(hitSound, hitPosition);
|
|
|
|
|
|
});
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2026-03-20 12:07:44 -04:00
|
|
|
|
AudioManager.Post(soundID, hitPosition);
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public class AttackAreaSubmoduleBase : SubmoduleBase<AttackAreaBase>
|
|
|
|
|
|
{
|
|
|
|
|
|
protected AttackAreaBase attackArea => owner;
|
|
|
|
|
|
public bool isEnabling;
|
|
|
|
|
|
|
|
|
|
|
|
public AttackAreaSubmoduleBase(AttackAreaBase owner) : base(owner)
|
|
|
|
|
|
{
|
|
|
|
|
|
isEnabling = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-08 05:27:53 -05:00
|
|
|
|
|
|
|
|
|
|
public class AttackResult
|
|
|
|
|
|
{
|
2026-02-13 09:22:11 -05:00
|
|
|
|
public CharacterBase attacker;
|
|
|
|
|
|
public CharacterBase target;
|
|
|
|
|
|
public Vector3 hitPosition;
|
|
|
|
|
|
public string spamGroupID;
|
2025-12-08 05:27:53 -05:00
|
|
|
|
|
2026-02-13 09:22:11 -05:00
|
|
|
|
public bool isBlocked;
|
|
|
|
|
|
public bool isDodged;
|
|
|
|
|
|
public bool isReflected;
|
|
|
|
|
|
public bool isMissed;
|
|
|
|
|
|
public bool isEvaded;
|
|
|
|
|
|
public bool causedDeath;
|
2026-03-20 12:07:44 -04:00
|
|
|
|
public bool isImmune;
|
|
|
|
|
|
public bool isForceInterrupt;
|
2026-02-13 09:22:11 -05:00
|
|
|
|
|
|
|
|
|
|
public AttackValue attackValue;
|
|
|
|
|
|
public float shieldBlockedDamage;
|
|
|
|
|
|
public float finalDamage;
|
|
|
|
|
|
|
|
|
|
|
|
public AttackResult(CharacterBase attacker, CharacterBase target,
|
|
|
|
|
|
string spamGroupMainPart, Vector3 hitPosition = default)
|
2025-12-08 05:27:53 -05:00
|
|
|
|
{
|
2026-02-13 09:22:11 -05:00
|
|
|
|
this.attacker = attacker;
|
|
|
|
|
|
this.target = target;
|
|
|
|
|
|
StringBuilder fullIDBuilder = new StringBuilder();
|
|
|
|
|
|
fullIDBuilder.Append(attacker is null ? "Null" : this.attacker.GetInstanceID().ToString());
|
|
|
|
|
|
fullIDBuilder.Append("_");
|
|
|
|
|
|
fullIDBuilder.Append(spamGroupMainPart);
|
|
|
|
|
|
fullIDBuilder.Append("_");
|
|
|
|
|
|
fullIDBuilder.Append(target is null ? "Null" : this.target.GetInstanceID().ToString());
|
|
|
|
|
|
this.spamGroupID = fullIDBuilder.ToString();
|
2026-03-20 12:07:44 -04:00
|
|
|
|
//Debug.Log($"Generated AttackResult with SpamGroupID: {this.spamGroupID}");
|
2026-02-13 09:22:11 -05:00
|
|
|
|
this.hitPosition = hitPosition;
|
2025-12-08 05:27:53 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|