Compare commits
12
Commits
53b6b261dd
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d785d5139 | ||
|
|
eec09a0bb4 | ||
|
|
1422db3b19 | ||
|
|
a38a8d4b39 | ||
|
|
55506eff9a | ||
|
|
2e54acbc85 | ||
|
|
dc2aa59d58 | ||
|
|
c76fd85a2c | ||
|
|
777c58b812 | ||
|
|
aa44170d56 | ||
|
|
0c92fd9886 | ||
|
|
26aaa945a9 |
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(unzip -l \"Assets/GameFramework/Libraries/GameFramework.xml\")"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
# Unity generated folders
|
|
||||||
Library/
|
|
||||||
Temp/
|
|
||||||
Obj/
|
|
||||||
Logs/
|
|
||||||
Build/
|
|
||||||
Builds/
|
|
||||||
UserSettings/
|
|
||||||
MemoryCaptures/
|
|
||||||
Recordings/
|
|
||||||
|
|
||||||
# IDE / tool caches
|
|
||||||
.vs/
|
|
||||||
.idea/
|
|
||||||
.gradle/
|
|
||||||
.cache/
|
|
||||||
|
|
||||||
# OS / misc
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
|
|
||||||
# Node / frontend caches if present
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
# Test / coverage outputs
|
|
||||||
coverage/
|
|
||||||
TestResults/
|
|
||||||
|
|
||||||
# Large binary assets that usually don't help code editing
|
|
||||||
*.apk
|
|
||||||
*.aab
|
|
||||||
*.unitypackage
|
|
||||||
*.zip
|
|
||||||
*.7z
|
|
||||||
*.rar
|
|
||||||
|
|
||||||
# Optional: generated csproj/sln if you mainly use Unity-generated ones
|
|
||||||
# *.csproj
|
|
||||||
# *.sln
|
|
||||||
@@ -98,10 +98,14 @@ InitTestScene*.unity*
|
|||||||
/[Aa]ssets/[Ii]nit[Tt]est[Ss]cene*.unity*
|
/[Aa]ssets/[Ii]nit[Tt]est[Ss]cene*.unity*
|
||||||
|
|
||||||
/[Aa]ssets/StreamingAssets/
|
/[Aa]ssets/StreamingAssets/
|
||||||
|
!/[Aa]ssets/StreamingAssets/.gitkeep
|
||||||
|
|
||||||
/.vscode
|
/.vscode
|
||||||
/.dotnet-home
|
/.dotnet-home
|
||||||
|
|
||||||
/[Aa]ssets/RawResources
|
/[Aa]ssets/RawResources
|
||||||
/[Aa]ssets/RawResources.meta
|
/[Aa]ssets/RawResources.meta
|
||||||
|
|
||||||
/.dotnet
|
/.dotnet
|
||||||
/.idea
|
/.idea
|
||||||
~$*.xlsx
|
~$*.xlsx
|
||||||
@@ -109,3 +113,5 @@ InitTestScene*.unity*
|
|||||||
/node_modules
|
/node_modules
|
||||||
package.json
|
package.json
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
/.omc
|
||||||
|
/.omx
|
||||||
Generated
-15
@@ -1,15 +0,0 @@
|
|||||||
# 默认忽略的文件
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# Rider 忽略的文件
|
|
||||||
/modules.xml
|
|
||||||
/.idea.GeometryTD.iml
|
|
||||||
/projectSettingsUpdater.xml
|
|
||||||
/contentModel.xml
|
|
||||||
# 已忽略包含查询文件的默认文件夹
|
|
||||||
/queries/
|
|
||||||
# Datasource local storage ignored files
|
|
||||||
/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
||||||
# 基于编辑器的 HTTP 客户端请求
|
|
||||||
/httpRequests/
|
|
||||||
-8
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="UserContentModel">
|
|
||||||
<attachedFolders />
|
|
||||||
<explicitIncludes />
|
|
||||||
<explicitExcludes />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-6
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@@ -1,355 +0,0 @@
|
|||||||
# Project Philosophy
|
|
||||||
|
|
||||||
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/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/Configs/` and `Assets/GameMain/DataTables/` store runtime configuration and data tables.
|
|
||||||
- `StreamingAssets/` is for runtime-loaded files that must be preserved on build.
|
|
||||||
- `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.
|
|
||||||
|
|
||||||
### Build, Test, and Development Commands
|
|
||||||
- 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).
|
|
||||||
- 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.
|
|
||||||
|
|
||||||
### Coding Style & Naming Conventions
|
|
||||||
- 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`.
|
|
||||||
- Namespaces follow `GeometryTD.*` by feature area (example: `GeometryTD.Procedure`).
|
|
||||||
- Keep Unity `.meta` files with their assets; do not delete or regenerate them manually.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Code Design Principles
|
|
||||||
|
|
||||||
### 1. Prefer explicit logic over abstraction
|
|
||||||
|
|
||||||
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.
|
|
||||||
@@ -4,3 +4,7 @@
|
|||||||
1 元素底座 [2,2.1,2.2,2.3,2.5] 0.2 Fire [Fire,BurnSpread,IgniteBurst,Inferno]
|
1 元素底座 [2,2.1,2.2,2.3,2.5] 0.2 Fire [Fire,BurnSpread,IgniteBurst,Inferno]
|
||||||
2 控制底座 [4,4.2,4.4,4.6,4.8] 0.1 Ice [Ice,FreezeMask,Shatter,AbsoluteZero]
|
2 控制底座 [4,4.2,4.4,4.6,4.8] 0.1 Ice [Ice,FreezeMask,Shatter,AbsoluteZero]
|
||||||
3 穿透底座 [1,1,1,1,1] 0 Physics [Pierce,Crit,Overpenetrate,Execution]
|
3 穿透底座 [1,1,1,1,1] 0 Physics [Pierce,Crit,Overpenetrate,Execution]
|
||||||
|
4 12底座 [1,1,1,1,1] 0.1 Poison [Fire,BurnSpread,IgniteBurst,Inferno,Ice,FreezeMask,Shatter,AbsoluteZero]
|
||||||
|
5 23底座 [1,1,1,1,1] 0.1 Water [Ice,FreezeMask,Shatter,AbsoluteZero,Pierce,Crit,Overpenetrate,Execution]
|
||||||
|
6 13底座 [1,1,1,1,1] 0.1 Earth [Fire,BurnSpread,IgniteBurst,Inferno,Pierce,Crit,Overpenetrate,Execution]
|
||||||
|
7 123底座 [1,1,1,1,1] 0.1 Fire [Fire,BurnSpread,IgniteBurst,Inferno,Ice,FreezeMask,Shatter,AbsoluteZero,Pierce,Crit,Overpenetrate,Execution]
|
||||||
|
|||||||
@@ -4,3 +4,7 @@
|
|||||||
1 元素轴承 [10,12,13,14,15] 15 [2,2,2,2,2] 0.3 [Fire,BurnSpread,IgniteBurst,Inferno]
|
1 元素轴承 [10,12,13,14,15] 15 [2,2,2,2,2] 0.3 [Fire,BurnSpread,IgniteBurst,Inferno]
|
||||||
2 控制轴承 [20,25,30,32,35] 0 [6,6.5,7,8,8] 0.2 [Ice,FreezeMask,Shatter,AbsoluteZero]
|
2 控制轴承 [20,25,30,32,35] 0 [6,6.5,7,8,8] 0.2 [Ice,FreezeMask,Shatter,AbsoluteZero]
|
||||||
3 穿透轴承 [60,70,80,90,100] 20 [4,4.5,5,5.5,6] 0 [Pierce,Crit,Overpenetrate,Execution]
|
3 穿透轴承 [60,70,80,90,100] 20 [4,4.5,5,5.5,6] 0 [Pierce,Crit,Overpenetrate,Execution]
|
||||||
|
4 12轴承 [60,70,80,90,100] 20 [4,4.5,5,5.5,6] 0 [Fire,BurnSpread,IgniteBurst,Inferno,Ice,FreezeMask,Shatter,AbsoluteZero]
|
||||||
|
5 23轴承 [60,70,80,90,100] 20 [4,4.5,5,5.5,6] 0 [Ice,FreezeMask,Shatter,AbsoluteZero,Pierce,Crit,Overpenetrate,Execution]
|
||||||
|
6 13轴承 [60,70,80,90,100] 20 [4,4.5,5,5.5,6] 0 [Fire,BurnSpread,IgniteBurst,Inferno,Pierce,Crit,Overpenetrate,Execution]
|
||||||
|
7 123轴承 [60,70,80,90,100] 20 [4,4.5,5,5.5,6] 0 [Fire,BurnSpread,IgniteBurst,Inferno,Ice,FreezeMask,Shatter,AbsoluteZero,Pierce,Crit,Overpenetrate,Execution]
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
# Id 策划备注 AssetName
|
# Id 策划备注 AssetName
|
||||||
# int string
|
# int string
|
||||||
# 实体编号 资源名称
|
# 实体编号 资源名称
|
||||||
101 测试枪口 TestMuzzle
|
|
||||||
201 测试轴承 TestBearing
|
|
||||||
301 测试底座 TestBase
|
|
||||||
401 防御塔实体 TowerEntity
|
401 防御塔实体 TowerEntity
|
||||||
501 普通子弹 NormalBullet
|
501 普通子弹 NormalBullet
|
||||||
1001 测试普通敌人 TestEnemy
|
1001 测试普通敌人 TestEnemy
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Id 列1 Title Description Options
|
# Id 列1 Title Description Option1 Option2 Option3 Option4
|
||||||
# int string string string
|
# int string string string string string string
|
||||||
# 事件编号 策划备注 事件题目 事件描述 事件选项
|
# 事件编号 策划备注 事件题目 事件描述 选项 1 选项 2 选项 3 选项 4
|
||||||
1 赌马 一名商人邀请你下注。赢了就能赚一笔。 [{"optionText":"下注 100(金)- 稳健:70% 赢 150","requirements":[{"type":"GoldAtLeast","param":{"Count":100}}],"costEffects":[{"type":"AddGold","param":{"Count":-100}}],"rewardEffects":[{"type":"AddGold","param":{"Count":150}}],"probability":0.7},{"optionText":"下注 100(金)- 激进:30% 赢 250","requirements":[{"type":"GoldAtLeast","param":{"Count":100}}],"costEffects":[{"type":"AddGold","param":{"Count":-100}}],"rewardEffects":[{"type":"AddGold","param":{"Count":250}}],"probability":0.3}]
|
1 赌马 一名商人邀请你下注。赢了就能赚一笔。 {"optionText":"下注 100(金)- 稳健:70% 赢 150","requirements":[{"type":"GoldAtLeast","param":{"Count":100}}],"costEffects":[{"type":"AddGold","param":{"Count":-100}}],"rewardEffects":[{"type":"AddGold","param":{"Count":150}}],"probability":0.7} {"optionText":"下注 100(金)- 激进:30% 赢 250","requirements":[{"type":"GoldAtLeast","param":{"Count":100}}],"costEffects":[{"type":"AddGold","param":{"Count":-100}}],"rewardEffects":[{"type":"AddGold","param":{"Count":250}}],"probability":0.3}
|
||||||
2 工匠的熔炉 工匠以金币交换防御塔组件。 [{"optionText":"交出 3 个白色组件,获得 50 金币","requirements":[{"type":"CompCountAtLeast","param":{"Count":3,"Rarity":"White"}}],"costEffects":[{"type":"RemoveRandomComps","param":{"Count":3,"Rarity":"White"}}],"rewardEffects":[{"type":"AddGold","param":{"Count":50}}]},{"optionText":"交出 2 个绿色组件,获得 70 金币","requirements":[{"type":"CompCountAtLeast","param":{"Count":2,"Rarity":"Green"}}],"costEffects":[{"type":"RemoveRandomComps","param":{"Count":2,"Rarity":"Green"}}],"rewardEffects":[{"type":"AddGold","param":{"Count":70}}]},{"optionText":"交出 1 个蓝色组件,获得 80 金币","requirements":[{"type":"CompCountAtLeast","param":{"Count":1,"Rarity":"Blue"}}],"costEffects":[{"type":"RemoveRandomComps","param":{"Count":1,"Rarity":"Blue"}}],"rewardEffects":[{"type":"AddGold","param":{"Count":80}}]},{"optionText":"拒绝","rewardEffects":[]}]
|
2 工匠的熔炉 工匠愿意熔炼你的旧组件,换给你一件更有希望的新货。 {"optionText":"交出 2 个白色组件,获得 1 个白色或绿色组件","requirements":[{"type":"CompCountAtLeast","param":{"Count":2,"Rarity":"White"}}],"costEffects":[{"type":"RemoveRandomComps","param":{"Count":2,"Rarity":"White"}}],"rewardEffects":[{"type":"AddRandomComps","param":{"Count":1,"MinRarity":"White","MaxRarity":"Green"}}]} {"optionText":"交出 2 个绿色组件,获得 1 个绿色或蓝色组件","requirements":[{"type":"CompCountAtLeast","param":{"Count":2,"Rarity":"Green"}}],"costEffects":[{"type":"RemoveRandomComps","param":{"Count":2,"Rarity":"Green"}}],"rewardEffects":[{"type":"AddRandomComps","param":{"Count":1,"MinRarity":"Green","MaxRarity":"Blue"}}]} {"optionText":"交出 2 个蓝色组件,获得 1 个蓝色或紫色组件","requirements":[{"type":"CompCountAtLeast","param":{"Count":2,"Rarity":"Blue"}}],"costEffects":[{"type":"RemoveRandomComps","param":{"Count":2,"Rarity":"Blue"}}],"rewardEffects":[{"type":"AddRandomComps","param":{"Count":1,"MinRarity":"Blue","MaxRarity":"Purple"}}]} {"optionText":"拒绝","requirements":[],"rewardEffects":[]}
|
||||||
3 代价与回报 某种黑暗力量向你索取代价。 [{"optionText":"展示你的防御塔","requirements":[{"type":"TowerCountAtLeast","param":{"Count":2}}],"rewardEffects":[{"type":"AddGold","param":{"Count":50}}]},{"optionText":"离开","rewardEffects":[]}]
|
3 代价与回报 某种黑暗力量承诺给你金币,但它索取的代价会落在你的一座防御塔上。 {"optionText":"让黑暗力量抽取一座防御塔 20 点耐久,获得 50 金币","requirements":[{"type":"TowerCountAtLeast","param":{"Count":1}}],"costEffects":[{"type":"DamageRandomTowersEndurance","param":{"Count":1,"Amount":20}}],"rewardEffects":[{"type":"AddGold","param":{"Count":50}}]} {"optionText":"离开","requirements":[],"rewardEffects":[]}
|
||||||
|
|||||||
@@ -4,3 +4,7 @@
|
|||||||
1 元素枪口 [20,30,40,50,80] 10 0.05 NormalBullet [Fire,BurnSpread,IgniteBurst,Inferno]
|
1 元素枪口 [20,30,40,50,80] 10 0.05 NormalBullet [Fire,BurnSpread,IgniteBurst,Inferno]
|
||||||
2 控制枪口 [30,50,70,90,100] 20 0.01 NormalBullet [Ice,FreezeMask,Shatter,AbsoluteZero]
|
2 控制枪口 [30,50,70,90,100] 20 0.01 NormalBullet [Ice,FreezeMask,Shatter,AbsoluteZero]
|
||||||
3 穿透枪口 [50,55,60,80,90] 30 0.02 NormalBullet [Pierce,Crit,Overpenetrate,Execution]
|
3 穿透枪口 [50,55,60,80,90] 30 0.02 NormalBullet [Pierce,Crit,Overpenetrate,Execution]
|
||||||
|
4 12枪口 [50,55,60,80,90] 30 0.02 NormalBullet [Fire,BurnSpread,IgniteBurst,Inferno,Ice,FreezeMask,Shatter,AbsoluteZero]
|
||||||
|
5 23枪口 [50,55,60,80,90] 30 0.02 NormalBullet [Ice,FreezeMask,Shatter,AbsoluteZero,Pierce,Crit,Overpenetrate,Execution]
|
||||||
|
6 13枪口 [50,55,60,80,90] 30 0.02 NormalBullet [Fire,BurnSpread,IgniteBurst,Inferno,Pierce,Crit,Overpenetrate,Execution]
|
||||||
|
7 123枪口 [50,55,60,80,90] 30 0.02 NormalBullet [Fire,BurnSpread,IgniteBurst,Inferno,Ice,FreezeMask,Shatter,AbsoluteZero,Pierce,Crit,Overpenetrate,Execution]
|
||||||
|
|||||||
@@ -1,48 +1,24 @@
|
|||||||
# Id 列1 LevelThemeType Rarity ItemType ItemId Weight MinPhase MaxPhase
|
# Id 列1 LevelThemeType ItemType ItemId Weights MinPhase MaxPhase
|
||||||
# int LevelThemeType RarityType ItemType int int int int
|
# int LevelThemeType ItemType int int[] int[] int[]
|
||||||
# 道具池号 策划备注 关卡所属主题类型 道具稀有度 道具类型 道具Id 权重 出现的最低波次 出现的最高波次
|
# 道具池号 策划备注 关卡所属主题类型 道具类型 道具Id 权重 出现的最低波次 出现的最高波次
|
||||||
1 平原战斗池 Plain White MuzzleComp 1 10 1 10
|
1 平原战斗池 Plain MuzzleComp 1 [20,10,6,4,2] [1,2,3,4,5] [10,10,15,20,50]
|
||||||
2 Plain White MuzzleComp 2 10 1 10
|
2 Plain MuzzleComp 2 [20,10,6,4,2] [1,2,3,4,5] [10,10,15,20,50]
|
||||||
3 Plain White MuzzleComp 3 10 1 10
|
3 Plain MuzzleComp 3 [20,10,6,4,2] [1,2,3,4,5] [10,10,15,20,50]
|
||||||
4 Plain White BearingComp 1 10 1 10
|
4 Plain MuzzleComp 4 [15,8,5,3,1] [1,2,3,4,5] [10,10,15,20,50]
|
||||||
5 Plain White BearingComp 2 10 1 10
|
5 Plain MuzzleComp 5 [15,8,5,3,1] [1,2,3,4,5] [10,10,15,20,50]
|
||||||
6 Plain White BearingComp 3 10 1 10
|
6 Plain MuzzleComp 6 [15,8,5,3,1] [1,2,3,4,5] [10,10,15,20,50]
|
||||||
7 Plain White BaseComp 1 10 1 10
|
7 Plain MuzzleComp 7 [15,8,5,3,1] [1,2,3,4,5] [10,10,15,20,50]
|
||||||
8 Plain White BaseComp 2 10 1 10
|
8 Plain BearingComp 1 [20,10,6,4,2] [1,2,3,4,5] [10,10,15,20,50]
|
||||||
9 Plain White BaseComp 3 10 1 10
|
9 Plain BearingComp 2 [20,10,6,4,2] [1,2,3,4,5] [10,10,15,20,50]
|
||||||
10 Plain Green MuzzleComp 1 5 2 10
|
10 Plain BearingComp 3 [20,10,6,4,2] [1,2,3,4,5] [10,10,15,20,50]
|
||||||
11 Plain Green MuzzleComp 2 5 2 10
|
11 Plain BearingComp 4 [15,8,5,3,1] [1,2,3,4,5] [10,10,15,20,50]
|
||||||
12 Plain Green MuzzleComp 3 5 2 10
|
12 Plain BearingComp 5 [15,8,5,3,1] [1,2,3,4,5] [10,10,15,20,50]
|
||||||
13 Plain Green BearingComp 1 5 2 10
|
13 Plain BearingComp 6 [15,8,5,3,1] [1,2,3,4,5] [10,10,15,20,50]
|
||||||
14 Plain Green BearingComp 2 5 2 10
|
14 Plain BearingComp 7 [15,8,5,3,1] [1,2,3,4,5] [10,10,15,20,50]
|
||||||
15 Plain Green BearingComp 3 5 2 10
|
15 Plain BaseComp 1 [20,10,6,4,2] [1,2,3,4,5] [10,10,15,20,50]
|
||||||
16 Plain Green BaseComp 1 5 2 10
|
16 Plain BaseComp 2 [20,10,6,4,2] [1,2,3,4,5] [10,10,15,20,50]
|
||||||
17 Plain Green BaseComp 2 5 2 10
|
17 Plain BaseComp 3 [20,10,6,4,2] [1,2,3,4,5] [10,10,15,20,50]
|
||||||
18 Plain Green BaseComp 3 5 2 10
|
18 Plain BaseComp 4 [15,8,5,3,1] [1,2,3,4,5] [10,10,15,20,50]
|
||||||
19 Plain Blue MuzzleComp 1 3 3 15
|
19 Plain BaseComp 5 [15,8,5,3,1] [1,2,3,4,5] [10,10,15,20,50]
|
||||||
20 Plain Blue MuzzleComp 2 3 3 15
|
20 Plain BaseComp 6 [15,8,5,3,1] [1,2,3,4,5] [10,10,15,20,50]
|
||||||
21 Plain Blue MuzzleComp 3 3 3 15
|
21 Plain BaseComp 7 [15,8,5,3,1] [1,2,3,4,5] [10,10,15,20,50]
|
||||||
22 Plain Blue BearingComp 1 3 3 15
|
|
||||||
23 Plain Blue BearingComp 2 3 3 15
|
|
||||||
24 Plain Blue BearingComp 3 3 3 15
|
|
||||||
25 Plain Blue BaseComp 1 3 3 15
|
|
||||||
26 Plain Blue BaseComp 2 3 3 15
|
|
||||||
27 Plain Blue BaseComp 3 3 3 15
|
|
||||||
28 Plain Purple MuzzleComp 1 2 4 20
|
|
||||||
29 Plain Purple MuzzleComp 2 2 4 20
|
|
||||||
30 Plain Purple MuzzleComp 3 2 4 20
|
|
||||||
31 Plain Purple BearingComp 1 2 4 20
|
|
||||||
32 Plain Purple BearingComp 2 2 4 20
|
|
||||||
33 Plain Purple BearingComp 3 2 4 20
|
|
||||||
34 Plain Purple BaseComp 1 2 4 20
|
|
||||||
35 Plain Purple BaseComp 2 2 4 20
|
|
||||||
36 Plain Purple BaseComp 3 2 4 20
|
|
||||||
37 Plain Red MuzzleComp 1 1 5 50
|
|
||||||
38 Plain Red MuzzleComp 2 1 5 50
|
|
||||||
39 Plain Red MuzzleComp 3 1 5 50
|
|
||||||
40 Plain Red BearingComp 1 1 5 50
|
|
||||||
41 Plain Red BearingComp 2 1 5 50
|
|
||||||
42 Plain Red BearingComp 3 1 5 50
|
|
||||||
43 Plain Red BaseComp 1 1 5 50
|
|
||||||
44 Plain Red BaseComp 2 1 5 50
|
|
||||||
45 Plain Red BaseComp 3 1 5 50
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ public partial class GameEntry
|
|||||||
|
|
||||||
public static InventoryGenerationComponent InventoryGeneration { get; private set; }
|
public static InventoryGenerationComponent InventoryGeneration { get; private set; }
|
||||||
|
|
||||||
|
public static TagRegistryComponent TagRegistry { get; private set; }
|
||||||
|
|
||||||
private static void InitCustomComponents()
|
private static void InitCustomComponents()
|
||||||
{
|
{
|
||||||
BuiltinData = UnityGameFramework.Runtime.GameEntry.GetComponent<BuiltinDataComponent>();
|
BuiltinData = UnityGameFramework.Runtime.GameEntry.GetComponent<BuiltinDataComponent>();
|
||||||
@@ -38,5 +40,6 @@ public partial class GameEntry
|
|||||||
ResolutionAdapter = UnityGameFramework.Runtime.GameEntry.GetComponent<ResolutionAdapterComponent>();
|
ResolutionAdapter = UnityGameFramework.Runtime.GameEntry.GetComponent<ResolutionAdapterComponent>();
|
||||||
SpriteCache = UnityGameFramework.Runtime.GameEntry.GetComponent<SpriteCacheComponent>();
|
SpriteCache = UnityGameFramework.Runtime.GameEntry.GetComponent<SpriteCacheComponent>();
|
||||||
InventoryGeneration = UnityGameFramework.Runtime.GameEntry.GetComponent<InventoryGenerationComponent>();
|
InventoryGeneration = UnityGameFramework.Runtime.GameEntry.GetComponent<InventoryGenerationComponent>();
|
||||||
|
TagRegistry = UnityGameFramework.Runtime.GameEntry.GetComponent<TagRegistryComponent>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,11 +177,7 @@ namespace GeometryTD.CustomComponent
|
|||||||
|
|
||||||
public void StartCombat(
|
public void StartCombat(
|
||||||
int levelId = 0,
|
int levelId = 0,
|
||||||
string runId = null,
|
RunNodeExecutionContext context = null)
|
||||||
int runSeed = 0,
|
|
||||||
int nodeId = 0,
|
|
||||||
RunNodeType nodeType = RunNodeType.None,
|
|
||||||
int sequenceIndex = -1)
|
|
||||||
{
|
{
|
||||||
if (!EnsureCombatRuntimeInitialized())
|
if (!EnsureCombatRuntimeInitialized())
|
||||||
{
|
{
|
||||||
@@ -232,11 +228,7 @@ namespace GeometryTD.CustomComponent
|
|||||||
selectedLevel,
|
selectedLevel,
|
||||||
phaseList,
|
phaseList,
|
||||||
_selectedSpawnEntriesByPhaseId,
|
_selectedSpawnEntriesByPhaseId,
|
||||||
runId,
|
context))
|
||||||
runSeed,
|
|
||||||
nodeId,
|
|
||||||
nodeType,
|
|
||||||
sequenceIndex))
|
|
||||||
{
|
{
|
||||||
CurrentLevel = null;
|
CurrentLevel = null;
|
||||||
return;
|
return;
|
||||||
|
|||||||
+14
-18
@@ -1,11 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using GeometryTD.CustomEvent;
|
|
||||||
using GeometryTD.DataTable;
|
using GeometryTD.DataTable;
|
||||||
using GeometryTD.Definition;
|
using GeometryTD.Definition;
|
||||||
using GeometryTD.Entity;
|
using GeometryTD.Entity;
|
||||||
using GeometryTD.Procedure;
|
using GeometryTD.Procedure;
|
||||||
using GeometryTD.UI;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityGameFramework.Runtime;
|
using UnityGameFramework.Runtime;
|
||||||
|
|
||||||
@@ -69,11 +67,7 @@ namespace GeometryTD.CustomComponent
|
|||||||
DRLevel level,
|
DRLevel level,
|
||||||
IReadOnlyList<DRLevelPhase> phases,
|
IReadOnlyList<DRLevelPhase> phases,
|
||||||
IReadOnlyDictionary<int, IReadOnlyList<DRLevelSpawnEntry>> spawnEntriesByPhaseId,
|
IReadOnlyDictionary<int, IReadOnlyList<DRLevelSpawnEntry>> spawnEntriesByPhaseId,
|
||||||
string runId = null,
|
RunNodeExecutionContext context = null)
|
||||||
int runSeed = 0,
|
|
||||||
int nodeId = 0,
|
|
||||||
RunNodeType nodeType = RunNodeType.None,
|
|
||||||
int sequenceIndex = -1)
|
|
||||||
{
|
{
|
||||||
if (!_initialized || _runtime.Entity == null)
|
if (!_initialized || _runtime.Entity == null)
|
||||||
{
|
{
|
||||||
@@ -95,16 +89,12 @@ namespace GeometryTD.CustomComponent
|
|||||||
_runtime.DidCombatWin = true;
|
_runtime.DidCombatWin = true;
|
||||||
|
|
||||||
_runtime.CurrentLevel = level;
|
_runtime.CurrentLevel = level;
|
||||||
_runtime.RunId = runId;
|
_runtime.NodeContext = context != null ? context.Clone() : null;
|
||||||
_runtime.RunSeed = runSeed;
|
_runtime.NextDropOrdinal = 0;
|
||||||
_runtime.NodeId = nodeId;
|
_runtime.NextRewardOrdinal = 0;
|
||||||
_runtime.NodeType = nodeType;
|
|
||||||
_runtime.SequenceIndex = sequenceIndex;
|
|
||||||
GameEntry.InventoryGeneration.ConfigureRunContext(runSeed, sequenceIndex);
|
|
||||||
_runtime.CombatRunResourceStore.InitializeForCombat(level);
|
_runtime.CombatRunResourceStore.InitializeForCombat(level);
|
||||||
for (int i = 0; i < phases.Count; i++)
|
foreach (var phase in phases)
|
||||||
{
|
{
|
||||||
DRLevelPhase phase = phases[i];
|
|
||||||
if (phase != null)
|
if (phase != null)
|
||||||
{
|
{
|
||||||
_runtime.PhaseBuffer.Add(phase);
|
_runtime.PhaseBuffer.Add(phase);
|
||||||
@@ -122,7 +112,8 @@ namespace GeometryTD.CustomComponent
|
|||||||
_runtime.PhaseLoopRuntime.SetPhases(_runtime.PhaseBuffer);
|
_runtime.PhaseLoopRuntime.SetPhases(_runtime.PhaseBuffer);
|
||||||
if (_runtime.PhaseLoopRuntime.PhaseCount <= 0)
|
if (_runtime.PhaseLoopRuntime.PhaseCount <= 0)
|
||||||
{
|
{
|
||||||
return _coordinator.HandleStartFailure($"CombatScheduler start failed. Level '{level.Id}' has no phase data.");
|
return _coordinator.HandleStartFailure(
|
||||||
|
$"CombatScheduler start failed. Level '{level.Id}' has no phase data.");
|
||||||
}
|
}
|
||||||
|
|
||||||
ChangeState(new CombatLoadingState(_runtime, _coordinator));
|
ChangeState(new CombatLoadingState(_runtime, _coordinator));
|
||||||
@@ -201,7 +192,13 @@ namespace GeometryTD.CustomComponent
|
|||||||
enemy,
|
enemy,
|
||||||
_runtime.PhaseLoopRuntime.DisplayPhaseIndex,
|
_runtime.PhaseLoopRuntime.DisplayPhaseIndex,
|
||||||
_coordinator.ResolveCurrentThemeType());
|
_coordinator.ResolveCurrentThemeType());
|
||||||
EnemyDropResult result = GameEntry.InventoryGeneration.ResolveEnemyDrop(context);
|
int nextDropOrdinal = _runtime.NextDropOrdinal;
|
||||||
|
EnemyDropResult result = GameEntry.InventoryGeneration.ResolveEnemyDrop(
|
||||||
|
context,
|
||||||
|
_runtime.NodeContext != null ? _runtime.NodeContext.RunSeed : 0,
|
||||||
|
_runtime.NodeContext != null ? _runtime.NodeContext.SequenceIndex : -1,
|
||||||
|
ref nextDropOrdinal);
|
||||||
|
_runtime.NextDropOrdinal = nextDropOrdinal;
|
||||||
_runtime.CombatRunResourceStore.AddEnemyDefeatedReward(result.Coin, result.Gold);
|
_runtime.CombatRunResourceStore.AddEnemyDefeatedReward(result.Coin, result.Gold);
|
||||||
_runtime.CombatRunResourceStore.AddEnemyDefeatedLoot(result.LootItem);
|
_runtime.CombatRunResourceStore.AddEnemyDefeatedLoot(result.LootItem);
|
||||||
}
|
}
|
||||||
@@ -326,6 +323,5 @@ namespace GeometryTD.CustomComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+21
-15
@@ -55,11 +55,9 @@ namespace GeometryTD.CustomComponent
|
|||||||
_runtime.DidCombatWin = true;
|
_runtime.DidCombatWin = true;
|
||||||
_runtime.IsCompleted = false;
|
_runtime.IsCompleted = false;
|
||||||
_runtime.NodeEnterFired = false;
|
_runtime.NodeEnterFired = false;
|
||||||
_runtime.RunId = null;
|
_runtime.NodeContext = null;
|
||||||
_runtime.RunSeed = 0;
|
_runtime.NextDropOrdinal = 0;
|
||||||
_runtime.NodeId = 0;
|
_runtime.NextRewardOrdinal = 0;
|
||||||
_runtime.NodeType = RunNodeType.None;
|
|
||||||
_runtime.SequenceIndex = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CleanupAllCombatEntities()
|
public void CleanupAllCombatEntities()
|
||||||
@@ -201,16 +199,20 @@ namespace GeometryTD.CustomComponent
|
|||||||
public void CompleteNormalCombatAndNotify(bool didCombatWin)
|
public void CompleteNormalCombatAndNotify(bool didCombatWin)
|
||||||
{
|
{
|
||||||
CompleteCombat(didCombatWin);
|
CompleteCombat(didCombatWin);
|
||||||
|
RunNodeExecutionContext nodeContext = _runtime.NodeContext;
|
||||||
GameEntry.Event.Fire(
|
GameEntry.Event.Fire(
|
||||||
this,
|
this,
|
||||||
NodeCompleteEventArgs.Create(
|
NodeCompleteEventArgs.Create(
|
||||||
_runtime.RunId,
|
nodeContext?.RunId,
|
||||||
_runtime.NodeId,
|
nodeContext?.NodeId ?? 0,
|
||||||
_runtime.NodeType,
|
nodeContext?.NodeType ?? RunNodeType.None,
|
||||||
_runtime.SequenceIndex,
|
nodeContext?.SequenceIndex ?? -1,
|
||||||
RunNodeCompletionStatus.Completed,
|
RunNodeCompletionStatus.Completed,
|
||||||
didCombatWin,
|
didCombatWin,
|
||||||
GameEntry.PlayerInventory != null ? GameEntry.PlayerInventory.GetInventorySnapshot() : null));
|
nodeContext != null
|
||||||
|
? nodeContext.CreateCompletionSnapshot(
|
||||||
|
GameEntry.PlayerInventory != null ? GameEntry.PlayerInventory.GetInventorySnapshot() : null)
|
||||||
|
: null));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CompleteFailureCombatAndNotify()
|
public void CompleteFailureCombatAndNotify()
|
||||||
@@ -220,16 +222,20 @@ namespace GeometryTD.CustomComponent
|
|||||||
CloseRewardSelectForm();
|
CloseRewardSelectForm();
|
||||||
CloseDialogForm();
|
CloseDialogForm();
|
||||||
CompleteCombat(false);
|
CompleteCombat(false);
|
||||||
|
RunNodeExecutionContext nodeContext = _runtime.NodeContext;
|
||||||
GameEntry.Event.Fire(
|
GameEntry.Event.Fire(
|
||||||
this,
|
this,
|
||||||
NodeCompleteEventArgs.Create(
|
NodeCompleteEventArgs.Create(
|
||||||
_runtime.RunId,
|
nodeContext?.RunId,
|
||||||
_runtime.NodeId,
|
nodeContext?.NodeId ?? 0,
|
||||||
_runtime.NodeType,
|
nodeContext?.NodeType ?? RunNodeType.None,
|
||||||
_runtime.SequenceIndex,
|
nodeContext?.SequenceIndex ?? -1,
|
||||||
RunNodeCompletionStatus.Exception,
|
RunNodeCompletionStatus.Exception,
|
||||||
false,
|
false,
|
||||||
GameEntry.PlayerInventory != null ? GameEntry.PlayerInventory.GetInventorySnapshot() : null));
|
nodeContext != null
|
||||||
|
? nodeContext.CreateCompletionSnapshot(
|
||||||
|
GameEntry.PlayerInventory != null ? GameEntry.PlayerInventory.GetInventorySnapshot() : null)
|
||||||
|
: null));
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HandleStartFailure(string errorMessage)
|
public bool HandleStartFailure(string errorMessage)
|
||||||
|
|||||||
+3
-5
@@ -29,10 +29,8 @@ namespace GeometryTD.CustomComponent
|
|||||||
public bool IsCompleted { get; set; }
|
public bool IsCompleted { get; set; }
|
||||||
public bool NodeEnterFired { get; set; }
|
public bool NodeEnterFired { get; set; }
|
||||||
public CombatSettlementContext SettlementContext { get; set; }
|
public CombatSettlementContext SettlementContext { get; set; }
|
||||||
public string RunId { get; set; }
|
public RunNodeExecutionContext NodeContext { get; set; }
|
||||||
public int RunSeed { get; set; }
|
public int NextDropOrdinal { get; set; }
|
||||||
public int NodeId { get; set; }
|
public int NextRewardOrdinal { get; set; }
|
||||||
public RunNodeType NodeType { get; set; }
|
|
||||||
public int SequenceIndex { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-212
@@ -11,10 +11,8 @@ namespace GeometryTD.CustomComponent
|
|||||||
public sealed class CombatSettlementService
|
public sealed class CombatSettlementService
|
||||||
{
|
{
|
||||||
private const int RewardSelectDisplayCount = 3;
|
private const int RewardSelectDisplayCount = 3;
|
||||||
private const float FullBaseHpGoldBonusRate = 0.3f;
|
private readonly CombatSettlementCalculator _calculator = new();
|
||||||
private const float HighBaseHpGoldBonusRate = 0.1f;
|
private readonly CombatSettlementCommitter _committer = new();
|
||||||
private const float HighBaseHpThreshold = 0.8f;
|
|
||||||
private const float SettlementTowerEnduranceLoss = 1f;
|
|
||||||
|
|
||||||
public CombatSettlementContext BuildSettlementContext(
|
public CombatSettlementContext BuildSettlementContext(
|
||||||
bool didCombatWin,
|
bool didCombatWin,
|
||||||
@@ -22,69 +20,25 @@ namespace GeometryTD.CustomComponent
|
|||||||
int defeatedEnemyCount,
|
int defeatedEnemyCount,
|
||||||
CombatRunResourceStore resourceStore)
|
CombatRunResourceStore resourceStore)
|
||||||
{
|
{
|
||||||
bool shouldOpenFullBaseHpRewardSelect = false;
|
return _calculator.BuildSettlementContext(
|
||||||
ResolveSettlementByBaseHp(
|
|
||||||
didCombatWin,
|
didCombatWin,
|
||||||
currentLevel,
|
currentLevel,
|
||||||
resourceStore,
|
defeatedEnemyCount,
|
||||||
out int currentBaseHp,
|
resourceStore);
|
||||||
out int maxBaseHp,
|
|
||||||
out int levelRewardGold,
|
|
||||||
out float bonusRate,
|
|
||||||
out int bonusGold,
|
|
||||||
out shouldOpenFullBaseHpRewardSelect);
|
|
||||||
|
|
||||||
CombatSettlementContext settlementContext = new CombatSettlementContext
|
|
||||||
{
|
|
||||||
};
|
|
||||||
settlementContext.Result.DidCombatWin = didCombatWin;
|
|
||||||
settlementContext.Result.FinalCoin = Mathf.Max(0, resourceStore != null ? resourceStore.CurrentCoin : 0);
|
|
||||||
settlementContext.Result.FinalBaseHp = currentBaseHp;
|
|
||||||
settlementContext.Result.MaxBaseHp = maxBaseHp;
|
|
||||||
settlementContext.Result.DefeatedEnemyCount = Mathf.Max(0, defeatedEnemyCount);
|
|
||||||
settlementContext.Result.GainedGold = Mathf.Max(0, resourceStore != null ? resourceStore.GainedGold : 0);
|
|
||||||
settlementContext.Result.RewardInventory = resourceStore != null
|
|
||||||
? resourceStore.GetRewardInventorySnapshot()
|
|
||||||
: new BackpackInventoryData();
|
|
||||||
PopulateEnduranceSettlement(settlementContext, resourceStore);
|
|
||||||
settlementContext.Flags.ShouldOpenRewardSelection = shouldOpenFullBaseHpRewardSelect;
|
|
||||||
settlementContext.Flags.DidEnterRewardSelection = false;
|
|
||||||
settlementContext.Summary.DefeatedEnemyCount = settlementContext.Result.DefeatedEnemyCount;
|
|
||||||
settlementContext.Summary.GainedGold = settlementContext.Result.GainedGold;
|
|
||||||
settlementContext.Summary.RewardInventory = settlementContext.Result.RewardInventory;
|
|
||||||
|
|
||||||
Log.Info(
|
|
||||||
"Combat settlement resolved. Level={0}, BaseHp={1}/{2}, LevelReward={3}, BonusRate={4:P0}, BonusGold={5}, FullHpRewardSelect={6}, EnduranceTargets={7}.",
|
|
||||||
currentLevel != null ? currentLevel.Id : 0,
|
|
||||||
currentBaseHp,
|
|
||||||
maxBaseHp,
|
|
||||||
levelRewardGold,
|
|
||||||
bonusRate,
|
|
||||||
bonusGold,
|
|
||||||
shouldOpenFullBaseHpRewardSelect,
|
|
||||||
settlementContext.Result.Endurance.TargetTowerInstanceIds.Count);
|
|
||||||
return settlementContext;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CommitSettlementInventory(CombatSettlementContext settlementContext)
|
public void CommitSettlementInventory(CombatSettlementContext settlementContext)
|
||||||
{
|
{
|
||||||
if (settlementContext == null || settlementContext.Flags.IsCommitted)
|
_committer.CommitSettlementInventory(settlementContext);
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
BackpackInventoryData rewardInventory = settlementContext.Result.RewardInventory ?? new BackpackInventoryData();
|
|
||||||
GameEntry.PlayerInventory?.MergeInventory(rewardInventory);
|
|
||||||
settlementContext.Result.RewardInventory = rewardInventory;
|
|
||||||
settlementContext.Summary.RewardInventory = rewardInventory;
|
|
||||||
settlementContext.Result.Endurance.AffectedTowerCount = ApplyDeferredSettlementEndurance(settlementContext);
|
|
||||||
settlementContext.Flags.IsCommitted = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryPrepareRewardSelection(
|
public bool TryPrepareRewardSelection(
|
||||||
CombatSettlementContext settlementContext,
|
CombatSettlementContext settlementContext,
|
||||||
int displayPhaseIndex,
|
int displayPhaseIndex,
|
||||||
LevelThemeType themeType,
|
LevelThemeType themeType,
|
||||||
|
int runSeed,
|
||||||
|
int sequenceIndex,
|
||||||
|
ref int nextRewardOrdinal,
|
||||||
RewardSelectFormUseCase rewardSelectFormUseCase,
|
RewardSelectFormUseCase rewardSelectFormUseCase,
|
||||||
Action<RewardSelectItemRawData> onRewardSelected,
|
Action<RewardSelectItemRawData> onRewardSelected,
|
||||||
Action onGiveUp)
|
Action onGiveUp)
|
||||||
@@ -97,36 +51,22 @@ namespace GeometryTD.CustomComponent
|
|||||||
IReadOnlyList<TowerCompItemData> candidateItems = GameEntry.InventoryGeneration.BuildRewardCandidates(
|
IReadOnlyList<TowerCompItemData> candidateItems = GameEntry.InventoryGeneration.BuildRewardCandidates(
|
||||||
displayPhaseIndex,
|
displayPhaseIndex,
|
||||||
themeType,
|
themeType,
|
||||||
RewardSelectDisplayCount);
|
RewardSelectDisplayCount,
|
||||||
|
runSeed,
|
||||||
|
sequenceIndex,
|
||||||
|
ref nextRewardOrdinal);
|
||||||
if (candidateItems == null || candidateItems.Count <= 0)
|
if (candidateItems == null || candidateItems.Count <= 0)
|
||||||
{
|
{
|
||||||
settlementContext.Flags.ShouldOpenRewardSelection = false;
|
settlementContext.Flags.ShouldOpenRewardSelection = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<RewardSelectItemRawData> rewardPool = new List<RewardSelectItemRawData>(candidateItems.Count);
|
|
||||||
foreach (var item in candidateItems)
|
|
||||||
{
|
|
||||||
if (item == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
rewardPool.Add(BuildRewardSelectRawData(item));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rewardPool.Count <= 0)
|
|
||||||
{
|
|
||||||
settlementContext.Flags.ShouldOpenRewardSelection = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
rewardSelectFormUseCase.SetCallbacks(onRewardSelected, onGiveUp);
|
rewardSelectFormUseCase.SetCallbacks(onRewardSelected, onGiveUp);
|
||||||
rewardSelectFormUseCase.ConfigureRewardPool(
|
rewardSelectFormUseCase.ConfigureRewardCandidates(
|
||||||
rewardPool,
|
candidateItems,
|
||||||
displayCount: RewardSelectDisplayCount,
|
displayCount: RewardSelectDisplayCount,
|
||||||
refreshCost: 0,
|
refreshCost: 0,
|
||||||
allowRefreshOnce: false,
|
allowRotateOnce: false,
|
||||||
allowGiveUp: false,
|
allowGiveUp: false,
|
||||||
tipText: "基地满血奖励:请选择 1 个组件");
|
tipText: "基地满血奖励:请选择 1 个组件");
|
||||||
|
|
||||||
@@ -144,13 +84,7 @@ namespace GeometryTD.CustomComponent
|
|||||||
|
|
||||||
public void ApplySelectedReward(CombatSettlementContext settlementContext, RewardSelectItemRawData selectedReward)
|
public void ApplySelectedReward(CombatSettlementContext settlementContext, RewardSelectItemRawData selectedReward)
|
||||||
{
|
{
|
||||||
if (settlementContext?.Result.RewardInventory == null || selectedReward?.SourceItem == null)
|
_committer.ApplySelectedReward(settlementContext, selectedReward);
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TryAppendRewardComponent(settlementContext.Result.RewardInventory, selectedReward.SourceItem);
|
|
||||||
settlementContext.Summary.RewardInventory = settlementContext.Result.RewardInventory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenCombatFinishForm(
|
public void OpenCombatFinishForm(
|
||||||
@@ -166,134 +100,5 @@ namespace GeometryTD.CustomComponent
|
|||||||
GameEntry.UIRouter.OpenUI(UIFormType.CombatFinishForm);
|
GameEntry.UIRouter.OpenUI(UIFormType.CombatFinishForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ResolveSettlementByBaseHp(
|
|
||||||
bool didCombatWin,
|
|
||||||
DRLevel currentLevel,
|
|
||||||
CombatRunResourceStore resourceStore,
|
|
||||||
out int currentBaseHp,
|
|
||||||
out int maxBaseHp,
|
|
||||||
out int levelRewardGold,
|
|
||||||
out float bonusRate,
|
|
||||||
out int bonusGold,
|
|
||||||
out bool shouldOpenFullBaseHpRewardSelect)
|
|
||||||
{
|
|
||||||
currentBaseHp = Mathf.Max(0, resourceStore != null ? resourceStore.CurrentBaseHp : 0);
|
|
||||||
maxBaseHp = resourceStore != null ? Mathf.Max(0, resourceStore.MaxBaseHp) : 0;
|
|
||||||
if (maxBaseHp > 0)
|
|
||||||
{
|
|
||||||
currentBaseHp = Mathf.Clamp(currentBaseHp, 0, maxBaseHp);
|
|
||||||
}
|
|
||||||
|
|
||||||
levelRewardGold = currentLevel != null ? Mathf.Max(0, currentLevel.RewardGold) : 0;
|
|
||||||
bonusRate = 0f;
|
|
||||||
bonusGold = 0;
|
|
||||||
shouldOpenFullBaseHpRewardSelect = false;
|
|
||||||
|
|
||||||
if (!didCombatWin || resourceStore == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maxBaseHp > 0 && currentBaseHp >= maxBaseHp)
|
|
||||||
{
|
|
||||||
bonusRate = FullBaseHpGoldBonusRate;
|
|
||||||
shouldOpenFullBaseHpRewardSelect = true;
|
|
||||||
}
|
|
||||||
else if (maxBaseHp > 0)
|
|
||||||
{
|
|
||||||
float hpRate = (float)currentBaseHp / maxBaseHp;
|
|
||||||
if (hpRate >= HighBaseHpThreshold)
|
|
||||||
{
|
|
||||||
bonusRate = HighBaseHpGoldBonusRate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int goldForBonusCalculation = Mathf.Max(0, resourceStore.GainedGold) + levelRewardGold;
|
|
||||||
bonusGold = bonusRate > 0f ? Mathf.FloorToInt(goldForBonusCalculation * bonusRate) : 0;
|
|
||||||
int settlementGold = levelRewardGold + bonusGold;
|
|
||||||
resourceStore.AddSettlementGold(settlementGold);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void PopulateEnduranceSettlement(
|
|
||||||
CombatSettlementContext settlementContext,
|
|
||||||
CombatRunResourceStore resourceStore)
|
|
||||||
{
|
|
||||||
if (settlementContext == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CombatSettlementEnduranceResult enduranceResult = settlementContext.Result.Endurance;
|
|
||||||
enduranceResult.TargetTowerInstanceIds.Clear();
|
|
||||||
enduranceResult.EnduranceLossPerComponent = SettlementTowerEnduranceLoss;
|
|
||||||
|
|
||||||
IReadOnlyList<long> participantTowerIds = resourceStore?.GetParticipantTowerInstanceIdSnapshot();
|
|
||||||
if (participantTowerIds == null || participantTowerIds.Count <= 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < participantTowerIds.Count; i++)
|
|
||||||
{
|
|
||||||
long towerId = participantTowerIds[i];
|
|
||||||
if (towerId > 0)
|
|
||||||
{
|
|
||||||
enduranceResult.TargetTowerInstanceIds.Add(towerId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int ApplyDeferredSettlementEndurance(CombatSettlementContext settlementContext)
|
|
||||||
{
|
|
||||||
if (settlementContext == null ||
|
|
||||||
settlementContext.Result.Endurance.EnduranceLossPerComponent <= 0f ||
|
|
||||||
settlementContext.Result.Endurance.TargetTowerInstanceIds.Count <= 0)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlayerInventoryComponent inventory = GameEntry.PlayerInventory;
|
|
||||||
if (inventory == null)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return inventory.ReduceTowerEndurance(
|
|
||||||
settlementContext.Result.Endurance.TargetTowerInstanceIds,
|
|
||||||
settlementContext.Result.Endurance.EnduranceLossPerComponent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool TryAppendRewardComponent(BackpackInventoryData rewardInventory, TowerCompItemData selectedItem)
|
|
||||||
{
|
|
||||||
if (rewardInventory == null || selectedItem == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedItem is MuzzleCompItemData muzzleComp)
|
|
||||||
{
|
|
||||||
rewardInventory.MuzzleComponents.Add(muzzleComp);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedItem is BearingCompItemData bearingComp)
|
|
||||||
{
|
|
||||||
rewardInventory.BearingComponents.Add(bearingComp);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedItem is BaseCompItemData baseComp)
|
|
||||||
{
|
|
||||||
rewardInventory.BaseComponents.Add(baseComp);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static RewardSelectItemRawData BuildRewardSelectRawData(TowerCompItemData item)
|
|
||||||
{
|
|
||||||
return RewardSelectItemRawDataBuilder.Build(item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -16,16 +16,23 @@ namespace GeometryTD.CustomComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
Coordinator.EnsureRewardSelectFormUseCaseBound();
|
Coordinator.EnsureRewardSelectFormUseCaseBound();
|
||||||
|
int nextRewardOrdinal = Runtime.NextRewardOrdinal;
|
||||||
if (!Runtime.CombatSettlementService.TryPrepareRewardSelection(
|
if (!Runtime.CombatSettlementService.TryPrepareRewardSelection(
|
||||||
Runtime.SettlementContext,
|
Runtime.SettlementContext,
|
||||||
Runtime.PhaseLoopRuntime.DisplayPhaseIndex,
|
Runtime.PhaseLoopRuntime.DisplayPhaseIndex,
|
||||||
Coordinator.ResolveCurrentThemeType(),
|
Coordinator.ResolveCurrentThemeType(),
|
||||||
|
Runtime.NodeContext != null ? Runtime.NodeContext.RunSeed : 0,
|
||||||
|
Runtime.NodeContext != null ? Runtime.NodeContext.SequenceIndex : -1,
|
||||||
|
ref nextRewardOrdinal,
|
||||||
Runtime.RewardSelectFormUseCase,
|
Runtime.RewardSelectFormUseCase,
|
||||||
Coordinator.OnFullBaseHpRewardSelected,
|
Coordinator.OnFullBaseHpRewardSelected,
|
||||||
Coordinator.OnFullBaseHpRewardGiveUp))
|
Coordinator.OnFullBaseHpRewardGiveUp))
|
||||||
{
|
{
|
||||||
Coordinator.ChangeState(new CombatFinishFormState(Runtime, Coordinator));
|
Coordinator.ChangeState(new CombatFinishFormState(Runtime, Coordinator));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Runtime.NextRewardOrdinal = nextRewardOrdinal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnExit()
|
public override void OnExit()
|
||||||
|
|||||||
+5
-4
@@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using GeometryTD.CustomEvent;
|
using GeometryTD.CustomEvent;
|
||||||
using GeometryTD.DataTable;
|
using GeometryTD.DataTable;
|
||||||
|
using GeometryTD.Procedure;
|
||||||
using UnityGameFramework.Runtime;
|
using UnityGameFramework.Runtime;
|
||||||
|
|
||||||
namespace GeometryTD.CustomComponent
|
namespace GeometryTD.CustomComponent
|
||||||
@@ -41,10 +42,10 @@ namespace GeometryTD.CustomComponent
|
|||||||
GameEntry.Event.Fire(
|
GameEntry.Event.Fire(
|
||||||
Coordinator,
|
Coordinator,
|
||||||
NodeEnterEventArgs.Create(
|
NodeEnterEventArgs.Create(
|
||||||
Runtime.RunId,
|
Runtime.NodeContext?.RunId,
|
||||||
Runtime.NodeId,
|
Runtime.NodeContext?.NodeId ?? 0,
|
||||||
Runtime.NodeType,
|
Runtime.NodeContext?.NodeType ?? RunNodeType.None,
|
||||||
Runtime.SequenceIndex));
|
Runtime.NodeContext?.SequenceIndex ?? -1));
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Info(
|
Log.Info(
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using GeometryTD.DataTable;
|
||||||
|
using GeometryTD.Definition;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityGameFramework.Runtime;
|
||||||
|
|
||||||
|
namespace GeometryTD.CustomComponent
|
||||||
|
{
|
||||||
|
public sealed class CombatSettlementCalculator
|
||||||
|
{
|
||||||
|
private const float FullBaseHpGoldBonusRate = 0.3f;
|
||||||
|
private const float HighBaseHpGoldBonusRate = 0.1f;
|
||||||
|
private const float HighBaseHpThreshold = 0.8f;
|
||||||
|
private const float SettlementTowerEnduranceLoss = 1f;
|
||||||
|
|
||||||
|
public CombatSettlementContext BuildSettlementContext(
|
||||||
|
bool didCombatWin,
|
||||||
|
DRLevel currentLevel,
|
||||||
|
int defeatedEnemyCount,
|
||||||
|
CombatRunResourceStore resourceStore)
|
||||||
|
{
|
||||||
|
bool shouldOpenFullBaseHpRewardSelect = false;
|
||||||
|
ResolveSettlementByBaseHp(
|
||||||
|
didCombatWin,
|
||||||
|
currentLevel,
|
||||||
|
resourceStore,
|
||||||
|
out int currentBaseHp,
|
||||||
|
out int maxBaseHp,
|
||||||
|
out int levelRewardGold,
|
||||||
|
out float bonusRate,
|
||||||
|
out int bonusGold,
|
||||||
|
out shouldOpenFullBaseHpRewardSelect);
|
||||||
|
|
||||||
|
CombatSettlementContext settlementContext = new CombatSettlementContext();
|
||||||
|
settlementContext.Result.DidCombatWin = didCombatWin;
|
||||||
|
settlementContext.Result.FinalCoin = Mathf.Max(0, resourceStore != null ? resourceStore.CurrentCoin : 0);
|
||||||
|
settlementContext.Result.FinalBaseHp = currentBaseHp;
|
||||||
|
settlementContext.Result.MaxBaseHp = maxBaseHp;
|
||||||
|
settlementContext.Result.DefeatedEnemyCount = Mathf.Max(0, defeatedEnemyCount);
|
||||||
|
settlementContext.Result.GainedGold = Mathf.Max(0, resourceStore != null ? resourceStore.GainedGold : 0);
|
||||||
|
settlementContext.Result.RewardInventory = resourceStore != null
|
||||||
|
? resourceStore.GetRewardInventorySnapshot()
|
||||||
|
: new BackpackInventoryData();
|
||||||
|
PopulateEnduranceSettlement(settlementContext, resourceStore);
|
||||||
|
settlementContext.Flags.ShouldOpenRewardSelection = shouldOpenFullBaseHpRewardSelect;
|
||||||
|
settlementContext.Flags.DidEnterRewardSelection = false;
|
||||||
|
settlementContext.Summary.DefeatedEnemyCount = settlementContext.Result.DefeatedEnemyCount;
|
||||||
|
settlementContext.Summary.GainedGold = settlementContext.Result.GainedGold;
|
||||||
|
settlementContext.Summary.RewardInventory = settlementContext.Result.RewardInventory;
|
||||||
|
|
||||||
|
Log.Info(
|
||||||
|
"Combat settlement resolved. Level={0}, BaseHp={1}/{2}, LevelReward={3}, BonusRate={4:P0}, BonusGold={5}, FullHpRewardSelect={6}, EnduranceTargets={7}.",
|
||||||
|
currentLevel != null ? currentLevel.Id : 0,
|
||||||
|
currentBaseHp,
|
||||||
|
maxBaseHp,
|
||||||
|
levelRewardGold,
|
||||||
|
bonusRate,
|
||||||
|
bonusGold,
|
||||||
|
shouldOpenFullBaseHpRewardSelect,
|
||||||
|
settlementContext.Result.Endurance.TargetTowerInstanceIds.Count);
|
||||||
|
return settlementContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ResolveSettlementByBaseHp(
|
||||||
|
bool didCombatWin,
|
||||||
|
DRLevel currentLevel,
|
||||||
|
CombatRunResourceStore resourceStore,
|
||||||
|
out int currentBaseHp,
|
||||||
|
out int maxBaseHp,
|
||||||
|
out int levelRewardGold,
|
||||||
|
out float bonusRate,
|
||||||
|
out int bonusGold,
|
||||||
|
out bool shouldOpenFullBaseHpRewardSelect)
|
||||||
|
{
|
||||||
|
currentBaseHp = Mathf.Max(0, resourceStore != null ? resourceStore.CurrentBaseHp : 0);
|
||||||
|
maxBaseHp = resourceStore != null ? Mathf.Max(0, resourceStore.MaxBaseHp) : 0;
|
||||||
|
if (maxBaseHp > 0)
|
||||||
|
{
|
||||||
|
currentBaseHp = Mathf.Clamp(currentBaseHp, 0, maxBaseHp);
|
||||||
|
}
|
||||||
|
|
||||||
|
levelRewardGold = currentLevel != null ? Mathf.Max(0, currentLevel.RewardGold) : 0;
|
||||||
|
bonusRate = 0f;
|
||||||
|
bonusGold = 0;
|
||||||
|
shouldOpenFullBaseHpRewardSelect = false;
|
||||||
|
|
||||||
|
if (!didCombatWin || resourceStore == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxBaseHp > 0 && currentBaseHp >= maxBaseHp)
|
||||||
|
{
|
||||||
|
bonusRate = FullBaseHpGoldBonusRate;
|
||||||
|
shouldOpenFullBaseHpRewardSelect = true;
|
||||||
|
}
|
||||||
|
else if (maxBaseHp > 0)
|
||||||
|
{
|
||||||
|
float hpRate = (float)currentBaseHp / maxBaseHp;
|
||||||
|
if (hpRate >= HighBaseHpThreshold)
|
||||||
|
{
|
||||||
|
bonusRate = HighBaseHpGoldBonusRate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int goldForBonusCalculation = Mathf.Max(0, resourceStore.GainedGold) + levelRewardGold;
|
||||||
|
bonusGold = bonusRate > 0f ? Mathf.FloorToInt(goldForBonusCalculation * bonusRate) : 0;
|
||||||
|
int settlementGold = levelRewardGold + bonusGold;
|
||||||
|
resourceStore.AddSettlementGold(settlementGold);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PopulateEnduranceSettlement(
|
||||||
|
CombatSettlementContext settlementContext,
|
||||||
|
CombatRunResourceStore resourceStore)
|
||||||
|
{
|
||||||
|
if (settlementContext == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CombatSettlementEnduranceResult enduranceResult = settlementContext.Result.Endurance;
|
||||||
|
enduranceResult.TargetTowerInstanceIds.Clear();
|
||||||
|
enduranceResult.EnduranceLossPerComponent = SettlementTowerEnduranceLoss;
|
||||||
|
|
||||||
|
IReadOnlyList<long> participantTowerIds = resourceStore?.GetParticipantTowerInstanceIdSnapshot();
|
||||||
|
if (participantTowerIds == null || participantTowerIds.Count <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < participantTowerIds.Count; i++)
|
||||||
|
{
|
||||||
|
long towerId = participantTowerIds[i];
|
||||||
|
if (towerId > 0)
|
||||||
|
{
|
||||||
|
enduranceResult.TargetTowerInstanceIds.Add(towerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c8aa78b2f9ff4eb4bf83b8c4f652ce24
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
using GeometryTD.Definition;
|
||||||
|
using GeometryTD.UI;
|
||||||
|
using UnityGameFramework.Runtime;
|
||||||
|
|
||||||
|
namespace GeometryTD.CustomComponent
|
||||||
|
{
|
||||||
|
public sealed class CombatSettlementCommitter
|
||||||
|
{
|
||||||
|
public void CommitSettlementInventory(CombatSettlementContext settlementContext)
|
||||||
|
{
|
||||||
|
if (settlementContext == null || settlementContext.Flags.IsCommitted)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BackpackInventoryData rewardInventory = settlementContext.Result.RewardInventory ?? new BackpackInventoryData();
|
||||||
|
GameEntry.PlayerInventory?.MergeInventory(rewardInventory);
|
||||||
|
settlementContext.Result.RewardInventory = rewardInventory;
|
||||||
|
settlementContext.Summary.RewardInventory = rewardInventory;
|
||||||
|
settlementContext.Result.Endurance.AffectedTowerCount = ApplyDeferredSettlementEndurance(settlementContext);
|
||||||
|
settlementContext.Flags.IsCommitted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplySelectedReward(CombatSettlementContext settlementContext, RewardSelectItemRawData selectedReward)
|
||||||
|
{
|
||||||
|
if (settlementContext?.Result.RewardInventory == null || selectedReward?.SourceItem == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TryAppendRewardComponent(settlementContext.Result.RewardInventory, selectedReward.SourceItem);
|
||||||
|
settlementContext.Summary.RewardInventory = settlementContext.Result.RewardInventory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ApplyDeferredSettlementEndurance(CombatSettlementContext settlementContext)
|
||||||
|
{
|
||||||
|
if (settlementContext == null ||
|
||||||
|
settlementContext.Result.Endurance.EnduranceLossPerComponent <= 0f ||
|
||||||
|
settlementContext.Result.Endurance.TargetTowerInstanceIds.Count <= 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerInventoryComponent inventory = GameEntry.PlayerInventory;
|
||||||
|
if (inventory == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return inventory.ReduceTowerEndurance(
|
||||||
|
settlementContext.Result.Endurance.TargetTowerInstanceIds,
|
||||||
|
settlementContext.Result.Endurance.EnduranceLossPerComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryAppendRewardComponent(BackpackInventoryData rewardInventory, TowerCompItemData selectedItem)
|
||||||
|
{
|
||||||
|
if (rewardInventory == null || selectedItem == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedItem is MuzzleCompItemData muzzleComp)
|
||||||
|
{
|
||||||
|
rewardInventory.MuzzleComponents.Add(muzzleComp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedItem is BearingCompItemData bearingComp)
|
||||||
|
{
|
||||||
|
rewardInventory.BearingComponents.Add(bearingComp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedItem is BaseCompItemData baseComp)
|
||||||
|
{
|
||||||
|
rewardInventory.BaseComponents.Add(baseComp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1e4e69cf76f34e5baec16562d765fb85
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -14,10 +14,8 @@ namespace GeometryTD.CustomComponent
|
|||||||
{
|
{
|
||||||
public class EventNodeComponent : GameFrameworkComponent
|
public class EventNodeComponent : GameFrameworkComponent
|
||||||
{
|
{
|
||||||
private string _activeRunId;
|
private RunNodeExecutionContext _activeContext;
|
||||||
private int _activeNodeId;
|
private EventItem _activeEvent;
|
||||||
private RunNodeType _activeNodeType = RunNodeType.None;
|
|
||||||
private int _activeSequenceIndex = -1;
|
|
||||||
|
|
||||||
private readonly List<EventItem> _eventItems = new List<EventItem>();
|
private readonly List<EventItem> _eventItems = new List<EventItem>();
|
||||||
|
|
||||||
@@ -54,7 +52,7 @@ namespace GeometryTD.CustomComponent
|
|||||||
Log.Info("EventNodeComponent initialized with {0} events.", _eventItems.Count);
|
Log.Info("EventNodeComponent initialized with {0} events.", _eventItems.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StartEvent(string runId = null, int nodeId = 0, RunNodeType nodeType = RunNodeType.None, int sequenceIndex = -1)
|
public void StartEvent(RunNodeExecutionContext context = null)
|
||||||
{
|
{
|
||||||
if (!_initialized)
|
if (!_initialized)
|
||||||
{
|
{
|
||||||
@@ -67,22 +65,29 @@ namespace GeometryTD.CustomComponent
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int index = Random.Range(0, _eventItems.Count);
|
|
||||||
EventItem randomEvent = _eventItems[index];
|
|
||||||
|
|
||||||
if (_eventFormUseCase == null)
|
if (_eventFormUseCase == null)
|
||||||
{
|
{
|
||||||
Log.Warning("EventNodeComponent StartEvent failed. Event form is not initialized.");
|
Log.Warning("EventNodeComponent StartEvent failed. Event form is not initialized.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_activeRunId = runId;
|
_activeContext = context != null ? context.Clone() : null;
|
||||||
_activeNodeId = nodeId;
|
_activeEvent = SelectActiveEvent(_activeContext);
|
||||||
_activeNodeType = nodeType;
|
if (_activeEvent == null)
|
||||||
_activeSequenceIndex = sequenceIndex;
|
{
|
||||||
_eventFormUseCase.SetCurrentEvent(randomEvent);
|
Log.Warning("EventNodeComponent StartEvent failed. No active event could be resolved.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_eventFormUseCase.BindEvent(_activeEvent, _activeContext);
|
||||||
GameEntry.UIRouter.OpenUI(UIFormType.EventForm);
|
GameEntry.UIRouter.OpenUI(UIFormType.EventForm);
|
||||||
GameEntry.Event.Fire(this, NodeEnterEventArgs.Create(runId, nodeId, nodeType, sequenceIndex));
|
GameEntry.Event.Fire(
|
||||||
|
this,
|
||||||
|
NodeEnterEventArgs.Create(
|
||||||
|
_activeContext?.RunId,
|
||||||
|
_activeContext?.NodeId ?? 0,
|
||||||
|
_activeContext?.NodeType ?? RunNodeType.None,
|
||||||
|
_activeContext?.SequenceIndex ?? -1));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EndEvent()
|
public void EndEvent()
|
||||||
@@ -91,22 +96,24 @@ namespace GeometryTD.CustomComponent
|
|||||||
GameEntry.Event.Fire(
|
GameEntry.Event.Fire(
|
||||||
this,
|
this,
|
||||||
NodeCompleteEventArgs.Create(
|
NodeCompleteEventArgs.Create(
|
||||||
_activeRunId,
|
_activeContext?.RunId,
|
||||||
_activeNodeId,
|
_activeContext?.NodeId ?? 0,
|
||||||
_activeNodeType,
|
_activeContext?.NodeType ?? RunNodeType.None,
|
||||||
_activeSequenceIndex,
|
_activeContext?.SequenceIndex ?? -1,
|
||||||
RunNodeCompletionStatus.Completed,
|
RunNodeCompletionStatus.Completed,
|
||||||
true,
|
true,
|
||||||
GameEntry.PlayerInventory != null ? GameEntry.PlayerInventory.GetInventorySnapshot() : null));
|
_activeContext != null
|
||||||
|
? _activeContext.CreateCompletionSnapshot(
|
||||||
|
GameEntry.PlayerInventory != null ? GameEntry.PlayerInventory.GetInventorySnapshot() : null)
|
||||||
|
: null));
|
||||||
ClearActiveNodeContext();
|
ClearActiveNodeContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClearActiveNodeContext()
|
private void ClearActiveNodeContext()
|
||||||
{
|
{
|
||||||
_activeRunId = null;
|
_activeContext = null;
|
||||||
_activeNodeId = 0;
|
_activeEvent = null;
|
||||||
_activeNodeType = RunNodeType.None;
|
_eventFormUseCase?.Clear();
|
||||||
_activeSequenceIndex = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static EventOption[] ParseOptions(string optionsRaw)
|
private static EventOption[] ParseOptions(string optionsRaw)
|
||||||
@@ -200,5 +207,34 @@ namespace GeometryTD.CustomComponent
|
|||||||
|
|
||||||
return effects.ToArray();
|
return effects.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private EventItem SelectActiveEvent(RunNodeExecutionContext context)
|
||||||
|
{
|
||||||
|
if (_eventItems.Count <= 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
int randomIndex = Random.Range(0, _eventItems.Count);
|
||||||
|
return _eventItems[randomIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Random random = new System.Random(BuildSelectionSeed(context));
|
||||||
|
return _eventItems[random.Next(0, _eventItems.Count)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int BuildSelectionSeed(RunNodeExecutionContext context)
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
int seed = 17;
|
||||||
|
seed = seed * 31 + context.RunSeed;
|
||||||
|
seed = seed * 31 + context.SequenceIndex;
|
||||||
|
seed = seed * 31 + context.NodeId;
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,24 @@ using GameFramework.DataTable;
|
|||||||
using GeometryTD.DataTable;
|
using GeometryTD.DataTable;
|
||||||
using GeometryTD.Definition;
|
using GeometryTD.Definition;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using Random = System.Random;
|
||||||
|
|
||||||
namespace GeometryTD.CustomComponent
|
namespace GeometryTD.CustomComponent
|
||||||
{
|
{
|
||||||
public sealed class DropPoolRoller
|
public sealed class DropPoolRoller
|
||||||
{
|
{
|
||||||
private const float RarityCurveScalePhase = 30f;
|
private const float RarityCurveScalePhase = 30f;
|
||||||
|
private static readonly RarityType[] OrderedRarities =
|
||||||
|
{
|
||||||
|
RarityType.White,
|
||||||
|
RarityType.Green,
|
||||||
|
RarityType.Blue,
|
||||||
|
RarityType.Purple,
|
||||||
|
RarityType.Red
|
||||||
|
};
|
||||||
|
|
||||||
private readonly List<DROutGameDropPool> _eligibleRowBuffer = new();
|
private readonly List<DROutGameDropPool> _eligibleRowBuffer = new();
|
||||||
private readonly Dictionary<RarityType, float> _rarityWeightBuffer = new();
|
private readonly float[] _rarityWeightBuffer = new float[OrderedRarities.Length];
|
||||||
private readonly IDataTable<DROutGameDropPool> _dropPoolTable;
|
private readonly IDataTable<DROutGameDropPool> _dropPoolTable;
|
||||||
|
|
||||||
public DropPoolRoller(IDataTable<DROutGameDropPool> dropPoolTable)
|
public DropPoolRoller(IDataTable<DROutGameDropPool> dropPoolTable)
|
||||||
@@ -19,9 +28,15 @@ namespace GeometryTD.CustomComponent
|
|||||||
_dropPoolTable = dropPoolTable;
|
_dropPoolTable = dropPoolTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryRollRow(int displayPhaseIndex, LevelThemeType themeType, out DROutGameDropPool selectedRow)
|
public bool TryRollRow(
|
||||||
|
int displayPhaseIndex,
|
||||||
|
LevelThemeType themeType,
|
||||||
|
Random random,
|
||||||
|
out DROutGameDropPool selectedRow,
|
||||||
|
out RarityType selectedRarity)
|
||||||
{
|
{
|
||||||
selectedRow = null;
|
selectedRow = null;
|
||||||
|
selectedRarity = RarityType.None;
|
||||||
|
|
||||||
DROutGameDropPool[] allRows = _dropPoolTable.GetAllDataRows();
|
DROutGameDropPool[] allRows = _dropPoolTable.GetAllDataRows();
|
||||||
if (allRows == null || allRows.Length <= 0)
|
if (allRows == null || allRows.Length <= 0)
|
||||||
@@ -35,22 +50,29 @@ namespace GeometryTD.CustomComponent
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
RarityType selectedRarity = RollRarity(displayPhaseIndex);
|
selectedRarity = RollRarity(displayPhaseIndex, random);
|
||||||
if (selectedRarity == RarityType.None)
|
if (selectedRarity == RarityType.None)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int totalWeight = 0;
|
int totalWeight = 0;
|
||||||
for (int i = 0; i < _eligibleRowBuffer.Count; i++)
|
DROutGameDropPool fallbackRow = null;
|
||||||
|
foreach (var row in _eligibleRowBuffer)
|
||||||
{
|
{
|
||||||
DROutGameDropPool row = _eligibleRowBuffer[i];
|
if (!IsEligibleAtPhase(row, selectedRarity, displayPhaseIndex))
|
||||||
if (row.Rarity != selectedRarity)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
totalWeight += Mathf.Max(1, row.Weight);
|
int rowWeight = row.GetWeight(selectedRarity);
|
||||||
|
if (rowWeight <= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fallbackRow = row;
|
||||||
|
totalWeight += rowWeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (totalWeight <= 0)
|
if (totalWeight <= 0)
|
||||||
@@ -58,16 +80,22 @@ namespace GeometryTD.CustomComponent
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int randomWeight = Random.Range(1, totalWeight + 1);
|
int randomWeight = random.Next(1, totalWeight + 1);
|
||||||
int cumulativeWeight = 0;
|
int cumulativeWeight = 0;
|
||||||
foreach (var row in _eligibleRowBuffer)
|
foreach (var row in _eligibleRowBuffer)
|
||||||
{
|
{
|
||||||
if (row.Rarity != selectedRarity)
|
if (!IsEligibleAtPhase(row, selectedRarity, displayPhaseIndex))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
cumulativeWeight += Mathf.Max(1, row.Weight);
|
int rowWeight = row.GetWeight(selectedRarity);
|
||||||
|
if (rowWeight <= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cumulativeWeight += rowWeight;
|
||||||
if (randomWeight <= cumulativeWeight)
|
if (randomWeight <= cumulativeWeight)
|
||||||
{
|
{
|
||||||
selectedRow = row;
|
selectedRow = row;
|
||||||
@@ -75,7 +103,7 @@ namespace GeometryTD.CustomComponent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedRow = _eligibleRowBuffer[_eligibleRowBuffer.Count - 1];
|
selectedRow = fallbackRow;
|
||||||
return selectedRow != null;
|
return selectedRow != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,9 +114,8 @@ namespace GeometryTD.CustomComponent
|
|||||||
{
|
{
|
||||||
_eligibleRowBuffer.Clear();
|
_eligibleRowBuffer.Clear();
|
||||||
|
|
||||||
for (int i = 0; i < allRows.Length; i++)
|
foreach (var row in allRows)
|
||||||
{
|
{
|
||||||
DROutGameDropPool row = allRows[i];
|
|
||||||
if (row == null)
|
if (row == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -99,7 +126,7 @@ namespace GeometryTD.CustomComponent
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (displayPhaseIndex < row.MinPhase || displayPhaseIndex > row.MaxPhase)
|
if (!IsEligibleAtPhase(row, displayPhaseIndex))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -108,35 +135,45 @@ namespace GeometryTD.CustomComponent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private RarityType RollRarity(int displayPhaseIndex)
|
private RarityType RollRarity(int displayPhaseIndex, Random random)
|
||||||
{
|
{
|
||||||
_rarityWeightBuffer.Clear();
|
for (int i = 0; i < _rarityWeightBuffer.Length; i++)
|
||||||
|
{
|
||||||
|
_rarityWeightBuffer[i] = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
float phaseT = Mathf.Clamp01((displayPhaseIndex - 1) / RarityCurveScalePhase);
|
float phaseT = Mathf.Clamp01((displayPhaseIndex - 1) / RarityCurveScalePhase);
|
||||||
|
|
||||||
for (int i = 0; i < _eligibleRowBuffer.Count; i++)
|
foreach (var row in _eligibleRowBuffer)
|
||||||
{
|
{
|
||||||
DROutGameDropPool row = _eligibleRowBuffer[i];
|
for (int rarityIndex = 0; rarityIndex < OrderedRarities.Length; rarityIndex++)
|
||||||
float curveWeight = GetRarityCurveWeight(row.Rarity, phaseT);
|
{
|
||||||
|
RarityType rarity = OrderedRarities[rarityIndex];
|
||||||
|
if (!IsEligibleAtPhase(row, rarity, displayPhaseIndex))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rowWeight = row.GetWeight(rarity);
|
||||||
|
if (rowWeight <= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float curveWeight = GetRarityCurveWeight(rarity, phaseT);
|
||||||
if (curveWeight <= 0f)
|
if (curveWeight <= 0f)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
float rowWeight = Mathf.Max(1, row.Weight) * curveWeight;
|
_rarityWeightBuffer[rarityIndex] += rowWeight * curveWeight;
|
||||||
if (_rarityWeightBuffer.TryGetValue(row.Rarity, out float existingWeight))
|
|
||||||
{
|
|
||||||
_rarityWeightBuffer[row.Rarity] = existingWeight + rowWeight;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_rarityWeightBuffer[row.Rarity] = rowWeight;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float totalWeight = 0f;
|
float totalWeight = 0f;
|
||||||
foreach (var pair in _rarityWeightBuffer)
|
foreach (var weight in _rarityWeightBuffer)
|
||||||
{
|
{
|
||||||
totalWeight += Mathf.Max(0f, pair.Value);
|
totalWeight += Mathf.Max(0f, weight);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (totalWeight <= 0f)
|
if (totalWeight <= 0f)
|
||||||
@@ -144,25 +181,54 @@ namespace GeometryTD.CustomComponent
|
|||||||
return RarityType.None;
|
return RarityType.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
float randomWeight = Random.value * totalWeight;
|
float randomWeight = (float)(random.NextDouble() * totalWeight);
|
||||||
float cumulativeWeight = 0f;
|
float cumulativeWeight = 0f;
|
||||||
foreach (var pair in _rarityWeightBuffer)
|
for (int rarityIndex = 0; rarityIndex < _rarityWeightBuffer.Length; rarityIndex++)
|
||||||
{
|
{
|
||||||
cumulativeWeight += Mathf.Max(0f, pair.Value);
|
cumulativeWeight += Mathf.Max(0f, _rarityWeightBuffer[rarityIndex]);
|
||||||
if (randomWeight <= cumulativeWeight)
|
if (randomWeight <= cumulativeWeight)
|
||||||
{
|
{
|
||||||
return pair.Key;
|
return OrderedRarities[rarityIndex];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var pair in _rarityWeightBuffer)
|
for (int rarityIndex = 0; rarityIndex < _rarityWeightBuffer.Length; rarityIndex++)
|
||||||
{
|
{
|
||||||
return pair.Key;
|
if (_rarityWeightBuffer[rarityIndex] > 0f)
|
||||||
|
{
|
||||||
|
return OrderedRarities[rarityIndex];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return RarityType.None;
|
return RarityType.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsEligibleAtPhase(DROutGameDropPool row, int displayPhaseIndex)
|
||||||
|
{
|
||||||
|
for (int rarityIndex = 0; rarityIndex < OrderedRarities.Length; rarityIndex++)
|
||||||
|
{
|
||||||
|
RarityType rarity = OrderedRarities[rarityIndex];
|
||||||
|
if (IsEligibleAtPhase(row, rarity, displayPhaseIndex))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsEligibleAtPhase(DROutGameDropPool row, RarityType rarity, int displayPhaseIndex)
|
||||||
|
{
|
||||||
|
if (row.GetWeight(rarity) <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int minPhase = row.GetMinPhase(rarity);
|
||||||
|
int maxPhase = row.GetMaxPhase(rarity);
|
||||||
|
return displayPhaseIndex >= minPhase && displayPhaseIndex <= maxPhase;
|
||||||
|
}
|
||||||
|
|
||||||
private static float GetRarityCurveWeight(RarityType rarityType, float phaseT)
|
private static float GetRarityCurveWeight(RarityType rarityType, float phaseT)
|
||||||
{
|
{
|
||||||
float hump = Mathf.Exp(-Mathf.Pow((phaseT - 0.35f) / 0.28f, 2f));
|
float hump = Mathf.Exp(-Mathf.Pow((phaseT - 0.35f) / 0.28f, 2f));
|
||||||
|
|||||||
+245
-136
@@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using GameFramework.DataTable;
|
using GameFramework.DataTable;
|
||||||
using GeometryTD.DataTable;
|
using GeometryTD.DataTable;
|
||||||
@@ -7,22 +6,12 @@ using GeometryTD.Factory;
|
|||||||
using GeometryTD.UI;
|
using GeometryTD.UI;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityGameFramework.Runtime;
|
using UnityGameFramework.Runtime;
|
||||||
using Random = UnityEngine.Random;
|
using Random = System.Random;
|
||||||
|
|
||||||
namespace GeometryTD.CustomComponent
|
namespace GeometryTD.CustomComponent
|
||||||
{
|
{
|
||||||
public sealed class InventoryGenerationComponent : GameFrameworkComponent
|
public sealed class InventoryGenerationComponent : GameFrameworkComponent
|
||||||
{
|
{
|
||||||
private const float DropChanceBase = 0.05f;
|
|
||||||
private const float DropChancePerPhase = 0.2f;
|
|
||||||
private const float DropChanceCap = 0.2f;
|
|
||||||
|
|
||||||
private long _nextTempInstanceId = 1000000;
|
|
||||||
private int _runSeed;
|
|
||||||
private int _nodeSequenceIndex = -1;
|
|
||||||
private int _nextDropTagOrdinal;
|
|
||||||
private int _nextRewardTagOrdinal;
|
|
||||||
|
|
||||||
private readonly List<DRShopPrice> _shopPriceRows = new();
|
private readonly List<DRShopPrice> _shopPriceRows = new();
|
||||||
private IDataTable<DRShopPrice> _shopPriceTable;
|
private IDataTable<DRShopPrice> _shopPriceTable;
|
||||||
private IDataTable<DROutGameDropPool> _dropPoolTable;
|
private IDataTable<DROutGameDropPool> _dropPoolTable;
|
||||||
@@ -32,22 +21,19 @@ namespace GeometryTD.CustomComponent
|
|||||||
private ShopGoodsBuilder _shopGoodsBuilder;
|
private ShopGoodsBuilder _shopGoodsBuilder;
|
||||||
private DropPoolRoller _dropPoolRoller;
|
private DropPoolRoller _dropPoolRoller;
|
||||||
private RewardCandidateBuilder _rewardCandidateBuilder;
|
private RewardCandidateBuilder _rewardCandidateBuilder;
|
||||||
|
private OutGameDropItemBuilder _outGameDropItemBuilder;
|
||||||
public void ConfigureRunContext(int runSeed, int sequenceIndex)
|
|
||||||
{
|
|
||||||
_runSeed = runSeed;
|
|
||||||
_nodeSequenceIndex = sequenceIndex;
|
|
||||||
_nextDropTagOrdinal = 0;
|
|
||||||
_nextRewardTagOrdinal = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<GoodsItemRawData> BuildShopGoods(int goodsCount, int runSeed = 0, int sequenceIndex = -1)
|
public List<GoodsItemRawData> BuildShopGoods(int goodsCount, int runSeed = 0, int sequenceIndex = -1)
|
||||||
{
|
{
|
||||||
EnsureShopBuilder();
|
EnsureShopBuilder();
|
||||||
return _shopGoodsBuilder.BuildGoods(goodsCount, runSeed, sequenceIndex, AllocateTempInstanceId);
|
return _shopGoodsBuilder.BuildGoods(goodsCount, runSeed, sequenceIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public EnemyDropResult ResolveEnemyDrop(in EnemyDropContext context)
|
public EnemyDropResult ResolveEnemyDrop(
|
||||||
|
in EnemyDropContext context,
|
||||||
|
int runSeed,
|
||||||
|
int sequenceIndex,
|
||||||
|
ref int nextDropOrdinal)
|
||||||
{
|
{
|
||||||
DREnemy enemy = context.Enemy;
|
DREnemy enemy = context.Enemy;
|
||||||
if (enemy == null)
|
if (enemy == null)
|
||||||
@@ -61,14 +47,25 @@ namespace GeometryTD.CustomComponent
|
|||||||
? Mathf.Clamp01(enemy.DropPercent * 0.01f)
|
? Mathf.Clamp01(enemy.DropPercent * 0.01f)
|
||||||
: Mathf.Clamp01(enemy.DropPercent);
|
: Mathf.Clamp01(enemy.DropPercent);
|
||||||
|
|
||||||
if (enemy.DropGold > 0 && dropRate > 0f && Random.value <= dropRate)
|
int dropOrdinal = nextDropOrdinal;
|
||||||
|
nextDropOrdinal++;
|
||||||
|
InventoryGenerationRandomContext randomContext =
|
||||||
|
new(runSeed, sequenceIndex, InventoryTagSourceType.Drop, dropOrdinal);
|
||||||
|
Random random = randomContext.CreateRandom();
|
||||||
|
|
||||||
|
if (enemy.DropGold > 0 && dropRate > 0f && random.NextDouble() <= dropRate)
|
||||||
{
|
{
|
||||||
gold = Mathf.Max(0, enemy.DropGold);
|
gold = Mathf.Max(0, enemy.DropGold);
|
||||||
}
|
}
|
||||||
|
|
||||||
TowerCompItemData lootItem = null;
|
TowerCompItemData lootItem = null;
|
||||||
if (ShouldRollOutGameItem(context.DisplayPhaseIndex) &&
|
if (OutGameDropRuleService.ShouldRollOutGameItem(context.DisplayPhaseIndex, random) &&
|
||||||
TryRollOutGameItem(context.DisplayPhaseIndex, context.ThemeType, out TowerCompItemData droppedItem))
|
TryRollOutGameItem(
|
||||||
|
context.DisplayPhaseIndex,
|
||||||
|
context.ThemeType,
|
||||||
|
randomContext,
|
||||||
|
random,
|
||||||
|
out TowerCompItemData droppedItem))
|
||||||
{
|
{
|
||||||
lootItem = droppedItem;
|
lootItem = droppedItem;
|
||||||
}
|
}
|
||||||
@@ -79,28 +76,104 @@ namespace GeometryTD.CustomComponent
|
|||||||
public IReadOnlyList<TowerCompItemData> BuildRewardCandidates(
|
public IReadOnlyList<TowerCompItemData> BuildRewardCandidates(
|
||||||
int displayPhaseIndex,
|
int displayPhaseIndex,
|
||||||
LevelThemeType themeType,
|
LevelThemeType themeType,
|
||||||
int candidateCount)
|
int candidateCount,
|
||||||
|
int runSeed,
|
||||||
|
int sequenceIndex,
|
||||||
|
ref int nextRewardOrdinal)
|
||||||
{
|
{
|
||||||
RewardCandidateBuilder rewardCandidateBuilder = EnsureRewardCandidateBuilder();
|
RewardCandidateBuilder rewardCandidateBuilder = EnsureRewardCandidateBuilder();
|
||||||
return rewardCandidateBuilder.BuildCandidates(
|
int rewardOrdinal = nextRewardOrdinal;
|
||||||
|
IReadOnlyList<TowerCompItemData> candidates = rewardCandidateBuilder.BuildCandidates(
|
||||||
displayPhaseIndex,
|
displayPhaseIndex,
|
||||||
themeType,
|
themeType,
|
||||||
candidateCount,
|
candidateCount,
|
||||||
|
CreateNextRewardRandomContext,
|
||||||
BuildRewardCandidateItem);
|
BuildRewardCandidateItem);
|
||||||
|
nextRewardOrdinal = rewardOrdinal;
|
||||||
|
return candidates;
|
||||||
|
|
||||||
|
InventoryGenerationRandomContext CreateNextRewardRandomContext()
|
||||||
|
{
|
||||||
|
return new InventoryGenerationRandomContext(
|
||||||
|
runSeed,
|
||||||
|
sequenceIndex,
|
||||||
|
InventoryTagSourceType.Reward,
|
||||||
|
rewardOrdinal++);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<TowerCompItemData> BuildEventRewardComponents(
|
||||||
|
int count,
|
||||||
|
RarityType rarity,
|
||||||
|
int runSeed,
|
||||||
|
int sequenceIndex,
|
||||||
|
int eventId,
|
||||||
|
int optionIndex,
|
||||||
|
int effectIndex)
|
||||||
|
{
|
||||||
|
return BuildEventRewardComponents(
|
||||||
|
count,
|
||||||
|
rarity,
|
||||||
|
rarity,
|
||||||
|
runSeed,
|
||||||
|
sequenceIndex,
|
||||||
|
eventId,
|
||||||
|
optionIndex,
|
||||||
|
effectIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<TowerCompItemData> BuildEventRewardComponents(
|
||||||
|
int count,
|
||||||
|
RarityType minRarity,
|
||||||
|
RarityType maxRarity,
|
||||||
|
int runSeed,
|
||||||
|
int sequenceIndex,
|
||||||
|
int eventId,
|
||||||
|
int optionIndex,
|
||||||
|
int effectIndex)
|
||||||
|
{
|
||||||
|
EnsureComponentTables();
|
||||||
|
|
||||||
|
int resolvedCount = Mathf.Max(0, count);
|
||||||
|
if (resolvedCount <= 0)
|
||||||
|
{
|
||||||
|
return System.Array.Empty<TowerCompItemData>();
|
||||||
|
}
|
||||||
|
|
||||||
|
RarityType normalizedMinRarity = InventoryRarityRuleService.NormalizeComponentRarity(minRarity);
|
||||||
|
RarityType normalizedMaxRarity = InventoryRarityRuleService.NormalizeComponentRarity(maxRarity);
|
||||||
|
if (normalizedMinRarity > normalizedMaxRarity)
|
||||||
|
{
|
||||||
|
throw new System.InvalidOperationException(
|
||||||
|
$"Event reward rarity range is invalid: {normalizedMinRarity} > {normalizedMaxRarity}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TowerCompItemData> result = new List<TowerCompItemData>(resolvedCount);
|
||||||
|
for (int i = 0; i < resolvedCount; i++)
|
||||||
|
{
|
||||||
|
InventoryGenerationRandomContext randomContext = CreateEventRandomContext(
|
||||||
|
runSeed,
|
||||||
|
sequenceIndex,
|
||||||
|
eventId,
|
||||||
|
optionIndex,
|
||||||
|
effectIndex,
|
||||||
|
i);
|
||||||
|
Random random = randomContext.CreateRandom();
|
||||||
|
result.Add(BuildRandomEventComponentItem(normalizedMinRarity, normalizedMaxRarity, randomContext, random));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsureShopTables()
|
private void EnsureShopTables()
|
||||||
{
|
{
|
||||||
_shopPriceTable ??= GameEntry.DataTable.GetDataTable<DRShopPrice>();
|
_shopPriceTable ??= GameEntry.DataTable.GetDataTable<DRShopPrice>();
|
||||||
_muzzleCompTable ??= GameEntry.DataTable.GetDataTable<DRMuzzleComp>();
|
EnsureComponentTables();
|
||||||
_bearingCompTable ??= GameEntry.DataTable.GetDataTable<DRBearingComp>();
|
|
||||||
_baseCompTable ??= GameEntry.DataTable.GetDataTable<DRBaseComp>();
|
|
||||||
|
|
||||||
if (_shopPriceTable == null || _muzzleCompTable == null || _bearingCompTable == null ||
|
if (_shopPriceTable == null)
|
||||||
_baseCompTable == null)
|
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
throw new System.InvalidOperationException(
|
||||||
"InventoryGenerationComponent requires ShopPrice, MuzzleComp, BearingComp, and BaseComp data tables.");
|
"InventoryGenerationComponent requires ShopPrice data table.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_shopPriceRows.Count > 0)
|
if (_shopPriceRows.Count > 0)
|
||||||
@@ -111,7 +184,7 @@ namespace GeometryTD.CustomComponent
|
|||||||
DRShopPrice[] rows = _shopPriceTable.GetAllDataRows();
|
DRShopPrice[] rows = _shopPriceTable.GetAllDataRows();
|
||||||
if (rows == null || rows.Length <= 0)
|
if (rows == null || rows.Length <= 0)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
throw new System.InvalidOperationException(
|
||||||
"InventoryGenerationComponent requires at least one shop price row.");
|
"InventoryGenerationComponent requires at least one shop price row.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +198,7 @@ namespace GeometryTD.CustomComponent
|
|||||||
|
|
||||||
if (_shopPriceRows.Count <= 0)
|
if (_shopPriceRows.Count <= 0)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("InventoryGenerationComponent requires non-null shop price rows.");
|
throw new System.InvalidOperationException("InventoryGenerationComponent requires non-null shop price rows.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,22 +212,15 @@ namespace GeometryTD.CustomComponent
|
|||||||
_baseCompTable);
|
_baseCompTable);
|
||||||
}
|
}
|
||||||
|
|
||||||
private long AllocateTempInstanceId()
|
|
||||||
{
|
|
||||||
return _nextTempInstanceId++;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EnsureDropTables()
|
private void EnsureDropTables()
|
||||||
{
|
{
|
||||||
_dropPoolTable ??= GameEntry.DataTable.GetDataTable<DROutGameDropPool>();
|
_dropPoolTable ??= GameEntry.DataTable.GetDataTable<DROutGameDropPool>();
|
||||||
_muzzleCompTable ??= GameEntry.DataTable.GetDataTable<DRMuzzleComp>();
|
EnsureComponentTables();
|
||||||
_bearingCompTable ??= GameEntry.DataTable.GetDataTable<DRBearingComp>();
|
|
||||||
_baseCompTable ??= GameEntry.DataTable.GetDataTable<DRBaseComp>();
|
|
||||||
|
|
||||||
if (_dropPoolTable == null || _muzzleCompTable == null || _bearingCompTable == null || _baseCompTable == null)
|
if (_dropPoolTable == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
throw new System.InvalidOperationException(
|
||||||
"InventoryGenerationComponent requires OutGameDropPool, MuzzleComp, BearingComp, and BaseComp data tables.");
|
"InventoryGenerationComponent requires OutGameDropPool data table.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,103 +237,45 @@ namespace GeometryTD.CustomComponent
|
|||||||
return _rewardCandidateBuilder;
|
return _rewardCandidateBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ShouldRollOutGameItem(int displayPhaseIndex)
|
private OutGameDropItemBuilder EnsureOutGameDropItemBuilder()
|
||||||
{
|
{
|
||||||
int phaseIndex = Mathf.Max(1, displayPhaseIndex);
|
EnsureDropTables();
|
||||||
float dropChance = Mathf.Clamp(DropChanceBase + (phaseIndex - 1) * DropChancePerPhase, 0f, DropChanceCap);
|
_outGameDropItemBuilder ??= new OutGameDropItemBuilder(
|
||||||
return Random.value <= dropChance;
|
_muzzleCompTable,
|
||||||
|
_bearingCompTable,
|
||||||
|
_baseCompTable);
|
||||||
|
return _outGameDropItemBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryRollOutGameItem(int displayPhaseIndex, LevelThemeType themeType, out TowerCompItemData droppedItem)
|
private bool TryRollOutGameItem(
|
||||||
|
int displayPhaseIndex,
|
||||||
|
LevelThemeType themeType,
|
||||||
|
InventoryGenerationRandomContext randomContext,
|
||||||
|
Random random,
|
||||||
|
out TowerCompItemData droppedItem)
|
||||||
{
|
{
|
||||||
droppedItem = null;
|
droppedItem = null;
|
||||||
DropPoolRoller dropPoolRoller = EnsureDropPoolRoller();
|
DropPoolRoller dropPoolRoller = EnsureDropPoolRoller();
|
||||||
int phaseIndex = Mathf.Max(1, displayPhaseIndex);
|
int phaseIndex = Mathf.Max(1, displayPhaseIndex);
|
||||||
if (!dropPoolRoller.TryRollRow(phaseIndex, themeType, out DROutGameDropPool selectedRow) || selectedRow == null)
|
if (!dropPoolRoller.TryRollRow(
|
||||||
|
phaseIndex,
|
||||||
|
themeType,
|
||||||
|
random,
|
||||||
|
out DROutGameDropPool selectedRow,
|
||||||
|
out RarityType selectedRarity) || selectedRow == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return TryBuildDropItem(selectedRow, InventoryTagSourceType.Drop, AllocateDropTagOrdinal(), out droppedItem);
|
return EnsureOutGameDropItemBuilder().TryBuildItem(selectedRow, selectedRarity, randomContext, out droppedItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryBuildDropItem(
|
private TowerCompItemData BuildRewardCandidateItem(
|
||||||
DROutGameDropPool row,
|
DROutGameDropPool row,
|
||||||
InventoryTagSourceType sourceType,
|
RarityType rarity,
|
||||||
int localOrdinal,
|
InventoryGenerationRandomContext randomContext)
|
||||||
out TowerCompItemData droppedItem)
|
|
||||||
{
|
{
|
||||||
droppedItem = null;
|
if (!EnsureOutGameDropItemBuilder().TryBuildItem(row, rarity, randomContext, out TowerCompItemData droppedItem))
|
||||||
if (row.ItemId <= 0 || string.IsNullOrWhiteSpace(row.ItemType))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
string itemType = row.ItemType.Trim();
|
|
||||||
if (itemType.Equals("MuzzleComp", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
DRMuzzleComp config = _muzzleCompTable.GetDataRow(row.ItemId);
|
|
||||||
if (config == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
droppedItem = ComponentItemFactory.CreateMuzzle(
|
|
||||||
config,
|
|
||||||
AllocateTempInstanceId(),
|
|
||||||
row.Rarity,
|
|
||||||
CreateRandomContext(sourceType, localOrdinal, config.Id));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (itemType.Equals("BearingComp", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
DRBearingComp config = _bearingCompTable.GetDataRow(row.ItemId);
|
|
||||||
if (config == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
droppedItem = ComponentItemFactory.CreateBearing(
|
|
||||||
config,
|
|
||||||
AllocateTempInstanceId(),
|
|
||||||
row.Rarity,
|
|
||||||
CreateRandomContext(sourceType, localOrdinal, config.Id));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (itemType.Equals("BaseComp", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
DRBaseComp config = _baseCompTable.GetDataRow(row.ItemId);
|
|
||||||
if (config == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
droppedItem = ComponentItemFactory.CreateBase(
|
|
||||||
config,
|
|
||||||
AllocateTempInstanceId(),
|
|
||||||
row.Rarity,
|
|
||||||
CreateRandomContext(sourceType, localOrdinal, config.Id));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int AllocateDropTagOrdinal()
|
|
||||||
{
|
|
||||||
return _nextDropTagOrdinal++;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int AllocateRewardTagOrdinal()
|
|
||||||
{
|
|
||||||
return _nextRewardTagOrdinal++;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TowerCompItemData BuildRewardCandidateItem(DROutGameDropPool row)
|
|
||||||
{
|
|
||||||
if (!TryBuildDropItem(row, InventoryTagSourceType.Reward, AllocateRewardTagOrdinal(), out TowerCompItemData droppedItem))
|
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -275,16 +283,117 @@ namespace GeometryTD.CustomComponent
|
|||||||
return droppedItem;
|
return droppedItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
private InventoryTagRandomContext CreateRandomContext(
|
private void EnsureComponentTables()
|
||||||
InventoryTagSourceType sourceType,
|
|
||||||
int localOrdinal,
|
|
||||||
int configId)
|
|
||||||
{
|
{
|
||||||
return sourceType switch
|
_muzzleCompTable ??= GameEntry.DataTable.GetDataTable<DRMuzzleComp>();
|
||||||
|
_bearingCompTable ??= GameEntry.DataTable.GetDataTable<DRBearingComp>();
|
||||||
|
_baseCompTable ??= GameEntry.DataTable.GetDataTable<DRBaseComp>();
|
||||||
|
|
||||||
|
if (_muzzleCompTable == null || _bearingCompTable == null || _baseCompTable == null)
|
||||||
{
|
{
|
||||||
InventoryTagSourceType.Reward => InventoryTagRandomContext.CreateReward(_runSeed, _nodeSequenceIndex, localOrdinal, configId),
|
throw new System.InvalidOperationException(
|
||||||
_ => InventoryTagRandomContext.CreateDrop(_runSeed, _nodeSequenceIndex, localOrdinal, configId)
|
"InventoryGenerationComponent requires MuzzleComp, BearingComp, and BaseComp data tables.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TowerCompItemData BuildRandomEventComponentItem(
|
||||||
|
RarityType minRarity,
|
||||||
|
RarityType maxRarity,
|
||||||
|
InventoryGenerationRandomContext randomContext,
|
||||||
|
Random random)
|
||||||
|
{
|
||||||
|
RarityType rarity = ResolveEventRewardRarity(minRarity, maxRarity, random);
|
||||||
|
int slotRoll = random.Next(0, 3);
|
||||||
|
return slotRoll switch
|
||||||
|
{
|
||||||
|
0 => BuildRandomEventMuzzleItem(rarity, randomContext, random),
|
||||||
|
1 => BuildRandomEventBearingItem(rarity, randomContext, random),
|
||||||
|
_ => BuildRandomEventBaseItem(rarity, randomContext, random)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static RarityType ResolveEventRewardRarity(
|
||||||
|
RarityType minRarity,
|
||||||
|
RarityType maxRarity,
|
||||||
|
Random random)
|
||||||
|
{
|
||||||
|
if (minRarity >= maxRarity)
|
||||||
|
{
|
||||||
|
return minRarity;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rarityValue = random.Next((int)minRarity, (int)maxRarity + 1);
|
||||||
|
return (RarityType)rarityValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MuzzleCompItemData BuildRandomEventMuzzleItem(
|
||||||
|
RarityType rarity,
|
||||||
|
InventoryGenerationRandomContext randomContext,
|
||||||
|
Random random)
|
||||||
|
{
|
||||||
|
DRMuzzleComp[] rows = _muzzleCompTable.GetAllDataRows();
|
||||||
|
DRMuzzleComp config = rows[random.Next(0, rows.Length)];
|
||||||
|
return ComponentItemFactory.CreateMuzzle(
|
||||||
|
config,
|
||||||
|
randomContext.CreateStableItemInstanceId(),
|
||||||
|
rarity,
|
||||||
|
randomContext.CreateTagRandomContext(config.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
private BearingCompItemData BuildRandomEventBearingItem(
|
||||||
|
RarityType rarity,
|
||||||
|
InventoryGenerationRandomContext randomContext,
|
||||||
|
Random random)
|
||||||
|
{
|
||||||
|
DRBearingComp[] rows = _bearingCompTable.GetAllDataRows();
|
||||||
|
DRBearingComp config = rows[random.Next(0, rows.Length)];
|
||||||
|
return ComponentItemFactory.CreateBearing(
|
||||||
|
config,
|
||||||
|
randomContext.CreateStableItemInstanceId(),
|
||||||
|
rarity,
|
||||||
|
randomContext.CreateTagRandomContext(config.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
private BaseCompItemData BuildRandomEventBaseItem(
|
||||||
|
RarityType rarity,
|
||||||
|
InventoryGenerationRandomContext randomContext,
|
||||||
|
Random random)
|
||||||
|
{
|
||||||
|
DRBaseComp[] rows = _baseCompTable.GetAllDataRows();
|
||||||
|
DRBaseComp config = rows[random.Next(0, rows.Length)];
|
||||||
|
return ComponentItemFactory.CreateBase(
|
||||||
|
config,
|
||||||
|
randomContext.CreateStableItemInstanceId(),
|
||||||
|
rarity,
|
||||||
|
randomContext.CreateTagRandomContext(config.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static InventoryGenerationRandomContext CreateEventRandomContext(
|
||||||
|
int runSeed,
|
||||||
|
int sequenceIndex,
|
||||||
|
int eventId,
|
||||||
|
int optionIndex,
|
||||||
|
int effectIndex,
|
||||||
|
int itemIndex)
|
||||||
|
{
|
||||||
|
return new InventoryGenerationRandomContext(
|
||||||
|
runSeed,
|
||||||
|
sequenceIndex,
|
||||||
|
InventoryTagSourceType.Event,
|
||||||
|
BuildEventLocalOrdinal(eventId, optionIndex, effectIndex, itemIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int BuildEventLocalOrdinal(int eventId, int optionIndex, int effectIndex, int itemIndex)
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
int value = 17;
|
||||||
|
value = value * 31 + eventId;
|
||||||
|
value = value * 31 + optionIndex;
|
||||||
|
value = value * 31 + effectIndex;
|
||||||
|
value = value * 31 + itemIndex;
|
||||||
|
return value & int.MaxValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
using System;
|
||||||
|
using Random = System.Random;
|
||||||
|
|
||||||
|
namespace GeometryTD.Definition
|
||||||
|
{
|
||||||
|
public readonly struct InventoryGenerationRandomContext
|
||||||
|
{
|
||||||
|
public InventoryGenerationRandomContext(
|
||||||
|
int runSeed,
|
||||||
|
int nodeSequenceIndex,
|
||||||
|
InventoryTagSourceType sourceType,
|
||||||
|
int localOrdinal)
|
||||||
|
{
|
||||||
|
RunSeed = runSeed;
|
||||||
|
NodeSequenceIndex = nodeSequenceIndex;
|
||||||
|
SourceType = sourceType;
|
||||||
|
LocalOrdinal = localOrdinal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int RunSeed { get; }
|
||||||
|
|
||||||
|
public int NodeSequenceIndex { get; }
|
||||||
|
|
||||||
|
public InventoryTagSourceType SourceType { get; }
|
||||||
|
|
||||||
|
public int LocalOrdinal { get; }
|
||||||
|
|
||||||
|
public Random CreateRandom()
|
||||||
|
{
|
||||||
|
return new Random(BuildSeed());
|
||||||
|
}
|
||||||
|
|
||||||
|
public long CreateStableItemInstanceId()
|
||||||
|
{
|
||||||
|
long normalizedSource = ((long)Math.Max(0, (int)SourceType) + 1L) << 48;
|
||||||
|
long normalizedSequence = ((long)Math.Max(0, NodeSequenceIndex) + 1L) << 24;
|
||||||
|
long normalizedOrdinal = (uint)(Math.Max(0, LocalOrdinal) + 1);
|
||||||
|
return normalizedSource | normalizedSequence | normalizedOrdinal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InventoryTagRandomContext CreateTagRandomContext(int configId)
|
||||||
|
{
|
||||||
|
return SourceType switch
|
||||||
|
{
|
||||||
|
InventoryTagSourceType.Shop => InventoryTagRandomContext.CreateShop(RunSeed, NodeSequenceIndex, LocalOrdinal, configId),
|
||||||
|
InventoryTagSourceType.Reward => InventoryTagRandomContext.CreateReward(RunSeed, NodeSequenceIndex, LocalOrdinal, configId),
|
||||||
|
InventoryTagSourceType.Event => InventoryTagRandomContext.CreateEvent(RunSeed, NodeSequenceIndex, LocalOrdinal, configId),
|
||||||
|
_ => InventoryTagRandomContext.CreateDrop(RunSeed, NodeSequenceIndex, LocalOrdinal, configId)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private int BuildSeed()
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
int seed = 17;
|
||||||
|
seed = seed * 31 + RunSeed;
|
||||||
|
seed = seed * 31 + NodeSequenceIndex;
|
||||||
|
seed = seed * 31 + (int)SourceType;
|
||||||
|
seed = seed * 31 + LocalOrdinal;
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 95b2ab672a7049ef9356a90d87baca50
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using Random = System.Random;
|
||||||
|
|
||||||
|
namespace GeometryTD.CustomComponent
|
||||||
|
{
|
||||||
|
public static class OutGameDropRuleService
|
||||||
|
{
|
||||||
|
private const float DropChanceBase = 0.05f;
|
||||||
|
private const float DropChancePerPhase = 0.2f;
|
||||||
|
private const float DropChanceCap = 0.2f;
|
||||||
|
|
||||||
|
public static bool ShouldRollOutGameItem(int displayPhaseIndex, Random random)
|
||||||
|
{
|
||||||
|
int phaseIndex = Mathf.Max(1, displayPhaseIndex);
|
||||||
|
float dropChance = Mathf.Clamp(DropChanceBase + (phaseIndex - 1) * DropChancePerPhase, 0f, DropChanceCap);
|
||||||
|
return random.NextDouble() <= dropChance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 519549c7542f40efabf83a2b1b2dff44
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+28
-7
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using GeometryTD.DataTable;
|
using GeometryTD.DataTable;
|
||||||
using GeometryTD.Definition;
|
using GeometryTD.Definition;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using Random = System.Random;
|
||||||
|
|
||||||
namespace GeometryTD.CustomComponent
|
namespace GeometryTD.CustomComponent
|
||||||
{
|
{
|
||||||
@@ -19,7 +20,8 @@ namespace GeometryTD.CustomComponent
|
|||||||
int displayPhaseIndex,
|
int displayPhaseIndex,
|
||||||
LevelThemeType themeType,
|
LevelThemeType themeType,
|
||||||
int candidateCount,
|
int candidateCount,
|
||||||
Func<DROutGameDropPool, TowerCompItemData> buildRewardItem)
|
Func<InventoryGenerationRandomContext> createRandomContext,
|
||||||
|
Func<DROutGameDropPool, RarityType, InventoryGenerationRandomContext, TowerCompItemData> buildRewardItem)
|
||||||
{
|
{
|
||||||
int resolvedCount = Mathf.Max(0, candidateCount);
|
int resolvedCount = Mathf.Max(0, candidateCount);
|
||||||
if (resolvedCount <= 0)
|
if (resolvedCount <= 0)
|
||||||
@@ -28,7 +30,7 @@ namespace GeometryTD.CustomComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<TowerCompItemData> candidates = new List<TowerCompItemData>(resolvedCount);
|
List<TowerCompItemData> candidates = new List<TowerCompItemData>(resolvedCount);
|
||||||
HashSet<int> selectedPoolRowIds = new HashSet<int>();
|
HashSet<long> selectedPoolEntryKeys = new HashSet<long>();
|
||||||
int maxAttempts = Mathf.Max(resolvedCount * 6, resolvedCount);
|
int maxAttempts = Mathf.Max(resolvedCount * 6, resolvedCount);
|
||||||
int phaseIndex = Mathf.Max(1, displayPhaseIndex);
|
int phaseIndex = Mathf.Max(1, displayPhaseIndex);
|
||||||
|
|
||||||
@@ -36,17 +38,24 @@ namespace GeometryTD.CustomComponent
|
|||||||
while (candidates.Count < resolvedCount && attempts < maxAttempts)
|
while (candidates.Count < resolvedCount && attempts < maxAttempts)
|
||||||
{
|
{
|
||||||
attempts++;
|
attempts++;
|
||||||
if (!_dropPoolRoller.TryRollRow(phaseIndex, themeType, out DROutGameDropPool selectedRow) || selectedRow == null)
|
InventoryGenerationRandomContext randomContext = createRandomContext();
|
||||||
|
Random random = randomContext.CreateRandom();
|
||||||
|
if (!_dropPoolRoller.TryRollRow(
|
||||||
|
phaseIndex,
|
||||||
|
themeType,
|
||||||
|
random,
|
||||||
|
out DROutGameDropPool selectedRow,
|
||||||
|
out RarityType selectedRarity) || selectedRow == null)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!selectedPoolRowIds.Add(selectedRow.Id))
|
if (!selectedPoolEntryKeys.Add(BuildSelectionKey(selectedRow.Id, selectedRarity)))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
TowerCompItemData candidate = buildRewardItem(selectedRow);
|
TowerCompItemData candidate = buildRewardItem(selectedRow, selectedRarity, randomContext);
|
||||||
if (candidate == null)
|
if (candidate == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -59,12 +68,19 @@ namespace GeometryTD.CustomComponent
|
|||||||
while (candidates.Count < resolvedCount && attempts < maxAttempts)
|
while (candidates.Count < resolvedCount && attempts < maxAttempts)
|
||||||
{
|
{
|
||||||
attempts++;
|
attempts++;
|
||||||
if (!_dropPoolRoller.TryRollRow(phaseIndex, themeType, out DROutGameDropPool selectedRow) || selectedRow == null)
|
InventoryGenerationRandomContext randomContext = createRandomContext();
|
||||||
|
Random random = randomContext.CreateRandom();
|
||||||
|
if (!_dropPoolRoller.TryRollRow(
|
||||||
|
phaseIndex,
|
||||||
|
themeType,
|
||||||
|
random,
|
||||||
|
out DROutGameDropPool selectedRow,
|
||||||
|
out RarityType selectedRarity) || selectedRow == null)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
TowerCompItemData candidate = buildRewardItem(selectedRow);
|
TowerCompItemData candidate = buildRewardItem(selectedRow, selectedRarity, randomContext);
|
||||||
if (candidate == null)
|
if (candidate == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -75,5 +91,10 @@ namespace GeometryTD.CustomComponent
|
|||||||
|
|
||||||
return candidates;
|
return candidates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static long BuildSelectionKey(int rowId, RarityType rarity)
|
||||||
|
{
|
||||||
|
return ((long)rowId << 32) | (uint)rarity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using GeometryTD.Definition;
|
|||||||
using GeometryTD.Factory;
|
using GeometryTD.Factory;
|
||||||
using GeometryTD.UI;
|
using GeometryTD.UI;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using Random = System.Random;
|
||||||
|
|
||||||
namespace GeometryTD.CustomComponent
|
namespace GeometryTD.CustomComponent
|
||||||
{
|
{
|
||||||
@@ -32,8 +33,7 @@ namespace GeometryTD.CustomComponent
|
|||||||
public List<GoodsItemRawData> BuildGoods(
|
public List<GoodsItemRawData> BuildGoods(
|
||||||
int goodsCount,
|
int goodsCount,
|
||||||
int runSeed,
|
int runSeed,
|
||||||
int sequenceIndex,
|
int sequenceIndex)
|
||||||
Func<long> allocateInstanceId)
|
|
||||||
{
|
{
|
||||||
if (goodsCount <= 0)
|
if (goodsCount <= 0)
|
||||||
{
|
{
|
||||||
@@ -43,7 +43,9 @@ namespace GeometryTD.CustomComponent
|
|||||||
List<GoodsItemRawData> goodsItems = new(goodsCount);
|
List<GoodsItemRawData> goodsItems = new(goodsCount);
|
||||||
for (int i = 0; i < goodsCount; i++)
|
for (int i = 0; i < goodsCount; i++)
|
||||||
{
|
{
|
||||||
goodsItems.Add(BuildGoodsItem(i, runSeed, sequenceIndex, allocateInstanceId));
|
InventoryGenerationRandomContext randomContext =
|
||||||
|
new(runSeed, sequenceIndex, InventoryTagSourceType.Shop, i);
|
||||||
|
goodsItems.Add(BuildGoodsItem(i, randomContext));
|
||||||
}
|
}
|
||||||
|
|
||||||
return goodsItems;
|
return goodsItems;
|
||||||
@@ -51,18 +53,17 @@ namespace GeometryTD.CustomComponent
|
|||||||
|
|
||||||
private GoodsItemRawData BuildGoodsItem(
|
private GoodsItemRawData BuildGoodsItem(
|
||||||
int goodsIndex,
|
int goodsIndex,
|
||||||
int runSeed,
|
InventoryGenerationRandomContext randomContext)
|
||||||
int sequenceIndex,
|
|
||||||
Func<long> allocateInstanceId)
|
|
||||||
{
|
{
|
||||||
TowerCompItemData sourceItem = BuildRandomComponentItem(goodsIndex, runSeed, sequenceIndex, allocateInstanceId);
|
Random random = randomContext.CreateRandom();
|
||||||
|
TowerCompItemData sourceItem = BuildRandomComponentItem(randomContext, random);
|
||||||
return new GoodsItemRawData
|
return new GoodsItemRawData
|
||||||
{
|
{
|
||||||
GoodsIndex = goodsIndex,
|
GoodsIndex = goodsIndex,
|
||||||
Title = sourceItem.Name,
|
Title = sourceItem.Name,
|
||||||
TypeText = BuildTypeText(sourceItem.SlotType),
|
TypeText = BuildTypeText(sourceItem.SlotType),
|
||||||
Description = BuildDescription(sourceItem),
|
Description = BuildDescription(sourceItem),
|
||||||
Price = ResolveRandomPrice(sourceItem.Rarity),
|
Price = ResolveRandomPrice(sourceItem.Rarity, random),
|
||||||
Tags = sourceItem.Tags != null ? (TagType[])sourceItem.Tags.Clone() : Array.Empty<TagType>(),
|
Tags = sourceItem.Tags != null ? (TagType[])sourceItem.Tags.Clone() : Array.Empty<TagType>(),
|
||||||
IconAreaContext = BuildIconAreaContext(sourceItem),
|
IconAreaContext = BuildIconAreaContext(sourceItem),
|
||||||
SourceItem = sourceItem,
|
SourceItem = sourceItem,
|
||||||
@@ -71,83 +72,58 @@ namespace GeometryTD.CustomComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
private TowerCompItemData BuildRandomComponentItem(
|
private TowerCompItemData BuildRandomComponentItem(
|
||||||
int goodsIndex,
|
InventoryGenerationRandomContext randomContext,
|
||||||
int runSeed,
|
Random random)
|
||||||
int sequenceIndex,
|
|
||||||
Func<long> allocateInstanceId)
|
|
||||||
{
|
{
|
||||||
int slotRoll = UnityEngine.Random.Range(0, 3);
|
int slotRoll = random.Next(0, 3);
|
||||||
DRShopPrice priceRow = _shopPriceRows[UnityEngine.Random.Range(0, _shopPriceRows.Count)];
|
DRShopPrice priceRow = _shopPriceRows[random.Next(0, _shopPriceRows.Count)];
|
||||||
RarityType rarity = InventoryRarityRuleService.NormalizeComponentRarity(
|
RarityType rarity = InventoryRarityRuleService.NormalizeComponentRarity(
|
||||||
priceRow != null ? priceRow.Rarity : RarityType.White);
|
priceRow != null ? priceRow.Rarity : RarityType.White);
|
||||||
|
|
||||||
return slotRoll switch
|
return slotRoll switch
|
||||||
{
|
{
|
||||||
0 => BuildRandomMuzzleItem(rarity, goodsIndex, runSeed, sequenceIndex, allocateInstanceId),
|
0 => BuildRandomMuzzleItem(rarity, randomContext, random),
|
||||||
1 => BuildRandomBearingItem(rarity, goodsIndex, runSeed, sequenceIndex, allocateInstanceId),
|
1 => BuildRandomBearingItem(rarity, randomContext, random),
|
||||||
_ => BuildRandomBaseItem(rarity, goodsIndex, runSeed, sequenceIndex, allocateInstanceId)
|
_ => BuildRandomBaseItem(rarity, randomContext, random)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private MuzzleCompItemData BuildRandomMuzzleItem(
|
private MuzzleCompItemData BuildRandomMuzzleItem(
|
||||||
RarityType rarity,
|
RarityType rarity,
|
||||||
int goodsIndex,
|
InventoryGenerationRandomContext randomContext,
|
||||||
int runSeed,
|
Random random)
|
||||||
int sequenceIndex,
|
|
||||||
Func<long> allocateInstanceId)
|
|
||||||
{
|
{
|
||||||
DRMuzzleComp[] rows = _muzzleCompTable.GetAllDataRows();
|
DRMuzzleComp[] rows = _muzzleCompTable.GetAllDataRows();
|
||||||
DRMuzzleComp config = rows[UnityEngine.Random.Range(0, rows.Length)];
|
DRMuzzleComp config = rows[random.Next(0, rows.Length)];
|
||||||
long instanceId = allocateInstanceId();
|
long instanceId = randomContext.CreateStableItemInstanceId();
|
||||||
InventoryTagRandomContext randomContext =
|
return ComponentItemFactory.CreateMuzzle(config, instanceId, rarity, randomContext.CreateTagRandomContext(config.Id));
|
||||||
InventoryTagRandomContext.CreateShop(runSeed, sequenceIndex, goodsIndex, config.Id);
|
|
||||||
return ComponentItemFactory.CreateMuzzle(config, instanceId, rarity, randomContext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private BearingCompItemData BuildRandomBearingItem(
|
private BearingCompItemData BuildRandomBearingItem(
|
||||||
RarityType rarity,
|
RarityType rarity,
|
||||||
int goodsIndex,
|
InventoryGenerationRandomContext randomContext,
|
||||||
int runSeed,
|
Random random)
|
||||||
int sequenceIndex,
|
|
||||||
Func<long> allocateInstanceId)
|
|
||||||
{
|
{
|
||||||
DRBearingComp[] rows = _bearingCompTable.GetAllDataRows();
|
DRBearingComp[] rows = _bearingCompTable.GetAllDataRows();
|
||||||
DRBearingComp config = rows[UnityEngine.Random.Range(0, rows.Length)];
|
DRBearingComp config = rows[random.Next(0, rows.Length)];
|
||||||
long instanceId = allocateInstanceId();
|
long instanceId = randomContext.CreateStableItemInstanceId();
|
||||||
InventoryTagRandomContext randomContext =
|
return ComponentItemFactory.CreateBearing(config, instanceId, rarity, randomContext.CreateTagRandomContext(config.Id));
|
||||||
InventoryTagRandomContext.CreateShop(runSeed, sequenceIndex, goodsIndex, config.Id);
|
|
||||||
return ComponentItemFactory.CreateBearing(config, instanceId, rarity, randomContext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private BaseCompItemData BuildRandomBaseItem(
|
private BaseCompItemData BuildRandomBaseItem(
|
||||||
RarityType rarity,
|
RarityType rarity,
|
||||||
int goodsIndex,
|
InventoryGenerationRandomContext randomContext,
|
||||||
int runSeed,
|
Random random)
|
||||||
int sequenceIndex,
|
|
||||||
Func<long> allocateInstanceId)
|
|
||||||
{
|
{
|
||||||
DRBaseComp[] rows = _baseCompTable.GetAllDataRows();
|
DRBaseComp[] rows = _baseCompTable.GetAllDataRows();
|
||||||
DRBaseComp config = rows[UnityEngine.Random.Range(0, rows.Length)];
|
DRBaseComp config = rows[random.Next(0, rows.Length)];
|
||||||
long instanceId = allocateInstanceId();
|
long instanceId = randomContext.CreateStableItemInstanceId();
|
||||||
InventoryTagRandomContext randomContext =
|
return ComponentItemFactory.CreateBase(config, instanceId, rarity, randomContext.CreateTagRandomContext(config.Id));
|
||||||
InventoryTagRandomContext.CreateShop(runSeed, sequenceIndex, goodsIndex, config.Id);
|
|
||||||
return ComponentItemFactory.CreateBase(config, instanceId, rarity, randomContext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int ResolveRandomPrice(RarityType rarity)
|
private int ResolveRandomPrice(RarityType rarity, Random random)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _shopPriceRows.Count; i++)
|
return ShopPriceRuleService.ResolveRandomBuyPrice(_shopPriceRows, rarity, random);
|
||||||
{
|
|
||||||
DRShopPrice row = _shopPriceRows[i];
|
|
||||||
if (row != null && row.Rarity == rarity)
|
|
||||||
{
|
|
||||||
int min = Mathf.Max(0, row.MinPrice);
|
|
||||||
int max = Mathf.Max(min, row.MaxPrice);
|
|
||||||
return UnityEngine.Random.Range(min, max + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IconAreaContext BuildIconAreaContext(TowerCompItemData item)
|
private static IconAreaContext BuildIconAreaContext(TowerCompItemData item)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace GeometryTD.CustomComponent
|
|||||||
private PlayerInventoryCommandModel _commandModel;
|
private PlayerInventoryCommandModel _commandModel;
|
||||||
private PlayerInventoryTowerRosterService _towerRosterService;
|
private PlayerInventoryTowerRosterService _towerRosterService;
|
||||||
private PlayerInventoryTowerAssemblyService _towerAssemblyService;
|
private PlayerInventoryTowerAssemblyService _towerAssemblyService;
|
||||||
|
private PlayerInventoryTradeService _tradeService;
|
||||||
|
|
||||||
public int Gold
|
public int Gold
|
||||||
{
|
{
|
||||||
@@ -100,6 +101,12 @@ namespace GeometryTD.CustomComponent
|
|||||||
return _commandModel.TryConsumeGold(costGold);
|
return _commandModel.TryConsumeGold(costGold);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool TryPurchaseComponent(TowerCompItemData item, int price)
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
return _tradeService.TryPurchaseComponent(item, price);
|
||||||
|
}
|
||||||
|
|
||||||
public void AddGold(int gainGold)
|
public void AddGold(int gainGold)
|
||||||
{
|
{
|
||||||
EnsureInitialized();
|
EnsureInitialized();
|
||||||
@@ -138,6 +145,24 @@ namespace GeometryTD.CustomComponent
|
|||||||
return _towerRosterService.ReduceTowerEndurance(towerInstanceIds, enduranceLoss);
|
return _towerRosterService.ReduceTowerEndurance(towerInstanceIds, enduranceLoss);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool TryDisassembleTower(long towerInstanceId)
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
return _towerAssemblyService.TryDisassembleTower(towerInstanceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetSaleCandidate(long itemId, out PlayerInventorySaleCandidate candidate)
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
return _tradeService.TryGetSaleCandidate(itemId, out candidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TrySellItems(IReadOnlyCollection<long> itemIds, out PlayerInventorySaleResult result)
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
return _tradeService.TrySellItems(itemIds, out result);
|
||||||
|
}
|
||||||
|
|
||||||
private void EnsureInitialized()
|
private void EnsureInitialized()
|
||||||
{
|
{
|
||||||
if (_queryModel.IsInitialized)
|
if (_queryModel.IsInitialized)
|
||||||
@@ -155,6 +180,7 @@ namespace GeometryTD.CustomComponent
|
|||||||
_commandModel ??= new PlayerInventoryCommandModel(_state);
|
_commandModel ??= new PlayerInventoryCommandModel(_state);
|
||||||
_towerRosterService ??= new PlayerInventoryTowerRosterService(_queryModel, MaxParticipantTowerCount);
|
_towerRosterService ??= new PlayerInventoryTowerRosterService(_queryModel, MaxParticipantTowerCount);
|
||||||
_towerAssemblyService ??= new PlayerInventoryTowerAssemblyService(_queryModel, _commandModel);
|
_towerAssemblyService ??= new PlayerInventoryTowerAssemblyService(_queryModel, _commandModel);
|
||||||
|
_tradeService ??= new PlayerInventoryTradeService(_queryModel, _commandModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-2
@@ -123,7 +123,7 @@ namespace GeometryTD.CustomComponent
|
|||||||
int[] values = new int[TowerLevelCount];
|
int[] values = new int[TowerLevelCount];
|
||||||
for (int i = 0; i < values.Length; i++)
|
for (int i = 0; i < values.Length; i++)
|
||||||
{
|
{
|
||||||
values[i] = baseValue + perLevel * i;
|
values[i] = Mathf.Max(0, baseValue + perLevel * i);
|
||||||
}
|
}
|
||||||
|
|
||||||
return values;
|
return values;
|
||||||
@@ -135,7 +135,7 @@ namespace GeometryTD.CustomComponent
|
|||||||
float[] values = new float[TowerLevelCount];
|
float[] values = new float[TowerLevelCount];
|
||||||
for (int i = 0; i < values.Length; i++)
|
for (int i = 0; i < values.Length; i++)
|
||||||
{
|
{
|
||||||
values[i] = baseValue + perLevel * i;
|
values[i] = Mathf.Max(0f, baseValue + perLevel * i);
|
||||||
}
|
}
|
||||||
|
|
||||||
return values;
|
return values;
|
||||||
@@ -180,5 +180,37 @@ namespace GeometryTD.CustomComponent
|
|||||||
_drBaseComp ??= GameEntry.DataTable.GetDataTable<DRBaseComp>();
|
_drBaseComp ??= GameEntry.DataTable.GetDataTable<DRBaseComp>();
|
||||||
return _drBaseComp;
|
return _drBaseComp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool TryDisassembleTower(long towerInstanceId)
|
||||||
|
{
|
||||||
|
BackpackInventoryData inventory = _queryModel.Inventory;
|
||||||
|
if (towerInstanceId <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_queryModel.TryGetTowerById(towerInstanceId, out TowerItemData tower) || tower == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_queryModel.TryGetComponentById(inventory.MuzzleComponents, tower.MuzzleComponentInstanceId, out MuzzleCompItemData muzzleComp) ||
|
||||||
|
!_queryModel.TryGetComponentById(inventory.BearingComponents, tower.BearingComponentInstanceId, out BearingCompItemData bearingComp) ||
|
||||||
|
!_queryModel.TryGetComponentById(inventory.BaseComponents, tower.BaseComponentInstanceId, out BaseCompItemData baseComp))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
InventoryParticipantUtility.TryRemoveParticipantTower(inventory, towerInstanceId, MaxParticipantTowerCount);
|
||||||
|
|
||||||
|
muzzleComp.IsAssembledIntoTower = false;
|
||||||
|
bearingComp.IsAssembledIntoTower = false;
|
||||||
|
baseComp.IsAssembledIntoTower = false;
|
||||||
|
|
||||||
|
inventory.Towers.Remove(tower);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private const int MaxParticipantTowerCount = 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-5
@@ -35,14 +35,27 @@ namespace GeometryTD.CustomComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int ReduceTowerEndurance(IReadOnlyList<long> towerInstanceIds, float enduranceLoss)
|
public int ReduceTowerEndurance(IReadOnlyList<long> towerInstanceIds, float enduranceLoss)
|
||||||
|
{
|
||||||
|
return InventoryTowerEnduranceUtility.ReduceTowerEndurance(
|
||||||
|
_queryModel.Inventory,
|
||||||
|
towerInstanceIds,
|
||||||
|
enduranceLoss);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class InventoryTowerEnduranceUtility
|
||||||
|
{
|
||||||
|
public static int ReduceTowerEndurance(
|
||||||
|
BackpackInventoryData inventory,
|
||||||
|
IReadOnlyList<long> towerInstanceIds,
|
||||||
|
float enduranceLoss)
|
||||||
{
|
{
|
||||||
float resolvedLoss = Mathf.Max(0f, enduranceLoss);
|
float resolvedLoss = Mathf.Max(0f, enduranceLoss);
|
||||||
BackpackInventoryData inventory = _queryModel.Inventory;
|
if (inventory?.Towers == null ||
|
||||||
if (resolvedLoss <= 0f ||
|
inventory.Towers.Count <= 0 ||
|
||||||
|
resolvedLoss <= 0f ||
|
||||||
towerInstanceIds == null ||
|
towerInstanceIds == null ||
|
||||||
towerInstanceIds.Count <= 0 ||
|
towerInstanceIds.Count <= 0)
|
||||||
inventory.Towers == null ||
|
|
||||||
inventory.Towers.Count <= 0)
|
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+400
@@ -0,0 +1,400 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using GameFramework.DataTable;
|
||||||
|
using GeometryTD.CustomUtility;
|
||||||
|
using GeometryTD.DataTable;
|
||||||
|
using GeometryTD.Definition;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace GeometryTD.CustomComponent
|
||||||
|
{
|
||||||
|
public enum PlayerInventorySaleFailureReason : byte
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
InvalidSelection = 1,
|
||||||
|
ItemNotFound = 2,
|
||||||
|
AssembledComponent = 3,
|
||||||
|
ParticipantTower = 4,
|
||||||
|
MissingTowerComponent = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PlayerInventorySaleCandidate
|
||||||
|
{
|
||||||
|
public long ItemId;
|
||||||
|
public bool IsSellable;
|
||||||
|
public bool IsTower;
|
||||||
|
public int Price;
|
||||||
|
public PlayerInventorySaleFailureReason FailureReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PlayerInventorySaleResult
|
||||||
|
{
|
||||||
|
public int GainedGold;
|
||||||
|
public int SoldComponentCount;
|
||||||
|
public int SoldTowerCount;
|
||||||
|
public PlayerInventorySaleFailureReason FailureReason;
|
||||||
|
|
||||||
|
public bool IsSuccess => FailureReason == PlayerInventorySaleFailureReason.None;
|
||||||
|
public int SoldItemCount => SoldComponentCount + SoldTowerCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PlayerInventoryTradeService
|
||||||
|
{
|
||||||
|
private readonly PlayerInventoryQueryModel _queryModel;
|
||||||
|
private readonly PlayerInventoryCommandModel _commandModel;
|
||||||
|
private IDataTable<DRShopPrice> _shopPriceTable;
|
||||||
|
|
||||||
|
public PlayerInventoryTradeService(
|
||||||
|
PlayerInventoryQueryModel queryModel,
|
||||||
|
PlayerInventoryCommandModel commandModel,
|
||||||
|
IDataTable<DRShopPrice> shopPriceTable = null)
|
||||||
|
{
|
||||||
|
_queryModel = queryModel;
|
||||||
|
_commandModel = commandModel;
|
||||||
|
_shopPriceTable = shopPriceTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryPurchaseComponent(TowerCompItemData item, int price)
|
||||||
|
{
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_commandModel.TryConsumeGold(price))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BackpackInventoryData inventoryDelta = WrapSingleItem(item);
|
||||||
|
PlayerInventoryMergeSummary summary = _commandModel.MergeInventory(inventoryDelta);
|
||||||
|
return summary.HasAnyGain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetSaleCandidate(long itemId, out PlayerInventorySaleCandidate candidate)
|
||||||
|
{
|
||||||
|
candidate = BuildSaleCandidate(itemId);
|
||||||
|
return candidate != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TrySellItems(IReadOnlyCollection<long> itemIds, out PlayerInventorySaleResult result)
|
||||||
|
{
|
||||||
|
result = new PlayerInventorySaleResult();
|
||||||
|
if (itemIds == null || itemIds.Count <= 0)
|
||||||
|
{
|
||||||
|
result.FailureReason = PlayerInventorySaleFailureReason.InvalidSelection;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
HashSet<long> uniqueIds = new HashSet<long>();
|
||||||
|
List<PlayerInventorySaleCandidate> candidates = new List<PlayerInventorySaleCandidate>(itemIds.Count);
|
||||||
|
foreach (long itemId in itemIds)
|
||||||
|
{
|
||||||
|
if (itemId <= 0 || !uniqueIds.Add(itemId))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerInventorySaleCandidate candidate = BuildSaleCandidate(itemId);
|
||||||
|
if (candidate == null || !candidate.IsSellable)
|
||||||
|
{
|
||||||
|
result.FailureReason = candidate?.FailureReason ?? PlayerInventorySaleFailureReason.ItemNotFound;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates.Add(candidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidates.Count <= 0)
|
||||||
|
{
|
||||||
|
result.FailureReason = PlayerInventorySaleFailureReason.InvalidSelection;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BackpackInventoryData inventory = _queryModel.Inventory;
|
||||||
|
for (int i = 0; i < candidates.Count; i++)
|
||||||
|
{
|
||||||
|
PlayerInventorySaleCandidate candidate = candidates[i];
|
||||||
|
if (candidate.IsTower)
|
||||||
|
{
|
||||||
|
if (!TryRemoveTower(inventory, candidate.ItemId))
|
||||||
|
{
|
||||||
|
result.FailureReason = PlayerInventorySaleFailureReason.MissingTowerComponent;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.SoldTowerCount++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!TryRemoveComponent(inventory, candidate.ItemId))
|
||||||
|
{
|
||||||
|
result.FailureReason = PlayerInventorySaleFailureReason.ItemNotFound;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.SoldComponentCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.GainedGold += Mathf.Max(0, candidate.Price);
|
||||||
|
}
|
||||||
|
|
||||||
|
_commandModel.AddGold(result.GainedGold);
|
||||||
|
result.FailureReason = PlayerInventorySaleFailureReason.None;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayerInventorySaleCandidate BuildSaleCandidate(long itemId)
|
||||||
|
{
|
||||||
|
if (itemId <= 0)
|
||||||
|
{
|
||||||
|
return new PlayerInventorySaleCandidate
|
||||||
|
{
|
||||||
|
ItemId = itemId,
|
||||||
|
IsSellable = false,
|
||||||
|
FailureReason = PlayerInventorySaleFailureReason.InvalidSelection
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
BackpackInventoryData inventory = _queryModel.Inventory;
|
||||||
|
if (inventory == null)
|
||||||
|
{
|
||||||
|
return new PlayerInventorySaleCandidate
|
||||||
|
{
|
||||||
|
ItemId = itemId,
|
||||||
|
IsSellable = false,
|
||||||
|
FailureReason = PlayerInventorySaleFailureReason.ItemNotFound
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_queryModel.TryGetTowerById(itemId, out TowerItemData tower) && tower != null)
|
||||||
|
{
|
||||||
|
if (inventory.ParticipantTowerInstanceIds != null && inventory.ParticipantTowerInstanceIds.Contains(itemId))
|
||||||
|
{
|
||||||
|
return new PlayerInventorySaleCandidate
|
||||||
|
{
|
||||||
|
ItemId = itemId,
|
||||||
|
IsSellable = false,
|
||||||
|
IsTower = true,
|
||||||
|
FailureReason = PlayerInventorySaleFailureReason.ParticipantTower
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ShopPriceRuleService.TryResolveTowerSalePrice(tower, inventory, out int towerPrice, EnsureShopPriceTable()))
|
||||||
|
{
|
||||||
|
return new PlayerInventorySaleCandidate
|
||||||
|
{
|
||||||
|
ItemId = itemId,
|
||||||
|
IsSellable = false,
|
||||||
|
IsTower = true,
|
||||||
|
FailureReason = PlayerInventorySaleFailureReason.MissingTowerComponent
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PlayerInventorySaleCandidate
|
||||||
|
{
|
||||||
|
ItemId = itemId,
|
||||||
|
IsSellable = true,
|
||||||
|
IsTower = true,
|
||||||
|
Price = towerPrice,
|
||||||
|
FailureReason = PlayerInventorySaleFailureReason.None
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryGetComponentById(inventory.MuzzleComponents, itemId, out MuzzleCompItemData muzzleComp))
|
||||||
|
{
|
||||||
|
return BuildComponentCandidate(muzzleComp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryGetComponentById(inventory.BearingComponents, itemId, out BearingCompItemData bearingComp))
|
||||||
|
{
|
||||||
|
return BuildComponentCandidate(bearingComp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryGetComponentById(inventory.BaseComponents, itemId, out BaseCompItemData baseComp))
|
||||||
|
{
|
||||||
|
return BuildComponentCandidate(baseComp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PlayerInventorySaleCandidate
|
||||||
|
{
|
||||||
|
ItemId = itemId,
|
||||||
|
IsSellable = false,
|
||||||
|
FailureReason = PlayerInventorySaleFailureReason.ItemNotFound
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayerInventorySaleCandidate BuildComponentCandidate(TowerCompItemData component)
|
||||||
|
{
|
||||||
|
if (component == null)
|
||||||
|
{
|
||||||
|
return new PlayerInventorySaleCandidate
|
||||||
|
{
|
||||||
|
IsSellable = false,
|
||||||
|
FailureReason = PlayerInventorySaleFailureReason.ItemNotFound
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.IsAssembledIntoTower)
|
||||||
|
{
|
||||||
|
return new PlayerInventorySaleCandidate
|
||||||
|
{
|
||||||
|
ItemId = component.InstanceId,
|
||||||
|
IsSellable = false,
|
||||||
|
FailureReason = PlayerInventorySaleFailureReason.AssembledComponent
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PlayerInventorySaleCandidate
|
||||||
|
{
|
||||||
|
ItemId = component.InstanceId,
|
||||||
|
IsSellable = true,
|
||||||
|
IsTower = false,
|
||||||
|
Price = ShopPriceRuleService.ResolveComponentSalePrice(component, EnsureShopPriceTable()),
|
||||||
|
FailureReason = PlayerInventorySaleFailureReason.None
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryRemoveTower(BackpackInventoryData inventory, long towerId)
|
||||||
|
{
|
||||||
|
if (inventory?.Towers == null || towerId <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TowerItemData targetTower = null;
|
||||||
|
for (int i = 0; i < inventory.Towers.Count; i++)
|
||||||
|
{
|
||||||
|
TowerItemData tower = inventory.Towers[i];
|
||||||
|
if (tower != null && tower.InstanceId == towerId)
|
||||||
|
{
|
||||||
|
targetTower = tower;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetTower == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ContainsInstanceId(inventory.MuzzleComponents, targetTower.MuzzleComponentInstanceId) ||
|
||||||
|
!ContainsInstanceId(inventory.BearingComponents, targetTower.BearingComponentInstanceId) ||
|
||||||
|
!ContainsInstanceId(inventory.BaseComponents, targetTower.BaseComponentInstanceId))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool removedMuzzle = RemoveByInstanceId(inventory.MuzzleComponents, targetTower.MuzzleComponentInstanceId);
|
||||||
|
bool removedBearing = RemoveByInstanceId(inventory.BearingComponents, targetTower.BearingComponentInstanceId);
|
||||||
|
bool removedBase = RemoveByInstanceId(inventory.BaseComponents, targetTower.BaseComponentInstanceId);
|
||||||
|
if (!removedMuzzle || !removedBearing || !removedBase)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inventory.Towers.Remove(targetTower);
|
||||||
|
inventory.ParticipantTowerInstanceIds?.Remove(towerId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryRemoveComponent(BackpackInventoryData inventory, long itemId)
|
||||||
|
{
|
||||||
|
return RemoveByInstanceId(inventory?.MuzzleComponents, itemId) ||
|
||||||
|
RemoveByInstanceId(inventory?.BearingComponents, itemId) ||
|
||||||
|
RemoveByInstanceId(inventory?.BaseComponents, itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool RemoveByInstanceId<TItem>(List<TItem> items, long instanceId)
|
||||||
|
where TItem : class
|
||||||
|
{
|
||||||
|
if (items == null || instanceId <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < items.Count; i++)
|
||||||
|
{
|
||||||
|
switch (items[i])
|
||||||
|
{
|
||||||
|
case TowerCompItemData component when component.InstanceId == instanceId:
|
||||||
|
items.RemoveAt(i);
|
||||||
|
return true;
|
||||||
|
case TowerItemData tower when tower.InstanceId == instanceId:
|
||||||
|
items.RemoveAt(i);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ContainsInstanceId<TItem>(IReadOnlyList<TItem> items, long instanceId)
|
||||||
|
where TItem : class
|
||||||
|
{
|
||||||
|
if (items == null || instanceId <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < items.Count; i++)
|
||||||
|
{
|
||||||
|
switch (items[i])
|
||||||
|
{
|
||||||
|
case TowerCompItemData component when component.InstanceId == instanceId:
|
||||||
|
return true;
|
||||||
|
case TowerItemData tower when tower.InstanceId == instanceId:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetComponentById<TComp>(IReadOnlyList<TComp> items, long instanceId, out TComp result)
|
||||||
|
where TComp : TowerCompItemData
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
if (items == null || instanceId <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < items.Count; i++)
|
||||||
|
{
|
||||||
|
TComp item = items[i];
|
||||||
|
if (item != null && item.InstanceId == instanceId)
|
||||||
|
{
|
||||||
|
result = item;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BackpackInventoryData WrapSingleItem(TowerCompItemData item)
|
||||||
|
{
|
||||||
|
BackpackInventoryData inventory = new BackpackInventoryData();
|
||||||
|
switch (item)
|
||||||
|
{
|
||||||
|
case MuzzleCompItemData muzzleComp:
|
||||||
|
inventory.MuzzleComponents.Add(InventoryCloneUtility.CloneMuzzleComp(muzzleComp));
|
||||||
|
break;
|
||||||
|
case BearingCompItemData bearingComp:
|
||||||
|
inventory.BearingComponents.Add(InventoryCloneUtility.CloneBearingComp(bearingComp));
|
||||||
|
break;
|
||||||
|
case BaseCompItemData baseComp:
|
||||||
|
inventory.BaseComponents.Add(InventoryCloneUtility.CloneBaseComp(baseComp));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return inventory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IDataTable<DRShopPrice> EnsureShopPriceTable()
|
||||||
|
{
|
||||||
|
_shopPriceTable ??= GameEntry.DataTable.GetDataTable<DRShopPrice>();
|
||||||
|
return _shopPriceTable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 298cf9214556453cb5e7ec7c0605c350
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -8,11 +8,7 @@ namespace GeometryTD.CustomComponent
|
|||||||
{
|
{
|
||||||
public class ShopNodeComponent : GameFrameworkComponent
|
public class ShopNodeComponent : GameFrameworkComponent
|
||||||
{
|
{
|
||||||
private string _activeRunId;
|
private RunNodeExecutionContext _activeContext;
|
||||||
private int _activeRunSeed;
|
|
||||||
private int _activeNodeId;
|
|
||||||
private RunNodeType _activeNodeType = RunNodeType.None;
|
|
||||||
private int _activeSequenceIndex = -1;
|
|
||||||
|
|
||||||
private ShopFormUseCase _shopFormUseCase;
|
private ShopFormUseCase _shopFormUseCase;
|
||||||
private bool _initialized;
|
private bool _initialized;
|
||||||
@@ -29,26 +25,30 @@ namespace GeometryTD.CustomComponent
|
|||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StartShop(string runId = null, int runSeed = 0, int nodeId = 0, RunNodeType nodeType = RunNodeType.None, int sequenceIndex = -1)
|
public void StartShop(RunNodeExecutionContext context = null)
|
||||||
{
|
{
|
||||||
if (!_initialized)
|
if (!_initialized)
|
||||||
{
|
{
|
||||||
OnInit();
|
OnInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int runSeed = context?.RunSeed ?? 0;
|
||||||
|
int sequenceIndex = context?.SequenceIndex ?? -1;
|
||||||
if (_shopFormUseCase == null || !_shopFormUseCase.PrepareForOpen(runSeed, sequenceIndex))
|
if (_shopFormUseCase == null || !_shopFormUseCase.PrepareForOpen(runSeed, sequenceIndex))
|
||||||
{
|
{
|
||||||
Log.Warning("ShopNodeComponent.StartShop() failed. Shop use case is unavailable or goods generation failed.");
|
Log.Warning("ShopNodeComponent.StartShop() failed. Shop use case is unavailable or goods generation failed.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_activeRunId = runId;
|
_activeContext = context != null ? context.Clone() : null;
|
||||||
_activeRunSeed = runSeed;
|
|
||||||
_activeNodeId = nodeId;
|
|
||||||
_activeNodeType = nodeType;
|
|
||||||
_activeSequenceIndex = sequenceIndex;
|
|
||||||
GameEntry.UIRouter.OpenUI(UIFormType.ShopForm);
|
GameEntry.UIRouter.OpenUI(UIFormType.ShopForm);
|
||||||
GameEntry.Event.Fire(this, NodeEnterEventArgs.Create(runId, nodeId, nodeType, sequenceIndex));
|
GameEntry.Event.Fire(
|
||||||
|
this,
|
||||||
|
NodeEnterEventArgs.Create(
|
||||||
|
_activeContext?.RunId,
|
||||||
|
_activeContext?.NodeId ?? 0,
|
||||||
|
_activeContext?.NodeType ?? RunNodeType.None,
|
||||||
|
_activeContext?.SequenceIndex ?? -1));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EndShop()
|
public void EndShop()
|
||||||
@@ -57,23 +57,22 @@ namespace GeometryTD.CustomComponent
|
|||||||
GameEntry.Event.Fire(
|
GameEntry.Event.Fire(
|
||||||
this,
|
this,
|
||||||
NodeCompleteEventArgs.Create(
|
NodeCompleteEventArgs.Create(
|
||||||
_activeRunId,
|
_activeContext?.RunId,
|
||||||
_activeNodeId,
|
_activeContext?.NodeId ?? 0,
|
||||||
_activeNodeType,
|
_activeContext?.NodeType ?? RunNodeType.None,
|
||||||
_activeSequenceIndex,
|
_activeContext?.SequenceIndex ?? -1,
|
||||||
RunNodeCompletionStatus.Completed,
|
RunNodeCompletionStatus.Completed,
|
||||||
true,
|
true,
|
||||||
GameEntry.PlayerInventory != null ? GameEntry.PlayerInventory.GetInventorySnapshot() : null));
|
_activeContext != null
|
||||||
|
? _activeContext.CreateCompletionSnapshot(
|
||||||
|
GameEntry.PlayerInventory != null ? GameEntry.PlayerInventory.GetInventorySnapshot() : null)
|
||||||
|
: null));
|
||||||
ClearActiveNodeContext();
|
ClearActiveNodeContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClearActiveNodeContext()
|
private void ClearActiveNodeContext()
|
||||||
{
|
{
|
||||||
_activeRunId = null;
|
_activeContext = null;
|
||||||
_activeRunSeed = 0;
|
|
||||||
_activeNodeId = 0;
|
|
||||||
_activeNodeType = RunNodeType.None;
|
|
||||||
_activeSequenceIndex = -1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 523d63397a9c41fdaaf238bfb0e95357
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
using System;
|
||||||
|
using GameFramework.DataTable;
|
||||||
|
using GeometryTD.DataTable;
|
||||||
|
using GeometryTD.Definition;
|
||||||
|
using UnityGameFramework.Runtime;
|
||||||
|
|
||||||
|
namespace GeometryTD.CustomComponent
|
||||||
|
{
|
||||||
|
public sealed class TagRegistryComponent : GameFrameworkComponent
|
||||||
|
{
|
||||||
|
private IDataTable<DRTagConfig> _tagConfigTable;
|
||||||
|
private IDataTable<DRTag> _tagTable;
|
||||||
|
private IDataTable<DRRarityTagBudget> _rarityTagBudgetTable;
|
||||||
|
|
||||||
|
public void OnInit()
|
||||||
|
{
|
||||||
|
ReloadAllFromLoadedTables();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReloadAllFromLoadedTables()
|
||||||
|
{
|
||||||
|
ReloadTagDefinitionsAndGenerationFromLoadedTables();
|
||||||
|
ReloadRarityTagBudgetFromLoadedTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReloadTagDefinitionsAndGenerationFromLoadedTables()
|
||||||
|
{
|
||||||
|
EnsureTagDefinitionTables();
|
||||||
|
DRTagConfig[] tagConfigRows = _tagConfigTable.GetAllDataRows();
|
||||||
|
DRTag[] tagRows = _tagTable.GetAllDataRows();
|
||||||
|
|
||||||
|
TagGenerationRuleRegistry.LoadFromRows(tagRows);
|
||||||
|
TagDefinitionRegistry.ReloadFromRows(tagConfigRows, tagRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReloadRarityTagBudgetFromLoadedTable()
|
||||||
|
{
|
||||||
|
EnsureRarityTagBudgetTable();
|
||||||
|
RarityTagBudgetRuleRegistry.LoadFromRows(_rarityTagBudgetTable.GetAllDataRows());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureTagDefinitionTables()
|
||||||
|
{
|
||||||
|
_tagConfigTable ??= GameEntry.DataTable.GetDataTable<DRTagConfig>();
|
||||||
|
_tagTable ??= GameEntry.DataTable.GetDataTable<DRTag>();
|
||||||
|
|
||||||
|
if (_tagConfigTable == null || _tagTable == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"TagRegistryComponent requires TagConfig and Tag data tables to be loaded before initialization.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureRarityTagBudgetTable()
|
||||||
|
{
|
||||||
|
_rarityTagBudgetTable ??= GameEntry.DataTable.GetDataTable<DRRarityTagBudget>();
|
||||||
|
if (_rarityTagBudgetTable == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"TagRegistryComponent requires RarityTagBudget data table to be loaded before initialization.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a268c57d4373494ab9b4d7167099f170
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using UnityGameFramework.Runtime;
|
using UnityGameFramework.Runtime;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace GeometryTD.DataTable
|
namespace GeometryTD.DataTable
|
||||||
{
|
{
|
||||||
@@ -30,6 +31,14 @@ namespace GeometryTD.DataTable
|
|||||||
/// <remarks>原始字符串(如 JSON 文本),不在此处做解析。</remarks>
|
/// <remarks>原始字符串(如 JSON 文本),不在此处做解析。</remarks>
|
||||||
public string OptionsRaw { get; private set; }
|
public string OptionsRaw { get; private set; }
|
||||||
|
|
||||||
|
public string Option1Raw { get; private set; }
|
||||||
|
|
||||||
|
public string Option2Raw { get; private set; }
|
||||||
|
|
||||||
|
public string Option3Raw { get; private set; }
|
||||||
|
|
||||||
|
public string Option4Raw { get; private set; }
|
||||||
|
|
||||||
public override bool ParseDataRow(string dataRowString, object userData)
|
public override bool ParseDataRow(string dataRowString, object userData)
|
||||||
{
|
{
|
||||||
string[] columnStrings = dataRowString.Split(DataTableExtension.DataSplitSeparators);
|
string[] columnStrings = dataRowString.Split(DataTableExtension.DataSplitSeparators);
|
||||||
@@ -44,9 +53,30 @@ namespace GeometryTD.DataTable
|
|||||||
index++;
|
index++;
|
||||||
Title = columnStrings[index++];
|
Title = columnStrings[index++];
|
||||||
Description = columnStrings[index++];
|
Description = columnStrings[index++];
|
||||||
OptionsRaw = columnStrings[index++];
|
Option1Raw = columnStrings[index++];
|
||||||
|
Option2Raw = columnStrings[index++];
|
||||||
|
Option3Raw = columnStrings[index++];
|
||||||
|
Option4Raw = columnStrings[index++];
|
||||||
|
OptionsRaw = BuildOptionsRaw(Option1Raw, Option2Raw, Option3Raw, Option4Raw);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string BuildOptionsRaw(params string[] optionColumns)
|
||||||
|
{
|
||||||
|
JArray array = new JArray();
|
||||||
|
for (int i = 0; i < optionColumns.Length; i++)
|
||||||
|
{
|
||||||
|
string optionRaw = optionColumns[i];
|
||||||
|
if (string.IsNullOrWhiteSpace(optionRaw))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
array.Add(JObject.Parse(optionRaw));
|
||||||
|
}
|
||||||
|
|
||||||
|
return array.ToString(Newtonsoft.Json.Formatting.None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
|
using System;
|
||||||
using GeometryTD.CustomUtility;
|
using GeometryTD.CustomUtility;
|
||||||
using GeometryTD.Definition;
|
using GeometryTD.Definition;
|
||||||
|
using UnityEngine;
|
||||||
using UnityGameFramework.Runtime;
|
using UnityGameFramework.Runtime;
|
||||||
|
|
||||||
namespace GeometryTD.DataTable
|
namespace GeometryTD.DataTable
|
||||||
@@ -14,12 +16,11 @@ namespace GeometryTD.DataTable
|
|||||||
public override int Id => m_Id;
|
public override int Id => m_Id;
|
||||||
|
|
||||||
public LevelThemeType LevelThemeType { get; private set; }
|
public LevelThemeType LevelThemeType { get; private set; }
|
||||||
public RarityType Rarity { get; private set; }
|
|
||||||
public string ItemType { get; private set; }
|
public string ItemType { get; private set; }
|
||||||
public int ItemId { get; private set; }
|
public int ItemId { get; private set; }
|
||||||
public int Weight { get; private set; }
|
public int[] Weights { get; private set; }
|
||||||
public int MinPhase { get; private set; }
|
public int[] MinPhase { get; private set; }
|
||||||
public int MaxPhase { get; private set; }
|
public int[] MaxPhase { get; private set; }
|
||||||
|
|
||||||
public override bool ParseDataRow(string dataRowString, object userData)
|
public override bool ParseDataRow(string dataRowString, object userData)
|
||||||
{
|
{
|
||||||
@@ -34,31 +35,110 @@ namespace GeometryTD.DataTable
|
|||||||
m_Id = int.Parse(columnStrings[index++]);
|
m_Id = int.Parse(columnStrings[index++]);
|
||||||
index++;
|
index++;
|
||||||
LevelThemeType = EnumUtility<LevelThemeType>.Get(columnStrings[index++]);
|
LevelThemeType = EnumUtility<LevelThemeType>.Get(columnStrings[index++]);
|
||||||
Rarity = EnumUtility<RarityType>.Get(columnStrings[index++]);
|
|
||||||
ItemType = columnStrings[index++];
|
ItemType = columnStrings[index++];
|
||||||
ItemId = ParseIntOrDefault(columnStrings[index++], 0);
|
ItemId = ParseIntOrDefault(columnStrings[index++], 0);
|
||||||
Weight = ParseIntOrDefault(columnStrings[index++], 1);
|
Weights = ParseWeights(columnStrings[index++]);
|
||||||
MinPhase = ParseIntOrDefault(columnStrings[index++], 1);
|
MinPhase = ParsePhases(columnStrings[index++], 1);
|
||||||
MaxPhase = ParseIntOrDefault(columnStrings[index++], int.MaxValue);
|
MaxPhase = ParsePhases(columnStrings[index++], int.MaxValue);
|
||||||
|
NormalizePhaseRanges();
|
||||||
if (Weight <= 0)
|
|
||||||
{
|
|
||||||
Weight = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MinPhase <= 0)
|
|
||||||
{
|
|
||||||
MinPhase = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MaxPhase < MinPhase)
|
|
||||||
{
|
|
||||||
MaxPhase = MinPhase;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int GetWeight(RarityType rarity)
|
||||||
|
{
|
||||||
|
int index = Mathf.Clamp((int)rarity - 1, 0, RarityCount - 1);
|
||||||
|
return Weights[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetMinPhase(RarityType rarity)
|
||||||
|
{
|
||||||
|
int index = Mathf.Clamp((int)rarity - 1, 0, RarityCount - 1);
|
||||||
|
return MinPhase[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetMaxPhase(RarityType rarity)
|
||||||
|
{
|
||||||
|
int index = Mathf.Clamp((int)rarity - 1, 0, RarityCount - 1);
|
||||||
|
return MaxPhase[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int[] ParseWeights(string raw)
|
||||||
|
{
|
||||||
|
if (!raw.StartsWith('[') || !raw.EndsWith(']'))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Weights must be enclosed in square brackets.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (raw.Length == 2)
|
||||||
|
{
|
||||||
|
return new int[RarityCount];
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] raws = raw.Substring(1, raw.Length - 2).Split(",");
|
||||||
|
if (raws.Length != RarityCount)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Weights must contain exactly {RarityCount} values.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] weights = new int[RarityCount];
|
||||||
|
for (int i = 0; i < raws.Length; i++)
|
||||||
|
{
|
||||||
|
weights[i] = Mathf.Max(0, int.Parse(raws[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return weights;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int[] ParsePhases(string raw, int fallbackValue)
|
||||||
|
{
|
||||||
|
if (!raw.StartsWith('[') || !raw.EndsWith(']'))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Phase ranges must be enclosed in square brackets.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (raw.Length == 2)
|
||||||
|
{
|
||||||
|
int[] fallbackValues = new int[RarityCount];
|
||||||
|
for (int i = 0; i < fallbackValues.Length; i++)
|
||||||
|
{
|
||||||
|
fallbackValues[i] = fallbackValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallbackValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] raws = raw.Substring(1, raw.Length - 2).Split(",");
|
||||||
|
if (raws.Length != RarityCount)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Phase ranges must contain exactly {RarityCount} values.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] phases = new int[RarityCount];
|
||||||
|
for (int i = 0; i < raws.Length; i++)
|
||||||
|
{
|
||||||
|
phases[i] = int.Parse(raws[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return phases;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NormalizePhaseRanges()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < RarityCount; i++)
|
||||||
|
{
|
||||||
|
if (MinPhase[i] <= 0)
|
||||||
|
{
|
||||||
|
MinPhase[i] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MaxPhase[i] < MinPhase[i])
|
||||||
|
{
|
||||||
|
MaxPhase[i] = MinPhase[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static int ParseIntOrDefault(string raw, int fallbackValue)
|
private static int ParseIntOrDefault(string raw, int fallbackValue)
|
||||||
{
|
{
|
||||||
if (int.TryParse(raw, out int parsed))
|
if (int.TryParse(raw, out int parsed))
|
||||||
@@ -68,5 +148,7 @@ namespace GeometryTD.DataTable
|
|||||||
|
|
||||||
return fallbackValue;
|
return fallbackValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const int RarityCount = (int)RarityType.Red;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ namespace GeometryTD.Definition
|
|||||||
Shop = 2,
|
Shop = 2,
|
||||||
Drop = 3,
|
Drop = 3,
|
||||||
Reward = 4,
|
Reward = 4,
|
||||||
|
Event = 5,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,11 +18,31 @@ namespace GeometryTD.Definition
|
|||||||
{
|
{
|
||||||
public int Count;
|
public int Count;
|
||||||
public RarityType Rarity;
|
public RarityType Rarity;
|
||||||
|
public RarityType MinRarity;
|
||||||
|
public RarityType MaxRarity;
|
||||||
|
|
||||||
public AddRandomCompsParam(int count, RarityType rarity)
|
public AddRandomCompsParam(int count, RarityType rarity)
|
||||||
{
|
{
|
||||||
Count = count;
|
Count = count;
|
||||||
Rarity = rarity;
|
RarityType normalizedRarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity);
|
||||||
|
Rarity = normalizedRarity;
|
||||||
|
MinRarity = normalizedRarity;
|
||||||
|
MaxRarity = normalizedRarity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AddRandomCompsParam(int count, RarityType minRarity, RarityType maxRarity)
|
||||||
|
{
|
||||||
|
Count = count;
|
||||||
|
MinRarity = InventoryRarityRuleService.NormalizeComponentRarity(minRarity);
|
||||||
|
MaxRarity = InventoryRarityRuleService.NormalizeComponentRarity(maxRarity);
|
||||||
|
if (MinRarity > MaxRarity)
|
||||||
|
{
|
||||||
|
throw new System.ArgumentOutOfRangeException(
|
||||||
|
nameof(minRarity),
|
||||||
|
$"AddRandomComps rarity range is invalid: {MinRarity} > {MaxRarity}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Rarity = MinRarity == MaxRarity ? MinRarity : RarityType.None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,490 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using GeometryTD.CustomComponent;
|
||||||
|
using GeometryTD.CustomUtility;
|
||||||
|
using GeometryTD.Procedure;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace GeometryTD.Definition
|
||||||
|
{
|
||||||
|
public sealed class EventOptionExecutor
|
||||||
|
{
|
||||||
|
public EventOptionAvailability EvaluateOption(EventOption option, BackpackInventoryData inventory)
|
||||||
|
{
|
||||||
|
if (option == null)
|
||||||
|
{
|
||||||
|
return EventOptionAvailability.Blocked("选项配置无效");
|
||||||
|
}
|
||||||
|
|
||||||
|
EventRequirementBase[] requirements = option.Requirements ?? Array.Empty<EventRequirementBase>();
|
||||||
|
for (int i = 0; i < requirements.Length; i++)
|
||||||
|
{
|
||||||
|
EventRequirementBase requirement = requirements[i];
|
||||||
|
if (requirement == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsRequirementSatisfied(requirement, inventory))
|
||||||
|
{
|
||||||
|
return EventOptionAvailability.Blocked(BuildBlockedReason(requirement));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return EventOptionAvailability.Selectable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventOptionExecutionResult Execute(
|
||||||
|
EventItem eventItem,
|
||||||
|
int optionIndex,
|
||||||
|
RunNodeExecutionContext context,
|
||||||
|
BackpackInventoryData workingInventory)
|
||||||
|
{
|
||||||
|
if (eventItem == null || workingInventory == null)
|
||||||
|
{
|
||||||
|
return EventOptionExecutionResult.Rejected("事件数据无效");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optionIndex < 0 || optionIndex >= eventItem.Options.Length)
|
||||||
|
{
|
||||||
|
return EventOptionExecutionResult.Rejected("事件选项索引无效");
|
||||||
|
}
|
||||||
|
|
||||||
|
EventOption option = eventItem.Options[optionIndex];
|
||||||
|
EventOptionAvailability availability = EvaluateOption(option, workingInventory);
|
||||||
|
if (!availability.IsSelectable)
|
||||||
|
{
|
||||||
|
return EventOptionExecutionResult.Rejected(availability.BlockedReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyEffects(option.CostEffects, eventItem, optionIndex, context, workingInventory, isRewardPhase: false);
|
||||||
|
|
||||||
|
bool isProbabilitySuccess = RollProbability(eventItem.Id, optionIndex, context, option.Probability);
|
||||||
|
if (isProbabilitySuccess)
|
||||||
|
{
|
||||||
|
ApplyEffects(option.RewardEffects, eventItem, optionIndex, context, workingInventory, isRewardPhase: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return EventOptionExecutionResult.Accepted(isProbabilitySuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyEffects(
|
||||||
|
EventEffectBase[] effects,
|
||||||
|
EventItem eventItem,
|
||||||
|
int optionIndex,
|
||||||
|
RunNodeExecutionContext context,
|
||||||
|
BackpackInventoryData workingInventory,
|
||||||
|
bool isRewardPhase)
|
||||||
|
{
|
||||||
|
if (effects == null || effects.Length <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < effects.Length; i++)
|
||||||
|
{
|
||||||
|
EventEffectBase effect = effects[i];
|
||||||
|
if (effect == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (effect.EffectType)
|
||||||
|
{
|
||||||
|
case EventEffectType.AddGold:
|
||||||
|
ApplyAddGoldEffect((AddGoldParam)effect.Param, workingInventory);
|
||||||
|
break;
|
||||||
|
case EventEffectType.RemoveRandomComps:
|
||||||
|
ApplyRemoveRandomComponentsEffect(
|
||||||
|
(RemoveRandomCompsParam)effect.Param,
|
||||||
|
eventItem.Id,
|
||||||
|
optionIndex,
|
||||||
|
context,
|
||||||
|
workingInventory,
|
||||||
|
i);
|
||||||
|
break;
|
||||||
|
case EventEffectType.AddRandomComps:
|
||||||
|
ApplyAddRandomComponentsEffect(
|
||||||
|
(AddRandomCompsParam)effect.Param,
|
||||||
|
eventItem.Id,
|
||||||
|
optionIndex,
|
||||||
|
context,
|
||||||
|
workingInventory,
|
||||||
|
i);
|
||||||
|
break;
|
||||||
|
case EventEffectType.DamageRandomTowersEndurance:
|
||||||
|
ApplyDamageRandomTowerEnduranceEffect(
|
||||||
|
(DamageRandomTowerEnduranceParam)effect.Param,
|
||||||
|
eventItem.Id,
|
||||||
|
optionIndex,
|
||||||
|
context,
|
||||||
|
workingInventory,
|
||||||
|
i);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Unsupported event effect at runtime: {effect.EffectType} (rewardPhase={isRewardPhase}).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyAddGoldEffect(AddGoldParam param, BackpackInventoryData workingInventory)
|
||||||
|
{
|
||||||
|
int nextGold = workingInventory.Gold + (param?.Count ?? 0);
|
||||||
|
if (nextGold < 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Event gold effect would reduce gold below zero.");
|
||||||
|
}
|
||||||
|
|
||||||
|
workingInventory.Gold = nextGold;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyRemoveRandomComponentsEffect(
|
||||||
|
RemoveRandomCompsParam param,
|
||||||
|
int eventId,
|
||||||
|
int optionIndex,
|
||||||
|
RunNodeExecutionContext context,
|
||||||
|
BackpackInventoryData workingInventory,
|
||||||
|
int effectIndex)
|
||||||
|
{
|
||||||
|
int removeCount = Mathf.Max(0, param?.Count ?? 0);
|
||||||
|
if (removeCount <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TowerCompItemData> candidates = CollectLooseComponents(workingInventory, param.Rarity);
|
||||||
|
if (candidates.Count < removeCount)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Event component removal effect does not have enough candidates.");
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Random random = CreateRandom(context, eventId, optionIndex, effectIndex, 101);
|
||||||
|
Shuffle(candidates, random);
|
||||||
|
for (int i = 0; i < removeCount; i++)
|
||||||
|
{
|
||||||
|
RemoveComponentByInstanceId(workingInventory, candidates[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyAddRandomComponentsEffect(
|
||||||
|
AddRandomCompsParam param,
|
||||||
|
int eventId,
|
||||||
|
int optionIndex,
|
||||||
|
RunNodeExecutionContext context,
|
||||||
|
BackpackInventoryData workingInventory,
|
||||||
|
int effectIndex)
|
||||||
|
{
|
||||||
|
int addCount = Mathf.Max(0, param?.Count ?? 0);
|
||||||
|
if (addCount <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GameEntry.InventoryGeneration == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Event component generation requires InventoryGenerationComponent.");
|
||||||
|
}
|
||||||
|
|
||||||
|
IReadOnlyList<TowerCompItemData> generatedComponents = GameEntry.InventoryGeneration.BuildEventRewardComponents(
|
||||||
|
addCount,
|
||||||
|
param.MinRarity,
|
||||||
|
param.MaxRarity,
|
||||||
|
context?.RunSeed ?? 0,
|
||||||
|
context?.SequenceIndex ?? -1,
|
||||||
|
eventId,
|
||||||
|
optionIndex,
|
||||||
|
effectIndex);
|
||||||
|
for (int i = 0; i < generatedComponents.Count; i++)
|
||||||
|
{
|
||||||
|
AddComponentToInventory(workingInventory, generatedComponents[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyDamageRandomTowerEnduranceEffect(
|
||||||
|
DamageRandomTowerEnduranceParam param,
|
||||||
|
int eventId,
|
||||||
|
int optionIndex,
|
||||||
|
RunNodeExecutionContext context,
|
||||||
|
BackpackInventoryData workingInventory,
|
||||||
|
int effectIndex)
|
||||||
|
{
|
||||||
|
int towerCount = Mathf.Max(0, param?.Count ?? 0);
|
||||||
|
float enduranceLoss = Mathf.Max(0, param?.Amount ?? 0);
|
||||||
|
if (towerCount <= 0 || enduranceLoss <= 0f || workingInventory.Towers == null || workingInventory.Towers.Count <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<long> candidateTowerIds = new List<long>(workingInventory.Towers.Count);
|
||||||
|
for (int i = 0; i < workingInventory.Towers.Count; i++)
|
||||||
|
{
|
||||||
|
TowerItemData tower = workingInventory.Towers[i];
|
||||||
|
if (tower != null && tower.InstanceId > 0)
|
||||||
|
{
|
||||||
|
candidateTowerIds.Add(tower.InstanceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidateTowerIds.Count <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Random random = CreateRandom(context, eventId, optionIndex, effectIndex, 211);
|
||||||
|
Shuffle(candidateTowerIds, random);
|
||||||
|
int resolvedCount = Mathf.Min(towerCount, candidateTowerIds.Count);
|
||||||
|
List<long> selectedTowerIds = candidateTowerIds.GetRange(0, resolvedCount);
|
||||||
|
InventoryTowerEnduranceUtility.ReduceTowerEndurance(workingInventory, selectedTowerIds, enduranceLoss);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsRequirementSatisfied(EventRequirementBase requirement, BackpackInventoryData inventory)
|
||||||
|
{
|
||||||
|
switch (requirement.RequirementType)
|
||||||
|
{
|
||||||
|
case EventRequirementType.GoldAtLeast:
|
||||||
|
return (inventory?.Gold ?? 0) >= ((GoldAtLeastParam)requirement.Param).Gold;
|
||||||
|
case EventRequirementType.CompCountAtLeast:
|
||||||
|
CompCountAtLeastParam compParam = (CompCountAtLeastParam)requirement.Param;
|
||||||
|
return CollectLooseComponents(inventory, compParam.Rarity).Count >= compParam.Count;
|
||||||
|
case EventRequirementType.TowerCountAtLeast:
|
||||||
|
return (inventory?.Towers?.Count ?? 0) >= ((TowerCountAtLeastParam)requirement.Param).Count;
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Unsupported event requirement at runtime: {requirement.RequirementType}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildBlockedReason(EventRequirementBase requirement)
|
||||||
|
{
|
||||||
|
switch (requirement.RequirementType)
|
||||||
|
{
|
||||||
|
case EventRequirementType.GoldAtLeast:
|
||||||
|
return $"需要至少 {((GoldAtLeastParam)requirement.Param).Gold} 金币";
|
||||||
|
case EventRequirementType.CompCountAtLeast:
|
||||||
|
CompCountAtLeastParam compParam = (CompCountAtLeastParam)requirement.Param;
|
||||||
|
return $"需要至少 {compParam.Count} 个未装配的{GetRarityText(compParam.Rarity)}组件";
|
||||||
|
case EventRequirementType.TowerCountAtLeast:
|
||||||
|
return $"需要至少 {((TowerCountAtLeastParam)requirement.Param).Count} 座防御塔";
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Unsupported event requirement at runtime: {requirement.RequirementType}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool RollProbability(int eventId, int optionIndex, RunNodeExecutionContext context, float probability)
|
||||||
|
{
|
||||||
|
float clampedProbability = Mathf.Clamp01(probability);
|
||||||
|
if (clampedProbability >= 1f)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clampedProbability <= 0f)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Random random = CreateRandom(context, eventId, optionIndex, 0, 17);
|
||||||
|
return random.NextDouble() <= clampedProbability;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static System.Random CreateRandom(
|
||||||
|
RunNodeExecutionContext context,
|
||||||
|
int eventId,
|
||||||
|
int optionIndex,
|
||||||
|
int effectIndex,
|
||||||
|
int salt)
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
int seed = 17;
|
||||||
|
seed = seed * 31 + (context?.RunSeed ?? 0);
|
||||||
|
seed = seed * 31 + (context?.SequenceIndex ?? -1);
|
||||||
|
seed = seed * 31 + eventId;
|
||||||
|
seed = seed * 31 + optionIndex;
|
||||||
|
seed = seed * 31 + effectIndex;
|
||||||
|
seed = seed * 31 + salt;
|
||||||
|
return new System.Random(seed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<TowerCompItemData> CollectLooseComponents(
|
||||||
|
BackpackInventoryData inventory,
|
||||||
|
RarityType rarity)
|
||||||
|
{
|
||||||
|
List<TowerCompItemData> result = new List<TowerCompItemData>();
|
||||||
|
if (inventory == null)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
RarityType normalizedRarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity);
|
||||||
|
CollectFromList(result, inventory.MuzzleComponents, normalizedRarity);
|
||||||
|
CollectFromList(result, inventory.BearingComponents, normalizedRarity);
|
||||||
|
CollectFromList(result, inventory.BaseComponents, normalizedRarity);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CollectFromList<TComp>(
|
||||||
|
List<TowerCompItemData> destination,
|
||||||
|
List<TComp> source,
|
||||||
|
RarityType rarity)
|
||||||
|
where TComp : TowerCompItemData
|
||||||
|
{
|
||||||
|
if (source == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < source.Count; i++)
|
||||||
|
{
|
||||||
|
TComp component = source[i];
|
||||||
|
if (component == null || component.IsAssembledIntoTower)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (InventoryRarityRuleService.NormalizeComponentRarity(component.Rarity) != rarity)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
destination.Add(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RemoveComponentByInstanceId(BackpackInventoryData inventory, TowerCompItemData component)
|
||||||
|
{
|
||||||
|
switch (component)
|
||||||
|
{
|
||||||
|
case MuzzleCompItemData muzzleComp:
|
||||||
|
RemoveByInstanceId(inventory.MuzzleComponents, muzzleComp.InstanceId);
|
||||||
|
break;
|
||||||
|
case BearingCompItemData bearingComp:
|
||||||
|
RemoveByInstanceId(inventory.BearingComponents, bearingComp.InstanceId);
|
||||||
|
break;
|
||||||
|
case BaseCompItemData baseComp:
|
||||||
|
RemoveByInstanceId(inventory.BaseComponents, baseComp.InstanceId);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException($"Unsupported component type for event removal: {component?.GetType().Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddComponentToInventory(BackpackInventoryData inventory, TowerCompItemData component)
|
||||||
|
{
|
||||||
|
if (component == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (component)
|
||||||
|
{
|
||||||
|
case MuzzleCompItemData muzzleComp:
|
||||||
|
inventory.MuzzleComponents.Add(InventoryCloneUtility.CloneMuzzleComp(muzzleComp));
|
||||||
|
break;
|
||||||
|
case BearingCompItemData bearingComp:
|
||||||
|
inventory.BearingComponents.Add(InventoryCloneUtility.CloneBearingComp(bearingComp));
|
||||||
|
break;
|
||||||
|
case BaseCompItemData baseComp:
|
||||||
|
inventory.BaseComponents.Add(InventoryCloneUtility.CloneBaseComp(baseComp));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException($"Unsupported component type for event addition: {component.GetType().Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RemoveByInstanceId<TComp>(List<TComp> components, long instanceId)
|
||||||
|
where TComp : TowerCompItemData
|
||||||
|
{
|
||||||
|
if (components == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < components.Count; i++)
|
||||||
|
{
|
||||||
|
TComp component = components[i];
|
||||||
|
if (component != null && component.InstanceId == instanceId)
|
||||||
|
{
|
||||||
|
components.RemoveAt(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException($"Failed to remove component instance #{instanceId} from inventory.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Shuffle<T>(IList<T> values, System.Random random)
|
||||||
|
{
|
||||||
|
for (int i = values.Count - 1; i > 0; i--)
|
||||||
|
{
|
||||||
|
int swapIndex = random.Next(0, i + 1);
|
||||||
|
(values[i], values[swapIndex]) = (values[swapIndex], values[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetRarityText(RarityType rarity)
|
||||||
|
{
|
||||||
|
return InventoryRarityRuleService.NormalizeComponentRarity(rarity) switch
|
||||||
|
{
|
||||||
|
RarityType.White => "白色",
|
||||||
|
RarityType.Green => "绿色",
|
||||||
|
RarityType.Blue => "蓝色",
|
||||||
|
RarityType.Purple => "紫色",
|
||||||
|
RarityType.Red => "红色",
|
||||||
|
_ => "未知"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class EventOptionAvailability
|
||||||
|
{
|
||||||
|
private EventOptionAvailability(bool isSelectable, string blockedReason)
|
||||||
|
{
|
||||||
|
IsSelectable = isSelectable;
|
||||||
|
BlockedReason = blockedReason ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSelectable { get; }
|
||||||
|
|
||||||
|
public string BlockedReason { get; }
|
||||||
|
|
||||||
|
public static EventOptionAvailability Selectable()
|
||||||
|
{
|
||||||
|
return new EventOptionAvailability(true, string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EventOptionAvailability Blocked(string blockedReason)
|
||||||
|
{
|
||||||
|
return new EventOptionAvailability(false, blockedReason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class EventOptionExecutionResult
|
||||||
|
{
|
||||||
|
private EventOptionExecutionResult(bool isAccepted, bool isProbabilitySuccess, string failureReason)
|
||||||
|
{
|
||||||
|
IsAccepted = isAccepted;
|
||||||
|
IsProbabilitySuccess = isProbabilitySuccess;
|
||||||
|
FailureReason = failureReason ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsAccepted { get; }
|
||||||
|
|
||||||
|
public bool IsProbabilitySuccess { get; }
|
||||||
|
|
||||||
|
public string FailureReason { get; }
|
||||||
|
|
||||||
|
public static EventOptionExecutionResult Accepted(bool isProbabilitySuccess)
|
||||||
|
{
|
||||||
|
return new EventOptionExecutionResult(true, isProbabilitySuccess, string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EventOptionExecutionResult Rejected(string failureReason)
|
||||||
|
{
|
||||||
|
return new EventOptionExecutionResult(false, false, failureReason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8e4d7125aa7b4cb9aa62cb0bf9301d5b
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -24,8 +24,8 @@ namespace GeometryTD.Definition
|
|||||||
int normalizedBase = (int)NormalizeComponentRarity(baseRarity);
|
int normalizedBase = (int)NormalizeComponentRarity(baseRarity);
|
||||||
|
|
||||||
float average = (normalizedMuzzle + normalizedBearing + normalizedBase) / 3f;
|
float average = (normalizedMuzzle + normalizedBearing + normalizedBase) / 3f;
|
||||||
int rounded = Mathf.RoundToInt(average);
|
int floored = Mathf.FloorToInt(average);
|
||||||
int clamped = Mathf.Clamp(rounded, (int)RarityType.White, (int)RarityType.Red);
|
int clamped = Mathf.Clamp(floored, (int)RarityType.White, (int)RarityType.Red);
|
||||||
return (RarityType)clamped;
|
return (RarityType)clamped;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,15 @@ namespace GeometryTD.Definition
|
|||||||
configId);
|
configId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static InventoryTagRandomContext CreateEvent(int runSeed, int nodeSequenceIndex, int eventOrdinal, int configId)
|
||||||
|
{
|
||||||
|
return new InventoryTagRandomContext(
|
||||||
|
runSeed,
|
||||||
|
InventoryTagSourceType.Event,
|
||||||
|
ComposeLocalItemInstanceId(nodeSequenceIndex, eventOrdinal),
|
||||||
|
configId);
|
||||||
|
}
|
||||||
|
|
||||||
public static long ComposeLocalItemInstanceId(int nodeSequenceIndex, int localOrdinal)
|
public static long ComposeLocalItemInstanceId(int nodeSequenceIndex, int localOrdinal)
|
||||||
{
|
{
|
||||||
long normalizedSequence = Math.Max(0, nodeSequenceIndex) + 1L;
|
long normalizedSequence = Math.Max(0, nodeSequenceIndex) + 1L;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using GeometryTD.UI;
|
using GeometryTD.UI;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.InputSystem;
|
||||||
using UnityEngine.Tilemaps;
|
using UnityEngine.Tilemaps;
|
||||||
|
|
||||||
namespace GeometryTD.Entity
|
namespace GeometryTD.Entity
|
||||||
@@ -70,7 +71,14 @@ namespace GeometryTD.Entity
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
|
Mouse mouse = Mouse.current;
|
||||||
|
if (mouse == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 pointerPosition = mouse.position.ReadValue();
|
||||||
|
Ray ray = mainCamera.ScreenPointToRay(pointerPosition);
|
||||||
float mapPlaneZ = tilemap != null
|
float mapPlaneZ = tilemap != null
|
||||||
? tilemap.transform.position.z
|
? tilemap.transform.position.z
|
||||||
: (mapTransform != null ? mapTransform.position.z : 0f);
|
: (mapTransform != null ? mapTransform.position.z : 0f);
|
||||||
@@ -84,7 +92,7 @@ namespace GeometryTD.Entity
|
|||||||
}
|
}
|
||||||
|
|
||||||
worldPosition = ray.GetPoint(enterDistance);
|
worldPosition = ray.GetPoint(enterDistance);
|
||||||
screenPosition = BuildScreenPosition(Input.mousePosition);
|
screenPosition = BuildScreenPosition(pointerPosition);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using GeometryTD.Entity.EntityData;
|
|||||||
using GeometryTD.UI;
|
using GeometryTD.UI;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.EventSystems;
|
using UnityEngine.EventSystems;
|
||||||
|
using UnityEngine.InputSystem;
|
||||||
using UnityEngine.Tilemaps;
|
using UnityEngine.Tilemaps;
|
||||||
using UnityGameFramework.Runtime;
|
using UnityGameFramework.Runtime;
|
||||||
|
|
||||||
@@ -272,7 +273,8 @@ namespace GeometryTD.Entity
|
|||||||
|
|
||||||
private void HandleCombatSelectInput()
|
private void HandleCombatSelectInput()
|
||||||
{
|
{
|
||||||
if (!_enableCombatSelectInput || !Input.GetMouseButtonDown(0))
|
Mouse mouse = Mouse.current;
|
||||||
|
if (!_enableCombatSelectInput || mouse == null || !mouse.leftButton.wasPressedThisFrame)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
using GameFramework;
|
using GameFramework;
|
||||||
using GameFramework.Event;
|
using GameFramework.Event;
|
||||||
using GeometryTD.CustomUtility;
|
|
||||||
using GeometryTD.Definition;
|
|
||||||
using GeometryTD.Procedure;
|
using GeometryTD.Procedure;
|
||||||
|
|
||||||
namespace GeometryTD.CustomEvent
|
namespace GeometryTD.CustomEvent
|
||||||
@@ -24,7 +22,7 @@ namespace GeometryTD.CustomEvent
|
|||||||
|
|
||||||
public bool CombatWon { get; private set; }
|
public bool CombatWon { get; private set; }
|
||||||
|
|
||||||
public BackpackInventoryData InventorySnapshotAfterNode { get; private set; }
|
public RunNodeCompletionSnapshot CompletionSnapshot { get; private set; }
|
||||||
|
|
||||||
public NodeCompleteEventArgs()
|
public NodeCompleteEventArgs()
|
||||||
{
|
{
|
||||||
@@ -42,7 +40,7 @@ namespace GeometryTD.CustomEvent
|
|||||||
int sequenceIndex,
|
int sequenceIndex,
|
||||||
RunNodeCompletionStatus completionStatus,
|
RunNodeCompletionStatus completionStatus,
|
||||||
bool combatWon,
|
bool combatWon,
|
||||||
BackpackInventoryData inventorySnapshotAfterNode)
|
RunNodeCompletionSnapshot completionSnapshot)
|
||||||
{
|
{
|
||||||
var args = ReferencePool.Acquire<NodeCompleteEventArgs>();
|
var args = ReferencePool.Acquire<NodeCompleteEventArgs>();
|
||||||
args.RunId = runId;
|
args.RunId = runId;
|
||||||
@@ -51,7 +49,7 @@ namespace GeometryTD.CustomEvent
|
|||||||
args.SequenceIndex = sequenceIndex;
|
args.SequenceIndex = sequenceIndex;
|
||||||
args.CompletionStatus = completionStatus;
|
args.CompletionStatus = completionStatus;
|
||||||
args.CombatWon = combatWon;
|
args.CombatWon = combatWon;
|
||||||
args.InventorySnapshotAfterNode = InventoryCloneUtility.CloneInventory(inventorySnapshotAfterNode);
|
args.CompletionSnapshot = completionSnapshot != null ? completionSnapshot.Clone() : null;
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
@@ -64,7 +62,7 @@ namespace GeometryTD.CustomEvent
|
|||||||
SequenceIndex = -1;
|
SequenceIndex = -1;
|
||||||
CompletionStatus = RunNodeCompletionStatus.None;
|
CompletionStatus = RunNodeCompletionStatus.None;
|
||||||
CombatWon = false;
|
CombatWon = false;
|
||||||
InventorySnapshotAfterNode = null;
|
CompletionSnapshot = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using GameFramework;
|
||||||
|
using GameFramework.Event;
|
||||||
|
|
||||||
|
namespace GeometryTD.CustomEvent
|
||||||
|
{
|
||||||
|
public sealed class RepoSellCancelRequestedEventArgs : GameEventArgs
|
||||||
|
{
|
||||||
|
public static int EventId => typeof(RepoSellCancelRequestedEventArgs).GetHashCode();
|
||||||
|
|
||||||
|
public override int Id => EventId;
|
||||||
|
|
||||||
|
public static RepoSellCancelRequestedEventArgs Create()
|
||||||
|
{
|
||||||
|
return ReferencePool.Acquire<RepoSellCancelRequestedEventArgs>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Clear()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2cc34a22ea6347619b065c5874e0b715
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using GameFramework;
|
||||||
|
using GameFramework.Event;
|
||||||
|
|
||||||
|
namespace GeometryTD.CustomEvent
|
||||||
|
{
|
||||||
|
public sealed class RepoSellConfirmRequestedEventArgs : GameEventArgs
|
||||||
|
{
|
||||||
|
public static int EventId => typeof(RepoSellConfirmRequestedEventArgs).GetHashCode();
|
||||||
|
|
||||||
|
public override int Id => EventId;
|
||||||
|
|
||||||
|
public static RepoSellConfirmRequestedEventArgs Create()
|
||||||
|
{
|
||||||
|
return ReferencePool.Acquire<RepoSellConfirmRequestedEventArgs>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Clear()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 598b87b9cb8e4783bc73db5e255d31f6
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using GameFramework;
|
||||||
|
using GameFramework.Event;
|
||||||
|
|
||||||
|
namespace GeometryTD.CustomEvent
|
||||||
|
{
|
||||||
|
public sealed class RepoSellModeToggleRequestedEventArgs : GameEventArgs
|
||||||
|
{
|
||||||
|
public static int EventId => typeof(RepoSellModeToggleRequestedEventArgs).GetHashCode();
|
||||||
|
|
||||||
|
public override int Id => EventId;
|
||||||
|
|
||||||
|
public static RepoSellModeToggleRequestedEventArgs Create()
|
||||||
|
{
|
||||||
|
return ReferencePool.Acquire<RepoSellModeToggleRequestedEventArgs>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Clear()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 44692273ce6a417a875dea9675663d82
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -31,6 +31,21 @@ namespace GeometryTD.Factory
|
|||||||
case EventEffectType.AddRandomComps:
|
case EventEffectType.AddRandomComps:
|
||||||
{
|
{
|
||||||
int count = GetInt(param, "Count");
|
int count = GetInt(param, "Count");
|
||||||
|
bool hasMinRarity = TryGetString(param, "MinRarity", out string minRarityRaw);
|
||||||
|
bool hasMaxRarity = TryGetString(param, "MaxRarity", out string maxRarityRaw);
|
||||||
|
if (hasMinRarity || hasMaxRarity)
|
||||||
|
{
|
||||||
|
if (!hasMinRarity || !hasMaxRarity)
|
||||||
|
{
|
||||||
|
throw new System.InvalidOperationException(
|
||||||
|
"AddRandomComps requires both MinRarity and MaxRarity when using ranged rarity config.");
|
||||||
|
}
|
||||||
|
|
||||||
|
RarityType minRarity = EnumUtility<RarityType>.Get(minRarityRaw);
|
||||||
|
RarityType maxRarity = EnumUtility<RarityType>.Get(maxRarityRaw);
|
||||||
|
return new AddRandomCompsEffect(new AddRandomCompsParam(count, minRarity, maxRarity), probability);
|
||||||
|
}
|
||||||
|
|
||||||
RarityType rarity = EnumUtility<RarityType>.Get(GetString(param, "Rarity"));
|
RarityType rarity = EnumUtility<RarityType>.Get(GetString(param, "Rarity"));
|
||||||
return new AddRandomCompsEffect(new AddRandomCompsParam(count, rarity), probability);
|
return new AddRandomCompsEffect(new AddRandomCompsParam(count, rarity), probability);
|
||||||
}
|
}
|
||||||
@@ -73,5 +88,17 @@ namespace GeometryTD.Factory
|
|||||||
|
|
||||||
return token.Value<string>();
|
return token.Value<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool TryGetString(JObject param, string key, out string value)
|
||||||
|
{
|
||||||
|
value = string.Empty;
|
||||||
|
if (param == null || !param.TryGetValue(key, out JToken token) || token.Type == JTokenType.Null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = token.Value<string>();
|
||||||
|
return !string.IsNullOrWhiteSpace(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
using System;
|
||||||
|
using GameFramework.DataTable;
|
||||||
|
using GeometryTD.DataTable;
|
||||||
|
using GeometryTD.Definition;
|
||||||
|
|
||||||
|
namespace GeometryTD.Factory
|
||||||
|
{
|
||||||
|
public sealed class OutGameDropItemBuilder
|
||||||
|
{
|
||||||
|
private readonly IDataTable<DRMuzzleComp> _muzzleCompTable;
|
||||||
|
private readonly IDataTable<DRBearingComp> _bearingCompTable;
|
||||||
|
private readonly IDataTable<DRBaseComp> _baseCompTable;
|
||||||
|
|
||||||
|
public OutGameDropItemBuilder(
|
||||||
|
IDataTable<DRMuzzleComp> muzzleCompTable,
|
||||||
|
IDataTable<DRBearingComp> bearingCompTable,
|
||||||
|
IDataTable<DRBaseComp> baseCompTable)
|
||||||
|
{
|
||||||
|
_muzzleCompTable = muzzleCompTable;
|
||||||
|
_bearingCompTable = bearingCompTable;
|
||||||
|
_baseCompTable = baseCompTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryBuildItem(
|
||||||
|
DROutGameDropPool row,
|
||||||
|
RarityType rarity,
|
||||||
|
InventoryGenerationRandomContext randomContext,
|
||||||
|
out TowerCompItemData droppedItem)
|
||||||
|
{
|
||||||
|
droppedItem = null;
|
||||||
|
if (row.ItemId <= 0 || string.IsNullOrWhiteSpace(row.ItemType))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string itemType = row.ItemType.Trim();
|
||||||
|
if (itemType.Equals("MuzzleComp", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
DRMuzzleComp config = _muzzleCompTable.GetDataRow(row.ItemId);
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
droppedItem = ComponentItemFactory.CreateMuzzle(
|
||||||
|
config,
|
||||||
|
randomContext.CreateStableItemInstanceId(),
|
||||||
|
rarity,
|
||||||
|
randomContext.CreateTagRandomContext(config.Id));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemType.Equals("BearingComp", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
DRBearingComp config = _bearingCompTable.GetDataRow(row.ItemId);
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
droppedItem = ComponentItemFactory.CreateBearing(
|
||||||
|
config,
|
||||||
|
randomContext.CreateStableItemInstanceId(),
|
||||||
|
rarity,
|
||||||
|
randomContext.CreateTagRandomContext(config.Id));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemType.Equals("BaseComp", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
DRBaseComp config = _baseCompTable.GetDataRow(row.ItemId);
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
droppedItem = ComponentItemFactory.CreateBase(
|
||||||
|
config,
|
||||||
|
randomContext.CreateStableItemInstanceId(),
|
||||||
|
rarity,
|
||||||
|
randomContext.CreateTagRandomContext(config.Id));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c5915901f51c430db50725908113ae1a
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -196,45 +196,6 @@ namespace GeometryTD.Procedure
|
|||||||
|
|
||||||
_loadedFlag[ne.DataTableAssetName] = true;
|
_loadedFlag[ne.DataTableAssetName] = true;
|
||||||
Log.Info("Load data table '{0}' OK.", ne.DataTableAssetName);
|
Log.Info("Load data table '{0}' OK.", ne.DataTableAssetName);
|
||||||
|
|
||||||
if (ne.DataTableAssetName == AssetUtility.GetDataTableAsset("TagConfig", false))
|
|
||||||
{
|
|
||||||
ReloadTagRegistriesFromLoadedTables();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ne.DataTableAssetName == AssetUtility.GetDataTableAsset("Tag", false))
|
|
||||||
{
|
|
||||||
ReloadTagRegistriesFromLoadedTables();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ne.DataTableAssetName == AssetUtility.GetDataTableAsset("RarityTagBudget", false))
|
|
||||||
{
|
|
||||||
var tagBudgetTable = GameEntry.DataTable.GetDataTable<DRRarityTagBudget>();
|
|
||||||
if (tagBudgetTable != null)
|
|
||||||
{
|
|
||||||
RarityTagBudgetRuleRegistry.LoadFromRows(tagBudgetTable.GetAllDataRows());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ReloadTagRegistriesFromLoadedTables()
|
|
||||||
{
|
|
||||||
DRTagConfig[] tagConfigRows = null;
|
|
||||||
var tagConfigTable = GameEntry.DataTable.GetDataTable<DRTagConfig>();
|
|
||||||
if (tagConfigTable != null)
|
|
||||||
{
|
|
||||||
tagConfigRows = tagConfigTable.GetAllDataRows();
|
|
||||||
}
|
|
||||||
|
|
||||||
DRTag[] tagRows = null;
|
|
||||||
var tagTable = GameEntry.DataTable.GetDataTable<DRTag>();
|
|
||||||
if (tagTable != null)
|
|
||||||
{
|
|
||||||
tagRows = tagTable.GetAllDataRows();
|
|
||||||
TagGenerationRuleRegistry.LoadFromRows(tagRows);
|
|
||||||
}
|
|
||||||
|
|
||||||
TagDefinitionRegistry.ReloadFromRows(tagConfigRows, tagRows);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLoadDataTableFailure(object sender, GameEventArgs e)
|
private void OnLoadDataTableFailure(object sender, GameEventArgs e)
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ namespace GeometryTD.Procedure
|
|||||||
GameEntry.Event.Subscribe(NodeEnterEventArgs.EventId, OnNodeEnter);
|
GameEntry.Event.Subscribe(NodeEnterEventArgs.EventId, OnNodeEnter);
|
||||||
GameEntry.Event.Subscribe(NodeMapNodeEnterRequestedEventArgs.EventId, OnNodeMapNodeEnterRequested);
|
GameEntry.Event.Subscribe(NodeMapNodeEnterRequestedEventArgs.EventId, OnNodeMapNodeEnterRequested);
|
||||||
|
|
||||||
|
GameEntry.TagRegistry.OnInit();
|
||||||
GameEntry.EventNode.OnInit();
|
GameEntry.EventNode.OnInit();
|
||||||
GameEntry.CombatNode.OnInit(LevelThemeType.Plain);
|
GameEntry.CombatNode.OnInit(LevelThemeType.Plain);
|
||||||
GameEntry.ShopNode.OnInit();
|
GameEntry.ShopNode.OnInit();
|
||||||
@@ -193,23 +194,36 @@ namespace GeometryTD.Procedure
|
|||||||
args.CompletionStatus,
|
args.CompletionStatus,
|
||||||
args.CombatWon);
|
args.CombatWon);
|
||||||
|
|
||||||
BackpackInventoryData snapshot = args.InventorySnapshotAfterNode;
|
RunNodeCompletionSnapshot completionSnapshot = args.CompletionSnapshot != null
|
||||||
if (snapshot == null && GameEntry.PlayerInventory != null)
|
? args.CompletionSnapshot.Clone()
|
||||||
|
: _currentRunState?.CreateCurrentNodeContext()?.CreateCompletionSnapshot(null);
|
||||||
|
BackpackInventoryData inventorySnapshot = completionSnapshot?.InventorySnapshot;
|
||||||
|
if (inventorySnapshot == null && GameEntry.PlayerInventory != null)
|
||||||
{
|
{
|
||||||
snapshot = GameEntry.PlayerInventory.GetInventorySnapshot();
|
inventorySnapshot = GameEntry.PlayerInventory.GetInventorySnapshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (completionSnapshot != null)
|
||||||
|
{
|
||||||
|
completionSnapshot.InventorySnapshot = inventorySnapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcedureMainParticipantTowerCleanupResult cleanupResult =
|
ProcedureMainParticipantTowerCleanupResult cleanupResult =
|
||||||
ProcedureMainParticipantTowerCleanupService.RemoveBrokenParticipantTowers(
|
ProcedureMainParticipantTowerCleanupService.RemoveBrokenParticipantTowers(
|
||||||
snapshot,
|
inventorySnapshot,
|
||||||
MaxParticipantTowerCount);
|
MaxParticipantTowerCount);
|
||||||
if (cleanupResult.HasAnyRemovedTower && GameEntry.PlayerInventory != null)
|
if (cleanupResult.HasAnyRemovedTower && GameEntry.PlayerInventory != null)
|
||||||
{
|
{
|
||||||
GameEntry.PlayerInventory.ReplaceInventorySnapshot(snapshot);
|
GameEntry.PlayerInventory.ReplaceInventorySnapshot(inventorySnapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (completionSnapshot != null)
|
||||||
|
{
|
||||||
|
completionSnapshot.InventorySnapshot = inventorySnapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcedureMainRunAdvanceResult advanceResult =
|
ProcedureMainRunAdvanceResult advanceResult =
|
||||||
ProcedureMainRunFlowService.TryAdvanceRun(_currentRunState, args.CompletionStatus, snapshot);
|
ProcedureMainRunFlowService.TryAdvanceRun(_currentRunState, args.CompletionStatus, completionSnapshot);
|
||||||
HandleRunAdvanceResult(advanceResult);
|
HandleRunAdvanceResult(advanceResult);
|
||||||
if (cleanupResult.HasAnyRemovedTower &&
|
if (cleanupResult.HasAnyRemovedTower &&
|
||||||
advanceResult != ProcedureMainRunAdvanceResult.RunCompleted)
|
advanceResult != ProcedureMainRunAdvanceResult.RunCompleted)
|
||||||
@@ -265,28 +279,20 @@ namespace GeometryTD.Procedure
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (GameEntry.CombatNode.CurrentThemeType != currentNode.ThemeType)
|
||||||
|
{
|
||||||
|
GameEntry.CombatNode.OnInit(currentNode.ThemeType);
|
||||||
|
}
|
||||||
|
|
||||||
GameEntry.CombatNode.StartCombat(
|
GameEntry.CombatNode.StartCombat(
|
||||||
currentNode.LinkedLevelId,
|
currentNode.LinkedLevelId,
|
||||||
_currentRunState.RunId,
|
_currentRunState.CreateCurrentNodeContext());
|
||||||
_currentRunState.RunSeed,
|
|
||||||
currentNode.NodeId,
|
|
||||||
currentNode.NodeType,
|
|
||||||
currentNode.SequenceIndex);
|
|
||||||
return;
|
return;
|
||||||
case RunNodeType.Event:
|
case RunNodeType.Event:
|
||||||
GameEntry.EventNode.StartEvent(
|
GameEntry.EventNode.StartEvent(_currentRunState.CreateCurrentNodeContext());
|
||||||
_currentRunState.RunId,
|
|
||||||
currentNode.NodeId,
|
|
||||||
currentNode.NodeType,
|
|
||||||
currentNode.SequenceIndex);
|
|
||||||
return;
|
return;
|
||||||
case RunNodeType.Shop:
|
case RunNodeType.Shop:
|
||||||
GameEntry.ShopNode.StartShop(
|
GameEntry.ShopNode.StartShop(_currentRunState.CreateCurrentNodeContext());
|
||||||
_currentRunState.RunId,
|
|
||||||
_currentRunState.RunSeed,
|
|
||||||
currentNode.NodeId,
|
|
||||||
currentNode.NodeType,
|
|
||||||
currentNode.SequenceIndex);
|
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
Log.Warning("ProcedureMain.OnNodeMapNodeEnterRequested() encountered unsupported node type: {0}.",
|
Log.Warning("ProcedureMain.OnNodeMapNodeEnterRequested() encountered unsupported node type: {0}.",
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ namespace GeometryTD.Procedure
|
|||||||
public static ProcedureMainRunAdvanceResult TryAdvanceRun(
|
public static ProcedureMainRunAdvanceResult TryAdvanceRun(
|
||||||
RunState runState,
|
RunState runState,
|
||||||
RunNodeCompletionStatus completionStatus,
|
RunNodeCompletionStatus completionStatus,
|
||||||
BackpackInventoryData snapshot)
|
RunNodeCompletionSnapshot completionSnapshot)
|
||||||
{
|
{
|
||||||
if (!RunStateAdvanceService.TryCompleteCurrentNode(runState, completionStatus, snapshot))
|
if (!RunStateAdvanceService.TryCompleteCurrentNode(runState, completionStatus, completionSnapshot))
|
||||||
{
|
{
|
||||||
return ProcedureMainRunAdvanceResult.NoChange;
|
return ProcedureMainRunAdvanceResult.NoChange;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,96 @@ namespace GeometryTD.Procedure
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public sealed class RunItemState
|
||||||
|
{
|
||||||
|
public int ItemId { get; set; }
|
||||||
|
public int StackCount { get; set; }
|
||||||
|
|
||||||
|
internal RunItemState Clone()
|
||||||
|
{
|
||||||
|
return new RunItemState
|
||||||
|
{
|
||||||
|
ItemId = ItemId,
|
||||||
|
StackCount = StackCount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public sealed class RunNodeExecutionContext
|
||||||
|
{
|
||||||
|
public string RunId { get; set; }
|
||||||
|
public int RunSeed { get; set; }
|
||||||
|
public int NodeId { get; set; }
|
||||||
|
public RunNodeType NodeType { get; set; }
|
||||||
|
public int SequenceIndex { get; set; }
|
||||||
|
public LevelThemeType ThemeType { get; set; }
|
||||||
|
public int ThemeStageIndex { get; set; }
|
||||||
|
public List<LevelThemeType> CurrentThemePool { get; set; } = new List<LevelThemeType>();
|
||||||
|
public List<LevelThemeType> ThemeHistory { get; set; } = new List<LevelThemeType>();
|
||||||
|
public int CurrentNodeContinueChallengeLayer { get; set; }
|
||||||
|
public List<RunItemState> RunItems { get; set; } = new List<RunItemState>();
|
||||||
|
|
||||||
|
internal RunNodeExecutionContext Clone()
|
||||||
|
{
|
||||||
|
return new RunNodeExecutionContext
|
||||||
|
{
|
||||||
|
RunId = RunId,
|
||||||
|
RunSeed = RunSeed,
|
||||||
|
NodeId = NodeId,
|
||||||
|
NodeType = NodeType,
|
||||||
|
SequenceIndex = SequenceIndex,
|
||||||
|
ThemeType = ThemeType,
|
||||||
|
ThemeStageIndex = ThemeStageIndex,
|
||||||
|
CurrentThemePool = RunStateCloneUtility.CloneThemeList(CurrentThemePool),
|
||||||
|
ThemeHistory = RunStateCloneUtility.CloneThemeList(ThemeHistory),
|
||||||
|
CurrentNodeContinueChallengeLayer = CurrentNodeContinueChallengeLayer,
|
||||||
|
RunItems = RunStateCloneUtility.CloneRunItems(RunItems)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public RunNodeCompletionSnapshot CreateCompletionSnapshot(BackpackInventoryData inventorySnapshot)
|
||||||
|
{
|
||||||
|
return new RunNodeCompletionSnapshot
|
||||||
|
{
|
||||||
|
InventorySnapshot = InventoryCloneUtility.CloneInventory(inventorySnapshot),
|
||||||
|
ThemeType = ThemeType,
|
||||||
|
ThemeStageIndex = ThemeStageIndex,
|
||||||
|
CurrentThemePool = RunStateCloneUtility.CloneThemeList(CurrentThemePool),
|
||||||
|
ThemeHistory = RunStateCloneUtility.CloneThemeList(ThemeHistory),
|
||||||
|
CurrentNodeContinueChallengeLayer = CurrentNodeContinueChallengeLayer,
|
||||||
|
RunItems = RunStateCloneUtility.CloneRunItems(RunItems)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public sealed class RunNodeCompletionSnapshot
|
||||||
|
{
|
||||||
|
public BackpackInventoryData InventorySnapshot { get; set; }
|
||||||
|
public LevelThemeType ThemeType { get; set; }
|
||||||
|
public int ThemeStageIndex { get; set; }
|
||||||
|
public List<LevelThemeType> CurrentThemePool { get; set; } = new List<LevelThemeType>();
|
||||||
|
public List<LevelThemeType> ThemeHistory { get; set; } = new List<LevelThemeType>();
|
||||||
|
public int CurrentNodeContinueChallengeLayer { get; set; }
|
||||||
|
public List<RunItemState> RunItems { get; set; } = new List<RunItemState>();
|
||||||
|
|
||||||
|
internal RunNodeCompletionSnapshot Clone()
|
||||||
|
{
|
||||||
|
return new RunNodeCompletionSnapshot
|
||||||
|
{
|
||||||
|
InventorySnapshot = InventoryCloneUtility.CloneInventory(InventorySnapshot),
|
||||||
|
ThemeType = ThemeType,
|
||||||
|
ThemeStageIndex = ThemeStageIndex,
|
||||||
|
CurrentThemePool = RunStateCloneUtility.CloneThemeList(CurrentThemePool),
|
||||||
|
ThemeHistory = RunStateCloneUtility.CloneThemeList(ThemeHistory),
|
||||||
|
CurrentNodeContinueChallengeLayer = CurrentNodeContinueChallengeLayer,
|
||||||
|
RunItems = RunStateCloneUtility.CloneRunItems(RunItems)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public sealed class RunState
|
public sealed class RunState
|
||||||
{
|
{
|
||||||
@@ -82,6 +172,11 @@ namespace GeometryTD.Procedure
|
|||||||
ThemeType = themeType;
|
ThemeType = themeType;
|
||||||
_nodes = nodes ?? new List<RunNodeState>();
|
_nodes = nodes ?? new List<RunNodeState>();
|
||||||
RunInventorySnapshot = InventoryCloneUtility.CloneInventory(runInventorySnapshot);
|
RunInventorySnapshot = InventoryCloneUtility.CloneInventory(runInventorySnapshot);
|
||||||
|
ThemeStageIndex = 0;
|
||||||
|
CurrentThemePool = CreateDefaultThemeList(themeType);
|
||||||
|
ThemeHistory = CreateDefaultThemeList(themeType);
|
||||||
|
CurrentNodeContinueChallengeLayer = 0;
|
||||||
|
RunItems = new List<RunItemState>();
|
||||||
CurrentNodeIndex = _nodes.Count > 0 ? 0 : -1;
|
CurrentNodeIndex = _nodes.Count > 0 ? 0 : -1;
|
||||||
IsCompleted = _nodes.Count <= 0;
|
IsCompleted = _nodes.Count <= 0;
|
||||||
}
|
}
|
||||||
@@ -98,6 +193,16 @@ namespace GeometryTD.Procedure
|
|||||||
|
|
||||||
public BackpackInventoryData RunInventorySnapshot { get; internal set; }
|
public BackpackInventoryData RunInventorySnapshot { get; internal set; }
|
||||||
|
|
||||||
|
public int ThemeStageIndex { get; internal set; }
|
||||||
|
|
||||||
|
public List<LevelThemeType> CurrentThemePool { get; internal set; }
|
||||||
|
|
||||||
|
public List<LevelThemeType> ThemeHistory { get; internal set; }
|
||||||
|
|
||||||
|
public int CurrentNodeContinueChallengeLayer { get; internal set; }
|
||||||
|
|
||||||
|
public List<RunItemState> RunItems { get; internal set; }
|
||||||
|
|
||||||
public IReadOnlyList<RunNodeState> Nodes => _nodes;
|
public IReadOnlyList<RunNodeState> Nodes => _nodes;
|
||||||
|
|
||||||
public int NodeCount => _nodes.Count;
|
public int NodeCount => _nodes.Count;
|
||||||
@@ -161,5 +266,90 @@ namespace GeometryTD.Procedure
|
|||||||
{
|
{
|
||||||
RunInventorySnapshot = InventoryCloneUtility.CloneInventory(inventorySnapshot);
|
RunInventorySnapshot = InventoryCloneUtility.CloneInventory(inventorySnapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RunNodeExecutionContext CreateCurrentNodeContext()
|
||||||
|
{
|
||||||
|
RunNodeState currentNode = CurrentNode;
|
||||||
|
if (currentNode == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RunNodeExecutionContext
|
||||||
|
{
|
||||||
|
RunId = RunId,
|
||||||
|
RunSeed = RunSeed,
|
||||||
|
NodeId = currentNode.NodeId,
|
||||||
|
NodeType = currentNode.NodeType,
|
||||||
|
SequenceIndex = currentNode.SequenceIndex,
|
||||||
|
ThemeType = ThemeType,
|
||||||
|
ThemeStageIndex = ThemeStageIndex,
|
||||||
|
CurrentThemePool = RunStateCloneUtility.CloneThemeList(CurrentThemePool),
|
||||||
|
ThemeHistory = RunStateCloneUtility.CloneThemeList(ThemeHistory),
|
||||||
|
CurrentNodeContinueChallengeLayer = CurrentNodeContinueChallengeLayer,
|
||||||
|
RunItems = RunStateCloneUtility.CloneRunItems(RunItems)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyCompletionSnapshot(RunNodeCompletionSnapshot completionSnapshot)
|
||||||
|
{
|
||||||
|
RunInventorySnapshot = InventoryCloneUtility.CloneInventory(completionSnapshot.InventorySnapshot);
|
||||||
|
ThemeType = completionSnapshot.ThemeType;
|
||||||
|
ThemeStageIndex = completionSnapshot.ThemeStageIndex;
|
||||||
|
CurrentThemePool = RunStateCloneUtility.CloneThemeList(completionSnapshot.CurrentThemePool);
|
||||||
|
ThemeHistory = RunStateCloneUtility.CloneThemeList(completionSnapshot.ThemeHistory);
|
||||||
|
CurrentNodeContinueChallengeLayer = completionSnapshot.CurrentNodeContinueChallengeLayer;
|
||||||
|
RunItems = RunStateCloneUtility.CloneRunItems(completionSnapshot.RunItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<LevelThemeType> CreateDefaultThemeList(LevelThemeType themeType)
|
||||||
|
{
|
||||||
|
List<LevelThemeType> themes = new List<LevelThemeType>();
|
||||||
|
if (themeType != LevelThemeType.None)
|
||||||
|
{
|
||||||
|
themes.Add(themeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return themes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class RunStateCloneUtility
|
||||||
|
{
|
||||||
|
internal static List<LevelThemeType> CloneThemeList(IReadOnlyList<LevelThemeType> source)
|
||||||
|
{
|
||||||
|
List<LevelThemeType> cloned = new List<LevelThemeType>();
|
||||||
|
if (source == null)
|
||||||
|
{
|
||||||
|
return cloned;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < source.Count; i++)
|
||||||
|
{
|
||||||
|
cloned.Add(source[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cloned;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static List<RunItemState> CloneRunItems(IReadOnlyList<RunItemState> source)
|
||||||
|
{
|
||||||
|
List<RunItemState> cloned = new List<RunItemState>();
|
||||||
|
if (source == null)
|
||||||
|
{
|
||||||
|
return cloned;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < source.Count; i++)
|
||||||
|
{
|
||||||
|
RunItemState item = source[i];
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
cloned.Add(item.Clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cloned;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using GeometryTD.Definition;
|
|
||||||
|
|
||||||
namespace GeometryTD.Procedure
|
namespace GeometryTD.Procedure
|
||||||
{
|
{
|
||||||
public static class RunStateAdvanceService
|
public static class RunStateAdvanceService
|
||||||
@@ -7,7 +5,7 @@ namespace GeometryTD.Procedure
|
|||||||
public static bool TryCompleteCurrentNode(
|
public static bool TryCompleteCurrentNode(
|
||||||
RunState runState,
|
RunState runState,
|
||||||
RunNodeCompletionStatus completionStatus,
|
RunNodeCompletionStatus completionStatus,
|
||||||
BackpackInventoryData inventorySnapshotAfterNode)
|
RunNodeCompletionSnapshot completionSnapshot)
|
||||||
{
|
{
|
||||||
if (runState == null || runState.IsCompleted)
|
if (runState == null || runState.IsCompleted)
|
||||||
{
|
{
|
||||||
@@ -20,7 +18,7 @@ namespace GeometryTD.Procedure
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
runState.ReplaceInventorySnapshot(inventorySnapshotAfterNode);
|
runState.ApplyCompletionSnapshot(completionSnapshot);
|
||||||
|
|
||||||
if (completionStatus == RunNodeCompletionStatus.Exception)
|
if (completionStatus == RunNodeCompletionStatus.Exception)
|
||||||
{
|
{
|
||||||
@@ -38,12 +36,14 @@ namespace GeometryTD.Procedure
|
|||||||
int nextIndex = runState.CurrentNodeIndex + 1;
|
int nextIndex = runState.CurrentNodeIndex + 1;
|
||||||
if (nextIndex >= runState.Nodes.Count)
|
if (nextIndex >= runState.Nodes.Count)
|
||||||
{
|
{
|
||||||
|
runState.CurrentNodeContinueChallengeLayer = 0;
|
||||||
runState.CurrentNodeIndex = runState.Nodes.Count;
|
runState.CurrentNodeIndex = runState.Nodes.Count;
|
||||||
runState.IsCompleted = true;
|
runState.IsCompleted = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
runState.CurrentNodeIndex = nextIndex;
|
runState.CurrentNodeIndex = nextIndex;
|
||||||
|
runState.CurrentNodeContinueChallengeLayer = 0;
|
||||||
RunNodeState nextNode = runState.CurrentNode;
|
RunNodeState nextNode = runState.CurrentNode;
|
||||||
if (nextNode != null && nextNode.Status == RunNodeStatus.Locked)
|
if (nextNode != null && nextNode.Status == RunNodeStatus.Locked)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,5 +4,7 @@ namespace GeometryTD.UI
|
|||||||
{
|
{
|
||||||
public int OptionIndex;
|
public int OptionIndex;
|
||||||
public string OptionText;
|
public string OptionText;
|
||||||
|
public bool IsSelectable;
|
||||||
|
public string BlockedReason;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
using GeometryTD.UI;
|
|
||||||
|
|
||||||
namespace GeometryTD.UI
|
namespace GeometryTD.UI
|
||||||
{
|
{
|
||||||
public class RepoFormContext : UIContext
|
public class RepoFormContext : UIContext
|
||||||
{
|
{
|
||||||
|
public string GoldText;
|
||||||
|
public RepoFormState State;
|
||||||
|
public bool ShowSellModeButton;
|
||||||
|
public string SellModeButtonText;
|
||||||
|
public bool ShowCombineArea;
|
||||||
|
public bool ShowSellArea;
|
||||||
public CombineAreaContext CombineAreaContext;
|
public CombineAreaContext CombineAreaContext;
|
||||||
|
public SellAreaContext SellAreaContext;
|
||||||
public CompAreaContext CompAreaContext;
|
public CompAreaContext CompAreaContext;
|
||||||
public ParticipantAreaContext ParticipantAreaContext;
|
public ParticipantAreaContext ParticipantAreaContext;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace GeometryTD.UI
|
||||||
|
{
|
||||||
|
public enum RepoFormState
|
||||||
|
{
|
||||||
|
Assemble = 0,
|
||||||
|
Sell = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9110fc764f3044bda40df4a8c02f320b
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -11,6 +11,9 @@ namespace GeometryTD.UI
|
|||||||
public float EnduranceRate01;
|
public float EnduranceRate01;
|
||||||
public RepoItemClickActionType ClickActionType;
|
public RepoItemClickActionType ClickActionType;
|
||||||
public TowerCompSlotType ComponentSlotType;
|
public TowerCompSlotType ComponentSlotType;
|
||||||
|
public bool IsSellMode;
|
||||||
|
public bool IsSellable = true;
|
||||||
|
public bool IsSellSelected;
|
||||||
public IconAreaContext IconAreaContext;
|
public IconAreaContext IconAreaContext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace GeometryTD.UI
|
||||||
|
{
|
||||||
|
public class SellAreaContext
|
||||||
|
{
|
||||||
|
public string TotalPriceText;
|
||||||
|
public bool CanConfirmSell;
|
||||||
|
public RepoItemContext[] ComponentItems;
|
||||||
|
public TowerRepoItemContext[] TowerItems;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f153d1a509df4bb69c6d231df5b60db9
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+138
-37
@@ -23,21 +23,44 @@ namespace GeometryTD.UI
|
|||||||
Dictionary<long, BearingCompItemData> bearingMap = BuildComponentMap(rawData.Inventory.BearingComponents);
|
Dictionary<long, BearingCompItemData> bearingMap = BuildComponentMap(rawData.Inventory.BearingComponents);
|
||||||
Dictionary<long, BaseCompItemData> baseMap = BuildComponentMap(rawData.Inventory.BaseComponents);
|
Dictionary<long, BaseCompItemData> baseMap = BuildComponentMap(rawData.Inventory.BaseComponents);
|
||||||
Dictionary<long, TowerItemData> towerMap = BuildTowerMap(rawData.Inventory.Towers);
|
Dictionary<long, TowerItemData> towerMap = BuildTowerMap(rawData.Inventory.Towers);
|
||||||
|
HashSet<long> selectedSellItemIds = BuildSelectedSellItemSet(rawData.SelectedSellItemIds);
|
||||||
|
bool isSellState = rawData.State == RepoFormState.Sell;
|
||||||
|
|
||||||
List<RepoItemContext> componentItems = new List<RepoItemContext>();
|
List<RepoItemContext> componentItems = new List<RepoItemContext>();
|
||||||
List<TowerRepoItemContext> towerItems = new List<TowerRepoItemContext>();
|
List<TowerRepoItemContext> towerItems = new List<TowerRepoItemContext>();
|
||||||
|
List<RepoItemContext> selectedSellComponentItems = new List<RepoItemContext>();
|
||||||
|
List<TowerRepoItemContext> selectedSellTowerItems = new List<TowerRepoItemContext>();
|
||||||
|
|
||||||
if (rawData.Inventory.Towers != null)
|
if (rawData.Inventory.Towers != null)
|
||||||
{
|
{
|
||||||
foreach (var tower in rawData.Inventory.Towers)
|
foreach (TowerItemData tower in rawData.Inventory.Towers)
|
||||||
{
|
{
|
||||||
if (tower == null)
|
if (tower == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
TowerRepoItemContext towerContext = BuildTowerRepoItemContext(tower, muzzleMap, bearingMap, baseMap,
|
bool isParticipantTower = rawData.Inventory.ParticipantTowerInstanceIds != null &&
|
||||||
RepoItemClickActionType.OpenDetail, true);
|
rawData.Inventory.ParticipantTowerInstanceIds.Contains(tower.InstanceId);
|
||||||
|
bool isSellSelected = isSellState && selectedSellItemIds.Contains(tower.InstanceId);
|
||||||
|
|
||||||
|
TowerRepoItemContext towerContext = BuildTowerRepoItemContext(
|
||||||
|
tower,
|
||||||
|
muzzleMap,
|
||||||
|
bearingMap,
|
||||||
|
baseMap,
|
||||||
|
RepoItemClickActionType.OpenDetail,
|
||||||
|
!isSellState,
|
||||||
|
isSellState,
|
||||||
|
!isParticipantTower,
|
||||||
|
isSellSelected,
|
||||||
|
false);
|
||||||
AddTowerItemContext(towerItems, towerContext);
|
AddTowerItemContext(towerItems, towerContext);
|
||||||
|
if (isSellSelected)
|
||||||
|
{
|
||||||
|
selectedSellTowerItems.Add(towerContext);
|
||||||
|
}
|
||||||
|
|
||||||
AddItemDescSeed(
|
AddItemDescSeed(
|
||||||
tower.InstanceId,
|
tower.InstanceId,
|
||||||
tower.Name,
|
tower.Name,
|
||||||
@@ -50,23 +73,24 @@ namespace GeometryTD.UI
|
|||||||
|
|
||||||
if (rawData.Inventory.MuzzleComponents != null)
|
if (rawData.Inventory.MuzzleComponents != null)
|
||||||
{
|
{
|
||||||
foreach (var item in rawData.Inventory.MuzzleComponents)
|
foreach (MuzzleCompItemData item in rawData.Inventory.MuzzleComponents)
|
||||||
{
|
{
|
||||||
if (item == null || item.IsAssembledIntoTower)
|
if (item == null || item.IsAssembledIntoTower)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
RepoItemContext componentContext = new RepoItemContext
|
RepoItemContext componentContext = BuildSellableComponentContext(
|
||||||
{
|
item,
|
||||||
InstanceId = item.InstanceId,
|
TowerCompSlotType.Muzzle,
|
||||||
CanDrag = true,
|
isSellState,
|
||||||
EnduranceRate01 = ItemDescUtility.ResolveComponentEnduranceRate(item),
|
selectedSellItemIds.Contains(item.InstanceId));
|
||||||
ClickActionType = RepoItemClickActionType.OpenDetail,
|
|
||||||
ComponentSlotType = TowerCompSlotType.Muzzle,
|
|
||||||
IconAreaContext = BuildIconAreaContext(item)
|
|
||||||
};
|
|
||||||
AddComponentItemContext(componentItems, componentContext);
|
AddComponentItemContext(componentItems, componentContext);
|
||||||
|
if (componentContext.IsSellSelected)
|
||||||
|
{
|
||||||
|
selectedSellComponentItems.Add(componentContext);
|
||||||
|
}
|
||||||
|
|
||||||
AddItemDescSeed(
|
AddItemDescSeed(
|
||||||
item.InstanceId,
|
item.InstanceId,
|
||||||
item.Name,
|
item.Name,
|
||||||
@@ -86,16 +110,17 @@ namespace GeometryTD.UI
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
RepoItemContext componentContext = new RepoItemContext
|
RepoItemContext componentContext = BuildSellableComponentContext(
|
||||||
{
|
item,
|
||||||
InstanceId = item.InstanceId,
|
TowerCompSlotType.Bearing,
|
||||||
CanDrag = true,
|
isSellState,
|
||||||
EnduranceRate01 = ItemDescUtility.ResolveComponentEnduranceRate(item),
|
selectedSellItemIds.Contains(item.InstanceId));
|
||||||
ClickActionType = RepoItemClickActionType.OpenDetail,
|
|
||||||
ComponentSlotType = TowerCompSlotType.Bearing,
|
|
||||||
IconAreaContext = BuildIconAreaContext(item)
|
|
||||||
};
|
|
||||||
AddComponentItemContext(componentItems, componentContext);
|
AddComponentItemContext(componentItems, componentContext);
|
||||||
|
if (componentContext.IsSellSelected)
|
||||||
|
{
|
||||||
|
selectedSellComponentItems.Add(componentContext);
|
||||||
|
}
|
||||||
|
|
||||||
AddItemDescSeed(
|
AddItemDescSeed(
|
||||||
item.InstanceId,
|
item.InstanceId,
|
||||||
item.Name,
|
item.Name,
|
||||||
@@ -115,16 +140,17 @@ namespace GeometryTD.UI
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
RepoItemContext componentContext = new RepoItemContext
|
RepoItemContext componentContext = BuildSellableComponentContext(
|
||||||
{
|
item,
|
||||||
InstanceId = item.InstanceId,
|
TowerCompSlotType.Base,
|
||||||
CanDrag = true,
|
isSellState,
|
||||||
EnduranceRate01 = ItemDescUtility.ResolveComponentEnduranceRate(item),
|
selectedSellItemIds.Contains(item.InstanceId));
|
||||||
ClickActionType = RepoItemClickActionType.OpenDetail,
|
|
||||||
ComponentSlotType = TowerCompSlotType.Base,
|
|
||||||
IconAreaContext = BuildIconAreaContext(item)
|
|
||||||
};
|
|
||||||
AddComponentItemContext(componentItems, componentContext);
|
AddComponentItemContext(componentItems, componentContext);
|
||||||
|
if (componentContext.IsSellSelected)
|
||||||
|
{
|
||||||
|
selectedSellComponentItems.Add(componentContext);
|
||||||
|
}
|
||||||
|
|
||||||
AddItemDescSeed(
|
AddItemDescSeed(
|
||||||
item.InstanceId,
|
item.InstanceId,
|
||||||
item.Name,
|
item.Name,
|
||||||
@@ -140,7 +166,20 @@ namespace GeometryTD.UI
|
|||||||
|
|
||||||
return new RepoFormContext
|
return new RepoFormContext
|
||||||
{
|
{
|
||||||
|
GoldText = $"金币: {rawData.Inventory.Gold}",
|
||||||
|
State = rawData.State,
|
||||||
|
ShowSellModeButton = rawData.State == RepoFormState.Assemble,
|
||||||
|
SellModeButtonText = "出售模式",
|
||||||
|
ShowCombineArea = rawData.State == RepoFormState.Assemble,
|
||||||
|
ShowSellArea = rawData.State == RepoFormState.Sell,
|
||||||
CombineAreaContext = new CombineAreaContext(),
|
CombineAreaContext = new CombineAreaContext(),
|
||||||
|
SellAreaContext = new SellAreaContext
|
||||||
|
{
|
||||||
|
TotalPriceText = $"总价值: {rawData.SelectedSellTotalPrice}",
|
||||||
|
CanConfirmSell = rawData.State == RepoFormState.Sell && rawData.SelectedSellItemCount > 0,
|
||||||
|
ComponentItems = selectedSellComponentItems.ToArray(),
|
||||||
|
TowerItems = selectedSellTowerItems.ToArray()
|
||||||
|
},
|
||||||
CompAreaContext = new CompAreaContext
|
CompAreaContext = new CompAreaContext
|
||||||
{
|
{
|
||||||
ComponentItems = componentItems.ToArray(),
|
ComponentItems = componentItems.ToArray(),
|
||||||
@@ -150,6 +189,26 @@ namespace GeometryTD.UI
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static RepoItemContext BuildSellableComponentContext(
|
||||||
|
TowerCompItemData item,
|
||||||
|
TowerCompSlotType slotType,
|
||||||
|
bool isSellState,
|
||||||
|
bool isSellSelected)
|
||||||
|
{
|
||||||
|
return new RepoItemContext
|
||||||
|
{
|
||||||
|
InstanceId = item.InstanceId,
|
||||||
|
CanDrag = !isSellState,
|
||||||
|
EnduranceRate01 = ItemDescUtility.ResolveComponentEnduranceRate(item),
|
||||||
|
ClickActionType = RepoItemClickActionType.OpenDetail,
|
||||||
|
ComponentSlotType = slotType,
|
||||||
|
IsSellMode = isSellState,
|
||||||
|
IsSellable = true,
|
||||||
|
IsSellSelected = isSellState && isSellSelected,
|
||||||
|
IconAreaContext = BuildIconAreaContext(item)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private void AddComponentItemContext(List<RepoItemContext> items, RepoItemContext itemContext)
|
private void AddComponentItemContext(List<RepoItemContext> items, RepoItemContext itemContext)
|
||||||
{
|
{
|
||||||
if (itemContext == null)
|
if (itemContext == null)
|
||||||
@@ -217,7 +276,7 @@ namespace GeometryTD.UI
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var item in items)
|
foreach (TComp item in items)
|
||||||
{
|
{
|
||||||
if (item == null || item.InstanceId <= 0)
|
if (item == null || item.InstanceId <= 0)
|
||||||
{
|
{
|
||||||
@@ -230,6 +289,26 @@ namespace GeometryTD.UI
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static HashSet<long> BuildSelectedSellItemSet(IReadOnlyList<long> selectedSellItemIds)
|
||||||
|
{
|
||||||
|
HashSet<long> selectedIds = new HashSet<long>();
|
||||||
|
if (selectedSellItemIds == null)
|
||||||
|
{
|
||||||
|
return selectedIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < selectedSellItemIds.Count; i++)
|
||||||
|
{
|
||||||
|
long itemId = selectedSellItemIds[i];
|
||||||
|
if (itemId > 0)
|
||||||
|
{
|
||||||
|
selectedIds.Add(itemId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedIds;
|
||||||
|
}
|
||||||
|
|
||||||
private static Dictionary<long, TowerItemData> BuildTowerMap(IReadOnlyList<TowerItemData> towers)
|
private static Dictionary<long, TowerItemData> BuildTowerMap(IReadOnlyList<TowerItemData> towers)
|
||||||
{
|
{
|
||||||
Dictionary<long, TowerItemData> map = new Dictionary<long, TowerItemData>();
|
Dictionary<long, TowerItemData> map = new Dictionary<long, TowerItemData>();
|
||||||
@@ -238,7 +317,7 @@ namespace GeometryTD.UI
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var tower in towers)
|
foreach (TowerItemData tower in towers)
|
||||||
{
|
{
|
||||||
if (tower == null || tower.InstanceId <= 0)
|
if (tower == null || tower.InstanceId <= 0)
|
||||||
{
|
{
|
||||||
@@ -262,7 +341,7 @@ namespace GeometryTD.UI
|
|||||||
List<TowerRepoItemContext> participantItems = new List<TowerRepoItemContext>();
|
List<TowerRepoItemContext> participantItems = new List<TowerRepoItemContext>();
|
||||||
if (inventory?.ParticipantTowerInstanceIds != null && towerMap != null)
|
if (inventory?.ParticipantTowerInstanceIds != null && towerMap != null)
|
||||||
{
|
{
|
||||||
foreach (var towerId in inventory.ParticipantTowerInstanceIds)
|
foreach (long towerId in inventory.ParticipantTowerInstanceIds)
|
||||||
{
|
{
|
||||||
if (towerId <= 0)
|
if (towerId <= 0)
|
||||||
{
|
{
|
||||||
@@ -279,8 +358,17 @@ namespace GeometryTD.UI
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
TowerRepoItemContext towerContext = BuildTowerRepoItemContext(tower, muzzleMap, bearingMap, baseMap,
|
TowerRepoItemContext towerContext = BuildTowerRepoItemContext(
|
||||||
RepoItemClickActionType.RemoveParticipant, false);
|
tower,
|
||||||
|
muzzleMap,
|
||||||
|
bearingMap,
|
||||||
|
baseMap,
|
||||||
|
RepoItemClickActionType.RemoveParticipant,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false);
|
||||||
if (towerContext != null)
|
if (towerContext != null)
|
||||||
{
|
{
|
||||||
participantItems.Add(towerContext);
|
participantItems.Add(towerContext);
|
||||||
@@ -303,6 +391,11 @@ namespace GeometryTD.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Context != null && Context.State == RepoFormState.Sell)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (long towerId in _compAreaTowerIds)
|
foreach (long towerId in _compAreaTowerIds)
|
||||||
{
|
{
|
||||||
Form.SetRepoItemSelected(towerId, _participantTowerIds.Contains(towerId));
|
Form.SetRepoItemSelected(towerId, _participantTowerIds.Contains(towerId));
|
||||||
@@ -322,6 +415,7 @@ namespace GeometryTD.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Form.RefreshGoldText(latestContext.GoldText);
|
||||||
Form.RefreshParticipantArea(latestContext.ParticipantAreaContext);
|
Form.RefreshParticipantArea(latestContext.ParticipantAreaContext);
|
||||||
ApplyParticipantSelection();
|
ApplyParticipantSelection();
|
||||||
}
|
}
|
||||||
@@ -332,7 +426,11 @@ namespace GeometryTD.UI
|
|||||||
IReadOnlyDictionary<long, BearingCompItemData> bearingMap,
|
IReadOnlyDictionary<long, BearingCompItemData> bearingMap,
|
||||||
IReadOnlyDictionary<long, BaseCompItemData> baseMap,
|
IReadOnlyDictionary<long, BaseCompItemData> baseMap,
|
||||||
RepoItemClickActionType clickActionType,
|
RepoItemClickActionType clickActionType,
|
||||||
bool canDrag)
|
bool canDrag,
|
||||||
|
bool isSellMode,
|
||||||
|
bool isSellable,
|
||||||
|
bool isSellSelected,
|
||||||
|
bool highlightSelected)
|
||||||
{
|
{
|
||||||
if (tower == null)
|
if (tower == null)
|
||||||
{
|
{
|
||||||
@@ -346,6 +444,9 @@ namespace GeometryTD.UI
|
|||||||
EnduranceRate01 = ItemDescUtility.ResolveTowerEnduranceRate(tower, muzzleMap, bearingMap, baseMap),
|
EnduranceRate01 = ItemDescUtility.ResolveTowerEnduranceRate(tower, muzzleMap, bearingMap, baseMap),
|
||||||
ClickActionType = clickActionType,
|
ClickActionType = clickActionType,
|
||||||
ComponentSlotType = TowerCompSlotType.None,
|
ComponentSlotType = TowerCompSlotType.None,
|
||||||
|
IsSellMode = isSellMode,
|
||||||
|
IsSellable = isSellable,
|
||||||
|
IsSellSelected = isSellSelected || highlightSelected,
|
||||||
IconAreaContext = new TowerIconAreaContext
|
IconAreaContext = new TowerIconAreaContext
|
||||||
{
|
{
|
||||||
Rarity = tower.Rarity,
|
Rarity = tower.Rarity,
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ namespace GeometryTD.UI
|
|||||||
|
|
||||||
private static EventFormContext BuildContext(EventFormRawData rawData)
|
private static EventFormContext BuildContext(EventFormRawData rawData)
|
||||||
{
|
{
|
||||||
if (rawData?.EventItem == null)
|
if (rawData == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -82,24 +82,30 @@ namespace GeometryTD.UI
|
|||||||
for (int i = 0; i < options.Length; i++)
|
for (int i = 0; i < options.Length; i++)
|
||||||
{
|
{
|
||||||
string optionText = string.Empty;
|
string optionText = string.Empty;
|
||||||
if (rawData.EventItem.Options != null && i < rawData.EventItem.Options.Length &&
|
bool isSelectable = false;
|
||||||
rawData.EventItem.Options[i] != null)
|
string blockedReason = string.Empty;
|
||||||
|
if (rawData.OptionItems != null && i < rawData.OptionItems.Length &&
|
||||||
|
rawData.OptionItems[i] != null)
|
||||||
{
|
{
|
||||||
optionText = rawData.EventItem.Options[i].OptionText;
|
optionText = rawData.OptionItems[i].OptionText;
|
||||||
|
isSelectable = rawData.OptionItems[i].IsSelectable;
|
||||||
|
blockedReason = rawData.OptionItems[i].BlockedReason;
|
||||||
}
|
}
|
||||||
|
|
||||||
options[i] = new EventOptionItemContext
|
options[i] = new EventOptionItemContext
|
||||||
{
|
{
|
||||||
OptionIndex = i,
|
OptionIndex = i,
|
||||||
OptionText = optionText
|
OptionText = optionText,
|
||||||
|
IsSelectable = isSelectable,
|
||||||
|
BlockedReason = blockedReason
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return new EventFormContext
|
return new EventFormContext
|
||||||
{
|
{
|
||||||
EventId = rawData.EventItem.Id,
|
EventId = rawData.EventId,
|
||||||
Title = rawData.EventItem.Title,
|
Title = rawData.Title,
|
||||||
Description = rawData.EventItem.Description,
|
Description = rawData.Description,
|
||||||
OptionItems = options
|
OptionItems = options
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -117,7 +123,7 @@ namespace GeometryTD.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_UseCase.SelectOption(args.SelectedItemId);
|
m_UseCase.TrySelectOption(args.SelectedItemId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ using GeometryTD.CustomEvent;
|
|||||||
using GeometryTD.CustomUtility;
|
using GeometryTD.CustomUtility;
|
||||||
using GeometryTD.Definition;
|
using GeometryTD.Definition;
|
||||||
using GameFramework.Event;
|
using GameFramework.Event;
|
||||||
|
using GeometryTD.CustomComponent;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityGameFramework.Runtime;
|
using UnityGameFramework.Runtime;
|
||||||
|
|
||||||
@@ -43,6 +44,9 @@ namespace GeometryTD.UI
|
|||||||
GameEntry.Event.Subscribe(RepoCombineRequestedEventArgs.EventId, OnRepoCombineRequested);
|
GameEntry.Event.Subscribe(RepoCombineRequestedEventArgs.EventId, OnRepoCombineRequested);
|
||||||
GameEntry.Event.Subscribe(RepoParticipantAssignRequestedEventArgs.EventId, OnRepoParticipantAssignRequested);
|
GameEntry.Event.Subscribe(RepoParticipantAssignRequestedEventArgs.EventId, OnRepoParticipantAssignRequested);
|
||||||
GameEntry.Event.Subscribe(RepoFormReturnEventArgs.EventId, OnRepoFormReturn);
|
GameEntry.Event.Subscribe(RepoFormReturnEventArgs.EventId, OnRepoFormReturn);
|
||||||
|
GameEntry.Event.Subscribe(RepoSellModeToggleRequestedEventArgs.EventId, OnRepoSellModeToggleRequested);
|
||||||
|
GameEntry.Event.Subscribe(RepoSellCancelRequestedEventArgs.EventId, OnRepoSellCancelRequested);
|
||||||
|
GameEntry.Event.Subscribe(RepoSellConfirmRequestedEventArgs.EventId, OnRepoSellConfirmRequested);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UnsubscribeCustomEvents()
|
protected override void UnsubscribeCustomEvents()
|
||||||
@@ -53,6 +57,9 @@ namespace GeometryTD.UI
|
|||||||
GameEntry.Event.Unsubscribe(RepoCombineRequestedEventArgs.EventId, OnRepoCombineRequested);
|
GameEntry.Event.Unsubscribe(RepoCombineRequestedEventArgs.EventId, OnRepoCombineRequested);
|
||||||
GameEntry.Event.Unsubscribe(RepoParticipantAssignRequestedEventArgs.EventId, OnRepoParticipantAssignRequested);
|
GameEntry.Event.Unsubscribe(RepoParticipantAssignRequestedEventArgs.EventId, OnRepoParticipantAssignRequested);
|
||||||
GameEntry.Event.Unsubscribe(RepoFormReturnEventArgs.EventId, OnRepoFormReturn);
|
GameEntry.Event.Unsubscribe(RepoFormReturnEventArgs.EventId, OnRepoFormReturn);
|
||||||
|
GameEntry.Event.Unsubscribe(RepoSellModeToggleRequestedEventArgs.EventId, OnRepoSellModeToggleRequested);
|
||||||
|
GameEntry.Event.Unsubscribe(RepoSellCancelRequestedEventArgs.EventId, OnRepoSellCancelRequested);
|
||||||
|
GameEntry.Event.Unsubscribe(RepoSellConfirmRequestedEventArgs.EventId, OnRepoSellConfirmRequested);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int? OpenUI(object userData = null)
|
public override int? OpenUI(object userData = null)
|
||||||
@@ -125,6 +132,24 @@ namespace GeometryTD.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_useCase != null && _useCase.State == RepoFormState.Sell)
|
||||||
|
{
|
||||||
|
if (_useCase.TryToggleSellSelection(
|
||||||
|
args.ItemId,
|
||||||
|
out RepoFormRawData sellModeRawData,
|
||||||
|
out PlayerInventorySaleFailureReason failureReason))
|
||||||
|
{
|
||||||
|
SetContext(BuildContext(sellModeRawData));
|
||||||
|
RefreshCurrentUI();
|
||||||
|
}
|
||||||
|
else if (failureReason == PlayerInventorySaleFailureReason.ParticipantTower)
|
||||||
|
{
|
||||||
|
OpenSellBlockedDialog("参战防御塔不能直接出售,请先移出参战区。");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (clickActionType == RepoItemClickActionType.RemoveParticipant)
|
if (clickActionType == RepoItemClickActionType.RemoveParticipant)
|
||||||
{
|
{
|
||||||
if (_useCase == null || Form == null)
|
if (_useCase == null || Form == null)
|
||||||
@@ -165,6 +190,11 @@ namespace GeometryTD.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Context != null && Context.State == RepoFormState.Sell)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(e is RepoItemDragEndedEventArgs args))
|
if (!(e is RepoItemDragEndedEventArgs args))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -188,6 +218,11 @@ namespace GeometryTD.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Context != null && Context.State == RepoFormState.Sell)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(e is CombineSlotClickedEventArgs args))
|
if (!(e is CombineSlotClickedEventArgs args))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -245,8 +280,8 @@ namespace GeometryTD.UI
|
|||||||
RepoFormContext latestContext = BuildContext(latestRawData);
|
RepoFormContext latestContext = BuildContext(latestRawData);
|
||||||
if (latestContext != null)
|
if (latestContext != null)
|
||||||
{
|
{
|
||||||
Form.RefreshUI(latestContext);
|
SetContext(latestContext);
|
||||||
ApplyParticipantSelection();
|
RefreshCurrentUI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,6 +292,11 @@ namespace GeometryTD.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Context != null && Context.State == RepoFormState.Sell)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(e is RepoParticipantAssignRequestedEventArgs args))
|
if (!(e is RepoParticipantAssignRequestedEventArgs args))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -288,6 +328,65 @@ namespace GeometryTD.UI
|
|||||||
RefreshParticipantAreaOnly();
|
RefreshParticipantAreaOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnRepoSellModeToggleRequested(object sender, GameEventArgs e)
|
||||||
|
{
|
||||||
|
if (!IsEventFromCurrentForm(sender) || !(e is RepoSellModeToggleRequestedEventArgs) || _useCase == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_useCase.State != RepoFormState.Assemble)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RepoFormRawData rawData = _useCase.EnterSellState();
|
||||||
|
SetContext(BuildContext(rawData));
|
||||||
|
RefreshCurrentUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRepoSellCancelRequested(object sender, GameEventArgs e)
|
||||||
|
{
|
||||||
|
if (!IsEventFromCurrentForm(sender) || !(e is RepoSellCancelRequestedEventArgs) || _useCase == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_useCase.State != RepoFormState.Sell)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetContext(BuildContext(_useCase.ExitSellState()));
|
||||||
|
RefreshCurrentUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRepoSellConfirmRequested(object sender, GameEventArgs e)
|
||||||
|
{
|
||||||
|
if (!IsEventFromCurrentForm(sender) || !(e is RepoSellConfirmRequestedEventArgs) || _useCase == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_useCase.TryConfirmSellSelection(out RepoFormRawData rawData, out PlayerInventorySaleResult result))
|
||||||
|
{
|
||||||
|
if (result != null && result.FailureReason == PlayerInventorySaleFailureReason.ParticipantTower)
|
||||||
|
{
|
||||||
|
OpenSellBlockedDialog("参战防御塔不能直接出售,请先移出参战区。");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetContext(BuildContext(rawData));
|
||||||
|
RefreshCurrentUI();
|
||||||
|
|
||||||
|
if (GameEntry.UI.GetUIForm(UIFormType.ShopForm) != null)
|
||||||
|
{
|
||||||
|
GameEntry.UIRouter.OpenUI(UIFormType.ShopForm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool IsEventFromCurrentForm(object sender)
|
private bool IsEventFromCurrentForm(object sender)
|
||||||
{
|
{
|
||||||
if (Form == null)
|
if (Form == null)
|
||||||
@@ -309,6 +408,18 @@ namespace GeometryTD.UI
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void OpenSellBlockedDialog(string message)
|
||||||
|
{
|
||||||
|
GameEntry.UIRouter.OpenUI(UIFormType.DialogForm, new DialogFormRawData
|
||||||
|
{
|
||||||
|
Mode = 1,
|
||||||
|
Title = "无法出售",
|
||||||
|
Message = message,
|
||||||
|
PauseGame = false,
|
||||||
|
ConfirmText = "知道了"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
using GeometryTD.Definition;
|
|
||||||
|
|
||||||
namespace GeometryTD.UI
|
namespace GeometryTD.UI
|
||||||
{
|
{
|
||||||
public class EventFormRawData
|
public class EventFormRawData
|
||||||
{
|
{
|
||||||
public EventItem EventItem;
|
public int EventId;
|
||||||
|
public string Title;
|
||||||
|
public string Description;
|
||||||
|
public EventOptionRawData[] OptionItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class EventOptionRawData
|
||||||
|
{
|
||||||
|
public int OptionIndex;
|
||||||
|
public string OptionText;
|
||||||
|
public bool IsSelectable;
|
||||||
|
public string BlockedReason;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,9 @@ namespace GeometryTD.UI
|
|||||||
public class RepoFormRawData
|
public class RepoFormRawData
|
||||||
{
|
{
|
||||||
public BackpackInventoryData Inventory;
|
public BackpackInventoryData Inventory;
|
||||||
|
public RepoFormState State;
|
||||||
|
public long[] SelectedSellItemIds;
|
||||||
|
public int SelectedSellItemCount;
|
||||||
|
public int SelectedSellTotalPrice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using GeometryTD.CustomComponent;
|
|
||||||
using GeometryTD.CustomEvent;
|
|
||||||
using GeometryTD.Definition;
|
using GeometryTD.Definition;
|
||||||
|
using GeometryTD.Procedure;
|
||||||
using UnityGameFramework.Runtime;
|
using UnityGameFramework.Runtime;
|
||||||
|
|
||||||
namespace GeometryTD.UI
|
namespace GeometryTD.UI
|
||||||
@@ -8,10 +7,21 @@ namespace GeometryTD.UI
|
|||||||
public class EventFormUseCase : IUIUseCase
|
public class EventFormUseCase : IUIUseCase
|
||||||
{
|
{
|
||||||
private EventItem _currentEvent;
|
private EventItem _currentEvent;
|
||||||
|
private RunNodeExecutionContext _currentContext;
|
||||||
|
private readonly EventOptionExecutor _executor = new EventOptionExecutor();
|
||||||
|
|
||||||
public void SetCurrentEvent(EventItem eventItem)
|
public void BindEvent(
|
||||||
|
EventItem eventItem,
|
||||||
|
RunNodeExecutionContext context)
|
||||||
{
|
{
|
||||||
_currentEvent = eventItem;
|
_currentEvent = eventItem;
|
||||||
|
_currentContext = context != null ? context.Clone() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_currentEvent = null;
|
||||||
|
_currentContext = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EventFormRawData CreateInitialModel()
|
public EventFormRawData CreateInitialModel()
|
||||||
@@ -23,26 +33,74 @@ namespace GeometryTD.UI
|
|||||||
|
|
||||||
return new EventFormRawData
|
return new EventFormRawData
|
||||||
{
|
{
|
||||||
EventItem = _currentEvent
|
EventId = _currentEvent.Id,
|
||||||
|
Title = _currentEvent.Title,
|
||||||
|
Description = _currentEvent.Description,
|
||||||
|
OptionItems = BuildOptionItems()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public EventOption SelectOption(int optionIndex)
|
public bool TrySelectOption(int optionIndex)
|
||||||
{
|
{
|
||||||
if (_currentEvent == null || _currentEvent.Options == null)
|
if (_currentEvent == null || _currentEvent.Options == null)
|
||||||
{
|
{
|
||||||
return null;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (optionIndex < 0 || optionIndex >= _currentEvent.Options.Length)
|
if (optionIndex < 0 || optionIndex >= _currentEvent.Options.Length)
|
||||||
{
|
{
|
||||||
Log.Warning("EventFormUseCase.SelectOption() option index is invalid: {0}", optionIndex);
|
Log.Warning("EventFormUseCase.TrySelectOption() option index is invalid: {0}", optionIndex);
|
||||||
return null;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BackpackInventoryData workingInventory = GameEntry.PlayerInventory != null
|
||||||
|
? GameEntry.PlayerInventory.GetInventorySnapshot()
|
||||||
|
: new BackpackInventoryData();
|
||||||
|
EventOptionExecutionResult executionResult = _executor.Execute(
|
||||||
|
_currentEvent,
|
||||||
|
optionIndex,
|
||||||
|
_currentContext,
|
||||||
|
workingInventory);
|
||||||
|
if (!executionResult.IsAccepted)
|
||||||
|
{
|
||||||
|
Log.Warning(
|
||||||
|
"EventFormUseCase.TrySelectOption() rejected. EventId={0}, OptionIndex={1}, Reason={2}",
|
||||||
|
_currentEvent.Id,
|
||||||
|
optionIndex,
|
||||||
|
executionResult.FailureReason);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GameEntry.PlayerInventory != null)
|
||||||
|
{
|
||||||
|
GameEntry.PlayerInventory.ReplaceInventorySnapshot(workingInventory);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 执行 Requirements 校验、CostEffects/RewardEffects 结算与后续节点推进。
|
|
||||||
GameEntry.EventNode.EndEvent();
|
GameEntry.EventNode.EndEvent();
|
||||||
return _currentEvent.Options[optionIndex];
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EventOptionRawData[] BuildOptionItems()
|
||||||
|
{
|
||||||
|
EventOption[] options = _currentEvent.Options ?? System.Array.Empty<EventOption>();
|
||||||
|
EventOptionRawData[] result = new EventOptionRawData[options.Length];
|
||||||
|
BackpackInventoryData inventory = GameEntry.PlayerInventory != null
|
||||||
|
? GameEntry.PlayerInventory.GetInventorySnapshot()
|
||||||
|
: new BackpackInventoryData();
|
||||||
|
for (int i = 0; i < options.Length; i++)
|
||||||
|
{
|
||||||
|
EventOption option = options[i];
|
||||||
|
EventOptionAvailability availability = _executor.EvaluateOption(option, inventory);
|
||||||
|
result[i] = new EventOptionRawData
|
||||||
|
{
|
||||||
|
OptionIndex = i,
|
||||||
|
OptionText = option?.OptionText ?? string.Empty,
|
||||||
|
IsSelectable = availability.IsSelectable,
|
||||||
|
BlockedReason = availability.BlockedReason
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
using GeometryTD.CustomUtility;
|
using System.Collections.Generic;
|
||||||
|
using GeometryTD.CustomComponent;
|
||||||
|
using GeometryTD.CustomUtility;
|
||||||
using GeometryTD.Definition;
|
using GeometryTD.Definition;
|
||||||
|
|
||||||
namespace GeometryTD.UI
|
namespace GeometryTD.UI
|
||||||
@@ -7,6 +9,8 @@ namespace GeometryTD.UI
|
|||||||
{
|
{
|
||||||
private const int MaxParticipantCount = 4;
|
private const int MaxParticipantCount = 4;
|
||||||
private BackpackInventoryData _fallbackInventory;
|
private BackpackInventoryData _fallbackInventory;
|
||||||
|
private readonly HashSet<long> _selectedSellItemIds = new HashSet<long>();
|
||||||
|
private RepoFormState _state = RepoFormState.Assemble;
|
||||||
|
|
||||||
public RepoFormRawData CreateInitialModel()
|
public RepoFormRawData CreateInitialModel()
|
||||||
{
|
{
|
||||||
@@ -15,12 +19,23 @@ namespace GeometryTD.UI
|
|||||||
: GetOrCreateFallbackInventory();
|
: GetOrCreateFallbackInventory();
|
||||||
return new RepoFormRawData
|
return new RepoFormRawData
|
||||||
{
|
{
|
||||||
Inventory = sample
|
Inventory = sample,
|
||||||
|
State = _state,
|
||||||
|
SelectedSellItemIds = BuildSelectedSellItemArray(),
|
||||||
|
SelectedSellItemCount = _selectedSellItemIds.Count,
|
||||||
|
SelectedSellTotalPrice = ResolveSelectedSellTotalPrice()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RepoFormState State => _state;
|
||||||
|
|
||||||
public bool TryAssembleTower(long muzzleItemId, long bearingItemId, long baseItemId)
|
public bool TryAssembleTower(long muzzleItemId, long bearingItemId, long baseItemId)
|
||||||
{
|
{
|
||||||
|
if (_state == RepoFormState.Sell)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (GameEntry.PlayerInventory == null)
|
if (GameEntry.PlayerInventory == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -35,6 +50,15 @@ namespace GeometryTD.UI
|
|||||||
|
|
||||||
public ParticipantTowerAssignResult TryAddParticipantTower(long towerItemId)
|
public ParticipantTowerAssignResult TryAddParticipantTower(long towerItemId)
|
||||||
{
|
{
|
||||||
|
if (_state == RepoFormState.Sell)
|
||||||
|
{
|
||||||
|
return new ParticipantTowerAssignResult
|
||||||
|
{
|
||||||
|
TowerInstanceId = towerItemId,
|
||||||
|
FailureReason = ParticipantTowerAssignFailureReason.ParticipantAreaFull
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (GameEntry.PlayerInventory == null)
|
if (GameEntry.PlayerInventory == null)
|
||||||
{
|
{
|
||||||
BackpackInventoryData fallbackInventory = GetOrCreateFallbackInventory();
|
BackpackInventoryData fallbackInventory = GetOrCreateFallbackInventory();
|
||||||
@@ -44,11 +68,16 @@ namespace GeometryTD.UI
|
|||||||
MaxParticipantCount);
|
MaxParticipantCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
return GameEntry.PlayerInventory.TryAddParticipantTower(towerItemId, 4);
|
return GameEntry.PlayerInventory.TryAddParticipantTower(towerItemId, MaxParticipantCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryRemoveParticipantTower(long towerItemId)
|
public bool TryRemoveParticipantTower(long towerItemId)
|
||||||
{
|
{
|
||||||
|
if (_state == RepoFormState.Sell)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (GameEntry.PlayerInventory == null)
|
if (GameEntry.PlayerInventory == null)
|
||||||
{
|
{
|
||||||
BackpackInventoryData fallbackInventory = GetOrCreateFallbackInventory();
|
BackpackInventoryData fallbackInventory = GetOrCreateFallbackInventory();
|
||||||
@@ -61,12 +90,120 @@ namespace GeometryTD.UI
|
|||||||
return GameEntry.PlayerInventory.TryRemoveParticipantTower(towerItemId);
|
return GameEntry.PlayerInventory.TryRemoveParticipantTower(towerItemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RepoFormRawData EnterSellState()
|
||||||
|
{
|
||||||
|
_state = RepoFormState.Sell;
|
||||||
|
_selectedSellItemIds.Clear();
|
||||||
|
return CreateInitialModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RepoFormRawData ExitSellState()
|
||||||
|
{
|
||||||
|
_state = RepoFormState.Assemble;
|
||||||
|
_selectedSellItemIds.Clear();
|
||||||
|
return CreateInitialModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryToggleSellSelection(
|
||||||
|
long itemId,
|
||||||
|
out RepoFormRawData rawData,
|
||||||
|
out PlayerInventorySaleFailureReason failureReason)
|
||||||
|
{
|
||||||
|
rawData = null;
|
||||||
|
failureReason = PlayerInventorySaleFailureReason.None;
|
||||||
|
if (_state != RepoFormState.Sell || itemId <= 0 || GameEntry.PlayerInventory == null)
|
||||||
|
{
|
||||||
|
failureReason = PlayerInventorySaleFailureReason.InvalidSelection;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_selectedSellItemIds.Contains(itemId))
|
||||||
|
{
|
||||||
|
_selectedSellItemIds.Remove(itemId);
|
||||||
|
rawData = CreateInitialModel();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!GameEntry.PlayerInventory.TryGetSaleCandidate(itemId, out PlayerInventorySaleCandidate candidate) ||
|
||||||
|
candidate == null)
|
||||||
|
{
|
||||||
|
failureReason = PlayerInventorySaleFailureReason.ItemNotFound;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!candidate.IsSellable)
|
||||||
|
{
|
||||||
|
failureReason = candidate.FailureReason;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_selectedSellItemIds.Add(itemId);
|
||||||
|
rawData = CreateInitialModel();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryConfirmSellSelection(out RepoFormRawData rawData, out PlayerInventorySaleResult result)
|
||||||
|
{
|
||||||
|
rawData = null;
|
||||||
|
result = null;
|
||||||
|
if (_state != RepoFormState.Sell || _selectedSellItemIds.Count <= 0 || GameEntry.PlayerInventory == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!GameEntry.PlayerInventory.TrySellItems(_selectedSellItemIds, out PlayerInventorySaleResult saleResult) ||
|
||||||
|
saleResult == null ||
|
||||||
|
!saleResult.IsSuccess)
|
||||||
|
{
|
||||||
|
result = saleResult;
|
||||||
|
rawData = CreateInitialModel();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_selectedSellItemIds.Clear();
|
||||||
|
result = saleResult;
|
||||||
|
rawData = CreateInitialModel();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private BackpackInventoryData GetOrCreateFallbackInventory()
|
private BackpackInventoryData GetOrCreateFallbackInventory()
|
||||||
{
|
{
|
||||||
_fallbackInventory ??= InventorySeedUtility.CreateSampleInventory();
|
_fallbackInventory ??= InventorySeedUtility.CreateSampleInventory();
|
||||||
InventoryParticipantUtility.NormalizeParticipantState(_fallbackInventory, MaxParticipantCount);
|
InventoryParticipantUtility.NormalizeParticipantState(_fallbackInventory, MaxParticipantCount);
|
||||||
return _fallbackInventory;
|
return _fallbackInventory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private long[] BuildSelectedSellItemArray()
|
||||||
|
{
|
||||||
|
if (_selectedSellItemIds.Count <= 0)
|
||||||
|
{
|
||||||
|
return System.Array.Empty<long>();
|
||||||
|
}
|
||||||
|
|
||||||
|
long[] itemIds = new long[_selectedSellItemIds.Count];
|
||||||
|
_selectedSellItemIds.CopyTo(itemIds);
|
||||||
|
return itemIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ResolveSelectedSellTotalPrice()
|
||||||
|
{
|
||||||
|
if (_state != RepoFormState.Sell || _selectedSellItemIds.Count <= 0 || GameEntry.PlayerInventory == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int total = 0;
|
||||||
|
foreach (long itemId in _selectedSellItemIds)
|
||||||
|
{
|
||||||
|
if (GameEntry.PlayerInventory.TryGetSaleCandidate(itemId, out PlayerInventorySaleCandidate candidate) &&
|
||||||
|
candidate != null &&
|
||||||
|
candidate.IsSellable)
|
||||||
|
{
|
||||||
|
total += candidate.Price;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ namespace GeometryTD.UI
|
|||||||
public class CombineArea : MonoBehaviour, IDropHandler
|
public class CombineArea : MonoBehaviour, IDropHandler
|
||||||
{
|
{
|
||||||
[SerializeField] private CombineSlotItem[] _slots;
|
[SerializeField] private CombineSlotItem[] _slots;
|
||||||
|
private bool _isInteractionEnabled = true;
|
||||||
|
|
||||||
public void OnInit(CombineAreaContext context)
|
public void OnInit(CombineAreaContext context)
|
||||||
{
|
{
|
||||||
@@ -43,6 +44,11 @@ namespace GeometryTD.UI
|
|||||||
|
|
||||||
public void OnCombineClick()
|
public void OnCombineClick()
|
||||||
{
|
{
|
||||||
|
if (!_isInteractionEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!TryGetBoundItemId(TowerCompSlotType.Muzzle, out long muzzleItemId) ||
|
if (!TryGetBoundItemId(TowerCompSlotType.Muzzle, out long muzzleItemId) ||
|
||||||
!TryGetBoundItemId(TowerCompSlotType.Bearing, out long bearingItemId) ||
|
!TryGetBoundItemId(TowerCompSlotType.Bearing, out long bearingItemId) ||
|
||||||
!TryGetBoundItemId(TowerCompSlotType.Base, out long baseItemId))
|
!TryGetBoundItemId(TowerCompSlotType.Base, out long baseItemId))
|
||||||
@@ -55,6 +61,11 @@ namespace GeometryTD.UI
|
|||||||
|
|
||||||
public bool TryAssignItem(RepoItemContext itemContext)
|
public bool TryAssignItem(RepoItemContext itemContext)
|
||||||
{
|
{
|
||||||
|
if (!_isInteractionEnabled)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (itemContext == null || itemContext.ComponentSlotType == TowerCompSlotType.None)
|
if (itemContext == null || itemContext.ComponentSlotType == TowerCompSlotType.None)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -91,6 +102,11 @@ namespace GeometryTD.UI
|
|||||||
|
|
||||||
public void OnDrop(PointerEventData eventData)
|
public void OnDrop(PointerEventData eventData)
|
||||||
{
|
{
|
||||||
|
if (!_isInteractionEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (eventData == null)
|
if (eventData == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -113,6 +129,11 @@ namespace GeometryTD.UI
|
|||||||
dragItem.SetDropResult(assigned);
|
dragItem.SetDropResult(assigned);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetInteractionEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
_isInteractionEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
private CombineSlotItem FindSlot(TowerCompSlotType slotType)
|
private CombineSlotItem FindSlot(TowerCompSlotType slotType)
|
||||||
{
|
{
|
||||||
if (_slots == null)
|
if (_slots == null)
|
||||||
|
|||||||
@@ -8,7 +8,17 @@ namespace GeometryTD.UI
|
|||||||
{
|
{
|
||||||
[SerializeField] private TMP_Text _optionText;
|
[SerializeField] private TMP_Text _optionText;
|
||||||
|
|
||||||
|
private static readonly Color SelectableTextColor = Color.white;
|
||||||
|
private static readonly Color BlockedTextColor = new Color(0.75f, 0.75f, 0.75f, 1f);
|
||||||
|
|
||||||
private int _optionIndex;
|
private int _optionIndex;
|
||||||
|
private bool _isSelectable;
|
||||||
|
private CommonButton _commonButton;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
_commonButton = GetComponent<CommonButton>();
|
||||||
|
}
|
||||||
|
|
||||||
public void OnInit(EventOptionItemContext context)
|
public void OnInit(EventOptionItemContext context)
|
||||||
{
|
{
|
||||||
@@ -20,9 +30,16 @@ namespace GeometryTD.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
_optionIndex = context.OptionIndex;
|
_optionIndex = context.OptionIndex;
|
||||||
|
_isSelectable = context.IsSelectable;
|
||||||
if (_optionText != null)
|
if (_optionText != null)
|
||||||
{
|
{
|
||||||
_optionText.text = context.OptionText ?? string.Empty;
|
_optionText.text = BuildDisplayText(context);
|
||||||
|
_optionText.color = _isSelectable ? SelectableTextColor : BlockedTextColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_commonButton != null)
|
||||||
|
{
|
||||||
|
_commonButton.Interactive = _isSelectable;
|
||||||
}
|
}
|
||||||
|
|
||||||
gameObject.SetActive(!string.IsNullOrEmpty(context.OptionText));
|
gameObject.SetActive(!string.IsNullOrEmpty(context.OptionText));
|
||||||
@@ -31,20 +48,38 @@ namespace GeometryTD.UI
|
|||||||
public void OnReset()
|
public void OnReset()
|
||||||
{
|
{
|
||||||
_optionIndex = -1;
|
_optionIndex = -1;
|
||||||
|
_isSelectable = false;
|
||||||
if (_optionText != null)
|
if (_optionText != null)
|
||||||
{
|
{
|
||||||
_optionText.text = string.Empty;
|
_optionText.text = string.Empty;
|
||||||
|
_optionText.color = SelectableTextColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_commonButton != null)
|
||||||
|
{
|
||||||
|
_commonButton.Interactive = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnClick()
|
public void OnClick()
|
||||||
{
|
{
|
||||||
if (_optionIndex < 0)
|
if (_optionIndex < 0 || !_isSelectable)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
GameEntry.Event.Fire(this, EventOptionItemSelectedEventArgs.Create(_optionIndex));
|
GameEntry.Event.Fire(this, EventOptionItemSelectedEventArgs.Create(_optionIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string BuildDisplayText(EventOptionItemContext context)
|
||||||
|
{
|
||||||
|
string optionText = context.OptionText ?? string.Empty;
|
||||||
|
if (context.IsSelectable || string.IsNullOrEmpty(context.BlockedReason))
|
||||||
|
{
|
||||||
|
return optionText;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"{optionText}\n<size=75%>{context.BlockedReason}</size>";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace GeometryTD.UI
|
|||||||
[SerializeField] private Transform _content;
|
[SerializeField] private Transform _content;
|
||||||
[SerializeField] private TowerRepoItem _towerItemTemplate;
|
[SerializeField] private TowerRepoItem _towerItemTemplate;
|
||||||
[SerializeField] private int _instancePoolCapacity = 8;
|
[SerializeField] private int _instancePoolCapacity = 8;
|
||||||
|
private bool _isInteractionEnabled = true;
|
||||||
|
|
||||||
private readonly List<TowerRepoItem> _activeItems = new List<TowerRepoItem>();
|
private readonly List<TowerRepoItem> _activeItems = new List<TowerRepoItem>();
|
||||||
private readonly HashSet<long> _boundItemIds = new HashSet<long>();
|
private readonly HashSet<long> _boundItemIds = new HashSet<long>();
|
||||||
@@ -81,6 +82,11 @@ namespace GeometryTD.UI
|
|||||||
|
|
||||||
public bool CanAssign(IRepoDragItemView dragItem)
|
public bool CanAssign(IRepoDragItemView dragItem)
|
||||||
{
|
{
|
||||||
|
if (!_isInteractionEnabled)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (dragItem == null || dragItem.InstanceId <= 0)
|
if (dragItem == null || dragItem.InstanceId <= 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -106,6 +112,11 @@ namespace GeometryTD.UI
|
|||||||
|
|
||||||
public void OnDrop(PointerEventData eventData)
|
public void OnDrop(PointerEventData eventData)
|
||||||
{
|
{
|
||||||
|
if (!_isInteractionEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (eventData == null)
|
if (eventData == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -134,6 +145,11 @@ namespace GeometryTD.UI
|
|||||||
GameEntry.Event.Fire(this, RepoParticipantAssignRequestedEventArgs.Create(dragItem.InstanceId));
|
GameEntry.Event.Fire(this, RepoParticipantAssignRequestedEventArgs.Create(dragItem.InstanceId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetInteractionEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
_isInteractionEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
private void EnsurePool()
|
private void EnsurePool()
|
||||||
{
|
{
|
||||||
if (_itemPool != null)
|
if (_itemPool != null)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using GeometryTD.CustomEvent;
|
using GeometryTD.CustomEvent;
|
||||||
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
using UnityGameFramework.Runtime;
|
using UnityGameFramework.Runtime;
|
||||||
|
|
||||||
namespace GeometryTD.UI
|
namespace GeometryTD.UI
|
||||||
@@ -7,10 +9,12 @@ namespace GeometryTD.UI
|
|||||||
public class RepoForm : UGuiForm
|
public class RepoForm : UGuiForm
|
||||||
{
|
{
|
||||||
[SerializeField] private CombineArea _combineArea;
|
[SerializeField] private CombineArea _combineArea;
|
||||||
|
|
||||||
[SerializeField] private CompArea _compArea;
|
[SerializeField] private CompArea _compArea;
|
||||||
|
[SerializeField] private SellArea _sellArea;
|
||||||
[SerializeField] private ParticipantArea _participantArea;
|
[SerializeField] private ParticipantArea _participantArea;
|
||||||
|
[SerializeField] private TMP_Text _goldText;
|
||||||
|
[SerializeField] private CommonButton _sellModeButton;
|
||||||
|
[SerializeField] private TMP_Text _sellModeButtonText;
|
||||||
|
|
||||||
public void RefreshUI(RepoFormContext context)
|
public void RefreshUI(RepoFormContext context)
|
||||||
{
|
{
|
||||||
@@ -19,9 +23,34 @@ namespace GeometryTD.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RefreshGoldText(context.GoldText);
|
||||||
|
RefreshStateUI(context);
|
||||||
|
|
||||||
_combineArea?.OnInit(context.CombineAreaContext);
|
_combineArea?.OnInit(context.CombineAreaContext);
|
||||||
_compArea?.OnInit(context.CompAreaContext);
|
_compArea?.OnInit(context.CompAreaContext);
|
||||||
|
_sellArea?.OnInit(context.SellAreaContext);
|
||||||
_participantArea?.OnInit(context.ParticipantAreaContext);
|
_participantArea?.OnInit(context.ParticipantAreaContext);
|
||||||
|
|
||||||
|
if (_combineArea != null)
|
||||||
|
{
|
||||||
|
_combineArea.gameObject.SetActive(context.ShowCombineArea);
|
||||||
|
_combineArea.SetInteractionEnabled(context.State == RepoFormState.Assemble);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_sellArea != null)
|
||||||
|
{
|
||||||
|
_sellArea.gameObject.SetActive(context.ShowSellArea);
|
||||||
|
}
|
||||||
|
|
||||||
|
_participantArea?.SetInteractionEnabled(context.State == RepoFormState.Assemble);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefreshGoldText(string goldText)
|
||||||
|
{
|
||||||
|
if (_goldText != null)
|
||||||
|
{
|
||||||
|
_goldText.text = goldText ?? string.Empty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryAssignItemToCombineArea(RepoItemContext itemContext)
|
public bool TryAssignItemToCombineArea(RepoItemContext itemContext)
|
||||||
@@ -69,8 +98,14 @@ namespace GeometryTD.UI
|
|||||||
|
|
||||||
protected override void OnClose(bool isShutdown, object userData)
|
protected override void OnClose(bool isShutdown, object userData)
|
||||||
{
|
{
|
||||||
|
if (_goldText != null)
|
||||||
|
{
|
||||||
|
_goldText.text = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
_combineArea?.OnReset();
|
_combineArea?.OnReset();
|
||||||
_compArea?.OnReset();
|
_compArea?.OnReset();
|
||||||
|
_sellArea?.OnReset();
|
||||||
_participantArea?.OnReset();
|
_participantArea?.OnReset();
|
||||||
base.OnClose(isShutdown, userData);
|
base.OnClose(isShutdown, userData);
|
||||||
}
|
}
|
||||||
@@ -79,5 +114,23 @@ namespace GeometryTD.UI
|
|||||||
{
|
{
|
||||||
GameEntry.Event.Fire(this, RepoFormReturnEventArgs.Create());
|
GameEntry.Event.Fire(this, RepoFormReturnEventArgs.Create());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnSellModeButtonClick()
|
||||||
|
{
|
||||||
|
GameEntry.Event.Fire(this, RepoSellModeToggleRequestedEventArgs.Create());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshStateUI(RepoFormContext context)
|
||||||
|
{
|
||||||
|
if (_sellModeButton != null)
|
||||||
|
{
|
||||||
|
_sellModeButton.gameObject.SetActive(context != null && context.ShowSellModeButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_sellModeButtonText != null)
|
||||||
|
{
|
||||||
|
_sellModeButtonText.text = context?.SellModeButtonText ?? "出售模式";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ namespace GeometryTD.UI
|
|||||||
[SerializeField] private IconArea _iconArea;
|
[SerializeField] private IconArea _iconArea;
|
||||||
|
|
||||||
private static readonly Color SelectedColor = new Color32(255, 216, 102, 255);
|
private static readonly Color SelectedColor = new Color32(255, 216, 102, 255);
|
||||||
|
private static readonly Color DisabledSellColor = new Color32(96, 96, 96, 255);
|
||||||
private static readonly Color EmptyEnduranceColor = new Color(0.8f, 0f, 0f, 1f);
|
private static readonly Color EmptyEnduranceColor = new Color(0.8f, 0f, 0f, 1f);
|
||||||
private static readonly Color FullEnduranceColor = new Color(0f, 0.8f, 0f, 1f);
|
private static readonly Color FullEnduranceColor = new Color(0f, 0.8f, 0f, 1f);
|
||||||
private static readonly Vector2 DefaultDragGhostSize = new Vector2(64f, 64f);
|
private static readonly Vector2 DefaultDragGhostSize = new Vector2(64f, 64f);
|
||||||
@@ -72,7 +73,7 @@ namespace GeometryTD.UI
|
|||||||
_context = context;
|
_context = context;
|
||||||
_iconArea.OnInit(context.IconAreaContext);
|
_iconArea.OnInit(context.IconAreaContext);
|
||||||
|
|
||||||
SetSelected(false);
|
SetSelected(context.IsSellSelected);
|
||||||
ResetDragState();
|
ResetDragState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +105,12 @@ namespace GeometryTD.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_context != null && _context.IsSellMode && !_context.IsSellable)
|
||||||
|
{
|
||||||
|
_bgImage.color = DisabledSellColor;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
float enduranceRate = _context != null ? Mathf.Clamp01(_context.EnduranceRate01) : 1f;
|
float enduranceRate = _context != null ? Mathf.Clamp01(_context.EnduranceRate01) : 1f;
|
||||||
_bgImage.color = Color.Lerp(EmptyEnduranceColor, FullEnduranceColor, enduranceRate);
|
_bgImage.color = Color.Lerp(EmptyEnduranceColor, FullEnduranceColor, enduranceRate);
|
||||||
}
|
}
|
||||||
@@ -212,6 +219,11 @@ namespace GeometryTD.UI
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_context.IsSellMode)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (_isSelected && _context.ComponentSlotType != TowerCompSlotType.None)
|
if (_isSelected && _context.ComponentSlotType != TowerCompSlotType.None)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using GeometryTD.CustomEvent;
|
||||||
|
using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace GeometryTD.UI
|
||||||
|
{
|
||||||
|
public class SellArea : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private Transform _content;
|
||||||
|
[SerializeField] private RepoItem _itemTemplate;
|
||||||
|
[SerializeField] private TowerRepoItem _towerItemTemplate;
|
||||||
|
[SerializeField] private TMP_Text _totalPrice;
|
||||||
|
|
||||||
|
private readonly List<RepoItem> _activeComponentItems = new List<RepoItem>();
|
||||||
|
private readonly List<TowerRepoItem> _activeTowerItems = new List<TowerRepoItem>();
|
||||||
|
private SellAreaContext _context;
|
||||||
|
|
||||||
|
public void OnInit(SellAreaContext context)
|
||||||
|
{
|
||||||
|
OnReset();
|
||||||
|
_context = context;
|
||||||
|
|
||||||
|
if (_totalPrice != null)
|
||||||
|
{
|
||||||
|
_totalPrice.text = context?.TotalPriceText ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context == null || _content == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.ComponentItems != null)
|
||||||
|
{
|
||||||
|
foreach (RepoItemContext itemContext in context.ComponentItems)
|
||||||
|
{
|
||||||
|
if (itemContext == null || _itemTemplate == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
RepoItem item = Instantiate(_itemTemplate, _content);
|
||||||
|
item.gameObject.SetActive(true);
|
||||||
|
item.OnInit(itemContext);
|
||||||
|
_activeComponentItems.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.TowerItems != null)
|
||||||
|
{
|
||||||
|
foreach (TowerRepoItemContext itemContext in context.TowerItems)
|
||||||
|
{
|
||||||
|
if (itemContext == null || _towerItemTemplate == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
TowerRepoItem item = Instantiate(_towerItemTemplate, _content);
|
||||||
|
item.gameObject.SetActive(true);
|
||||||
|
item.OnInit(itemContext);
|
||||||
|
_activeTowerItems.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnReset()
|
||||||
|
{
|
||||||
|
for (int i = _activeComponentItems.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
RepoItem item = _activeComponentItems[i];
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
Destroy(item.gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = _activeTowerItems.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
TowerRepoItem item = _activeTowerItems[i];
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
Destroy(item.gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_activeComponentItems.Clear();
|
||||||
|
_activeTowerItems.Clear();
|
||||||
|
_context = null;
|
||||||
|
|
||||||
|
if (_totalPrice != null)
|
||||||
|
{
|
||||||
|
_totalPrice.text = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnCancelButtonClick()
|
||||||
|
{
|
||||||
|
GameEntry.Event.Fire(this, RepoSellCancelRequestedEventArgs.Create());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnConfirmButtonClick()
|
||||||
|
{
|
||||||
|
if (_context == null || !_context.CanConfirmSell)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameEntry.Event.Fire(this, RepoSellConfirmRequestedEventArgs.Create());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 548fcad4dc8202f41b67f48aab7bed13
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -110,7 +110,7 @@ namespace GeometryTD.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RewardSelectFormRawData nextRawData = _useCase.TryRefresh();
|
RewardSelectFormRawData nextRawData = _useCase.TryRotateSelection();
|
||||||
if (nextRawData == null)
|
if (nextRawData == null)
|
||||||
{
|
{
|
||||||
CloseUI();
|
CloseUI();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using GeometryTD.Definition;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Random = UnityEngine.Random;
|
|
||||||
|
|
||||||
namespace GeometryTD.UI
|
namespace GeometryTD.UI
|
||||||
{
|
{
|
||||||
@@ -15,16 +15,20 @@ namespace GeometryTD.UI
|
|||||||
|
|
||||||
private int _displayCount = 3;
|
private int _displayCount = 3;
|
||||||
private int _refreshCost;
|
private int _refreshCost;
|
||||||
private bool _allowRefreshOnce = true;
|
private bool _allowRotateOnce = true;
|
||||||
private bool _allowGiveUp = true;
|
private bool _allowGiveUp = true;
|
||||||
private bool _hasRefreshed;
|
private bool _hasRotated;
|
||||||
|
private int _selectionOffset;
|
||||||
private string _tipText = "Select one reward";
|
private string _tipText = "Select one reward";
|
||||||
|
|
||||||
|
// RewardSelectForm keeps a fixed reward pool for the current node.
|
||||||
|
// The "refresh" action only rotates which slice of that pool is shown so save/load
|
||||||
|
// can reopen the same node with the exact same reward contents.
|
||||||
public void ConfigureRewardPool(
|
public void ConfigureRewardPool(
|
||||||
IReadOnlyList<RewardSelectItemRawData> rewardPool,
|
IReadOnlyList<RewardSelectItemRawData> rewardPool,
|
||||||
int displayCount = 3,
|
int displayCount = 3,
|
||||||
int refreshCost = 0,
|
int refreshCost = 0,
|
||||||
bool allowRefreshOnce = true,
|
bool allowRotateOnce = true,
|
||||||
bool allowGiveUp = true,
|
bool allowGiveUp = true,
|
||||||
string tipText = null)
|
string tipText = null)
|
||||||
{
|
{
|
||||||
@@ -44,10 +48,43 @@ namespace GeometryTD.UI
|
|||||||
|
|
||||||
_displayCount = Mathf.Max(1, displayCount);
|
_displayCount = Mathf.Max(1, displayCount);
|
||||||
_refreshCost = Mathf.Max(0, refreshCost);
|
_refreshCost = Mathf.Max(0, refreshCost);
|
||||||
_allowRefreshOnce = allowRefreshOnce;
|
_allowRotateOnce = allowRotateOnce;
|
||||||
_allowGiveUp = allowGiveUp;
|
_allowGiveUp = allowGiveUp;
|
||||||
_tipText = string.IsNullOrWhiteSpace(tipText) ? "Select one reward" : tipText;
|
_tipText = string.IsNullOrWhiteSpace(tipText) ? "Select one reward" : tipText;
|
||||||
_hasRefreshed = false;
|
_hasRotated = false;
|
||||||
|
_selectionOffset = 0;
|
||||||
|
_currentModel = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConfigureRewardCandidates(
|
||||||
|
IReadOnlyList<TowerCompItemData> rewardCandidates,
|
||||||
|
int displayCount = 3,
|
||||||
|
int refreshCost = 0,
|
||||||
|
bool allowRotateOnce = true,
|
||||||
|
bool allowGiveUp = true,
|
||||||
|
string tipText = null)
|
||||||
|
{
|
||||||
|
_rewardPool.Clear();
|
||||||
|
if (rewardCandidates != null)
|
||||||
|
{
|
||||||
|
foreach (var item in rewardCandidates)
|
||||||
|
{
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_rewardPool.Add(RewardSelectItemRawDataBuilder.Build(item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_displayCount = Mathf.Max(1, displayCount);
|
||||||
|
_refreshCost = Mathf.Max(0, refreshCost);
|
||||||
|
_allowRotateOnce = allowRotateOnce;
|
||||||
|
_allowGiveUp = allowGiveUp;
|
||||||
|
_tipText = string.IsNullOrWhiteSpace(tipText) ? "Select one reward" : tipText;
|
||||||
|
_hasRotated = false;
|
||||||
|
_selectionOffset = 0;
|
||||||
_currentModel = null;
|
_currentModel = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,30 +96,35 @@ namespace GeometryTD.UI
|
|||||||
|
|
||||||
public RewardSelectFormRawData CreateInitialModel()
|
public RewardSelectFormRawData CreateInitialModel()
|
||||||
{
|
{
|
||||||
_hasRefreshed = false;
|
_hasRotated = false;
|
||||||
_currentModel = BuildModel();
|
_currentModel = BuildModel();
|
||||||
return _currentModel;
|
return _currentModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RewardSelectFormRawData TryRefresh()
|
public RewardSelectFormRawData TryRotateSelection()
|
||||||
{
|
{
|
||||||
if (_currentModel == null)
|
if (_currentModel == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!CanRefreshInternal())
|
if (!CanRotateSelectionInternal())
|
||||||
{
|
{
|
||||||
return _currentModel;
|
return _currentModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryConsumeRefreshCost())
|
if (!TryConsumeRotateCost())
|
||||||
{
|
{
|
||||||
_currentModel.CanRefresh = false;
|
_currentModel.CanRefresh = false;
|
||||||
return _currentModel;
|
return _currentModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
_hasRefreshed = true;
|
_hasRotated = true;
|
||||||
|
if (_rewardPool.Count > 0)
|
||||||
|
{
|
||||||
|
_selectionOffset = (_selectionOffset + _displayCount) % _rewardPool.Count;
|
||||||
|
}
|
||||||
|
|
||||||
_currentModel = BuildModel();
|
_currentModel = BuildModel();
|
||||||
return _currentModel;
|
return _currentModel;
|
||||||
}
|
}
|
||||||
@@ -127,7 +169,7 @@ namespace GeometryTD.UI
|
|||||||
TipText = _tipText,
|
TipText = _tipText,
|
||||||
RewardItems = selectedRewards,
|
RewardItems = selectedRewards,
|
||||||
RefreshCost = _refreshCost,
|
RefreshCost = _refreshCost,
|
||||||
CanRefresh = selectedRewards.Length > 0 && CanRefreshInternal(),
|
CanRefresh = selectedRewards.Length > 0 && CanRotateSelectionInternal(),
|
||||||
CanGiveUp = _allowGiveUp
|
CanGiveUp = _allowGiveUp
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -140,39 +182,28 @@ namespace GeometryTD.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
int finalCount = Mathf.Clamp(_displayCount, 1, _rewardPool.Count);
|
int finalCount = Mathf.Clamp(_displayCount, 1, _rewardPool.Count);
|
||||||
int[] indexes = new int[_rewardPool.Count];
|
|
||||||
for (int i = 0; i < indexes.Length; i++)
|
|
||||||
{
|
|
||||||
indexes[i] = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < finalCount; i++)
|
|
||||||
{
|
|
||||||
int randomIndex = Random.Range(i, indexes.Length);
|
|
||||||
(indexes[i], indexes[randomIndex]) = (indexes[randomIndex], indexes[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
RewardSelectItemRawData[] results = new RewardSelectItemRawData[finalCount];
|
RewardSelectItemRawData[] results = new RewardSelectItemRawData[finalCount];
|
||||||
for (int i = 0; i < finalCount; i++)
|
for (int i = 0; i < finalCount; i++)
|
||||||
{
|
{
|
||||||
RewardSelectItemRawData source = _rewardPool[indexes[i]];
|
int sourceIndex = (_selectionOffset + i) % _rewardPool.Count;
|
||||||
|
RewardSelectItemRawData source = _rewardPool[sourceIndex];
|
||||||
results[i] = source;
|
results[i] = source;
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CanRefreshInternal()
|
private bool CanRotateSelectionInternal()
|
||||||
{
|
{
|
||||||
if (!_allowRefreshOnce || _hasRefreshed)
|
if (!_allowRotateOnce || _hasRotated)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return CanPayRefreshCost();
|
return CanPayRotateCost();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CanPayRefreshCost()
|
private bool CanPayRotateCost()
|
||||||
{
|
{
|
||||||
if (_refreshCost <= 0)
|
if (_refreshCost <= 0)
|
||||||
{
|
{
|
||||||
@@ -187,7 +218,7 @@ namespace GeometryTD.UI
|
|||||||
return GameEntry.PlayerInventory.Gold >= _refreshCost;
|
return GameEntry.PlayerInventory.Gold >= _refreshCost;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryConsumeRefreshCost()
|
private bool TryConsumeRotateCost()
|
||||||
{
|
{
|
||||||
if (_refreshCost <= 0)
|
if (_refreshCost <= 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.InputSystem;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
using UnityGameFramework.Runtime;
|
using UnityGameFramework.Runtime;
|
||||||
|
|
||||||
@@ -360,24 +361,27 @@ namespace GeometryTD.UI
|
|||||||
|
|
||||||
private static bool TryGetPointerDownScreenPosition(out Vector2 screenPoint)
|
private static bool TryGetPointerDownScreenPosition(out Vector2 screenPoint)
|
||||||
{
|
{
|
||||||
if (Input.touchCount > 0)
|
Touchscreen touchscreen = Touchscreen.current;
|
||||||
|
if (touchscreen != null)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < Input.touchCount; i++)
|
var touches = touchscreen.touches;
|
||||||
|
for (int i = 0; i < touches.Count; i++)
|
||||||
{
|
{
|
||||||
Touch touch = Input.GetTouch(i);
|
var touch = touches[i];
|
||||||
if (touch.phase != TouchPhase.Began)
|
if (!touch.press.wasPressedThisFrame)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
screenPoint = touch.position;
|
screenPoint = touch.position.ReadValue();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Input.GetMouseButtonDown(0))
|
Mouse mouse = Mouse.current;
|
||||||
|
if (mouse != null && mouse.leftButton.wasPressedThisFrame)
|
||||||
{
|
{
|
||||||
screenPoint = Input.mousePosition;
|
screenPoint = mouse.position.ReadValue();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,37 +61,16 @@ namespace GeometryTD.UI
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!GameEntry.PlayerInventory.TryConsumeGold(goodsItem.Price))
|
if (!GameEntry.PlayerInventory.TryPurchaseComponent(goodsItem.SourceItem, goodsItem.Price))
|
||||||
{
|
{
|
||||||
Log.Warning("ShopFormUseCase.TryPurchase() failed. Not enough gold for goods item {0}.", goodsIndex);
|
Log.Warning("ShopFormUseCase.TryPurchase() failed. Purchase command was rejected for goods item {0}.", goodsIndex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
BackpackInventoryData inventoryDelta = WrapSingleItem(goodsItem.SourceItem);
|
|
||||||
GameEntry.PlayerInventory.MergeInventory(inventoryDelta);
|
|
||||||
goodsItem.IsPurchased = true;
|
goodsItem.IsPurchased = true;
|
||||||
updatedRawData = CreateInitialModel();
|
updatedRawData = CreateInitialModel();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BackpackInventoryData WrapSingleItem(TowerCompItemData item)
|
|
||||||
{
|
|
||||||
BackpackInventoryData inventory = new BackpackInventoryData();
|
|
||||||
switch (item)
|
|
||||||
{
|
|
||||||
case MuzzleCompItemData muzzleComp:
|
|
||||||
inventory.MuzzleComponents.Add(InventoryCloneUtility.CloneMuzzleComp(muzzleComp));
|
|
||||||
break;
|
|
||||||
case BearingCompItemData bearingComp:
|
|
||||||
inventory.BearingComponents.Add(InventoryCloneUtility.CloneBearingComp(bearingComp));
|
|
||||||
break;
|
|
||||||
case BaseCompItemData baseComp:
|
|
||||||
inventory.BaseComponents.Add(InventoryCloneUtility.CloneBaseComp(baseComp));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return inventory;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ namespace GeometryTD.CustomUtility
|
|||||||
AttackPropertyType.None => "无效",
|
AttackPropertyType.None => "无效",
|
||||||
AttackPropertyType.Physics => "<color=#ffff00ff>物理</color>", //yellow
|
AttackPropertyType.Physics => "<color=#ffff00ff>物理</color>", //yellow
|
||||||
AttackPropertyType.Fire => "<color=#ff0000ff>火</color>", //red
|
AttackPropertyType.Fire => "<color=#ff0000ff>火</color>", //red
|
||||||
AttackPropertyType.Water => "<color=#00ffffff>水/color>", //cyan
|
AttackPropertyType.Water => "<color=#00ffffff>水</color>", //cyan
|
||||||
AttackPropertyType.Earth => "<color=#00ff00ff>自然</color>", //lime
|
AttackPropertyType.Earth => "<color=#00ff00ff>自然</color>", //lime
|
||||||
AttackPropertyType.Poison => "<color=#008000ff>毒</color>", //green
|
AttackPropertyType.Poison => "<color=#008000ff>毒</color>", //green
|
||||||
AttackPropertyType.Ice => "<color=#0000a0ff>冰</color>", //darkblue
|
AttackPropertyType.Ice => "<color=#0000a0ff>冰</color>", //darkblue
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using GameFramework.DataTable;
|
||||||
|
using GeometryTD.DataTable;
|
||||||
|
using GeometryTD.Definition;
|
||||||
|
using UnityEngine;
|
||||||
|
using Random = System.Random;
|
||||||
|
|
||||||
|
namespace GeometryTD.CustomUtility
|
||||||
|
{
|
||||||
|
public static class ShopPriceRuleService
|
||||||
|
{
|
||||||
|
public static int ResolveRandomBuyPrice(IReadOnlyList<DRShopPrice> shopPriceRows, RarityType rarity, Random random)
|
||||||
|
{
|
||||||
|
if (!TryFindPriceRow(shopPriceRows, rarity, out DRShopPrice row) || row == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int min = Mathf.Max(0, row.MinPrice);
|
||||||
|
int max = Mathf.Max(min, row.MaxPrice);
|
||||||
|
return random != null ? random.Next(min, max + 1) : min;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int ResolveComponentSalePrice(TowerCompItemData component, IDataTable<DRShopPrice> shopPriceTable = null)
|
||||||
|
{
|
||||||
|
if (component == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResolveBasePrice(component.Rarity, shopPriceTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryResolveTowerSalePrice(
|
||||||
|
TowerItemData tower,
|
||||||
|
BackpackInventoryData inventory,
|
||||||
|
out int price,
|
||||||
|
IDataTable<DRShopPrice> shopPriceTable = null)
|
||||||
|
{
|
||||||
|
price = 0;
|
||||||
|
if (tower == null || inventory == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryGetComponentById(inventory.MuzzleComponents, tower.MuzzleComponentInstanceId, out MuzzleCompItemData muzzleComp) ||
|
||||||
|
!TryGetComponentById(inventory.BearingComponents, tower.BearingComponentInstanceId, out BearingCompItemData bearingComp) ||
|
||||||
|
!TryGetComponentById(inventory.BaseComponents, tower.BaseComponentInstanceId, out BaseCompItemData baseComp))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
price = ResolveComponentSalePrice(muzzleComp, shopPriceTable) +
|
||||||
|
ResolveComponentSalePrice(bearingComp, shopPriceTable) +
|
||||||
|
ResolveComponentSalePrice(baseComp, shopPriceTable);
|
||||||
|
return price > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int ResolveBasePrice(RarityType rarity, IDataTable<DRShopPrice> shopPriceTable = null)
|
||||||
|
{
|
||||||
|
IDataTable<DRShopPrice> resolvedTable = shopPriceTable ?? GameEntry.DataTable.GetDataTable<DRShopPrice>();
|
||||||
|
if (resolvedTable == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DRShopPrice[] rows = resolvedTable.GetAllDataRows();
|
||||||
|
if (!TryFindPriceRow(rows, rarity, out DRShopPrice row) || row == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int min = Mathf.Max(0, row.MinPrice);
|
||||||
|
int max = Mathf.Max(min, row.MaxPrice);
|
||||||
|
return Mathf.RoundToInt((min + max) * 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryFindPriceRow(IReadOnlyList<DRShopPrice> rows, RarityType rarity, out DRShopPrice result)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
if (rows == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < rows.Count; i++)
|
||||||
|
{
|
||||||
|
DRShopPrice row = rows[i];
|
||||||
|
if (row != null && row.Rarity == rarity)
|
||||||
|
{
|
||||||
|
result = row;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetComponentById<TComp>(IReadOnlyList<TComp> components, long instanceId, out TComp result)
|
||||||
|
where TComp : TowerCompItemData
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
if (components == null || instanceId <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < components.Count; i++)
|
||||||
|
{
|
||||||
|
TComp component = components[i];
|
||||||
|
if (component != null && component.InstanceId == instanceId)
|
||||||
|
{
|
||||||
|
result = component;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b1bcf5c75bea4faaacdb4ffc5adfaf36
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -158,6 +158,7 @@ Transform:
|
|||||||
- {fileID: 1322505022}
|
- {fileID: 1322505022}
|
||||||
- {fileID: 1062564689}
|
- {fileID: 1062564689}
|
||||||
- {fileID: 1554147129}
|
- {fileID: 1554147129}
|
||||||
|
- {fileID: 336799288}
|
||||||
m_Father: {fileID: 1852670053}
|
m_Father: {fileID: 1852670053}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!1 &120093239
|
--- !u!1 &120093239
|
||||||
@@ -294,6 +295,50 @@ MonoBehaviour:
|
|||||||
m_Script: {fileID: 11500000, guid: 0a0c1c547ca24c95819f5f62f0bd3ea3, type: 3}
|
m_Script: {fileID: 11500000, guid: 0a0c1c547ca24c95819f5f62f0bd3ea3, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
|
--- !u!1 &336799287
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 336799288}
|
||||||
|
- component: {fileID: 336799289}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: TagRegistery
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!4 &336799288
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 336799287}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 119167776}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!114 &336799289
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 336799287}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: a268c57d4373494ab9b4d7167099f170, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
--- !u!1001 &343730742
|
--- !u!1001 &343730742
|
||||||
PrefabInstance:
|
PrefabInstance:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -684,6 +729,10 @@ PrefabInstance:
|
|||||||
propertyPath: m_FrameRate
|
propertyPath: m_FrameRate
|
||||||
value: 60
|
value: 60
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 11499388, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
|
||||||
|
propertyPath: m_GameSpeed
|
||||||
|
value: 2
|
||||||
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 11499388, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
|
- target: {fileID: 11499388, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
|
||||||
propertyPath: m_EditorLanguage
|
propertyPath: m_EditorLanguage
|
||||||
value: 8
|
value: 8
|
||||||
|
|||||||
@@ -183,11 +183,15 @@ namespace GeometryTD.Tests.EditMode
|
|||||||
CreateBoundPlayerInventory(CreateInventory(100f, 100f, 100f));
|
CreateBoundPlayerInventory(CreateInventory(100f, 100f, 100f));
|
||||||
CombatSettlementContext settlementContext = new CombatSettlementContext();
|
CombatSettlementContext settlementContext = new CombatSettlementContext();
|
||||||
settlementContext.Flags.ShouldOpenRewardSelection = true;
|
settlementContext.Flags.ShouldOpenRewardSelection = true;
|
||||||
|
int nextRewardOrdinal = 0;
|
||||||
|
|
||||||
bool prepared = new CombatSettlementService().TryPrepareRewardSelection(
|
bool prepared = new CombatSettlementService().TryPrepareRewardSelection(
|
||||||
settlementContext,
|
settlementContext,
|
||||||
displayPhaseIndex: 1,
|
displayPhaseIndex: 1,
|
||||||
themeType: LevelThemeType.Plain,
|
themeType: LevelThemeType.Plain,
|
||||||
|
runSeed: 1001,
|
||||||
|
sequenceIndex: 3,
|
||||||
|
ref nextRewardOrdinal,
|
||||||
rewardSelectFormUseCase: null,
|
rewardSelectFormUseCase: null,
|
||||||
onRewardSelected: _ => { },
|
onRewardSelected: _ => { },
|
||||||
onGiveUp: null);
|
onGiveUp: null);
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
using GeometryTD.Definition;
|
||||||
|
using GeometryTD.UI;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace GeometryTD.Tests.EditMode
|
||||||
|
{
|
||||||
|
public sealed class ContinueChallengeMultiplierTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void BuildRawData_Uses_OneX_Multiplier_For_First_Loop_And_Invalid_Input()
|
||||||
|
{
|
||||||
|
CombatInfoFormRawData firstLoop = CombatInfoFormUseCase.BuildRawData(
|
||||||
|
LevelThemeType.Plain,
|
||||||
|
levelId: 1,
|
||||||
|
currentPhaseIndex: 1,
|
||||||
|
totalPhaseCount: 4,
|
||||||
|
coin: 10,
|
||||||
|
baseHp: 20,
|
||||||
|
baseHpMax: 20,
|
||||||
|
canEnd: true);
|
||||||
|
CombatInfoFormRawData invalidInput = CombatInfoFormUseCase.BuildRawData(
|
||||||
|
LevelThemeType.Plain,
|
||||||
|
levelId: 1,
|
||||||
|
currentPhaseIndex: 0,
|
||||||
|
totalPhaseCount: 0,
|
||||||
|
coin: 10,
|
||||||
|
baseHp: 20,
|
||||||
|
baseHpMax: 20,
|
||||||
|
canEnd: false);
|
||||||
|
|
||||||
|
Assert.That(firstLoop.EnemyHpRateMultiplier, Is.EqualTo(1));
|
||||||
|
Assert.That(invalidInput.EnemyHpRateMultiplier, Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void BuildRawData_Doubles_EnemyHp_After_Each_Full_Loop()
|
||||||
|
{
|
||||||
|
CombatInfoFormRawData secondLoop = CombatInfoFormUseCase.BuildRawData(
|
||||||
|
LevelThemeType.Volcano,
|
||||||
|
levelId: 2,
|
||||||
|
currentPhaseIndex: 5,
|
||||||
|
totalPhaseCount: 4,
|
||||||
|
coin: 15,
|
||||||
|
baseHp: 18,
|
||||||
|
baseHpMax: 20,
|
||||||
|
canEnd: true);
|
||||||
|
CombatInfoFormRawData thirdLoop = CombatInfoFormUseCase.BuildRawData(
|
||||||
|
LevelThemeType.Mountain,
|
||||||
|
levelId: 3,
|
||||||
|
currentPhaseIndex: 9,
|
||||||
|
totalPhaseCount: 4,
|
||||||
|
coin: 20,
|
||||||
|
baseHp: 16,
|
||||||
|
baseHpMax: 20,
|
||||||
|
canEnd: true);
|
||||||
|
|
||||||
|
Assert.That(secondLoop.EnemyHpRateMultiplier, Is.EqualTo(2));
|
||||||
|
Assert.That(thirdLoop.EnemyHpRateMultiplier, Is.EqualTo(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void BuildRawData_Clamps_EnemyHp_Multiplier_At_IntMaxValue()
|
||||||
|
{
|
||||||
|
CombatInfoFormRawData rawData = CombatInfoFormUseCase.BuildRawData(
|
||||||
|
LevelThemeType.Plain,
|
||||||
|
levelId: 1,
|
||||||
|
currentPhaseIndex: 121,
|
||||||
|
totalPhaseCount: 4,
|
||||||
|
coin: 30,
|
||||||
|
baseHp: 10,
|
||||||
|
baseHpMax: 20,
|
||||||
|
canEnd: false);
|
||||||
|
|
||||||
|
Assert.That(rawData.EnemyHpRateMultiplier, Is.EqualTo(int.MaxValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user