- S4-03 | 先固化 Tag 系统设计与首发范围
- S4-04 | 实现组件实例 Tag 的统一生成入口 - S4-05 | 实现组塔后的 Tag 汇总与展示入口
This commit is contained in:
parent
73b7adedb8
commit
34ef001ef3
349
AGENTS.md
349
AGENTS.md
|
|
@ -1,34 +1,355 @@
|
||||||
# Repository Guidelines
|
# Project Philosophy
|
||||||
|
|
||||||
## Project Structure & Module Organization
|
This project prioritizes **clarity, correctness, and maintainability** over defensive boilerplate or unnecessary abstraction.
|
||||||
|
|
||||||
|
AI-generated code should:
|
||||||
|
|
||||||
|
* follow existing architecture
|
||||||
|
* keep logic simple and explicit
|
||||||
|
* avoid hiding bugs
|
||||||
|
* avoid speculative abstractions
|
||||||
|
|
||||||
|
If uncertain, prefer **simple direct code** over generalized frameworks.
|
||||||
|
|
||||||
|
## Repository Guidelines
|
||||||
|
|
||||||
|
### Project Structure & Module Organization
|
||||||
- `Assets/` contains all Unity assets and code. Game-specific content lives under `Assets/GameMain/`.
|
- `Assets/` contains all Unity assets and code. Game-specific content lives under `Assets/GameMain/`.
|
||||||
- `Assets/GameMain/Scripts/` holds gameplay code organized by domain (`Procedure/`, `Entity/`, `UI/`, `Scene/`, `Sound/`, `Utility/`).
|
- `Assets/GameMain/Scripts/` holds gameplay code organized by domain (`Procedure/`, `Entity/`, `UI/`, `Scene/`, `Sound/`, `Utility/`).
|
||||||
- `Assets/GameMain/Scenes/` stores Unity scenes (start from `Assets/Launcher.unity`).
|
- `Assets/GameMain/Scenes/` stores Unity scenes (start from `Assets/Launcher.unity`).
|
||||||
- `Assets/GameMain/Configs/` and `Assets/GameMain/DataTables/` store runtime configuration and data tables.
|
- `Assets/GameMain/Configs/` and `Assets/GameMain/DataTables/` store runtime configuration and data tables.
|
||||||
- `StreamingAssets/` is for runtime-loaded files that must be preserved on build.
|
- `StreamingAssets/` is for runtime-loaded files that must be preserved on build.
|
||||||
- `docs/` contains design notes (see `docs/GameDesign.md`).
|
- `docs/` contains design notes (see `docs/GameDesign.md`).
|
||||||
- `Êý¾Ý±í/` is a top-level data table workspace; keep it in sync with `Assets/GameMain/DataTables/` when exporting.
|
- `<EFBFBD><EFBFBD><EFBFBD>ݱ<EFBFBD>/` is a top-level data table workspace; keep it in sync with `Assets/GameMain/DataTables/` when exporting.
|
||||||
|
|
||||||
## Build, Test, and Development Commands
|
### Build, Test, and Development Commands
|
||||||
- Open the project with Unity Hub and load `GeometryTD` as a Unity project.
|
- Open the project with Unity Hub and load `GeometryTD` as a Unity project.
|
||||||
- Play locally via the Unity Editor Play button (no custom CLI runner is defined in this repo).
|
- Play locally via the Unity Editor Play button (no custom CLI runner is defined in this repo).
|
||||||
- Build via Unity: `File > Build Settings...` then choose target and build.
|
- Build via Unity: `File > Build Settings...` then choose target and build.
|
||||||
- IDE support: open `GeometryTD.sln` or `Assembly-CSharp.csproj` for C# navigation and tooling.
|
- IDE support: open `GeometryTD.sln` or `Assembly-CSharp.csproj` for C# navigation and tooling.
|
||||||
|
|
||||||
## Coding Style & Naming Conventions
|
### Coding Style & Naming Conventions
|
||||||
- C# uses 4-space indentation and Allman braces (see `Assets/GameMain/Scripts/Procedure/ProcedureLaunch.cs`).
|
- C# uses 4-space indentation and Allman braces (see `Assets/GameMain/Scripts/Procedure/ProcedureLaunch.cs`).
|
||||||
- Types, methods, and public members use `PascalCase`; locals and parameters use `camelCase`.
|
- Types, methods, and public members use `PascalCase`; locals and parameters use `camelCase`.
|
||||||
- Namespaces follow `GeometryTD.*` by feature area (example: `GeometryTD.Procedure`).
|
- Namespaces follow `GeometryTD.*` by feature area (example: `GeometryTD.Procedure`).
|
||||||
- Keep Unity `.meta` files with their assets; do not delete or regenerate them manually.
|
- Keep Unity `.meta` files with their assets; do not delete or regenerate them manually.
|
||||||
|
|
||||||
## Testing Guidelines
|
---
|
||||||
- No project-specific tests are present yet. Unity¡¯s Test Runner is available if tests are added.
|
|
||||||
- If you add tests, place them under `Assets/` with `Editor` or `Runtime` assembly definition files and use `*Tests.cs` naming.
|
|
||||||
|
|
||||||
## Commit & Pull Request Guidelines
|
## Code Design Principles
|
||||||
- This repository does not include Git history in the workspace, so no commit convention can be inferred.
|
|
||||||
- Use clear, scoped commit messages (example: `Add tower targeting component`), and include a brief PR description plus screenshots for visual changes.
|
|
||||||
|
|
||||||
## Configuration Tips
|
### 1. Prefer explicit logic over abstraction
|
||||||
- Project settings live in `ProjectSettings/` and `Packages/`; review diffs carefully when they change.
|
|
||||||
- Avoid committing `Library/`, `Temp/`, or `Logs/` outputs; they are generated by Unity.
|
Do not introduce abstractions unless they are clearly justified.
|
||||||
|
|
||||||
|
Avoid creating layers such as:
|
||||||
|
|
||||||
|
```
|
||||||
|
Manager
|
||||||
|
Service
|
||||||
|
Provider
|
||||||
|
Repository
|
||||||
|
Coordinator
|
||||||
|
Bridge
|
||||||
|
```
|
||||||
|
|
||||||
|
unless the architecture explicitly requires them.
|
||||||
|
|
||||||
|
Simple gameplay logic should remain simple.
|
||||||
|
|
||||||
|
Bad example:
|
||||||
|
|
||||||
|
```
|
||||||
|
EnemyService -> EnemyManager -> EnemyRepository
|
||||||
|
```
|
||||||
|
|
||||||
|
Good example:
|
||||||
|
|
||||||
|
```
|
||||||
|
EnemySpawner
|
||||||
|
Enemy
|
||||||
|
EnemyAI
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Do not create abstractions for a single implementation
|
||||||
|
|
||||||
|
Avoid introducing interfaces unless multiple implementations are expected.
|
||||||
|
|
||||||
|
Bad:
|
||||||
|
|
||||||
|
```
|
||||||
|
IEnemyService
|
||||||
|
EnemyService
|
||||||
|
```
|
||||||
|
|
||||||
|
Good:
|
||||||
|
|
||||||
|
```
|
||||||
|
EnemySystem
|
||||||
|
```
|
||||||
|
|
||||||
|
Interfaces are only appropriate when:
|
||||||
|
|
||||||
|
* multiple implementations are required
|
||||||
|
* plugin/mod systems are involved
|
||||||
|
* testing requires mocking
|
||||||
|
* architecture explicitly defines extension points
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Avoid speculative generalization
|
||||||
|
|
||||||
|
Do not design systems for hypothetical future use.
|
||||||
|
|
||||||
|
Implement only what the current feature requires.
|
||||||
|
|
||||||
|
Avoid:
|
||||||
|
|
||||||
|
* generic service layers
|
||||||
|
* premature plugin systems
|
||||||
|
* unnecessary configuration frameworks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Defensive Programming Policy
|
||||||
|
|
||||||
|
### Boundary validation only
|
||||||
|
|
||||||
|
Validation is appropriate only at **true system boundaries**, such as:
|
||||||
|
|
||||||
|
* user input
|
||||||
|
* network input
|
||||||
|
* file/config loading
|
||||||
|
* inspector/external data
|
||||||
|
* public API boundaries
|
||||||
|
|
||||||
|
Internal gameplay logic should assume that invariants are already satisfied.
|
||||||
|
|
||||||
|
Do not add redundant validation inside internal code.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Do not hide errors
|
||||||
|
|
||||||
|
Do not silently ignore invalid states.
|
||||||
|
|
||||||
|
Avoid code such as:
|
||||||
|
|
||||||
|
```
|
||||||
|
if (enemy == null)
|
||||||
|
return;
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```
|
||||||
|
if (config == null)
|
||||||
|
continue;
|
||||||
|
```
|
||||||
|
|
||||||
|
unless the behavior is intentionally designed.
|
||||||
|
|
||||||
|
Unexpected states should be **visible**, not hidden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Prefer fail-fast over silent failure
|
||||||
|
|
||||||
|
If a condition should never happen in correct execution:
|
||||||
|
|
||||||
|
Use assertions.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
Debug.Assert(config != null);
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not convert invariant violations into no-op behavior.
|
||||||
|
|
||||||
|
Bad:
|
||||||
|
|
||||||
|
```
|
||||||
|
if (config == null)
|
||||||
|
return;
|
||||||
|
```
|
||||||
|
|
||||||
|
Good:
|
||||||
|
|
||||||
|
```
|
||||||
|
Debug.Assert(config != null);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Do not weaken required dependencies
|
||||||
|
|
||||||
|
Required references must remain required.
|
||||||
|
|
||||||
|
Do not turn required dependencies into optional ones by adding defensive checks.
|
||||||
|
|
||||||
|
Bad:
|
||||||
|
|
||||||
|
```
|
||||||
|
if (enemy.Config == null)
|
||||||
|
return;
|
||||||
|
```
|
||||||
|
|
||||||
|
If a dependency is required by design, assume it exists.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
Do not introduce `try/catch` blocks unless there is a **clear recovery strategy**.
|
||||||
|
|
||||||
|
Avoid:
|
||||||
|
|
||||||
|
```
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DoSomething();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogError(e);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Exceptions should generally propagate during development so bugs are visible.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Early Return Policy
|
||||||
|
|
||||||
|
Early returns are acceptable for **normal control flow simplification**.
|
||||||
|
|
||||||
|
However, do not use early returns to hide invariant violations or missing required state.
|
||||||
|
|
||||||
|
Bad:
|
||||||
|
|
||||||
|
```
|
||||||
|
if (enemy == null)
|
||||||
|
return;
|
||||||
|
```
|
||||||
|
|
||||||
|
Good:
|
||||||
|
|
||||||
|
```
|
||||||
|
if (!enemy.IsAlive)
|
||||||
|
return;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Logging
|
||||||
|
|
||||||
|
Avoid excessive logging.
|
||||||
|
|
||||||
|
Do not log inside per-frame loops unless necessary.
|
||||||
|
|
||||||
|
Avoid logs like:
|
||||||
|
|
||||||
|
```
|
||||||
|
Debug.Log("Updating enemy");
|
||||||
|
```
|
||||||
|
|
||||||
|
Use logging only when it provides meaningful debugging value.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Utility Classes
|
||||||
|
|
||||||
|
Avoid creating generic utility classes such as:
|
||||||
|
|
||||||
|
```
|
||||||
|
GameUtils
|
||||||
|
CommonHelper
|
||||||
|
MathHelper
|
||||||
|
```
|
||||||
|
|
||||||
|
Prefer placing behavior in the domain object responsible for it.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
Bad:
|
||||||
|
|
||||||
|
```
|
||||||
|
DamageHelper.CalculateDamage(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
Good:
|
||||||
|
|
||||||
|
```
|
||||||
|
enemy.CalculateDamage(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Data Structures
|
||||||
|
|
||||||
|
Avoid large context objects with many loosely related fields.
|
||||||
|
|
||||||
|
Bad:
|
||||||
|
|
||||||
|
```
|
||||||
|
EffectContext
|
||||||
|
{
|
||||||
|
int value;
|
||||||
|
int count;
|
||||||
|
float rate;
|
||||||
|
object source;
|
||||||
|
object target;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Prefer explicit parameters or strongly typed structures.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code Generation Rules for AI
|
||||||
|
|
||||||
|
Before adding any of the following:
|
||||||
|
|
||||||
|
* null check
|
||||||
|
* range check
|
||||||
|
* fallback default
|
||||||
|
* try/catch
|
||||||
|
* abstraction layer
|
||||||
|
|
||||||
|
First determine:
|
||||||
|
|
||||||
|
1. Is this a boundary input?
|
||||||
|
2. Is the value optional by design?
|
||||||
|
3. Is there a real recovery strategy?
|
||||||
|
|
||||||
|
If the answer is **no**, do not add defensive code.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Preferred Coding Style
|
||||||
|
|
||||||
|
Prefer:
|
||||||
|
|
||||||
|
* simple classes
|
||||||
|
* explicit logic
|
||||||
|
* minimal indirection
|
||||||
|
* clear ownership of behavior
|
||||||
|
|
||||||
|
Avoid:
|
||||||
|
|
||||||
|
* unnecessary patterns
|
||||||
|
* speculative architecture
|
||||||
|
* defensive boilerplate
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Guiding Principle
|
||||||
|
|
||||||
|
Prefer:
|
||||||
|
|
||||||
|
**clear failure over hidden corruption**
|
||||||
|
|
||||||
|
A bug should surface near its source rather than being silently ignored.
|
||||||
|
|
|
||||||
|
|
@ -17,17 +17,20 @@ namespace GeometryTD.CustomComponent
|
||||||
|
|
||||||
private readonly List<DROutGameDropPool> _eligibleDropPoolBuffer = new();
|
private readonly List<DROutGameDropPool> _eligibleDropPoolBuffer = new();
|
||||||
private readonly Dictionary<RarityType, float> _rarityRollWeightBuffer = new();
|
private readonly Dictionary<RarityType, float> _rarityRollWeightBuffer = new();
|
||||||
|
private readonly Dictionary<TagType, RarityType> _tagMinRarityByTag = new();
|
||||||
|
|
||||||
private IDataTable<DROutGameDropPool> _drOutGameDropPool;
|
private IDataTable<DROutGameDropPool> _drOutGameDropPool;
|
||||||
private IDataTable<DRMuzzleComp> _drMuzzleComp;
|
private IDataTable<DRMuzzleComp> _drMuzzleComp;
|
||||||
private IDataTable<DRBearingComp> _drBearingComp;
|
private IDataTable<DRBearingComp> _drBearingComp;
|
||||||
private IDataTable<DRBaseComp> _drBaseComp;
|
private IDataTable<DRBaseComp> _drBaseComp;
|
||||||
|
private IDataTable<DRTag> _drTag;
|
||||||
private long _nextDropItemInstanceId = 1;
|
private long _nextDropItemInstanceId = 1;
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
_eligibleDropPoolBuffer.Clear();
|
_eligibleDropPoolBuffer.Clear();
|
||||||
_rarityRollWeightBuffer.Clear();
|
_rarityRollWeightBuffer.Clear();
|
||||||
|
_tagMinRarityByTag.Clear();
|
||||||
_nextDropItemInstanceId = 1;
|
_nextDropItemInstanceId = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -304,6 +307,35 @@ namespace GeometryTD.CustomComponent
|
||||||
return _drOutGameDropPool;
|
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)
|
private bool TryBuildDropItem(DROutGameDropPool row, out TowerCompItemData droppedItem)
|
||||||
{
|
{
|
||||||
droppedItem = null;
|
droppedItem = null;
|
||||||
|
|
@ -340,21 +372,34 @@ namespace GeometryTD.CustomComponent
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!EnsureTagMinRarityLookup())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
DRMuzzleComp config = _drMuzzleComp.GetDataRow(row.ItemId);
|
DRMuzzleComp config = _drMuzzleComp.GetDataRow(row.ItemId);
|
||||||
if (config == null)
|
if (config == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long instanceId = _nextDropItemInstanceId++;
|
||||||
|
RarityType rarity = InventoryRarityRuleService.NormalizeComponentRarity(row.Rarity);
|
||||||
droppedItem = new MuzzleCompItemData
|
droppedItem = new MuzzleCompItemData
|
||||||
{
|
{
|
||||||
InstanceId = _nextDropItemInstanceId++,
|
InstanceId = instanceId,
|
||||||
ConfigId = config.Id,
|
ConfigId = config.Id,
|
||||||
Name = config.Name,
|
Name = config.Name,
|
||||||
Rarity = InventoryRarityRuleService.NormalizeComponentRarity(row.Rarity),
|
Rarity = rarity,
|
||||||
Endurance = 100f,
|
Endurance = 100f,
|
||||||
Constraint = config.Constraint,
|
Constraint = config.Constraint,
|
||||||
Tags = config.PossibleTag != null ? (TagType[])config.PossibleTag.Clone() : Array.Empty<TagType>(),
|
Tags = InventoryTagRuleService.ResolveComponentTags(
|
||||||
|
config.PossibleTag,
|
||||||
|
rarity,
|
||||||
|
InventoryTagSourceType.Drop,
|
||||||
|
instanceId,
|
||||||
|
config.Id,
|
||||||
|
_tagMinRarityByTag),
|
||||||
AttackDamage = config.AttackDamage != null ? (int[])config.AttackDamage.Clone() : Array.Empty<int>(),
|
AttackDamage = config.AttackDamage != null ? (int[])config.AttackDamage.Clone() : Array.Empty<int>(),
|
||||||
DamageRandomRate = config.DamageRandomRate,
|
DamageRandomRate = config.DamageRandomRate,
|
||||||
AttackMethodType = config.AttackMethodType
|
AttackMethodType = config.AttackMethodType
|
||||||
|
|
@ -371,21 +416,34 @@ namespace GeometryTD.CustomComponent
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!EnsureTagMinRarityLookup())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
DRBearingComp config = _drBearingComp.GetDataRow(row.ItemId);
|
DRBearingComp config = _drBearingComp.GetDataRow(row.ItemId);
|
||||||
if (config == null)
|
if (config == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long instanceId = _nextDropItemInstanceId++;
|
||||||
|
RarityType rarity = InventoryRarityRuleService.NormalizeComponentRarity(row.Rarity);
|
||||||
droppedItem = new BearingCompItemData
|
droppedItem = new BearingCompItemData
|
||||||
{
|
{
|
||||||
InstanceId = _nextDropItemInstanceId++,
|
InstanceId = instanceId,
|
||||||
ConfigId = config.Id,
|
ConfigId = config.Id,
|
||||||
Name = config.Name,
|
Name = config.Name,
|
||||||
Rarity = InventoryRarityRuleService.NormalizeComponentRarity(row.Rarity),
|
Rarity = rarity,
|
||||||
Endurance = 100f,
|
Endurance = 100f,
|
||||||
Constraint = config.Constraint,
|
Constraint = config.Constraint,
|
||||||
Tags = config.PossibleTag != null ? (TagType[])config.PossibleTag.Clone() : Array.Empty<TagType>(),
|
Tags = InventoryTagRuleService.ResolveComponentTags(
|
||||||
|
config.PossibleTag,
|
||||||
|
rarity,
|
||||||
|
InventoryTagSourceType.Drop,
|
||||||
|
instanceId,
|
||||||
|
config.Id,
|
||||||
|
_tagMinRarityByTag),
|
||||||
RotateSpeed = config.RotateSpeed != null ? (float[])config.RotateSpeed.Clone() : Array.Empty<float>(),
|
RotateSpeed = config.RotateSpeed != null ? (float[])config.RotateSpeed.Clone() : Array.Empty<float>(),
|
||||||
AttackRange = config.AttackRange != null ? (float[])config.AttackRange.Clone() : Array.Empty<float>()
|
AttackRange = config.AttackRange != null ? (float[])config.AttackRange.Clone() : Array.Empty<float>()
|
||||||
};
|
};
|
||||||
|
|
@ -401,21 +459,34 @@ namespace GeometryTD.CustomComponent
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!EnsureTagMinRarityLookup())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
DRBaseComp config = _drBaseComp.GetDataRow(row.ItemId);
|
DRBaseComp config = _drBaseComp.GetDataRow(row.ItemId);
|
||||||
if (config == null)
|
if (config == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long instanceId = _nextDropItemInstanceId++;
|
||||||
|
RarityType rarity = InventoryRarityRuleService.NormalizeComponentRarity(row.Rarity);
|
||||||
droppedItem = new BaseCompItemData
|
droppedItem = new BaseCompItemData
|
||||||
{
|
{
|
||||||
InstanceId = _nextDropItemInstanceId++,
|
InstanceId = instanceId,
|
||||||
ConfigId = config.Id,
|
ConfigId = config.Id,
|
||||||
Name = config.Name,
|
Name = config.Name,
|
||||||
Rarity = InventoryRarityRuleService.NormalizeComponentRarity(row.Rarity),
|
Rarity = rarity,
|
||||||
Endurance = 100f,
|
Endurance = 100f,
|
||||||
Constraint = config.Constraint,
|
Constraint = config.Constraint,
|
||||||
Tags = config.PossibleTag != null ? (TagType[])config.PossibleTag.Clone() : Array.Empty<TagType>(),
|
Tags = InventoryTagRuleService.ResolveComponentTags(
|
||||||
|
config.PossibleTag,
|
||||||
|
rarity,
|
||||||
|
InventoryTagSourceType.Drop,
|
||||||
|
instanceId,
|
||||||
|
config.Id,
|
||||||
|
_tagMinRarityByTag),
|
||||||
AttackSpeed = config.AttackSpeed != null ? (float[])config.AttackSpeed.Clone() : Array.Empty<float>(),
|
AttackSpeed = config.AttackSpeed != null ? (float[])config.AttackSpeed.Clone() : Array.Empty<float>(),
|
||||||
AttackPropertyType = config.AttackPropertyType
|
AttackPropertyType = config.AttackPropertyType
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -107,8 +107,12 @@ namespace GeometryTD.CustomComponent
|
||||||
AttackSpeed = BuildLevelFloatArray(baseComp.AttackSpeed, baseComp.Rarity, baseConfig.AttackSpeedPerLevel),
|
AttackSpeed = BuildLevelFloatArray(baseComp.AttackSpeed, baseComp.Rarity, baseConfig.AttackSpeedPerLevel),
|
||||||
AttackMethodType = muzzleComp.AttackMethodType,
|
AttackMethodType = muzzleComp.AttackMethodType,
|
||||||
AttackPropertyType = baseComp.AttackPropertyType,
|
AttackPropertyType = baseComp.AttackPropertyType,
|
||||||
Tags = MergeTags(muzzleComp.Tags, bearingComp.Tags, baseComp.Tags)
|
TagRuntimes = TowerTagAggregationService.AggregateTowerTags(
|
||||||
|
muzzleComp.Tags,
|
||||||
|
bearingComp.Tags,
|
||||||
|
baseComp.Tags)
|
||||||
};
|
};
|
||||||
|
stats.Tags = TowerTagAggregationService.FlattenUniqueTags(stats.TagRuntimes);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -159,31 +163,6 @@ namespace GeometryTD.CustomComponent
|
||||||
return rarityBaseArray[rarityIndex];
|
return rarityBaseArray[rarityIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TagType[] MergeTags(params TagType[][] sources)
|
|
||||||
{
|
|
||||||
HashSet<TagType> uniqueTags = new HashSet<TagType>();
|
|
||||||
if (sources != null)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < sources.Length; i++)
|
|
||||||
{
|
|
||||||
TagType[] tags = sources[i];
|
|
||||||
if (tags == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int j = 0; j < tags.Length; j++)
|
|
||||||
{
|
|
||||||
uniqueTags.Add(tags[j]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TagType[] mergedTags = new TagType[uniqueTags.Count];
|
|
||||||
uniqueTags.CopyTo(mergedTags);
|
|
||||||
return mergedTags;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IDataTable<DRMuzzleComp> EnsureMuzzleTable()
|
private IDataTable<DRMuzzleComp> EnsureMuzzleTable()
|
||||||
{
|
{
|
||||||
_drMuzzleComp ??= GameEntry.DataTable.GetDataTable<DRMuzzleComp>();
|
_drMuzzleComp ??= GameEntry.DataTable.GetDataTable<DRMuzzleComp>();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GeometryTD.Definition
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
public sealed class TagRuntimeData
|
||||||
|
{
|
||||||
|
public TagType TagType { get; set; }
|
||||||
|
public int TotalStack { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 71784829e4844ae3acbd58fb4f88a1ed
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -18,6 +18,7 @@ namespace GeometryTD.Definition
|
||||||
public float[] AttackSpeed { get; set; }
|
public float[] AttackSpeed { get; set; }
|
||||||
public AttackMethodType AttackMethodType { get; set; }
|
public AttackMethodType AttackMethodType { get; set; }
|
||||||
public AttackPropertyType AttackPropertyType { get; set; }
|
public AttackPropertyType AttackPropertyType { get; set; }
|
||||||
|
public TagRuntimeData[] TagRuntimes { get; set; }
|
||||||
public TagType[] Tags { get; set; }
|
public TagType[] Tags { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
namespace GeometryTD.Definition
|
||||||
|
{
|
||||||
|
public enum InventoryTagSourceType : byte
|
||||||
|
{
|
||||||
|
Seed = 1,
|
||||||
|
Shop = 2,
|
||||||
|
Drop = 3,
|
||||||
|
Reward = 4,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a31a31d8d96e4d659fb5be2a8f76836d
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,166 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
TagType[] eligibleTags = GetEligibleTags(possibleTags, rarity, minRarityByTag);
|
||||||
|
if (eligibleTags.Length <= 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<TagType>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Random random = new Random(BuildStableSeed(rarity, sourceType, itemInstanceId, configId));
|
||||||
|
int tagBudget = ResolveTagBudget(rarity, random);
|
||||||
|
if (tagBudget <= 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<TagType>();
|
||||||
|
}
|
||||||
|
|
||||||
|
int finalCount = Math.Min(tagBudget, eligibleTags.Length);
|
||||||
|
List<TagType> pool = new List<TagType>(eligibleTags);
|
||||||
|
TagType[] result = new TagType[finalCount];
|
||||||
|
for (int i = 0; i < finalCount; i++)
|
||||||
|
{
|
||||||
|
int index = random.Next(0, pool.Count);
|
||||||
|
result[i] = pool[index];
|
||||||
|
pool.RemoveAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TagType[] GetEligibleTags(
|
||||||
|
IReadOnlyList<TagType> possibleTags,
|
||||||
|
RarityType rarity,
|
||||||
|
IReadOnlyDictionary<TagType, RarityType> minRarityByTag = null)
|
||||||
|
{
|
||||||
|
if (possibleTags == null || possibleTags.Count <= 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<TagType>();
|
||||||
|
}
|
||||||
|
|
||||||
|
RarityType normalizedRarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity);
|
||||||
|
IReadOnlyDictionary<TagType, RarityType> rarityLookup = minRarityByTag ?? DefaultMinRarityByTag;
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryGetMinRarity(tagType, rarityLookup, out RarityType minRarity))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedRarity < InventoryRarityRuleService.NormalizeComponentRarity(minRarity))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uniqueTags.Add(tagType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uniqueTags.Count <= 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<TagType>();
|
||||||
|
}
|
||||||
|
|
||||||
|
TagType[] result = new TagType[uniqueTags.Count];
|
||||||
|
uniqueTags.CopyTo(result);
|
||||||
|
Array.Sort(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int ResolveTagBudget(RarityType rarity, Random random)
|
||||||
|
{
|
||||||
|
RarityType normalizedRarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity);
|
||||||
|
random ??= new Random(0);
|
||||||
|
|
||||||
|
switch (normalizedRarity)
|
||||||
|
{
|
||||||
|
case RarityType.White:
|
||||||
|
return random.Next(0, 2);
|
||||||
|
case RarityType.Green:
|
||||||
|
return 1;
|
||||||
|
case RarityType.Blue:
|
||||||
|
return random.Next(1, 3);
|
||||||
|
case RarityType.Purple:
|
||||||
|
return 2;
|
||||||
|
case RarityType.Red:
|
||||||
|
return random.Next(2, 4);
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsSupportedLaunchTag(TagType tagType)
|
||||||
|
{
|
||||||
|
return SupportedLaunchTags.Contains(tagType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetMinRarity(
|
||||||
|
TagType tagType,
|
||||||
|
IReadOnlyDictionary<TagType, RarityType> rarityLookup,
|
||||||
|
out RarityType minRarity)
|
||||||
|
{
|
||||||
|
if (rarityLookup != null && rarityLookup.TryGetValue(tagType, out minRarity))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DefaultMinRarityByTag.TryGetValue(tagType, out minRarity))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
minRarity = RarityType.White;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int BuildStableSeed(
|
||||||
|
RarityType rarity,
|
||||||
|
InventoryTagSourceType sourceType,
|
||||||
|
long itemInstanceId,
|
||||||
|
int configId)
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
int seed = 17;
|
||||||
|
seed = seed * 31 + (int)InventoryRarityRuleService.NormalizeComponentRarity(rarity);
|
||||||
|
seed = seed * 31 + (int)sourceType;
|
||||||
|
seed = seed * 31 + configId;
|
||||||
|
seed = seed * 31 + (int)itemInstanceId;
|
||||||
|
seed = seed * 31 + (int)(itemInstanceId >> 32);
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 56c9c8fd48d246a882bfae5f8c8c02e7
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,134 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace GeometryTD.Definition
|
||||||
|
{
|
||||||
|
public static class TowerTagAggregationService
|
||||||
|
{
|
||||||
|
public static TagRuntimeData[] AggregateTowerTags(params TagType[][] componentTags)
|
||||||
|
{
|
||||||
|
Dictionary<TagType, int> stackByTag = new Dictionary<TagType, int>();
|
||||||
|
if (componentTags != null)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < componentTags.Length; i++)
|
||||||
|
{
|
||||||
|
TagType[] tags = componentTags[i];
|
||||||
|
if (tags == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0; j < tags.Length; j++)
|
||||||
|
{
|
||||||
|
TagType tagType = tags[j];
|
||||||
|
if (tagType == TagType.None || !Enum.IsDefined(typeof(TagType), tagType))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stackByTag.TryGetValue(tagType, out int stack))
|
||||||
|
{
|
||||||
|
stackByTag[tagType] = stack + 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stackByTag[tagType] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stackByTag.Count <= 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<TagRuntimeData>();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TagRuntimeData> runtimes = new List<TagRuntimeData>(stackByTag.Count);
|
||||||
|
foreach (KeyValuePair<TagType, int> pair in stackByTag)
|
||||||
|
{
|
||||||
|
runtimes.Add(new TagRuntimeData
|
||||||
|
{
|
||||||
|
TagType = pair.Key,
|
||||||
|
TotalStack = Math.Max(1, pair.Value)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
runtimes.Sort((left, right) => left.TagType.CompareTo(right.TagType));
|
||||||
|
return runtimes.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TagRuntimeData[] BuildRuntimeTagsFromUniqueTags(IReadOnlyList<TagType> tags)
|
||||||
|
{
|
||||||
|
if (tags == null || tags.Count <= 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<TagRuntimeData>();
|
||||||
|
}
|
||||||
|
|
||||||
|
HashSet<TagType> uniqueTags = new HashSet<TagType>();
|
||||||
|
for (int i = 0; i < tags.Count; i++)
|
||||||
|
{
|
||||||
|
TagType tagType = tags[i];
|
||||||
|
if (tagType == TagType.None || !Enum.IsDefined(typeof(TagType), tagType))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uniqueTags.Add(tagType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uniqueTags.Count <= 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<TagRuntimeData>();
|
||||||
|
}
|
||||||
|
|
||||||
|
TagType[] orderedTags = new TagType[uniqueTags.Count];
|
||||||
|
uniqueTags.CopyTo(orderedTags);
|
||||||
|
Array.Sort(orderedTags);
|
||||||
|
|
||||||
|
TagRuntimeData[] runtimes = new TagRuntimeData[orderedTags.Length];
|
||||||
|
for (int i = 0; i < orderedTags.Length; i++)
|
||||||
|
{
|
||||||
|
runtimes[i] = new TagRuntimeData
|
||||||
|
{
|
||||||
|
TagType = orderedTags[i],
|
||||||
|
TotalStack = 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return runtimes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TagType[] FlattenUniqueTags(IReadOnlyList<TagRuntimeData> tagRuntimes)
|
||||||
|
{
|
||||||
|
if (tagRuntimes == null || tagRuntimes.Count <= 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<TagType>();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TagType> tags = new List<TagType>(tagRuntimes.Count);
|
||||||
|
for (int i = 0; i < tagRuntimes.Count; i++)
|
||||||
|
{
|
||||||
|
TagRuntimeData runtime = tagRuntimes[i];
|
||||||
|
if (runtime == null || runtime.TagType == TagType.None || !Enum.IsDefined(typeof(TagType), runtime.TagType))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runtime.TotalStack <= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
tags.Add(runtime.TagType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tags.Count <= 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<TagType>();
|
||||||
|
}
|
||||||
|
|
||||||
|
tags.Sort();
|
||||||
|
return tags.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4a2b16218c9c4f90a90c12b975d9a5c6
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -18,7 +18,7 @@ namespace GeometryTD.UI
|
||||||
public string Title;
|
public string Title;
|
||||||
public string TypeText;
|
public string TypeText;
|
||||||
public string Description;
|
public string Description;
|
||||||
public TagType[] Tags;
|
public string[] TagTexts;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override UIFormType UIFormTypeId => UIFormType.CombatFinishForm;
|
protected override UIFormType UIFormTypeId => UIFormType.CombatFinishForm;
|
||||||
|
|
@ -138,7 +138,7 @@ namespace GeometryTD.UI
|
||||||
tower.Name,
|
tower.Name,
|
||||||
"Tower",
|
"Tower",
|
||||||
ItemDescUtility.BuildTowerDesc(tower, muzzleMap, bearingMap, baseMap) ?? string.Empty,
|
ItemDescUtility.BuildTowerDesc(tower, muzzleMap, bearingMap, baseMap) ?? string.Empty,
|
||||||
tower.Stats != null ? tower.Stats.Tags : null);
|
TagDisplayUtility.BuildTowerTagTexts(tower.Stats));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -166,7 +166,7 @@ namespace GeometryTD.UI
|
||||||
item.Name,
|
item.Name,
|
||||||
BuildComponentTypeText(item.SlotType),
|
BuildComponentTypeText(item.SlotType),
|
||||||
ItemDescUtility.BuildMuzzleDesc(item) ?? string.Empty,
|
ItemDescUtility.BuildMuzzleDesc(item) ?? string.Empty,
|
||||||
item.Tags);
|
TagDisplayUtility.BuildTagTexts(item.Tags));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -194,7 +194,7 @@ namespace GeometryTD.UI
|
||||||
item.Name,
|
item.Name,
|
||||||
BuildComponentTypeText(item.SlotType),
|
BuildComponentTypeText(item.SlotType),
|
||||||
ItemDescUtility.BuildBearingDesc(item) ?? string.Empty,
|
ItemDescUtility.BuildBearingDesc(item) ?? string.Empty,
|
||||||
item.Tags);
|
TagDisplayUtility.BuildTagTexts(item.Tags));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -222,7 +222,7 @@ namespace GeometryTD.UI
|
||||||
item.Name,
|
item.Name,
|
||||||
BuildComponentTypeText(item.SlotType),
|
BuildComponentTypeText(item.SlotType),
|
||||||
ItemDescUtility.BuildBaseDesc(item) ?? string.Empty,
|
ItemDescUtility.BuildBaseDesc(item) ?? string.Empty,
|
||||||
item.Tags);
|
TagDisplayUtility.BuildTagTexts(item.Tags));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,7 +261,7 @@ namespace GeometryTD.UI
|
||||||
return Color.white;
|
return Color.white;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddItemDescSeed(long itemId, string title, string typeText, string description, TagType[] tags)
|
private void AddItemDescSeed(long itemId, string title, string typeText, string description, string[] tagTexts)
|
||||||
{
|
{
|
||||||
if (itemId <= 0)
|
if (itemId <= 0)
|
||||||
{
|
{
|
||||||
|
|
@ -273,13 +273,13 @@ namespace GeometryTD.UI
|
||||||
Title = string.IsNullOrWhiteSpace(title) ? $"Item {itemId}" : title,
|
Title = string.IsNullOrWhiteSpace(title) ? $"Item {itemId}" : title,
|
||||||
TypeText = typeText ?? string.Empty,
|
TypeText = typeText ?? string.Empty,
|
||||||
Description = description ?? string.Empty,
|
Description = description ?? string.Empty,
|
||||||
Tags = CloneTags(tags)
|
TagTexts = CloneTagTexts(tagTexts)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TagType[] CloneTags(TagType[] tags)
|
private static string[] CloneTagTexts(string[] tagTexts)
|
||||||
{
|
{
|
||||||
return tags != null ? (TagType[])tags.Clone() : System.Array.Empty<TagType>();
|
return tagTexts != null ? (string[])tagTexts.Clone() : System.Array.Empty<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string BuildComponentTypeText(TowerCompSlotType slotType)
|
private static string BuildComponentTypeText(TowerCompSlotType slotType)
|
||||||
|
|
@ -374,7 +374,7 @@ namespace GeometryTD.UI
|
||||||
Description = seed.Description ?? string.Empty,
|
Description = seed.Description ?? string.Empty,
|
||||||
Price = 0,
|
Price = 0,
|
||||||
TargetPos = args.TargetPos,
|
TargetPos = args.TargetPos,
|
||||||
Tags = CloneTags(seed.Tags)
|
TagTexts = CloneTagTexts(seed.TagTexts)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -405,4 +405,3 @@ namespace GeometryTD.UI
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ namespace GeometryTD.UI
|
||||||
tower.Name,
|
tower.Name,
|
||||||
"Tower",
|
"Tower",
|
||||||
ItemDescUtility.BuildTowerDesc(tower, muzzleMap, bearingMap, baseMap) ?? string.Empty,
|
ItemDescUtility.BuildTowerDesc(tower, muzzleMap, bearingMap, baseMap) ?? string.Empty,
|
||||||
tower.Stats != null ? tower.Stats.Tags : null);
|
TagDisplayUtility.BuildTowerTagTexts(tower.Stats));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,7 +71,7 @@ namespace GeometryTD.UI
|
||||||
item.Name,
|
item.Name,
|
||||||
BuildComponentTypeText(item.SlotType),
|
BuildComponentTypeText(item.SlotType),
|
||||||
ItemDescUtility.BuildMuzzleDesc(item) ?? string.Empty,
|
ItemDescUtility.BuildMuzzleDesc(item) ?? string.Empty,
|
||||||
item.Tags);
|
TagDisplayUtility.BuildTagTexts(item.Tags));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,7 +99,7 @@ namespace GeometryTD.UI
|
||||||
item.Name,
|
item.Name,
|
||||||
BuildComponentTypeText(item.SlotType),
|
BuildComponentTypeText(item.SlotType),
|
||||||
ItemDescUtility.BuildBearingDesc(item) ?? string.Empty,
|
ItemDescUtility.BuildBearingDesc(item) ?? string.Empty,
|
||||||
item.Tags);
|
TagDisplayUtility.BuildTagTexts(item.Tags));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,7 +127,7 @@ namespace GeometryTD.UI
|
||||||
item.Name,
|
item.Name,
|
||||||
BuildComponentTypeText(item.SlotType),
|
BuildComponentTypeText(item.SlotType),
|
||||||
ItemDescUtility.BuildBaseDesc(item) ?? string.Empty,
|
ItemDescUtility.BuildBaseDesc(item) ?? string.Empty,
|
||||||
item.Tags);
|
TagDisplayUtility.BuildTagTexts(item.Tags));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,7 +169,7 @@ namespace GeometryTD.UI
|
||||||
_compAreaTowerIds.Add(itemContext.InstanceId);
|
_compAreaTowerIds.Add(itemContext.InstanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddItemDescSeed(long itemId, string title, string typeText, string description, TagType[] tags)
|
private void AddItemDescSeed(long itemId, string title, string typeText, string description, string[] tagTexts)
|
||||||
{
|
{
|
||||||
if (itemId <= 0)
|
if (itemId <= 0)
|
||||||
{
|
{
|
||||||
|
|
@ -181,15 +181,10 @@ namespace GeometryTD.UI
|
||||||
Title = string.IsNullOrWhiteSpace(title) ? $"Item {itemId}" : title,
|
Title = string.IsNullOrWhiteSpace(title) ? $"Item {itemId}" : title,
|
||||||
TypeText = typeText ?? string.Empty,
|
TypeText = typeText ?? string.Empty,
|
||||||
Description = description ?? string.Empty,
|
Description = description ?? string.Empty,
|
||||||
Tags = CloneTags(tags)
|
TagTexts = tagTexts != null ? (string[])tagTexts.Clone() : Array.Empty<string>()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TagType[] CloneTags(TagType[] tags)
|
|
||||||
{
|
|
||||||
return tags != null ? (TagType[])tags.Clone() : Array.Empty<TagType>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string BuildComponentTypeText(TowerCompSlotType slotType)
|
private static string BuildComponentTypeText(TowerCompSlotType slotType)
|
||||||
{
|
{
|
||||||
return slotType switch
|
return slotType switch
|
||||||
|
|
@ -391,4 +386,3 @@ namespace GeometryTD.UI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ namespace GeometryTD.UI
|
||||||
public string Title;
|
public string Title;
|
||||||
public string TypeText;
|
public string TypeText;
|
||||||
public string Description;
|
public string Description;
|
||||||
public TagType[] Tags;
|
public string[] TagTexts;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override UIFormType UIFormTypeId => UIFormType.RepoForm;
|
protected override UIFormType UIFormTypeId => UIFormType.RepoForm;
|
||||||
|
|
@ -151,7 +151,7 @@ namespace GeometryTD.UI
|
||||||
Description = seed.Description ?? string.Empty,
|
Description = seed.Description ?? string.Empty,
|
||||||
Price = 0,
|
Price = 0,
|
||||||
TargetPos = args.TargetPos,
|
TargetPos = args.TargetPos,
|
||||||
Tags = CloneTags(seed.Tags)
|
TagTexts = CloneTagTexts(seed.TagTexts)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -301,5 +301,9 @@ namespace GeometryTD.UI
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
private static string[] CloneTagTexts(string[] tagTexts)
|
||||||
|
{
|
||||||
|
return tagTexts != null ? (string[])tagTexts.Clone() : System.Array.Empty<string>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using GeometryTD.Definition;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace GeometryTD.UI
|
namespace GeometryTD.UI
|
||||||
|
|
@ -11,6 +9,6 @@ namespace GeometryTD.UI
|
||||||
public string Description;
|
public string Description;
|
||||||
public int Price;
|
public int Price;
|
||||||
public Vector3 TargetPos;
|
public Vector3 TargetPos;
|
||||||
public List<TagType> Tags;
|
public TagItemContext[] Tags;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using GameFramework.Event;
|
using GameFramework.Event;
|
||||||
|
using GeometryTD.CustomUtility;
|
||||||
using GeometryTD.Definition;
|
using GeometryTD.Definition;
|
||||||
using UnityGameFramework.Runtime;
|
using UnityGameFramework.Runtime;
|
||||||
|
|
||||||
|
|
@ -35,18 +35,33 @@ namespace GeometryTD.UI
|
||||||
Description = rawData.Description,
|
Description = rawData.Description,
|
||||||
Price = rawData.Price,
|
Price = rawData.Price,
|
||||||
TargetPos = rawData.TargetPos,
|
TargetPos = rawData.TargetPos,
|
||||||
Tags = BuildTags(rawData.Tags)
|
Tags = BuildTags(rawData)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<TagType> BuildTags(TagType[] tags)
|
private static TagItemContext[] BuildTags(ItemDescFormRawData rawData)
|
||||||
{
|
{
|
||||||
if (tags == null || tags.Length <= 0)
|
string[] tagTexts = rawData?.TagTexts;
|
||||||
|
if ((tagTexts == null || tagTexts.Length <= 0) && rawData?.Tags != null)
|
||||||
{
|
{
|
||||||
return new List<TagType>();
|
tagTexts = TagDisplayUtility.BuildTagTexts(rawData.Tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new List<TagType>(tags);
|
if (tagTexts == null || tagTexts.Length <= 0)
|
||||||
|
{
|
||||||
|
return System.Array.Empty<TagItemContext>();
|
||||||
|
}
|
||||||
|
|
||||||
|
TagItemContext[] contexts = new TagItemContext[tagTexts.Length];
|
||||||
|
for (int i = 0; i < tagTexts.Length; i++)
|
||||||
|
{
|
||||||
|
contexts[i] = new TagItemContext
|
||||||
|
{
|
||||||
|
TagName = tagTexts[i] ?? string.Empty
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return contexts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int? OpenUI(ItemDescFormRawData rawData)
|
public int? OpenUI(ItemDescFormRawData rawData)
|
||||||
|
|
|
||||||
|
|
@ -179,44 +179,24 @@ namespace GeometryTD.UI
|
||||||
|
|
||||||
private static TagItemContext[] BuildTagContexts(TagType[] tags)
|
private static TagItemContext[] BuildTagContexts(TagType[] tags)
|
||||||
{
|
{
|
||||||
if (tags == null || tags.Length <= 0)
|
string[] tagTexts = TagDisplayUtility.BuildTagTexts(tags);
|
||||||
|
if (tagTexts == null || tagTexts.Length <= 0)
|
||||||
{
|
{
|
||||||
return System.Array.Empty<TagItemContext>();
|
return System.Array.Empty<TagItemContext>();
|
||||||
}
|
}
|
||||||
|
|
||||||
TagItemContext[] contexts = new TagItemContext[tags.Length];
|
TagItemContext[] contexts = new TagItemContext[tagTexts.Length];
|
||||||
for (int i = 0; i < tags.Length; i++)
|
for (int i = 0; i < tagTexts.Length; i++)
|
||||||
{
|
{
|
||||||
TagType tagType = tags[i];
|
|
||||||
contexts[i] = new TagItemContext
|
contexts[i] = new TagItemContext
|
||||||
{
|
{
|
||||||
TagName = ResolveTagName(tagType)
|
TagName = tagTexts[i]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return contexts;
|
return contexts;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ResolveTagName(TagType tagType)
|
|
||||||
{
|
|
||||||
if (tagType == TagType.None)
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
var tagTable = GameEntry.DataTable.GetDataTable<DRTag>();
|
|
||||||
if (tagTable != null)
|
|
||||||
{
|
|
||||||
DRTag tagRow = tagTable.GetDataRow((int)tagType);
|
|
||||||
if (tagRow != null && !string.IsNullOrWhiteSpace(tagRow.Name))
|
|
||||||
{
|
|
||||||
return tagRow.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tagType.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnRewardSelected(object sender, GameEventArgs e)
|
private void OnRewardSelected(object sender, GameEventArgs e)
|
||||||
{
|
{
|
||||||
if (!IsEventFromCurrentForm(sender) || !(e is RewardSelectItemSelectedEventArgs args))
|
if (!IsEventFromCurrentForm(sender) || !(e is RewardSelectItemSelectedEventArgs args))
|
||||||
|
|
|
||||||
|
|
@ -11,5 +11,6 @@ namespace GeometryTD.UI
|
||||||
public int Price;
|
public int Price;
|
||||||
public Vector3 TargetPos;
|
public Vector3 TargetPos;
|
||||||
public TagType[] Tags;
|
public TagType[] Tags;
|
||||||
|
public string[] TagTexts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using GeometryTD.DataTable;
|
|
||||||
using GeometryTD.Definition;
|
|
||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
|
@ -249,11 +247,11 @@ namespace GeometryTD.UI
|
||||||
return value >= min && value <= max;
|
return value >= min && value <= max;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshTags(List<TagType> tags)
|
private void RefreshTags(TagItemContext[] tags)
|
||||||
{
|
{
|
||||||
ClearTags();
|
ClearTags();
|
||||||
|
|
||||||
if (_tagAreaParent == null || _tagItemPrefab == null || tags == null || tags.Count <= 0)
|
if (_tagAreaParent == null || _tagItemPrefab == null || tags == null || tags.Length <= 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -263,10 +261,10 @@ namespace GeometryTD.UI
|
||||||
_tagItemPrefab.SetActive(false);
|
_tagItemPrefab.SetActive(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < tags.Count; i++)
|
for (int i = 0; i < tags.Length; i++)
|
||||||
{
|
{
|
||||||
string tagName = ResolveTagName(tags[i]);
|
TagItemContext tagContext = tags[i];
|
||||||
if (string.IsNullOrWhiteSpace(tagName))
|
if (tagContext == null || string.IsNullOrWhiteSpace(tagContext.TagName))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -276,17 +274,14 @@ namespace GeometryTD.UI
|
||||||
|
|
||||||
if (tagGo.TryGetComponent(out TagItem tagItem))
|
if (tagGo.TryGetComponent(out TagItem tagItem))
|
||||||
{
|
{
|
||||||
tagItem.OnInit(new TagItemContext
|
tagItem.OnInit(tagContext);
|
||||||
{
|
|
||||||
TagName = tagName
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TMP_Text tagText = tagGo.GetComponentInChildren<TMP_Text>(true);
|
TMP_Text tagText = tagGo.GetComponentInChildren<TMP_Text>(true);
|
||||||
if (tagText != null)
|
if (tagText != null)
|
||||||
{
|
{
|
||||||
tagText.text = tagName;
|
tagText.text = tagContext.TagName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -308,26 +303,6 @@ namespace GeometryTD.UI
|
||||||
_runtimeTagItems.Clear();
|
_runtimeTagItems.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ResolveTagName(TagType tagType)
|
|
||||||
{
|
|
||||||
if (tagType == TagType.None)
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
var tagTable = GameEntry.DataTable.GetDataTable<DRTag>();
|
|
||||||
if (tagTable != null)
|
|
||||||
{
|
|
||||||
DRTag tagRow = tagTable.GetDataRow((int)tagType);
|
|
||||||
if (tagRow != null && !string.IsNullOrWhiteSpace(tagRow.Name))
|
|
||||||
{
|
|
||||||
return tagRow.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tagType.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CloseSelf()
|
private void CloseSelf()
|
||||||
{
|
{
|
||||||
GameEntry.UI.CloseUIForm(this);
|
GameEntry.UI.CloseUIForm(this);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using GeometryTD.CustomEvent;
|
using GeometryTD.CustomEvent;
|
||||||
using GeometryTD.Definition;
|
using GeometryTD.Definition;
|
||||||
using GameFramework.Event;
|
using GameFramework.Event;
|
||||||
|
using GeometryTD.CustomUtility;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityGameFramework.Runtime;
|
using UnityGameFramework.Runtime;
|
||||||
|
|
||||||
|
|
@ -165,18 +166,7 @@ namespace GeometryTD.UI
|
||||||
|
|
||||||
private static string[] BuildTagTexts(TagType[] tags)
|
private static string[] BuildTagTexts(TagType[] tags)
|
||||||
{
|
{
|
||||||
if (tags == null || tags.Length <= 0)
|
return TagDisplayUtility.BuildTagTexts(tags);
|
||||||
{
|
|
||||||
return System.Array.Empty<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
string[] results = new string[tags.Length];
|
|
||||||
for (int i = 0; i < tags.Length; i++)
|
|
||||||
{
|
|
||||||
results[i] = tags[i].ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,13 @@ namespace GeometryTD.UI
|
||||||
|
|
||||||
private readonly List<GoodsItemRawData> _currentGoods = new List<GoodsItemRawData>(GoodsCount);
|
private readonly List<GoodsItemRawData> _currentGoods = new List<GoodsItemRawData>(GoodsCount);
|
||||||
private readonly List<DRShopPrice> _shopPriceRows = new List<DRShopPrice>();
|
private readonly List<DRShopPrice> _shopPriceRows = new List<DRShopPrice>();
|
||||||
|
private readonly Dictionary<TagType, RarityType> _tagMinRarityByTag = new Dictionary<TagType, RarityType>();
|
||||||
|
|
||||||
private IDataTable<DRShopPrice> _shopPriceTable;
|
private IDataTable<DRShopPrice> _shopPriceTable;
|
||||||
private IDataTable<DRMuzzleComp> _muzzleCompTable;
|
private IDataTable<DRMuzzleComp> _muzzleCompTable;
|
||||||
private IDataTable<DRBearingComp> _bearingCompTable;
|
private IDataTable<DRBearingComp> _bearingCompTable;
|
||||||
private IDataTable<DRBaseComp> _baseCompTable;
|
private IDataTable<DRBaseComp> _baseCompTable;
|
||||||
|
private IDataTable<DRTag> _tagTable;
|
||||||
|
|
||||||
public bool PrepareForOpen()
|
public bool PrepareForOpen()
|
||||||
{
|
{
|
||||||
|
|
@ -99,8 +101,9 @@ namespace GeometryTD.UI
|
||||||
_muzzleCompTable ??= GameEntry.DataTable.GetDataTable<DRMuzzleComp>();
|
_muzzleCompTable ??= GameEntry.DataTable.GetDataTable<DRMuzzleComp>();
|
||||||
_bearingCompTable ??= GameEntry.DataTable.GetDataTable<DRBearingComp>();
|
_bearingCompTable ??= GameEntry.DataTable.GetDataTable<DRBearingComp>();
|
||||||
_baseCompTable ??= GameEntry.DataTable.GetDataTable<DRBaseComp>();
|
_baseCompTable ??= GameEntry.DataTable.GetDataTable<DRBaseComp>();
|
||||||
|
_tagTable ??= GameEntry.DataTable.GetDataTable<DRTag>();
|
||||||
|
|
||||||
if (_shopPriceTable == null || _muzzleCompTable == null || _bearingCompTable == null || _baseCompTable == null)
|
if (_shopPriceTable == null || _muzzleCompTable == null || _bearingCompTable == null || _baseCompTable == null || _tagTable == null)
|
||||||
{
|
{
|
||||||
Log.Warning("ShopFormUseCase.EnsureTables() failed. Missing required data tables.");
|
Log.Warning("ShopFormUseCase.EnsureTables() failed. Missing required data tables.");
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -124,10 +127,13 @@ namespace GeometryTD.UI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EnsureTagMinRarityLookup();
|
||||||
|
|
||||||
return _shopPriceRows.Count > 0 &&
|
return _shopPriceRows.Count > 0 &&
|
||||||
_muzzleCompTable.Count > 0 &&
|
_muzzleCompTable.Count > 0 &&
|
||||||
_bearingCompTable.Count > 0 &&
|
_bearingCompTable.Count > 0 &&
|
||||||
_baseCompTable.Count > 0;
|
_baseCompTable.Count > 0 &&
|
||||||
|
_tagMinRarityByTag.Count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryBuildRandomGoodsItem(int goodsIndex, out GoodsItemRawData goodsItem)
|
private bool TryBuildRandomGoodsItem(int goodsIndex, out GoodsItemRawData goodsItem)
|
||||||
|
|
@ -176,15 +182,23 @@ namespace GeometryTD.UI
|
||||||
{
|
{
|
||||||
DRMuzzleComp[] rows = _muzzleCompTable.GetAllDataRows();
|
DRMuzzleComp[] rows = _muzzleCompTable.GetAllDataRows();
|
||||||
DRMuzzleComp config = rows[UnityEngine.Random.Range(0, rows.Length)];
|
DRMuzzleComp config = rows[UnityEngine.Random.Range(0, rows.Length)];
|
||||||
|
long instanceId = _nextTempInstanceId++;
|
||||||
|
RarityType normalizedRarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity);
|
||||||
return new MuzzleCompItemData
|
return new MuzzleCompItemData
|
||||||
{
|
{
|
||||||
InstanceId = _nextTempInstanceId++,
|
InstanceId = instanceId,
|
||||||
ConfigId = config.Id,
|
ConfigId = config.Id,
|
||||||
Name = config.Name,
|
Name = config.Name,
|
||||||
Rarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity),
|
Rarity = normalizedRarity,
|
||||||
Endurance = 100f,
|
Endurance = 100f,
|
||||||
Constraint = config.Constraint,
|
Constraint = config.Constraint,
|
||||||
Tags = config.PossibleTag != null ? (TagType[])config.PossibleTag.Clone() : Array.Empty<TagType>(),
|
Tags = InventoryTagRuleService.ResolveComponentTags(
|
||||||
|
config.PossibleTag,
|
||||||
|
normalizedRarity,
|
||||||
|
InventoryTagSourceType.Shop,
|
||||||
|
instanceId,
|
||||||
|
config.Id,
|
||||||
|
_tagMinRarityByTag),
|
||||||
AttackDamage = config.AttackDamage != null ? (int[])config.AttackDamage.Clone() : Array.Empty<int>(),
|
AttackDamage = config.AttackDamage != null ? (int[])config.AttackDamage.Clone() : Array.Empty<int>(),
|
||||||
DamageRandomRate = config.DamageRandomRate,
|
DamageRandomRate = config.DamageRandomRate,
|
||||||
AttackMethodType = config.AttackMethodType
|
AttackMethodType = config.AttackMethodType
|
||||||
|
|
@ -195,15 +209,23 @@ namespace GeometryTD.UI
|
||||||
{
|
{
|
||||||
DRBearingComp[] rows = _bearingCompTable.GetAllDataRows();
|
DRBearingComp[] rows = _bearingCompTable.GetAllDataRows();
|
||||||
DRBearingComp config = rows[UnityEngine.Random.Range(0, rows.Length)];
|
DRBearingComp config = rows[UnityEngine.Random.Range(0, rows.Length)];
|
||||||
|
long instanceId = _nextTempInstanceId++;
|
||||||
|
RarityType normalizedRarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity);
|
||||||
return new BearingCompItemData
|
return new BearingCompItemData
|
||||||
{
|
{
|
||||||
InstanceId = _nextTempInstanceId++,
|
InstanceId = instanceId,
|
||||||
ConfigId = config.Id,
|
ConfigId = config.Id,
|
||||||
Name = config.Name,
|
Name = config.Name,
|
||||||
Rarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity),
|
Rarity = normalizedRarity,
|
||||||
Endurance = 100f,
|
Endurance = 100f,
|
||||||
Constraint = config.Constraint,
|
Constraint = config.Constraint,
|
||||||
Tags = config.PossibleTag != null ? (TagType[])config.PossibleTag.Clone() : Array.Empty<TagType>(),
|
Tags = InventoryTagRuleService.ResolveComponentTags(
|
||||||
|
config.PossibleTag,
|
||||||
|
normalizedRarity,
|
||||||
|
InventoryTagSourceType.Shop,
|
||||||
|
instanceId,
|
||||||
|
config.Id,
|
||||||
|
_tagMinRarityByTag),
|
||||||
RotateSpeed = config.RotateSpeed != null ? (float[])config.RotateSpeed.Clone() : Array.Empty<float>(),
|
RotateSpeed = config.RotateSpeed != null ? (float[])config.RotateSpeed.Clone() : Array.Empty<float>(),
|
||||||
AttackRange = config.AttackRange != null ? (float[])config.AttackRange.Clone() : Array.Empty<float>()
|
AttackRange = config.AttackRange != null ? (float[])config.AttackRange.Clone() : Array.Empty<float>()
|
||||||
};
|
};
|
||||||
|
|
@ -213,20 +235,49 @@ namespace GeometryTD.UI
|
||||||
{
|
{
|
||||||
DRBaseComp[] rows = _baseCompTable.GetAllDataRows();
|
DRBaseComp[] rows = _baseCompTable.GetAllDataRows();
|
||||||
DRBaseComp config = rows[UnityEngine.Random.Range(0, rows.Length)];
|
DRBaseComp config = rows[UnityEngine.Random.Range(0, rows.Length)];
|
||||||
|
long instanceId = _nextTempInstanceId++;
|
||||||
|
RarityType normalizedRarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity);
|
||||||
return new BaseCompItemData
|
return new BaseCompItemData
|
||||||
{
|
{
|
||||||
InstanceId = _nextTempInstanceId++,
|
InstanceId = instanceId,
|
||||||
ConfigId = config.Id,
|
ConfigId = config.Id,
|
||||||
Name = config.Name,
|
Name = config.Name,
|
||||||
Rarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity),
|
Rarity = normalizedRarity,
|
||||||
Endurance = 100f,
|
Endurance = 100f,
|
||||||
Constraint = config.Constraint,
|
Constraint = config.Constraint,
|
||||||
Tags = config.PossibleTag != null ? (TagType[])config.PossibleTag.Clone() : Array.Empty<TagType>(),
|
Tags = InventoryTagRuleService.ResolveComponentTags(
|
||||||
|
config.PossibleTag,
|
||||||
|
normalizedRarity,
|
||||||
|
InventoryTagSourceType.Shop,
|
||||||
|
instanceId,
|
||||||
|
config.Id,
|
||||||
|
_tagMinRarityByTag),
|
||||||
AttackSpeed = config.AttackSpeed != null ? (float[])config.AttackSpeed.Clone() : Array.Empty<float>(),
|
AttackSpeed = config.AttackSpeed != null ? (float[])config.AttackSpeed.Clone() : Array.Empty<float>(),
|
||||||
AttackPropertyType = config.AttackPropertyType
|
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)
|
private int ResolveRandomPrice(RarityType rarity)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _shopPriceRows.Count; i++)
|
for (int i = 0; i < _shopPriceRows.Count; i++)
|
||||||
|
|
|
||||||
|
|
@ -184,6 +184,7 @@ namespace GeometryTD.CustomUtility
|
||||||
AttackSpeed = CloneFloatArray(source.AttackSpeed),
|
AttackSpeed = CloneFloatArray(source.AttackSpeed),
|
||||||
AttackMethodType = source.AttackMethodType,
|
AttackMethodType = source.AttackMethodType,
|
||||||
AttackPropertyType = source.AttackPropertyType,
|
AttackPropertyType = source.AttackPropertyType,
|
||||||
|
TagRuntimes = CloneTagRuntimes(source.TagRuntimes),
|
||||||
Tags = CloneTags(source.Tags)
|
Tags = CloneTags(source.Tags)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -202,9 +203,34 @@ namespace GeometryTD.CustomUtility
|
||||||
{
|
{
|
||||||
return source != null ? (TagType[])source.Clone() : Array.Empty<TagType>();
|
return source != null ? (TagType[])source.Clone() : Array.Empty<TagType>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static TagRuntimeData[] CloneTagRuntimes(TagRuntimeData[] source)
|
||||||
|
{
|
||||||
|
if (source == null || source.Length <= 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<TagRuntimeData>();
|
||||||
|
}
|
||||||
|
|
||||||
|
TagRuntimeData[] cloned = new TagRuntimeData[source.Length];
|
||||||
|
for (int i = 0; i < source.Length; i++)
|
||||||
|
{
|
||||||
|
TagRuntimeData runtime = source[i];
|
||||||
|
if (runtime == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cloned[i] = new TagRuntimeData
|
||||||
|
{
|
||||||
|
TagType = runtime.TagType,
|
||||||
|
TotalStack = runtime.TotalStack
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return cloned;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,14 @@ namespace GeometryTD.CustomUtility
|
||||||
{
|
{
|
||||||
public static class InventorySeedUtility
|
public static class InventorySeedUtility
|
||||||
{
|
{
|
||||||
|
private static readonly TagType[] FirePool = { TagType.Fire };
|
||||||
|
private static readonly TagType[] IceFreezePool = { TagType.Ice, TagType.FreezeMask };
|
||||||
|
private static readonly TagType[] CritPiercePool = { TagType.Pierce, TagType.Crit };
|
||||||
|
private static readonly TagType[] IceShatterPool = { TagType.Ice, TagType.Shatter };
|
||||||
|
private static readonly TagType[] PierceOverpenetratePool = { TagType.Pierce, TagType.Overpenetrate };
|
||||||
|
private static readonly TagType[] IceAbsoluteZeroPool = { TagType.Ice, TagType.AbsoluteZero };
|
||||||
|
private static readonly TagType[] PierceExecutionPool = { TagType.Pierce, TagType.Execution };
|
||||||
|
|
||||||
public static BackpackInventoryData CreateSampleInventory()
|
public static BackpackInventoryData CreateSampleInventory()
|
||||||
{
|
{
|
||||||
BackpackInventoryData inventory = new BackpackInventoryData
|
BackpackInventoryData inventory = new BackpackInventoryData
|
||||||
|
|
@ -23,7 +31,7 @@ namespace GeometryTD.CustomUtility
|
||||||
DamageRandomRate = 0.05f,
|
DamageRandomRate = 0.05f,
|
||||||
AttackMethodType = AttackMethodType.NormalBullet,
|
AttackMethodType = AttackMethodType.NormalBullet,
|
||||||
Constraint = string.Empty,
|
Constraint = string.Empty,
|
||||||
Tags = new[] { TagType.Fire }
|
Tags = ResolveSeedTags(FirePool, RarityType.Green, 10001, 1)
|
||||||
};
|
};
|
||||||
|
|
||||||
BearingCompItemData bearing = new BearingCompItemData
|
BearingCompItemData bearing = new BearingCompItemData
|
||||||
|
|
@ -37,7 +45,7 @@ namespace GeometryTD.CustomUtility
|
||||||
RotateSpeed = new[] { 100f, 120f, 130f, 140f, 150f },
|
RotateSpeed = new[] { 100f, 120f, 130f, 140f, 150f },
|
||||||
AttackRange = new[] { 3f, 4f, 5f, 6f, 8f },
|
AttackRange = new[] { 3f, 4f, 5f, 6f, 8f },
|
||||||
Constraint = string.Empty,
|
Constraint = string.Empty,
|
||||||
Tags = new[] { TagType.Fire }
|
Tags = ResolveSeedTags(FirePool, RarityType.Green, 20001, 1)
|
||||||
};
|
};
|
||||||
|
|
||||||
BaseCompItemData baseComp = new BaseCompItemData
|
BaseCompItemData baseComp = new BaseCompItemData
|
||||||
|
|
@ -51,7 +59,7 @@ namespace GeometryTD.CustomUtility
|
||||||
AttackSpeed = new[] { 1f, 2f, 3f, 3.5f, 0.7f },
|
AttackSpeed = new[] { 1f, 2f, 3f, 3.5f, 0.7f },
|
||||||
AttackPropertyType = AttackPropertyType.Fire,
|
AttackPropertyType = AttackPropertyType.Fire,
|
||||||
Constraint = string.Empty,
|
Constraint = string.Empty,
|
||||||
Tags = new[] { TagType.Fire }
|
Tags = ResolveSeedTags(FirePool, RarityType.Green, 30001, 1)
|
||||||
};
|
};
|
||||||
|
|
||||||
TowerItemData tower = new TowerItemData
|
TowerItemData tower = new TowerItemData
|
||||||
|
|
@ -75,9 +83,13 @@ namespace GeometryTD.CustomUtility
|
||||||
AttackSpeed = new[] { 1.0f, 1.2f, 1.3f, 1.4f, 0.5f },
|
AttackSpeed = new[] { 1.0f, 1.2f, 1.3f, 1.4f, 0.5f },
|
||||||
AttackMethodType = AttackMethodType.NormalBullet,
|
AttackMethodType = AttackMethodType.NormalBullet,
|
||||||
AttackPropertyType = AttackPropertyType.Fire,
|
AttackPropertyType = AttackPropertyType.Fire,
|
||||||
Tags = new[] { TagType.Fire, TagType.BurnSpread }
|
TagRuntimes = TowerTagAggregationService.AggregateTowerTags(
|
||||||
|
muzzle.Tags,
|
||||||
|
bearing.Tags,
|
||||||
|
baseComp.Tags)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
tower.Stats.Tags = TowerTagAggregationService.FlattenUniqueTags(tower.Stats.TagRuntimes);
|
||||||
|
|
||||||
inventory.MuzzleComponents.Add(muzzle);
|
inventory.MuzzleComponents.Add(muzzle);
|
||||||
inventory.BaseComponents.Add(baseComp);
|
inventory.BaseComponents.Add(baseComp);
|
||||||
|
|
@ -96,7 +108,7 @@ namespace GeometryTD.CustomUtility
|
||||||
DamageRandomRate = 0.01f,
|
DamageRandomRate = 0.01f,
|
||||||
AttackMethodType = AttackMethodType.NormalBullet,
|
AttackMethodType = AttackMethodType.NormalBullet,
|
||||||
Constraint = string.Empty,
|
Constraint = string.Empty,
|
||||||
Tags = new[] { TagType.Ice, TagType.FreezeMask }
|
Tags = ResolveSeedTags(IceFreezePool, RarityType.Blue, 10002, 2)
|
||||||
});
|
});
|
||||||
|
|
||||||
inventory.MuzzleComponents.Add(new MuzzleCompItemData
|
inventory.MuzzleComponents.Add(new MuzzleCompItemData
|
||||||
|
|
@ -111,7 +123,7 @@ namespace GeometryTD.CustomUtility
|
||||||
DamageRandomRate = 0.02f,
|
DamageRandomRate = 0.02f,
|
||||||
AttackMethodType = AttackMethodType.NormalBullet,
|
AttackMethodType = AttackMethodType.NormalBullet,
|
||||||
Constraint = string.Empty,
|
Constraint = string.Empty,
|
||||||
Tags = new[] { TagType.Pierce, TagType.Crit }
|
Tags = ResolveSeedTags(CritPiercePool, RarityType.Purple, 10003, 3)
|
||||||
});
|
});
|
||||||
|
|
||||||
inventory.BearingComponents.Add(new BearingCompItemData
|
inventory.BearingComponents.Add(new BearingCompItemData
|
||||||
|
|
@ -125,7 +137,7 @@ namespace GeometryTD.CustomUtility
|
||||||
RotateSpeed = new[] { 200f, 250f, 300f, 320f, 350f },
|
RotateSpeed = new[] { 200f, 250f, 300f, 320f, 350f },
|
||||||
AttackRange = new[] { 6f, 6.5f, 7f, 8f, 8f },
|
AttackRange = new[] { 6f, 6.5f, 7f, 8f, 8f },
|
||||||
Constraint = string.Empty,
|
Constraint = string.Empty,
|
||||||
Tags = new[] { TagType.Ice, TagType.Shatter }
|
Tags = ResolveSeedTags(IceShatterPool, RarityType.Blue, 20002, 2)
|
||||||
});
|
});
|
||||||
|
|
||||||
inventory.BearingComponents.Add(new BearingCompItemData
|
inventory.BearingComponents.Add(new BearingCompItemData
|
||||||
|
|
@ -139,7 +151,7 @@ namespace GeometryTD.CustomUtility
|
||||||
RotateSpeed = new[] { 60f, 70f, 80f, 90f, 100f },
|
RotateSpeed = new[] { 60f, 70f, 80f, 90f, 100f },
|
||||||
AttackRange = new[] { 4f, 4.5f, 5f, 5.5f, 6f },
|
AttackRange = new[] { 4f, 4.5f, 5f, 5.5f, 6f },
|
||||||
Constraint = string.Empty,
|
Constraint = string.Empty,
|
||||||
Tags = new[] { TagType.Pierce, TagType.Overpenetrate }
|
Tags = ResolveSeedTags(PierceOverpenetratePool, RarityType.Purple, 20003, 3)
|
||||||
});
|
});
|
||||||
|
|
||||||
inventory.BaseComponents.Add(new BaseCompItemData
|
inventory.BaseComponents.Add(new BaseCompItemData
|
||||||
|
|
@ -153,7 +165,7 @@ namespace GeometryTD.CustomUtility
|
||||||
AttackSpeed = new[] { 4f, 4.2f, 4.4f, 4.6f, 4.8f },
|
AttackSpeed = new[] { 4f, 4.2f, 4.4f, 4.6f, 4.8f },
|
||||||
AttackPropertyType = AttackPropertyType.Ice,
|
AttackPropertyType = AttackPropertyType.Ice,
|
||||||
Constraint = string.Empty,
|
Constraint = string.Empty,
|
||||||
Tags = new[] { TagType.Ice, TagType.AbsoluteZero }
|
Tags = ResolveSeedTags(IceAbsoluteZeroPool, RarityType.Blue, 30002, 2)
|
||||||
});
|
});
|
||||||
|
|
||||||
inventory.BaseComponents.Add(new BaseCompItemData
|
inventory.BaseComponents.Add(new BaseCompItemData
|
||||||
|
|
@ -167,12 +179,22 @@ namespace GeometryTD.CustomUtility
|
||||||
AttackSpeed = new[] { 1f, 1f, 1f, 1f, 1f },
|
AttackSpeed = new[] { 1f, 1f, 1f, 1f, 1f },
|
||||||
AttackPropertyType = AttackPropertyType.Physics,
|
AttackPropertyType = AttackPropertyType.Physics,
|
||||||
Constraint = string.Empty,
|
Constraint = string.Empty,
|
||||||
Tags = new[] { TagType.Pierce, TagType.Execution }
|
Tags = ResolveSeedTags(PierceExecutionPool, RarityType.Purple, 30003, 3)
|
||||||
});
|
});
|
||||||
|
|
||||||
inventory.ParticipantTowerInstanceIds.Add(90001);
|
inventory.ParticipantTowerInstanceIds.Add(90001);
|
||||||
|
|
||||||
return inventory;
|
return inventory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static TagType[] ResolveSeedTags(TagType[] possibleTags, RarityType rarity, long instanceId, int configId)
|
||||||
|
{
|
||||||
|
return InventoryTagRuleService.ResolveComponentTags(
|
||||||
|
possibleTags,
|
||||||
|
rarity,
|
||||||
|
InventoryTagSourceType.Seed,
|
||||||
|
instanceId,
|
||||||
|
configId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using GeometryTD.DataTable;
|
||||||
|
using GeometryTD.Definition;
|
||||||
|
|
||||||
|
namespace GeometryTD.CustomUtility
|
||||||
|
{
|
||||||
|
public static class TagDisplayUtility
|
||||||
|
{
|
||||||
|
public static string ResolveTagName(TagType tagType)
|
||||||
|
{
|
||||||
|
if (tagType == TagType.None)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GameEntry.DataTable != null)
|
||||||
|
{
|
||||||
|
var tagTable = GameEntry.DataTable.GetDataTable<DRTag>();
|
||||||
|
if (tagTable != null)
|
||||||
|
{
|
||||||
|
DRTag tagRow = tagTable.GetDataRow((int)tagType);
|
||||||
|
if (tagRow != null && !string.IsNullOrWhiteSpace(tagRow.Name))
|
||||||
|
{
|
||||||
|
return tagRow.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tagType.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string[] BuildTagTexts(IReadOnlyList<TagType> tags)
|
||||||
|
{
|
||||||
|
if (tags == null || tags.Count <= 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<string> results = new List<string>(tags.Count);
|
||||||
|
for (int i = 0; i < tags.Count; i++)
|
||||||
|
{
|
||||||
|
string tagName = ResolveTagName(tags[i]);
|
||||||
|
if (!string.IsNullOrWhiteSpace(tagName))
|
||||||
|
{
|
||||||
|
results.Add(tagName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string[] BuildTagTexts(IReadOnlyList<TagRuntimeData> tagRuntimes)
|
||||||
|
{
|
||||||
|
if (tagRuntimes == null || tagRuntimes.Count <= 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<string> results = new List<string>(tagRuntimes.Count);
|
||||||
|
for (int i = 0; i < tagRuntimes.Count; i++)
|
||||||
|
{
|
||||||
|
TagRuntimeData tagRuntime = tagRuntimes[i];
|
||||||
|
if (tagRuntime == null || tagRuntime.TagType == TagType.None || tagRuntime.TotalStack <= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string tagName = ResolveTagName(tagRuntime.TagType);
|
||||||
|
if (string.IsNullOrWhiteSpace(tagName))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
results.Add(tagRuntime.TotalStack > 1 ? $"{tagName} x{tagRuntime.TotalStack}" : tagName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string[] BuildTowerTagTexts(TowerStatsData towerStats)
|
||||||
|
{
|
||||||
|
if (towerStats == null)
|
||||||
|
{
|
||||||
|
return Array.Empty<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (towerStats.TagRuntimes != null && towerStats.TagRuntimes.Length > 0)
|
||||||
|
{
|
||||||
|
return BuildTagTexts(towerStats.TagRuntimes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return BuildTagTexts(towerStats.Tags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 93b93ce08ff34afea2cdf0cfe8f417f6
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using GeometryTD.CustomUtility;
|
||||||
|
using GeometryTD.Definition;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace GeometryTD.Tests.EditMode
|
||||||
|
{
|
||||||
|
public sealed class InventoryTagRuleServiceTests
|
||||||
|
{
|
||||||
|
private static readonly IReadOnlyDictionary<TagType, RarityType> MinRarityByTag =
|
||||||
|
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 },
|
||||||
|
};
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetEligibleTags_Filters_Invalid_Unsupported_And_HighRarity_Tags()
|
||||||
|
{
|
||||||
|
TagType[] result = InventoryTagRuleService.GetEligibleTags(
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
TagType.None,
|
||||||
|
TagType.Fire,
|
||||||
|
TagType.Fire,
|
||||||
|
TagType.BurnSpread,
|
||||||
|
TagType.Inferno,
|
||||||
|
(TagType)99
|
||||||
|
},
|
||||||
|
RarityType.Green,
|
||||||
|
MinRarityByTag);
|
||||||
|
|
||||||
|
Assert.That(result, Is.EqualTo(new[] { TagType.Fire }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ResolveComponentTags_Is_Deterministic_For_Same_Context()
|
||||||
|
{
|
||||||
|
TagType[] first = InventoryTagRuleService.ResolveComponentTags(
|
||||||
|
new[] { TagType.Fire, TagType.Ice, TagType.Crit, TagType.Shatter },
|
||||||
|
RarityType.Blue,
|
||||||
|
InventoryTagSourceType.Shop,
|
||||||
|
12345,
|
||||||
|
7,
|
||||||
|
MinRarityByTag);
|
||||||
|
|
||||||
|
TagType[] second = InventoryTagRuleService.ResolveComponentTags(
|
||||||
|
new[] { TagType.Fire, TagType.Ice, TagType.Crit, TagType.Shatter },
|
||||||
|
RarityType.Blue,
|
||||||
|
InventoryTagSourceType.Shop,
|
||||||
|
12345,
|
||||||
|
7,
|
||||||
|
MinRarityByTag);
|
||||||
|
|
||||||
|
Assert.That(second, Is.EqualTo(first));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ResolveComponentTags_Uses_Purple_Budget_And_Does_Not_Repeat()
|
||||||
|
{
|
||||||
|
TagType[] result = InventoryTagRuleService.ResolveComponentTags(
|
||||||
|
new[] { TagType.Fire, TagType.Ice, TagType.Crit, TagType.Shatter, TagType.Inferno, TagType.Execution },
|
||||||
|
RarityType.Purple,
|
||||||
|
InventoryTagSourceType.Drop,
|
||||||
|
9001,
|
||||||
|
4,
|
||||||
|
MinRarityByTag);
|
||||||
|
|
||||||
|
Assert.That(result.Length, Is.EqualTo(2));
|
||||||
|
Assert.That(new HashSet<TagType>(result).Count, Is.EqualTo(result.Length));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ResolveComponentTags_Falls_Back_When_Eligible_Count_Is_Lower_Than_Budget()
|
||||||
|
{
|
||||||
|
TagType[] result = InventoryTagRuleService.ResolveComponentTags(
|
||||||
|
new[] { TagType.Fire, TagType.BurnSpread },
|
||||||
|
RarityType.Red,
|
||||||
|
InventoryTagSourceType.Seed,
|
||||||
|
42,
|
||||||
|
1,
|
||||||
|
MinRarityByTag);
|
||||||
|
|
||||||
|
Assert.That(result, Is.EqualTo(new[] { TagType.Fire }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CreateSampleInventory_Generates_SeedTags_Within_Launch_Set_And_Matches_Tower_Stats()
|
||||||
|
{
|
||||||
|
BackpackInventoryData inventory = InventorySeedUtility.CreateSampleInventory();
|
||||||
|
TowerItemData tower = inventory.Towers[0];
|
||||||
|
MuzzleCompItemData muzzle = inventory.MuzzleComponents[0];
|
||||||
|
BearingCompItemData bearing = inventory.BearingComponents[0];
|
||||||
|
BaseCompItemData baseComp = inventory.BaseComponents[0];
|
||||||
|
|
||||||
|
Assert.That(muzzle.Tags, Is.EqualTo(new[] { TagType.Fire }));
|
||||||
|
Assert.That(bearing.Tags, Is.EqualTo(new[] { TagType.Fire }));
|
||||||
|
Assert.That(baseComp.Tags, Is.EqualTo(new[] { TagType.Fire }));
|
||||||
|
Assert.That(tower.Stats.TagRuntimes, Has.Length.EqualTo(1));
|
||||||
|
Assert.That(tower.Stats.TagRuntimes[0].TagType, Is.EqualTo(TagType.Fire));
|
||||||
|
Assert.That(tower.Stats.TagRuntimes[0].TotalStack, Is.EqualTo(3));
|
||||||
|
Assert.That(tower.Stats.Tags, Is.EqualTo(new[] { TagType.Fire }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9049175734024291804e223045c84b95
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
using GeometryTD.CustomUtility;
|
||||||
|
using GeometryTD.Definition;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace GeometryTD.Tests.EditMode
|
||||||
|
{
|
||||||
|
public sealed class TowerTagAggregationServiceTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void AggregateTowerTags_Returns_Stacked_Runtime_Result()
|
||||||
|
{
|
||||||
|
TagRuntimeData[] runtimes = TowerTagAggregationService.AggregateTowerTags(
|
||||||
|
new[] { TagType.Fire, TagType.Crit },
|
||||||
|
new[] { TagType.Fire },
|
||||||
|
new[] { TagType.Ice });
|
||||||
|
|
||||||
|
Assert.That(runtimes, Has.Length.EqualTo(3));
|
||||||
|
Assert.That(runtimes[0].TagType, Is.EqualTo(TagType.Fire));
|
||||||
|
Assert.That(runtimes[0].TotalStack, Is.EqualTo(2));
|
||||||
|
Assert.That(runtimes[1].TagType, Is.EqualTo(TagType.Ice));
|
||||||
|
Assert.That(runtimes[1].TotalStack, Is.EqualTo(1));
|
||||||
|
Assert.That(runtimes[2].TagType, Is.EqualTo(TagType.Crit));
|
||||||
|
Assert.That(runtimes[2].TotalStack, Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void AggregateTowerTags_Ignores_None_And_Invalid_Values()
|
||||||
|
{
|
||||||
|
TagRuntimeData[] runtimes = TowerTagAggregationService.AggregateTowerTags(
|
||||||
|
new[] { TagType.None, TagType.Fire, (TagType)99 },
|
||||||
|
null,
|
||||||
|
new[] { TagType.Fire });
|
||||||
|
|
||||||
|
Assert.That(runtimes, Has.Length.EqualTo(1));
|
||||||
|
Assert.That(runtimes[0].TagType, Is.EqualTo(TagType.Fire));
|
||||||
|
Assert.That(runtimes[0].TotalStack, Is.EqualTo(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void FlattenUniqueTags_Returns_Stable_Unique_Tag_List()
|
||||||
|
{
|
||||||
|
TagType[] tags = TowerTagAggregationService.FlattenUniqueTags(new[]
|
||||||
|
{
|
||||||
|
new TagRuntimeData { TagType = TagType.Crit, TotalStack = 1 },
|
||||||
|
new TagRuntimeData { TagType = TagType.Fire, TotalStack = 2 },
|
||||||
|
new TagRuntimeData { TagType = TagType.None, TotalStack = 3 }
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(tags, Is.EqualTo(new[] { TagType.Fire, TagType.Crit }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void BuildTowerTagTexts_Uses_Runtime_Stacks_For_Display()
|
||||||
|
{
|
||||||
|
string[] texts = TagDisplayUtility.BuildTowerTagTexts(new TowerStatsData
|
||||||
|
{
|
||||||
|
TagRuntimes = new[]
|
||||||
|
{
|
||||||
|
new TagRuntimeData { TagType = TagType.Fire, TotalStack = 2 },
|
||||||
|
new TagRuntimeData { TagType = TagType.Crit, TotalStack = 1 }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(texts, Is.EqualTo(new[] { "Fire x2", "Crit" }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 11bb7495dfba4efba76782352043fef5
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -128,14 +128,18 @@
|
||||||
| [x] | S4-01 | 先确定 M1 需要的品质 / Tag 规则边界 | `docs/CodeX-TODO.md`<br>`docs/TODO.md` | 文档先对齐,再落代码 |
|
| [x] | S4-01 | 先确定 M1 需要的品质 / Tag 规则边界 | `docs/CodeX-TODO.md`<br>`docs/TODO.md` | 文档先对齐,再落代码 |
|
||||||
| [x] | S4-02 | 把品质计算整理成单一入口 | `Assets/GameMain/Scripts/Definition/`<br>`Assets/GameMain/Scripts/CustomComponent/PlayerInventory/` | 组装、掉落、商店使用一致结果 |
|
| [x] | S4-02 | 把品质计算整理成单一入口 | `Assets/GameMain/Scripts/Definition/`<br>`Assets/GameMain/Scripts/CustomComponent/PlayerInventory/` | 组装、掉落、商店使用一致结果 |
|
||||||
| [x] | S4-03 | 先固化 Tag 系统设计与首发范围 | `docs/TagSystemDesign.md`<br>`docs/CodeX-TODO.md` | Tag 的来源、汇总、生效与首发集合口径固定 |
|
| [x] | S4-03 | 先固化 Tag 系统设计与首发范围 | `docs/TagSystemDesign.md`<br>`docs/CodeX-TODO.md` | Tag 的来源、汇总、生效与首发集合口径固定 |
|
||||||
| [ ] | S4-04 | 实现组件实例 Tag 的统一生成入口 | `Assets/GameMain/Scripts/Definition/`<br>`Assets/GameMain/Scripts/CustomComponent/PlayerInventory/` | 掉落、商店、初始种子、事件奖励共用同一生成结果 |
|
| [x] | S4-04 | 实现组件实例 Tag 的统一生成入口 | `Assets/GameMain/Scripts/Definition/`<br>`Assets/GameMain/Scripts/CustomComponent/PlayerInventory/` | 掉落、商店、初始种子、事件奖励共用同一生成结果 |
|
||||||
| [ ] | S4-05 | 实现组塔后的 Tag 汇总与展示入口 | `Assets/GameMain/Scripts/Definition/`<br>`Assets/GameMain/Scripts/CustomComponent/PlayerInventory/`<br>`Assets/GameMain/Scripts/UI/` | 组件 Tag 可汇总为塔级结果,且 UI 展示口径一致 |
|
| [x] | S4-05 | 实现组塔后的 Tag 汇总与展示入口 | `Assets/GameMain/Scripts/Definition/`<br>`Assets/GameMain/Scripts/CustomComponent/PlayerInventory/`<br>`Assets/GameMain/Scripts/UI/` | 组件 Tag 可汇总为塔级结果,且 UI 展示口径一致 |
|
||||||
| [ ] | S4-06 | 实现首批基础 Tag 的战斗生效 | `Assets/GameMain/Scripts/Entity/`<br>`Assets/GameMain/Scripts/Components/`<br>`Assets/GameMain/Scripts/Definition/` | 首发 6~8 个基础 Tag 至少完成一批可验证效果 |
|
| [ ] | S4-06 | 实现首批基础 Tag 的战斗生效 | `Assets/GameMain/Scripts/Entity/`<br>`Assets/GameMain/Scripts/Components/`<br>`Assets/GameMain/Scripts/Definition/` | 首发 6~8 个基础 Tag 至少完成一批可验证效果 |
|
||||||
| [ ] | S4-07 | 补齐 Tag 规则与数据表的映射关系 | `Assets/GameMain/Scripts/DataTable/`<br>`Assets/GameMain/Scripts/Definition/` | 表字段不是只存在而未被消费,Tag 参数可配置可解释 |
|
| [ ] | S4-07 | 补齐 Tag 规则与数据表的映射关系 | `Assets/GameMain/Scripts/DataTable/`<br>`Assets/GameMain/Scripts/Definition/` | 表字段不是只存在而未被消费,Tag 参数可配置可解释 |
|
||||||
|
|
||||||
> 2026-03-09 更新:`InventoryRarityRuleService` 已落地;塔品质计算与组件品质归一化已统一收口,`PlayerInventoryTowerAssemblyService`、`ShopFormUseCase`、`EnemyDropResolver`、`InventorySeedUtility` 已接入同一规则入口。你已确认 Unity Test Runner 中 `Assets/Tests/EditMode` 全部通过,其中包含新增的 `InventoryRarityRuleServiceTests`。
|
> 2026-03-09 更新:`InventoryRarityRuleService` 已落地;塔品质计算与组件品质归一化已统一收口,`PlayerInventoryTowerAssemblyService`、`ShopFormUseCase`、`EnemyDropResolver`、`InventorySeedUtility` 已接入同一规则入口。你已确认 Unity Test Runner 中 `Assets/Tests/EditMode` 全部通过,其中包含新增的 `InventoryRarityRuleServiceTests`。
|
||||||
>
|
>
|
||||||
> 2026-03-09 更新:`S4-03` 文档口径已冻结;`TagSystemDesign.md` 已明确组件实例生成、塔级 `Stack` 汇总、四段触发阶段、`Tag.txt + TagRule` 双表方向,以及 MVP 正式首发 7 个 Tag:`Fire`、`Ice`、`Crit`、`Execution`、`Shatter`、`Inferno`、`AbsoluteZero`。`BurnSpread` 已明确后移。
|
> 2026-03-09 更新:`S4-03` 文档口径已冻结;`TagSystemDesign.md` 已明确组件实例生成、塔级 `Stack` 汇总、四段触发阶段、`Tag.txt + TagRule` 双表方向,以及 MVP 正式首发 7 个 Tag:`Fire`、`Ice`、`Crit`、`Execution`、`Shatter`、`Inferno`、`AbsoluteZero`。`BurnSpread` 已明确后移。
|
||||||
|
>
|
||||||
|
> 2026-03-09 更新:`S4-04` 已落地 `InventoryTagRuleService` 与 `InventoryTagSourceType`;组件实例 Tag 现在统一按 `PossibleTag + Tag.txt.MinRarity + 品质预算` 生成,并只保留当前正式首发 7 个 Tag。`ShopFormUseCase`、`EnemyDropResolver`、`InventorySeedUtility` 已接入该入口,样例库存的组件与塔展示 Tag 已同步为统一结果;同时新增 `InventoryTagRuleServiceTests`。当前 CLI 下 `dotnet build GeometryTD.sln` 仍因本机缺少 Unity 引用和 `Unity.SourceGenerators*.dll` 失败,未能替代 Unity Test Runner 完成最终验证。
|
||||||
|
>
|
||||||
|
> 2026-03-09 更新:`S4-05` 已新增 `TagRuntimeData` 与 `TowerTagAggregationService`;组塔与样例塔现在统一生成塔级 `TagRuntimes`,并保留兼容 `Tags` 投影。`RepoForm`、`CombatFinishForm`、`ItemDescForm` 的塔展示已切到聚合结果,重复 Tag 以 `xN` 文本显示;组件展示仍沿用组件实例 `Tags`。同时新增 `TowerTagAggregationServiceTests`;本轮改动后的最终验证仍以 Unity Test Runner 实跑结果为准。
|
||||||
|
|
||||||
### S4-01 边界结论
|
### S4-01 边界结论
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue