using System;
using System.Text;
using Cielonos.MainGame.Buffs.Character;
using Cielonos.MainGame.Inventory;
using DamageNumbersPro;
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;
[TitleGroup("Submodules")] [HideInEditorMode]
public SelfTimeSubmodule selfTimeSm;
[HideInEditorMode] public AttributeSubmodule attributeSm;
[HideInEditorMode] public EventSubmodule eventSm;
[HideInEditorMode] public BuffSubmodule buffSm;
[HideInEditorMode] 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()
{
selfTimeSm?.SetUp(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);
RegisterAttributeCallbacks();
}
///
/// 注册属性变更回调,使 Health/Energy 变更时自动触发对应的 EventSubmodule 事件。
/// 子类可 override 追加额外回调(如 UI 更新)。
///
protected virtual void RegisterAttributeCallbacks()
{
attributeSm.RegisterValueChangedCallback(CharacterAttribute.Health,
(oldVal, newVal) => eventSm.onHealthChanged.Invoke(newVal - oldVal));
attributeSm.RegisterValueChangedCallback(CharacterAttribute.Energy,
(oldVal, newVal) => eventSm.onEnergyChanged.Invoke(newVal - oldVal));
}
protected virtual void InitializeSubcontrollers()
{
renderSc?.Initialize();
movementSc?.Initialize();
animationSc?.Initialize();
collisionSc?.Initialize();
bodyPartsSc?.Initialize();
audioSc?.Initialize();
reactionSc?.Initialize();
feedbackSc?.Initialize();
}
}
public partial class CharacterBase
{
///
/// 护甲减伤公式的缩放常量。armor / (armor + K) = 减伤比例。
/// K = 100 时,100 护甲 = 50% 减伤,200 护甲 ≈ 66.7% 减伤。
///
private const float ArmorScalingConstant = 100f;
///
/// 根据攻击者属性和自身属性计算最终伤害值。
///
/// 攻击来源角色,为 null 时跳过攻击者侧的伤害倍率。
/// 攻击数值参数。
public float GetDamageValue(CharacterBase attacker, Attack.Value attackValue)
{
string dealtMultiplier = attackValue.type.AttackTypeToString() + "DamageDealtMultiplier";
string receivedMultiplier = attackValue.type.AttackTypeToString() + "DamageReceivedMultiplier";
float baseDamage = attackValue.damage;
// Phase 1: 攻击者侧倍率
baseDamage *= attacker is not null ? attacker.attributeSm[dealtMultiplier] : 1;
baseDamage *= attacker is not null ? attacker.attributeSm[CharacterAttribute.FinalDamageDealtMultiplier] : 1;
// Phase 2: 护甲减伤(非线性,先于 ReceivedMultiplier)
float armor = attributeSm[CharacterAttribute.Armor];
if (armor > 0f)
{
float armorReduction = armor / (armor + ArmorScalingConstant);
baseDamage *= 1f - armorReduction;
}
else
{
// 负护甲增加伤害,线性叠加,每 -100 护甲增加 100% 伤害
float negativeArmor = Mathf.Abs(armor);
float negativeArmorIncrease = negativeArmor / ArmorScalingConstant;
baseDamage *= 1f + negativeArmorIncrease;
}
// Phase 3: 受击者侧倍率
baseDamage *= attributeSm[receivedMultiplier];
baseDamage *= attributeSm[CharacterAttribute.FinalDamageReceivedMultiplier];
return (baseDamage + attackValue.additionalFlatDamage) * attackValue.damageMultiplier;
}
///
/// 对角色施加伤害,填充 attackResult 中的最终伤害、护盾吸收和死亡标记。
///
public void TakeDamage(Attack.Result attackResult)
{
float damage = GetDamageValue(attackResult.attacker, attackResult.value);
if (attributeSm.Has(CharacterAttribute.Shield) && attributeSm[CharacterAttribute.Shield] > 0)
{
attackResult.shieldBlockedDamage = Mathf.Min(damage, attributeSm[CharacterAttribute.Shield]);
if (damage > attributeSm[CharacterAttribute.Shield])
{
damage -= attributeSm[CharacterAttribute.Shield];
attributeSm[CharacterAttribute.Shield] = 0;
}
else
{
attributeSm[CharacterAttribute.Shield] -= damage;
damage = 0;
}
}
attributeSm[CharacterAttribute.Health] -= damage;
attackResult.finalDamage = damage;
// 实际扣除了生命值才算受伤,被盾完全抵挡则不触发
if (damage > 0)
{
// 通过 EventSubmodule.onHurt 统一分发;无攻击区域来源(Buff路径)时传 null
eventSm.onHurt.Invoke(null);
}
if (attributeSm[CharacterAttribute.Health] <= 0)
{
attackResult.causedDeath = true;
attributeSm[CharacterAttribute.Health] = 0;
Die();
}
else
{
attackResult.causedDeath = false;
}
}
///
/// 对角色施加伤害,并生成伤害数字。适用于有明确攻击来源和攻击数值的情况,如直接攻击;
/// 对于持续伤害等无明确攻击来源的情况,建议先调用 TakeDamage(Attack.Result) 再手动生成伤害数字,以避免重复计算伤害值。
///
public void TakeDamage(Attack.Result attackResult, out DamageNumber damageNumber)
{
TakeDamage(attackResult);
// 护盾吸收伤害单独显示
if (attackResult.shieldBlockedDamage > 0)
{
MainGameBaseCollection.Instance.ShieldedDamageNumber()
.Spawn(attackResult.hitPosition, attackResult.shieldBlockedDamage, centerPoint);
}
Attack.Type type = attackResult.value.type;
bool isCritical = attackResult.value.isCritical;
damageNumber = MainGameBaseCollection.Instance.DamageNumber(type, isCritical)
.Spawn(attackResult.hitPosition, attackResult.finalDamage, attackResult.target.centerPoint);
damageNumber.SetSpamGroup(attackResult.spamGroupID);
}
public virtual void Heal(float healAmount)
{
if (healAmount <= 0) return;
attributeSm[CharacterAttribute.Health] += healAmount;
attributeSm[CharacterAttribute.Health] = Mathf.Min(attributeSm[CharacterAttribute.Health], attributeSm[CharacterAttribute.MaximumHealth]);
MainGameBaseCollection.Instance.HealText().Spawn(centerPosition, healAmount, centerPoint);
}
}
public partial class CharacterBase
{
public virtual bool CheckBreakthrough(Breakthrough.Type breakthroughType)
{
return !reactionSc.breakthroughResistances[breakthroughType].Value;
}
public virtual bool CheckDisruption(DisruptionType disruptionType)
{
return animationSc.fullBodyFuncAnimSm.CheckDisruption(disruptionType);
}
public virtual bool GetHit(Breakthrough.Type breakthroughType, out float recoveryTime,
DisruptionType disruptionType = DisruptionType.NormalExternal,
Vector3 direction = default, string funcAnimName = "")
{
renderSc.GetHitBlink();
float intensity = breakthroughType switch
{
Breakthrough.Type.None => 0,
Breakthrough.Type.Weak => 0.2f,
Breakthrough.Type.Medium => 0.4f,
Breakthrough.Type.Heavy or Breakthrough.Type.Disruption or Breakthrough.Type.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 >= Breakthrough.Type.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(Breakthrough.Type breakthroughType, Vector3 direction)
{
string prefix = "GetHitMedium";
if (breakthroughType >= Breakthrough.Type.Medium)
{
prefix = breakthroughType switch
{
Breakthrough.Type.Medium => "GetHitMedium",
Breakthrough.Type.Heavy => "GetHitHeavy",
Breakthrough.Type.Disruption => "GetHitHeavy",
Breakthrough.Type.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);
}
}
#if UNITY_EDITOR
public partial class CharacterBase
{
[Title("Editor Tools")]
[HideInPlayMode]
[Button]
protected virtual void CollectSubcontrollers()
{
movementSc ??= GetComponent();
animationSc ??= GetComponent();
collisionSc ??= GetComponent();
renderSc ??= GetComponent();
bodyPartsSc ??= GetComponent();
audioSc ??= GetComponent();
reactionSc ??= GetComponent();
feedbackSc ??= GetComponent();
}
}
#endif
}