feat: Introduce new services and refactor existing code for better structure

- Added ISpriteCacheService, IStaticDataService, and ITextService interfaces for improved service management.
- Implemented RunContracts for managing run states and node execution contexts.
- Created EnemyTagStatusRuntime to handle enemy status effects and their application.
- Refactored ShopPriceRuleService to utilize static data service for shop prices.
- Enhanced InventoryTowerEnduranceUtility to manage tower endurance reduction.
- Updated EventFormUseCase to work with new EventOptionExecutionContext.
- Removed unused dictionary loading code from ProcedurePreload.
- Cleaned up various utility classes and ensured consistent naming conventions.
- Updated tests to reflect changes in context and service usage.
This commit is contained in:
SepComet 2026-05-13 22:18:47 +08:00
parent 9fc0ef2216
commit 7106e0adbe
45 changed files with 590 additions and 426 deletions

View File

@ -1,5 +1,6 @@
using System.Collections.Generic;
using GameFramework.DataTable;
using GeometryTD.Core;
using GeometryTD.CustomEvent;
using GeometryTD.DataTable;
using GeometryTD.Definition;
@ -79,7 +80,7 @@ namespace GeometryTD.CustomComponent
return;
}
_eventFormUseCase.BindEvent(_activeEvent, _activeContext);
_eventFormUseCase.BindEvent(_activeEvent, CreateEventOptionExecutionContext(_activeContext));
GameEntry.UIRouter.OpenUI(UIFormType.EventForm);
GameEntry.Event.Fire(
this,
@ -236,5 +237,12 @@ namespace GeometryTD.CustomComponent
return seed;
}
}
private static EventOptionExecutionContext CreateEventOptionExecutionContext(RunNodeExecutionContext context)
{
return new EventOptionExecutionContext(
context?.RunSeed ?? 0,
context?.SequenceIndex ?? -1);
}
}
}

View File

@ -123,7 +123,7 @@ namespace GeometryTD.CustomComponent
private int ResolveRandomPrice(RarityType rarity, Random random)
{
return ShopPriceRuleService.ResolveRandomBuyPrice(_shopPriceRows, rarity, random);
return ShopPriceRuleService.ResolveRandomBuyPrice(rarity, random);
}
private static IconAreaContext BuildIconAreaContext(TowerCompItemData item)

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Collections.Generic;
using GeometryTD.Core;
using GeometryTD.CustomUtility;
using GeometryTD.Definition;
using UnityEngine;
@ -42,108 +43,4 @@ namespace GeometryTD.CustomComponent
enduranceLoss);
}
}
public static class InventoryTowerEnduranceUtility
{
public static int ReduceTowerEndurance(
BackpackInventoryData inventory,
IReadOnlyList<long> towerInstanceIds,
float enduranceLoss)
{
float resolvedLoss = Mathf.Max(0f, enduranceLoss);
if (inventory?.Towers == null ||
inventory.Towers.Count <= 0 ||
resolvedLoss <= 0f ||
towerInstanceIds == null ||
towerInstanceIds.Count <= 0)
{
return 0;
}
Dictionary<long, MuzzleCompItemData> muzzleMap = BuildComponentMap(inventory.MuzzleComponents);
Dictionary<long, BearingCompItemData> bearingMap = BuildComponentMap(inventory.BearingComponents);
Dictionary<long, BaseCompItemData> baseMap = BuildComponentMap(inventory.BaseComponents);
HashSet<long> processedTowerIds = new HashSet<long>();
int affectedCount = 0;
for (int i = 0; i < towerInstanceIds.Count; i++)
{
long towerInstanceId = towerInstanceIds[i];
if (towerInstanceId <= 0 || !processedTowerIds.Add(towerInstanceId))
{
continue;
}
if (!InventoryParticipantUtility.TryGetTowerById(inventory, towerInstanceId, out TowerItemData tower) ||
tower == null)
{
continue;
}
bool towerAffected = false;
if (muzzleMap.TryGetValue(tower.MuzzleComponentInstanceId, out MuzzleCompItemData muzzleComp))
{
towerAffected |= TryReduceComponentEndurance(muzzleComp, resolvedLoss);
}
if (bearingMap.TryGetValue(tower.BearingComponentInstanceId, out BearingCompItemData bearingComp))
{
towerAffected |= TryReduceComponentEndurance(bearingComp, resolvedLoss);
}
if (baseMap.TryGetValue(tower.BaseComponentInstanceId, out BaseCompItemData baseComp))
{
towerAffected |= TryReduceComponentEndurance(baseComp, resolvedLoss);
}
if (towerAffected)
{
affectedCount++;
}
}
return affectedCount;
}
private static bool TryReduceComponentEndurance(TowerCompItemData component, float enduranceLoss)
{
if (component == null)
{
return false;
}
float originalEndurance = component.Endurance;
float nextEndurance = Mathf.Clamp(originalEndurance - Mathf.Max(0f, enduranceLoss), 0f, 100f);
if (nextEndurance >= originalEndurance)
{
return false;
}
component.Endurance = nextEndurance;
return true;
}
private static Dictionary<long, TComp> BuildComponentMap<TComp>(List<TComp> components)
where TComp : TowerCompItemData
{
Dictionary<long, TComp> map = new Dictionary<long, TComp>();
if (components == null || components.Count <= 0)
{
return map;
}
for (int i = 0; i < components.Count; i++)
{
TComp component = components[i];
if (component == null || component.InstanceId <= 0)
{
continue;
}
map[component.InstanceId] = component;
}
return map;
}
}
}

