Start Tag System

This commit is contained in:
2026-03-10 13:11:30 +08:00
parent 34ef001ef3
commit 52f9e212b9
107 changed files with 1850 additions and 50 deletions
@@ -0,0 +1,23 @@
using System;
using GeometryTD.CustomUtility;
namespace GeometryTD.Definition
{
[Serializable]
public sealed class AttackPayload
{
public int BaseDamage { get; set; }
public AttackPropertyType AttackPropertyType { get; set; }
public TagRuntimeData[] TagRuntimes { get; set; } = Array.Empty<TagRuntimeData>();
public AttackPayload Clone()
{
return new AttackPayload
{
BaseDamage = BaseDamage,
AttackPropertyType = AttackPropertyType,
TagRuntimes = InventoryCloneUtility.CloneTagRuntimes(TagRuntimes)
};
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4ed2fd20f9feb0f4d9448029b5bf63df
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,13 @@
using System;
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; }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c26677bee93904e4da19e339c6e3a803
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,10 @@
namespace GeometryTD.Definition
{
public enum TagCategory : byte
{
None = 0,
Status = 1,
NumericModifier = 2,
AttackShape = 3
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8c0d6ea8de936264db4aa2f5df963d2b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,11 @@
namespace GeometryTD.Definition
{
public enum TagTriggerPhase : byte
{
None = 0,
OnBeforeHit = 1,
OnHit = 2,
OnAfterHit = 3,
OnKill = 4
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bdfe1f7eba0a78742ae112cd61e9da49
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a2a3c49c622040edbd52d0c66df2377e
timeCreated: 1773105664
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1ff28b0dd76d49ecb0f6e615df4f7a62
timeCreated: 1773105681
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1909b51822cc4368bc604674fd119123
timeCreated: 1773105715
@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace GeometryTD.Definition
{
public sealed class AbsoluteZeroTagEffect : EnemyStatusTagEffectBase
{
public override TagType TagType => TagType.AbsoluteZero;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: deecaa9a38c04725b5abeb5488cbe53f
timeCreated: 1773106137
@@ -0,0 +1,32 @@
using System;
using GeometryTD.Entity;
namespace GeometryTD.Definition
{
public abstract class EnemyStatusTagEffectBase : IEnemyStatusTagEffect
{
public abstract TagType TagType { get; }
public virtual void Apply(AttackPayload attackPayload, EnemyTagStatusRuntime runtime,
TagRuntimeData runtimeData)
{
_ = attackPayload;
_ = runtime;
_ = runtimeData;
}
public virtual bool Tick(EnemyTagStatusRuntime runtime, float deltaTime, Action<int> applyDamage)
{
_ = runtime;
_ = deltaTime;
_ = applyDamage;
return false;
}
public virtual float GetMoveSpeedMultiplier(EnemyTagStatusRuntime runtime)
{
_ = runtime;
return 1f;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: aefaa5745cc94c0aae92109e64d83dc1
timeCreated: 1773105755
@@ -0,0 +1,68 @@
using System;
using GeometryTD.Entity;
using UnityEngine;
namespace GeometryTD.Definition
{
public sealed class FireTagEffect : EnemyStatusTagEffectBase
{
public override TagType TagType => TagType.Fire;
public override void Apply(AttackPayload attackPayload, EnemyTagStatusRuntime runtime,
TagRuntimeData runtimeData)
{
Debug.Assert(TagConfigRegistry.TryGetDefinition(TagType, out TagDefinitionData definition));
FireTagConfig config = definition.Config as FireTagConfig;
Debug.Assert(config != null);
if (!config.IsImplemented)
{
return;
}
FireTagState state = runtime.GetOrCreateState<FireTagState>(TagType);
float burnDamagePerSecond = Mathf.Max(0f,
config.BurnDamagePerSecondPerStack * runtimeData.TotalStack);
if (burnDamagePerSecond <= 0f)
{
return;
}
state.RemainingDuration = Mathf.Max(state.RemainingDuration, config.BurnDurationSeconds);
state.DamagePerSecond = Mathf.Max(state.DamagePerSecond, burnDamagePerSecond);
runtime.Activate(TagType);
}
public override bool Tick(EnemyTagStatusRuntime runtime, float deltaTime, Action<int> applyDamage)
{
FireTagState state = runtime.GetState<FireTagState>(TagType);
Debug.Assert(state != null);
float resolvedDeltaTime = Mathf.Max(0f, deltaTime);
if (resolvedDeltaTime <= 0f || state.RemainingDuration <= 0f)
{
return state.RemainingDuration > 0f;
}
float appliedDuration = Mathf.Min(resolvedDeltaTime, state.RemainingDuration);
state.RemainingDuration = Mathf.Max(0f, state.RemainingDuration - appliedDuration);
state.PendingDamage += state.DamagePerSecond * appliedDuration;
int resolvedDamage = Mathf.FloorToInt(state.PendingDamage);
if (resolvedDamage > 0)
{
applyDamage?.Invoke(resolvedDamage);
state.PendingDamage -= resolvedDamage;
}
if (state.RemainingDuration > 0f)
{
return true;
}
state.PendingDamage = 0f;
return false;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6609b360347c4fc9bccbe288efa60ae4
timeCreated: 1773105786
@@ -0,0 +1,13 @@
using System;
using GeometryTD.Entity;
namespace GeometryTD.Definition
{
public interface IEnemyStatusTagEffect
{
TagType TagType { get; }
void Apply(AttackPayload attackPayload, EnemyTagStatusRuntime runtime, TagRuntimeData runtimeData);
bool Tick(EnemyTagStatusRuntime runtime, float deltaTime, Action<int> applyDamage);
float GetMoveSpeedMultiplier(EnemyTagStatusRuntime runtime);
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 07eac037b9274ddaa6352412c9043f62
timeCreated: 1773105725
@@ -0,0 +1,50 @@
using System;
using GeometryTD.Entity;
using UnityEngine;
namespace GeometryTD.Definition
{
public sealed class IceTagEffect : EnemyStatusTagEffectBase
{
public override TagType TagType => TagType.Ice;
public override void Apply(AttackPayload attackPayload, EnemyTagStatusRuntime runtime,
TagRuntimeData runtimeData)
{
_ = attackPayload;
Debug.Assert(runtimeData != null);
Debug.Assert(runtimeData.TotalStack > 0);
Debug.Assert(TagConfigRegistry.TryGetDefinition(TagType, out TagDefinitionData definition));
IceTagConfig config = definition.Config as IceTagConfig;
Debug.Assert(config != null);
if (!config.IsImplemented)
{
return;
}
IceTagState state = runtime.GetOrCreateState<IceTagState>(TagType);
float slowMultiplier = Mathf.Max(config.MinMoveSpeedMultiplier,
1f - runtimeData.TotalStack * config.SlowRatioPerStack);
state.RemainingDuration = Mathf.Max(state.RemainingDuration, config.SlowDurationSeconds);
state.SlowMultiplier = Mathf.Min(state.SlowMultiplier, slowMultiplier);
runtime.Activate(TagType);
}
public override bool Tick(EnemyTagStatusRuntime runtime, float deltaTime, Action<int> applyDamage)
{
_ = applyDamage;
IceTagState state = runtime.GetState<IceTagState>(TagType);
Debug.Assert(state != null);
state.RemainingDuration = Mathf.Max(0f, state.RemainingDuration - Mathf.Max(0f, deltaTime));
return state.RemainingDuration > 0f;
}
public override float GetMoveSpeedMultiplier(EnemyTagStatusRuntime runtime)
{
IceTagState state = runtime.GetState<IceTagState>(TagType);
return state == null ? 1f : state.SlowMultiplier;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0787cd2263b441ceba5b3ad381827677
timeCreated: 1773106119
@@ -0,0 +1,7 @@
namespace GeometryTD.Definition
{
public sealed class InfernoTagEffect : EnemyStatusTagEffectBase
{
public override TagType TagType => TagType.Inferno;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2aa813ec42ea4dddb717ff28da764f15
timeCreated: 1773106082
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using GeometryTD.Entity;
using UnityEngine;
namespace GeometryTD.Definition
{
public static class EnemyStatusTagRegistry
{
private static readonly Dictionary<TagType, IEnemyStatusTagEffect> EffectsByTag =
new Dictionary<TagType, IEnemyStatusTagEffect>
{
[TagType.Fire] = new FireTagEffect(),
[TagType.Inferno] = new InfernoTagEffect(),
[TagType.Ice] = new IceTagEffect(),
[TagType.AbsoluteZero] = new AbsoluteZeroTagEffect()
};
public static IReadOnlyDictionary<TagType, IEnemyStatusTagEffect> Effects => EffectsByTag;
public static bool TryGetEffect(TagType tagType, out IEnemyStatusTagEffect effect)
{
return EffectsByTag.TryGetValue(tagType, out effect);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 49ed835d5a444f199fa74b2cb4d06a51
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c90264b23a9c45c0b21bc627a6109282
timeCreated: 1773105821
@@ -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; } = 0f;
public float BonusSlowRatioPerStack { get; set; } = 0f;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a92545f498ff4b8ab974a73d220f2cbc
timeCreated: 1773105984
@@ -0,0 +1,15 @@
using System;
namespace GeometryTD.Definition
{
[Serializable]
public sealed class BurnSpreadTagConfig : TagConfigBase
{
public BurnSpreadTagConfig(bool isImplemented) : base(isImplemented)
{
}
public float SpreadRadius { get; set; } = 0f;
public float SpreadDamageRate { get; set; } = 0f;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 16fa8f668caf4a6a9d384f53c86ef6d8
timeCreated: 1773105868
@@ -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;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 21c22a1b79d74aa58ef0054fed71b90a
timeCreated: 1773106012
@@ -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;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 865ffdb6e1df446fabfb2355d06ef60a
timeCreated: 1773106044
@@ -0,0 +1,15 @@
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;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3225e9a8d28b4133a6484f576c4662ae
timeCreated: 1773105844
@@ -0,0 +1,14 @@
using System;
namespace GeometryTD.Definition
{
[Serializable]
public sealed class FreezeMaskTagConfig : TagConfigBase
{
public FreezeMaskTagConfig(bool isImplemented) : base(isImplemented)
{
}
public float FreezeBuildUpPerStack { get; set; } = 0f;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fa016e0d4c76466b8fb665656ee4bc9e
timeCreated: 1773105940
@@ -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;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 546b506913154d74a9a7cd4b3aa36d12
timeCreated: 1773105919
@@ -0,0 +1,15 @@
using System;
namespace GeometryTD.Definition
{
[Serializable]
public sealed class IgniteBurstTagConfig : TagConfigBase
{
public IgniteBurstTagConfig(bool isImplemented) : base(isImplemented)
{
}
public float BurstRadius { get; set; } = 0f;
public float BurstDamageRate { get; set; } = 0f;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b412ab82145242e3a7ce99991199cc35
timeCreated: 1773105884
@@ -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; } = 0f;
public float BonusBurnDamagePerSecondPerStack { get; set; } = 0f;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: be707e334ca74a2a8a2a9d7773e70138
timeCreated: 1773105897
@@ -0,0 +1,15 @@
using System;
namespace GeometryTD.Definition
{
[Serializable]
public sealed class OverpenetrateTagConfig : TagConfigBase
{
public OverpenetrateTagConfig(bool isImplemented) : base(isImplemented)
{
}
public int ExtraPenetrationCount { get; set; } = 0;
public float RemainingDamageRate { get; set; } = 0f;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1d017f96198041cc97f2db1cb4f84fcb
timeCreated: 1773106028
@@ -0,0 +1,14 @@
using System;
namespace GeometryTD.Definition
{
[Serializable]
public sealed class PierceTagConfig : TagConfigBase
{
public PierceTagConfig(bool isImplemented) : base(isImplemented)
{
}
public int ExtraPierceCount { get; set; } = 0;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3081fa157ac74961a2a4ffb3a6e10851
timeCreated: 1773105999
@@ -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; } = 0f;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d0f84f8e59bd4d0ea620742802223d87
timeCreated: 1773105968
@@ -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;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c2d0d4db7370b9f4b9d4d2cc4e84e46c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,103 @@
using System.Collections.Generic;
namespace GeometryTD.Definition
{
public static class TagConfigRegistry
{
private static readonly Dictionary<TagType, TagDefinitionData> DefinitionsByTag =
new Dictionary<TagType, TagDefinitionData>
{
[TagType.Fire] = new TagDefinitionData
{
TagType = TagType.Fire,
Category = TagCategory.Status,
TriggerPhase = TagTriggerPhase.OnAfterHit,
Config = new FireTagConfig(true)
},
[TagType.BurnSpread] = new TagDefinitionData
{
TagType = TagType.BurnSpread,
Category = TagCategory.AttackShape,
TriggerPhase = TagTriggerPhase.OnKill,
Config = new BurnSpreadTagConfig(false)
},
[TagType.IgniteBurst] = new TagDefinitionData
{
TagType = TagType.IgniteBurst,
Category = TagCategory.AttackShape,
TriggerPhase = TagTriggerPhase.OnKill,
Config = new IgniteBurstTagConfig(false)
},
[TagType.Inferno] = new TagDefinitionData
{
TagType = TagType.Inferno,
Category = TagCategory.Status,
TriggerPhase = TagTriggerPhase.OnAfterHit,
Config = new InfernoTagConfig(false)
},
[TagType.Ice] = new TagDefinitionData
{
TagType = TagType.Ice,
Category = TagCategory.Status,
TriggerPhase = TagTriggerPhase.OnAfterHit,
Config = new IceTagConfig(true)
},
[TagType.FreezeMask] = new TagDefinitionData
{
TagType = TagType.FreezeMask,
Category = TagCategory.AttackShape,
TriggerPhase = TagTriggerPhase.OnAfterHit,
Config = new FreezeMaskTagConfig(false)
},
[TagType.Shatter] = new TagDefinitionData
{
TagType = TagType.Shatter,
Category = TagCategory.NumericModifier,
TriggerPhase = TagTriggerPhase.OnBeforeHit,
Config = new ShatterTagConfig(false)
},
[TagType.AbsoluteZero] = new TagDefinitionData
{
TagType = TagType.AbsoluteZero,
Category = TagCategory.Status,
TriggerPhase = TagTriggerPhase.OnAfterHit,
Config = new AbsoluteZeroTagConfig(false)
},
[TagType.Pierce] = new TagDefinitionData
{
TagType = TagType.Pierce,
Category = TagCategory.AttackShape,
TriggerPhase = TagTriggerPhase.OnHit,
Config = new PierceTagConfig(false)
},
[TagType.Crit] = new TagDefinitionData
{
TagType = TagType.Crit,
Category = TagCategory.NumericModifier,
TriggerPhase = TagTriggerPhase.OnBeforeHit,
Config = new CritTagConfig(true)
},
[TagType.Overpenetrate] = new TagDefinitionData
{
TagType = TagType.Overpenetrate,
Category = TagCategory.AttackShape,
TriggerPhase = TagTriggerPhase.OnHit,
Config = new OverpenetrateTagConfig(false)
},
[TagType.Execution] = new TagDefinitionData
{
TagType = TagType.Execution,
Category = TagCategory.NumericModifier,
TriggerPhase = TagTriggerPhase.OnBeforeHit,
Config = new ExecutionTagConfig(true)
}
};
public static IReadOnlyDictionary<TagType, TagDefinitionData> Definitions => DefinitionsByTag;
public static bool TryGetDefinition(TagType tagType, out TagDefinitionData definition)
{
return DefinitionsByTag.TryGetValue(tagType, out definition);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 717bc2031fdf31e45a3de05cf4e2f24f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,13 @@
using System;
namespace GeometryTD.Definition
{
[Serializable]
public sealed class TagDefinitionData
{
public TagType TagType { get; set; }
public TagCategory Category { get; set; }
public TagTriggerPhase TriggerPhase { get; set; }
public TagConfigBase Config { get; set; }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c0011d6ce1b3dd14a89c6af6524cc732
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6fb08c76711246c2afa02d60769d76ef
timeCreated: 1773106291
@@ -0,0 +1,41 @@
namespace GeometryTD.Definition
{
public static class AttackShapeTagEffectHandler
{
public static void Apply(HitContext hitContext, TagTriggerPhase triggerPhase)
{
if (hitContext?.AttackPayload?.TagRuntimes == null || hitContext.AttackPayload.TagRuntimes.Length <= 0)
{
return;
}
for (int i = 0; i < hitContext.AttackPayload.TagRuntimes.Length; i++)
{
TagRuntimeData runtime = hitContext.AttackPayload.TagRuntimes[i];
if (runtime == null || runtime.TotalStack <= 0)
{
continue;
}
if (!TagConfigRegistry.TryGetDefinition(runtime.TagType, out TagDefinitionData definition) ||
definition == null ||
definition.Category != TagCategory.AttackShape ||
definition.TriggerPhase != triggerPhase ||
definition.Config == null)
{
continue;
}
switch (runtime.TagType)
{
case TagType.BurnSpread:
case TagType.IgniteBurst:
case TagType.FreezeMask:
case TagType.Pierce:
case TagType.Overpenetrate:
break;
}
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 434d659f0e0963d40a7da7e03b144966
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,107 @@
using UnityEngine;
namespace GeometryTD.Definition
{
public static class NumericTagEffectHandler
{
public static void ApplyBeforeHit(
HitContext hitContext,
int targetCurrentHealth,
int targetMaxHealth,
bool targetHasSlowStatus,
float? critRoll = null)
{
if (hitContext == null || hitContext.AttackPayload == null)
{
return;
}
TagRuntimeData[] tagRuntimes = hitContext.AttackPayload.TagRuntimes;
if (tagRuntimes == null || tagRuntimes.Length <= 0)
{
return;
}
for (int i = 0; i < tagRuntimes.Length; i++)
{
TagRuntimeData runtime = tagRuntimes[i];
if (runtime == null || runtime.TotalStack <= 0)
{
continue;
}
if (!TagConfigRegistry.TryGetDefinition(runtime.TagType, out TagDefinitionData definition) ||
definition == null ||
definition.Category != TagCategory.NumericModifier ||
definition.TriggerPhase != TagTriggerPhase.OnBeforeHit ||
definition.Config == null)
{
continue;
}
switch (runtime.TagType)
{
case TagType.Crit:
ApplyCrit(hitContext, runtime.TotalStack, definition.Config as CritTagConfig, critRoll);
break;
case TagType.Execution:
ApplyExecution(hitContext, runtime.TotalStack, targetCurrentHealth, targetMaxHealth, definition.Config as ExecutionTagConfig);
break;
case TagType.Shatter:
ApplyShatter(hitContext, runtime.TotalStack, targetHasSlowStatus, definition.Config as ShatterTagConfig);
break;
}
}
hitContext.IsKilled = targetCurrentHealth > 0 && hitContext.FinalDamage >= targetCurrentHealth;
}
private static void ApplyCrit(HitContext hitContext, int stack, CritTagConfig config, float? critRoll)
{
if (config == null || !config.IsImplemented || stack <= 0)
{
return;
}
float chance = Mathf.Clamp01(stack * config.CritChancePerStack);
float resolvedCritRoll = critRoll ?? Random.value;
if (resolvedCritRoll > chance)
{
return;
}
hitContext.FinalDamage = Mathf.Max(0, Mathf.RoundToInt(hitContext.FinalDamage * config.CritDamageMultiplier));
hitContext.IsCriticalHit = true;
}
private static void ApplyExecution(
HitContext hitContext,
int stack,
int targetCurrentHealth,
int targetMaxHealth,
ExecutionTagConfig config)
{
if (config == null || !config.IsImplemented || stack <= 0 || targetMaxHealth <= 0)
{
return;
}
float healthRatio = Mathf.Clamp01((float)Mathf.Max(0, targetCurrentHealth) / targetMaxHealth);
if (healthRatio > config.TargetHealthThreshold)
{
return;
}
float multiplier = 1f + stack * config.DamageBonusPerStack;
hitContext.FinalDamage = Mathf.Max(0, Mathf.RoundToInt(hitContext.FinalDamage * multiplier));
}
private static void ApplyShatter(HitContext hitContext, int stack, bool targetHasSlowStatus, ShatterTagConfig config)
{
_ = hitContext;
_ = stack;
_ = targetHasSlowStatus;
_ = config;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eef83fab509bf1b4aaea8504c8a2fc7f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,99 @@
using GeometryTD.Entity;
using UnityEngine;
namespace GeometryTD.Definition
{
public static class TagEffectResolver
{
public static HitContext ResolveBeforeHit(
AttackPayload attackPayload,
int targetCurrentHealth,
int targetMaxHealth,
bool targetHasSlowStatus,
float? critRoll = null)
{
_ = targetHasSlowStatus;
AttackPayload resolvedPayload = attackPayload?.Clone() ?? new AttackPayload();
HitContext hitContext = new HitContext
{
AttackPayload = resolvedPayload,
FinalDamage = Mathf.Max(0, resolvedPayload.BaseDamage),
IsCriticalHit = false,
IsKilled = targetCurrentHealth > 0 && resolvedPayload.BaseDamage >= targetCurrentHealth
};
NumericTagEffectHandler.ApplyBeforeHit(
hitContext,
targetCurrentHealth,
targetMaxHealth,
targetHasSlowStatus,
critRoll);
return hitContext;
}
public static void ApplyAfterHit(AttackPayload attackPayload, EnemyTagStatusRuntime targetStatusRuntime)
{
if (attackPayload != null && targetStatusRuntime != null)
{
TagRuntimeData[] tagRuntimes = attackPayload.TagRuntimes;
if (tagRuntimes != null && tagRuntimes.Length > 0)
{
foreach (var tag in tagRuntimes)
{
if (tag == null || tag.TotalStack <= 0)
{
continue;
}
if (!TagConfigRegistry.TryGetDefinition(tag.TagType, out TagDefinitionData definition) ||
definition == null ||
definition.Category != TagCategory.Status ||
definition.TriggerPhase != TagTriggerPhase.OnAfterHit ||
definition.Config == null ||
!EnemyStatusTagRegistry.TryGetEffect(tag.TagType, out IEnemyStatusTagEffect effect))
{
continue;
}
effect.Apply(attackPayload, targetStatusRuntime, tag);
}
}
}
AttackShapeTagEffectHandler.Apply(new HitContext
{
AttackPayload = attackPayload
}, TagTriggerPhase.OnAfterHit);
}
public static void ApplyOnHit(HitContext hitContext)
{
AttackShapeTagEffectHandler.Apply(hitContext, TagTriggerPhase.OnHit);
}
public static void ApplyOnKill(HitContext hitContext)
{
AttackShapeTagEffectHandler.Apply(hitContext, TagTriggerPhase.OnKill);
}
public static int GetTagStack(TagRuntimeData[] tagRuntimes, TagType tagType)
{
if (tagRuntimes == null || tagRuntimes.Length <= 0 || tagType == TagType.None)
{
return 0;
}
foreach (var tag in tagRuntimes)
{
if (tag == null || tag.TagType != tagType || tag.TotalStack <= 0)
{
continue;
}
return tag.TotalStack;
}
return 0;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5e0b2b45c147579459081b36d26d471c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0f2ab8a7e488575498ac933a125360d1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,7 @@
namespace GeometryTD.Definition
{
public abstract class EnemyStatusTagStateBase
{
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 36215e56239c41c5bb196de6927643a8
timeCreated: 1773105536
@@ -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; }
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 20ad1b81a1634d06b9c8bbadec37bcd4
timeCreated: 1773105566
@@ -0,0 +1,8 @@
namespace GeometryTD.Definition
{
public sealed class IceTagState : EnemyStatusTagStateBase
{
public float RemainingDuration { get; set; }
public float SlowMultiplier { get; set; } = 1f;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 43c7eb87fa774e60a894f4cc8e0089a5
timeCreated: 1773105599