mirror of
http://47.107.252.169:3000/Roguelite-Game-Developing-Team/Gen_Hack-and-Slash-Roguelite.git
synced 2025-11-20 04:17:13 +08:00
(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:
48
Client/Assets/Scripts/HediffComps/HediffComp.cs
Normal file
48
Client/Assets/Scripts/HediffComps/HediffComp.cs
Normal 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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Client/Assets/Scripts/HediffComps/HediffComp.cs.meta
Normal file
3
Client/Assets/Scripts/HediffComps/HediffComp.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3f6a16d500840bea821c21ea612a5c6
|
||||
timeCreated: 1756612166
|
||||
@@ -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 时已保证非 null,emptyOffset 是 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 < 0.5"
|
||||
/// </summary>
|
||||
[JsonProperty("condition")]
|
||||
public string ConditionExpression { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当条件满足时应用的属性偏移定义。
|
||||
/// </summary>
|
||||
[JsonProperty("offsetDef")]
|
||||
public AttributesOffsetDef OffsetDef { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 24d8cac401634909bec96e227a4cbe01
|
||||
timeCreated: 1759755254
|
||||
@@ -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))
|
||||
// 对于这些模式,阈值应为正值,表示变化量。
|
||||
// 如果是RemainingRatio,Threshold可以是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%)
|
||||
// 对于 RemainingRatio,Threshold 是一个目标剩余比例 (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秒更新一次
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94a20acfff7d46eb9340b3ad0412649d
|
||||
timeCreated: 1759760823
|
||||
204
Client/Assets/Scripts/HediffComps/HediffComp_ExecuteEvent.cs
Normal file
204
Client/Assets/Scripts/HediffComps/HediffComp_ExecuteEvent.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d05fad8ce3c40208d1a79cc91fab985
|
||||
timeCreated: 1759977279
|
||||
Reference in New Issue
Block a user