S4-07 process 2
当前 `Tag.txt -> DRTag -> TagGenerationRuleRegistry` 已形成组件 Tag 生成规则闭环: - `InventoryTagRuleService` 不再主要依赖内部 `MinRarity` 硬编码,而是统一消费 `DRTag` 提供的 `MinRarity + Weight` - `ShopFormUseCase`、`EnemyDropResolver`、`InventorySeedUtility` 也已切回这一统一入口。 - 现阶段的职责边界明确为:`Tag.txt` 负责基础字典与生成规则,`TagConfig.txt` 负责触发阶段、描述与效果参数。
This commit is contained in:
parent
9de2e50262
commit
b1b68ebde5
|
|
@ -5,17 +5,17 @@
|
|||
1002 平原1.2 55 TimeElapsed 60
|
||||
1003 平原1.3 55 TimeElapsed 60
|
||||
1004 平原1.4 55 EnemiesCleared
|
||||
1005 平原1.* 0
|
||||
1005 平原1.* 0 None
|
||||
2001 平原2.1 55 TimeElapsed 60
|
||||
2002 平原2.2 55 TimeElapsed 60
|
||||
2003 平原2.3 55 TimeElapsed 60
|
||||
2004 平原2.4 55 EnemiesCleared
|
||||
2005 平原2.* 0
|
||||
2005 平原2.* 0 None
|
||||
3001 平原3.1 55 TimeElapsed 60
|
||||
3002 平原3.2 55 TimeElapsed 60
|
||||
3003 平原3.3 55 TimeElapsed 60
|
||||
3004 平原3.4 55 EnemiesCleared
|
||||
3005 平原3.* 0
|
||||
3005 平原3.* 0 None
|
||||
4001 平原4.1 55 TimeElapsed 60
|
||||
4002 平原4.2 55 TimeElapsed 60
|
||||
4003 平原4.3 55 TimeElapsed 60
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
# Id 列1 Name MinRarity
|
||||
# int string RarityType
|
||||
# Tag编号 策划备注 Tag名 获取最低稀有度
|
||||
1 元素 Fire White
|
||||
2 元素 BurnSpread White
|
||||
3 元素 IgniteBurst Green
|
||||
4 元素 Inferno Purple
|
||||
5 控制 Ice White
|
||||
6 控制 FreezeMask White
|
||||
7 控制 Shatter Green
|
||||
8 控制 AbsoluteZero Purple
|
||||
9 穿透 Pierce White
|
||||
10 穿透 Crit White
|
||||
11 穿透 Overpenetrate Green
|
||||
12 穿透 Execution Purple
|
||||
# Id 列1 Name MinRarity Weight
|
||||
# int string RarityType int
|
||||
# Tag编号 策划备注 Tag名 获取最低稀有度 权重
|
||||
1 元素 Fire White 20
|
||||
2 元素 BurnSpread White 20
|
||||
3 元素 IgniteBurst Green 15
|
||||
4 元素 Inferno Purple 5
|
||||
5 控制 Ice White 1
|
||||
6 控制 FreezeMask White 20
|
||||
7 控制 Shatter Green 15
|
||||
8 控制 AbsoluteZero Purple 1
|
||||
9 穿透 Pierce White 20
|
||||
10 穿透 Crit White 20
|
||||
11 穿透 Overpenetrate Green 15
|
||||
12 穿透 Execution Purple 5
|
||||
|
|
|
|||
|
|
@ -1,51 +1,15 @@
|
|||
# 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\
|
||||
}
|
||||
# Id 列1 TagType TriggerPhase Description Param
|
||||
# int TagType TagTriggerPhase string string
|
||||
# Tag配置编号 策划备注 所属Tag类型 触发阶段 描述 参数Json
|
||||
1 元素 Fire OnAfterHit 持续对敌人造成<color=red>火</color>伤害 {"BurnDurationSeconds":3,"BurnDamagePerSecondPerStack":20,"MaxEffectiveStack":5}
|
||||
2 元素 BurnSpread None 燃烧向邻近敌人传播 {"SpreadRadius":2,"SpreadDamageRate":1}
|
||||
3 元素 IgniteBurst None 燃烧结束或击杀时爆炸 {"BurstRadius":1,"BurstDamageRate":0.5}
|
||||
4 元素 Inferno OnAfterHit 强化燃烧伤害或持续时间 {"BonusBurnDurationSeconds":0.1,"BonusBurnDamagePerSecondPerStack":0.1}
|
||||
5 控制 Ice OnAfterHit 命中附加减速 {"SlowDurationSeconds":2,"SlowRatioPerStack":0.2,"MinMoveSpeedMultiplier":0.4}
|
||||
6 控制 FreezeMask None 冻结积累条 / 冻结面具机制 {"FreezeBuildUpPerStack":0.1}
|
||||
7 控制 Shatter OnBeforeHit 对已减速 / 已冻结目标增伤 {"RequiresSlowedTarget":true,"DamageBonusPerStack":0.1}
|
||||
8 控制 AbsoluteZero OnAfterHit 强化减速,或提高冻结触发速度 {"BonusSlowDurationSeconds":0.1,"BonusSlowRatioPerStack":0.2}
|
||||
9 穿透 Pierce None 子弹贯穿多个目标 {"ExtraPierceCount":2}
|
||||
10 穿透 Crit OnBeforeHit 命中前按概率暴击 {"CritChancePerStack":0.1,"CritDamageMultiplier":1.5}
|
||||
11 穿透 Overpenetrate None 贯穿后保留部分伤害继续飞行 {"ExtraPenetrationCount":0.1,"RemainingDamageRate":0.2}
|
||||
12 穿透 Execution OnBeforeHit 对低血量目标增伤或直接处决 {"TargetHealthThreshold":0.3,"DamageBonusPerStack":0.5}
|
||||
|
|
|
|||
|
|
@ -17,20 +17,16 @@ namespace GeometryTD.CustomComponent
|
|||
|
||||
private readonly List<DROutGameDropPool> _eligibleDropPoolBuffer = new();
|
||||
private readonly Dictionary<RarityType, float> _rarityRollWeightBuffer = new();
|
||||
private readonly Dictionary<TagType, RarityType> _tagMinRarityByTag = new();
|
||||
|
||||
private IDataTable<DROutGameDropPool> _drOutGameDropPool;
|
||||
private IDataTable<DRMuzzleComp> _drMuzzleComp;
|
||||
private IDataTable<DRBearingComp> _drBearingComp;
|
||||
private IDataTable<DRBaseComp> _drBaseComp;
|
||||
private IDataTable<DRTag> _drTag;
|
||||
private long _nextDropItemInstanceId = 1;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_eligibleDropPoolBuffer.Clear();
|
||||
_rarityRollWeightBuffer.Clear();
|
||||
_tagMinRarityByTag.Clear();
|
||||
_nextDropItemInstanceId = 1;
|
||||
}
|
||||
|
||||
|
|
@ -307,35 +303,6 @@ namespace GeometryTD.CustomComponent
|
|||
return _drOutGameDropPool;
|
||||
}
|
||||
|
||||
private bool EnsureTagMinRarityLookup()
|
||||
{
|
||||
if (_tagMinRarityByTag.Count > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
_drTag ??= GameEntry.DataTable.GetDataTable<DRTag>();
|
||||
if (_drTag == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DRTag[] rows = _drTag.GetAllDataRows();
|
||||
for (int i = 0; i < rows.Length; i++)
|
||||
{
|
||||
DRTag row = rows[i];
|
||||
if (row == null || row.Id <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
TagType tagType = (TagType)row.Id;
|
||||
_tagMinRarityByTag[tagType] = row.MinRarity;
|
||||
}
|
||||
|
||||
return _tagMinRarityByTag.Count > 0;
|
||||
}
|
||||
|
||||
private bool TryBuildDropItem(DROutGameDropPool row, out TowerCompItemData droppedItem)
|
||||
{
|
||||
droppedItem = null;
|
||||
|
|
@ -372,11 +339,6 @@ namespace GeometryTD.CustomComponent
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!EnsureTagMinRarityLookup())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DRMuzzleComp config = _drMuzzleComp.GetDataRow(row.ItemId);
|
||||
if (config == null)
|
||||
{
|
||||
|
|
@ -398,8 +360,7 @@ namespace GeometryTD.CustomComponent
|
|||
rarity,
|
||||
InventoryTagSourceType.Drop,
|
||||
instanceId,
|
||||
config.Id,
|
||||
_tagMinRarityByTag),
|
||||
config.Id),
|
||||
AttackDamage = config.AttackDamage != null ? (int[])config.AttackDamage.Clone() : Array.Empty<int>(),
|
||||
DamageRandomRate = config.DamageRandomRate,
|
||||
AttackMethodType = config.AttackMethodType
|
||||
|
|
@ -416,11 +377,6 @@ namespace GeometryTD.CustomComponent
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!EnsureTagMinRarityLookup())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DRBearingComp config = _drBearingComp.GetDataRow(row.ItemId);
|
||||
if (config == null)
|
||||
{
|
||||
|
|
@ -442,8 +398,7 @@ namespace GeometryTD.CustomComponent
|
|||
rarity,
|
||||
InventoryTagSourceType.Drop,
|
||||
instanceId,
|
||||
config.Id,
|
||||
_tagMinRarityByTag),
|
||||
config.Id),
|
||||
RotateSpeed = config.RotateSpeed != null ? (float[])config.RotateSpeed.Clone() : Array.Empty<float>(),
|
||||
AttackRange = config.AttackRange != null ? (float[])config.AttackRange.Clone() : Array.Empty<float>()
|
||||
};
|
||||
|
|
@ -459,11 +414,6 @@ namespace GeometryTD.CustomComponent
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!EnsureTagMinRarityLookup())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DRBaseComp config = _drBaseComp.GetDataRow(row.ItemId);
|
||||
if (config == null)
|
||||
{
|
||||
|
|
@ -485,8 +435,7 @@ namespace GeometryTD.CustomComponent
|
|||
rarity,
|
||||
InventoryTagSourceType.Drop,
|
||||
instanceId,
|
||||
config.Id,
|
||||
_tagMinRarityByTag),
|
||||
config.Id),
|
||||
AttackSpeed = config.AttackSpeed != null ? (float[])config.AttackSpeed.Clone() : Array.Empty<float>(),
|
||||
AttackPropertyType = config.AttackPropertyType
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using UnityGameFramework.Runtime;
|
|||
namespace GeometryTD.DataTable
|
||||
{
|
||||
/// <summary>
|
||||
/// 枪口组件表
|
||||
/// Tag 表
|
||||
/// </summary>
|
||||
public class DRTag : DataRowBase
|
||||
{
|
||||
|
|
@ -22,8 +22,12 @@ namespace GeometryTD.DataTable
|
|||
/// </summary>
|
||||
public string Name { get; private set; }
|
||||
|
||||
public TagType TagType => (TagType)m_Id;
|
||||
|
||||
public RarityType MinRarity { get; private set; }
|
||||
|
||||
public int Weight { get; private set; }
|
||||
|
||||
public override bool ParseDataRow(string dataRowString, object userData)
|
||||
{
|
||||
string[] columnStrings = dataRowString.Split(DataTableExtension.DataSplitSeparators);
|
||||
|
|
@ -38,6 +42,7 @@ namespace GeometryTD.DataTable
|
|||
index++;
|
||||
Name = columnStrings[index++];
|
||||
MinRarity = EnumUtility<RarityType>.Get(columnStrings[index++]);
|
||||
Weight = int.Parse(columnStrings[index++]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum ProcedureMainCombatEntryBlockReason
|
||||
{
|
||||
None = 0,
|
||||
InventoryUnavailable = 1,
|
||||
NoValidParticipantTower = 2
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f6d8af9f1d7d4041bfcee904724303e9
|
||||
timeCreated: 1773198097
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum ProcedureMainFlowPhase
|
||||
{
|
||||
Hub = 0,
|
||||
NodeActive = 1,
|
||||
RunCompletedPendingFinish = 2
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 01450486bd824f47bcd74dda8a160ba4
|
||||
timeCreated: 1773198057
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum ProcedureMainRunAdvanceResult
|
||||
{
|
||||
NoChange = 0,
|
||||
NodeException = 1,
|
||||
AdvancedToNextNode = 2,
|
||||
RunCompleted = 3
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e9e0fa56d7e84874ac0f9d4a400fda13
|
||||
timeCreated: 1773198071
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum ProcedureMainRunCompletionResult
|
||||
{
|
||||
NoChange = 0,
|
||||
ShowCompletionDialog = 1,
|
||||
ReturnToMenu = 2
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e3d0df05f77043a99c463a790b5ac35f
|
||||
timeCreated: 1773198084
|
||||
|
|
@ -1,33 +1,22 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Random = System.Random;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class InventoryTagRuleService
|
||||
{
|
||||
private static readonly IReadOnlyDictionary<TagType, RarityType> DefaultMinRarityByTag =
|
||||
new Dictionary<TagType, RarityType>
|
||||
{
|
||||
{ TagType.Fire, RarityType.White },
|
||||
{ TagType.Ice, RarityType.White },
|
||||
{ TagType.Crit, RarityType.White },
|
||||
{ TagType.Shatter, RarityType.Green },
|
||||
{ TagType.Inferno, RarityType.Purple },
|
||||
{ TagType.AbsoluteZero, RarityType.Purple },
|
||||
{ TagType.Execution, RarityType.Purple },
|
||||
};
|
||||
|
||||
private static readonly HashSet<TagType> SupportedLaunchTags = new HashSet<TagType>(DefaultMinRarityByTag.Keys);
|
||||
|
||||
public static TagType[] ResolveComponentTags(
|
||||
IReadOnlyList<TagType> possibleTags,
|
||||
RarityType rarity,
|
||||
InventoryTagSourceType sourceType,
|
||||
long itemInstanceId,
|
||||
int configId,
|
||||
IReadOnlyDictionary<TagType, RarityType> minRarityByTag = null)
|
||||
IReadOnlyDictionary<TagType, TagGenerationRuleData> rulesByTag = null)
|
||||
{
|
||||
TagType[] eligibleTags = GetEligibleTags(possibleTags, rarity, minRarityByTag);
|
||||
IReadOnlyDictionary<TagType, TagGenerationRuleData> ruleLookup = rulesByTag ?? TagGenerationRuleRegistry.Rules;
|
||||
TagType[] eligibleTags = GetEligibleTags(possibleTags, rarity, ruleLookup);
|
||||
if (eligibleTags.Length <= 0)
|
||||
{
|
||||
return Array.Empty<TagType>();
|
||||
|
|
@ -45,7 +34,7 @@ namespace GeometryTD.Definition
|
|||
TagType[] result = new TagType[finalCount];
|
||||
for (int i = 0; i < finalCount; i++)
|
||||
{
|
||||
int index = random.Next(0, pool.Count);
|
||||
int index = RollWeightedIndex(pool, ruleLookup, random);
|
||||
result[i] = pool[index];
|
||||
pool.RemoveAt(index);
|
||||
}
|
||||
|
|
@ -56,7 +45,7 @@ namespace GeometryTD.Definition
|
|||
public static TagType[] GetEligibleTags(
|
||||
IReadOnlyList<TagType> possibleTags,
|
||||
RarityType rarity,
|
||||
IReadOnlyDictionary<TagType, RarityType> minRarityByTag = null)
|
||||
IReadOnlyDictionary<TagType, TagGenerationRuleData> rulesByTag = null)
|
||||
{
|
||||
if (possibleTags == null || possibleTags.Count <= 0)
|
||||
{
|
||||
|
|
@ -64,23 +53,23 @@ namespace GeometryTD.Definition
|
|||
}
|
||||
|
||||
RarityType normalizedRarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity);
|
||||
IReadOnlyDictionary<TagType, RarityType> rarityLookup = minRarityByTag ?? DefaultMinRarityByTag;
|
||||
IReadOnlyDictionary<TagType, TagGenerationRuleData> ruleLookup = rulesByTag ?? TagGenerationRuleRegistry.Rules;
|
||||
HashSet<TagType> uniqueTags = new HashSet<TagType>();
|
||||
|
||||
for (int i = 0; i < possibleTags.Count; i++)
|
||||
{
|
||||
TagType tagType = possibleTags[i];
|
||||
if (!IsSupportedLaunchTag(tagType) || tagType == TagType.None || !Enum.IsDefined(typeof(TagType), tagType))
|
||||
if (tagType == TagType.None || !Enum.IsDefined(typeof(TagType), tagType) || !IsSupportedLaunchTag(tagType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryGetMinRarity(tagType, rarityLookup, out RarityType minRarity))
|
||||
if (!TryGetRule(tagType, ruleLookup, out TagGenerationRuleData rule))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (normalizedRarity < InventoryRarityRuleService.NormalizeComponentRarity(minRarity))
|
||||
if (normalizedRarity < InventoryRarityRuleService.NormalizeComponentRarity(rule.MinRarity))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
@ -123,28 +112,55 @@ namespace GeometryTD.Definition
|
|||
|
||||
private static bool IsSupportedLaunchTag(TagType tagType)
|
||||
{
|
||||
return SupportedLaunchTags.Contains(tagType);
|
||||
return TagConfigRegistry.TryGetDefinition(tagType, out TagDefinitionData definition) &&
|
||||
definition.Config != null &&
|
||||
definition.Config.IsImplemented;
|
||||
}
|
||||
|
||||
private static bool TryGetMinRarity(
|
||||
private static bool TryGetRule(
|
||||
TagType tagType,
|
||||
IReadOnlyDictionary<TagType, RarityType> rarityLookup,
|
||||
out RarityType minRarity)
|
||||
IReadOnlyDictionary<TagType, TagGenerationRuleData> ruleLookup,
|
||||
out TagGenerationRuleData rule)
|
||||
{
|
||||
if (rarityLookup != null && rarityLookup.TryGetValue(tagType, out minRarity))
|
||||
if (ruleLookup != null && ruleLookup.TryGetValue(tagType, out rule))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (DefaultMinRarityByTag.TryGetValue(tagType, out minRarity))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
minRarity = RarityType.White;
|
||||
rule = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int RollWeightedIndex(
|
||||
IReadOnlyList<TagType> pool,
|
||||
IReadOnlyDictionary<TagType, TagGenerationRuleData> ruleLookup,
|
||||
Random random)
|
||||
{
|
||||
int totalWeight = 0;
|
||||
for (int i = 0; i < pool.Count; i++)
|
||||
{
|
||||
TagGenerationRuleData rule = ruleLookup[pool[i]];
|
||||
Debug.Assert(rule.Weight > 0);
|
||||
totalWeight += rule.Weight;
|
||||
}
|
||||
|
||||
Debug.Assert(totalWeight > 0);
|
||||
|
||||
int roll = random.Next(1, totalWeight + 1);
|
||||
int cumulative = 0;
|
||||
for (int i = 0; i < pool.Count; i++)
|
||||
{
|
||||
TagGenerationRuleData rule = ruleLookup[pool[i]];
|
||||
cumulative += rule.Weight;
|
||||
if (roll <= cumulative)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return pool.Count - 1;
|
||||
}
|
||||
|
||||
private static int BuildStableSeed(
|
||||
RarityType rarity,
|
||||
InventoryTagSourceType sourceType,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
namespace GeometryTD.Definition
|
||||
{
|
||||
public sealed class TagGenerationRuleData
|
||||
{
|
||||
public TagType TagType { get; set; }
|
||||
public RarityType MinRarity { get; set; }
|
||||
public int Weight { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cfe508e818ce47649a112ecdf86cf26a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
using System.Collections.Generic;
|
||||
using GeometryTD.DataTable;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class TagGenerationRuleRegistry
|
||||
{
|
||||
private static readonly Dictionary<TagType, TagGenerationRuleData> RulesByTag = CreateDefaultRules();
|
||||
|
||||
public static IReadOnlyDictionary<TagType, TagGenerationRuleData> Rules => RulesByTag;
|
||||
|
||||
public static void ResetToDefaults()
|
||||
{
|
||||
RulesByTag.Clear();
|
||||
foreach (KeyValuePair<TagType, TagGenerationRuleData> pair in CreateDefaultRules())
|
||||
{
|
||||
RulesByTag.Add(pair.Key, pair.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadFromRows(IEnumerable<DRTag> rows)
|
||||
{
|
||||
ResetToDefaults();
|
||||
foreach (DRTag row in rows)
|
||||
{
|
||||
ApplyRow(row);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryGetRule(TagType tagType, out TagGenerationRuleData rule)
|
||||
{
|
||||
return RulesByTag.TryGetValue(tagType, out rule);
|
||||
}
|
||||
|
||||
private static Dictionary<TagType, TagGenerationRuleData> CreateDefaultRules()
|
||||
{
|
||||
return new Dictionary<TagType, TagGenerationRuleData>
|
||||
{
|
||||
[TagType.Fire] = CreateRule(TagType.Fire, RarityType.White, 20),
|
||||
[TagType.BurnSpread] = CreateRule(TagType.BurnSpread, RarityType.White, 20),
|
||||
[TagType.IgniteBurst] = CreateRule(TagType.IgniteBurst, RarityType.Green, 15),
|
||||
[TagType.Inferno] = CreateRule(TagType.Inferno, RarityType.Purple, 5),
|
||||
[TagType.Ice] = CreateRule(TagType.Ice, RarityType.White, 20),
|
||||
[TagType.FreezeMask] = CreateRule(TagType.FreezeMask, RarityType.White, 20),
|
||||
[TagType.Shatter] = CreateRule(TagType.Shatter, RarityType.Green, 15),
|
||||
[TagType.AbsoluteZero] = CreateRule(TagType.AbsoluteZero, RarityType.Purple, 5),
|
||||
[TagType.Pierce] = CreateRule(TagType.Pierce, RarityType.White, 20),
|
||||
[TagType.Crit] = CreateRule(TagType.Crit, RarityType.White, 20),
|
||||
[TagType.Overpenetrate] = CreateRule(TagType.Overpenetrate, RarityType.Green, 15),
|
||||
[TagType.Execution] = CreateRule(TagType.Execution, RarityType.Purple, 5)
|
||||
};
|
||||
}
|
||||
|
||||
private static TagGenerationRuleData CreateRule(TagType tagType, RarityType minRarity, int weight)
|
||||
{
|
||||
return new TagGenerationRuleData
|
||||
{
|
||||
TagType = tagType,
|
||||
MinRarity = minRarity,
|
||||
Weight = weight
|
||||
};
|
||||
}
|
||||
|
||||
private static void ApplyRow(DRTag row)
|
||||
{
|
||||
Debug.Assert(row != null);
|
||||
Debug.Assert(row.Id > 0);
|
||||
Debug.Assert(row.Weight > 0);
|
||||
|
||||
RulesByTag[row.TagType] = CreateRule(row.TagType, row.MinRarity, row.Weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2e72e26721274985ba2d44ca825d7f75
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -204,6 +204,15 @@ namespace GeometryTD.Procedure
|
|||
TagConfigRegistry.LoadFromRows(tagConfigTable.GetAllDataRows());
|
||||
}
|
||||
}
|
||||
|
||||
if (ne.DataTableAssetName == AssetUtility.GetDataTableAsset("Tag", false))
|
||||
{
|
||||
var tagTable = GameEntry.DataTable.GetDataTable<DRTag>();
|
||||
if (tagTable != null)
|
||||
{
|
||||
TagGenerationRuleRegistry.LoadFromRows(tagTable.GetAllDataRows());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLoadDataTableFailure(object sender, GameEventArgs e)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f18d07456c02b1546b198ed25ba69e18
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
using System.Text;
|
||||
using GameFramework.Event;
|
||||
using GameFramework.Fsm;
|
||||
using GameFramework.Procedure;
|
||||
|
|
@ -9,243 +8,6 @@ using UnityGameFramework.Runtime;
|
|||
|
||||
namespace GeometryTD.Procedure
|
||||
{
|
||||
public enum ProcedureMainFlowPhase
|
||||
{
|
||||
Hub = 0,
|
||||
NodeActive = 1,
|
||||
RunCompletedPendingFinish = 2
|
||||
}
|
||||
|
||||
public enum ProcedureMainRunAdvanceResult
|
||||
{
|
||||
NoChange = 0,
|
||||
NodeException = 1,
|
||||
AdvancedToNextNode = 2,
|
||||
RunCompleted = 3
|
||||
}
|
||||
|
||||
public enum ProcedureMainRunCompletionResult
|
||||
{
|
||||
NoChange = 0,
|
||||
ShowCompletionDialog = 1,
|
||||
ReturnToMenu = 2
|
||||
}
|
||||
|
||||
public enum ProcedureMainCombatEntryBlockReason
|
||||
{
|
||||
None = 0,
|
||||
InventoryUnavailable = 1,
|
||||
NoValidParticipantTower = 2
|
||||
}
|
||||
|
||||
public sealed class ProcedureMainCombatEntryValidationResult
|
||||
{
|
||||
public bool CanEnterCombat => BlockReason == ProcedureMainCombatEntryBlockReason.None;
|
||||
|
||||
public ProcedureMainCombatEntryBlockReason BlockReason { get; set; }
|
||||
|
||||
public CombatParticipantTowerValidationSummary ValidationSummary { get; set; }
|
||||
}
|
||||
|
||||
public static class ProcedureMainRunFlowService
|
||||
{
|
||||
public static ProcedureMainRunAdvanceResult TryAdvanceRun(
|
||||
RunState runState,
|
||||
RunNodeCompletionStatus completionStatus,
|
||||
BackpackInventoryData snapshot)
|
||||
{
|
||||
if (!RunStateAdvanceService.TryCompleteCurrentNode(runState, completionStatus, snapshot))
|
||||
{
|
||||
return ProcedureMainRunAdvanceResult.NoChange;
|
||||
}
|
||||
|
||||
if (runState != null && runState.IsCompleted)
|
||||
{
|
||||
return ProcedureMainRunAdvanceResult.RunCompleted;
|
||||
}
|
||||
|
||||
return completionStatus == RunNodeCompletionStatus.Exception
|
||||
? ProcedureMainRunAdvanceResult.NodeException
|
||||
: ProcedureMainRunAdvanceResult.AdvancedToNextNode;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ProcedureMainRunCompletionService
|
||||
{
|
||||
public static ProcedureMainRunCompletionResult TryEnterCompletedPendingFinish(bool isCompletionDialogShown)
|
||||
{
|
||||
return isCompletionDialogShown
|
||||
? ProcedureMainRunCompletionResult.NoChange
|
||||
: ProcedureMainRunCompletionResult.ShowCompletionDialog;
|
||||
}
|
||||
|
||||
public static ProcedureMainRunCompletionResult TryConfirmReturnToMenu(
|
||||
ProcedureMainFlowPhase flowPhase,
|
||||
bool isReturnToMenuPending)
|
||||
{
|
||||
if (flowPhase != ProcedureMainFlowPhase.RunCompletedPendingFinish || isReturnToMenuPending)
|
||||
{
|
||||
return ProcedureMainRunCompletionResult.NoChange;
|
||||
}
|
||||
|
||||
return ProcedureMainRunCompletionResult.ReturnToMenu;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ProcedureMainNodeEventGuardService
|
||||
{
|
||||
public static bool MatchesCurrentNode(
|
||||
RunState runState,
|
||||
int nodeId,
|
||||
RunNodeType nodeType,
|
||||
int sequenceIndex)
|
||||
{
|
||||
RunNodeState currentNode = runState?.CurrentNode;
|
||||
if (currentNode == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return (nodeId <= 0 || nodeId == currentNode.NodeId) &&
|
||||
(nodeType == RunNodeType.None || nodeType == currentNode.NodeType) &&
|
||||
(sequenceIndex < 0 || sequenceIndex == currentNode.SequenceIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ProcedureMainCombatEntryValidationService
|
||||
{
|
||||
public static ProcedureMainCombatEntryValidationResult Validate(BackpackInventoryData inventory)
|
||||
{
|
||||
if (inventory == null)
|
||||
{
|
||||
return new ProcedureMainCombatEntryValidationResult
|
||||
{
|
||||
BlockReason = ProcedureMainCombatEntryBlockReason.InventoryUnavailable,
|
||||
ValidationSummary = new CombatParticipantTowerValidationSummary()
|
||||
};
|
||||
}
|
||||
|
||||
CombatParticipantTowerValidationSummary summary =
|
||||
CombatParticipantTowerValidationService.ValidateParticipantTowers(inventory);
|
||||
|
||||
return new ProcedureMainCombatEntryValidationResult
|
||||
{
|
||||
BlockReason = summary.HasAnyValidParticipantTower
|
||||
? ProcedureMainCombatEntryBlockReason.None
|
||||
: ProcedureMainCombatEntryBlockReason.NoValidParticipantTower,
|
||||
ValidationSummary = summary
|
||||
};
|
||||
}
|
||||
|
||||
public static string BuildInvalidParticipantTowerLog(
|
||||
CombatParticipantTowerValidationSummary summary)
|
||||
{
|
||||
if (summary?.InvalidResults == null || summary.InvalidResults.Count <= 0)
|
||||
{
|
||||
return "none";
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0; i < summary.InvalidResults.Count; i++)
|
||||
{
|
||||
CombatParticipantTowerValidationResult result = summary.InvalidResults[i];
|
||||
if (result == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (builder.Length > 0)
|
||||
{
|
||||
builder.Append(", ");
|
||||
}
|
||||
|
||||
builder.Append('#');
|
||||
builder.Append(result.TowerInstanceId);
|
||||
builder.Append(':');
|
||||
builder.Append(result.FailureReason);
|
||||
}
|
||||
|
||||
return builder.Length > 0 ? builder.ToString() : "none";
|
||||
}
|
||||
|
||||
public static DialogFormRawData BuildBlockedCombatDialogRawData(
|
||||
ProcedureMainCombatEntryValidationResult validationResult)
|
||||
{
|
||||
return new DialogFormRawData
|
||||
{
|
||||
Mode = 1,
|
||||
Title = "无法进入战斗",
|
||||
Message = BuildBlockedCombatDialogMessage(validationResult),
|
||||
PauseGame = false,
|
||||
ConfirmText = "知道了"
|
||||
};
|
||||
}
|
||||
|
||||
private static string BuildBlockedCombatDialogMessage(
|
||||
ProcedureMainCombatEntryValidationResult validationResult)
|
||||
{
|
||||
if (validationResult == null)
|
||||
{
|
||||
return "当前无法确认出战信息,请稍后重试。";
|
||||
}
|
||||
|
||||
switch (validationResult.BlockReason)
|
||||
{
|
||||
case ProcedureMainCombatEntryBlockReason.InventoryUnavailable:
|
||||
return "当前无法读取库存快照,暂时不能进入战斗。请重新进入本轮流程后再试。";
|
||||
case ProcedureMainCombatEntryBlockReason.NoValidParticipantTower:
|
||||
return BuildNoValidParticipantTowerMessage(validationResult.ValidationSummary);
|
||||
default:
|
||||
return "当前出战校验未通过,暂时不能进入战斗。";
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildNoValidParticipantTowerMessage(
|
||||
CombatParticipantTowerValidationSummary summary)
|
||||
{
|
||||
if (summary?.InvalidResults == null || summary.InvalidResults.Count <= 0)
|
||||
{
|
||||
return "参战区至少需要 1 座完整装配了枪口、轴承、底座的塔,才能进入战斗。";
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.Append("参战区没有可出战的完整塔。");
|
||||
for (int i = 0; i < summary.InvalidResults.Count; i++)
|
||||
{
|
||||
CombatParticipantTowerValidationResult result = summary.InvalidResults[i];
|
||||
if (result == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.Append('\n');
|
||||
builder.Append("塔 #");
|
||||
builder.Append(result.TowerInstanceId);
|
||||
builder.Append(' ');
|
||||
builder.Append(GetFailureReasonMessage(result.FailureReason));
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static string GetFailureReasonMessage(CombatParticipantTowerValidationFailureReason failureReason)
|
||||
{
|
||||
switch (failureReason)
|
||||
{
|
||||
case CombatParticipantTowerValidationFailureReason.TowerMissing:
|
||||
return "已不存在,无法参战。";
|
||||
case CombatParticipantTowerValidationFailureReason.MissingMuzzleComponent:
|
||||
return "缺少枪口组件。";
|
||||
case CombatParticipantTowerValidationFailureReason.MissingBearingComponent:
|
||||
return "缺少轴承组件。";
|
||||
case CombatParticipantTowerValidationFailureReason.MissingBaseComponent:
|
||||
return "缺少底座组件。";
|
||||
default:
|
||||
return "不满足当前参战条件。";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ProcedureMain : ProcedureBase
|
||||
{
|
||||
public override bool UseNativeDialog => false;
|
||||
|
|
@ -467,7 +229,9 @@ namespace GeometryTD.Procedure
|
|||
case RunNodeType.BossCombat:
|
||||
ProcedureMainCombatEntryValidationResult validationResult =
|
||||
ProcedureMainCombatEntryValidationService.Validate(
|
||||
GameEntry.PlayerInventory != null ? GameEntry.PlayerInventory.GetInventorySnapshot() : null);
|
||||
GameEntry.PlayerInventory != null
|
||||
? GameEntry.PlayerInventory.GetInventorySnapshot()
|
||||
: null);
|
||||
if (!validationResult.CanEnterCombat)
|
||||
{
|
||||
LogCombatEntryBlocked(currentNode, validationResult);
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
using GeometryTD.Definition;
|
||||
|
||||
namespace GeometryTD.Procedure
|
||||
{
|
||||
public sealed class ProcedureMainCombatEntryValidationResult
|
||||
{
|
||||
public bool CanEnterCombat => BlockReason == ProcedureMainCombatEntryBlockReason.None;
|
||||
|
||||
public ProcedureMainCombatEntryBlockReason BlockReason { get; set; }
|
||||
|
||||
public CombatParticipantTowerValidationSummary ValidationSummary { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5699aa9933294974875bfce1d34d1d4b
|
||||
timeCreated: 1773198121
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
using System.Text;
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.UI;
|
||||
|
||||
namespace GeometryTD.Procedure
|
||||
{
|
||||
public static class ProcedureMainCombatEntryValidationService
|
||||
{
|
||||
public static ProcedureMainCombatEntryValidationResult Validate(BackpackInventoryData inventory)
|
||||
{
|
||||
if (inventory == null)
|
||||
{
|
||||
return new ProcedureMainCombatEntryValidationResult
|
||||
{
|
||||
BlockReason = ProcedureMainCombatEntryBlockReason.InventoryUnavailable,
|
||||
ValidationSummary = new CombatParticipantTowerValidationSummary()
|
||||
};
|
||||
}
|
||||
|
||||
CombatParticipantTowerValidationSummary summary =
|
||||
CombatParticipantTowerValidationService.ValidateParticipantTowers(inventory);
|
||||
|
||||
return new ProcedureMainCombatEntryValidationResult
|
||||
{
|
||||
BlockReason = summary.HasAnyValidParticipantTower
|
||||
? ProcedureMainCombatEntryBlockReason.None
|
||||
: ProcedureMainCombatEntryBlockReason.NoValidParticipantTower,
|
||||
ValidationSummary = summary
|
||||
};
|
||||
}
|
||||
|
||||
public static string BuildInvalidParticipantTowerLog(
|
||||
CombatParticipantTowerValidationSummary summary)
|
||||
{
|
||||
if (summary?.InvalidResults == null || summary.InvalidResults.Count <= 0)
|
||||
{
|
||||
return "none";
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0; i < summary.InvalidResults.Count; i++)
|
||||
{
|
||||
CombatParticipantTowerValidationResult result = summary.InvalidResults[i];
|
||||
if (result == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (builder.Length > 0)
|
||||
{
|
||||
builder.Append(", ");
|
||||
}
|
||||
|
||||
builder.Append('#');
|
||||
builder.Append(result.TowerInstanceId);
|
||||
builder.Append(':');
|
||||
builder.Append(result.FailureReason);
|
||||
}
|
||||
|
||||
return builder.Length > 0 ? builder.ToString() : "none";
|
||||
}
|
||||
|
||||
public static DialogFormRawData BuildBlockedCombatDialogRawData(
|
||||
ProcedureMainCombatEntryValidationResult validationResult)
|
||||
{
|
||||
return new DialogFormRawData
|
||||
{
|
||||
Mode = 1,
|
||||
Title = "无法进入战斗",
|
||||
Message = BuildBlockedCombatDialogMessage(validationResult),
|
||||
PauseGame = false,
|
||||
ConfirmText = "知道了"
|
||||
};
|
||||
}
|
||||
|
||||
private static string BuildBlockedCombatDialogMessage(
|
||||
ProcedureMainCombatEntryValidationResult validationResult)
|
||||
{
|
||||
if (validationResult == null)
|
||||
{
|
||||
return "当前无法确认出战信息,请稍后重试。";
|
||||
}
|
||||
|
||||
switch (validationResult.BlockReason)
|
||||
{
|
||||
case ProcedureMainCombatEntryBlockReason.InventoryUnavailable:
|
||||
return "当前无法读取库存快照,暂时不能进入战斗。请重新进入本轮流程后再试。";
|
||||
case ProcedureMainCombatEntryBlockReason.NoValidParticipantTower:
|
||||
return BuildNoValidParticipantTowerMessage(validationResult.ValidationSummary);
|
||||
default:
|
||||
return "当前出战校验未通过,暂时不能进入战斗。";
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildNoValidParticipantTowerMessage(
|
||||
CombatParticipantTowerValidationSummary summary)
|
||||
{
|
||||
if (summary?.InvalidResults == null || summary.InvalidResults.Count <= 0)
|
||||
{
|
||||
return "参战区至少需要 1 座完整装配了枪口、轴承、底座的塔,才能进入战斗。";
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.Append("参战区没有可出战的完整塔。");
|
||||
for (int i = 0; i < summary.InvalidResults.Count; i++)
|
||||
{
|
||||
CombatParticipantTowerValidationResult result = summary.InvalidResults[i];
|
||||
if (result == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.Append('\n');
|
||||
builder.Append("塔 #");
|
||||
builder.Append(result.TowerInstanceId);
|
||||
builder.Append(' ');
|
||||
builder.Append(GetFailureReasonMessage(result.FailureReason));
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static string GetFailureReasonMessage(CombatParticipantTowerValidationFailureReason failureReason)
|
||||
{
|
||||
switch (failureReason)
|
||||
{
|
||||
case CombatParticipantTowerValidationFailureReason.TowerMissing:
|
||||
return "已不存在,无法参战。";
|
||||
case CombatParticipantTowerValidationFailureReason.MissingMuzzleComponent:
|
||||
return "缺少枪口组件。";
|
||||
case CombatParticipantTowerValidationFailureReason.MissingBearingComponent:
|
||||
return "缺少轴承组件。";
|
||||
case CombatParticipantTowerValidationFailureReason.MissingBaseComponent:
|
||||
return "缺少底座组件。";
|
||||
default:
|
||||
return "不满足当前参战条件。";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: be3e90d1a1874e26af6d1555c301aa26
|
||||
timeCreated: 1773198334
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
namespace GeometryTD.Procedure
|
||||
{
|
||||
public static class ProcedureMainNodeEventGuardService
|
||||
{
|
||||
public static bool MatchesCurrentNode(
|
||||
RunState runState,
|
||||
int nodeId,
|
||||
RunNodeType nodeType,
|
||||
int sequenceIndex)
|
||||
{
|
||||
RunNodeState currentNode = runState?.CurrentNode;
|
||||
if (currentNode == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return (nodeId <= 0 || nodeId == currentNode.NodeId) &&
|
||||
(nodeType == RunNodeType.None || nodeType == currentNode.NodeType) &&
|
||||
(sequenceIndex < 0 || sequenceIndex == currentNode.SequenceIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 212a0e12be2248bf9415a779414312a1
|
||||
timeCreated: 1773198194
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
using GeometryTD.Definition;
|
||||
|
||||
namespace GeometryTD.Procedure
|
||||
{
|
||||
public static class ProcedureMainRunCompletionService
|
||||
{
|
||||
public static ProcedureMainRunCompletionResult TryEnterCompletedPendingFinish(bool isCompletionDialogShown)
|
||||
{
|
||||
return isCompletionDialogShown
|
||||
? ProcedureMainRunCompletionResult.NoChange
|
||||
: ProcedureMainRunCompletionResult.ShowCompletionDialog;
|
||||
}
|
||||
|
||||
public static ProcedureMainRunCompletionResult TryConfirmReturnToMenu(
|
||||
ProcedureMainFlowPhase flowPhase,
|
||||
bool isReturnToMenuPending)
|
||||
{
|
||||
if (flowPhase != ProcedureMainFlowPhase.RunCompletedPendingFinish || isReturnToMenuPending)
|
||||
{
|
||||
return ProcedureMainRunCompletionResult.NoChange;
|
||||
}
|
||||
|
||||
return ProcedureMainRunCompletionResult.ReturnToMenu;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8d565626cc594843989da76a623a1698
|
||||
timeCreated: 1773198165
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
using GeometryTD.Definition;
|
||||
|
||||
namespace GeometryTD.Procedure
|
||||
{
|
||||
public static class ProcedureMainRunFlowService
|
||||
{
|
||||
public static ProcedureMainRunAdvanceResult TryAdvanceRun(
|
||||
RunState runState,
|
||||
RunNodeCompletionStatus completionStatus,
|
||||
BackpackInventoryData snapshot)
|
||||
{
|
||||
if (!RunStateAdvanceService.TryCompleteCurrentNode(runState, completionStatus, snapshot))
|
||||
{
|
||||
return ProcedureMainRunAdvanceResult.NoChange;
|
||||
}
|
||||
|
||||
if (runState != null && runState.IsCompleted)
|
||||
{
|
||||
return ProcedureMainRunAdvanceResult.RunCompleted;
|
||||
}
|
||||
|
||||
return completionStatus == RunNodeCompletionStatus.Exception
|
||||
? ProcedureMainRunAdvanceResult.NodeException
|
||||
: ProcedureMainRunAdvanceResult.AdvancedToNextNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f62dcb2d97fe4f0c884e6eab3d500edf
|
||||
timeCreated: 1773198145
|
||||
|
|
@ -16,13 +16,10 @@ namespace GeometryTD.UI
|
|||
|
||||
private readonly List<GoodsItemRawData> _currentGoods = new List<GoodsItemRawData>(GoodsCount);
|
||||
private readonly List<DRShopPrice> _shopPriceRows = new List<DRShopPrice>();
|
||||
private readonly Dictionary<TagType, RarityType> _tagMinRarityByTag = new Dictionary<TagType, RarityType>();
|
||||
|
||||
private IDataTable<DRShopPrice> _shopPriceTable;
|
||||
private IDataTable<DRMuzzleComp> _muzzleCompTable;
|
||||
private IDataTable<DRBearingComp> _bearingCompTable;
|
||||
private IDataTable<DRBaseComp> _baseCompTable;
|
||||
private IDataTable<DRTag> _tagTable;
|
||||
|
||||
public bool PrepareForOpen()
|
||||
{
|
||||
|
|
@ -101,9 +98,8 @@ namespace GeometryTD.UI
|
|||
_muzzleCompTable ??= GameEntry.DataTable.GetDataTable<DRMuzzleComp>();
|
||||
_bearingCompTable ??= GameEntry.DataTable.GetDataTable<DRBearingComp>();
|
||||
_baseCompTable ??= GameEntry.DataTable.GetDataTable<DRBaseComp>();
|
||||
_tagTable ??= GameEntry.DataTable.GetDataTable<DRTag>();
|
||||
|
||||
if (_shopPriceTable == null || _muzzleCompTable == null || _bearingCompTable == null || _baseCompTable == null || _tagTable == null)
|
||||
if (_shopPriceTable == null || _muzzleCompTable == null || _bearingCompTable == null || _baseCompTable == null)
|
||||
{
|
||||
Log.Warning("ShopFormUseCase.EnsureTables() failed. Missing required data tables.");
|
||||
return false;
|
||||
|
|
@ -127,13 +123,10 @@ namespace GeometryTD.UI
|
|||
}
|
||||
}
|
||||
|
||||
EnsureTagMinRarityLookup();
|
||||
|
||||
return _shopPriceRows.Count > 0 &&
|
||||
_muzzleCompTable.Count > 0 &&
|
||||
_bearingCompTable.Count > 0 &&
|
||||
_baseCompTable.Count > 0 &&
|
||||
_tagMinRarityByTag.Count > 0;
|
||||
_baseCompTable.Count > 0;
|
||||
}
|
||||
|
||||
private bool TryBuildRandomGoodsItem(int goodsIndex, out GoodsItemRawData goodsItem)
|
||||
|
|
@ -197,8 +190,7 @@ namespace GeometryTD.UI
|
|||
normalizedRarity,
|
||||
InventoryTagSourceType.Shop,
|
||||
instanceId,
|
||||
config.Id,
|
||||
_tagMinRarityByTag),
|
||||
config.Id),
|
||||
AttackDamage = config.AttackDamage != null ? (int[])config.AttackDamage.Clone() : Array.Empty<int>(),
|
||||
DamageRandomRate = config.DamageRandomRate,
|
||||
AttackMethodType = config.AttackMethodType
|
||||
|
|
@ -224,8 +216,7 @@ namespace GeometryTD.UI
|
|||
normalizedRarity,
|
||||
InventoryTagSourceType.Shop,
|
||||
instanceId,
|
||||
config.Id,
|
||||
_tagMinRarityByTag),
|
||||
config.Id),
|
||||
RotateSpeed = config.RotateSpeed != null ? (float[])config.RotateSpeed.Clone() : Array.Empty<float>(),
|
||||
AttackRange = config.AttackRange != null ? (float[])config.AttackRange.Clone() : Array.Empty<float>()
|
||||
};
|
||||
|
|
@ -250,34 +241,12 @@ namespace GeometryTD.UI
|
|||
normalizedRarity,
|
||||
InventoryTagSourceType.Shop,
|
||||
instanceId,
|
||||
config.Id,
|
||||
_tagMinRarityByTag),
|
||||
config.Id),
|
||||
AttackSpeed = config.AttackSpeed != null ? (float[])config.AttackSpeed.Clone() : Array.Empty<float>(),
|
||||
AttackPropertyType = config.AttackPropertyType
|
||||
};
|
||||
}
|
||||
|
||||
private void EnsureTagMinRarityLookup()
|
||||
{
|
||||
if (_tagMinRarityByTag.Count > 0 || _tagTable == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DRTag[] rows = _tagTable.GetAllDataRows();
|
||||
for (int i = 0; i < rows.Length; i++)
|
||||
{
|
||||
DRTag row = rows[i];
|
||||
if (row == null || row.Id <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
TagType tagType = (TagType)row.Id;
|
||||
_tagMinRarityByTag[tagType] = row.MinRarity;
|
||||
}
|
||||
}
|
||||
|
||||
private int ResolveRandomPrice(RarityType rarity)
|
||||
{
|
||||
for (int i = 0; i < _shopPriceRows.Count; i++)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using GeometryTD.CustomUtility;
|
||||
using GeometryTD.DataTable;
|
||||
using GeometryTD.Definition;
|
||||
using NUnit.Framework;
|
||||
|
||||
|
|
@ -7,16 +8,16 @@ namespace GeometryTD.Tests.EditMode
|
|||
{
|
||||
public sealed class InventoryTagRuleServiceTests
|
||||
{
|
||||
private static readonly IReadOnlyDictionary<TagType, RarityType> MinRarityByTag =
|
||||
new Dictionary<TagType, RarityType>
|
||||
private static readonly IReadOnlyDictionary<TagType, TagGenerationRuleData> RulesByTag =
|
||||
new Dictionary<TagType, TagGenerationRuleData>
|
||||
{
|
||||
{ TagType.Fire, RarityType.White },
|
||||
{ TagType.Ice, RarityType.White },
|
||||
{ TagType.Crit, RarityType.White },
|
||||
{ TagType.Shatter, RarityType.Green },
|
||||
{ TagType.Inferno, RarityType.Purple },
|
||||
{ TagType.AbsoluteZero, RarityType.Purple },
|
||||
{ TagType.Execution, RarityType.Purple },
|
||||
{ TagType.Fire, new TagGenerationRuleData { TagType = TagType.Fire, MinRarity = RarityType.White, Weight = 20 } },
|
||||
{ TagType.Ice, new TagGenerationRuleData { TagType = TagType.Ice, MinRarity = RarityType.White, Weight = 20 } },
|
||||
{ TagType.Crit, new TagGenerationRuleData { TagType = TagType.Crit, MinRarity = RarityType.White, Weight = 20 } },
|
||||
{ TagType.Shatter, new TagGenerationRuleData { TagType = TagType.Shatter, MinRarity = RarityType.Green, Weight = 15 } },
|
||||
{ TagType.Inferno, new TagGenerationRuleData { TagType = TagType.Inferno, MinRarity = RarityType.Purple, Weight = 5 } },
|
||||
{ TagType.AbsoluteZero, new TagGenerationRuleData { TagType = TagType.AbsoluteZero, MinRarity = RarityType.Purple, Weight = 5 } },
|
||||
{ TagType.Execution, new TagGenerationRuleData { TagType = TagType.Execution, MinRarity = RarityType.Purple, Weight = 5 } },
|
||||
};
|
||||
|
||||
[Test]
|
||||
|
|
@ -33,7 +34,7 @@ namespace GeometryTD.Tests.EditMode
|
|||
(TagType)99
|
||||
},
|
||||
RarityType.Green,
|
||||
MinRarityByTag);
|
||||
RulesByTag);
|
||||
|
||||
Assert.That(result, Is.EqualTo(new[] { TagType.Fire }));
|
||||
}
|
||||
|
|
@ -47,7 +48,7 @@ namespace GeometryTD.Tests.EditMode
|
|||
InventoryTagSourceType.Shop,
|
||||
12345,
|
||||
7,
|
||||
MinRarityByTag);
|
||||
RulesByTag);
|
||||
|
||||
TagType[] second = InventoryTagRuleService.ResolveComponentTags(
|
||||
new[] { TagType.Fire, TagType.Ice, TagType.Crit, TagType.Shatter },
|
||||
|
|
@ -55,7 +56,7 @@ namespace GeometryTD.Tests.EditMode
|
|||
InventoryTagSourceType.Shop,
|
||||
12345,
|
||||
7,
|
||||
MinRarityByTag);
|
||||
RulesByTag);
|
||||
|
||||
Assert.That(second, Is.EqualTo(first));
|
||||
}
|
||||
|
|
@ -69,7 +70,7 @@ namespace GeometryTD.Tests.EditMode
|
|||
InventoryTagSourceType.Drop,
|
||||
9001,
|
||||
4,
|
||||
MinRarityByTag);
|
||||
RulesByTag);
|
||||
|
||||
Assert.That(result.Length, Is.EqualTo(2));
|
||||
Assert.That(new HashSet<TagType>(result).Count, Is.EqualTo(result.Length));
|
||||
|
|
@ -84,11 +85,82 @@ namespace GeometryTD.Tests.EditMode
|
|||
InventoryTagSourceType.Seed,
|
||||
42,
|
||||
1,
|
||||
MinRarityByTag);
|
||||
RulesByTag);
|
||||
|
||||
Assert.That(result, Is.EqualTo(new[] { TagType.Fire }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ResolveComponentTags_Uses_Weight_Within_EligiblePool()
|
||||
{
|
||||
IReadOnlyDictionary<TagType, TagGenerationRuleData> weightedRules =
|
||||
new Dictionary<TagType, TagGenerationRuleData>
|
||||
{
|
||||
{ TagType.Fire, new TagGenerationRuleData { TagType = TagType.Fire, MinRarity = RarityType.White, Weight = 100 } },
|
||||
{ TagType.Ice, new TagGenerationRuleData { TagType = TagType.Ice, MinRarity = RarityType.White, Weight = 1 } },
|
||||
{ TagType.Crit, new TagGenerationRuleData { TagType = TagType.Crit, MinRarity = RarityType.White, Weight = 1 } },
|
||||
};
|
||||
|
||||
TagType[] result = InventoryTagRuleService.ResolveComponentTags(
|
||||
new[] { TagType.Fire, TagType.Ice, TagType.Crit },
|
||||
RarityType.Green,
|
||||
InventoryTagSourceType.Shop,
|
||||
1,
|
||||
1,
|
||||
weightedRules);
|
||||
|
||||
Assert.That(result, Has.Length.EqualTo(1));
|
||||
Assert.That(result[0], Is.EqualTo(TagType.Fire));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ResolveComponentTags_Prefers_HighWeight_Tag_In_ExpandedPool()
|
||||
{
|
||||
IReadOnlyDictionary<TagType, TagGenerationRuleData> weightedRules =
|
||||
new Dictionary<TagType, TagGenerationRuleData>
|
||||
{
|
||||
{ TagType.Fire, new TagGenerationRuleData { TagType = TagType.Fire, MinRarity = RarityType.White, Weight = 500 } },
|
||||
{ TagType.Ice, new TagGenerationRuleData { TagType = TagType.Ice, MinRarity = RarityType.White, Weight = 1 } },
|
||||
{ TagType.Crit, new TagGenerationRuleData { TagType = TagType.Crit, MinRarity = RarityType.White, Weight = 1 } },
|
||||
{ TagType.Shatter, new TagGenerationRuleData { TagType = TagType.Shatter, MinRarity = RarityType.White, Weight = 1 } },
|
||||
};
|
||||
|
||||
int fireCount = 0;
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
TagType[] result = InventoryTagRuleService.ResolveComponentTags(
|
||||
new[] { TagType.Fire, TagType.Ice, TagType.Crit, TagType.Shatter },
|
||||
RarityType.Green,
|
||||
InventoryTagSourceType.Shop,
|
||||
1000 + i,
|
||||
1,
|
||||
weightedRules);
|
||||
|
||||
Assert.That(result, Has.Length.EqualTo(1));
|
||||
if (result[0] == TagType.Fire)
|
||||
{
|
||||
fireCount++;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.That(fireCount, Is.GreaterThanOrEqualTo(28));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TagGenerationRuleRegistry_LoadFromRows_Overrides_MinRarity_And_Weight()
|
||||
{
|
||||
DRTag fireRow = new DRTag();
|
||||
Assert.That(fireRow.ParseDataRow("\t1\t元素\tFire\tGreen\t33", null), Is.True);
|
||||
|
||||
TagGenerationRuleRegistry.LoadFromRows(new[] { fireRow });
|
||||
|
||||
Assert.That(TagGenerationRuleRegistry.TryGetRule(TagType.Fire, out TagGenerationRuleData rule), Is.True);
|
||||
Assert.That(rule.MinRarity, Is.EqualTo(RarityType.Green));
|
||||
Assert.That(rule.Weight, Is.EqualTo(33));
|
||||
|
||||
TagGenerationRuleRegistry.ResetToDefaults();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateSampleInventory_Generates_SeedTags_Within_Launch_Set_And_Matches_Tower_Stats()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -148,6 +148,10 @@
|
|||
> 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`(如权重、生成规则等)全部收口。
|
||||
>
|
||||
> 2026-03-11 更新:`S4-07` 已继续推进到第二阶段。当前 `Tag.txt -> DRTag -> TagGenerationRuleRegistry` 已形成组件 Tag 生成规则闭环,`InventoryTagRuleService` 不再主要依赖内部 `MinRarity` 硬编码,而是统一消费 `DRTag` 提供的 `MinRarity + Weight`;`ShopFormUseCase`、`EnemyDropResolver`、`InventorySeedUtility` 也已切回这一统一入口。现阶段的职责边界明确为:`Tag.txt` 负责基础字典与生成规则,`TagConfig.txt` 负责触发阶段、描述与效果参数。
|
||||
>
|
||||
> 2026-03-11 补充验证:已通过手工扩充组件 `PossibleTag` 候选池并调整 `Weight` 验证生成权重实际生效;同时把所有 Tag 的 `MinRarity` 提升到 `Red` 后,低品质组件不再生成任何 Tag,说明 `Tag.txt` 中的 `MinRarity` 与 `Weight` 均已进入统一生成链。
|
||||
|
||||
### S4-01 边界结论
|
||||
|
||||
|
|
@ -183,7 +187,8 @@
|
|||
- `Shatter` 已接入命中前数值修正链;`Inferno`、`AbsoluteZero` 已按“强化现有 `Fire` / `Ice` 状态”的首发口径落地,状态类 Tag 的内部结构继续保持注册式运行时。
|
||||
- `BurnSpread`、`IgniteBurst`、`FreezeMask`、`Pierce`、`Overpenetrate` 已进入分类与配置骨架,但仍属于后续扩展,不计入当前 `S4` 的完成标准。
|
||||
- `S4-07` 已进入第一阶段实现。当前仓库已具备 `TagConfig.txt -> DRTagConfig -> TagConfigRegistry -> ItemDescForm` 的消费闭环,首发 7 个 Tag 的触发阶段、描述与核心参数已可由表覆盖。
|
||||
- `S4-07` 仍未完成最终收口。当前采用的是 `TagConfig` 表驱动,而不是文档原方案里的完整 `TagRule`;权重、生成规则等字段仍未全部映射回 DataTable / DataRow / 运行时消费链。
|
||||
- `S4-07` 已推进到第二阶段。当前仓库同时具备 `Tag.txt -> DRTag -> TagGenerationRuleRegistry -> InventoryTagRuleService` 与 `TagConfig.txt -> DRTagConfig -> TagConfigRegistry -> ItemDescForm` 两条消费闭环,首发 7 个 Tag 的生成规则、触发阶段、描述与核心参数都已开始由表驱动。
|
||||
- `S4-07` 仍未完成最终收口。当前采用的是 `Tag.txt + TagConfig.txt` 的分层方案,而不是文档原方案里的单独 `TagRule`;更深的规则字段与完整命名口径仍待后续决定是否继续统一。
|
||||
|
||||
### S4-06 当前代码状态
|
||||
|
||||
|
|
@ -201,8 +206,8 @@
|
|||
|
||||
### S4 后续执行计划
|
||||
|
||||
1. 继续推进 `S4-07`:在现有 `TagConfig` 表驱动基础上,决定是否补成文档中的完整 `TagRule`,或明确将 `TagConfig` 作为当前阶段的等价收口方案。
|
||||
2. `S4-07` 完成标准仍以“表字段被实际消费、参数可解释、运行时与文档口径一致”为准;当前已完成首发 7 个 Tag 的参数和说明配置化,后续重点转向剩余规则字段是否也要配置化。
|
||||
1. 继续推进 `S4-07`:在现有 `Tag.txt + TagConfig.txt` 分层方案基础上,决定是否补成文档中的完整 `TagRule`,或明确当前双表就是本阶段的等价收口方案。
|
||||
2. `S4-07` 完成标准仍以“表字段被实际消费、参数可解释、运行时与文档口径一致”为准;当前已完成首发 7 个 Tag 的生成规则、参数和说明配置化,后续重点转向是否还要继续配置化更深层的规则字段。
|
||||
3. `BurnSpread`、`IgniteBurst`、`FreezeMask`、`Pierce`、`Overpenetrate` 继续留在后续扩展阶段,不因为 `S4-06` 完成而提前进入当前迭代。
|
||||
|
||||
## 阶段 S5 - 收口耐久规则
|
||||
|
|
|
|||
BIN
数据表/Tag.xlsx
BIN
数据表/Tag.xlsx
Binary file not shown.
Binary file not shown.
|
|
@ -36,7 +36,12 @@ def convert_excel_to_txt(folder_path='.'):
|
|||
excel_file = pd.ExcelFile(file_path)
|
||||
valid_sheets = []
|
||||
for sheet_name in excel_file.sheet_names:
|
||||
df = pd.read_excel(excel_file, sheet_name=sheet_name, header=None)
|
||||
df = pd.read_excel(
|
||||
excel_file,
|
||||
sheet_name=sheet_name,
|
||||
header=None,
|
||||
keep_default_na=False,
|
||||
)
|
||||
if is_valid_sheet(df):
|
||||
valid_sheets.append((sheet_name, df))
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue