Files
Cielonos/Assets/Scripts/MainGame/AttackArea/AttackAreaBase.cs
2025-12-22 18:36:29 -05:00

376 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
using Random = UnityEngine.Random;
namespace Cielonos.MainGame
{
public abstract partial class AttackAreaBase : MonoBehaviour
{
private static Dictionary<string, int> areaNameCountDictionary = new Dictionary<string, int>();
[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")]
public string areaName;
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;
public T Initialize<T>(CharacterBase creator, params Fraction[] targetFractions) where T : AttackAreaBase
{
return Initialize<T>(creator, null, targetFractions);
}
public T Initialize<T>(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<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;
}
if (!areaNameCountDictionary.TryAdd(topParent.name, 1))
{
areaNameCountDictionary[topParent.name]++;
}
areaName = $"{topParent.name}_{areaNameCountDictionary[topParent.name]}";
foreach (TrailRenderer trail in GetComponentsInChildren<TrailRenderer>())
{
trail.Clear();
}
this.SetReactionSubmodule<T>();
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<T>(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<T>(float lifeTime) where T : AttackAreaBase
{
timeSm = new TimeSubmodule(this, lifeTime);
return this as T;
}
public T SetTimeSubmodule<T>(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<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,
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<T>(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
/// <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;
}
#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,
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<CharacterBase>();
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);
BreakthroughType breakthroughType = attackSm.modifiedAttackValue.breakthroughType;
DisruptionType disruptionType = attackSm.modifiedAttackValue.disruptionType;
Vector3 direction = (target.flexibleCenterPoint.position - creator.flexibleCenterPoint.position).normalized;
target.GetHit(breakthroughType, out float recoveryTime, disruptionType, direction);
}
//应用额外力
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(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, 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<AttackAreaBase>
{
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;
}
}
}