299 lines
10 KiB
C#
299 lines
10 KiB
C#
using System;
|
||
using System.Text;
|
||
using Cielonos.MainGame.Buffs.Character;
|
||
using Cielonos.MainGame.Characters.Inventory;
|
||
using SickscoreGames.HUDNavigationSystem;
|
||
using Sirenix.OdinInspector;
|
||
using SLSUtilities.General;
|
||
using SLSUtilities.WwiseAssistance;
|
||
using SLSUtilities.FunctionalAnimation;
|
||
using UniRx;
|
||
using UnityEngine;
|
||
using UnityEngine.AI;
|
||
using UnityEngine.Serialization;
|
||
|
||
namespace Cielonos.MainGame.Characters
|
||
{
|
||
public enum Fraction
|
||
{
|
||
Player = 0,
|
||
AlliedMinion = 1,
|
||
Enemy = 10,
|
||
Neutral = 20
|
||
}
|
||
|
||
public partial class CharacterBase : SerializedMonoBehaviour, IFuncAnimExecutor
|
||
{
|
||
public Fraction fraction;
|
||
public Transform centerPoint => bodyPartsSc.flexibleCenterPoint;
|
||
public Vector3 centerPosition => centerPoint.position;
|
||
|
||
[TitleGroup("Data")]
|
||
public AttributeData attributeData;
|
||
public VFXData vfxData;
|
||
public BlockData blockData;
|
||
public BaseAnimationGroup baseAnimationGroup;
|
||
|
||
[TitleGroup("Submodules")]
|
||
public SelfTimeSubmodule selfTimeSm;
|
||
public AttributeSubmodule attributeSm;
|
||
|
||
public EventSubmodule eventSm;
|
||
public BuffSubmodule buffSm;
|
||
public StatusSubmodule statusSm;
|
||
|
||
[TitleGroup("Subcontrollers")]
|
||
public CollisionSubcontrollerBase collisionSc;
|
||
public MovementSubcontrollerBase movementSc;
|
||
public AnimationSubcontrollerBase animationSc;
|
||
public RenderSubcontrollerBase renderSc;
|
||
public BodyPartsSubcontroller bodyPartsSc;
|
||
public AudioSubcontroller audioSc;
|
||
public ReactionSubcontroller reactionSc;
|
||
public FeedbackSubcontroller feedbackSc;
|
||
|
||
[TitleGroup("Navigation")]
|
||
public HUDNavigationElement navigationElement;
|
||
|
||
protected void Awake()
|
||
{
|
||
InitializeSubmodules();
|
||
InitializeSubcontrollers();
|
||
}
|
||
|
||
protected virtual void Start()
|
||
{
|
||
vfxData?.Initialize(this);
|
||
selfTimeSm?.SetUp(this);
|
||
|
||
if (fraction == Fraction.Enemy)
|
||
{
|
||
BattleManager.EnemySm.activeEnemiesList.Add(this);
|
||
}
|
||
}
|
||
|
||
protected virtual void Update()
|
||
{
|
||
selfTimeSm.Update();
|
||
buffSm.Update();
|
||
}
|
||
|
||
public virtual void Die()
|
||
{
|
||
Destroy(gameObject);//TODO: 后续改为死亡动画+回收
|
||
}
|
||
}
|
||
|
||
public partial class CharacterBase
|
||
{
|
||
protected virtual void InitializeSubmodules()
|
||
{
|
||
selfTimeSm ??= new SelfTimeSubmodule(this);
|
||
attributeSm ??= new AttributeSubmodule(this);
|
||
eventSm ??= new EventSubmodule(this);
|
||
buffSm ??= new BuffSubmodule(this);
|
||
statusSm ??= new StatusSubmodule(this);
|
||
}
|
||
|
||
protected virtual void InitializeSubcontrollers()
|
||
{
|
||
renderSc?.Initialize();
|
||
movementSc?.Initialize();
|
||
animationSc?.Initialize();
|
||
collisionSc?.Initialize();
|
||
bodyPartsSc?.Initialize();
|
||
audioSc?.Initialize();
|
||
reactionSc?.Initialize();
|
||
feedbackSc?.Initialize();
|
||
}
|
||
}
|
||
|
||
public partial class CharacterBase
|
||
{
|
||
public float GetDamageValue(AttackValue attackValue)
|
||
{
|
||
string dealtMultiplier = attackValue.attackType.AttackTypeToString() + "DamageDealtMultiplier";
|
||
string gainMultiplier = attackValue.attackType.AttackTypeToString() + "DamageGainMultiplier";
|
||
|
||
float baseDamage = attackValue.damage;
|
||
|
||
baseDamage *= attackValue.attacker is not null ? attackValue.attacker.attributeSm[dealtMultiplier] : 1;
|
||
baseDamage *= attributeSm[gainMultiplier];
|
||
|
||
baseDamage *= attackValue.attacker is not null ? attackValue.attacker.attributeSm["FinalDamageDealtMultiplier"] : 1;
|
||
baseDamage *= attributeSm["FinalDamageGainMultiplier"];
|
||
|
||
return (baseDamage + attackValue.additionalFlatDamage) * attackValue.damageMultiplier;
|
||
}
|
||
|
||
public void TakeDamage(ref AttackResult attackResult)
|
||
{
|
||
float damage = GetDamageValue(attackResult.attackValue);
|
||
if (attributeSm.Has("Shield") && attributeSm["Shield"] > 0)
|
||
{
|
||
attackResult.shieldBlockedDamage = Mathf.Min(damage, attributeSm["Shield"]);
|
||
//GeneralUtilities.InstantiateBlockedDamageNumber(flexibleCenterPoint.position, blockedDamage);
|
||
|
||
if(damage > attributeSm["Shield"])
|
||
{
|
||
damage -= attributeSm["Shield"];
|
||
attributeSm["Shield"] = 0;
|
||
}
|
||
else
|
||
{
|
||
attributeSm["Shield"] -= damage;
|
||
damage = 0;
|
||
}
|
||
}
|
||
|
||
attributeSm["Health"] -= damage;
|
||
attackResult.finalDamage = damage;
|
||
|
||
// 实际扣除了生命值才算受伤,被盾完全抵挡则不触发
|
||
if (damage > 0)
|
||
{
|
||
// 通过 EventSubmodule.onHurt 统一分发;无攻击区域来源(Buff路径)时传 null
|
||
eventSm.onHurt.Invoke(null);
|
||
}
|
||
|
||
if (attributeSm["Health"] <= 0)
|
||
{
|
||
attackResult.causedDeath = true;
|
||
attributeSm["Health"] = 0;
|
||
Die();
|
||
}
|
||
else
|
||
{
|
||
attackResult.causedDeath = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
public partial class CharacterBase
|
||
{
|
||
public virtual bool CheckBreakthrough(BreakthroughType breakthroughType)
|
||
{
|
||
return !reactionSc.breakthroughResistances[breakthroughType].Value;
|
||
}
|
||
|
||
public virtual bool CheckDisruption(DisruptionType disruptionType)
|
||
{
|
||
return animationSc.fullBodyFuncAnimSm.CheckDisruption(disruptionType);
|
||
}
|
||
|
||
public virtual bool GetHit(BreakthroughType breakthroughType, out float recoveryTime,
|
||
DisruptionType disruptionType = DisruptionType.NormalExternal,
|
||
Vector3 direction = default, string funcAnimName = "")
|
||
{
|
||
renderSc.GetHitBlink();
|
||
|
||
float intensity = breakthroughType switch
|
||
{
|
||
BreakthroughType.None => 0,
|
||
BreakthroughType.Weak => 0.2f,
|
||
BreakthroughType.Medium => 0.4f,
|
||
BreakthroughType.Heavy or BreakthroughType.Disruption or BreakthroughType.Forced => 0.8f,
|
||
_ => 0
|
||
};
|
||
|
||
if (string.IsNullOrEmpty(funcAnimName))
|
||
{
|
||
funcAnimName = GetHitFuncAnimName(breakthroughType, direction);
|
||
}
|
||
|
||
if (CheckBreakthrough(breakthroughType))
|
||
{
|
||
if (!animationSc.fullBodyFuncAnimSm.Stop(disruptionType))
|
||
{
|
||
recoveryTime = 0f;
|
||
return true;
|
||
}
|
||
|
||
if (breakthroughType >= BreakthroughType.Medium)
|
||
{
|
||
animationSc.PlayGetHitAnimation(funcAnimName, out recoveryTime);
|
||
}
|
||
else
|
||
{
|
||
recoveryTime = 0f;
|
||
animationSc.PlayGetHitBoneShake(intensity, direction);
|
||
}
|
||
|
||
statusSm.AddStatus(StatusType.Stun, recoveryTime);
|
||
return true;
|
||
}
|
||
|
||
recoveryTime = 0f;
|
||
animationSc.PlayGetHitBoneShake(intensity, direction);
|
||
return false;
|
||
}
|
||
|
||
protected virtual string GetHitFuncAnimName(BreakthroughType breakthroughType, Vector3 direction)
|
||
{
|
||
string prefix = "GetHitMedium";
|
||
|
||
if (breakthroughType >= BreakthroughType.Medium)
|
||
{
|
||
prefix = breakthroughType switch
|
||
{
|
||
BreakthroughType.Medium => "GetHitMedium",
|
||
BreakthroughType.Heavy => "GetHitHeavy",
|
||
BreakthroughType.Disruption => "GetHitHeavy",
|
||
BreakthroughType.Forced => "GetHitForced",
|
||
_ => "GetHit"
|
||
};
|
||
}
|
||
|
||
string directionStr = "Front";
|
||
if (direction != default)
|
||
{
|
||
float angle = Vector3.SignedAngle(transform.forward, direction, Vector3.up);
|
||
directionStr = angle switch
|
||
{
|
||
> -45f and <= 45f => "Back",
|
||
> 45f and <= 135f => "Left",
|
||
> -135f and <= -45f => "Right",
|
||
_ => "Front"
|
||
};
|
||
|
||
if (directionStr != "Front" && !animationSc.fullBodyFuncAnimSm.collection.ContainsKey(prefix + directionStr))
|
||
{
|
||
directionStr = "Front";
|
||
}
|
||
}
|
||
|
||
string fullName = prefix + directionStr;
|
||
|
||
if (!animationSc.fullBodyFuncAnimSm.collection.ContainsKey(fullName))
|
||
{
|
||
if (prefix == "GetHitForced")
|
||
{
|
||
prefix = "GetHitHeavy"; //如果没有专门的“强制等级”受击动画,就使用“重度等级”的动画
|
||
fullName = prefix + directionStr;
|
||
}
|
||
}
|
||
|
||
return fullName;
|
||
}
|
||
}
|
||
|
||
public partial class CharacterBase
|
||
{
|
||
public Vector2 GetNormalizedScreenPosition(Camera cam = null)
|
||
{
|
||
if (this is Player player)
|
||
{
|
||
cam ??= player.viewSc.playerCamera;
|
||
}
|
||
else
|
||
{
|
||
if (cam == null)
|
||
{
|
||
throw new ArgumentNullException(nameof(cam), "Camera must be provided for non-player characters.");
|
||
}
|
||
}
|
||
|
||
return SpaceConverter.WorldPointToNormalizedScreenPoint(centerPoint.position, cam);
|
||
}
|
||
}
|
||
} |