using System;
using Data;
using Entity;
using Managers;
using Newtonsoft.Json;
using Parsing;
namespace HediffComps
{
///
/// 健康状态组件:在冷却时间结束后或满足特定条件时触发一个预定义的事件。
///
public class HediffComp_ExecuteEvent : HediffComp
{
// 从 JSON 反序列化得到的配置
private PropertiesConfig config;
// 评估条件是否满足的委托 (如果定义了)
private Func condition;
// 距离下次事件触发或条件评估还剩多少秒
private float remainingCooldown;
// 如果配置为 TriggerOnce,记录是否已触发过一次
private bool hasTriggered;
///
/// 组件初始化时调用,在构造函数之后。
/// 负责解析 中的 JSON 配置,并创建条件委托。
///
/// 父级 Hediff 实例。
/// 组件定义。
/// 当 为空或 null 时抛出。
/// 当 JSON 反序列化失败时抛出。
/// 当配置对象、事件名称为空时抛出。
/// 当条件委托创建失败时抛出(包装原始异常)。
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_ExecuteEvent ({def.defName}) requires configuration in HediffCompDef.properties.");
// 反序列化配置 JSON 字符串到 PropertiesConfig 对象
try
{
config = JsonConvert.DeserializeObject(def.properties);
}
catch (JsonException ex)
{
throw new JsonException($"Failed to deserialize HediffComp_ExecuteEvent ({def.defName}) properties " +
$"JSON: '{def.properties}'. Error: {ex.Message}", ex);
}
// 检查反序列化后的配置对象是否为 null
if (config == null)
throw new InvalidOperationException(
$"HediffComp_ExecuteEvent ({def.defName}) PropertiesConfig deserialization resulted in null. " +
$"Input JSON: '{def.properties}'.");
// 检查事件名称是否为空或 null
if (string.IsNullOrEmpty(config.EventName))
throw new InvalidOperationException(
$"HediffComp_ExecuteEvent ({def.defName}) requires an 'eventName' in its properties.");
// 如果定义了条件表达式,则创建条件委托
if (!string.IsNullOrEmpty(config.ConditionExpression))
{
try
{
condition = ConditionDelegateFactory.CreateConditionDelegate(
config.ConditionExpression,
typeof(Entity.Entity),
typeof(ConditionFunctions)
);
}
catch (Exception ex)
{
throw new Exception($"Failed to create condition delegate for HediffComp_ExecuteEvent ({def.defName}) " +
$"expression: '{config.ConditionExpression}'. Error: {ex.Message}", ex);
}
}
// 初始化冷却时间。如果 CooldownSeconds 小于0,则视为0。
remainingCooldown = Math.Max(0f, config.CooldownSeconds);
hasTriggered = false; // 初始状态为未触发
}
///
/// 当其父 被添加到实体时调用。
/// 重置冷却时间和触发状态。
///
public override void OnAdded()
{
base.OnAdded();
// 每次添加到实体时,重置冷却时间和触发状态,确保组件可以重新开始工作
remainingCooldown = Math.Max(0f, config.CooldownSeconds);
hasTriggered = false;
}
///
/// 当其父 从实体移除时调用。
/// 清空引用以辅助垃圾回收。
///
public override void OnRemoved()
{
base.OnRemoved();
// 释放对配置和委托的引用,以辅助垃圾回收
config = null;
condition = null;
}
///
/// 每帧更新时调用。
/// 在此周期性地检查冷却时间和条件,以触发事件。
///
/// 自上次更新以来的时间(秒)。
public override void Tick(float deltaTime)
{
base.Tick(deltaTime);
// 如果组件设置为只触发一次且已触发,则不再进行任何操作。
if (config.TriggerOnce && hasTriggered)
{
return;
}
// 如果实体不存在,则无法触发事件或评估条件
if (parentHediff?.entity == null)
{
return;
}
// 递减冷却时间
remainingCooldown -= deltaTime;
// 冷却时间结束后,检查是否可以触发事件
if (remainingCooldown <= 0)
{
// 默认条件满足,除非有条件委托且评估失败
bool conditionMet = true;
// 如果存在条件委托,则评估条件
if (condition != null)
{
conditionMet = condition(parentHediff.entity);
}
if (conditionMet)
{
// 触发事件
EventManager.Instance.Action(config.EventName, parentHediff.entity);
hasTriggered = true; // 标记已触发
// 如果不是只触发一次,则重置冷却时间以便下次触发
if (!config.TriggerOnce)
{
remainingCooldown = Math.Max(0f, config.CooldownSeconds);
}
}
else
{
// 如果冷却结束但条件不满足,并且是非一次性触发,重置冷却时间以便下次尝试
// 仅当 CooldownSeconds > 0 时才重置,避免0冷却无限Tick评估条件导致性能问题
if (!config.TriggerOnce && config.CooldownSeconds > 0)
{
remainingCooldown = Math.Max(0f, config.CooldownSeconds);
}
}
}
}
///
/// 用于反序列化 JSON 字符串的配置类。
///
private class PropertiesConfig
{
///
/// 要触发的事件名称。
///
[JsonProperty("eventName")]
public string EventName { get; set; }
///
/// 可选的条件表达式字符串。如果存在,只在条件满足时触发。
///
[JsonProperty("condition")]
public string ConditionExpression { get; set; }
///
/// 可选的冷却时间(秒)。事件触发后需要等待此时间才能再次触发。默认为0。
/// JSON 中未定义时默认为0。
///
[JsonProperty("cooldownSeconds")]
public float CooldownSeconds { get; set; } = 0f;
///
/// 可选的布尔值。如果为 true,事件只触发一次。默认为 false。
/// JSON 中未定义时默认为false。
///
[JsonProperty("triggerOnce")]
public bool TriggerOnce { get; set; } = false;
}
}
}