S4-07 process 1

This commit is contained in:
SepComet
2026-03-11 10:18:14 +08:00
parent 52f9e212b9
commit 9de2e50262
32 changed files with 803 additions and 194 deletions
@@ -4,24 +4,6 @@ using UnityEngine;
namespace GeometryTD.Definition
{
/// <summary>
/// 防御塔独立属性快照。
/// 注意:这里是塔实例的独立值,不通过组件引用实时计算。
/// </summary>
[Serializable]
public sealed class TowerStatsData
{
public int[] AttackDamage { get; set; }
public float DamageRandomRate { get; set; }
public float[] RotateSpeed { get; set; }
public float[] AttackRange { get; set; }
public float[] AttackSpeed { get; set; }
public AttackMethodType AttackMethodType { get; set; }
public AttackPropertyType AttackPropertyType { get; set; }
public TagRuntimeData[] TagRuntimes { get; set; }
public TagType[] Tags { get; set; }
}
/// <summary>
/// 背包内防御塔实例数据。
/// </summary>
@@ -69,4 +51,4 @@ namespace GeometryTD.Definition
[JsonIgnore] public string ComposedIconKey { get; set; }
}
}
}
@@ -0,0 +1,22 @@
using System;
namespace GeometryTD.Definition
{
/// <summary>
/// 防御塔独立属性快照。
/// 注意:这里是塔实例的独立值,不通过组件引用实时计算。
/// </summary>
[Serializable]
public sealed class TowerStatsData
{
public int[] AttackDamage { get; set; }
public float DamageRandomRate { get; set; }
public float[] RotateSpeed { get; set; }
public float[] AttackRange { get; set; }
public float[] AttackSpeed { get; set; }
public AttackMethodType AttackMethodType { get; set; }
public AttackPropertyType AttackPropertyType { get; set; }
public TagRuntimeData[] TagRuntimes { get; set; }
public TagType[] Tags { get; set; }
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 33934c152fda4ff4827aabfbf6f65856
timeCreated: 1773194446
@@ -21,15 +21,19 @@ namespace GeometryTD.Definition
}
FireTagState state = runtime.GetOrCreateState<FireTagState>(TagType);
float burnDamagePerSecond = Mathf.Max(0f,
config.BurnDamagePerSecondPerStack * runtimeData.TotalStack);
int infernoStack = TagEffectResolver.GetTagStack(attackPayload?.TagRuntimes, TagType.Inferno);
float burnDamagePerSecond = Mathf.Max(
0f,
config.BurnDamagePerSecondPerStack * runtimeData.TotalStack + GetInfernoBonusDamagePerSecond(infernoStack));
if (burnDamagePerSecond <= 0f)
{
return;
}
state.RemainingDuration = Mathf.Max(state.RemainingDuration, config.BurnDurationSeconds);
state.RemainingDuration = Mathf.Max(
state.RemainingDuration,
config.BurnDurationSeconds + GetInfernoBonusDuration(infernoStack));
state.DamagePerSecond = Mathf.Max(state.DamagePerSecond, burnDamagePerSecond);
runtime.Activate(TagType);
}
@@ -64,5 +68,39 @@ namespace GeometryTD.Definition
state.PendingDamage = 0f;
return false;
}
private static float GetInfernoBonusDuration(int infernoStack)
{
if (infernoStack <= 0 ||
!TagConfigRegistry.TryGetDefinition(TagType.Inferno, out TagDefinitionData definition))
{
return 0f;
}
InfernoTagConfig infernoConfig = definition.Config as InfernoTagConfig;
if (infernoConfig == null || !infernoConfig.IsImplemented)
{
return 0f;
}
return infernoStack * infernoConfig.BonusBurnDurationSeconds;
}
private static float GetInfernoBonusDamagePerSecond(int infernoStack)
{
if (infernoStack <= 0 ||
!TagConfigRegistry.TryGetDefinition(TagType.Inferno, out TagDefinitionData definition))
{
return 0f;
}
InfernoTagConfig infernoConfig = definition.Config as InfernoTagConfig;
if (infernoConfig == null || !infernoConfig.IsImplemented)
{
return 0f;
}
return infernoStack * infernoConfig.BonusBurnDamagePerSecondPerStack;
}
}
}
}
@@ -24,9 +24,12 @@ namespace GeometryTD.Definition
}
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);
int absoluteZeroStack = TagEffectResolver.GetTagStack(attackPayload?.TagRuntimes, TagType.AbsoluteZero);
float slowRatio = runtimeData.TotalStack * config.SlowRatioPerStack + GetAbsoluteZeroBonusSlowRatio(absoluteZeroStack);
float slowMultiplier = Mathf.Max(config.MinMoveSpeedMultiplier, 1f - slowRatio);
state.RemainingDuration = Mathf.Max(
state.RemainingDuration,
config.SlowDurationSeconds + GetAbsoluteZeroBonusDuration(absoluteZeroStack));
state.SlowMultiplier = Mathf.Min(state.SlowMultiplier, slowMultiplier);
runtime.Activate(TagType);
}
@@ -46,5 +49,39 @@ namespace GeometryTD.Definition
IceTagState state = runtime.GetState<IceTagState>(TagType);
return state == null ? 1f : state.SlowMultiplier;
}
private static float GetAbsoluteZeroBonusDuration(int absoluteZeroStack)
{
if (absoluteZeroStack <= 0 ||
!TagConfigRegistry.TryGetDefinition(TagType.AbsoluteZero, out TagDefinitionData definition))
{
return 0f;
}
AbsoluteZeroTagConfig absoluteZeroConfig = definition.Config as AbsoluteZeroTagConfig;
if (absoluteZeroConfig == null || !absoluteZeroConfig.IsImplemented)
{
return 0f;
}
return absoluteZeroStack * absoluteZeroConfig.BonusSlowDurationSeconds;
}
private static float GetAbsoluteZeroBonusSlowRatio(int absoluteZeroStack)
{
if (absoluteZeroStack <= 0 ||
!TagConfigRegistry.TryGetDefinition(TagType.AbsoluteZero, out TagDefinitionData definition))
{
return 0f;
}
AbsoluteZeroTagConfig absoluteZeroConfig = definition.Config as AbsoluteZeroTagConfig;
if (absoluteZeroConfig == null || !absoluteZeroConfig.IsImplemented)
{
return 0f;
}
return absoluteZeroStack * absoluteZeroConfig.BonusSlowRatioPerStack;
}
}
}
}
@@ -9,7 +9,7 @@ namespace GeometryTD.Definition
{
}
public float BonusSlowDurationSeconds { get; set; } = 0f;
public float BonusSlowRatioPerStack { get; set; } = 0f;
public float BonusSlowDurationSeconds { get; set; } = 1f;
public float BonusSlowRatioPerStack { get; set; } = 0.1f;
}
}
}
@@ -11,5 +11,6 @@ namespace GeometryTD.Definition
public float BurnDurationSeconds { get; set; } = 3f;
public float BurnDamagePerSecondPerStack { get; set; } = 20f;
public int MaxEffectiveStack { get; set; } = 5;
}
}
}
@@ -9,7 +9,7 @@ namespace GeometryTD.Definition
{
}
public float BonusBurnDurationSeconds { get; set; } = 0f;
public float BonusBurnDamagePerSecondPerStack { get; set; } = 0f;
public float BonusBurnDurationSeconds { get; set; } = 2f;
public float BonusBurnDamagePerSecondPerStack { get; set; } = 20f;
}
}
@@ -9,6 +9,6 @@ namespace GeometryTD.Definition
{
}
public int ExtraPierceCount { get; set; } = 0;
public int ExtraPierceCount { get; set; } = 2;
}
}
@@ -10,6 +10,6 @@ namespace GeometryTD.Definition
}
public bool RequiresSlowedTarget { get; set; } = true;
public float DamageBonusPerStack { get; set; } = 0f;
public float DamageBonusPerStack { get; set; } = 0.25f;
}
}
}
@@ -1,103 +1,174 @@
using System.Collections.Generic;
using GeometryTD.DataTable;
using Newtonsoft.Json.Linq;
using UnityEngine;
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)
}
};
private static readonly Dictionary<TagType, TagDefinitionData> DefinitionsByTag = CreateDefaultDefinitions();
public static IReadOnlyDictionary<TagType, TagDefinitionData> Definitions => DefinitionsByTag;
public static void ResetToDefaults()
{
DefinitionsByTag.Clear();
foreach (KeyValuePair<TagType, TagDefinitionData> pair in CreateDefaultDefinitions())
{
DefinitionsByTag.Add(pair.Key, pair.Value);
}
}
public static void LoadFromRows(IEnumerable<DRTagConfig> rows)
{
ResetToDefaults();
foreach (DRTagConfig row in rows)
{
ApplyRow(row);
}
}
public static bool TryGetDefinition(TagType tagType, out TagDefinitionData definition)
{
return DefinitionsByTag.TryGetValue(tagType, out definition);
}
private static Dictionary<TagType, TagDefinitionData> CreateDefaultDefinitions()
{
return new Dictionary<TagType, TagDefinitionData>
{
[TagType.Fire] = CreateDefinition(TagType.Fire, TagCategory.Status, TagTriggerPhase.OnAfterHit, new FireTagConfig(true)),
[TagType.BurnSpread] = CreateDefinition(TagType.BurnSpread, TagCategory.AttackShape, TagTriggerPhase.OnKill, new BurnSpreadTagConfig(false)),
[TagType.IgniteBurst] = CreateDefinition(TagType.IgniteBurst, TagCategory.AttackShape, TagTriggerPhase.OnKill, new IgniteBurstTagConfig(false)),
[TagType.Inferno] = CreateDefinition(TagType.Inferno, TagCategory.Status, 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.OnAfterHit, new FreezeMaskTagConfig(false)),
[TagType.Shatter] = CreateDefinition(TagType.Shatter, TagCategory.NumericModifier, TagTriggerPhase.OnBeforeHit, new ShatterTagConfig(true)),
[TagType.AbsoluteZero] = CreateDefinition(TagType.AbsoluteZero, TagCategory.Status, TagTriggerPhase.OnAfterHit, new AbsoluteZeroTagConfig(true)),
[TagType.Pierce] = CreateDefinition(TagType.Pierce, TagCategory.AttackShape, TagTriggerPhase.OnHit, new PierceTagConfig(false)),
[TagType.Crit] = CreateDefinition(TagType.Crit, TagCategory.NumericModifier, TagTriggerPhase.OnBeforeHit, new CritTagConfig(true)),
[TagType.Overpenetrate] = CreateDefinition(TagType.Overpenetrate, TagCategory.AttackShape, TagTriggerPhase.OnHit, new OverpenetrateTagConfig(false)),
[TagType.Execution] = CreateDefinition(TagType.Execution, TagCategory.NumericModifier, TagTriggerPhase.OnBeforeHit, new ExecutionTagConfig(true))
};
}
private static TagDefinitionData CreateDefinition(
TagType tagType,
TagCategory category,
TagTriggerPhase triggerPhase,
TagConfigBase config)
{
return new TagDefinitionData
{
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 TagDefinitionData 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 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>();
}
}
}
@@ -8,6 +8,7 @@ namespace GeometryTD.Definition
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; }
}
}
@@ -98,10 +98,18 @@ namespace GeometryTD.Definition
private static void ApplyShatter(HitContext hitContext, int stack, bool targetHasSlowStatus, ShatterTagConfig config)
{
_ = hitContext;
_ = stack;
_ = targetHasSlowStatus;
_ = config;
if (config == null || !config.IsImplemented || stack <= 0)
{
return;
}
if (config.RequiresSlowedTarget && !targetHasSlowStatus)
{
return;
}
float multiplier = 1f + stack * config.DamageBonusPerStack;
hitContext.FinalDamage = Mathf.Max(0, Mathf.RoundToInt(hitContext.FinalDamage * multiplier));
}
}
}
@@ -10,17 +10,15 @@ namespace GeometryTD.Definition
Dictionary<TagType, int> stackByTag = new Dictionary<TagType, int>();
if (componentTags != null)
{
for (int i = 0; i < componentTags.Length; i++)
foreach (var tags in componentTags)
{
TagType[] tags = componentTags[i];
if (tags == null)
{
continue;
}
for (int j = 0; j < tags.Length; j++)
foreach (var tagType in tags)
{
TagType tagType = tags[j];
if (tagType == TagType.None || !Enum.IsDefined(typeof(TagType), tagType))
{
continue;
@@ -44,7 +42,7 @@ namespace GeometryTD.Definition
}
List<TagRuntimeData> runtimes = new List<TagRuntimeData>(stackByTag.Count);
foreach (KeyValuePair<TagType, int> pair in stackByTag)
foreach (var pair in stackByTag)
{
runtimes.Add(new TagRuntimeData
{
@@ -65,9 +63,8 @@ namespace GeometryTD.Definition
}
HashSet<TagType> uniqueTags = new HashSet<TagType>();
for (int i = 0; i < tags.Count; i++)
foreach (var tagType in tags)
{
TagType tagType = tags[i];
if (tagType == TagType.None || !Enum.IsDefined(typeof(TagType), tagType))
{
continue;
@@ -106,9 +103,8 @@ namespace GeometryTD.Definition
}
List<TagType> tags = new List<TagType>(tagRuntimes.Count);
for (int i = 0; i < tagRuntimes.Count; i++)
foreach (var runtime in tagRuntimes)
{
TagRuntimeData runtime = tagRuntimes[i];
if (runtime == null || runtime.TagType == TagType.None || !Enum.IsDefined(typeof(TagType), runtime.TagType))
{
continue;