View File

@ -1,7 +1,5 @@
using System.Collections.Generic;
using GameFramework.DataTable;
using GeometryTD.CustomUtility;
using GeometryTD.DataTable;
using GeometryTD.Definition;
using UnityEngine;
@ -41,16 +39,13 @@ namespace GeometryTD.CustomComponent
{
private readonly PlayerInventoryQueryModel _queryModel;
private readonly PlayerInventoryCommandModel _commandModel;
private IDataTable<DRShopPrice> _shopPriceTable;
public PlayerInventoryTradeService(
PlayerInventoryQueryModel queryModel,
PlayerInventoryCommandModel commandModel,
IDataTable<DRShopPrice> shopPriceTable = null)
PlayerInventoryCommandModel commandModel)
{
_queryModel = queryModel;
_commandModel = commandModel;
_shopPriceTable = shopPriceTable;
}
public bool TryPurchaseComponent(TowerCompItemData item, int price)
@ -179,7 +174,7 @@ namespace GeometryTD.CustomComponent
};
}
if (!ShopPriceRuleService.TryResolveTowerSalePrice(tower, inventory, out int towerPrice, EnsureShopPriceTable()))
if (!ShopPriceRuleService.TryResolveTowerSalePrice(tower, inventory, out int towerPrice))
{
return new PlayerInventorySaleCandidate
{
@ -249,7 +244,7 @@ namespace GeometryTD.CustomComponent
ItemId = component.InstanceId,
IsSellable = true,
IsTower = false,
Price = ShopPriceRuleService.ResolveComponentSalePrice(component, EnsureShopPriceTable()),
Price = ShopPriceRuleService.ResolveComponentSalePrice(component),
FailureReason = PlayerInventorySaleFailureReason.None
};
}
@ -390,11 +385,5 @@ namespace GeometryTD.CustomComponent
return inventory;
}
private IDataTable<DRShopPrice> EnsureShopPriceTable()
{
_shopPriceTable ??= GameEntry.DataTable.GetDataTable<DRShopPrice>();
return _shopPriceTable;
}
}
}

View File

@ -2,9 +2,7 @@
"name": "GeometryTD.Core",
"rootNamespace": "GeometryTD.Core",
"references": [
"GUID:363c5eb08ff8e6a439b85e37b8c20d96",
"GUID:75469ad4d38634e559750d17036d5f7c",
"GUID:6055be8ebefd69e48b49212b09b47b2f"
"GUID:363c5eb08ff8e6a439b85e37b8c20d96"
],
"includePlatforms": [],
"excludePlatforms": [],

View File

