init
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class TagRuntimeData
|
||||
{
|
||||
public TagType TagType { get; set; }
|
||||
public int TotalStack { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class TowerTagAggregationService
|
||||
{
|
||||
public static TagRuntimeData[] AggregateTowerTags(params TagType[][] componentTags)
|
||||
{
|
||||
Dictionary<TagType, int> stackByTag = new Dictionary<TagType, int>();
|
||||
if (componentTags != null)
|
||||
{
|
||||
foreach (var tags in componentTags)
|
||||
{
|
||||
if (tags == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var tagType in tags)
|
||||
{
|
||||
if (tagType == TagType.None || !Enum.IsDefined(typeof(TagType), tagType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stackByTag.TryGetValue(tagType, out int stack))
|
||||
{
|
||||
stackByTag[tagType] = stack + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
stackByTag[tagType] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stackByTag.Count <= 0)
|
||||
{
|
||||
return Array.Empty<TagRuntimeData>();
|
||||
}
|
||||
|
||||
List<TagRuntimeData> runtimes = new List<TagRuntimeData>(stackByTag.Count);
|
||||
foreach (var pair in stackByTag)
|
||||
{
|
||||
runtimes.Add(new TagRuntimeData
|
||||
{
|
||||
TagType = pair.Key,
|
||||
TotalStack = Math.Max(1, pair.Value)
|
||||
});
|
||||
}
|
||||
|
||||
runtimes.Sort((left, right) => left.TagType.CompareTo(right.TagType));
|
||||
return runtimes.ToArray();
|
||||
}
|
||||
|
||||
public static TagRuntimeData[] BuildRuntimeTagsFromUniqueTags(IReadOnlyList<TagType> tags)
|
||||
{
|
||||
if (tags == null || tags.Count <= 0)
|
||||
{
|
||||
return Array.Empty<TagRuntimeData>();
|
||||
}
|
||||
|
||||
HashSet<TagType> uniqueTags = new HashSet<TagType>();
|
||||
foreach (var tagType in tags)
|
||||
{
|
||||
if (tagType == TagType.None || !Enum.IsDefined(typeof(TagType), tagType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
uniqueTags.Add(tagType);
|
||||
}
|
||||
|
||||
if (uniqueTags.Count <= 0)
|
||||
{
|
||||
return Array.Empty<TagRuntimeData>();
|
||||
}
|
||||
|
||||
TagType[] orderedTags = new TagType[uniqueTags.Count];
|
||||
uniqueTags.CopyTo(orderedTags);
|
||||
Array.Sort(orderedTags);
|
||||
|
||||
TagRuntimeData[] runtimes = new TagRuntimeData[orderedTags.Length];
|
||||
for (int i = 0; i < orderedTags.Length; i++)
|
||||
{
|
||||
runtimes[i] = new TagRuntimeData
|
||||
{
|
||||
TagType = orderedTags[i],
|
||||
TotalStack = 1
|
||||
};
|
||||
}
|
||||
|
||||
return runtimes;
|
||||
}
|
||||
|
||||
public static TagType[] FlattenUniqueTags(IReadOnlyList<TagRuntimeData> tagRuntimes)
|
||||
{
|
||||
if (tagRuntimes == null || tagRuntimes.Count <= 0)
|
||||
{
|
||||
return Array.Empty<TagType>();
|
||||
}
|
||||
|
||||
List<TagType> tags = new List<TagType>(tagRuntimes.Count);
|
||||
foreach (var runtime in tagRuntimes)
|
||||
{
|
||||
if (runtime == null || runtime.TagType == TagType.None || !Enum.IsDefined(typeof(TagType), runtime.TagType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (runtime.TotalStack <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
tags.Add(runtime.TagType);
|
||||
}
|
||||
|
||||
if (tags.Count <= 0)
|
||||
{
|
||||
return Array.Empty<TagType>();
|
||||
}
|
||||
|
||||
tags.Sort();
|
||||
return tags.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.Entity;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class EnemyStatusTagRegistry
|
||||
{
|
||||
// Only tags that create or tick enemy-held status state belong here.
|
||||
private static readonly Dictionary<TagType, IEnemyStatusTagEffect> EffectsByTag =
|
||||
new Dictionary<TagType, IEnemyStatusTagEffect>
|
||||
{
|
||||
[TagType.Fire] = new FireTagEffect(),
|
||||
[TagType.Ice] = new IceTagEffect()
|
||||
};
|
||||
|
||||
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,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 (!TagDefinitionRegistry.TryGetDefinition(runtime.TagType, out TagDefinition 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,109 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class NumericTagEffectHandler
|
||||
{
|
||||
public static void ApplyBeforeHit(HitContext hitContext)
|
||||
{
|
||||
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 (!TagDefinitionRegistry.TryGetDefinition(runtime.TagType, out TagDefinition 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);
|
||||
break;
|
||||
case TagType.Execution:
|
||||
ApplyExecution(hitContext, runtime.TotalStack, definition.Config as ExecutionTagConfig);
|
||||
break;
|
||||
case TagType.Shatter:
|
||||
ApplyShatter(hitContext, runtime.TotalStack, definition.Config as ShatterTagConfig);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
hitContext.IsKilled = hitContext.TargetCurrentHealthBeforeHit > 0 &&
|
||||
hitContext.FinalDamage >= hitContext.TargetCurrentHealthBeforeHit;
|
||||
}
|
||||
|
||||
private static void ApplyCrit(HitContext hitContext, int stack, CritTagConfig config)
|
||||
{
|
||||
if (config == null || !config.IsImplemented || stack <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float chance = Mathf.Clamp01(stack * config.CritChancePerStack);
|
||||
float resolvedCritRoll = hitContext.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,
|
||||
ExecutionTagConfig config)
|
||||
{
|
||||
if (config == null || !config.IsImplemented || stack <= 0 || hitContext.TargetMaxHealth <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float healthRatio = Mathf.Clamp01((float)Mathf.Max(0, hitContext.TargetCurrentHealthBeforeHit) / hitContext.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, ShatterTagConfig config)
|
||||
{
|
||||
if (config == null || !config.IsImplemented || stack <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.RequiresSlowedTarget && !hitContext.HasSlowStatusBeforeHit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float multiplier = 1f + stack * config.DamageBonusPerStack;
|
||||
hitContext.FinalDamage = Mathf.Max(0, Mathf.RoundToInt(hitContext.FinalDamage * multiplier));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public abstract class EnemyStatusTagStateBase
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,8 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public sealed class IceTagState : EnemyStatusTagStateBase
|
||||
{
|
||||
public float RemainingDuration { get; set; }
|
||||
public float SlowMultiplier { get; set; } = 1f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using GeometryTD.Entity;
|
||||
|
||||
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<int> applyDamage)
|
||||
{
|
||||
_ = runtime;
|
||||
_ = deltaTime;
|
||||
_ = applyDamage;
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual float GetMoveSpeedMultiplier(EnemyTagStatusRuntime runtime)
|
||||
{
|
||||
_ = runtime;
|
||||
return 1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
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(HitContext hitContext, TagRuntimeData runtimeData)
|
||||
{
|
||||
Debug.Assert(TagDefinitionRegistry.TryGetDefinition(TagType, out TagDefinition definition));
|
||||
Debug.Assert(hitContext != null);
|
||||
Debug.Assert(hitContext.TargetStatusRuntime != null);
|
||||
|
||||
FireTagConfig config = definition.Config as FireTagConfig;
|
||||
Debug.Assert(config != null);
|
||||
if (!config.IsImplemented)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnemyTagStatusRuntime runtime = hitContext.TargetStatusRuntime;
|
||||
FireTagState state = runtime.GetOrCreateState<FireTagState>(TagType);
|
||||
int effectiveStack = Mathf.Max(0, Mathf.Min(runtimeData.TotalStack, config.MaxEffectiveStack));
|
||||
HitStatusModifierContext modifierContext = hitContext.StatusModifierContext ?? new HitStatusModifierContext();
|
||||
float burnDamagePerSecond = Mathf.Max(
|
||||
0f,
|
||||
config.BurnDamagePerSecondPerStack * effectiveStack + modifierContext.BonusBurnDamagePerSecond);
|
||||
|
||||
if (burnDamagePerSecond <= 0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
state.RemainingDuration = Mathf.Max(
|
||||
state.RemainingDuration,
|
||||
config.BurnDurationSeconds + modifierContext.BonusBurnDurationSeconds);
|
||||
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,13 @@
|
||||
using System;
|
||||
using GeometryTD.Entity;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public interface IEnemyStatusTagEffect
|
||||
{
|
||||
TagType TagType { get; }
|
||||
void Apply(HitContext hitContext, TagRuntimeData runtimeData);
|
||||
bool Tick(EnemyTagStatusRuntime runtime, float deltaTime, Action<int> applyDamage);
|
||||
float GetMoveSpeedMultiplier(EnemyTagStatusRuntime runtime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
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(HitContext hitContext, TagRuntimeData runtimeData)
|
||||
{
|
||||
Debug.Assert(hitContext != null);
|
||||
Debug.Assert(hitContext.TargetStatusRuntime != null);
|
||||
Debug.Assert(runtimeData != null);
|
||||
Debug.Assert(runtimeData.TotalStack > 0);
|
||||
Debug.Assert(TagDefinitionRegistry.TryGetDefinition(TagType, out TagDefinition definition));
|
||||
|
||||
IceTagConfig config = definition.Config as IceTagConfig;
|
||||
Debug.Assert(config != null);
|
||||
if (!config.IsImplemented)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnemyTagStatusRuntime runtime = hitContext.TargetStatusRuntime;
|
||||
IceTagState state = runtime.GetOrCreateState<IceTagState>(TagType);
|
||||
HitStatusModifierContext modifierContext = hitContext.StatusModifierContext ?? new HitStatusModifierContext();
|
||||
float slowRatio = runtimeData.TotalStack * config.SlowRatioPerStack + modifierContext.BonusSlowRatio;
|
||||
float slowMultiplier = Mathf.Max(config.MinMoveSpeedMultiplier, 1f - slowRatio);
|
||||
state.RemainingDuration = Mathf.Max(
|
||||
state.RemainingDuration,
|
||||
config.SlowDurationSeconds + modifierContext.BonusSlowDurationSeconds);
|
||||
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,177 @@
|
||||
using GeometryTD.Entity;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class TagEffectResolver
|
||||
{
|
||||
private static readonly HitStatusModifierContext EmptyStatusModifierContext = new HitStatusModifierContext();
|
||||
|
||||
public static HitContext ResolveBeforeHit(HitContext hitContext)
|
||||
{
|
||||
if (hitContext == null)
|
||||
{
|
||||
return new HitContext
|
||||
{
|
||||
AttackPayload = new AttackPayload()
|
||||
};
|
||||
}
|
||||
|
||||
AttackPayload resolvedPayload = hitContext.AttackPayload?.Clone() ?? new AttackPayload();
|
||||
HitContext resolvedContext = new HitContext
|
||||
{
|
||||
AttackPayload = resolvedPayload,
|
||||
FinalDamage = Mathf.Max(0, resolvedPayload.BaseDamage),
|
||||
IsCriticalHit = false,
|
||||
IsKilled = hitContext.TargetCurrentHealthBeforeHit > 0 &&
|
||||
resolvedPayload.BaseDamage >= hitContext.TargetCurrentHealthBeforeHit,
|
||||
TargetEntityId = hitContext.TargetEntityId,
|
||||
TargetPosition = hitContext.TargetPosition,
|
||||
TargetCurrentHealthBeforeHit = hitContext.TargetCurrentHealthBeforeHit,
|
||||
TargetCurrentHealthAfterHit = hitContext.TargetCurrentHealthAfterHit,
|
||||
TargetMaxHealth = hitContext.TargetMaxHealth,
|
||||
TargetMoveSpeedMultiplierBeforeHit = hitContext.TargetMoveSpeedMultiplierBeforeHit,
|
||||
TargetStatusTagsBeforeHit = hitContext.TargetStatusTagsBeforeHit ?? System.Array.Empty<TagType>(),
|
||||
TargetStatusRuntime = hitContext.TargetStatusRuntime,
|
||||
CritRoll = hitContext.CritRoll,
|
||||
StatusModifierContext = new HitStatusModifierContext()
|
||||
};
|
||||
|
||||
NumericTagEffectHandler.ApplyBeforeHit(resolvedContext);
|
||||
return resolvedContext;
|
||||
}
|
||||
|
||||
public static void ApplyAfterHit(HitContext hitContext)
|
||||
{
|
||||
if (hitContext?.StatusModifierContext == null)
|
||||
{
|
||||
if (hitContext != null)
|
||||
{
|
||||
hitContext.StatusModifierContext = new HitStatusModifierContext();
|
||||
}
|
||||
}
|
||||
|
||||
hitContext?.StatusModifierContext?.Reset();
|
||||
ApplyStatusModifiers(hitContext);
|
||||
|
||||
if (hitContext?.AttackPayload != null && hitContext.TargetStatusRuntime != null)
|
||||
{
|
||||
TagRuntimeData[] tagRuntimes = hitContext.AttackPayload.TagRuntimes;
|
||||
if (tagRuntimes != null && tagRuntimes.Length > 0)
|
||||
{
|
||||
foreach (var tag in tagRuntimes)
|
||||
{
|
||||
if (tag == null || tag.TotalStack <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TagDefinitionRegistry.TryGetDefinition(tag.TagType, out TagDefinition definition) ||
|
||||
definition == null ||
|
||||
definition.Category != TagCategory.Status ||
|
||||
definition.TriggerPhase != TagTriggerPhase.OnAfterHit ||
|
||||
definition.Config == null ||
|
||||
!EnemyStatusTagRegistry.TryGetEffect(tag.TagType, out IEnemyStatusTagEffect effect))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
effect.Apply(hitContext, tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AttackShapeTagEffectHandler.Apply(hitContext, 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;
|
||||
}
|
||||
|
||||
private static void ApplyStatusModifiers(HitContext hitContext)
|
||||
{
|
||||
if (hitContext?.AttackPayload?.TagRuntimes == null || hitContext.AttackPayload.TagRuntimes.Length <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HitStatusModifierContext modifierContext = hitContext.StatusModifierContext ?? EmptyStatusModifierContext;
|
||||
for (int i = 0; i < hitContext.AttackPayload.TagRuntimes.Length; i++)
|
||||
{
|
||||
TagRuntimeData runtime = hitContext.AttackPayload.TagRuntimes[i];
|
||||
if (runtime == null || runtime.TotalStack <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TagDefinitionRegistry.TryGetDefinition(runtime.TagType, out TagDefinition definition) ||
|
||||
definition == null ||
|
||||
definition.Category != TagCategory.StatusModifier ||
|
||||
definition.TriggerPhase != TagTriggerPhase.OnAfterHit ||
|
||||
definition.Config == null ||
|
||||
!definition.Config.IsImplemented)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (runtime.TagType)
|
||||
{
|
||||
case TagType.Inferno:
|
||||
ApplyInfernoModifier(runtime.TotalStack, definition.Config as InfernoTagConfig, modifierContext);
|
||||
break;
|
||||
case TagType.AbsoluteZero:
|
||||
ApplyAbsoluteZeroModifier(runtime.TotalStack, definition.Config as AbsoluteZeroTagConfig, modifierContext);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyInfernoModifier(int stack, InfernoTagConfig config, HitStatusModifierContext modifierContext)
|
||||
{
|
||||
if (config == null || stack <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
modifierContext.BonusBurnDurationSeconds += stack * config.BonusBurnDurationSeconds;
|
||||
modifierContext.BonusBurnDamagePerSecond += stack * config.BonusBurnDamagePerSecondPerStack;
|
||||
}
|
||||
|
||||
private static void ApplyAbsoluteZeroModifier(int stack, AbsoluteZeroTagConfig config, HitStatusModifierContext modifierContext)
|
||||
{
|
||||
if (config == null || stack <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
modifierContext.BonusSlowDurationSeconds += stack * config.BonusSlowDurationSeconds;
|
||||
modifierContext.BonusSlowRatio += stack * config.BonusSlowRatioPerStack;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Random = System.Random;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class ComponentTagGenerationService
|
||||
{
|
||||
public static TagType[] ResolveComponentTags(
|
||||
IReadOnlyList<TagType> possibleTags,
|
||||
RarityType rarity,
|
||||
InventoryTagRandomContext randomContext,
|
||||
IReadOnlyDictionary<TagType, TagGenerationRule> rulesByTag = null,
|
||||
IReadOnlyDictionary<RarityType, RarityTagBudgetRule> rarityTagBudgetRulesByRarity = null)
|
||||
{
|
||||
IReadOnlyDictionary<TagType, TagGenerationRule> ruleLookup = rulesByTag ?? TagGenerationRuleRegistry.Rules;
|
||||
TagType[] eligibleTags = GetEligibleTags(possibleTags, rarity, ruleLookup);
|
||||
if (eligibleTags.Length <= 0)
|
||||
{
|
||||
return Array.Empty<TagType>();
|
||||
}
|
||||
|
||||
Random random = new Random(BuildStableSeed(rarity, randomContext));
|
||||
int tagBudget = ResolveRarityTagBudget(rarity, random, rarityTagBudgetRulesByRarity);
|
||||
if (tagBudget <= 0)
|
||||
{
|
||||
return Array.Empty<TagType>();
|
||||
}
|
||||
|
||||
int finalCount = Math.Min(tagBudget, eligibleTags.Length);
|
||||
List<TagType> pool = new List<TagType>(eligibleTags);
|
||||
TagType[] result = new TagType[finalCount];
|
||||
for (int i = 0; i < finalCount; i++)
|
||||
{
|
||||
int index = RollWeightedIndex(pool, ruleLookup, random);
|
||||
result[i] = pool[index];
|
||||
pool.RemoveAt(index);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static TagType[] ResolveComponentTags(
|
||||
IReadOnlyList<TagType> possibleTags,
|
||||
RarityType rarity,
|
||||
InventoryTagSourceType sourceType,
|
||||
long itemInstanceId,
|
||||
int configId,
|
||||
IReadOnlyDictionary<TagType, TagGenerationRule> rulesByTag = null,
|
||||
IReadOnlyDictionary<RarityType, RarityTagBudgetRule> rarityTagBudgetRulesByRarity = null)
|
||||
{
|
||||
return ResolveComponentTags(
|
||||
possibleTags,
|
||||
rarity,
|
||||
new InventoryTagRandomContext(0, sourceType, itemInstanceId, configId),
|
||||
rulesByTag,
|
||||
rarityTagBudgetRulesByRarity);
|
||||
}
|
||||
|
||||
public static TagType[] GetEligibleTags(
|
||||
IReadOnlyList<TagType> possibleTags,
|
||||
RarityType rarity,
|
||||
IReadOnlyDictionary<TagType, TagGenerationRule> rulesByTag = null)
|
||||
{
|
||||
if (possibleTags == null || possibleTags.Count <= 0)
|
||||
{
|
||||
return Array.Empty<TagType>();
|
||||
}
|
||||
|
||||
RarityType normalizedRarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity);
|
||||
IReadOnlyDictionary<TagType, TagGenerationRule> ruleLookup = rulesByTag ?? TagGenerationRuleRegistry.Rules;
|
||||
HashSet<TagType> uniqueTags = new HashSet<TagType>();
|
||||
|
||||
for (int i = 0; i < possibleTags.Count; i++)
|
||||
{
|
||||
TagType tagType = possibleTags[i];
|
||||
if (tagType == TagType.None || !Enum.IsDefined(typeof(TagType), tagType) || !IsSupportedLaunchTag(tagType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryGetRule(tagType, ruleLookup, out TagGenerationRule rule))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (normalizedRarity < InventoryRarityRuleService.NormalizeComponentRarity(rule.MinRarity))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
uniqueTags.Add(tagType);
|
||||
}
|
||||
|
||||
if (uniqueTags.Count <= 0)
|
||||
{
|
||||
return Array.Empty<TagType>();
|
||||
}
|
||||
|
||||
TagType[] result = new TagType[uniqueTags.Count];
|
||||
uniqueTags.CopyTo(result);
|
||||
Array.Sort(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int ResolveRarityTagBudget(
|
||||
RarityType rarity,
|
||||
Random random,
|
||||
IReadOnlyDictionary<RarityType, RarityTagBudgetRule> rarityTagBudgetRulesByRarity = null)
|
||||
{
|
||||
RarityType normalizedRarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity);
|
||||
IReadOnlyDictionary<RarityType, RarityTagBudgetRule> ruleLookup = rarityTagBudgetRulesByRarity ?? RarityTagBudgetRuleRegistry.Rules;
|
||||
RarityTagBudgetRule rule = GetRarityTagBudgetRule(normalizedRarity, ruleLookup);
|
||||
|
||||
Debug.Assert(rule.MinCount >= 0);
|
||||
Debug.Assert(rule.MaxCount >= rule.MinCount);
|
||||
|
||||
if (rule.MinCount == rule.MaxCount)
|
||||
{
|
||||
return rule.MinCount;
|
||||
}
|
||||
|
||||
random ??= new Random(0);
|
||||
return random.Next(rule.MinCount, rule.MaxCount + 1);
|
||||
}
|
||||
|
||||
private static bool IsSupportedLaunchTag(TagType tagType)
|
||||
{
|
||||
return TagDefinitionRegistry.TryGetDefinition(tagType, out TagDefinition definition) &&
|
||||
definition.Config != null &&
|
||||
definition.Config.IsImplemented;
|
||||
}
|
||||
|
||||
private static bool TryGetRule(
|
||||
TagType tagType,
|
||||
IReadOnlyDictionary<TagType, TagGenerationRule> ruleLookup,
|
||||
out TagGenerationRule rule)
|
||||
{
|
||||
if (ruleLookup != null && ruleLookup.TryGetValue(tagType, out rule))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
rule = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static RarityTagBudgetRule GetRarityTagBudgetRule(
|
||||
RarityType rarity,
|
||||
IReadOnlyDictionary<RarityType, RarityTagBudgetRule> ruleLookup)
|
||||
{
|
||||
Debug.Assert(ruleLookup != null);
|
||||
Debug.Assert(ruleLookup.TryGetValue(rarity, out _));
|
||||
return ruleLookup[rarity];
|
||||
}
|
||||
|
||||
private static int RollWeightedIndex(
|
||||
IReadOnlyList<TagType> pool,
|
||||
IReadOnlyDictionary<TagType, TagGenerationRule> ruleLookup,
|
||||
Random random)
|
||||
{
|
||||
int totalWeight = 0;
|
||||
for (int i = 0; i < pool.Count; i++)
|
||||
{
|
||||
TagGenerationRule rule = ruleLookup[pool[i]];
|
||||
Debug.Assert(rule.Weight > 0);
|
||||
totalWeight += rule.Weight;
|
||||
}
|
||||
|
||||
Debug.Assert(totalWeight > 0);
|
||||
|
||||
int roll = random.Next(1, totalWeight + 1);
|
||||
int cumulative = 0;
|
||||
for (int i = 0; i < pool.Count; i++)
|
||||
{
|
||||
TagGenerationRule rule = ruleLookup[pool[i]];
|
||||
cumulative += rule.Weight;
|
||||
if (roll <= cumulative)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return pool.Count - 1;
|
||||
}
|
||||
|
||||
private static int BuildStableSeed(RarityType rarity, InventoryTagRandomContext randomContext)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int seed = 17;
|
||||
seed = seed * 31 + randomContext.RunSeed;
|
||||
seed = seed * 31 + (int)InventoryRarityRuleService.NormalizeComponentRarity(rarity);
|
||||
seed = seed * 31 + (int)randomContext.SourceType;
|
||||
seed = seed * 31 + randomContext.ConfigId;
|
||||
seed = seed * 31 + (int)randomContext.ItemInstanceId;
|
||||
seed = seed * 31 + (int)(randomContext.ItemInstanceId >> 32);
|
||||
return seed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public readonly struct InventoryTagRandomContext
|
||||
{
|
||||
public InventoryTagRandomContext(int runSeed, InventoryTagSourceType sourceType, long itemInstanceId, int configId)
|
||||
{
|
||||
RunSeed = runSeed;
|
||||
SourceType = sourceType;
|
||||
ItemInstanceId = itemInstanceId;
|
||||
ConfigId = configId;
|
||||
}
|
||||
|
||||
public int RunSeed { get; }
|
||||
|
||||
public InventoryTagSourceType SourceType { get; }
|
||||
|
||||
public long ItemInstanceId { get; }
|
||||
|
||||
public int ConfigId { get; }
|
||||
|
||||
public static InventoryTagRandomContext CreateSeed(int runSeed, long itemInstanceId, int configId)
|
||||
{
|
||||
return new InventoryTagRandomContext(runSeed, InventoryTagSourceType.Seed, itemInstanceId, configId);
|
||||
}
|
||||
|
||||
public static InventoryTagRandomContext CreateShop(int runSeed, int nodeSequenceIndex, int goodsIndex, int configId)
|
||||
{
|
||||
return new InventoryTagRandomContext(
|
||||
runSeed,
|
||||
InventoryTagSourceType.Shop,
|
||||
ComposeLocalItemInstanceId(nodeSequenceIndex, goodsIndex),
|
||||
configId);
|
||||
}
|
||||
|
||||
public static InventoryTagRandomContext CreateDrop(int runSeed, int nodeSequenceIndex, int dropOrdinal, int configId)
|
||||
{
|
||||
return new InventoryTagRandomContext(
|
||||
runSeed,
|
||||
InventoryTagSourceType.Drop,
|
||||
ComposeLocalItemInstanceId(nodeSequenceIndex, dropOrdinal),
|
||||
configId);
|
||||
}
|
||||
|
||||
public static InventoryTagRandomContext CreateReward(int runSeed, int nodeSequenceIndex, int rewardOrdinal, int configId)
|
||||
{
|
||||
return new InventoryTagRandomContext(
|
||||
runSeed,
|
||||
InventoryTagSourceType.Reward,
|
||||
ComposeLocalItemInstanceId(nodeSequenceIndex, rewardOrdinal),
|
||||
configId);
|
||||
}
|
||||
|
||||
public static InventoryTagRandomContext CreateEvent(int runSeed, int nodeSequenceIndex, int eventOrdinal, int configId)
|
||||
{
|
||||
return new InventoryTagRandomContext(
|
||||
runSeed,
|
||||
InventoryTagSourceType.Event,
|
||||
ComposeLocalItemInstanceId(nodeSequenceIndex, eventOrdinal),
|
||||
configId);
|
||||
}
|
||||
|
||||
public static long ComposeLocalItemInstanceId(int nodeSequenceIndex, int localOrdinal)
|
||||
{
|
||||
long normalizedSequence = Math.Max(0, nodeSequenceIndex) + 1L;
|
||||
long normalizedOrdinal = Math.Max(0, localOrdinal) + 1L;
|
||||
return (normalizedSequence << 32) | (uint)normalizedOrdinal;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public sealed class RarityTagBudgetRule
|
||||
{
|
||||
public RarityType Rarity { get; set; }
|
||||
public int MinCount { get; set; }
|
||||
public int MaxCount { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.DataTable;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class RarityTagBudgetRuleRegistry
|
||||
{
|
||||
private static readonly Dictionary<RarityType, RarityTagBudgetRule> RulesByRarity = CreateDefaultRules();
|
||||
|
||||
public static IReadOnlyDictionary<RarityType, RarityTagBudgetRule> Rules => RulesByRarity;
|
||||
|
||||
public static void ResetToDefaults()
|
||||
{
|
||||
RulesByRarity.Clear();
|
||||
foreach (KeyValuePair<RarityType, RarityTagBudgetRule> pair in CreateDefaultRules())
|
||||
{
|
||||
RulesByRarity.Add(pair.Key, pair.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadFromRows(IEnumerable<DRRarityTagBudget> rows)
|
||||
{
|
||||
ResetToDefaults();
|
||||
foreach (DRRarityTagBudget row in rows)
|
||||
{
|
||||
ApplyRow(row);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryGetRule(RarityType rarity, out RarityTagBudgetRule rule)
|
||||
{
|
||||
return RulesByRarity.TryGetValue(rarity, out rule);
|
||||
}
|
||||
|
||||
private static Dictionary<RarityType, RarityTagBudgetRule> CreateDefaultRules()
|
||||
{
|
||||
return new Dictionary<RarityType, RarityTagBudgetRule>
|
||||
{
|
||||
[RarityType.White] = CreateRule(RarityType.White, 0, 1),
|
||||
[RarityType.Green] = CreateRule(RarityType.Green, 0, 2),
|
||||
[RarityType.Blue] = CreateRule(RarityType.Blue, 1, 3),
|
||||
[RarityType.Purple] = CreateRule(RarityType.Purple, 1, 3),
|
||||
[RarityType.Red] = CreateRule(RarityType.Red, 2, 4)
|
||||
};
|
||||
}
|
||||
|
||||
private static RarityTagBudgetRule CreateRule(RarityType rarity, int minCount, int maxCount)
|
||||
{
|
||||
Debug.Assert(rarity >= RarityType.White && rarity <= RarityType.Red);
|
||||
Debug.Assert(minCount >= 0);
|
||||
Debug.Assert(maxCount >= minCount);
|
||||
|
||||
return new RarityTagBudgetRule
|
||||
{
|
||||
Rarity = rarity,
|
||||
MinCount = minCount,
|
||||
MaxCount = maxCount
|
||||
};
|
||||
}
|
||||
|
||||
private static void ApplyRow(DRRarityTagBudget row)
|
||||
{
|
||||
Debug.Assert(row != null);
|
||||
Debug.Assert(row.Id > 0);
|
||||
Debug.Assert(row.Rarity >= RarityType.White && row.Rarity <= RarityType.Red);
|
||||
Debug.Assert(row.MinCount >= 0);
|
||||
Debug.Assert(row.MaxCount >= row.MinCount);
|
||||
|
||||
RulesByRarity[row.Rarity] = CreateRule(row.Rarity, row.MinCount, row.MaxCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public sealed class TagGenerationRule
|
||||
{
|
||||
public TagType TagType { get; set; }
|
||||
public RarityType MinRarity { get; set; }
|
||||
public int Weight { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.DataTable;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class TagGenerationRuleRegistry
|
||||
{
|
||||
private static readonly Dictionary<TagType, TagGenerationRule> RulesByTag = CreateDefaultRules();
|
||||
|
||||
public static IReadOnlyDictionary<TagType, TagGenerationRule> Rules => RulesByTag;
|
||||
|
||||
public static void ResetToDefaults()
|
||||
{
|
||||
RulesByTag.Clear();
|
||||
foreach (KeyValuePair<TagType, TagGenerationRule> pair in CreateDefaultRules())
|
||||
{
|
||||
RulesByTag.Add(pair.Key, pair.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadFromRows(IEnumerable<DRTag> rows)
|
||||
{
|
||||
ResetToDefaults();
|
||||
foreach (DRTag row in rows)
|
||||
{
|
||||
ApplyRow(row);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryGetRule(TagType tagType, out TagGenerationRule rule)
|
||||
{
|
||||
return RulesByTag.TryGetValue(tagType, out rule);
|
||||
}
|
||||
|
||||
private static Dictionary<TagType, TagGenerationRule> CreateDefaultRules()
|
||||
{
|
||||
return new Dictionary<TagType, TagGenerationRule>
|
||||
{
|
||||
[TagType.Fire] = CreateRule(TagType.Fire, RarityType.White, 20),
|
||||
[TagType.BurnSpread] = CreateRule(TagType.BurnSpread, RarityType.White, 20),
|
||||
[TagType.IgniteBurst] = CreateRule(TagType.IgniteBurst, RarityType.Green, 15),
|
||||
[TagType.Inferno] = CreateRule(TagType.Inferno, RarityType.Purple, 5),
|
||||
[TagType.Ice] = CreateRule(TagType.Ice, RarityType.White, 1),
|
||||
[TagType.FreezeMask] = CreateRule(TagType.FreezeMask, RarityType.White, 20),
|
||||
[TagType.Shatter] = CreateRule(TagType.Shatter, RarityType.Green, 15),
|
||||
[TagType.AbsoluteZero] = CreateRule(TagType.AbsoluteZero, RarityType.Purple, 1),
|
||||
[TagType.Pierce] = CreateRule(TagType.Pierce, RarityType.White, 20),
|
||||
[TagType.Crit] = CreateRule(TagType.Crit, RarityType.White, 20),
|
||||
[TagType.Overpenetrate] = CreateRule(TagType.Overpenetrate, RarityType.Green, 15),
|
||||
[TagType.Execution] = CreateRule(TagType.Execution, RarityType.Purple, 5)
|
||||
};
|
||||
}
|
||||
|
||||
private static TagGenerationRule CreateRule(TagType tagType, RarityType minRarity, int weight)
|
||||
{
|
||||
return new TagGenerationRule
|
||||
{
|
||||
TagType = tagType,
|
||||
MinRarity = minRarity,
|
||||
Weight = weight
|
||||
};
|
||||
}
|
||||
|
||||
private static void ApplyRow(DRTag row)
|
||||
{
|
||||
Debug.Assert(row != null);
|
||||
Debug.Assert(row.Id > 0);
|
||||
Debug.Assert(row.Weight > 0);
|
||||
|
||||
RulesByTag[row.TagType] = CreateRule(row.TagType, row.MinRarity, row.Weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class BurnSpreadTagConfig : TagConfigBase
|
||||
{
|
||||
public BurnSpreadTagConfig(bool isImplemented) : base(isImplemented)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,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,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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class FreezeMaskTagConfig : TagConfigBase
|
||||
{
|
||||
public FreezeMaskTagConfig(bool isImplemented) : base(isImplemented)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,12 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class IgniteBurstTagConfig : TagConfigBase
|
||||
{
|
||||
public IgniteBurstTagConfig(bool isImplemented) : base(isImplemented)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class OverpenetrateTagConfig : TagConfigBase
|
||||
{
|
||||
public OverpenetrateTagConfig(bool isImplemented) : base(isImplemented)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class PierceTagConfig : TagConfigBase
|
||||
{
|
||||
public PierceTagConfig(bool isImplemented) : base(isImplemented)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.DataTable;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class TagDefinitionRegistry
|
||||
{
|
||||
private static readonly Dictionary<TagType, TagDefinition> DefinitionsByTag = CreateDefaultDefinitions();
|
||||
|
||||
public static IReadOnlyDictionary<TagType, TagDefinition> Definitions => DefinitionsByTag;
|
||||
|
||||
public static void ResetToDefaults()
|
||||
{
|
||||
DefinitionsByTag.Clear();
|
||||
foreach (KeyValuePair<TagType, TagDefinition> pair in CreateDefaultDefinitions())
|
||||
{
|
||||
DefinitionsByTag.Add(pair.Key, pair.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadFromRows(IEnumerable<DRTagConfig> rows)
|
||||
{
|
||||
ReloadFromRows(rows, null);
|
||||
}
|
||||
|
||||
public static bool TryGetDefinition(TagType tagType, out TagDefinition definition)
|
||||
{
|
||||
return DefinitionsByTag.TryGetValue(tagType, out definition);
|
||||
}
|
||||
|
||||
public static void ApplyTagRows(IEnumerable<DRTag> rows)
|
||||
{
|
||||
if (rows == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (DRTag row in rows)
|
||||
{
|
||||
ApplyTagRow(row);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReloadFromRows(IEnumerable<DRTagConfig> tagConfigRows, IEnumerable<DRTag> tagRows)
|
||||
{
|
||||
ResetToDefaults();
|
||||
|
||||
if (tagConfigRows != null)
|
||||
{
|
||||
foreach (DRTagConfig row in tagConfigRows)
|
||||
{
|
||||
ApplyRow(row);
|
||||
}
|
||||
}
|
||||
|
||||
if (tagRows != null)
|
||||
{
|
||||
foreach (DRTag row in tagRows)
|
||||
{
|
||||
ApplyTagRow(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<TagType, TagDefinition> CreateDefaultDefinitions()
|
||||
{
|
||||
return new Dictionary<TagType, TagDefinition>
|
||||
{
|
||||
[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
|
||||
};
|
||||
}
|
||||
|
||||
private static void ApplyRow(DRTagConfig row)
|
||||
{
|
||||
Debug.Assert(row != null);
|
||||
Debug.Assert(DefinitionsByTag.TryGetValue(row.TagType, out TagDefinition definition));
|
||||
|
||||
definition.TriggerPhase = row.TriggerPhase;
|
||||
definition.Description = row.Description ?? string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(row.ParamJson))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
JObject param = JObject.Parse(row.ParamJson);
|
||||
switch (row.TagType)
|
||||
{
|
||||
case TagType.Fire:
|
||||
ApplyFireConfig((FireTagConfig)definition.Config, param);
|
||||
break;
|
||||
case TagType.Inferno:
|
||||
ApplyInfernoConfig((InfernoTagConfig)definition.Config, param);
|
||||
break;
|
||||
case TagType.Ice:
|
||||
ApplyIceConfig((IceTagConfig)definition.Config, param);
|
||||
break;
|
||||
case TagType.Shatter:
|
||||
ApplyShatterConfig((ShatterTagConfig)definition.Config, param);
|
||||
break;
|
||||
case TagType.AbsoluteZero:
|
||||
ApplyAbsoluteZeroConfig((AbsoluteZeroTagConfig)definition.Config, param);
|
||||
break;
|
||||
case TagType.Crit:
|
||||
ApplyCritConfig((CritTagConfig)definition.Config, param);
|
||||
break;
|
||||
case TagType.Execution:
|
||||
ApplyExecutionConfig((ExecutionTagConfig)definition.Config, param);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyTagRow(DRTag row)
|
||||
{
|
||||
Debug.Assert(row != null);
|
||||
Debug.Assert(row.Id > 0);
|
||||
Debug.Assert(DefinitionsByTag.TryGetValue(row.TagType, out TagDefinition definition));
|
||||
Debug.Assert(definition.Config != null);
|
||||
|
||||
definition.Config.IsImplemented = row.IsImplemented;
|
||||
}
|
||||
|
||||
private static void ApplyFireConfig(FireTagConfig config, JObject param)
|
||||
{
|
||||
config.BurnDurationSeconds = ReadFloat(param, nameof(FireTagConfig.BurnDurationSeconds), config.BurnDurationSeconds);
|
||||
config.BurnDamagePerSecondPerStack = ReadFloat(param, nameof(FireTagConfig.BurnDamagePerSecondPerStack), config.BurnDamagePerSecondPerStack);
|
||||
config.MaxEffectiveStack = ReadInt(param, nameof(FireTagConfig.MaxEffectiveStack), config.MaxEffectiveStack);
|
||||
}
|
||||
|
||||
private static void ApplyInfernoConfig(InfernoTagConfig config, JObject param)
|
||||
{
|
||||
config.BonusBurnDurationSeconds = ReadFloat(param, nameof(InfernoTagConfig.BonusBurnDurationSeconds), config.BonusBurnDurationSeconds);
|
||||
config.BonusBurnDamagePerSecondPerStack = ReadFloat(param, nameof(InfernoTagConfig.BonusBurnDamagePerSecondPerStack), config.BonusBurnDamagePerSecondPerStack);
|
||||
}
|
||||
|
||||
private static void ApplyIceConfig(IceTagConfig config, JObject param)
|
||||
{
|
||||
config.SlowDurationSeconds = ReadFloat(param, nameof(IceTagConfig.SlowDurationSeconds), config.SlowDurationSeconds);
|
||||
config.SlowRatioPerStack = ReadFloat(param, nameof(IceTagConfig.SlowRatioPerStack), config.SlowRatioPerStack);
|
||||
config.MinMoveSpeedMultiplier = ReadFloat(param, nameof(IceTagConfig.MinMoveSpeedMultiplier), config.MinMoveSpeedMultiplier);
|
||||
}
|
||||
|
||||
private static void ApplyShatterConfig(ShatterTagConfig config, JObject param)
|
||||
{
|
||||
config.RequiresSlowedTarget = ReadBool(param, nameof(ShatterTagConfig.RequiresSlowedTarget), config.RequiresSlowedTarget);
|
||||
config.DamageBonusPerStack = ReadFloat(param, nameof(ShatterTagConfig.DamageBonusPerStack), config.DamageBonusPerStack);
|
||||
}
|
||||
|
||||
private static void ApplyAbsoluteZeroConfig(AbsoluteZeroTagConfig config, JObject param)
|
||||
{
|
||||
config.BonusSlowDurationSeconds = ReadFloat(param, nameof(AbsoluteZeroTagConfig.BonusSlowDurationSeconds), config.BonusSlowDurationSeconds);
|
||||
config.BonusSlowRatioPerStack = ReadFloat(param, nameof(AbsoluteZeroTagConfig.BonusSlowRatioPerStack), config.BonusSlowRatioPerStack);
|
||||
}
|
||||
|
||||
private static void ApplyCritConfig(CritTagConfig config, JObject param)
|
||||
{
|
||||
config.CritChancePerStack = ReadFloat(param, nameof(CritTagConfig.CritChancePerStack), config.CritChancePerStack);
|
||||
config.CritDamageMultiplier = ReadFloat(param, nameof(CritTagConfig.CritDamageMultiplier), config.CritDamageMultiplier);
|
||||
}
|
||||
|
||||
private static void ApplyExecutionConfig(ExecutionTagConfig config, JObject param)
|
||||
{
|
||||
config.TargetHealthThreshold = ReadFloat(param, nameof(ExecutionTagConfig.TargetHealthThreshold), config.TargetHealthThreshold);
|
||||
config.DamageBonusPerStack = ReadFloat(param, nameof(ExecutionTagConfig.DamageBonusPerStack), config.DamageBonusPerStack);
|
||||
}
|
||||
|
||||
private static float ReadFloat(JObject param, string key, float defaultValue)
|
||||
{
|
||||
JToken token = param[key];
|
||||
return token == null ? defaultValue : token.Value<float>();
|
||||
}
|
||||
|
||||
private static int ReadInt(JObject param, string key, int defaultValue)
|
||||
{
|
||||
JToken token = param[key];
|
||||
return token == null ? defaultValue : token.Value<int>();
|
||||
}
|
||||
|
||||
private static bool ReadBool(JObject param, string key, bool defaultValue)
|
||||
{
|
||||
JToken token = param[key];
|
||||
return token == null ? defaultValue : token.Value<bool>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.DataTable;
|
||||
using GeometryTD.Definition;
|
||||
|
||||
namespace GeometryTD.CustomUtility
|
||||
{
|
||||
public static class TagDisplayUtility
|
||||
{
|
||||
public static string ResolveTagName(TagType tagType)
|
||||
{
|
||||
if (tagType == TagType.None)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (GameEntry.DataTable != null)
|
||||
{
|
||||
var tagTable = GameEntry.DataTable.GetDataTable<DRTag>();
|
||||
if (tagTable != null)
|
||||
{
|
||||
DRTag tagRow = tagTable.GetDataRow((int)tagType);
|
||||
if (tagRow != null && !string.IsNullOrWhiteSpace(tagRow.Name))
|
||||
{
|
||||
return tagRow.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tagType.ToString();
|
||||
}
|
||||
|
||||
public static string[] BuildTagTexts(IReadOnlyList<TagType> tags)
|
||||
{
|
||||
if (tags == null || tags.Count <= 0)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
List<string> results = new List<string>(tags.Count);
|
||||
for (int i = 0; i < tags.Count; i++)
|
||||
{
|
||||
string tagName = ResolveTagName(tags[i]);
|
||||
if (!string.IsNullOrWhiteSpace(tagName))
|
||||
{
|
||||
results.Add(tagName);
|
||||
}
|
||||
}
|
||||
|
||||
return results.ToArray();
|
||||
}
|
||||
|
||||
public static string[] BuildTagTexts(IReadOnlyList<TagRuntimeData> tagRuntimes)
|
||||
{
|
||||
if (tagRuntimes == null || tagRuntimes.Count <= 0)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
List<string> results = new List<string>(tagRuntimes.Count);
|
||||
for (int i = 0; i < tagRuntimes.Count; i++)
|
||||
{
|
||||
TagRuntimeData tagRuntime = tagRuntimes[i];
|
||||
if (tagRuntime == null || tagRuntime.TagType == TagType.None || tagRuntime.TotalStack <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string tagName = ResolveTagName(tagRuntime.TagType);
|
||||
if (string.IsNullOrWhiteSpace(tagName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
results.Add(tagRuntime.TotalStack > 1 ? $"{tagName} x{tagRuntime.TotalStack}" : tagName);
|
||||
}
|
||||
|
||||
return results.ToArray();
|
||||
}
|
||||
|
||||
public static string[] BuildTowerTagTexts(TowerStatsData towerStats)
|
||||
{
|
||||
if (towerStats == null)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
if (towerStats.TagRuntimes != null && towerStats.TagRuntimes.Length > 0)
|
||||
{
|
||||
return BuildTagTexts(towerStats.TagRuntimes);
|
||||
}
|
||||
|
||||
return BuildTagTexts(towerStats.Tags);
|
||||
}
|
||||
|
||||
public static string BuildTagDescriptionText(IReadOnlyList<TagType> tags)
|
||||
{
|
||||
if (tags == null || tags.Count <= 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
List<string> results = new List<string>(tags.Count);
|
||||
for (int i = 0; i < tags.Count; i++)
|
||||
{
|
||||
TagType tagType = tags[i];
|
||||
if (tagType == TagType.None)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string tagName = ResolveTagName(tagType);
|
||||
string tagDescription = ResolveTagDescription(tagType);
|
||||
if (string.IsNullOrWhiteSpace(tagDescription))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
results.Add(string.IsNullOrWhiteSpace(tagName) ? tagDescription : $"{tagName}: {tagDescription}");
|
||||
}
|
||||
|
||||
return string.Join("\n", results);
|
||||
}
|
||||
|
||||
public static string BuildTagDescriptionText(IReadOnlyList<TagRuntimeData> tagRuntimes)
|
||||
{
|
||||
if (tagRuntimes == null || tagRuntimes.Count <= 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
List<string> results = new List<string>(tagRuntimes.Count);
|
||||
for (int i = 0; i < tagRuntimes.Count; i++)
|
||||
{
|
||||
TagRuntimeData runtime = tagRuntimes[i];
|
||||
if (runtime == null || runtime.TagType == TagType.None || runtime.TotalStack <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string tagName = ResolveTagName(runtime.TagType);
|
||||
if (runtime.TotalStack > 1 && !string.IsNullOrWhiteSpace(tagName))
|
||||
{
|
||||
tagName = $"{tagName} x{runtime.TotalStack}";
|
||||
}
|
||||
|
||||
string tagDescription = ResolveTagDescription(runtime.TagType);
|
||||
if (string.IsNullOrWhiteSpace(tagDescription))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
results.Add(string.IsNullOrWhiteSpace(tagName) ? tagDescription : $"{tagName}: {tagDescription}");
|
||||
}
|
||||
|
||||
return string.Join("\n", results);
|
||||
}
|
||||
|
||||
public static string ResolveTagDescription(TagType tagType)
|
||||
{
|
||||
if (tagType == TagType.None)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (TagDefinitionRegistry.TryGetDefinition(tagType, out TagDefinition definition) &&
|
||||
!string.IsNullOrWhiteSpace(definition.Description))
|
||||
{
|
||||
return definition.Description;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user