diff --git a/Assets/GameMain/DataTables/LevelPhase.txt b/Assets/GameMain/DataTables/LevelPhase.txt
index b3fa983..551f061 100644
--- a/Assets/GameMain/DataTables/LevelPhase.txt
+++ b/Assets/GameMain/DataTables/LevelPhase.txt
@@ -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
diff --git a/Assets/GameMain/DataTables/Tag.txt b/Assets/GameMain/DataTables/Tag.txt
index f07dbaa..5572a8f 100644
--- a/Assets/GameMain/DataTables/Tag.txt
+++ b/Assets/GameMain/DataTables/Tag.txt
@@ -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
diff --git a/Assets/GameMain/DataTables/TagConfig.txt b/Assets/GameMain/DataTables/TagConfig.txt
index 99c6a28..6f6160a 100644
--- a/Assets/GameMain/DataTables/TagConfig.txt
+++ b/Assets/GameMain/DataTables/TagConfig.txt
@@ -1,51 +1,15 @@
-# Id 列1 TagType Weight TriggerPhase Description Param
-# int TagType int TagTriggerPhase string string
-# Tag配置编号 策划备注 所属Tag类型 权重 触发阶段 描述 参数Json
- 1 元素 Fire 20 OnAfterHit 持续对敌人造成火伤害 {\
- "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 持续对敌人造成火伤害 {"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}
diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/EnemyDrop/EnemyDropResolver.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/EnemyDrop/EnemyDropResolver.cs
index 5795946..c79736d 100644
--- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/EnemyDrop/EnemyDropResolver.cs
+++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/EnemyDrop/EnemyDropResolver.cs
@@ -17,20 +17,16 @@ namespace GeometryTD.CustomComponent
private readonly List _eligibleDropPoolBuffer = new();
private readonly Dictionary _rarityRollWeightBuffer = new();
- private readonly Dictionary _tagMinRarityByTag = new();
-
private IDataTable _drOutGameDropPool;
private IDataTable _drMuzzleComp;
private IDataTable _drBearingComp;
private IDataTable _drBaseComp;
- private IDataTable _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();
- 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(),
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(),
AttackRange = config.AttackRange != null ? (float[])config.AttackRange.Clone() : Array.Empty()
};
@@ -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(),
AttackPropertyType = config.AttackPropertyType
};
diff --git a/Assets/GameMain/Scripts/DataTable/DRTag.cs b/Assets/GameMain/Scripts/DataTable/DRTag.cs
index 054387b..644bb6f 100644
--- a/Assets/GameMain/Scripts/DataTable/DRTag.cs
+++ b/Assets/GameMain/Scripts/DataTable/DRTag.cs
@@ -6,7 +6,7 @@ using UnityGameFramework.Runtime;
namespace GeometryTD.DataTable
{
///
- /// 枪口组件表
+ /// Tag 表
///
public class DRTag : DataRowBase
{
@@ -22,8 +22,12 @@ namespace GeometryTD.DataTable
///
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,8 +42,9 @@ namespace GeometryTD.DataTable
index++;
Name = columnStrings[index++];
MinRarity = EnumUtility.Get(columnStrings[index++]);
+ Weight = int.Parse(columnStrings[index++]);
return true;
}
}
-}
\ No newline at end of file
+}
diff --git a/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainCombatEntryBlockReason.cs b/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainCombatEntryBlockReason.cs
new file mode 100644
index 0000000..a3feef7
--- /dev/null
+++ b/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainCombatEntryBlockReason.cs
@@ -0,0 +1,9 @@
+namespace GeometryTD.Definition
+{
+ public enum ProcedureMainCombatEntryBlockReason
+ {
+ None = 0,
+ InventoryUnavailable = 1,
+ NoValidParticipantTower = 2
+ }
+}
\ No newline at end of file
diff --git a/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainCombatEntryBlockReason.cs.meta b/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainCombatEntryBlockReason.cs.meta
new file mode 100644
index 0000000..d03926a
--- /dev/null
+++ b/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainCombatEntryBlockReason.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: f6d8af9f1d7d4041bfcee904724303e9
+timeCreated: 1773198097
\ No newline at end of file
diff --git a/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainFlowPhase.cs b/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainFlowPhase.cs
new file mode 100644
index 0000000..d9305ba
--- /dev/null
+++ b/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainFlowPhase.cs
@@ -0,0 +1,9 @@
+namespace GeometryTD.Definition
+{
+ public enum ProcedureMainFlowPhase
+ {
+ Hub = 0,
+ NodeActive = 1,
+ RunCompletedPendingFinish = 2
+ }
+}
\ No newline at end of file
diff --git a/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainFlowPhase.cs.meta b/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainFlowPhase.cs.meta
new file mode 100644
index 0000000..fbe8201
--- /dev/null
+++ b/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainFlowPhase.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 01450486bd824f47bcd74dda8a160ba4
+timeCreated: 1773198057
\ No newline at end of file
diff --git a/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainRunAdvanceResult.cs b/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainRunAdvanceResult.cs
new file mode 100644
index 0000000..6fe78cc
--- /dev/null
+++ b/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainRunAdvanceResult.cs
@@ -0,0 +1,10 @@
+namespace GeometryTD.Definition
+{
+ public enum ProcedureMainRunAdvanceResult
+ {
+ NoChange = 0,
+ NodeException = 1,
+ AdvancedToNextNode = 2,
+ RunCompleted = 3
+ }
+}
\ No newline at end of file
diff --git a/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainRunAdvanceResult.cs.meta b/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainRunAdvanceResult.cs.meta
new file mode 100644
index 0000000..d603b9e
--- /dev/null
+++ b/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainRunAdvanceResult.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: e9e0fa56d7e84874ac0f9d4a400fda13
+timeCreated: 1773198071
\ No newline at end of file
diff --git a/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainRunCompletionResult.cs b/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainRunCompletionResult.cs
new file mode 100644
index 0000000..f5fe2f6
--- /dev/null
+++ b/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainRunCompletionResult.cs
@@ -0,0 +1,9 @@
+namespace GeometryTD.Definition
+{
+ public enum ProcedureMainRunCompletionResult
+ {
+ NoChange = 0,
+ ShowCompletionDialog = 1,
+ ReturnToMenu = 2
+ }
+}
\ No newline at end of file
diff --git a/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainRunCompletionResult.cs.meta b/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainRunCompletionResult.cs.meta
new file mode 100644
index 0000000..360e4bf
--- /dev/null
+++ b/Assets/GameMain/Scripts/Definition/Enum/ProcedureMainRunCompletionResult.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: e3d0df05f77043a99c463a790b5ac35f
+timeCreated: 1773198084
\ No newline at end of file
diff --git a/Assets/GameMain/Scripts/Definition/InventoryTagRuleService.cs b/Assets/GameMain/Scripts/Definition/InventoryTagRuleService.cs
index 8dc4bba..4b27ee7 100644
--- a/Assets/GameMain/Scripts/Definition/InventoryTagRuleService.cs
+++ b/Assets/GameMain/Scripts/Definition/InventoryTagRuleService.cs
@@ -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 DefaultMinRarityByTag =
- new Dictionary
- {
- { 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 SupportedLaunchTags = new HashSet(DefaultMinRarityByTag.Keys);
-
public static TagType[] ResolveComponentTags(
IReadOnlyList possibleTags,
RarityType rarity,
InventoryTagSourceType sourceType,
long itemInstanceId,
int configId,
- IReadOnlyDictionary minRarityByTag = null)
+ IReadOnlyDictionary rulesByTag = null)
{
- TagType[] eligibleTags = GetEligibleTags(possibleTags, rarity, minRarityByTag);
+ IReadOnlyDictionary ruleLookup = rulesByTag ?? TagGenerationRuleRegistry.Rules;
+ TagType[] eligibleTags = GetEligibleTags(possibleTags, rarity, ruleLookup);
if (eligibleTags.Length <= 0)
{
return Array.Empty();
@@ -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 possibleTags,
RarityType rarity,
- IReadOnlyDictionary minRarityByTag = null)
+ IReadOnlyDictionary rulesByTag = null)
{
if (possibleTags == null || possibleTags.Count <= 0)
{
@@ -64,23 +53,23 @@ namespace GeometryTD.Definition
}
RarityType normalizedRarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity);
- IReadOnlyDictionary rarityLookup = minRarityByTag ?? DefaultMinRarityByTag;
+ IReadOnlyDictionary ruleLookup = rulesByTag ?? TagGenerationRuleRegistry.Rules;
HashSet uniqueTags = new HashSet();
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 rarityLookup,
- out RarityType minRarity)
+ IReadOnlyDictionary 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 pool,
+ IReadOnlyDictionary 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,
diff --git a/Assets/GameMain/Scripts/Definition/TagGenerationRuleData.cs b/Assets/GameMain/Scripts/Definition/TagGenerationRuleData.cs
new file mode 100644
index 0000000..3ec22f6
--- /dev/null
+++ b/Assets/GameMain/Scripts/Definition/TagGenerationRuleData.cs
@@ -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; }
+ }
+}
diff --git a/Assets/GameMain/Scripts/Definition/TagGenerationRuleData.cs.meta b/Assets/GameMain/Scripts/Definition/TagGenerationRuleData.cs.meta
new file mode 100644
index 0000000..9782d13
--- /dev/null
+++ b/Assets/GameMain/Scripts/Definition/TagGenerationRuleData.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: cfe508e818ce47649a112ecdf86cf26a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/GameMain/Scripts/Definition/TagGenerationRuleRegistry.cs b/Assets/GameMain/Scripts/Definition/TagGenerationRuleRegistry.cs
new file mode 100644
index 0000000..5b5d8e3
--- /dev/null
+++ b/Assets/GameMain/Scripts/Definition/TagGenerationRuleRegistry.cs
@@ -0,0 +1,74 @@
+using System.Collections.Generic;
+using GeometryTD.DataTable;
+using UnityEngine;
+
+namespace GeometryTD.Definition
+{
+ public static class TagGenerationRuleRegistry
+ {
+ private static readonly Dictionary RulesByTag = CreateDefaultRules();
+
+ public static IReadOnlyDictionary Rules => RulesByTag;
+
+ public static void ResetToDefaults()
+ {
+ RulesByTag.Clear();
+ foreach (KeyValuePair pair in CreateDefaultRules())
+ {
+ RulesByTag.Add(pair.Key, pair.Value);
+ }
+ }
+
+ public static void LoadFromRows(IEnumerable 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 CreateDefaultRules()
+ {
+ return new Dictionary
+ {
+ [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);
+ }
+ }
+}
diff --git a/Assets/GameMain/Scripts/Definition/TagGenerationRuleRegistry.cs.meta b/Assets/GameMain/Scripts/Definition/TagGenerationRuleRegistry.cs.meta
new file mode 100644
index 0000000..31f3f9d
--- /dev/null
+++ b/Assets/GameMain/Scripts/Definition/TagGenerationRuleRegistry.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2e72e26721274985ba2d44ca825d7f75
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/GameMain/Scripts/Procedure/Base/ProcedurePreload.cs b/Assets/GameMain/Scripts/Procedure/Base/ProcedurePreload.cs
index 8649028..239d556 100644
--- a/Assets/GameMain/Scripts/Procedure/Base/ProcedurePreload.cs
+++ b/Assets/GameMain/Scripts/Procedure/Base/ProcedurePreload.cs
@@ -204,6 +204,15 @@ namespace GeometryTD.Procedure
TagConfigRegistry.LoadFromRows(tagConfigTable.GetAllDataRows());
}
}
+
+ if (ne.DataTableAssetName == AssetUtility.GetDataTableAsset("Tag", false))
+ {
+ var tagTable = GameEntry.DataTable.GetDataTable();
+ if (tagTable != null)
+ {
+ TagGenerationRuleRegistry.LoadFromRows(tagTable.GetAllDataRows());
+ }
+ }
}
private void OnLoadDataTableFailure(object sender, GameEventArgs e)
diff --git a/Assets/GameMain/Scripts/Procedure/ProcedureMain.meta b/Assets/GameMain/Scripts/Procedure/ProcedureMain.meta
new file mode 100644
index 0000000..23aa111
--- /dev/null
+++ b/Assets/GameMain/Scripts/Procedure/ProcedureMain.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: f18d07456c02b1546b198ed25ba69e18
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/GameMain/Scripts/Procedure/FixedRunNodeSequenceBuilder.cs b/Assets/GameMain/Scripts/Procedure/ProcedureMain/FixedRunNodeSequenceBuilder.cs
similarity index 100%
rename from Assets/GameMain/Scripts/Procedure/FixedRunNodeSequenceBuilder.cs
rename to Assets/GameMain/Scripts/Procedure/ProcedureMain/FixedRunNodeSequenceBuilder.cs
diff --git a/Assets/GameMain/Scripts/Procedure/FixedRunNodeSequenceBuilder.cs.meta b/Assets/GameMain/Scripts/Procedure/ProcedureMain/FixedRunNodeSequenceBuilder.cs.meta
similarity index 100%
rename from Assets/GameMain/Scripts/Procedure/FixedRunNodeSequenceBuilder.cs.meta
rename to Assets/GameMain/Scripts/Procedure/ProcedureMain/FixedRunNodeSequenceBuilder.cs.meta
diff --git a/Assets/GameMain/Scripts/Procedure/ProcedureMain.cs b/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMain.cs
similarity index 66%
rename from Assets/GameMain/Scripts/Procedure/ProcedureMain.cs
rename to Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMain.cs
index e2989df..5ed0f84 100644
--- a/Assets/GameMain/Scripts/Procedure/ProcedureMain.cs
+++ b/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMain.cs
@@ -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);
@@ -659,4 +423,4 @@ namespace GeometryTD.Procedure
_isReturnToMenuPending = true;
}
}
-}
+}
\ No newline at end of file
diff --git a/Assets/GameMain/Scripts/Procedure/ProcedureMain.cs.meta b/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMain.cs.meta
similarity index 100%
rename from Assets/GameMain/Scripts/Procedure/ProcedureMain.cs.meta
rename to Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMain.cs.meta
diff --git a/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainCombatEntryValidationResult.cs b/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainCombatEntryValidationResult.cs
new file mode 100644
index 0000000..6173e64
--- /dev/null
+++ b/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainCombatEntryValidationResult.cs
@@ -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; }
+ }
+}
\ No newline at end of file
diff --git a/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainCombatEntryValidationResult.cs.meta b/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainCombatEntryValidationResult.cs.meta
new file mode 100644
index 0000000..0ea22f0
--- /dev/null
+++ b/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainCombatEntryValidationResult.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 5699aa9933294974875bfce1d34d1d4b
+timeCreated: 1773198121
\ No newline at end of file
diff --git a/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainCombatEntryValidationService.cs b/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainCombatEntryValidationService.cs
new file mode 100644
index 0000000..98857ad
--- /dev/null
+++ b/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainCombatEntryValidationService.cs
@@ -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 "不满足当前参战条件。";
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainCombatEntryValidationService.cs.meta b/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainCombatEntryValidationService.cs.meta
new file mode 100644
index 0000000..25713fa
--- /dev/null
+++ b/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainCombatEntryValidationService.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: be3e90d1a1874e26af6d1555c301aa26
+timeCreated: 1773198334
\ No newline at end of file
diff --git a/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainNodeEventGuardService.cs b/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainNodeEventGuardService.cs
new file mode 100644
index 0000000..a342c3a
--- /dev/null
+++ b/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainNodeEventGuardService.cs
@@ -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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainNodeEventGuardService.cs.meta b/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainNodeEventGuardService.cs.meta
new file mode 100644
index 0000000..7b4235a
--- /dev/null
+++ b/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainNodeEventGuardService.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 212a0e12be2248bf9415a779414312a1
+timeCreated: 1773198194
\ No newline at end of file
diff --git a/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainRunCompletionService.cs b/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainRunCompletionService.cs
new file mode 100644
index 0000000..8559562
--- /dev/null
+++ b/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainRunCompletionService.cs
@@ -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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainRunCompletionService.cs.meta b/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainRunCompletionService.cs.meta
new file mode 100644
index 0000000..7c3a125
--- /dev/null
+++ b/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainRunCompletionService.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 8d565626cc594843989da76a623a1698
+timeCreated: 1773198165
\ No newline at end of file
diff --git a/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainRunFlowService.cs b/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainRunFlowService.cs
new file mode 100644
index 0000000..e0942d7
--- /dev/null
+++ b/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainRunFlowService.cs
@@ -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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainRunFlowService.cs.meta b/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainRunFlowService.cs.meta
new file mode 100644
index 0000000..8cb8984
--- /dev/null
+++ b/Assets/GameMain/Scripts/Procedure/ProcedureMain/ProcedureMainRunFlowService.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: f62dcb2d97fe4f0c884e6eab3d500edf
+timeCreated: 1773198145
\ No newline at end of file
diff --git a/Assets/GameMain/Scripts/Procedure/RunModel.cs b/Assets/GameMain/Scripts/Procedure/ProcedureMain/RunModel.cs
similarity index 100%
rename from Assets/GameMain/Scripts/Procedure/RunModel.cs
rename to Assets/GameMain/Scripts/Procedure/ProcedureMain/RunModel.cs
diff --git a/Assets/GameMain/Scripts/Procedure/RunModel.cs.meta b/Assets/GameMain/Scripts/Procedure/ProcedureMain/RunModel.cs.meta
similarity index 100%
rename from Assets/GameMain/Scripts/Procedure/RunModel.cs.meta
rename to Assets/GameMain/Scripts/Procedure/ProcedureMain/RunModel.cs.meta
diff --git a/Assets/GameMain/Scripts/Procedure/RunStateAdvanceService.cs b/Assets/GameMain/Scripts/Procedure/ProcedureMain/RunStateAdvanceService.cs
similarity index 100%
rename from Assets/GameMain/Scripts/Procedure/RunStateAdvanceService.cs
rename to Assets/GameMain/Scripts/Procedure/ProcedureMain/RunStateAdvanceService.cs
diff --git a/Assets/GameMain/Scripts/Procedure/RunStateAdvanceService.cs.meta b/Assets/GameMain/Scripts/Procedure/ProcedureMain/RunStateAdvanceService.cs.meta
similarity index 100%
rename from Assets/GameMain/Scripts/Procedure/RunStateAdvanceService.cs.meta
rename to Assets/GameMain/Scripts/Procedure/ProcedureMain/RunStateAdvanceService.cs.meta
diff --git a/Assets/GameMain/Scripts/Procedure/RunStateFactory.cs b/Assets/GameMain/Scripts/Procedure/ProcedureMain/RunStateFactory.cs
similarity index 100%
rename from Assets/GameMain/Scripts/Procedure/RunStateFactory.cs
rename to Assets/GameMain/Scripts/Procedure/ProcedureMain/RunStateFactory.cs
diff --git a/Assets/GameMain/Scripts/Procedure/RunStateFactory.cs.meta b/Assets/GameMain/Scripts/Procedure/ProcedureMain/RunStateFactory.cs.meta
similarity index 100%
rename from Assets/GameMain/Scripts/Procedure/RunStateFactory.cs.meta
rename to Assets/GameMain/Scripts/Procedure/ProcedureMain/RunStateFactory.cs.meta
diff --git a/Assets/GameMain/Scripts/UI/Shop/UseCase/ShopFormUseCase.cs b/Assets/GameMain/Scripts/UI/Shop/UseCase/ShopFormUseCase.cs
index 701e05d..be428da 100644
--- a/Assets/GameMain/Scripts/UI/Shop/UseCase/ShopFormUseCase.cs
+++ b/Assets/GameMain/Scripts/UI/Shop/UseCase/ShopFormUseCase.cs
@@ -16,13 +16,10 @@ namespace GeometryTD.UI
private readonly List _currentGoods = new List(GoodsCount);
private readonly List _shopPriceRows = new List();
- private readonly Dictionary _tagMinRarityByTag = new Dictionary();
-
private IDataTable _shopPriceTable;
private IDataTable _muzzleCompTable;
private IDataTable _bearingCompTable;
private IDataTable _baseCompTable;
- private IDataTable _tagTable;
public bool PrepareForOpen()
{
@@ -101,9 +98,8 @@ namespace GeometryTD.UI
_muzzleCompTable ??= GameEntry.DataTable.GetDataTable();
_bearingCompTable ??= GameEntry.DataTable.GetDataTable();
_baseCompTable ??= GameEntry.DataTable.GetDataTable();
- _tagTable ??= GameEntry.DataTable.GetDataTable();
- 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(),
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(),
AttackRange = config.AttackRange != null ? (float[])config.AttackRange.Clone() : Array.Empty()
};
@@ -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(),
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++)
diff --git a/Assets/Tests/EditMode/InventoryTagRuleServiceTests.cs b/Assets/Tests/EditMode/InventoryTagRuleServiceTests.cs
index 8ece44f..48a3d5c 100644
--- a/Assets/Tests/EditMode/InventoryTagRuleServiceTests.cs
+++ b/Assets/Tests/EditMode/InventoryTagRuleServiceTests.cs
@@ -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 MinRarityByTag =
- new Dictionary
+ private static readonly IReadOnlyDictionary RulesByTag =
+ new Dictionary
{
- { 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(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 weightedRules =
+ new Dictionary
+ {
+ { 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 weightedRules =
+ new Dictionary
+ {
+ { 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()
{
diff --git a/docs/CodeX-TODO.md b/docs/CodeX-TODO.md
index 1690080..8170546 100644
--- a/docs/CodeX-TODO.md
+++ b/docs/CodeX-TODO.md
@@ -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 - 收口耐久规则
diff --git a/数据表/Tag.xlsx b/数据表/Tag.xlsx
index a6607b7..9b23883 100644
Binary files a/数据表/Tag.xlsx and b/数据表/Tag.xlsx differ
diff --git a/数据表/TowerComp.xlsx b/数据表/TowerComp.xlsx
index 991d461..e1caaeb 100644
Binary files a/数据表/TowerComp.xlsx and b/数据表/TowerComp.xlsx differ
diff --git a/数据表/convert.py b/数据表/convert.py
index 4a83ecc..c8a034c 100644
--- a/数据表/convert.py
+++ b/数据表/convert.py
@@ -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))