From 4c413ccd6d6532dbe4952138d6740952e6255951 Mon Sep 17 00:00:00 2001 From: SepComet <202308010230@stu.csust.edu.cn> Date: Thu, 30 Apr 2026 23:03:47 +0800 Subject: [PATCH] process Lo --- TODO.md | 90 ++++++++++----- .../Definition/DataStruct/AttackPayload.cs | 46 ++++++++ .../Definition/DataStruct/HitContext.cs | 44 +++++++ .../DataStruct/TagRuntimeUtility.cs | 33 ++++++ .../Tag/Aggregation/TagRuntimeData.cs | 11 ++ .../Tag/Combat/EnemyStatusTagRegistry.cs | 20 ++++ .../Combat/States/EnemyStatusTagStateBase.cs | 6 + .../Tag/Combat/States/FireTagState.cs | 9 ++ .../Tag/Combat/States/IceTagState.cs | 8 ++ .../StatusEffects/EnemyStatusTagEffectBase.cs | 29 +++++ .../Tag/Combat/StatusEffects/FireTagEffect.cs | 91 +++++++++++++++ .../StatusEffects/IEnemyStatusTagEffect.cs | 12 ++ .../Tag/Combat/StatusEffects/IceTagEffect.cs | 63 +++++++++++ .../Metadata/Config/AbsoluteZeroTagConfig.cs | 15 +++ .../Metadata/Config/BurnSpreadTagConfig.cs | 12 ++ .../Tag/Metadata/Config/CritTagConfig.cs | 15 +++ .../Tag/Metadata/Config/ExecutionTagConfig.cs | 15 +++ .../Tag/Metadata/Config/FireTagConfig.cs | 16 +++ .../Metadata/Config/FreezeMaskTagConfig.cs | 12 ++ .../Tag/Metadata/Config/IceTagConfig.cs | 16 +++ .../Metadata/Config/IgniteBurstTagConfig.cs | 12 ++ .../Tag/Metadata/Config/InfernoTagConfig.cs | 15 +++ .../Metadata/Config/OverpenetrateTagConfig.cs | 12 ++ .../Tag/Metadata/Config/PierceTagConfig.cs | 12 ++ .../Tag/Metadata/Config/ShatterTagConfig.cs | 15 +++ .../Tag/Metadata/Config/TagConfigBase.cs | 15 +++ .../Definition/Tag/Metadata/TagDefinition.cs | 14 +++ .../Tag/Metadata/TagDefinitionRegistry.cs | 69 +++++++++++ .../EntityLogic/EnemyTagStatusRuntime.cs | 107 ++++++++++++++++++ .../EntityLogic/EnemyTagStatusRuntime.cs | 103 +++++++++++++++++ 30 files changed, 910 insertions(+), 27 deletions(-) create mode 100644 src/GeometryTD.Domain/Definition/DataStruct/AttackPayload.cs create mode 100644 src/GeometryTD.Domain/Definition/DataStruct/HitContext.cs create mode 100644 src/GeometryTD.Domain/Definition/DataStruct/TagRuntimeUtility.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Aggregation/TagRuntimeData.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Combat/EnemyStatusTagRegistry.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Combat/States/EnemyStatusTagStateBase.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Combat/States/FireTagState.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Combat/States/IceTagState.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Combat/StatusEffects/EnemyStatusTagEffectBase.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Combat/StatusEffects/FireTagEffect.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Combat/StatusEffects/IEnemyStatusTagEffect.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Combat/StatusEffects/IceTagEffect.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Metadata/Config/AbsoluteZeroTagConfig.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Metadata/Config/BurnSpreadTagConfig.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Metadata/Config/CritTagConfig.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Metadata/Config/ExecutionTagConfig.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Metadata/Config/FireTagConfig.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Metadata/Config/FreezeMaskTagConfig.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Metadata/Config/IceTagConfig.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Metadata/Config/IgniteBurstTagConfig.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Metadata/Config/InfernoTagConfig.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Metadata/Config/OverpenetrateTagConfig.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Metadata/Config/PierceTagConfig.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Metadata/Config/ShatterTagConfig.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Metadata/Config/TagConfigBase.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Metadata/TagDefinition.cs create mode 100644 src/GeometryTD.Domain/Definition/Tag/Metadata/TagDefinitionRegistry.cs create mode 100644 src/GeometryTD.Domain/Entity/EntityLogic/EnemyTagStatusRuntime.cs create mode 100644 src/GeometryTD.Infrastructure/Entity/EntityLogic/EnemyTagStatusRuntime.cs diff --git a/TODO.md b/TODO.md index 66d88f8..55eb5b3 100644 --- a/TODO.md +++ b/TODO.md @@ -3,13 +3,14 @@ 最后更新:2026-04-30 > **2026-04-30 第一波完成**:Definition/Enum + Event +> **2026-04-30 第二波完成**:Vector3/Mathf 替换,AttackPayload/HitContext 迁移 ## 概述 | 指标 | 数量 | 状态 | |------|------|------| | 总文件数 | 457 | - | -| L0 (Domain) 可直接迁移 | ~180 | **第一波已完成 62 个文件** | +| L0 (Domain) 可直接迁移 | ~180 | **第一波已完成 62 个文件,第二波完成 5 个文件** | | L1 (Infrastructure) 需重构 | ~80 | - | | L2 (Presentation) Unity 依赖 | ~197 | - | @@ -63,6 +64,8 @@ src/ ### Definition/DataStruct(8 个文件) +- [x] `Definition/DataStruct/AttackPayload.cs` ⚠️ **已迁移** - Vector3 → System.Numerics.Vector3 +- [x] `Definition/DataStruct/HitContext.cs` ⚠️ **已迁移** - Vector3 → System.Numerics.Vector3,TargetStatusRuntime 已恢复 - [ ] `Definition/DataStruct/BackpackInventoryData.cs` - [ ] `Definition/DataStruct/BuildInfo.cs` - [ ] `Definition/DataStruct/EventItem.cs` @@ -127,18 +130,19 @@ src/ ### Definition/Tag(37 个文件) -- [ ] `Definition/Tag/Aggregation/TagRuntimeData.cs` +- [x] `Definition/Tag/Aggregation/TagRuntimeData.cs` ⚠️ **已迁移** - 无 Unity 依赖 +- [x] `Definition/Tag/Aggregation/TagRuntimeUtility.cs` ⚠️ **已新建** - CloneTagRuntimes 方法 - [ ] `Definition/Tag/Aggregation/TowerTagAggregationService.cs` -- [ ] `Definition/Tag/Combat/EnemyStatusTagRegistry.cs` +- [x] `Definition/Tag/Combat/EnemyStatusTagRegistry.cs` ⚠️ **已迁移** - 简化版(含 FireTagEffect、IceTagEffect 注册) - [ ] `Definition/Tag/Combat/Handlers/AttackShapeTagEffectHandler.cs` - [ ] `Definition/Tag/Combat/Handlers/NumericTagEffectHandler.cs` -- [ ] `Definition/Tag/Combat/States/EnemyStatusTagStateBase.cs` -- [ ] `Definition/Tag/Combat/States/FireTagState.cs` -- [ ] `Definition/Tag/Combat/States/IceTagState.cs` -- [ ] `Definition/Tag/Combat/StatusEffects/EnemyStatusTagEffectBase.cs` -- [ ] `Definition/Tag/Combat/StatusEffects/FireTagEffect.cs` -- [ ] `Definition/Tag/Combat/StatusEffects/IEnemyStatusTagEffect.cs` -- [ ] `Definition/Tag/Combat/StatusEffects/IceTagEffect.cs` +- [x] `Definition/Tag/Combat/States/EnemyStatusTagStateBase.cs` ⚠️ **已迁移** - 无 Unity 依赖 +- [x] `Definition/Tag/Combat/States/FireTagState.cs` ⚠️ **已迁移** - 无 Unity 依赖 +- [x] `Definition/Tag/Combat/States/IceTagState.cs` ⚠️ **已迁移** - 无 Unity 依赖 +- [x] `Definition/Tag/Combat/StatusEffects/EnemyStatusTagEffectBase.cs` ⚠️ **已迁移** - 无 Unity 依赖 +- [x] `Definition/Tag/Combat/StatusEffects/FireTagEffect.cs` ⚠️ **已迁移** - Mathf → System.Math +- [x] `Definition/Tag/Combat/StatusEffects/IEnemyStatusTagEffect.cs` ⚠️ **已迁移** - 无 Unity 依赖 +- [x] `Definition/Tag/Combat/StatusEffects/IceTagEffect.cs` ⚠️ **已迁移** - Mathf → System.Math - [ ] `Definition/Tag/Combat/TagEffectResolver.cs` - [ ] `Definition/Tag/Generation/ComponentTagGenerationService.cs` - [ ] `Definition/Tag/Generation/InventoryTagRandomContext.cs` @@ -146,20 +150,21 @@ src/ - [ ] `Definition/Tag/Generation/RarityTagBudgetRuleRegistry.cs` - [ ] `Definition/Tag/Generation/TagGenerationRule.cs` - [ ] `Definition/Tag/Generation/TagGenerationRuleRegistry.cs` -- [ ] `Definition/Tag/Metadata/Config/AbsoluteZeroTagConfig.cs` -- [ ] `Definition/Tag/Metadata/Config/BurnSpreadTagConfig.cs` -- [ ] `Definition/Tag/Metadata/Config/CritTagConfig.cs` -- [ ] `Definition/Tag/Metadata/Config/ExecutionTagConfig.cs` -- [ ] `Definition/Tag/Metadata/Config/FireTagConfig.cs` -- [ ] `Definition/Tag/Metadata/Config/FreezeMaskTagConfig.cs` -- [ ] `Definition/Tag/Metadata/Config/IceTagConfig.cs` -- [ ] `Definition/Tag/Metadata/Config/IgniteBurstTagConfig.cs` -- [ ] `Definition/Tag/Metadata/Config/InfernoTagConfig.cs` -- [ ] `Definition/Tag/Metadata/Config/OverpenetrateTagConfig.cs` -- [ ] `Definition/Tag/Metadata/Config/PierceTagConfig.cs` -- [ ] `Definition/Tag/Metadata/Config/ShatterTagConfig.cs` -- [ ] `Definition/Tag/Metadata/Config/TagConfigBase.cs` -- [ ] `Definition/Tag/Metadata/TagDefinition.cs` +- [x] `Definition/Tag/Metadata/Config/AbsoluteZeroTagConfig.cs` ⚠️ **已迁移** - 无 Unity 依赖 +- [x] `Definition/Tag/Metadata/Config/BurnSpreadTagConfig.cs` ⚠️ **已迁移** - 无 Unity 依赖 +- [x] `Definition/Tag/Metadata/Config/CritTagConfig.cs` ⚠️ **已迁移** - 无 Unity 依赖 +- [x] `Definition/Tag/Metadata/Config/ExecutionTagConfig.cs` ⚠️ **已迁移** - 无 Unity 依赖 +- [x] `Definition/Tag/Metadata/Config/FireTagConfig.cs` ⚠️ **已迁移** - 无 Unity 依赖 +- [x] `Definition/Tag/Metadata/Config/FreezeMaskTagConfig.cs` ⚠️ **已迁移** - 无 Unity 依赖 +- [x] `Definition/Tag/Metadata/Config/IceTagConfig.cs` ⚠️ **已迁移** - 无 Unity 依赖 +- [x] `Definition/Tag/Metadata/Config/IgniteBurstTagConfig.cs` ⚠️ **已迁移** - 无 Unity 依赖 +- [x] `Definition/Tag/Metadata/Config/InfernoTagConfig.cs` ⚠️ **已迁移** - 无 Unity 依赖 +- [x] `Definition/Tag/Metadata/Config/OverpenetrateTagConfig.cs` ⚠️ **已迁移** - 无 Unity 依赖 +- [x] `Definition/Tag/Metadata/Config/PierceTagConfig.cs` ⚠️ **已迁移** - 无 Unity 依赖 +- [x] `Definition/Tag/Metadata/Config/ShatterTagConfig.cs` ⚠️ **已迁移** - 无 Unity 依赖 +- [x] `Definition/Tag/Metadata/Config/TagConfigBase.cs` ⚠️ **已迁移** - 无 Unity 依赖 +- [x] `Definition/Tag/Metadata/TagDefinition.cs` ⚠️ **已迁移** - 无 Unity 依赖 +- [x] `Definition/Tag/Metadata/TagDefinitionRegistry.cs` ⚠️ **已迁移** - 简化版(移除 DR* 依赖) ### Definition/Event(6 个文件) @@ -555,8 +560,8 @@ L0 迁移阻塞链: ### 重构优先级 1. **第一波**:Definition/Enum + Event ✅ 已完成 -2. **第二波**:DataTable 整体移入 L1 -3. **第三波**:Vector3 → System.Numerics.Vector3 替换 +2. **第二波**:Vector3 → System.Numerics.Vector3,Mathf → System.Math ✅ **已完成部分** +3. **第三波**:Tag 系统迁移到 L1(TagDefinitionRegistry、TagEffectResolver 等) 4. **第四波**:GameEntry 静态调用 → 接口注入 5. **第五波**:Tilemap 接口抽象 6. **第六波**:剩余 L1 文件迁移 @@ -602,9 +607,40 @@ L0 迁移阻塞链: --- +## 第二波修改记录(2026-04-30) + +### 进度统计 + +| 类别 | 源文件数 | 已迁移 | 说明 | +|------|----------|--------|------| +| Definition/DataStruct | 2 | 2 | AttackPayload, HitContext(Vector3 替换) | +| Definition/Tag | 17 | 17 | Tag系统(TagDefinition、TagConfig系列、TagEffect系列等) | +| Entity/EntityLogic | 1 | 1 | EnemyTagStatusRuntime(Mathf → System.Math) | +| **小计** | **20** | **20** | - | + +### 新增/迁移文件(Mathf 替换) + +| 文件 | 修改内容 | +|------|----------| +| `Entity/EntityLogic/EnemyTagStatusRuntime.cs` | Mathf → System.Math | +| `Definition/Tag/Combat/StatusEffects/FireTagEffect.cs` | 新建,Mathf → System.Math | +| `Definition/Tag/Combat/StatusEffects/IceTagEffect.cs` | 新建,Mathf → System.Math | +| `Definition/Tag/Metadata/TagDefinitionRegistry.cs` | 简化版,移除 DR* 依赖 | + +### L0 构建状态 + +✅ `dotnet build GeometryTD.Domain` **成功**,0 警告 0 错误 + +### 阻塞说明 + +1. `TagDefinitionRegistry` 简化版已可用,`DRTag`/`DRTagConfig` 依赖已移除 +2. `TagGenerationRuleRegistry` 等仍有 `Debug.Assert` 待替换 + +--- + ## 验收标准 -- [ ] L0 独立构建成功(`dotnet build GeometryTD.Domain`) +- [x] L0 独立构建成功(`dotnet build GeometryTD.Domain`)✅ **2026-04-30** - [ ] L1 引用 L0 无循环依赖 - [ ] L2 引用 L1 无循环依赖 - [ ] 三层整体构建成功(`dotnet build Geometry-Tower-Defense-Base.sln`) diff --git a/src/GeometryTD.Domain/Definition/DataStruct/AttackPayload.cs b/src/GeometryTD.Domain/Definition/DataStruct/AttackPayload.cs new file mode 100644 index 0000000..91f8818 --- /dev/null +++ b/src/GeometryTD.Domain/Definition/DataStruct/AttackPayload.cs @@ -0,0 +1,46 @@ +using System; +using System.Numerics; + +namespace GeometryTD.Definition +{ + [Serializable] + public sealed class HitStatusModifierContext + { + public float BonusBurnDurationSeconds { get; set; } + public float BonusBurnDamagePerSecond { get; set; } + public float BonusSlowDurationSeconds { get; set; } + public float BonusSlowRatio { get; set; } + + public void Reset() + { + BonusBurnDurationSeconds = 0f; + BonusBurnDamagePerSecond = 0f; + BonusSlowDurationSeconds = 0f; + BonusSlowRatio = 0f; + } + } + + [Serializable] + public sealed class AttackPayload + { + public int BaseDamage { get; set; } + public AttackPropertyType AttackPropertyType { get; set; } + public int SourceEntityId { get; set; } + public int ProjectileEntityId { get; set; } + public Vector3 OriginPosition { get; set; } + public TagRuntimeData[] TagRuntimes { get; set; } = Array.Empty(); + + public AttackPayload Clone() + { + return new AttackPayload + { + BaseDamage = BaseDamage, + AttackPropertyType = AttackPropertyType, + SourceEntityId = SourceEntityId, + ProjectileEntityId = ProjectileEntityId, + OriginPosition = OriginPosition, + TagRuntimes = TagRuntimeUtility.CloneTagRuntimes(TagRuntimes) + }; + } + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/DataStruct/HitContext.cs b/src/GeometryTD.Domain/Definition/DataStruct/HitContext.cs new file mode 100644 index 0000000..6f4ae31 --- /dev/null +++ b/src/GeometryTD.Domain/Definition/DataStruct/HitContext.cs @@ -0,0 +1,44 @@ +using System; +using System.Numerics; + +namespace GeometryTD.Definition +{ + [Serializable] + public sealed class HitContext + { + public AttackPayload AttackPayload { get; set; } + public int FinalDamage { get; set; } + public bool IsCriticalHit { get; set; } + public bool IsKilled { get; set; } + public int TargetEntityId { get; set; } + public Vector3 TargetPosition { get; set; } + public int TargetCurrentHealthBeforeHit { get; set; } + public int TargetCurrentHealthAfterHit { get; set; } + public int TargetMaxHealth { get; set; } + public float TargetMoveSpeedMultiplierBeforeHit { get; set; } = 1f; + public TagType[] TargetStatusTagsBeforeHit { get; set; } = Array.Empty(); + public EnemyTagStatusRuntime TargetStatusRuntime { get; set; } + public float? CritRoll { get; set; } + public HitStatusModifierContext StatusModifierContext { get; set; } = new HitStatusModifierContext(); + + public bool HasTargetStatus(TagType tagType) + { + if (tagType == TagType.None || TargetStatusTagsBeforeHit == null || TargetStatusTagsBeforeHit.Length <= 0) + { + return false; + } + + for (int i = 0; i < TargetStatusTagsBeforeHit.Length; i++) + { + if (TargetStatusTagsBeforeHit[i] == tagType) + { + return true; + } + } + + return false; + } + + public bool HasSlowStatusBeforeHit => TargetMoveSpeedMultiplierBeforeHit < 0.999f || HasTargetStatus(TagType.Ice); + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/DataStruct/TagRuntimeUtility.cs b/src/GeometryTD.Domain/Definition/DataStruct/TagRuntimeUtility.cs new file mode 100644 index 0000000..51ed231 --- /dev/null +++ b/src/GeometryTD.Domain/Definition/DataStruct/TagRuntimeUtility.cs @@ -0,0 +1,33 @@ +using System; + +namespace GeometryTD.Definition +{ + public static class TagRuntimeUtility + { + public static TagRuntimeData[] CloneTagRuntimes(TagRuntimeData[] source) + { + if (source == null || source.Length <= 0) + { + return Array.Empty(); + } + + TagRuntimeData[] cloned = new TagRuntimeData[source.Length]; + for (int i = 0; i < source.Length; i++) + { + TagRuntimeData runtime = source[i]; + if (runtime == null) + { + continue; + } + + cloned[i] = new TagRuntimeData + { + TagType = runtime.TagType, + TotalStack = runtime.TotalStack + }; + } + + return cloned; + } + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Aggregation/TagRuntimeData.cs b/src/GeometryTD.Domain/Definition/Tag/Aggregation/TagRuntimeData.cs new file mode 100644 index 0000000..89f5826 --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Aggregation/TagRuntimeData.cs @@ -0,0 +1,11 @@ +using System; + +namespace GeometryTD.Definition +{ + [Serializable] + public sealed class TagRuntimeData + { + public TagType TagType { get; set; } + public int TotalStack { get; set; } + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Combat/EnemyStatusTagRegistry.cs b/src/GeometryTD.Domain/Definition/Tag/Combat/EnemyStatusTagRegistry.cs new file mode 100644 index 0000000..b8de7d5 --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Combat/EnemyStatusTagRegistry.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace GeometryTD.Definition +{ + public static class EnemyStatusTagRegistry + { + private static readonly Dictionary EffectsByTag = new Dictionary + { + [TagType.Fire] = new FireTagEffect(), + [TagType.Ice] = new IceTagEffect() + }; + + public static IReadOnlyDictionary Effects => EffectsByTag; + + public static bool TryGetEffect(TagType tagType, out IEnemyStatusTagEffect effect) + { + return EffectsByTag.TryGetValue(tagType, out effect); + } + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Combat/States/EnemyStatusTagStateBase.cs b/src/GeometryTD.Domain/Definition/Tag/Combat/States/EnemyStatusTagStateBase.cs new file mode 100644 index 0000000..b49e85a --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Combat/States/EnemyStatusTagStateBase.cs @@ -0,0 +1,6 @@ +namespace GeometryTD.Definition +{ + public abstract class EnemyStatusTagStateBase + { + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Combat/States/FireTagState.cs b/src/GeometryTD.Domain/Definition/Tag/Combat/States/FireTagState.cs new file mode 100644 index 0000000..6c77f8c --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Combat/States/FireTagState.cs @@ -0,0 +1,9 @@ +namespace GeometryTD.Definition +{ + public sealed class FireTagState : EnemyStatusTagStateBase + { + public float RemainingDuration { get; set; } + public float DamagePerSecond { get; set; } + public float PendingDamage { get; set; } + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Combat/States/IceTagState.cs b/src/GeometryTD.Domain/Definition/Tag/Combat/States/IceTagState.cs new file mode 100644 index 0000000..cd2b0bf --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Combat/States/IceTagState.cs @@ -0,0 +1,8 @@ +namespace GeometryTD.Definition +{ + public sealed class IceTagState : EnemyStatusTagStateBase + { + public float RemainingDuration { get; set; } + public float SlowMultiplier { get; set; } = 1f; + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Combat/StatusEffects/EnemyStatusTagEffectBase.cs b/src/GeometryTD.Domain/Definition/Tag/Combat/StatusEffects/EnemyStatusTagEffectBase.cs new file mode 100644 index 0000000..3df958c --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Combat/StatusEffects/EnemyStatusTagEffectBase.cs @@ -0,0 +1,29 @@ +using System; + +namespace GeometryTD.Definition +{ + public abstract class EnemyStatusTagEffectBase : IEnemyStatusTagEffect + { + public abstract TagType TagType { get; } + + public virtual void Apply(HitContext hitContext, TagRuntimeData runtimeData) + { + _ = hitContext; + _ = runtimeData; + } + + public virtual bool Tick(EnemyTagStatusRuntime runtime, float deltaTime, Action applyDamage) + { + _ = runtime; + _ = deltaTime; + _ = applyDamage; + return false; + } + + public virtual float GetMoveSpeedMultiplier(EnemyTagStatusRuntime runtime) + { + _ = runtime; + return 1f; + } + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Combat/StatusEffects/FireTagEffect.cs b/src/GeometryTD.Domain/Definition/Tag/Combat/StatusEffects/FireTagEffect.cs new file mode 100644 index 0000000..867412c --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Combat/StatusEffects/FireTagEffect.cs @@ -0,0 +1,91 @@ +using System; + +namespace GeometryTD.Definition +{ + public sealed class FireTagEffect : EnemyStatusTagEffectBase + { + public override TagType TagType => TagType.Fire; + + public override void Apply(HitContext hitContext, TagRuntimeData runtimeData) + { + if (!TagDefinitionRegistry.TryGetDefinition(TagType, out TagDefinition definition)) + { + return; + } + + if (hitContext == null) + { + return; + } + + if (hitContext.TargetStatusRuntime == null) + { + return; + } + + FireTagConfig config = definition.Config as FireTagConfig; + if (config == null) + { + return; + } + + if (!config.IsImplemented) + { + return; + } + + EnemyTagStatusRuntime runtime = hitContext.TargetStatusRuntime; + FireTagState state = runtime.GetOrCreateState(TagType); + int effectiveStack = (int)System.Math.Max(0, System.Math.Min(runtimeData.TotalStack, config.MaxEffectiveStack)); + HitStatusModifierContext modifierContext = hitContext.StatusModifierContext ?? new HitStatusModifierContext(); + float burnDamagePerSecond = System.Math.Max( + 0f, + config.BurnDamagePerSecondPerStack * effectiveStack + modifierContext.BonusBurnDamagePerSecond); + + if (burnDamagePerSecond <= 0f) + { + return; + } + + state.RemainingDuration = System.Math.Max( + state.RemainingDuration, + config.BurnDurationSeconds + modifierContext.BonusBurnDurationSeconds); + state.DamagePerSecond = System.Math.Max(state.DamagePerSecond, burnDamagePerSecond); + runtime.Activate(TagType); + } + + public override bool Tick(EnemyTagStatusRuntime runtime, float deltaTime, Action applyDamage) + { + FireTagState state = runtime.GetState(TagType); + if (state == null) + { + return false; + } + + float resolvedDeltaTime = System.Math.Max(0f, deltaTime); + if (resolvedDeltaTime <= 0f || state.RemainingDuration <= 0f) + { + return state.RemainingDuration > 0f; + } + + float appliedDuration = System.Math.Min(resolvedDeltaTime, state.RemainingDuration); + state.RemainingDuration = System.Math.Max(0f, state.RemainingDuration - appliedDuration); + state.PendingDamage += state.DamagePerSecond * appliedDuration; + + int resolvedDamage = (int)System.Math.Floor(state.PendingDamage); + if (resolvedDamage > 0) + { + applyDamage?.Invoke(resolvedDamage); + state.PendingDamage -= resolvedDamage; + } + + if (state.RemainingDuration > 0f) + { + return true; + } + + state.PendingDamage = 0f; + return false; + } + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Combat/StatusEffects/IEnemyStatusTagEffect.cs b/src/GeometryTD.Domain/Definition/Tag/Combat/StatusEffects/IEnemyStatusTagEffect.cs new file mode 100644 index 0000000..8741d7e --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Combat/StatusEffects/IEnemyStatusTagEffect.cs @@ -0,0 +1,12 @@ +using System; + +namespace GeometryTD.Definition +{ + public interface IEnemyStatusTagEffect + { + TagType TagType { get; } + void Apply(HitContext hitContext, TagRuntimeData runtimeData); + bool Tick(EnemyTagStatusRuntime runtime, float deltaTime, Action applyDamage); + float GetMoveSpeedMultiplier(EnemyTagStatusRuntime runtime); + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Combat/StatusEffects/IceTagEffect.cs b/src/GeometryTD.Domain/Definition/Tag/Combat/StatusEffects/IceTagEffect.cs new file mode 100644 index 0000000..392622c --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Combat/StatusEffects/IceTagEffect.cs @@ -0,0 +1,63 @@ +using System; + +namespace GeometryTD.Definition +{ + public sealed class IceTagEffect : EnemyStatusTagEffectBase + { + public override TagType TagType => TagType.Ice; + + public override void Apply(HitContext hitContext, TagRuntimeData runtimeData) + { + if (hitContext == null || runtimeData == null) + { + return; + } + + if (hitContext.TargetStatusRuntime == null) + { + return; + } + + if (!TagDefinitionRegistry.TryGetDefinition(TagType, out TagDefinition definition)) + { + return; + } + + IceTagConfig config = definition.Config as IceTagConfig; + if (config == null || !config.IsImplemented) + { + return; + } + + EnemyTagStatusRuntime runtime = hitContext.TargetStatusRuntime; + IceTagState state = runtime.GetOrCreateState(TagType); + HitStatusModifierContext modifierContext = hitContext.StatusModifierContext ?? new HitStatusModifierContext(); + float slowRatio = runtimeData.TotalStack * config.SlowRatioPerStack + modifierContext.BonusSlowRatio; + float slowMultiplier = System.Math.Max(config.MinMoveSpeedMultiplier, 1f - slowRatio); + state.RemainingDuration = System.Math.Max( + state.RemainingDuration, + config.SlowDurationSeconds + modifierContext.BonusSlowDurationSeconds); + state.SlowMultiplier = System.Math.Min(state.SlowMultiplier, slowMultiplier); + runtime.Activate(TagType); + } + + public override bool Tick(EnemyTagStatusRuntime runtime, float deltaTime, Action applyDamage) + { + _ = applyDamage; + IceTagState state = runtime.GetState(TagType); + if (state == null) + { + return false; + } + + state.RemainingDuration = System.Math.Max(0f, state.RemainingDuration - System.Math.Max(0f, deltaTime)); + return state.RemainingDuration > 0f; + } + + public override float GetMoveSpeedMultiplier(EnemyTagStatusRuntime runtime) + { + IceTagState state = runtime.GetState(TagType); + return state == null ? 1f : state.SlowMultiplier; + } + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/AbsoluteZeroTagConfig.cs b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/AbsoluteZeroTagConfig.cs new file mode 100644 index 0000000..a14ff0f --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/AbsoluteZeroTagConfig.cs @@ -0,0 +1,15 @@ +using System; + +namespace GeometryTD.Definition +{ + [Serializable] + public sealed class AbsoluteZeroTagConfig : TagConfigBase + { + public AbsoluteZeroTagConfig(bool isImplemented) : base(isImplemented) + { + } + + public float BonusSlowDurationSeconds { get; set; } = 1f; + public float BonusSlowRatioPerStack { get; set; } = 0.1f; + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/BurnSpreadTagConfig.cs b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/BurnSpreadTagConfig.cs new file mode 100644 index 0000000..94f2307 --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/BurnSpreadTagConfig.cs @@ -0,0 +1,12 @@ +using System; + +namespace GeometryTD.Definition +{ + [Serializable] + public sealed class BurnSpreadTagConfig : TagConfigBase + { + public BurnSpreadTagConfig(bool isImplemented) : base(isImplemented) + { + } + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/CritTagConfig.cs b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/CritTagConfig.cs new file mode 100644 index 0000000..0872c09 --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/CritTagConfig.cs @@ -0,0 +1,15 @@ +using System; + +namespace GeometryTD.Definition +{ + [Serializable] + public sealed class CritTagConfig : TagConfigBase + { + public CritTagConfig(bool isImplemented) : base(isImplemented) + { + } + + public float CritChancePerStack { get; set; } = 0.1f; + public float CritDamageMultiplier { get; set; } = 1.5f; + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/ExecutionTagConfig.cs b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/ExecutionTagConfig.cs new file mode 100644 index 0000000..cb6771c --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/ExecutionTagConfig.cs @@ -0,0 +1,15 @@ +using System; + +namespace GeometryTD.Definition +{ + [Serializable] + public sealed class ExecutionTagConfig : TagConfigBase + { + public ExecutionTagConfig(bool isImplemented) : base(isImplemented) + { + } + + public float TargetHealthThreshold { get; set; } = 0.3f; + public float DamageBonusPerStack { get; set; } = 0.5f; + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/FireTagConfig.cs b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/FireTagConfig.cs new file mode 100644 index 0000000..9a85bdc --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/FireTagConfig.cs @@ -0,0 +1,16 @@ +using System; + +namespace GeometryTD.Definition +{ + [Serializable] + public sealed class FireTagConfig : TagConfigBase + { + public FireTagConfig(bool isImplemented) : base(isImplemented) + { + } + + public float BurnDurationSeconds { get; set; } = 3f; + public float BurnDamagePerSecondPerStack { get; set; } = 20f; + public int MaxEffectiveStack { get; set; } = 5; + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/FreezeMaskTagConfig.cs b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/FreezeMaskTagConfig.cs new file mode 100644 index 0000000..59d7106 --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/FreezeMaskTagConfig.cs @@ -0,0 +1,12 @@ +using System; + +namespace GeometryTD.Definition +{ + [Serializable] + public sealed class FreezeMaskTagConfig : TagConfigBase + { + public FreezeMaskTagConfig(bool isImplemented) : base(isImplemented) + { + } + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/IceTagConfig.cs b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/IceTagConfig.cs new file mode 100644 index 0000000..4cafc66 --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/IceTagConfig.cs @@ -0,0 +1,16 @@ +using System; + +namespace GeometryTD.Definition +{ + [Serializable] + public sealed class IceTagConfig : TagConfigBase + { + public IceTagConfig(bool isImplemented) : base(isImplemented) + { + } + + public float SlowDurationSeconds { get; set; } = 2f; + public float SlowRatioPerStack { get; set; } = 0.2f; + public float MinMoveSpeedMultiplier { get; set; } = 0.4f; + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/IgniteBurstTagConfig.cs b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/IgniteBurstTagConfig.cs new file mode 100644 index 0000000..4ce16cd --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/IgniteBurstTagConfig.cs @@ -0,0 +1,12 @@ +using System; + +namespace GeometryTD.Definition +{ + [Serializable] + public sealed class IgniteBurstTagConfig : TagConfigBase + { + public IgniteBurstTagConfig(bool isImplemented) : base(isImplemented) + { + } + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/InfernoTagConfig.cs b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/InfernoTagConfig.cs new file mode 100644 index 0000000..e064936 --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/InfernoTagConfig.cs @@ -0,0 +1,15 @@ +using System; + +namespace GeometryTD.Definition +{ + [Serializable] + public sealed class InfernoTagConfig : TagConfigBase + { + public InfernoTagConfig(bool isImplemented) : base(isImplemented) + { + } + + public float BonusBurnDurationSeconds { get; set; } = 2f; + public float BonusBurnDamagePerSecondPerStack { get; set; } = 20f; + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/OverpenetrateTagConfig.cs b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/OverpenetrateTagConfig.cs new file mode 100644 index 0000000..b93d17e --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/OverpenetrateTagConfig.cs @@ -0,0 +1,12 @@ +using System; + +namespace GeometryTD.Definition +{ + [Serializable] + public sealed class OverpenetrateTagConfig : TagConfigBase + { + public OverpenetrateTagConfig(bool isImplemented) : base(isImplemented) + { + } + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/PierceTagConfig.cs b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/PierceTagConfig.cs new file mode 100644 index 0000000..dd2eacc --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/PierceTagConfig.cs @@ -0,0 +1,12 @@ +using System; + +namespace GeometryTD.Definition +{ + [Serializable] + public sealed class PierceTagConfig : TagConfigBase + { + public PierceTagConfig(bool isImplemented) : base(isImplemented) + { + } + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/ShatterTagConfig.cs b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/ShatterTagConfig.cs new file mode 100644 index 0000000..00fd49e --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/ShatterTagConfig.cs @@ -0,0 +1,15 @@ +using System; + +namespace GeometryTD.Definition +{ + [Serializable] + public sealed class ShatterTagConfig : TagConfigBase + { + public ShatterTagConfig(bool isImplemented) : base(isImplemented) + { + } + + public bool RequiresSlowedTarget { get; set; } = true; + public float DamageBonusPerStack { get; set; } = 0.25f; + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/TagConfigBase.cs b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/TagConfigBase.cs new file mode 100644 index 0000000..11635a4 --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Metadata/Config/TagConfigBase.cs @@ -0,0 +1,15 @@ +using System; + +namespace GeometryTD.Definition +{ + [Serializable] + public abstract class TagConfigBase + { + public bool IsImplemented { get; set; } + + protected TagConfigBase(bool isImplemented) + { + IsImplemented = isImplemented; + } + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Metadata/TagDefinition.cs b/src/GeometryTD.Domain/Definition/Tag/Metadata/TagDefinition.cs new file mode 100644 index 0000000..cff758a --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Metadata/TagDefinition.cs @@ -0,0 +1,14 @@ +using System; + +namespace GeometryTD.Definition +{ + [Serializable] + public sealed class TagDefinition + { + public TagType TagType { get; set; } + public TagCategory Category { get; set; } + public TagTriggerPhase TriggerPhase { get; set; } + public string Description { get; set; } + public TagConfigBase Config { get; set; } + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Definition/Tag/Metadata/TagDefinitionRegistry.cs b/src/GeometryTD.Domain/Definition/Tag/Metadata/TagDefinitionRegistry.cs new file mode 100644 index 0000000..47e3f49 --- /dev/null +++ b/src/GeometryTD.Domain/Definition/Tag/Metadata/TagDefinitionRegistry.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; + +namespace GeometryTD.Definition +{ + public static class TagDefinitionRegistry + { + private static readonly Dictionary DefinitionsByTag = CreateDefaultDefinitions(); + + public static IReadOnlyDictionary Definitions => DefinitionsByTag; + + public static void ResetToDefaults() + { + DefinitionsByTag.Clear(); + foreach (KeyValuePair pair in CreateDefaultDefinitions()) + { + DefinitionsByTag.Add(pair.Key, pair.Value); + } + } + + public static bool TryGetDefinition(TagType tagType, out TagDefinition definition) + { + return DefinitionsByTag.TryGetValue(tagType, out definition); + } + + public static void ApplyTagRow(int tagId, bool isImplemented) + { + TagType tagType = (TagType)tagId; + if (DefinitionsByTag.TryGetValue(tagType, out TagDefinition definition) && definition.Config != null) + { + definition.Config.IsImplemented = isImplemented; + } + } + + private static Dictionary CreateDefaultDefinitions() + { + return new Dictionary + { + [TagType.Fire] = CreateDefinition(TagType.Fire, TagCategory.Status, TagTriggerPhase.OnAfterHit, new FireTagConfig(true)), + [TagType.BurnSpread] = CreateDefinition(TagType.BurnSpread, TagCategory.AttackShape, TagTriggerPhase.None, new BurnSpreadTagConfig(false)), + [TagType.IgniteBurst] = CreateDefinition(TagType.IgniteBurst, TagCategory.AttackShape, TagTriggerPhase.None, new IgniteBurstTagConfig(false)), + [TagType.Inferno] = CreateDefinition(TagType.Inferno, TagCategory.StatusModifier, TagTriggerPhase.OnAfterHit, new InfernoTagConfig(true)), + [TagType.Ice] = CreateDefinition(TagType.Ice, TagCategory.Status, TagTriggerPhase.OnAfterHit, new IceTagConfig(true)), + [TagType.FreezeMask] = CreateDefinition(TagType.FreezeMask, TagCategory.AttackShape, TagTriggerPhase.None, new FreezeMaskTagConfig(false)), + [TagType.Shatter] = CreateDefinition(TagType.Shatter, TagCategory.NumericModifier, TagTriggerPhase.OnBeforeHit, new ShatterTagConfig(true)), + [TagType.AbsoluteZero] = CreateDefinition(TagType.AbsoluteZero, TagCategory.StatusModifier, TagTriggerPhase.OnAfterHit, new AbsoluteZeroTagConfig(true)), + [TagType.Pierce] = CreateDefinition(TagType.Pierce, TagCategory.AttackShape, TagTriggerPhase.None, new PierceTagConfig(false)), + [TagType.Crit] = CreateDefinition(TagType.Crit, TagCategory.NumericModifier, TagTriggerPhase.OnBeforeHit, new CritTagConfig(true)), + [TagType.Overpenetrate] = CreateDefinition(TagType.Overpenetrate, TagCategory.AttackShape, TagTriggerPhase.None, new OverpenetrateTagConfig(false)), + [TagType.Execution] = CreateDefinition(TagType.Execution, TagCategory.NumericModifier, TagTriggerPhase.OnBeforeHit, new ExecutionTagConfig(true)) + }; + } + + private static TagDefinition CreateDefinition( + TagType tagType, + TagCategory category, + TagTriggerPhase triggerPhase, + TagConfigBase config) + { + return new TagDefinition + { + TagType = tagType, + Category = category, + TriggerPhase = triggerPhase, + Description = string.Empty, + Config = config + }; + } + } +} \ No newline at end of file diff --git a/src/GeometryTD.Domain/Entity/EntityLogic/EnemyTagStatusRuntime.cs b/src/GeometryTD.Domain/Entity/EntityLogic/EnemyTagStatusRuntime.cs new file mode 100644 index 0000000..0c7f13a --- /dev/null +++ b/src/GeometryTD.Domain/Entity/EntityLogic/EnemyTagStatusRuntime.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; + +namespace GeometryTD.Definition +{ + public sealed class EnemyTagStatusRuntime + { + private readonly Dictionary _statesByTag = new(); + private readonly List _activeTags = new(); + + public bool HasStatus(TagType tagType) + { + return _statesByTag.ContainsKey(tagType); + } + + public TagType[] GetActiveTagSnapshot() + { + if (_activeTags.Count <= 0) + { + return Array.Empty(); + } + + return _activeTags.ToArray(); + } + + public void Reset() + { + _statesByTag.Clear(); + _activeTags.Clear(); + } + + public void Activate(TagType tagType) + { + if (_activeTags.Contains(tagType)) + { + return; + } + + _activeTags.Add(tagType); + } + + public TState GetState(TagType tagType) where TState : EnemyStatusTagStateBase + { + if (!_statesByTag.TryGetValue(tagType, out EnemyStatusTagStateBase state)) + { + return null; + } + + return (TState)state; + } + + public TState GetOrCreateState(TagType tagType) where TState : EnemyStatusTagStateBase, new() + { + if (_statesByTag.TryGetValue(tagType, out EnemyStatusTagStateBase existingState)) + { + return (TState)existingState; + } + + TState state = new TState(); + _statesByTag[tagType] = state; + return state; + } + + public void Tick(float deltaTime, Action applyDamage) + { + float resolvedDeltaTime = System.Math.Max(0f, deltaTime); + if (resolvedDeltaTime <= 0f) + { + return; + } + + for (int i = _activeTags.Count - 1; i >= 0; i--) + { + TagType tagType = _activeTags[i]; + if (!EnemyStatusTagRegistry.TryGetEffect(tagType, out IEnemyStatusTagEffect effect)) + { + continue; + } + + if (effect.Tick(this, resolvedDeltaTime, applyDamage)) + { + continue; + } + + _statesByTag.Remove(tagType); + _activeTags.RemoveAt(i); + } + } + + public float GetMoveSpeedMultiplier() + { + float multiplier = 1f; + for (int i = 0; i < _activeTags.Count; i++) + { + TagType tagType = _activeTags[i]; + if (!EnemyStatusTagRegistry.TryGetEffect(tagType, out IEnemyStatusTagEffect effect)) + { + continue; + } + + multiplier = System.Math.Min(multiplier, effect.GetMoveSpeedMultiplier(this)); + } + + return multiplier; + } + } +} \ No newline at end of file diff --git a/src/GeometryTD.Infrastructure/Entity/EntityLogic/EnemyTagStatusRuntime.cs b/src/GeometryTD.Infrastructure/Entity/EntityLogic/EnemyTagStatusRuntime.cs new file mode 100644 index 0000000..5aaafa8 --- /dev/null +++ b/src/GeometryTD.Infrastructure/Entity/EntityLogic/EnemyTagStatusRuntime.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using GeometryTD.Definition; + +namespace GeometryTD.Entity +{ + public sealed class EnemyTagStatusRuntime + { + private readonly Dictionary _statesByTag = new(); + private readonly List _activeTags = new(); + + public bool HasStatus(TagType tagType) + { + return _statesByTag.ContainsKey(tagType); + } + + public TagType[] GetActiveTagSnapshot() + { + if (_activeTags.Count <= 0) + { + return Array.Empty(); + } + + return _activeTags.ToArray(); + } + + public void Reset() + { + _statesByTag.Clear(); + _activeTags.Clear(); + } + + public void Activate(TagType tagType) + { + if (_activeTags.Contains(tagType)) + { + return; + } + + _activeTags.Add(tagType); + } + + public TState GetState(TagType tagType) where TState : EnemyStatusTagStateBase + { + if (!_statesByTag.TryGetValue(tagType, out EnemyStatusTagStateBase state)) + { + return null; + } + + Debug.Assert(state is TState); + return state as TState; + } + + public TState GetOrCreateState(TagType tagType) where TState : EnemyStatusTagStateBase, new() + { + if (_statesByTag.TryGetValue(tagType, out EnemyStatusTagStateBase existingState)) + { + Debug.Assert(existingState is TState); + return existingState as TState; + } + + TState state = new TState(); + _statesByTag[tagType] = state; + return state; + } + + public void Tick(float deltaTime, Action applyDamage) + { + float resolvedDeltaTime = System.Math.Max(0f, deltaTime); + if (resolvedDeltaTime <= 0f) + { + return; + } + + for (int i = _activeTags.Count - 1; i >= 0; i--) + { + TagType tagType = _activeTags[i]; + Debug.Assert(EnemyStatusTagRegistry.TryGetEffect(tagType, out IEnemyStatusTagEffect effect)); + + if (effect.Tick(this, resolvedDeltaTime, applyDamage)) + { + continue; + } + + _statesByTag.Remove(tagType); + _activeTags.RemoveAt(i); + } + } + + public float GetMoveSpeedMultiplier() + { + float multiplier = 1f; + for (int i = 0; i < _activeTags.Count; i++) + { + TagType tagType = _activeTags[i]; + Debug.Assert(EnemyStatusTagRegistry.TryGetEffect(tagType, out IEnemyStatusTagEffect effect)); + multiplier = System.Math.Min(multiplier, effect.GetMoveSpeedMultiplier(this)); + } + + return multiplier; + } + } +} \ No newline at end of file