S4-07 process 1
This commit is contained in:
parent
52f9e212b9
commit
9de2e50262
|
|
@ -104,4 +104,5 @@ InitTestScene*.unity*
|
||||||
/[Aa]ssets/RawResources.meta
|
/[Aa]ssets/RawResources.meta
|
||||||
/.dotnet
|
/.dotnet
|
||||||
/.idea
|
/.idea
|
||||||
~$*.xlsx
|
~$*.xlsx
|
||||||
|
/tools
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
# Tag表
|
# Id 列1 Name MinRarity
|
||||||
# Id Name MinRarity
|
|
||||||
# int string RarityType
|
# int string RarityType
|
||||||
# Tag编号 策划备注 Tag名 获取最低稀有度
|
# Tag编号 策划备注 Tag名 获取最低稀有度
|
||||||
1 元素 Fire White
|
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
|
102 关于 AboutForm Medium False True
|
||||||
110 主界面 MainForm Medium False False
|
110 主界面 MainForm Medium False False
|
||||||
111 仓库UI RepoForm 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
|
113 详细信息 ItemDescForm Medium True False
|
||||||
114 奖励选择UI RewardSelectForm Medium False True
|
114 奖励选择UI RewardSelectForm Medium False True
|
||||||
130 事件UI EventForm 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
|
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>
|
||||||
/// 背包内防御塔实例数据。
|
/// 背包内防御塔实例数据。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -69,4 +51,4 @@ namespace GeometryTD.Definition
|
||||||
|
|
||||||
[JsonIgnore] public string ComposedIconKey { get; set; }
|
[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);
|
FireTagState state = runtime.GetOrCreateState<FireTagState>(TagType);
|
||||||
float burnDamagePerSecond = Mathf.Max(0f,
|
int infernoStack = TagEffectResolver.GetTagStack(attackPayload?.TagRuntimes, TagType.Inferno);
|
||||||
config.BurnDamagePerSecondPerStack * runtimeData.TotalStack);
|
float burnDamagePerSecond = Mathf.Max(
|
||||||
|
0f,
|
||||||
|
config.BurnDamagePerSecondPerStack * runtimeData.TotalStack + GetInfernoBonusDamagePerSecond(infernoStack));
|
||||||
|
|
||||||
if (burnDamagePerSecond <= 0f)
|
if (burnDamagePerSecond <= 0f)
|
||||||
{
|
{
|
||||||
return;
|
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);
|
state.DamagePerSecond = Mathf.Max(state.DamagePerSecond, burnDamagePerSecond);
|
||||||
runtime.Activate(TagType);
|
runtime.Activate(TagType);
|
||||||
}
|
}
|
||||||
|
|
@ -64,5 +68,39 @@ namespace GeometryTD.Definition
|
||||||
state.PendingDamage = 0f;
|
state.PendingDamage = 0f;
|
||||||
return false;
|
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);
|
IceTagState state = runtime.GetOrCreateState<IceTagState>(TagType);
|
||||||
float slowMultiplier = Mathf.Max(config.MinMoveSpeedMultiplier,
|
int absoluteZeroStack = TagEffectResolver.GetTagStack(attackPayload?.TagRuntimes, TagType.AbsoluteZero);
|
||||||
1f - runtimeData.TotalStack * config.SlowRatioPerStack);
|
float slowRatio = runtimeData.TotalStack * config.SlowRatioPerStack + GetAbsoluteZeroBonusSlowRatio(absoluteZeroStack);
|
||||||
state.RemainingDuration = Mathf.Max(state.RemainingDuration, config.SlowDurationSeconds);
|
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);
|
state.SlowMultiplier = Mathf.Min(state.SlowMultiplier, slowMultiplier);
|
||||||
runtime.Activate(TagType);
|
runtime.Activate(TagType);
|
||||||
}
|
}
|
||||||
|
|
@ -46,5 +49,39 @@ namespace GeometryTD.Definition
|
||||||
IceTagState state = runtime.GetState<IceTagState>(TagType);
|
IceTagState state = runtime.GetState<IceTagState>(TagType);
|
||||||
return state == null ? 1f : state.SlowMultiplier;
|
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 BonusSlowDurationSeconds { get; set; } = 1f;
|
||||||
public float BonusSlowRatioPerStack { get; set; } = 0f;
|
public float BonusSlowRatioPerStack { get; set; } = 0.1f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,5 +11,6 @@ namespace GeometryTD.Definition
|
||||||
|
|
||||||
public float BurnDurationSeconds { get; set; } = 3f;
|
public float BurnDurationSeconds { get; set; } = 3f;
|
||||||
public float BurnDamagePerSecondPerStack { get; set; } = 20f;
|
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 BonusBurnDurationSeconds { get; set; } = 2f;
|
||||||
public float BonusBurnDamagePerSecondPerStack { get; set; } = 0f;
|
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 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 System.Collections.Generic;
|
||||||
|
using GeometryTD.DataTable;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace GeometryTD.Definition
|
namespace GeometryTD.Definition
|
||||||
{
|
{
|
||||||
public static class TagConfigRegistry
|
public static class TagConfigRegistry
|
||||||
{
|
{
|
||||||
private static readonly Dictionary<TagType, TagDefinitionData> DefinitionsByTag =
|
private static readonly Dictionary<TagType, TagDefinitionData> DefinitionsByTag = CreateDefaultDefinitions();
|
||||||
new Dictionary<TagType, TagDefinitionData>
|
|
||||||
{
|
|
||||||
[TagType.Fire] = new TagDefinitionData
|
|
||||||
{
|
|
||||||
TagType = TagType.Fire,
|
|
||||||
Category = TagCategory.Status,
|
|
||||||
TriggerPhase = TagTriggerPhase.OnAfterHit,
|
|
||||||
Config = new FireTagConfig(true)
|
|
||||||
},
|
|
||||||
[TagType.BurnSpread] = new TagDefinitionData
|
|
||||||
{
|
|
||||||
TagType = TagType.BurnSpread,
|
|
||||||
Category = TagCategory.AttackShape,
|
|
||||||
TriggerPhase = TagTriggerPhase.OnKill,
|
|
||||||
Config = new BurnSpreadTagConfig(false)
|
|
||||||
},
|
|
||||||
[TagType.IgniteBurst] = new TagDefinitionData
|
|
||||||
{
|
|
||||||
TagType = TagType.IgniteBurst,
|
|
||||||
Category = TagCategory.AttackShape,
|
|
||||||
TriggerPhase = TagTriggerPhase.OnKill,
|
|
||||||
Config = new IgniteBurstTagConfig(false)
|
|
||||||
},
|
|
||||||
[TagType.Inferno] = new TagDefinitionData
|
|
||||||
{
|
|
||||||
TagType = TagType.Inferno,
|
|
||||||
Category = TagCategory.Status,
|
|
||||||
TriggerPhase = TagTriggerPhase.OnAfterHit,
|
|
||||||
Config = new InfernoTagConfig(false)
|
|
||||||
},
|
|
||||||
[TagType.Ice] = new TagDefinitionData
|
|
||||||
{
|
|
||||||
TagType = TagType.Ice,
|
|
||||||
Category = TagCategory.Status,
|
|
||||||
TriggerPhase = TagTriggerPhase.OnAfterHit,
|
|
||||||
Config = new IceTagConfig(true)
|
|
||||||
},
|
|
||||||
[TagType.FreezeMask] = new TagDefinitionData
|
|
||||||
{
|
|
||||||
TagType = TagType.FreezeMask,
|
|
||||||
Category = TagCategory.AttackShape,
|
|
||||||
TriggerPhase = TagTriggerPhase.OnAfterHit,
|
|
||||||
Config = new FreezeMaskTagConfig(false)
|
|
||||||
},
|
|
||||||
[TagType.Shatter] = new TagDefinitionData
|
|
||||||
{
|
|
||||||
TagType = TagType.Shatter,
|
|
||||||
Category = TagCategory.NumericModifier,
|
|
||||||
TriggerPhase = TagTriggerPhase.OnBeforeHit,
|
|
||||||
Config = new ShatterTagConfig(false)
|
|
||||||
},
|
|
||||||
[TagType.AbsoluteZero] = new TagDefinitionData
|
|
||||||
{
|
|
||||||
TagType = TagType.AbsoluteZero,
|
|
||||||
Category = TagCategory.Status,
|
|
||||||
TriggerPhase = TagTriggerPhase.OnAfterHit,
|
|
||||||
Config = new AbsoluteZeroTagConfig(false)
|
|
||||||
},
|
|
||||||
[TagType.Pierce] = new TagDefinitionData
|
|
||||||
{
|
|
||||||
TagType = TagType.Pierce,
|
|
||||||
Category = TagCategory.AttackShape,
|
|
||||||
TriggerPhase = TagTriggerPhase.OnHit,
|
|
||||||
Config = new PierceTagConfig(false)
|
|
||||||
},
|
|
||||||
[TagType.Crit] = new TagDefinitionData
|
|
||||||
{
|
|
||||||
TagType = TagType.Crit,
|
|
||||||
Category = TagCategory.NumericModifier,
|
|
||||||
TriggerPhase = TagTriggerPhase.OnBeforeHit,
|
|
||||||
Config = new CritTagConfig(true)
|
|
||||||
},
|
|
||||||
[TagType.Overpenetrate] = new TagDefinitionData
|
|
||||||
{
|
|
||||||
TagType = TagType.Overpenetrate,
|
|
||||||
Category = TagCategory.AttackShape,
|
|
||||||
TriggerPhase = TagTriggerPhase.OnHit,
|
|
||||||
Config = new OverpenetrateTagConfig(false)
|
|
||||||
},
|
|
||||||
[TagType.Execution] = new TagDefinitionData
|
|
||||||
{
|
|
||||||
TagType = TagType.Execution,
|
|
||||||
Category = TagCategory.NumericModifier,
|
|
||||||
TriggerPhase = TagTriggerPhase.OnBeforeHit,
|
|
||||||
Config = new ExecutionTagConfig(true)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public static IReadOnlyDictionary<TagType, TagDefinitionData> Definitions => DefinitionsByTag;
|
public static 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)
|
public static bool TryGetDefinition(TagType tagType, out TagDefinitionData definition)
|
||||||
{
|
{
|
||||||
return DefinitionsByTag.TryGetValue(tagType, out 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 TagType TagType { get; set; }
|
||||||
public TagCategory Category { get; set; }
|
public TagCategory Category { get; set; }
|
||||||
public TagTriggerPhase TriggerPhase { get; set; }
|
public TagTriggerPhase TriggerPhase { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
public TagConfigBase Config { 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)
|
private static void ApplyShatter(HitContext hitContext, int stack, bool targetHasSlowStatus, ShatterTagConfig config)
|
||||||
{
|
{
|
||||||
_ = hitContext;
|
if (config == null || !config.IsImplemented || stack <= 0)
|
||||||
_ = stack;
|
{
|
||||||
_ = targetHasSlowStatus;
|
return;
|
||||||
_ = config;
|
}
|
||||||
|
|
||||||
|
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>();
|
Dictionary<TagType, int> stackByTag = new Dictionary<TagType, int>();
|
||||||
if (componentTags != null)
|
if (componentTags != null)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < componentTags.Length; i++)
|
foreach (var tags in componentTags)
|
||||||
{
|
{
|
||||||
TagType[] tags = componentTags[i];
|
|
||||||
if (tags == null)
|
if (tags == null)
|
||||||
{
|
{
|
||||||
continue;
|
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))
|
if (tagType == TagType.None || !Enum.IsDefined(typeof(TagType), tagType))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -44,7 +42,7 @@ namespace GeometryTD.Definition
|
||||||
}
|
}
|
||||||
|
|
||||||
List<TagRuntimeData> runtimes = new List<TagRuntimeData>(stackByTag.Count);
|
List<TagRuntimeData> runtimes = new List<TagRuntimeData>(stackByTag.Count);
|
||||||
foreach (KeyValuePair<TagType, int> pair in stackByTag)
|
foreach (var pair in stackByTag)
|
||||||
{
|
{
|
||||||
runtimes.Add(new TagRuntimeData
|
runtimes.Add(new TagRuntimeData
|
||||||
{
|
{
|
||||||
|
|
@ -65,9 +63,8 @@ namespace GeometryTD.Definition
|
||||||
}
|
}
|
||||||
|
|
||||||
HashSet<TagType> uniqueTags = new HashSet<TagType>();
|
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))
|
if (tagType == TagType.None || !Enum.IsDefined(typeof(TagType), tagType))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -106,9 +103,8 @@ namespace GeometryTD.Definition
|
||||||
}
|
}
|
||||||
|
|
||||||
List<TagType> tags = new List<TagType>(tagRuntimes.Count);
|
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))
|
if (runtime == null || runtime.TagType == TagType.None || !Enum.IsDefined(typeof(TagType), runtime.TagType))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,11 @@ namespace GeometryTD.Entity.EntityData
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class TowerData : EntityDataBase
|
public class TowerData : EntityDataBase
|
||||||
{
|
{
|
||||||
[SerializeField] private TowerStatsData _stats = new TowerStatsData();
|
[SerializeField] private TowerStatsData _stats;
|
||||||
[SerializeField] private int _towerLevel = 0;
|
[SerializeField] private int _towerLevel = 0;
|
||||||
[SerializeField] private Color _muzzleColor = Color.white;
|
[SerializeField] private Color _muzzleColor;
|
||||||
[SerializeField] private Color _bearingColor = Color.white;
|
[SerializeField] private Color _bearingColor;
|
||||||
[SerializeField] private Color _baseColor = Color.white;
|
[SerializeField] private Color _baseColor;
|
||||||
|
|
||||||
public TowerData(int entityId, int typeId, Vector3 position, Quaternion rotation,
|
public TowerData(int entityId, int typeId, Vector3 position, Quaternion rotation,
|
||||||
TowerStatsData stats, int towerLevel = 0, Color? muzzleColor = null, Color? bearingColor = null,
|
TowerStatsData stats, int towerLevel = 0, Color? muzzleColor = null, Color? bearingColor = null,
|
||||||
|
|
@ -57,4 +57,4 @@ namespace GeometryTD.Entity.EntityData
|
||||||
set => _baseColor = value;
|
set => _baseColor = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -31,6 +31,7 @@ namespace GeometryTD.Procedure
|
||||||
"ShopPrice",
|
"ShopPrice",
|
||||||
"Sound",
|
"Sound",
|
||||||
"Tag",
|
"Tag",
|
||||||
|
"TagConfig",
|
||||||
"OutGameDropPool",
|
"OutGameDropPool",
|
||||||
"UIForm",
|
"UIForm",
|
||||||
"UISound",
|
"UISound",
|
||||||
|
|
@ -194,6 +195,15 @@ namespace GeometryTD.Procedure
|
||||||
|
|
||||||
_loadedFlag[ne.DataTableAssetName] = true;
|
_loadedFlag[ne.DataTableAssetName] = true;
|
||||||
Log.Info("Load data table '{0}' OK.", ne.DataTableAssetName);
|
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)
|
private void OnLoadDataTableFailure(object sender, GameEventArgs e)
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ namespace GeometryTD.UI
|
||||||
public string Title;
|
public string Title;
|
||||||
public string TypeText;
|
public string TypeText;
|
||||||
public string Description;
|
public string Description;
|
||||||
public string[] TagTexts;
|
public TagType[] Tags;
|
||||||
|
public TagRuntimeData[] TagRuntimes;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override UIFormType UIFormTypeId => UIFormType.CombatFinishForm;
|
protected override UIFormType UIFormTypeId => UIFormType.CombatFinishForm;
|
||||||
|
|
@ -138,7 +139,8 @@ namespace GeometryTD.UI
|
||||||
tower.Name,
|
tower.Name,
|
||||||
"Tower",
|
"Tower",
|
||||||
ItemDescUtility.BuildTowerDesc(tower, muzzleMap, bearingMap, baseMap) ?? string.Empty,
|
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,
|
item.Name,
|
||||||
BuildComponentTypeText(item.SlotType),
|
BuildComponentTypeText(item.SlotType),
|
||||||
ItemDescUtility.BuildMuzzleDesc(item) ?? string.Empty,
|
ItemDescUtility.BuildMuzzleDesc(item) ?? string.Empty,
|
||||||
TagDisplayUtility.BuildTagTexts(item.Tags));
|
item.Tags,
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -194,7 +197,8 @@ namespace GeometryTD.UI
|
||||||
item.Name,
|
item.Name,
|
||||||
BuildComponentTypeText(item.SlotType),
|
BuildComponentTypeText(item.SlotType),
|
||||||
ItemDescUtility.BuildBearingDesc(item) ?? string.Empty,
|
ItemDescUtility.BuildBearingDesc(item) ?? string.Empty,
|
||||||
TagDisplayUtility.BuildTagTexts(item.Tags));
|
item.Tags,
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -222,7 +226,8 @@ namespace GeometryTD.UI
|
||||||
item.Name,
|
item.Name,
|
||||||
BuildComponentTypeText(item.SlotType),
|
BuildComponentTypeText(item.SlotType),
|
||||||
ItemDescUtility.BuildBaseDesc(item) ?? string.Empty,
|
ItemDescUtility.BuildBaseDesc(item) ?? string.Empty,
|
||||||
TagDisplayUtility.BuildTagTexts(item.Tags));
|
item.Tags,
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,7 +266,13 @@ namespace GeometryTD.UI
|
||||||
return Color.white;
|
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)
|
if (itemId <= 0)
|
||||||
{
|
{
|
||||||
|
|
@ -273,13 +284,19 @@ namespace GeometryTD.UI
|
||||||
Title = string.IsNullOrWhiteSpace(title) ? $"Item {itemId}" : title,
|
Title = string.IsNullOrWhiteSpace(title) ? $"Item {itemId}" : title,
|
||||||
TypeText = typeText ?? string.Empty,
|
TypeText = typeText ?? string.Empty,
|
||||||
Description = description ?? 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)
|
private static string BuildComponentTypeText(TowerCompSlotType slotType)
|
||||||
|
|
@ -374,7 +391,8 @@ namespace GeometryTD.UI
|
||||||
Description = seed.Description ?? string.Empty,
|
Description = seed.Description ?? string.Empty,
|
||||||
Price = 0,
|
Price = 0,
|
||||||
TargetPos = args.TargetPos,
|
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.Name,
|
||||||
"Tower",
|
"Tower",
|
||||||
ItemDescUtility.BuildTowerDesc(tower, muzzleMap, bearingMap, baseMap) ?? string.Empty,
|
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,
|
item.Name,
|
||||||
BuildComponentTypeText(item.SlotType),
|
BuildComponentTypeText(item.SlotType),
|
||||||
ItemDescUtility.BuildMuzzleDesc(item) ?? string.Empty,
|
ItemDescUtility.BuildMuzzleDesc(item) ?? string.Empty,
|
||||||
TagDisplayUtility.BuildTagTexts(item.Tags));
|
item.Tags,
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,7 +101,8 @@ namespace GeometryTD.UI
|
||||||
item.Name,
|
item.Name,
|
||||||
BuildComponentTypeText(item.SlotType),
|
BuildComponentTypeText(item.SlotType),
|
||||||
ItemDescUtility.BuildBearingDesc(item) ?? string.Empty,
|
ItemDescUtility.BuildBearingDesc(item) ?? string.Empty,
|
||||||
TagDisplayUtility.BuildTagTexts(item.Tags));
|
item.Tags,
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,7 +130,8 @@ namespace GeometryTD.UI
|
||||||
item.Name,
|
item.Name,
|
||||||
BuildComponentTypeText(item.SlotType),
|
BuildComponentTypeText(item.SlotType),
|
||||||
ItemDescUtility.BuildBaseDesc(item) ?? string.Empty,
|
ItemDescUtility.BuildBaseDesc(item) ?? string.Empty,
|
||||||
TagDisplayUtility.BuildTagTexts(item.Tags));
|
item.Tags,
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,7 +173,13 @@ namespace GeometryTD.UI
|
||||||
_compAreaTowerIds.Add(itemContext.InstanceId);
|
_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)
|
if (itemId <= 0)
|
||||||
{
|
{
|
||||||
|
|
@ -181,7 +191,8 @@ namespace GeometryTD.UI
|
||||||
Title = string.IsNullOrWhiteSpace(title) ? $"Item {itemId}" : title,
|
Title = string.IsNullOrWhiteSpace(title) ? $"Item {itemId}" : title,
|
||||||
TypeText = typeText ?? string.Empty,
|
TypeText = typeText ?? string.Empty,
|
||||||
Description = description ?? 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 System.Collections.Generic;
|
||||||
using GeometryTD.CustomEvent;
|
using GeometryTD.CustomEvent;
|
||||||
|
using GeometryTD.CustomUtility;
|
||||||
using GeometryTD.Definition;
|
using GeometryTD.Definition;
|
||||||
using GameFramework.Event;
|
using GameFramework.Event;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
@ -22,7 +23,8 @@ namespace GeometryTD.UI
|
||||||
public string Title;
|
public string Title;
|
||||||
public string TypeText;
|
public string TypeText;
|
||||||
public string Description;
|
public string Description;
|
||||||
public string[] TagTexts;
|
public TagType[] Tags;
|
||||||
|
public TagRuntimeData[] TagRuntimes;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override UIFormType UIFormTypeId => UIFormType.RepoForm;
|
protected override UIFormType UIFormTypeId => UIFormType.RepoForm;
|
||||||
|
|
@ -151,10 +153,21 @@ namespace GeometryTD.UI
|
||||||
Description = seed.Description ?? string.Empty,
|
Description = seed.Description ?? string.Empty,
|
||||||
Price = 0,
|
Price = 0,
|
||||||
TargetPos = args.TargetPos,
|
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)
|
private void OnRepoItemDragEnded(object sender, GameEventArgs e)
|
||||||
{
|
{
|
||||||
if (!IsEventFromCurrentForm(sender))
|
if (!IsEventFromCurrentForm(sender))
|
||||||
|
|
|
||||||
|
|
@ -32,20 +32,39 @@ namespace GeometryTD.UI
|
||||||
{
|
{
|
||||||
Title = rawData.Title,
|
Title = rawData.Title,
|
||||||
TypeText = rawData.TypeText,
|
TypeText = rawData.TypeText,
|
||||||
Description = rawData.Description,
|
Description = BuildDescription(rawData),
|
||||||
Price = rawData.Price,
|
Price = rawData.Price,
|
||||||
TargetPos = rawData.TargetPos,
|
TargetPos = rawData.TargetPos,
|
||||||
Tags = BuildTags(rawData)
|
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)
|
private static TagItemContext[] BuildTags(ItemDescFormRawData rawData)
|
||||||
{
|
{
|
||||||
string[] tagTexts = rawData?.TagTexts;
|
string[] tagTexts = rawData?.TagRuntimes != null && rawData.TagRuntimes.Length > 0
|
||||||
if ((tagTexts == null || tagTexts.Length <= 0) && rawData?.Tags != null)
|
? TagDisplayUtility.BuildTagTexts(rawData.TagRuntimes)
|
||||||
{
|
: rawData?.Tags != null
|
||||||
tagTexts = TagDisplayUtility.BuildTagTexts(rawData.Tags);
|
? TagDisplayUtility.BuildTagTexts(rawData.Tags)
|
||||||
}
|
: null;
|
||||||
|
|
||||||
if (tagTexts == null || tagTexts.Length <= 0)
|
if (tagTexts == null || tagTexts.Length <= 0)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,6 @@ namespace GeometryTD.UI
|
||||||
public int Price;
|
public int Price;
|
||||||
public Vector3 TargetPos;
|
public Vector3 TargetPos;
|
||||||
public TagType[] Tags;
|
public TagType[] Tags;
|
||||||
public string[] TagTexts;
|
public TagRuntimeData[] TagRuntimes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -92,5 +92,84 @@ namespace GeometryTD.CustomUtility
|
||||||
|
|
||||||
return BuildTagTexts(towerStats.Tags);
|
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 Components;
|
||||||
|
using GeometryTD.DataTable;
|
||||||
using GeometryTD.Definition;
|
using GeometryTD.Definition;
|
||||||
using GeometryTD.Entity;
|
using GeometryTD.Entity;
|
||||||
using GeometryTD.Entity.EntityData;
|
using GeometryTD.Entity.EntityData;
|
||||||
|
using GeometryTD.CustomUtility;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
|
|
@ -47,6 +49,47 @@ namespace GeometryTD.Tests.EditMode
|
||||||
Assert.That(lowHealthHit.FinalDamage, Is.EqualTo(150));
|
Assert.That(lowHealthHit.FinalDamage, Is.EqualTo(150));
|
||||||
Assert.That(highHealthHit.FinalDamage, Is.EqualTo(100));
|
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
|
public sealed class EnemyTagStatusRuntimeTests
|
||||||
|
|
@ -124,6 +167,101 @@ namespace GeometryTD.Tests.EditMode
|
||||||
Assert.That(runtime.GetMoveSpeedMultiplier(), Is.EqualTo(1f).Within(0.001f));
|
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]
|
[Test]
|
||||||
public void Tick_OnlyUpdatesActivatedStatusTags()
|
public void Tick_OnlyUpdatesActivatedStatusTags()
|
||||||
{
|
{
|
||||||
|
|
@ -177,21 +315,84 @@ namespace GeometryTD.Tests.EditMode
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[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(TagConfigRegistry.TryGetDefinition(TagType.Shatter, out TagDefinitionData shatter), Is.True);
|
||||||
Assert.That(shatter.Config, Is.TypeOf<ShatterTagConfig>());
|
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(TagConfigRegistry.TryGetDefinition(TagType.Inferno, out TagDefinitionData inferno), Is.True);
|
||||||
Assert.That(inferno.Config, Is.TypeOf<InfernoTagConfig>());
|
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(TagConfigRegistry.TryGetDefinition(TagType.Pierce, out TagDefinitionData pierce), Is.True);
|
||||||
Assert.That(pierce.Config, Is.TypeOf<PierceTagConfig>());
|
Assert.That(pierce.Config, Is.TypeOf<PierceTagConfig>());
|
||||||
Assert.That(((PierceTagConfig)pierce.Config).IsImplemented, Is.False);
|
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]
|
[Test]
|
||||||
public void AttackShape_Placeholders_Are_Routable_Without_Runtime_Effect()
|
public void AttackShape_Placeholders_Are_Routable_Without_Runtime_Effect()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -130,8 +130,8 @@
|
||||||
| [x] | S4-03 | 先固化 Tag 系统设计与首发范围 | `docs/TagSystemDesign.md`<br>`docs/CodeX-TODO.md` | Tag 的来源、汇总、生效与首发集合口径固定 |
|
| [x] | S4-03 | 先固化 Tag 系统设计与首发范围 | `docs/TagSystemDesign.md`<br>`docs/CodeX-TODO.md` | Tag 的来源、汇总、生效与首发集合口径固定 |
|
||||||
| [x] | S4-04 | 实现组件实例 Tag 的统一生成入口 | `Assets/GameMain/Scripts/Definition/`<br>`Assets/GameMain/Scripts/CustomComponent/PlayerInventory/` | 掉落、商店、初始种子、事件奖励共用同一生成结果 |
|
| [x] | S4-04 | 实现组件实例 Tag 的统一生成入口 | `Assets/GameMain/Scripts/Definition/`<br>`Assets/GameMain/Scripts/CustomComponent/PlayerInventory/` | 掉落、商店、初始种子、事件奖励共用同一生成结果 |
|
||||||
| [x] | S4-05 | 实现组塔后的 Tag 汇总与展示入口 | `Assets/GameMain/Scripts/Definition/`<br>`Assets/GameMain/Scripts/CustomComponent/PlayerInventory/`<br>`Assets/GameMain/Scripts/UI/` | 组件 Tag 可汇总为塔级结果,且 UI 展示口径一致 |
|
| [x] | S4-05 | 实现组塔后的 Tag 汇总与展示入口 | `Assets/GameMain/Scripts/Definition/`<br>`Assets/GameMain/Scripts/CustomComponent/PlayerInventory/`<br>`Assets/GameMain/Scripts/UI/` | 组件 Tag 可汇总为塔级结果,且 UI 展示口径一致 |
|
||||||
| [ ] | S4-06 | 实现首批基础 Tag 的战斗生效 | `Assets/GameMain/Scripts/Entity/`<br>`Assets/GameMain/Scripts/Components/`<br>`Assets/GameMain/Scripts/Definition/` | 首发 6~8 个基础 Tag 至少完成一批可验证效果,且战斗入口与状态运行时结构可继续扩展 |
|
| [x] | S4-06 | 实现首批基础 Tag 的战斗生效 | `Assets/GameMain/Scripts/Entity/`<br>`Assets/GameMain/Scripts/Components/`<br>`Assets/GameMain/Scripts/Definition/` | 首发 6~8 个基础 Tag 至少完成一批可验证效果,且战斗入口与状态运行时结构可继续扩展 |
|
||||||
| [ ] | S4-07 | 补齐 Tag 规则与数据表的映射关系 | `Assets/GameMain/Scripts/DataTable/`<br>`Assets/GameMain/Scripts/Definition/` | 表字段不是只存在而未被消费,Tag 参数可配置可解释 |
|
| [~] | S4-07 | 补齐 Tag 规则与数据表的映射关系 | `Assets/GameMain/Scripts/DataTable/`<br>`Assets/GameMain/Scripts/Definition/` | 表字段不是只存在而未被消费,Tag 参数可配置可解释 |
|
||||||
|
|
||||||
> 2026-03-09 更新:`InventoryRarityRuleService` 已落地;塔品质计算与组件品质归一化已统一收口,`PlayerInventoryTowerAssemblyService`、`ShopFormUseCase`、`EnemyDropResolver`、`InventorySeedUtility` 已接入同一规则入口。你已确认 Unity Test Runner 中 `Assets/Tests/EditMode` 全部通过,其中包含新增的 `InventoryRarityRuleServiceTests`。
|
> 2026-03-09 更新:`InventoryRarityRuleService` 已落地;塔品质计算与组件品质归一化已统一收口,`PlayerInventoryTowerAssemblyService`、`ShopFormUseCase`、`EnemyDropResolver`、`InventorySeedUtility` 已接入同一规则入口。你已确认 Unity Test Runner 中 `Assets/Tests/EditMode` 全部通过,其中包含新增的 `InventoryRarityRuleServiceTests`。
|
||||||
>
|
>
|
||||||
|
|
@ -143,9 +143,11 @@
|
||||||
>
|
>
|
||||||
> 2026-03-10 更新:`S4-06` 已完成第一段落地。战斗链现在已从旧的 `damage + AttackPropertyType` 入口提升为携带 `TagRuntimeData[]` 的命中载荷;`AttackPayload`、`HitContext`、`TagEffectResolver`、`EnemyTagStatusRuntime` 已接入主命中链。当前已实际生效的基础 Tag 为 `Fire`、`Ice`、`Crit`、`Execution`:其中 `Fire` 已改为独立 DOT 公式,并按 `EnemyEntity` 每帧提供的 `deltaTime` 连续结算;`Ice` 已有独立减速状态;`Crit` 与 `Execution` 已进入命中前数值修正。状态类 Tag 已按“每个 Tag 各自配置文件、状态文件、效果文件”的方式拆开,`EnemyTagStatusRuntime` 只负责按激活 Tag 动态 Tick,不再持有具体 Tag 字段。
|
> 2026-03-10 更新:`S4-06` 已完成第一段落地。战斗链现在已从旧的 `damage + AttackPropertyType` 入口提升为携带 `TagRuntimeData[]` 的命中载荷;`AttackPayload`、`HitContext`、`TagEffectResolver`、`EnemyTagStatusRuntime` 已接入主命中链。当前已实际生效的基础 Tag 为 `Fire`、`Ice`、`Crit`、`Execution`:其中 `Fire` 已改为独立 DOT 公式,并按 `EnemyEntity` 每帧提供的 `deltaTime` 连续结算;`Ice` 已有独立减速状态;`Crit` 与 `Execution` 已进入命中前数值修正。状态类 Tag 已按“每个 Tag 各自配置文件、状态文件、效果文件”的方式拆开,`EnemyTagStatusRuntime` 只负责按激活 Tag 动态 Tick,不再持有具体 Tag 字段。
|
||||||
>
|
>
|
||||||
> 2026-03-10 更新:当前 `S4-06` 仍未整体完成。`Shatter`、`Inferno`、`AbsoluteZero` 仍处于“已注册但 no-op 占位”的状态;`BurnSpread`、`IgniteBurst`、`FreezeMask`、`Pierce`、`Overpenetrate` 仍只有分类与占位路由,没有实际战斗效果。因此当前仓库状态应视为“首批 4 个基础 Tag 已进入战斗,首发 7 个 Tag 尚未收满”,而不是 `S4-06` 全量完成。
|
> 2026-03-10 更新:`S4-06` 已补齐首发剩余 3 个 Tag。`Shatter` 已接入命中前数值修正链,并按“目标已减速”口径增伤;`Inferno` 与 `AbsoluteZero` 已按首发方案作为 `Fire` / `Ice` 的强化 Tag 落地,分别增强 DOT 时长/伤害与减速时长/强度;对应 EditMode 测试已同步补齐。`BurnSpread`、`IgniteBurst`、`FreezeMask`、`Pierce`、`Overpenetrate` 仍保持分类与占位路由,没有实际战斗效果,因此仍属于后续扩展,不计入 `S4-06` 当前完成标准。
|
||||||
>
|
>
|
||||||
> 2026-03-10 更新:`S4-07` 仍未开始正式收口。当前 Tag 参数仍主要承载在代码侧 `TagConfigRegistry` 与各 Tag 配置类中,`Tag.txt` / DataTable 仍只提供基础字典与 `MinRarity` 输入;文档中约定的 `TagRule` 表、触发阶段、权重、效果参数等尚未形成 DataRow 与运行时消费闭环。因此 `S4-07` 继续保持未完成状态。
|
> 2026-03-10 更新:`S4-07` 仍未开始正式收口。当前 Tag 参数仍主要承载在代码侧 `TagConfigRegistry` 与各 Tag 配置类中,`Tag.txt` / DataTable 仍只提供基础字典与 `MinRarity` 输入;文档中约定的 `TagRule` 表、触发阶段、权重、效果参数等尚未形成 DataRow 与运行时消费闭环。因此 `S4-07` 继续保持未完成状态。
|
||||||
|
>
|
||||||
|
> 2026-03-11 更新:`S4-07` 已进入第一阶段实现。当前已新增 `TagConfig.txt` 与 `DRTagConfig`,`ProcedurePreload` 会在加载 `TagConfig` 表后驱动 `TagConfigRegistry.LoadFromRows(...)`,把 `TriggerPhase`、`Description` 以及首发 7 个 Tag 的配置参数从表覆盖到运行时强类型 `TagConfig`。`ItemDescForm` 也已开始消费该配置说明;塔详情会优先使用 `TagRuntimes` 构建 `Ice x2` 这类叠层展示。因此 `S4-07` 不再是“完全未开始”,但当前仍只完成了 `TagConfig` 级别的参数映射与 UI 消费,尚未把文档中的完整 `TagRule`(如权重、生成规则等)全部收口。
|
||||||
|
|
||||||
### S4-01 边界结论
|
### S4-01 边界结论
|
||||||
|
|
||||||
|
|
@ -177,10 +179,11 @@
|
||||||
### S4 当前进度结论(2026-03-10)
|
### S4 当前进度结论(2026-03-10)
|
||||||
|
|
||||||
- `S4-02 ~ S4-05` 已完成,品质、组件实例 Tag 生成、塔级 `TagRuntimeData` 汇总、以及 UI 展示口径都已收口。
|
- `S4-02 ~ S4-05` 已完成,品质、组件实例 Tag 生成、塔级 `TagRuntimeData` 汇总、以及 UI 展示口径都已收口。
|
||||||
- `S4-06` 已完成第一阶段:战斗链已支持 Tag 透传与统一结算,`Fire`、`Ice`、`Crit`、`Execution` 已有可验证效果,状态类 Tag 的内部结构也已从集中字段改为注册式运行时。
|
- `S4-06` 已完成:战斗链已支持 Tag 透传与统一结算,正式首发 7 个 Tag `Fire`、`Ice`、`Crit`、`Execution`、`Shatter`、`Inferno`、`AbsoluteZero` 均已有可验证效果。
|
||||||
- 当前 `S4-06` 的未完成部分集中在首发剩余 3 个 Tag:`Shatter`、`Inferno`、`AbsoluteZero`。它们虽然已进入 `TagType`、配置类、注册表和占位效果类,但还没有实际战斗语义。
|
- `Shatter` 已接入命中前数值修正链;`Inferno`、`AbsoluteZero` 已按“强化现有 `Fire` / `Ice` 状态”的首发口径落地,状态类 Tag 的内部结构继续保持注册式运行时。
|
||||||
- `BurnSpread`、`IgniteBurst`、`FreezeMask`、`Pierce`、`Overpenetrate` 已进入分类与配置骨架,但仍属于后续扩展,不计入当前 `S4` 的完成标准。
|
- `BurnSpread`、`IgniteBurst`、`FreezeMask`、`Pierce`、`Overpenetrate` 已进入分类与配置骨架,但仍属于后续扩展,不计入当前 `S4` 的完成标准。
|
||||||
- `S4-07` 仍未开始正式实现。当前仓库只有“代码内注册表配置”,还没有把 `TagRule` 设计回写成 DataTable / DataRow / 运行时消费链。
|
- `S4-07` 已进入第一阶段实现。当前仓库已具备 `TagConfig.txt -> DRTagConfig -> TagConfigRegistry -> ItemDescForm` 的消费闭环,首发 7 个 Tag 的触发阶段、描述与核心参数已可由表覆盖。
|
||||||
|
- `S4-07` 仍未完成最终收口。当前采用的是 `TagConfig` 表驱动,而不是文档原方案里的完整 `TagRule`;权重、生成规则等字段仍未全部映射回 DataTable / DataRow / 运行时消费链。
|
||||||
|
|
||||||
### S4-06 当前代码状态
|
### S4-06 当前代码状态
|
||||||
|
|
||||||
|
|
@ -193,16 +196,14 @@
|
||||||
- `EnemyTagStatusRuntime` 已不再硬编码 `burn/slow` 字段,而是按激活的状态类 Tag 动态调度 Tick。
|
- `EnemyTagStatusRuntime` 已不再硬编码 `burn/slow` 字段,而是按激活的状态类 Tag 动态调度 Tick。
|
||||||
- `Fire` 已改为独立 DOT 公式:当前使用独立 `BurnDamagePerSecondPerStack`,并按敌人实体每帧提供的 `deltaTime` 连续结算,不再依赖命中伤害或内部 `TickInterval`。
|
- `Fire` 已改为独立 DOT 公式:当前使用独立 `BurnDamagePerSecondPerStack`,并按敌人实体每帧提供的 `deltaTime` 连续结算,不再依赖命中伤害或内部 `TickInterval`。
|
||||||
- `Ice` 仍是独立减速状态,移速倍率通过状态运行时聚合得到。
|
- `Ice` 仍是独立减速状态,移速倍率通过状态运行时聚合得到。
|
||||||
- `Crit`、`Execution` 已通过数值修正链路生效;`Shatter` 仍仅有占位配置与入口,没有实际增伤逻辑。
|
- `Crit`、`Execution`、`Shatter` 已通过数值修正链路生效;`Shatter` 当前按“目标已减速时增伤”收口。
|
||||||
|
- `Inferno` 与 `AbsoluteZero` 已不再是独立状态效果,而是分别在 `Fire` / `Ice` 生效时读取同次命中的强化 Tag 层数,增强 DOT 时长/伤害与减速时长/强度。
|
||||||
|
|
||||||
### S4 后续执行计划
|
### S4 后续执行计划
|
||||||
|
|
||||||
1. 先补完 `S4-06` 的首发缺口,只做正式首发集合剩余的 `Shatter`、`Inferno`、`AbsoluteZero`,不提前展开传播、爆炸、穿透、多命中体系。
|
1. 继续推进 `S4-07`:在现有 `TagConfig` 表驱动基础上,决定是否补成文档中的完整 `TagRule`,或明确将 `TagConfig` 作为当前阶段的等价收口方案。
|
||||||
2. `Shatter` 优先接入现有命中前数值修正链,直接消费“目标已减速”这一当前已存在的状态查询能力。
|
2. `S4-07` 完成标准仍以“表字段被实际消费、参数可解释、运行时与文档口径一致”为准;当前已完成首发 7 个 Tag 的参数和说明配置化,后续重点转向剩余规则字段是否也要配置化。
|
||||||
3. `Inferno` 与 `AbsoluteZero` 优先作为对已有 `Fire` / `Ice` 的强化 Tag 落地,不额外引入第二套状态系统;要求仍沿用当前“单 Tag 配置 + 单 Tag 效果 + 注册式状态运行时”的结构。
|
3. `BurnSpread`、`IgniteBurst`、`FreezeMask`、`Pierce`、`Overpenetrate` 继续留在后续扩展阶段,不因为 `S4-06` 完成而提前进入当前迭代。
|
||||||
4. 首发 7 个 Tag 全部进入战斗并补齐对应 EditMode 测试后,再将 `S4-06` 标记为完成。
|
|
||||||
5. 之后进入 `S4-07`:新增并消费 `TagRule` 表或等价 DataTable 映射,把当前代码内的 Tag 参数逐步迁移为可配置数据,而不是继续堆在 `TagConfigRegistry` 默认值里。
|
|
||||||
6. `S4-07` 完成标准仍以“表字段被实际消费、参数可解释、运行时与文档口径一致”为准,不要求一步到位把全部 12 个 Tag 都配置化。
|
|
||||||
|
|
||||||
## 阶段 S5 - 收口耐久规则
|
## 阶段 S5 - 收口耐久规则
|
||||||
|
|
||||||
|
|
@ -227,7 +228,7 @@
|
||||||
1. 先做 `S1`,把主流程真正收成一条稳定可验收的链。
|
1. 先做 `S1`,把主流程真正收成一条稳定可验收的链。
|
||||||
2. `S2` 已完成口径对齐,`NodeMapForm` 不再作为 M1 阻塞项。
|
2. `S2` 已完成口径对齐,`NodeMapForm` 不再作为 M1 阻塞项。
|
||||||
3. 接下来优先做 `S3`,补齐出战合法性,解决 `P0-10`。
|
3. 接下来优先做 `S3`,补齐出战合法性,解决 `P0-10`。
|
||||||
4. 再做 `S4`,统一品质 / Tag 的规则口径,解决 `P0-11`。其中当前优先级已经收口为:先补完 `S4-06` 的首发 7 Tag,再做 `S4-07` 的数据表映射。
|
4. 再做 `S4`,统一品质 / Tag 的规则口径,解决 `P0-11`。其中当前优先级已经收口为:`S4-06` 已完成,下一步转入 `S4-07` 的数据表映射。
|
||||||
5. 最后做 `S5`,决定耐久是完整收口还是同步缩范围,解决 `P0-12`。
|
5. 最后做 `S5`,决定耐久是完整收口还是同步缩范围,解决 `P0-12`。
|
||||||
6. 全部完成后做 `S6`,补测试并同步文档状态。
|
6. 全部完成后做 `S6`,补测试并同步文档状态。
|
||||||
|
|
||||||
|
|
@ -235,9 +236,8 @@
|
||||||
|
|
||||||
1. `S1` 与 `S2` 已完成口径收口,可直接进入规则侧收尾
|
1. `S1` 与 `S2` 已完成口径收口,可直接进入规则侧收尾
|
||||||
2. `S3-01 ~ S3-04` 已完成,当前可转入 `S4`
|
2. `S3-01 ~ S3-04` 已完成,当前可转入 `S4`
|
||||||
3. 当前先继续完成 `S4-06`,补齐 `Shatter`、`Inferno`、`AbsoluteZero`
|
3. 当前转入 `S4-07`,把 `TagRule` / DataTable 映射真正接进运行时
|
||||||
4. 然后进入 `S4-07`,把 `TagRule` / DataTable 映射真正接进运行时
|
4. 然后补 `S6-01 ~ S6-04`
|
||||||
5. 最后补 `S6-01 ~ S6-04`
|
|
||||||
|
|
||||||
## 备注
|
## 备注
|
||||||
|
|
||||||
|
|
|
||||||
BIN
数据表/Tag.xlsx
BIN
数据表/Tag.xlsx
Binary file not shown.
Loading…
Reference in New Issue