S4-07 process 1
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
# Tag表
|
||||
# Id Name MinRarity
|
||||
# Id 列1 Name MinRarity
|
||||
# int string RarityType
|
||||
# Tag编号 策划备注 Tag名 获取最低稀有度
|
||||
1 元素 Fire White
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# Id 列1 TagType Weight TriggerPhase Description Param
|
||||
# int TagType int TagTriggerPhase string string
|
||||
# Tag配置编号 策划备注 所属Tag类型 权重 触发阶段 描述 参数Json
|
||||
1 元素 Fire 20 OnAfterHit 持续对敌人造成<color=red>火</color>伤害 {\
|
||||
"BurnDurationSeconds": 3,\
|
||||
"BurnDamagePerSecondPerStack": 20,\
|
||||
"MaxEffectiveStack": 5\
|
||||
}
|
||||
2 元素 BurnSpread 20 燃烧向邻近敌人传播 {\
|
||||
"SpreadRadius": 0,\
|
||||
"SpreadDamageRate": 0\
|
||||
}
|
||||
3 元素 IgniteBurst 15 燃烧结束或击杀时爆炸 {\
|
||||
"BurstRadius": 0,\
|
||||
"BurstDamageRate": 0\
|
||||
}
|
||||
4 元素 Inferno 5 OnAfterHit 强化燃烧伤害或持续时间 {\
|
||||
"BonusBurnDurationSeconds": 0,\
|
||||
"BonusBurnDamagePerSecondPerStack": 0\
|
||||
}
|
||||
5 控制 Ice 20 OnAfterHit 命中附加减速 {\
|
||||
"SlowDurationSeconds": 2,\
|
||||
"SlowRatioPerStack": 0.2,\
|
||||
"MinMoveSpeedMultiplier": 0.4\
|
||||
}
|
||||
6 控制 FreezeMask 20 冻结积累条 / 冻结面具机制 {\
|
||||
"FreezeBuildUpPerStack": 0\
|
||||
}
|
||||
7 控制 Shatter 15 OnBeforeHit 对已减速 / 已冻结目标增伤 {\
|
||||
"RequiresSlowedTarget": true,\
|
||||
"DamageBonusPerStack": 0\
|
||||
}
|
||||
8 控制 AbsoluteZero 5 OnAfterHit 强化减速,或提高冻结触发速度 {\
|
||||
"BonusSlowDurationSeconds": 1,\
|
||||
"BonusSlowRatioPerStack": 0.1\
|
||||
}
|
||||
9 穿透 Pierce 20 子弹贯穿多个目标 {\
|
||||
"ExtraPierceCount": 2\
|
||||
}
|
||||
10 穿透 Crit 20 OnBeforeHit 命中前按概率暴击 {\
|
||||
"CritChancePerStack": 0.1,\
|
||||
"CritDamageMultiplier": 1.5\
|
||||
}
|
||||
11 穿透 Overpenetrate 15 贯穿后保留部分伤害继续飞行 {\
|
||||
"ExtraPenetrationCount": 0,\
|
||||
"RemainingDamageRate": 0\
|
||||
}
|
||||
12 穿透 Execution 5 OnBeforeHit 对低血量目标增伤或直接处决 {\
|
||||
"TargetHealthThreshold": 0.3,\
|
||||
"DamageBonusPerStack": 0.5\
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 237e82d08a4faa64da2401808bc652fc
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -7,7 +7,7 @@
|
||||
102 关于 AboutForm Medium False True
|
||||
110 主界面 MainForm Medium False False
|
||||
111 仓库UI RepoForm Medium False False
|
||||
112 大地图UI NodeMapForm Medium False True
|
||||
112 大地图UI NodeMapForm Medium False False
|
||||
113 详细信息 ItemDescForm Medium True False
|
||||
114 奖励选择UI RewardSelectForm Medium False True
|
||||
130 事件UI EventForm Medium False True
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using GeometryTD.CustomUtility;
|
||||
using GeometryTD.Definition;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.DataTable
|
||||
{
|
||||
public sealed class DRTagConfig : DataRowBase
|
||||
{
|
||||
private int m_Id;
|
||||
|
||||
public override int Id => m_Id;
|
||||
|
||||
public TagType TagType { get; private set; }
|
||||
|
||||
public TagTriggerPhase TriggerPhase { get; private set; }
|
||||
|
||||
public string Description { get; private set; }
|
||||
|
||||
public string ParamJson { get; private set; }
|
||||
|
||||
public override bool ParseDataRow(string dataRowString, object userData)
|
||||
{
|
||||
string[] columnStrings = dataRowString.Split(DataTableExtension.DataSplitSeparators);
|
||||
for (int i = 0; i < columnStrings.Length; i++)
|
||||
{
|
||||
columnStrings[i] = columnStrings[i].Trim(DataTableExtension.DataTrimSeparators);
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
index++;
|
||||
m_Id = int.Parse(columnStrings[index++]);
|
||||
index++;
|
||||
TagType = EnumUtility<TagType>.Get(columnStrings[index++]);
|
||||
TriggerPhase = EnumUtility<TagTriggerPhase>.Get(columnStrings[index++]);
|
||||
Description = columnStrings[index++];
|
||||
ParamJson = columnStrings[index++];
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3cd426c0ec84c0bbfd9918d768464f9
|
||||
timeCreated: 1773139200
|
||||
@@ -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;
|
||||
|
||||
@@ -7,11 +7,11 @@ namespace GeometryTD.Entity.EntityData
|
||||
[Serializable]
|
||||
public class TowerData : EntityDataBase
|
||||
{
|
||||
[SerializeField] private TowerStatsData _stats = new TowerStatsData();
|
||||
[SerializeField] private TowerStatsData _stats;
|
||||
[SerializeField] private int _towerLevel = 0;
|
||||
[SerializeField] private Color _muzzleColor = Color.white;
|
||||
[SerializeField] private Color _bearingColor = Color.white;
|
||||
[SerializeField] private Color _baseColor = Color.white;
|
||||
[SerializeField] private Color _muzzleColor;
|
||||
[SerializeField] private Color _bearingColor;
|
||||
[SerializeField] private Color _baseColor;
|
||||
|
||||
public TowerData(int entityId, int typeId, Vector3 position, Quaternion rotation,
|
||||
TowerStatsData stats, int towerLevel = 0, Color? muzzleColor = null, Color? bearingColor = null,
|
||||
@@ -57,4 +57,4 @@ namespace GeometryTD.Entity.EntityData
|
||||
set => _baseColor = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ namespace GeometryTD.Procedure
|
||||
"ShopPrice",
|
||||
"Sound",
|
||||
"Tag",
|
||||
"TagConfig",
|
||||
"OutGameDropPool",
|
||||
"UIForm",
|
||||
"UISound",
|
||||
@@ -194,6 +195,15 @@ namespace GeometryTD.Procedure
|
||||
|
||||
_loadedFlag[ne.DataTableAssetName] = true;
|
||||
Log.Info("Load data table '{0}' OK.", ne.DataTableAssetName);
|
||||
|
||||
if (ne.DataTableAssetName == AssetUtility.GetDataTableAsset("TagConfig", false))
|
||||
{
|
||||
var tagConfigTable = GameEntry.DataTable.GetDataTable<DRTagConfig>();
|
||||
if (tagConfigTable != null)
|
||||
{
|
||||
TagConfigRegistry.LoadFromRows(tagConfigTable.GetAllDataRows());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLoadDataTableFailure(object sender, GameEventArgs e)
|
||||
|
||||
@@ -18,7 +18,8 @@ namespace GeometryTD.UI
|
||||
public string Title;
|
||||
public string TypeText;
|
||||
public string Description;
|
||||
public string[] TagTexts;
|
||||
public TagType[] Tags;
|
||||
public TagRuntimeData[] TagRuntimes;
|
||||
}
|
||||
|
||||
protected override UIFormType UIFormTypeId => UIFormType.CombatFinishForm;
|
||||
@@ -138,7 +139,8 @@ namespace GeometryTD.UI
|
||||
tower.Name,
|
||||
"Tower",
|
||||
ItemDescUtility.BuildTowerDesc(tower, muzzleMap, bearingMap, baseMap) ?? string.Empty,
|
||||
TagDisplayUtility.BuildTowerTagTexts(tower.Stats));
|
||||
tower.Stats?.Tags,
|
||||
tower.Stats?.TagRuntimes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +168,8 @@ namespace GeometryTD.UI
|
||||
item.Name,
|
||||
BuildComponentTypeText(item.SlotType),
|
||||
ItemDescUtility.BuildMuzzleDesc(item) ?? string.Empty,
|
||||
TagDisplayUtility.BuildTagTexts(item.Tags));
|
||||
item.Tags,
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +197,8 @@ namespace GeometryTD.UI
|
||||
item.Name,
|
||||
BuildComponentTypeText(item.SlotType),
|
||||
ItemDescUtility.BuildBearingDesc(item) ?? string.Empty,
|
||||
TagDisplayUtility.BuildTagTexts(item.Tags));
|
||||
item.Tags,
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +226,8 @@ namespace GeometryTD.UI
|
||||
item.Name,
|
||||
BuildComponentTypeText(item.SlotType),
|
||||
ItemDescUtility.BuildBaseDesc(item) ?? string.Empty,
|
||||
TagDisplayUtility.BuildTagTexts(item.Tags));
|
||||
item.Tags,
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +266,13 @@ namespace GeometryTD.UI
|
||||
return Color.white;
|
||||
}
|
||||
|
||||
private void AddItemDescSeed(long itemId, string title, string typeText, string description, string[] tagTexts)
|
||||
private void AddItemDescSeed(
|
||||
long itemId,
|
||||
string title,
|
||||
string typeText,
|
||||
string description,
|
||||
TagType[] tags,
|
||||
TagRuntimeData[] tagRuntimes)
|
||||
{
|
||||
if (itemId <= 0)
|
||||
{
|
||||
@@ -273,13 +284,19 @@ namespace GeometryTD.UI
|
||||
Title = string.IsNullOrWhiteSpace(title) ? $"Item {itemId}" : title,
|
||||
TypeText = typeText ?? string.Empty,
|
||||
Description = description ?? string.Empty,
|
||||
TagTexts = CloneTagTexts(tagTexts)
|
||||
Tags = CloneTags(tags),
|
||||
TagRuntimes = CloneTagRuntimes(tagRuntimes)
|
||||
};
|
||||
}
|
||||
|
||||
private static string[] CloneTagTexts(string[] tagTexts)
|
||||
private static TagType[] CloneTags(TagType[] tags)
|
||||
{
|
||||
return tagTexts != null ? (string[])tagTexts.Clone() : System.Array.Empty<string>();
|
||||
return tags != null ? (TagType[])tags.Clone() : System.Array.Empty<TagType>();
|
||||
}
|
||||
|
||||
private static TagRuntimeData[] CloneTagRuntimes(TagRuntimeData[] tagRuntimes)
|
||||
{
|
||||
return InventoryCloneUtility.CloneTagRuntimes(tagRuntimes);
|
||||
}
|
||||
|
||||
private static string BuildComponentTypeText(TowerCompSlotType slotType)
|
||||
@@ -374,7 +391,8 @@ namespace GeometryTD.UI
|
||||
Description = seed.Description ?? string.Empty,
|
||||
Price = 0,
|
||||
TargetPos = args.TargetPos,
|
||||
TagTexts = CloneTagTexts(seed.TagTexts)
|
||||
Tags = CloneTags(seed.Tags),
|
||||
TagRuntimes = CloneTagRuntimes(seed.TagRuntimes)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -402,6 +420,3 @@ namespace GeometryTD.UI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -43,7 +43,8 @@ namespace GeometryTD.UI
|
||||
tower.Name,
|
||||
"Tower",
|
||||
ItemDescUtility.BuildTowerDesc(tower, muzzleMap, bearingMap, baseMap) ?? string.Empty,
|
||||
TagDisplayUtility.BuildTowerTagTexts(tower.Stats));
|
||||
tower.Stats?.Tags,
|
||||
tower.Stats?.TagRuntimes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +72,8 @@ namespace GeometryTD.UI
|
||||
item.Name,
|
||||
BuildComponentTypeText(item.SlotType),
|
||||
ItemDescUtility.BuildMuzzleDesc(item) ?? string.Empty,
|
||||
TagDisplayUtility.BuildTagTexts(item.Tags));
|
||||
item.Tags,
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +101,8 @@ namespace GeometryTD.UI
|
||||
item.Name,
|
||||
BuildComponentTypeText(item.SlotType),
|
||||
ItemDescUtility.BuildBearingDesc(item) ?? string.Empty,
|
||||
TagDisplayUtility.BuildTagTexts(item.Tags));
|
||||
item.Tags,
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +130,8 @@ namespace GeometryTD.UI
|
||||
item.Name,
|
||||
BuildComponentTypeText(item.SlotType),
|
||||
ItemDescUtility.BuildBaseDesc(item) ?? string.Empty,
|
||||
TagDisplayUtility.BuildTagTexts(item.Tags));
|
||||
item.Tags,
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +173,13 @@ namespace GeometryTD.UI
|
||||
_compAreaTowerIds.Add(itemContext.InstanceId);
|
||||
}
|
||||
|
||||
private void AddItemDescSeed(long itemId, string title, string typeText, string description, string[] tagTexts)
|
||||
private void AddItemDescSeed(
|
||||
long itemId,
|
||||
string title,
|
||||
string typeText,
|
||||
string description,
|
||||
TagType[] tags,
|
||||
TagRuntimeData[] tagRuntimes)
|
||||
{
|
||||
if (itemId <= 0)
|
||||
{
|
||||
@@ -181,7 +191,8 @@ namespace GeometryTD.UI
|
||||
Title = string.IsNullOrWhiteSpace(title) ? $"Item {itemId}" : title,
|
||||
TypeText = typeText ?? string.Empty,
|
||||
Description = description ?? string.Empty,
|
||||
TagTexts = tagTexts != null ? (string[])tagTexts.Clone() : Array.Empty<string>()
|
||||
Tags = CloneTags(tags),
|
||||
TagRuntimes = CloneTagRuntimes(tagRuntimes)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.CustomEvent;
|
||||
using GeometryTD.CustomUtility;
|
||||
using GeometryTD.Definition;
|
||||
using GameFramework.Event;
|
||||
using UnityEngine;
|
||||
@@ -22,7 +23,8 @@ namespace GeometryTD.UI
|
||||
public string Title;
|
||||
public string TypeText;
|
||||
public string Description;
|
||||
public string[] TagTexts;
|
||||
public TagType[] Tags;
|
||||
public TagRuntimeData[] TagRuntimes;
|
||||
}
|
||||
|
||||
protected override UIFormType UIFormTypeId => UIFormType.RepoForm;
|
||||
@@ -151,10 +153,21 @@ namespace GeometryTD.UI
|
||||
Description = seed.Description ?? string.Empty,
|
||||
Price = 0,
|
||||
TargetPos = args.TargetPos,
|
||||
TagTexts = CloneTagTexts(seed.TagTexts)
|
||||
Tags = CloneTags(seed.Tags),
|
||||
TagRuntimes = CloneTagRuntimes(seed.TagRuntimes)
|
||||
});
|
||||
}
|
||||
|
||||
private static TagType[] CloneTags(TagType[] tags)
|
||||
{
|
||||
return tags != null ? (TagType[])tags.Clone() : System.Array.Empty<TagType>();
|
||||
}
|
||||
|
||||
private static TagRuntimeData[] CloneTagRuntimes(TagRuntimeData[] tagRuntimes)
|
||||
{
|
||||
return InventoryCloneUtility.CloneTagRuntimes(tagRuntimes);
|
||||
}
|
||||
|
||||
private void OnRepoItemDragEnded(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!IsEventFromCurrentForm(sender))
|
||||
|
||||
@@ -32,20 +32,39 @@ namespace GeometryTD.UI
|
||||
{
|
||||
Title = rawData.Title,
|
||||
TypeText = rawData.TypeText,
|
||||
Description = rawData.Description,
|
||||
Description = BuildDescription(rawData),
|
||||
Price = rawData.Price,
|
||||
TargetPos = rawData.TargetPos,
|
||||
Tags = BuildTags(rawData)
|
||||
};
|
||||
}
|
||||
|
||||
private static string BuildDescription(ItemDescFormRawData rawData)
|
||||
{
|
||||
string baseDescription = rawData?.Description ?? string.Empty;
|
||||
string tagDescription = rawData?.TagRuntimes != null && rawData.TagRuntimes.Length > 0
|
||||
? TagDisplayUtility.BuildTagDescriptionText(rawData.TagRuntimes)
|
||||
: TagDisplayUtility.BuildTagDescriptionText(rawData?.Tags);
|
||||
if (string.IsNullOrWhiteSpace(tagDescription))
|
||||
{
|
||||
return baseDescription;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(baseDescription))
|
||||
{
|
||||
return tagDescription;
|
||||
}
|
||||
|
||||
return $"{baseDescription}\n{tagDescription}";
|
||||
}
|
||||
|
||||
private static TagItemContext[] BuildTags(ItemDescFormRawData rawData)
|
||||
{
|
||||
string[] tagTexts = rawData?.TagTexts;
|
||||
if ((tagTexts == null || tagTexts.Length <= 0) && rawData?.Tags != null)
|
||||
{
|
||||
tagTexts = TagDisplayUtility.BuildTagTexts(rawData.Tags);
|
||||
}
|
||||
string[] tagTexts = rawData?.TagRuntimes != null && rawData.TagRuntimes.Length > 0
|
||||
? TagDisplayUtility.BuildTagTexts(rawData.TagRuntimes)
|
||||
: rawData?.Tags != null
|
||||
? TagDisplayUtility.BuildTagTexts(rawData.Tags)
|
||||
: null;
|
||||
|
||||
if (tagTexts == null || tagTexts.Length <= 0)
|
||||
{
|
||||
|
||||
@@ -11,6 +11,6 @@ namespace GeometryTD.UI
|
||||
public int Price;
|
||||
public Vector3 TargetPos;
|
||||
public TagType[] Tags;
|
||||
public string[] TagTexts;
|
||||
public TagRuntimeData[] TagRuntimes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,5 +92,84 @@ namespace GeometryTD.CustomUtility
|
||||
|
||||
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 (TagConfigRegistry.TryGetDefinition(tagType, out TagDefinitionData definition) &&
|
||||
!string.IsNullOrWhiteSpace(definition.Description))
|
||||
{
|
||||
return definition.Description;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using Components;
|
||||
using GeometryTD.DataTable;
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.Entity;
|
||||
using GeometryTD.Entity.EntityData;
|
||||
using GeometryTD.CustomUtility;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -47,6 +49,47 @@ namespace GeometryTD.Tests.EditMode
|
||||
Assert.That(lowHealthHit.FinalDamage, Is.EqualTo(150));
|
||||
Assert.That(highHealthHit.FinalDamage, Is.EqualTo(100));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ResolveBeforeHit_Shatter_OnlyTriggersOnSlowedTarget()
|
||||
{
|
||||
AttackPayload attackPayload = new AttackPayload
|
||||
{
|
||||
BaseDamage = 100,
|
||||
AttackPropertyType = AttackPropertyType.Physics,
|
||||
TagRuntimes = new[]
|
||||
{
|
||||
new TagRuntimeData { TagType = TagType.Shatter, TotalStack = 2 }
|
||||
}
|
||||
};
|
||||
|
||||
HitContext slowedHit = TagEffectResolver.ResolveBeforeHit(attackPayload, 100, 100, true);
|
||||
HitContext normalHit = TagEffectResolver.ResolveBeforeHit(attackPayload, 100, 100, false);
|
||||
|
||||
Assert.That(slowedHit.FinalDamage, Is.EqualTo(150));
|
||||
Assert.That(normalHit.FinalDamage, Is.EqualTo(100));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ResolveBeforeHit_Shatter_CanStackWithOtherNumericTags()
|
||||
{
|
||||
AttackPayload attackPayload = new AttackPayload
|
||||
{
|
||||
BaseDamage = 100,
|
||||
AttackPropertyType = AttackPropertyType.Physics,
|
||||
TagRuntimes = new[]
|
||||
{
|
||||
new TagRuntimeData { TagType = TagType.Crit, TotalStack = 1 },
|
||||
new TagRuntimeData { TagType = TagType.Execution, TotalStack = 1 },
|
||||
new TagRuntimeData { TagType = TagType.Shatter, TotalStack = 1 }
|
||||
}
|
||||
};
|
||||
|
||||
HitContext hitContext = TagEffectResolver.ResolveBeforeHit(attackPayload, 30, 100, true, 0.05f);
|
||||
|
||||
Assert.That(hitContext.IsCriticalHit, Is.True);
|
||||
Assert.That(hitContext.FinalDamage, Is.EqualTo(281));
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class EnemyTagStatusRuntimeTests
|
||||
@@ -124,6 +167,101 @@ namespace GeometryTD.Tests.EditMode
|
||||
Assert.That(runtime.GetMoveSpeedMultiplier(), Is.EqualTo(1f).Within(0.001f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ApplyAfterHit_FireWithInferno_IncreasesBurnDamageAndDuration()
|
||||
{
|
||||
EnemyTagStatusRuntime runtime = new EnemyTagStatusRuntime();
|
||||
AttackPayload attackPayload = new AttackPayload
|
||||
{
|
||||
BaseDamage = 100,
|
||||
TagRuntimes = new[]
|
||||
{
|
||||
new TagRuntimeData { TagType = TagType.Fire, TotalStack = 1 },
|
||||
new TagRuntimeData { TagType = TagType.Inferno, TotalStack = 1 }
|
||||
}
|
||||
};
|
||||
int totalDamage = 0;
|
||||
|
||||
TagEffectResolver.ApplyAfterHit(attackPayload, runtime);
|
||||
Assert.That(runtime.HasStatus(TagType.Fire), Is.True);
|
||||
Assert.That(runtime.HasStatus(TagType.Inferno), Is.False);
|
||||
|
||||
runtime.Tick(1f, damage => totalDamage += damage);
|
||||
runtime.Tick(1f, damage => totalDamage += damage);
|
||||
runtime.Tick(1f, damage => totalDamage += damage);
|
||||
runtime.Tick(1f, damage => totalDamage += damage);
|
||||
runtime.Tick(1f, damage => totalDamage += damage);
|
||||
|
||||
Assert.That(totalDamage, Is.EqualTo(200));
|
||||
Assert.That(runtime.HasStatus(TagType.Fire), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ApplyAfterHit_InfernoWithoutFire_DoesNotCreateStatus()
|
||||
{
|
||||
EnemyTagStatusRuntime runtime = new EnemyTagStatusRuntime();
|
||||
AttackPayload attackPayload = new AttackPayload
|
||||
{
|
||||
BaseDamage = 100,
|
||||
TagRuntimes = new[]
|
||||
{
|
||||
new TagRuntimeData { TagType = TagType.Inferno, TotalStack = 1 }
|
||||
}
|
||||
};
|
||||
|
||||
TagEffectResolver.ApplyAfterHit(attackPayload, runtime);
|
||||
|
||||
Assert.That(runtime.HasStatus(TagType.Fire), Is.False);
|
||||
Assert.That(runtime.HasStatus(TagType.Inferno), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ApplyAfterHit_IceWithAbsoluteZero_IncreasesSlowStrengthAndDuration()
|
||||
{
|
||||
EnemyTagStatusRuntime runtime = new EnemyTagStatusRuntime();
|
||||
AttackPayload attackPayload = new AttackPayload
|
||||
{
|
||||
BaseDamage = 100,
|
||||
TagRuntimes = new[]
|
||||
{
|
||||
new TagRuntimeData { TagType = TagType.Ice, TotalStack = 2 },
|
||||
new TagRuntimeData { TagType = TagType.AbsoluteZero, TotalStack = 1 }
|
||||
}
|
||||
};
|
||||
|
||||
TagEffectResolver.ApplyAfterHit(attackPayload, runtime);
|
||||
|
||||
Assert.That(runtime.HasStatus(TagType.Ice), Is.True);
|
||||
Assert.That(runtime.HasStatus(TagType.AbsoluteZero), Is.False);
|
||||
Assert.That(runtime.GetMoveSpeedMultiplier(), Is.EqualTo(0.5f).Within(0.001f));
|
||||
|
||||
runtime.Tick(2.5f, null);
|
||||
Assert.That(runtime.HasStatus(TagType.Ice), Is.True);
|
||||
|
||||
runtime.Tick(0.5f, null);
|
||||
Assert.That(runtime.HasStatus(TagType.Ice), Is.False);
|
||||
Assert.That(runtime.GetMoveSpeedMultiplier(), Is.EqualTo(1f).Within(0.001f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ApplyAfterHit_AbsoluteZeroWithoutIce_DoesNotCreateStatus()
|
||||
{
|
||||
EnemyTagStatusRuntime runtime = new EnemyTagStatusRuntime();
|
||||
AttackPayload attackPayload = new AttackPayload
|
||||
{
|
||||
BaseDamage = 100,
|
||||
TagRuntimes = new[]
|
||||
{
|
||||
new TagRuntimeData { TagType = TagType.AbsoluteZero, TotalStack = 1 }
|
||||
}
|
||||
};
|
||||
|
||||
TagEffectResolver.ApplyAfterHit(attackPayload, runtime);
|
||||
|
||||
Assert.That(runtime.HasStatus(TagType.Ice), Is.False);
|
||||
Assert.That(runtime.HasStatus(TagType.AbsoluteZero), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Tick_OnlyUpdatesActivatedStatusTags()
|
||||
{
|
||||
@@ -177,21 +315,84 @@ namespace GeometryTD.Tests.EditMode
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Definitions_Keep_Unimplemented_Tags_As_Explicit_NoOpPlaceholders()
|
||||
public void Definitions_Mark_FirstBatch_SevenTags_AsImplemented()
|
||||
{
|
||||
Assert.That(TagConfigRegistry.TryGetDefinition(TagType.Shatter, out TagDefinitionData shatter), Is.True);
|
||||
Assert.That(shatter.Config, Is.TypeOf<ShatterTagConfig>());
|
||||
Assert.That(((ShatterTagConfig)shatter.Config).IsImplemented, Is.False);
|
||||
Assert.That(((ShatterTagConfig)shatter.Config).IsImplemented, Is.True);
|
||||
|
||||
Assert.That(TagConfigRegistry.TryGetDefinition(TagType.Inferno, out TagDefinitionData inferno), Is.True);
|
||||
Assert.That(inferno.Config, Is.TypeOf<InfernoTagConfig>());
|
||||
Assert.That(((InfernoTagConfig)inferno.Config).IsImplemented, Is.False);
|
||||
Assert.That(((InfernoTagConfig)inferno.Config).IsImplemented, Is.True);
|
||||
|
||||
Assert.That(TagConfigRegistry.TryGetDefinition(TagType.AbsoluteZero, out TagDefinitionData absoluteZero), Is.True);
|
||||
Assert.That(absoluteZero.Config, Is.TypeOf<AbsoluteZeroTagConfig>());
|
||||
Assert.That(((AbsoluteZeroTagConfig)absoluteZero.Config).IsImplemented, Is.True);
|
||||
|
||||
Assert.That(TagConfigRegistry.TryGetDefinition(TagType.Pierce, out TagDefinitionData pierce), Is.True);
|
||||
Assert.That(pierce.Config, Is.TypeOf<PierceTagConfig>());
|
||||
Assert.That(((PierceTagConfig)pierce.Config).IsImplemented, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LoadFromRows_Overrides_TriggerPhase_And_ConfigValues()
|
||||
{
|
||||
DRTagConfig fireRow = new DRTagConfig();
|
||||
bool parsed = fireRow.ParseDataRow("\t1\t元素\tFire\tOnAfterHit\t火焰测试描述\t{\"BurnDurationSeconds\":4,\"BurnDamagePerSecondPerStack\":25,\"MaxEffectiveStack\":3}", null);
|
||||
Assert.That(parsed, Is.True);
|
||||
|
||||
TagConfigRegistry.LoadFromRows(new[] { fireRow });
|
||||
|
||||
Assert.That(TagConfigRegistry.TryGetDefinition(TagType.Fire, out TagDefinitionData fire), Is.True);
|
||||
Assert.That(fire.TriggerPhase, Is.EqualTo(TagTriggerPhase.OnAfterHit));
|
||||
Assert.That(fire.Description, Is.EqualTo("火焰测试描述"));
|
||||
Assert.That(fire.Config, Is.TypeOf<FireTagConfig>());
|
||||
|
||||
FireTagConfig config = (FireTagConfig)fire.Config;
|
||||
Assert.That(config.BurnDurationSeconds, Is.EqualTo(4f).Within(0.001f));
|
||||
Assert.That(config.BurnDamagePerSecondPerStack, Is.EqualTo(25f).Within(0.001f));
|
||||
Assert.That(config.MaxEffectiveStack, Is.EqualTo(3));
|
||||
|
||||
TagConfigRegistry.ResetToDefaults();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildTagDescriptionText_Uses_Registry_Descriptions()
|
||||
{
|
||||
DRTagConfig fireRow = new DRTagConfig();
|
||||
DRTagConfig shatterRow = new DRTagConfig();
|
||||
Assert.That(fireRow.ParseDataRow("\t1\t元素\tFire\tOnAfterHit\t持续燃烧\t{\"BurnDurationSeconds\":3,\"BurnDamagePerSecondPerStack\":20,\"MaxEffectiveStack\":5}", null), Is.True);
|
||||
Assert.That(shatterRow.ParseDataRow("\t7\t控制\tShatter\tOnBeforeHit\t对减速目标增伤\t{\"RequiresSlowedTarget\":true,\"DamageBonusPerStack\":0.25}", null), Is.True);
|
||||
|
||||
TagConfigRegistry.LoadFromRows(new[] { fireRow, shatterRow });
|
||||
|
||||
string description = TagDisplayUtility.BuildTagDescriptionText(new[] { TagType.Fire, TagType.Shatter });
|
||||
|
||||
Assert.That(description, Does.Contain("持续燃烧"));
|
||||
Assert.That(description, Does.Contain("对减速目标增伤"));
|
||||
|
||||
TagConfigRegistry.ResetToDefaults();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildTagDescriptionText_FromTagRuntimes_Preserves_StackText()
|
||||
{
|
||||
DRTagConfig iceRow = new DRTagConfig();
|
||||
Assert.That(iceRow.ParseDataRow("\t5\t控制\tIce\tOnAfterHit\t命中附加减速\t{\"SlowDurationSeconds\":2,\"SlowRatioPerStack\":0.2,\"MinMoveSpeedMultiplier\":0.4}", null), Is.True);
|
||||
|
||||
TagConfigRegistry.LoadFromRows(new[] { iceRow });
|
||||
|
||||
string description = TagDisplayUtility.BuildTagDescriptionText(new[]
|
||||
{
|
||||
new TagRuntimeData { TagType = TagType.Ice, TotalStack = 2 }
|
||||
});
|
||||
|
||||
Assert.That(description, Does.Contain("Ice x2"));
|
||||
Assert.That(description, Does.Contain("命中附加减速"));
|
||||
|
||||
TagConfigRegistry.ResetToDefaults();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AttackShape_Placeholders_Are_Routable_Without_Runtime_Effect()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user