@ -1,12 +1,21 @@
using System;
using System.Collections.Generic;
using GeometryTD.CustomComponent;
using GeometryTD.Core;
using GeometryTD.Procedure;
using UnityEngine;
namespace GeometryTD.Core
{
public sealed class EventOptionExecutionContext
{
public EventOptionExecutionContext(int runSeed, int sequenceIndex)
{
RunSeed = runSeed;
SequenceIndex = sequenceIndex;
}
public int RunSeed { get; }
public int SequenceIndex { get; }
}
public sealed class EventOptionExecutor
{
public EventOptionAvailability EvaluateOption(EventOption option, BackpackInventoryData inventory)
@ -37,7 +46,7 @@ namespace GeometryTD.Core
public EventOptionExecutionResult Execute(
EventItem eventItem,
int optionIndex,
RunNodeExecutionContext context,
EventOptionExecutionContext context,
BackpackInventoryData workingInventory)
{
if (eventItem == null || workingInventory == null)
@ -72,7 +81,7 @@ namespace GeometryTD.Core
EventEffectBase[] effects,
EventItem eventItem,
int optionIndex,
RunNodeExecutionContext context,
EventOptionExecutionContext context,
BackpackInventoryData workingInventory,
bool isRewardPhase)
{
@ -143,7 +152,7 @@ namespace GeometryTD.Core
RemoveRandomCompsParam param,
int eventId,
int optionIndex,
RunNodeExecutionContext context,
EventOptionExecutionContext context,
BackpackInventoryData workingInventory,
int effectIndex)
{
@ -171,7 +180,7 @@ namespace GeometryTD.Core
AddRandomCompsParam param,
int eventId,
int optionIndex,
RunNodeExecutionContext context,
EventOptionExecutionContext context,
BackpackInventoryData workingInventory,
int effectIndex)
{
@ -181,12 +190,12 @@ namespace GeometryTD.Core
return;
}
if (GameEntry.InventoryGeneration == null)
if (CoreServiceHub.InventoryGeneration == null)
{
throw new InvalidOperationException("Event component generation requires InventoryGenerationComponent.");
}
IReadOnlyList<TowerCompItemData> generatedComponents = GameEntry.InventoryGeneration.BuildEventRewardComponents(
IReadOnlyList<TowerCompItemData> generatedComponents = CoreServiceHub.InventoryGeneration.BuildEventRewardComponents(
addCount,
param.MinRarity,
param.MaxRarity,
@ -205,7 +214,7 @@ namespace GeometryTD.Core
DamageRandomTowerEnduranceParam param,
int eventId,
int optionIndex,
RunNodeExecutionContext context,
EventOptionExecutionContext context,
BackpackInventoryData workingInventory,
int effectIndex)
{
@ -272,7 +281,7 @@ namespace GeometryTD.Core
}
}
private static bool RollProbability(int eventId, int optionIndex, RunNodeExecutionContext context, float probability)
private static bool RollProbability(int eventId, int optionIndex, EventOptionExecutionContext context, float probability)
{
float clampedProbability = Mathf.Clamp01(probability);
if (clampedProbability >= 1f)
@ -290,7 +299,7 @@ namespace GeometryTD.Core
}
private static System.Random CreateRandom(
RunNodeExecutionContext context,
EventOptionExecutionContext context,
int eventId,
int optionIndex,
int effectIndex,

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9aeb92ccac2068f489dcbd0200417463
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,31 @@
using System;
namespace GeometryTD.Core
{
public static class CoreServiceHub
{
public static IPlayerInventoryManager PlayerInventory { get; private set; }
public static IInventoryGenerationManager InventoryGeneration { get; private set; }
public static IEventFlowManager EventFlow { get; private set; }
public static ISpriteCacheService SpriteCache { get; private set; }
public static IStaticDataService StaticData { get; private set; }
public static ITextService TextService { get; private set; }
public static void Bind(
IPlayerInventoryManager playerInventory,
IInventoryGenerationManager inventoryGeneration,
IEventFlowManager eventFlow,
ISpriteCacheService spriteCache,
IStaticDataService staticData,
ITextService textService)
{
PlayerInventory = playerInventory ?? throw new ArgumentNullException(nameof(playerInventory));
InventoryGeneration = inventoryGeneration ?? throw new
ArgumentNullException(nameof(inventoryGeneration));
EventFlow = eventFlow ?? throw new ArgumentNullException(nameof(eventFlow));
SpriteCache = spriteCache ?? throw new ArgumentNullException(nameof(spriteCache));
StaticData = staticData ?? throw new ArgumentNullException(nameof(staticData));
TextService = textService ?? throw new ArgumentNullException(nameof(textService));
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 95f8a75dcf78e4545b36f1e24b92d82e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,7 @@
namespace GeometryTD.Core
{
public interface IEventFlowManager
{
void EndEvent();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c14fccc4cf6d239489afdc5b302c03b9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,17 @@
using System.Collections.Generic;
namespace GeometryTD.Core
{
public interface IInventoryGenerationManager
{
IReadOnlyList<TowerCompItemData> BuildEventRewardComponents(
int count,
RarityType minRarity,
RarityType maxRarity,
int runSeed,
int sequenceIndex,
int eventId,
int optionIndex,
int effectIndex);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ba83daa081633074ab92ff9ad75c9c52
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
namespace GeometryTD.Core
{
public interface IPlayerInventoryManager
{
BackpackInventoryData GetInventorySnapshot();
void ReplaceInventorySnapshot(BackpackInventoryData snapshot);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0ce0e4176795db941a0f7ed57065f73d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,11 @@
using System;
using UnityEngine;
namespace GeometryTD.Core
{
public interface ISpriteCacheService
{
void Get(string key, Action<Sprite> callback);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 675e9ac521922ac46b3b7afe4973a706
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,12 @@
namespace GeometryTD.Core
{
public interface IStaticDataService
{
DRLevel GetLevel(int id);
DRTag GetTag(int id);
DRShopPrice[] GetAllShopPrices();
DRBearingComp[] GetAllBearingComps();
DRBaseComp[] GetAllBaseComps();
DRMuzzleComp[] GetAllMuzzleComps();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: db37b943863752b41a8a0deef641dca6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,7 @@
namespace GeometryTD.Core
{
public interface ITextService
{
string Get(string key, params object[] args);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9440fb32740fa50409ce370f9eab4d6d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f9d6ff9eff8570b4a96f296f9b107baa
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
namespace GeometryTD.Core
{
public enum RunNodeType
{
None = 0,
Combat = 1,
Event = 2,
Shop = 3,
BossCombat = 4
}
public enum RunNodeCompletionStatus
{
None = 0,
Completed = 1,
Exception = 2
}
[Serializable]
public sealed class RunItemState
{
public int ItemId { get; set; }
public int StackCount { get; set; }
public 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>();
public 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>();
public 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)
};
}
}
public static class RunStateCloneUtility
{
public 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;
}
public 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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e73cd16e1283cc94f903bb3d7728f543
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using GeometryTD.Definition;
using GeometryTD.Core;
using UnityEngine;
namespace GeometryTD.Entity
namespace GeometryTD.Core
{
public sealed class EnemyTagStatusRuntime
{

View File

@ -1,5 +1,4 @@
using System;
using GeometryTD.Entity;
namespace GeometryTD.Core
{

View File

@ -1,5 +1,4 @@
using System;
using GeometryTD.Entity;
using UnityEngine;
namespace GeometryTD.Core

View File

@ -1,5 +1,4 @@
using System;
using GeometryTD.Entity;
namespace GeometryTD.Core
{

View File

@ -1,5 +1,4 @@
using System;
using GeometryTD.Entity;
using UnityEngine;
namespace GeometryTD.Core

View File

@ -13,17 +13,10 @@ namespace GeometryTD.Core
return string.Empty;
}
if (GameEntry.DataTable != null)
DRTag tagRow = CoreServiceHub.StaticData.GetTag((int)tagType);
if (tagRow != null && !string.IsNullOrWhiteSpace(tagRow.Name))
{
var tagTable = GameEntry.DataTable.GetDataTable<DRTag>();
if (tagTable != null)
{
DRTag tagRow = tagTable.GetDataRow((int)tagType);
if (tagRow != null && !string.IsNullOrWhiteSpace(tagRow.Name))
{
return tagRow.Name;
}
}
return tagRow.Name;
}
return tagType.ToString();
@ -171,4 +164,4 @@ namespace GeometryTD.Core
return string.Empty;
}
}
}
}

View File

@ -1,6 +1,5 @@
using GameFramework;
using GameFramework.Event;
using GeometryTD.Procedure;
namespace GeometryTD.Core
{

View File

@ -1,6 +1,5 @@
using GameFramework;
using GameFramework.Event;
using GeometryTD.Procedure;
namespace GeometryTD.Core
{

View File

@ -50,8 +50,6 @@ namespace GeometryTD.Procedure
GameEntry.Event.Subscribe(LoadConfigFailureEventArgs.EventId, OnLoadConfigFailure);
GameEntry.Event.Subscribe(LoadDataTableSuccessEventArgs.EventId, OnLoadDataTableSuccess);
GameEntry.Event.Subscribe(LoadDataTableFailureEventArgs.EventId, OnLoadDataTableFailure);
GameEntry.Event.Subscribe(LoadDictionarySuccessEventArgs.EventId, OnLoadDictionarySuccess);
GameEntry.Event.Subscribe(LoadDictionaryFailureEventArgs.EventId, OnLoadDictionaryFailure);
_loadedFlag.Clear();
@ -64,8 +62,6 @@ namespace GeometryTD.Procedure
GameEntry.Event.Unsubscribe(LoadConfigFailureEventArgs.EventId, OnLoadConfigFailure);
GameEntry.Event.Unsubscribe(LoadDataTableSuccessEventArgs.EventId, OnLoadDataTableSuccess);
GameEntry.Event.Unsubscribe(LoadDataTableFailureEventArgs.EventId, OnLoadDataTableFailure);
GameEntry.Event.Unsubscribe(LoadDictionarySuccessEventArgs.EventId, OnLoadDictionarySuccess);
GameEntry.Event.Unsubscribe(LoadDictionaryFailureEventArgs.EventId, OnLoadDictionaryFailure);
base.OnLeave(procedureOwner, isShutdown);
}
@ -97,9 +93,6 @@ namespace GeometryTD.Procedure
LoadDataTable(dataTableName);
}
// Preload dictionaries
//LoadDictionary("Default");
// Preload fonts
LoadFont("MainFont");
LoadTMPFont("MainTMPFont");
@ -119,13 +112,6 @@ namespace GeometryTD.Procedure
GameEntry.DataTable.LoadDataTable(dataTableName, dataTableAssetName, this);
}
private void LoadDictionary(string dictionaryName)
{
string dictionaryAssetName = AssetUtility.GetDictionaryAsset(dictionaryName, false);
_loadedFlag.Add(dictionaryAssetName, false);
GameEntry.Localization.ReadData(dictionaryAssetName, this);
}
private void LoadFont(string fontName)
{
_loadedFlag.Add(Utility.Text.Format("Font.{0}", fontName), false);
@ -209,29 +195,5 @@ namespace GeometryTD.Procedure
Log.Error("Can not load data table '{0}' from '{1}' with error message '{2}'.", ne.DataTableAssetName,
ne.DataTableAssetName, ne.ErrorMessage);
}
private void OnLoadDictionarySuccess(object sender, GameEventArgs e)
{
LoadDictionarySuccessEventArgs ne = (LoadDictionarySuccessEventArgs)e;
if (ne.UserData != this)
{
return;
}
_loadedFlag[ne.DictionaryAssetName] = true;
Log.Info("Load dictionary '{0}' OK.", ne.DictionaryAssetName);
}
private void OnLoadDictionaryFailure(object sender, GameEventArgs e)
{
LoadDictionaryFailureEventArgs ne = (LoadDictionaryFailureEventArgs)e;
if (ne.UserData != this)
{
return;
}
Log.Error("Can not load dictionary '{0}' from '{1}' with error message '{2}'.", ne.DictionaryAssetName,
ne.DictionaryAssetName, ne.ErrorMessage);
}
}
}

View File

@ -1,20 +1,10 @@
using System;
using System.Collections.Generic;
using GeometryTD.CustomUtility;
using GeometryTD.Definition;
using GeometryTD.Core;
using GeometryTD.Factory;
namespace GeometryTD.Procedure
{
public enum RunNodeType
{
None = 0,
Combat = 1,
Event = 2,
Shop = 3,
BossCombat = 4
}
public enum RunNodeStatus
{
Locked = 0,
@ -24,13 +14,6 @@ namespace GeometryTD.Procedure
Skipped = 4
}
public enum RunNodeCompletionStatus
{
None = 0,
Completed = 1,
Exception = 2
}
[Serializable]
public sealed class RunNodeSeed
{
@ -65,96 +48,6 @@ 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]
public sealed class RunState
{
@ -314,42 +207,4 @@ namespace GeometryTD.Procedure
}
}
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;
}
}
}

View File

@ -1,5 +1,5 @@
using GeometryTD.Core;
using GeometryTD.Definition;
using GeometryTD.Procedure;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
@ -7,15 +7,15 @@ namespace GeometryTD.UI
public class EventFormUseCase : IUIUseCase
{
private EventItem _currentEvent;
private RunNodeExecutionContext _currentContext;
private EventOptionExecutionContext _currentContext;
private readonly EventOptionExecutor _executor = new EventOptionExecutor();
public void BindEvent(
EventItem eventItem,
RunNodeExecutionContext context)
EventOptionExecutionContext context)
{
_currentEvent = eventItem;
_currentContext = context != null ? context.Clone() : null;
_currentContext = context;
}
public void Clear()

View File

@ -15,12 +15,6 @@ namespace GeometryTD.Core
return Utility.Text.Format("Assets/GameMain/DataTables/{0}.{1}", assetName, fromBytes ? "bytes" : "txt");
}
public static string GetDictionaryAsset(string assetName, bool fromBytes)
{
return Utility.Text.Format("Assets/GameMain/Localization/{0}/Dictionaries/{1}.{2}",
GameEntry.Localization.Language, assetName, fromBytes ? "bytes" : "xml");
}
public static string GetFontAsset(string assetName)
{
return Utility.Text.Format("Assets/GameMain/Fonts/{0}.ttf", assetName);

View File

@ -121,7 +121,7 @@ namespace GeometryTD.Core
private static void TryRefreshRangesFromDataTables()
{
if (GameEntry.DataTable == null)
if (CoreServiceHub.StaticData == null)
{
return;
}
@ -154,13 +154,7 @@ namespace GeometryTD.Core
attackDamageRange = _attackDamageRange;
damageRandomRateRange = _damageRandomRateRange;
IDataTable<DRMuzzleComp> dataTable = GameEntry.DataTable.GetDataTable<DRMuzzleComp>();
if (dataTable == null || dataTable.Count <= 0)
{
return false;
}
DRMuzzleComp[] rows = dataTable.GetAllDataRows();
DRMuzzleComp[] rows = CoreServiceHub.StaticData.GetAllMuzzleComps();
if (rows == null || rows.Length <= 0)
{
return false;
@ -201,13 +195,7 @@ namespace GeometryTD.Core
rotateSpeedRange = _rotateSpeedRange;
attackRangeRange = _attackRangeRange;
IDataTable<DRBearingComp> dataTable = GameEntry.DataTable.GetDataTable<DRBearingComp>();
if (dataTable == null || dataTable.Count <= 0)
{
return false;
}
DRBearingComp[] rows = dataTable.GetAllDataRows();
DRBearingComp[] rows = CoreServiceHub.StaticData.GetAllBearingComps();
if (rows == null || rows.Length <= 0)
{
return false;
@ -246,13 +234,7 @@ namespace GeometryTD.Core
{
attackSpeedRange = _attackSpeedRange;
IDataTable<DRBaseComp> dataTable = GameEntry.DataTable.GetDataTable<DRBaseComp>();
if (dataTable == null || dataTable.Count <= 0)
{
return false;
}
DRBaseComp[] rows = dataTable.GetAllDataRows();
DRBaseComp[] rows = CoreServiceHub.StaticData.GetAllBaseComps();
if (rows == null || rows.Length <= 0)
{
return false;

View File

@ -0,0 +1,109 @@
using System.Collections.Generic;
using UnityEngine;
namespace GeometryTD.Core
{
public static class InventoryTowerEnduranceUtility
{
public static int ReduceTowerEndurance(
BackpackInventoryData inventory,
IReadOnlyList<long> towerInstanceIds,
float enduranceLoss)
{
float resolvedLoss = Mathf.Max(0f, enduranceLoss);
if (inventory?.Towers == null ||
inventory.Towers.Count <= 0 ||
resolvedLoss <= 0f ||
towerInstanceIds == null ||
towerInstanceIds.Count <= 0)
{
return 0;
}
Dictionary<long, MuzzleCompItemData> muzzleMap = BuildComponentMap(inventory.MuzzleComponents);
Dictionary<long, BearingCompItemData> bearingMap = BuildComponentMap(inventory.BearingComponents);
Dictionary<long, BaseCompItemData> baseMap = BuildComponentMap(inventory.BaseComponents);
HashSet<long> processedTowerIds = new HashSet<long>();
int affectedCount = 0;
for (int i = 0; i < towerInstanceIds.Count; i++)
{
long towerInstanceId = towerInstanceIds[i];
if (towerInstanceId <= 0 || !processedTowerIds.Add(towerInstanceId))
{
continue;
}
if (!InventoryParticipantUtility.TryGetTowerById(inventory, towerInstanceId, out TowerItemData tower) ||
tower == null)
{
continue;
}
bool towerAffected = false;
if (muzzleMap.TryGetValue(tower.MuzzleComponentInstanceId, out MuzzleCompItemData muzzleComp))
{
towerAffected |= TryReduceComponentEndurance(muzzleComp, resolvedLoss);
}
if (bearingMap.TryGetValue(tower.BearingComponentInstanceId, out BearingCompItemData bearingComp))
{
towerAffected |= TryReduceComponentEndurance(bearingComp, resolvedLoss);
}
if (baseMap.TryGetValue(tower.BaseComponentInstanceId, out BaseCompItemData baseComp))
{
towerAffected |= TryReduceComponentEndurance(baseComp, resolvedLoss);
}
if (towerAffected)
{
affectedCount++;
}
}
return affectedCount;
}
private static bool TryReduceComponentEndurance(TowerCompItemData component, float enduranceLoss)
{
if (component == null)
{
return false;
}
float originalEndurance = component.Endurance;
float nextEndurance = Mathf.Clamp(originalEndurance - Mathf.Max(0f, enduranceLoss), 0f, 100f);
if (nextEndurance >= originalEndurance)
{
return false;
}
component.Endurance = nextEndurance;
return true;
}
private static Dictionary<long, TComp> BuildComponentMap<TComp>(List<TComp> components)
where TComp : TowerCompItemData
{
Dictionary<long, TComp> map = new Dictionary<long, TComp>();
if (components == null || components.Count <= 0)
{
return map;
}
for (int i = 0; i < components.Count; i++)
{
TComp component = components[i];
if (component == null || component.InstanceId <= 0)
{
continue;
}
map[component.InstanceId] = component;
}
return map;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: abb0c22f9d11c6b4aae1d76fede68320
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using GameFramework.DataTable;
using GeometryTD.Core;
using UnityEngine;
using Random = System.Random;
@ -9,9 +7,11 @@ namespace GeometryTD.Core
{
public static class ShopPriceRuleService
{
public static int ResolveRandomBuyPrice(IReadOnlyList<DRShopPrice> shopPriceRows, RarityType rarity, Random random)
private static Dictionary<RarityType, DRShopPrice> _shopPriceByRarity;
public static int ResolveRandomBuyPrice(RarityType rarity, Random random)
{
if (!TryFindPriceRow(shopPriceRows, rarity, out DRShopPrice row) || row == null)
if (!TryFindPriceRow(rarity, out DRShopPrice row) || row == null)
{
return 0;
}
@ -21,21 +21,20 @@ namespace GeometryTD.Core
return random != null ? random.Next(min, max + 1) : min;
}
public static int ResolveComponentSalePrice(TowerCompItemData component, IDataTable<DRShopPrice> shopPriceTable = null)
public static int ResolveComponentSalePrice(TowerCompItemData component)
{
if (component == null)
{
return 0;
}
return ResolveBasePrice(component.Rarity, shopPriceTable);
return ResolveBasePrice(component.Rarity);
}
public static bool TryResolveTowerSalePrice(
TowerItemData tower,
BackpackInventoryData inventory,
out int price,
IDataTable<DRShopPrice> shopPriceTable = null)
out int price)
{
price = 0;
if (tower == null || inventory == null)
@ -50,22 +49,15 @@ namespace GeometryTD.Core
return false;
}
price = ResolveComponentSalePrice(muzzleComp, shopPriceTable) +
ResolveComponentSalePrice(bearingComp, shopPriceTable) +
ResolveComponentSalePrice(baseComp, shopPriceTable);
price = ResolveComponentSalePrice(muzzleComp) +
ResolveComponentSalePrice(bearingComp) +
ResolveComponentSalePrice(baseComp);
return price > 0;
}
public static int ResolveBasePrice(RarityType rarity, IDataTable<DRShopPrice> shopPriceTable = null)
public static int ResolveBasePrice(RarityType rarity)
{
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)
if (!TryFindPriceRow(rarity, out DRShopPrice row) || row == null)
{
return 0;
}
@ -75,25 +67,49 @@ namespace GeometryTD.Core
return Mathf.RoundToInt((min + max) * 0.5f);
}
private static bool TryFindPriceRow(IReadOnlyList<DRShopPrice> rows, RarityType rarity, out DRShopPrice result)
public static void ClearCache()
{
result = null;
if (rows == null)
_shopPriceByRarity = null;
}
private static bool TryFindPriceRow(RarityType rarity, out DRShopPrice result)
{
EnsureShopPriceCache();
if (_shopPriceByRarity == null)
{
result = null;
return false;
}
for (int i = 0; i < rows.Count; i++)
return _shopPriceByRarity.TryGetValue(rarity, out result);
}
private static void EnsureShopPriceCache()
{
if (_shopPriceByRarity != null)
{
DRShopPrice row = rows[i];
if (row != null && row.Rarity == rarity)
{
result = row;
return true;
}
return;
}
return false;
DRShopPrice[] rows = CoreServiceHub.StaticData?.GetAllShopPrices();
if (rows == null)
{
return;
}
Dictionary<RarityType, DRShopPrice> shopPriceByRarity = new Dictionary<RarityType, DRShopPrice>();
for (int i = 0; i < rows.Length; i++)
{
DRShopPrice row = rows[i];
if (row == null)
{
continue;
}
shopPriceByRarity[row.Rarity] = row;
}
_shopPriceByRarity = shopPriceByRarity;
}
private static bool TryGetComponentById<TComp>(IReadOnlyList<TComp> components, long instanceId, out TComp result)

View File

@ -13,9 +13,9 @@ namespace GeometryTD.Core
private static Sprite _muzzleSprite;
private static Sprite _bearingSprite;
private static Sprite _baseSprite;
private static bool s_RequestedMuzzle;
private static bool s_RequestedBearing;
private static bool s_RequestedBase;
private static bool _requestedMuzzle;
private static bool _requestedBearing;
private static bool _requestedBase;
public static Sprite ResolveTowerIconSprite(
TowerItemData tower,
@ -103,28 +103,23 @@ namespace GeometryTD.Core
private static void EnsureBaseSpritesRequested()
{
if (GameEntry.SpriteCache == null)
if (!_requestedMuzzle)
{
return;
_requestedMuzzle = true;
CoreServiceHub.SpriteCache.Get(MuzzleAssetName, sprite => { _muzzleSprite = sprite; });
}
if (!s_RequestedMuzzle)
if (!_requestedBearing)
{
s_RequestedMuzzle = true;
GameEntry.SpriteCache.GetSprite(MuzzleAssetName, sprite => { _muzzleSprite = sprite; });
_requestedBearing = true;
CoreServiceHub.SpriteCache.Get(BearingAssetName, sprite => { _bearingSprite = sprite; });
}
if (!s_RequestedBearing)
if (!_requestedBase)
{
s_RequestedBearing = true;
GameEntry.SpriteCache.GetSprite(BearingAssetName, sprite => { _bearingSprite = sprite; });
}
if (!s_RequestedBase)
{
s_RequestedBase = true;
GameEntry.SpriteCache.GetSprite(BaseAssetName, sprite => { _baseSprite = sprite; });
_requestedBase = true;
CoreServiceHub.SpriteCache.Get(BaseAssetName, sprite => { _baseSprite = sprite; });
}
}
}
}
}

View File

@ -194,7 +194,7 @@ namespace GeometryTD.Tests.EditMode
new AddRandomCompsEffect(new AddRandomCompsParam(2, RarityType.Blue))
})
});
RunNodeExecutionContext context = CreateContext();
EventOptionExecutionContext context = CreateContext();
BackpackInventoryData firstInventory = new BackpackInventoryData();
BackpackInventoryData secondInventory = new BackpackInventoryData();
@ -407,7 +407,7 @@ namespace GeometryTD.Tests.EditMode
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.That(method, Is.Not.Null);
RunNodeExecutionContext context = CreateContext();
RunNodeExecutionContext context = CreateRunNodeContext();
EventItem first = (EventItem)method.Invoke(component, new object[] { context });
EventItem second = (EventItem)method.Invoke(component, new object[] { context });
@ -437,7 +437,14 @@ namespace GeometryTD.Tests.EditMode
SetStaticInventoryGeneration(component);
}
private static RunNodeExecutionContext CreateContext()
private static EventOptionExecutionContext CreateContext()
{
return new EventOptionExecutionContext(
runSeed: 12345,
sequenceIndex: 3);
}
private static RunNodeExecutionContext CreateRunNodeContext()
{
return new RunNodeExecutionContext
{

View File

@ -10,7 +10,7 @@
"GeometryTD.Gameplay"
],
"includePlatforms": [
"Editor"
"Android"
],
"excludePlatforms": [],
"allowUnsafeCode": false,

View File

@ -1,7 +1,5 @@
using System.Collections.Generic;
using System.Reflection;
using CustomComponent;
using GeometryTD.CustomComponent;
using GeometryTD.Core;
using GeometryTD.Factory;
using GeometryTD.Procedure;