Files
Gen_Hack-and-Slash-Roguelite/Client/Assets/Scripts/HediffComps/HediffComp_ApplyAttributesOffset.cs

197 lines
9.4 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 Data;
using Entity;
using Newtonsoft.Json;
using Parsing;
namespace HediffComps
{
/// <summary>
/// 健康状态组件:在特定条件下为实体提供属性偏移量。
/// 当条件满足时,该组件会提供预设的 <see cref="AttributesOffsetDef" />;否则提供一个所有值为零的空偏移量。
/// </summary>
public class HediffComp_ApplyAttributesOffset : HediffComp, IAttributesOffsetProvider
{
// 不活跃时返回的空属性偏移量 (所有字段为0)。这是一个只读字段,在构造时已初始化,因此永远不需要空检查。
private readonly AttributesOffsetDef emptyOffset = new();
private AttributesOffsetDef cachedActiveOffset; // 当活跃时返回的实际属性偏移量
private Func<Entity.Entity, bool> condition; // 评估条件的委托
private PropertiesConfig config; // 从 JSON 反序列化得到的配置
private bool isActive; // 当前属性偏移是否活跃(即条件是否满足)
/// <summary>
/// 获取此提供者当前的属性偏移量定义。
/// 实现 <see cref="IAttributesOffsetProvider" /> 接口。
/// </summary>
/// <returns>此提供者所带来的属性偏移量。</returns>
public AttributesOffsetDef GetAttributesOffset()
{
// 如果组件活跃,则返回实际的属性偏移量;否则返回一个空的(所有值为零)的偏移量。
// cachedActiveOffset 在 Initialize 时已保证非 nullemptyOffset 是 readonly 字段并已初始化,因此不需空检查。
return isActive ? cachedActiveOffset : emptyOffset;
}
/// <summary>
/// 组件初始化时调用,在构造函数之后。
/// 负责解析 <see cref="HediffCompDef.properties" /> 中的 JSON 配置,并创建条件委托。
/// </summary>
/// <param name="parentHediff">父级 Hediff 实例。</param>
/// <param name="def">组件定义。</param>
/// <exception cref="ArgumentNullException">当 <paramref name="def.properties" /> 为空或 null 时抛出。</exception>
/// <exception cref="JsonException">当 JSON 反序列化失败时抛出。</exception>
/// <exception cref="InvalidOperationException">当配置对象、条件表达式或偏移定义为 null 时抛出。</exception>
/// <exception cref="Exception">当条件委托创建失败时抛出(包装原始异常)。</exception>
public override void Initialize(Hediff parentHediff, HediffCompDef def)
{
base.Initialize(parentHediff, def);
// 检查配置 JSON 字符串是否为空或 null
if (string.IsNullOrEmpty(def.properties))
throw new ArgumentNullException(nameof(def.properties),
$"HediffComp_ApplyAttributesOffset ({def.defName}) requires configuration in HediffCompDef.properties.");
// 反序列化配置 JSON 字符串到 PropertiesConfig 对象
try
{
config = JsonConvert.DeserializeObject<PropertiesConfig>(def.properties);
}
catch (JsonException ex)
{
// JsonException 是一种特定类型的异常,直接抛出,而不是包装在泛型 Exception 中。
throw new JsonException($"Failed to deserialize HediffComp_ApplyAttributesOffset ({def.defName}) properties " +
$"JSON: '{def.properties}'. Error: {ex.Message}", ex);
}
// 检查反序列化后的配置对象是否为 null
if (config == null)
throw new InvalidOperationException(
$"HediffComp_ApplyAttributesOffset ({def.defName}) PropertiesConfig deserialization resulted in null. " +
$"Input JSON: '{def.properties}'.");
// 检查条件表达式是否为空或 null
if (string.IsNullOrEmpty(config.ConditionExpression))
throw new InvalidOperationException(
$"HediffComp_ApplyAttributesOffset ({def.defName}) requires a 'condition' expression in its properties.");
// 创建条件委托
try
{
condition = ConditionDelegateFactory.CreateConditionDelegate(
config.ConditionExpression,
typeof(Entity.Entity),
typeof(ConditionFunctions)
);
}
catch (Exception ex)
{
// 包装原始异常,提供更多上下文信息。
throw new Exception($"Failed to create condition delegate for HediffComp_ApplyAttributesOffset ({def.defName}) " +
$"expression: '{config.ConditionExpression}'. Error: {ex.Message}", ex);
}
// 检查属性偏移定义是否为 null
if (config.OffsetDef == null)
throw new InvalidOperationException(
$"HediffComp_ApplyAttributesOffset ({def.defName}) requires 'offsetDef' in its properties, but it was null.");
// 缓存实际的偏移量,当组件活跃时使用。此时已保证 config.OffsetDef 非空。
cachedActiveOffset = config.OffsetDef;
}
/// <summary>
/// 当其父 <see cref="Hediff" /> 被添加到实体时调用。
/// 在此进行初始条件评估,并通知实体属性可能已更改。
/// </summary>
public override void OnAdded()
{
base.OnAdded();
// 在被添加到实体时进行首次条件评估,设置初始 isActive 状态
EvaluateConditionAndSetState();
// 通知实体属性可能已更改,以便其重新计算属性
parentHediff?.entity?.SetAttribsDirty();
}
/// <summary>
/// 当其父 <see cref="Hediff" /> 从实体移除时调用。
/// 确保状态被重置为不活跃,并通知实体属性可能已更改。
/// </summary>
public override void OnRemoved()
{
base.OnRemoved();
// 确保移除时,如果组件当前活跃,则将状态设置为不活跃,并通知实体属性更新。
// 这样可以消除此 HediffComp 组件移除时带来的属性影响。
if (isActive)
{
isActive = false;
parentHediff?.entity?.SetAttribsDirty();
}
// 重置 cachedActiveOffset 和 condition 等引用,有助于 GC 并在理论上防止组件被缓存后引用旧数据。
cachedActiveOffset = null;
condition = null;
config = null; // 释放对配置的引用
}
/// <summary>
/// 每帧更新时调用。
/// 在此周期性地评估条件以更新 <see cref="isActive" /> 状态。
/// </summary>
/// <param name="deltaTime">自上次更新以来的时间(秒)。</param>
public override void Tick(float deltaTime)
{
base.Tick(deltaTime);
EvaluateConditionAndSetState();
}
/// <summary>
/// 评估当前条件并更新 <see cref="isActive" /> 状态。
/// 如果 <see cref="isActive" /> 状态发生变化,则通知实体属性更新。
/// </summary>
private void EvaluateConditionAndSetState()
{
// 如果父 Hediff 的实体为空或条件委托未初始化,则无法评估条件。
// 此时应确保偏移量处于非激活状态,并通知属性变化(如果状态有变)。
// condition 已经通过 Initialize 方法保证非 null除非父 Hediff.entity 为 null
// 否则这里可以简化。但为了更强的健壮性,保留 condition == null 检查。
if (parentHediff?.entity == null || condition == null)
{
if (isActive) // 如果当前是活跃状态,但现在条件不可评估或实体不存在,需要重置
{
isActive = false;
parentHediff?.entity?.SetAttribsDirty(); // 通知属性脏,以便移除此组件的影响
}
return;
}
// 评估条件委托
var newIsActive = condition(parentHediff.entity);
// 如果 isActive 状态发生变化,则更新状态并通知实体。
if (newIsActive != isActive)
{
isActive = newIsActive;
parentHediff.entity.SetAttribsDirty();
}
}
/// <summary>
/// 用于反序列化 <see cref="HediffCompDef.properties" /> JSON 字符串的配置类。
/// </summary>
private class PropertiesConfig
{
/// <summary>
/// 条件表达式字符串,用于通过 <see cref="ConditionDelegateFactory" /> 创建条件委托。
/// 例如:"Entity.IsHuman && Entity.HealthRatio &lt; 0.5"
/// </summary>
[JsonProperty("condition")]
public string ConditionExpression { get; set; }
/// <summary>
/// 当条件满足时应用的属性偏移定义。
/// </summary>
[JsonProperty("offsetDef")]
public AttributesOffsetDef OffsetDef { get; set; }
}
}
}