(client) feat:健康给予,路径优化,结算界面,商店界面 (#60)

Co-authored-by: m0_75251201 <m0_75251201@noreply.gitcode.com>
Reviewed-on: http://47.107.252.169:3000/Roguelite-Game-Developing-Team/Gen_Hack-and-Slash-Roguelite/pulls/60
This commit is contained in:
2025-10-10 14:08:23 +08:00
parent 9a797479ff
commit 16b49f3d3a
1900 changed files with 114053 additions and 34157 deletions

View File

@@ -0,0 +1,48 @@
using Data;
using Entity;
namespace HediffComps
{
// 运行时健康状态组件的抽象基类
public abstract class HediffComp
{
// 对组件定义的引用
protected HediffCompDef def;
// 对父 Hediff 的引用
protected Hediff parentHediff;
/// <summary>
/// 组件初始化时调用,在构造函数之后。
/// </summary>
/// <param name="parentHediff">父级 Hediff 实例。</param>
/// <param name="def">组件定义。</param>
public virtual void Initialize(Hediff parentHediff, HediffCompDef def)
{
this.parentHediff = parentHediff;
this.def = def;
}
/// <summary>
/// 每帧更新时调用。
/// </summary>
/// <param name="deltaTime">自上次更新以来的时间(秒)。</param>
public virtual void Tick(float deltaTime)
{
}
/// <summary>
/// 当其父 Hediff 被添加到实体时调用。
/// </summary>
public virtual void OnAdded()
{
}
/// <summary>
/// 当其父 Hediff 从实体移除时调用。
/// </summary>
public virtual void OnRemoved()
{
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f3f6a16d500840bea821c21ea612a5c6
timeCreated: 1756612166

View File

@@ -0,0 +1,196 @@
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; }
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 24d8cac401634909bec96e227a4cbe01
timeCreated: 1759755254

View File

@@ -0,0 +1,400 @@
using System;
using Data;
using Entity;
using Newtonsoft.Json;
using UnityEngine;
// 假设您使用 Unity 的 Debug.LogError
namespace HediffComps
{
// 假设 IAttributesOffsetProvider 接口已定义
/// <summary>
/// 健康状态组件:动态监控实体属性,并根据属性差值、当前值、占比等,
/// 施加额外的累积或非累积的属性偏移。
/// </summary>
public class HediffComp_DynamicAttributeOffset : HediffComp, IAttributesOffsetProvider
{
/// <summary>
/// 可监控的属性类型。
/// </summary>
public enum MonitoredAttributeType
{
None,
Health,
MoveSpeed,
Attack,
Defense,
AttackSpeed,
AttackRange,
AttackTargetCount
}
/// <summary>
/// 属性监控模式。
/// </summary>
public enum MonitorMode
{
None,
/// <summary>监控当前值与基准值的绝对差值 (Base - Current)。</summary>
Difference,
/// <summary>监控当前属性的绝对值。</summary>
CurrentValue,
/// <summary>监控当前值占基准值的剩余百分比 (Current / Base)。</summary>
RemainingRatio,
/// <summary>监控相对于基准值减少的百分比 ((Base - Current) / Base)。</summary>
DecreasedRatio,
/// <summary>监控相对于基准值增加的百分比 ((Current - Base) / Base)。</summary>
IncreasedRatio
}
private PropertiesConfig config;
private AttributesOffsetDef currentDynamicOffset = new(); // 当前组件贡献的动态偏移量
private int lastAppliedStackCount; // 上次应用时的效果堆叠次数
private float lastMonitoredValue; // 上次监控时的属性值 (实际值或计算后的值)
private float timeSinceLastTick;
/// <summary>
/// 获取此提供者当前的属性偏移量定义。
/// </summary>
/// <returns>此提供者所带来的属性偏移量。</returns>
public AttributesOffsetDef GetAttributesOffset()
{
// 直接返回由 MonitorAndApplyOffset 方法维护的最新动态偏移量
return currentDynamicOffset;
}
/// <summary>
/// 辅助方法:从 Attributes 对象中获取指定属性的值。
/// </summary>
private float GetAttributeValue(Attributes attributes, MonitoredAttributeType attributeType)
{
if (attributes == null) return 0f;
switch (attributeType)
{
case MonitoredAttributeType.Health: return attributes.health;
case MonitoredAttributeType.MoveSpeed: return attributes.moveSpeed;
case MonitoredAttributeType.Attack: return attributes.attack;
case MonitoredAttributeType.Defense: return attributes.defense;
case MonitoredAttributeType.AttackSpeed: return attributes.attackSpeed;
case MonitoredAttributeType.AttackRange: return attributes.attackRange;
case MonitoredAttributeType.AttackTargetCount: return attributes.attackTargetCount;
default:
// 对于未知的或 MonitoredAttributeType.None 类型记录警告并返回0
Debug.LogWarning(
$"<color=yellow>警告:</color>HediffComp_DynamicAttributeOffset ({def.defName}): 尝试获取不支持的监控属性类型值: {attributeType}。返回0。");
return 0f;
}
}
/// <summary>
/// 辅助方法:获取当前监控属性的现在值和基础值。
/// </summary>
private void GetCurrentMonitoredValues(out float currentValue, out float baseValue)
{
currentValue = 0f;
baseValue = 0f;
if (parentHediff?.entity == null)
{
Debug.LogWarning(
$"<color=yellow>警告:</color>HediffComp_DynamicAttributeOffset ({def.defName}): 属性监控期间父实体为空。");
return;
}
if (parentHediff.entity.AttributesNow == null)
{
Debug.LogWarning(
$"<color=yellow>警告:</color>HediffComp_DynamicAttributeOffset ({def.defName}): 实体当前属性为空。");
return;
}
if (parentHediff.entity.BaseAttributes == null)
{
Debug.LogWarning(
$"<color=yellow>警告:</color>HediffComp_DynamicAttributeOffset ({def.defName}): 实体基础属性为空。");
return;
}
currentValue = GetAttributeValue(parentHediff.entity.AttributesNow, config.MonitoredAttribute);
baseValue = GetAttributeValue(parentHediff.entity.BaseAttributes, config.MonitoredAttribute);
}
/// <summary>
/// 组件初始化时调用。解析配置并进行校验。
/// </summary>
public override void Initialize(Hediff parentHediff, HediffCompDef def)
{
base.Initialize(parentHediff, def);
if (string.IsNullOrEmpty(def.properties))
throw new ArgumentNullException(nameof(def.properties),
$"<color=red>错误:</color>HediffComp_DynamicAttributeOffset ({def.defName}) 需要在 HediffCompDef.properties 中配置。");
try
{
config = JsonConvert.DeserializeObject<PropertiesConfig>(def.properties);
}
catch (JsonException ex)
{
throw new Exception(
$"<color=red>错误:</color>HediffComp_DynamicAttributeOffset ({def.defName}) 从 JSON 反序列化属性失败:'{def.properties}'。错误信息:{ex.Message}",
ex);
}
if (config == null)
throw new InvalidOperationException(
$"<color=red>错误:</color>HediffComp_DynamicAttributeOffset ({def.defName}) 的 PropertiesConfig 反序列化结果为空。输入 JSON'{def.properties}'。");
// --- 配置校验 ---
if (config.MonitoredAttribute == MonitoredAttributeType.None)
throw new InvalidOperationException(
$"<color=red>错误:</color>HediffComp_DynamicAttributeOffset ({def.defName}): 'monitoredAttribute' 必须指定且不能为 {MonitoredAttributeType.None}。");
if (config.MonitorMode == MonitorMode.None)
throw new InvalidOperationException(
$"<color=red>错误:</color>HediffComp_DynamicAttributeOffset ({def.defName}): 'monitorMode' 必须指定且不能为 {MonitorMode.None}。");
if (config.Threshold <= 0f && (config.MonitorMode == MonitorMode.Difference ||
config.MonitorMode == MonitorMode.DecreasedRatio ||
config.MonitorMode == MonitorMode.IncreasedRatio))
// 对于这些模式,阈值应为正值,表示变化量。
// 如果是RemainingRatioThreshold可以是0到1的百分比。
Debug.LogWarning(
$"<color=yellow>警告:</color>HediffComp_DynamicAttributeOffset ({def.defName}): 'MonitorMode' ({config.MonitorMode}) 通常期望正阈值,但当前阈值为零或负值。这可能导致意外行为。");
if (config.OffsetRule == null)
throw new InvalidOperationException(
$"<color=red>错误:</color>HediffComp_DynamicAttributeOffset ({def.defName}): 属性中必须提供 'offsetRule'。");
if (config.TickInterval <= 0f)
{
Debug.LogWarning(
$"<color=yellow>警告:</color>HediffComp_DynamicAttributeOffset ({def.defName}): 'tickInterval' 为零或负值。已设置为默认值 0.5 秒。");
config.TickInterval = 0.5f;
}
currentDynamicOffset = new AttributesOffsetDef(); // 初始化为空偏移量
timeSinceLastTick = 0f;
lastAppliedStackCount = 0;
// 获取初始监控值,用于后续差异计算
GetCurrentMonitoredValues(out lastMonitoredValue, out _);
}
/// <summary>
/// 当其父 Hediff 被添加到实体时调用。
/// </summary>
public override void OnAdded()
{
base.OnAdded();
if (parentHediff?.entity == null) return; // 安全检查
MonitorAndApplyOffset(); // 首次评估并应用
parentHediff.entity.SetAttribsDirty(); // 通知实体属性可能已更改
}
/// <summary>
/// 当其父 Hediff 从实体移除时调用。
/// </summary>
public override void OnRemoved()
{
base.OnRemoved();
if (parentHediff?.entity == null) return; // 安全检查
// 在移除时,强制清零偏移量,并通知实体
var changed = lastAppliedStackCount != 0; // 检查是否有应用的效果
if (changed)
{
// 创建一个全新的 AttributesOffsetDef 作为零值避免修改currentDynamicOffset原有实例
currentDynamicOffset = new AttributesOffsetDef();
lastAppliedStackCount = 0;
parentHediff.entity.SetAttribsDirty();
}
}
/// <summary>
/// 每帧更新时调用,根据 TickInterval 周期性地监控和应用偏移。
/// </summary>
public override void Tick(float deltaTime)
{
base.Tick(deltaTime);
if (parentHediff?.entity == null) return; // 安全检查
timeSinceLastTick += deltaTime;
if (timeSinceLastTick >= config.TickInterval)
{
MonitorAndApplyOffset();
timeSinceLastTick -= config.TickInterval; // 减去而不是直接清零,保持时间累积的精确性
}
}
/// <summary>
/// 核心逻辑:监控属性变化,计算并应用动态偏移。
/// </summary>
private void MonitorAndApplyOffset()
{
if (parentHediff?.entity == null || parentHediff.entity.AttributesNow == null ||
parentHediff.entity.BaseAttributes == null)
{
// 如果实体或其属性无效,且当前有效果,则取消效果
if (lastAppliedStackCount != 0)
{
currentDynamicOffset = new AttributesOffsetDef();
lastAppliedStackCount = 0;
parentHediff?.entity?.SetAttribsDirty();
}
return;
}
GetCurrentMonitoredValues(out var currentValue, out var baseValue);
var calculatedValue = 0f;
// 特殊处理 baseValue 为 0f 的情况,避免除以零导致 NaN
var canCalculateRatio = baseValue != 0f;
if (!canCalculateRatio && (config.MonitorMode == MonitorMode.RemainingRatio ||
config.MonitorMode == MonitorMode.DecreasedRatio ||
config.MonitorMode == MonitorMode.IncreasedRatio))
{
Debug.LogWarning(
$"<color=yellow>警告:</color>HediffComp_DynamicAttributeOffset ({def.defName}): {config.MonitoredAttribute} 的基础值为0。无法为模式 {config.MonitorMode} 计算比率。未应用偏移。");
calculatedValue = 0f; // 无法计算,视为无意义或无变化
}
else
{
switch (config.MonitorMode)
{
case MonitorMode.Difference:
calculatedValue = baseValue - currentValue;
break;
case MonitorMode.CurrentValue:
calculatedValue = currentValue;
break;
case MonitorMode.RemainingRatio:
calculatedValue = currentValue / baseValue;
break;
case MonitorMode.DecreasedRatio:
calculatedValue = (baseValue - currentValue) / baseValue;
break;
case MonitorMode.IncreasedRatio:
calculatedValue = (currentValue - baseValue) / baseValue;
break;
default:
Debug.LogError(
$"<color=red>错误:</color>HediffComp_DynamicAttributeOffset ({def.defName}): 未知或不支持的监控模式: {config.MonitorMode}。");
return;
}
}
var numStacks = 0;
// 对于 DecreasedRatio, IncreasedRatio 等Threshold 是一个变化量 (0.01 表示 1%)
// 对于 RemainingRatioThreshold 是一个目标剩余比例 (0.5 表示 50%)
if (config.Threshold > 0f) // 避免除以零
{
if (config.Accumulate)
{
numStacks = (int)(calculatedValue / config.Threshold);
}
else
{
// 非累积模式只要达到阈值就触发1层否则0层
if (config.MonitorMode == MonitorMode.RemainingRatio)
{
// 剩余比例模式下,如果当前比例 <= 阈值则视为满足条件触发1层
if (calculatedValue <= config.Threshold) numStacks = 1;
}
else // 其他模式,如果计算值 >= 阈值则视为满足条件触发1层
{
if (calculatedValue >= config.Threshold) numStacks = 1;
}
}
}
// 确保堆叠数不为负 (例如属性增加时DecreasedRatio 算出来是负的)
numStacks = Math.Max(0, numStacks);
if (numStacks != lastAppliedStackCount)
{
// 只有当效果堆叠数发生变化时才更新和通知
var newOffset = new AttributesOffsetDef();
// 应用偏移规则 (OffsetRule) 乘以堆叠数
if (config.OffsetRule != null)
{
// 绝对值偏移
newOffset.healthOffset = config.OffsetRule.healthOffset * numStacks;
newOffset.moveSpeedOffset = config.OffsetRule.moveSpeedOffset * numStacks;
newOffset.attackOffset = config.OffsetRule.attackOffset * numStacks;
newOffset.defenseOffset = config.OffsetRule.defenseOffset * numStacks;
newOffset.attackSpeedOffset = config.OffsetRule.attackSpeedOffset * numStacks;
newOffset.attackRangeOffset = config.OffsetRule.attackRangeOffset * numStacks;
newOffset.attackTargetCountOffset = config.OffsetRule.attackTargetCountOffset * numStacks;
newOffset.damageTakenOffset = config.OffsetRule.damageTakenOffset * numStacks;
newOffset.effectiveDamageOffset = config.OffsetRule.effectiveDamageOffset * numStacks;
// 百分比偏移
newOffset.healthPercentOffset = config.OffsetRule.healthPercentOffset * numStacks;
newOffset.moveSpeedPercentOffset = config.OffsetRule.moveSpeedPercentOffset * numStacks;
newOffset.attackPercentOffset = config.OffsetRule.attackPercentOffset * numStacks;
newOffset.defensePercentOffset = config.OffsetRule.defensePercentOffset * numStacks;
newOffset.attackSpeedPercentOffset = config.OffsetRule.attackSpeedPercentOffset * numStacks;
newOffset.attackRangePercentOffset = config.OffsetRule.attackRangePercentOffset * numStacks;
newOffset.attackTargetCountPercentOffset =
config.OffsetRule.attackTargetCountPercentOffset * numStacks;
newOffset.damageTakenPercentOffset = config.OffsetRule.damageTakenPercentOffset * numStacks;
newOffset.effectiveDamagePercentOffset = config.OffsetRule.effectiveDamagePercentOffset * numStacks;
}
currentDynamicOffset = newOffset; // 更新组件当前的动态偏移
lastAppliedStackCount = numStacks; // 更新已应用的堆叠数
// 通知实体其属性可能已更改
parentHediff.entity.SetAttribsDirty();
}
}
/// <summary>
/// 用于反序列化 HediffCompDef.properties JSON 字符串的配置类。
/// </summary>
private class PropertiesConfig
{
[JsonProperty("monitoredAttribute")]
public MonitoredAttributeType MonitoredAttribute { get; set; } = MonitoredAttributeType.None;
[JsonProperty("monitorMode")] public MonitorMode MonitorMode { get; set; } = MonitorMode.None;
/// <summary>
/// 触发规则的阈值。根据 MonitorMode 有不同含义。
/// 例如MonitorMode=DecreasedRatio 且 Threshold=0.01f 表示每减少1%。
/// </summary>
[JsonProperty("threshold")]
public float Threshold { get; set; }
/// <summary>
/// 每次满足阈值时施加的单个属性偏移定义。
/// </summary>
[JsonProperty("offsetRule")]
public AttributesOffsetDef OffsetRule { get; set; }
/// <summary>
/// 是否累积效果。如果为 true每满足阈值一次就施加一次 OffsetRule 的效果;
/// 如果为 false只在达到 Threshold 时施加一次 OffsetRule 的效果。
/// </summary>
[JsonProperty("accumulate")]
public bool Accumulate { get; set; } = true;
/// <summary>
/// 监控属性的间隔时间(秒),避免每帧都计算,优化性能。
/// </summary>
[JsonProperty("tickInterval")]
public float TickInterval { get; set; } = 0.5f; // 默认0.5秒更新一次
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 94a20acfff7d46eb9340b3ad0412649d
timeCreated: 1759760823

View File

@@ -0,0 +1,204 @@
using System;
using Data;
using Entity;
using Managers;
using Newtonsoft.Json;
using Parsing;
namespace HediffComps
{
/// <summary>
/// 健康状态组件:在冷却时间结束后或满足特定条件时触发一个预定义的事件。
/// </summary>
public class HediffComp_ExecuteEvent : HediffComp
{
// 从 JSON 反序列化得到的配置
private PropertiesConfig config;
// 评估条件是否满足的委托 (如果定义了)
private Func<Entity.Entity, bool> condition;
// 距离下次事件触发或条件评估还剩多少秒
private float remainingCooldown;
// 如果配置为 TriggerOnce记录是否已触发过一次
private bool hasTriggered;
/// <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">当配置对象、事件名称为空时抛出。</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_ExecuteEvent ({def.defName}) requires configuration in HediffCompDef.properties.");
// 反序列化配置 JSON 字符串到 PropertiesConfig 对象
try
{
config = JsonConvert.DeserializeObject<PropertiesConfig>(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; // 初始状态为未触发
}
/// <summary>
/// 当其父 <see cref="Hediff" /> 被添加到实体时调用。
/// 重置冷却时间和触发状态。
/// </summary>
public override void OnAdded()
{
base.OnAdded();
// 每次添加到实体时,重置冷却时间和触发状态,确保组件可以重新开始工作
remainingCooldown = Math.Max(0f, config.CooldownSeconds);
hasTriggered = false;
}
/// <summary>
/// 当其父 <see cref="Hediff" /> 从实体移除时调用。
/// 清空引用以辅助垃圾回收。
/// </summary>
public override void OnRemoved()
{
base.OnRemoved();
// 释放对配置和委托的引用,以辅助垃圾回收
config = null;
condition = null;
}
/// <summary>
/// 每帧更新时调用。
/// 在此周期性地检查冷却时间和条件,以触发事件。
/// </summary>
/// <param name="deltaTime">自上次更新以来的时间(秒)。</param>
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);
}
}
}
}
/// <summary>
/// 用于反序列化 <see cref="HediffCompDef.properties" /> JSON 字符串的配置类。
/// </summary>
private class PropertiesConfig
{
/// <summary>
/// 要触发的事件名称。
/// </summary>
[JsonProperty("eventName")]
public string EventName { get; set; }
/// <summary>
/// 可选的条件表达式字符串。如果存在,只在条件满足时触发。
/// </summary>
[JsonProperty("condition")]
public string ConditionExpression { get; set; }
/// <summary>
/// 可选的冷却时间。事件触发后需要等待此时间才能再次触发。默认为0。
/// JSON 中未定义时默认为0。
/// </summary>
[JsonProperty("cooldownSeconds")]
public float CooldownSeconds { get; set; } = 0f;
/// <summary>
/// 可选的布尔值。如果为 true事件只触发一次。默认为 false。
/// JSON 中未定义时默认为false。
/// </summary>
[JsonProperty("triggerOnce")]
public bool TriggerOnce { get; set; } = false;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2d05fad8ce3c40208d1a79cc91fab985
timeCreated: 1759977279