This commit is contained in:
2026-04-30 19:33:16 +08:00
commit fb2252f688
556 changed files with 69721 additions and 0 deletions
@@ -0,0 +1,186 @@
using System.Collections.Generic;
using GeometryTD.CustomUtility;
using GeometryTD.Definition;
using UnityGameFramework.Runtime;
namespace GeometryTD.CustomComponent
{
public class PlayerInventoryComponent : GameFrameworkComponent
{
private const int MaxParticipantTowerCount = 4;
private PlayerInventoryState _state;
private PlayerInventoryQueryModel _queryModel;
private PlayerInventoryCommandModel _commandModel;
private PlayerInventoryTowerRosterService _towerRosterService;
private PlayerInventoryTowerAssemblyService _towerAssemblyService;
private PlayerInventoryTradeService _tradeService;
public int Gold
{
get
{
EnsureInitialized();
return _queryModel.Gold;
}
}
public void OnInit(BackpackInventoryData initialInventory = null)
{
EnsureServices();
_commandModel.Initialize(initialInventory ?? InventorySeedUtility.CreateSampleInventory(), MaxParticipantTowerCount);
BackpackInventoryData inventory = _queryModel.Inventory;
Log.Info(
"PlayerInventory initialized. Gold={0}, Tower={1}, Muzzle={2}, Bearing={3}, Base={4}.",
inventory.Gold,
inventory.Towers.Count,
inventory.MuzzleComponents.Count,
inventory.BearingComponents.Count,
inventory.BaseComponents.Count);
}
public BackpackInventoryData GetInventorySnapshot()
{
EnsureInitialized();
return _queryModel.GetSnapshot();
}
public void ReplaceInventorySnapshot(BackpackInventoryData inventorySnapshot)
{
EnsureServices();
_commandModel.ReplaceInventorySnapshot(inventorySnapshot, MaxParticipantTowerCount);
}
public IReadOnlyList<TowerItemData> GetParticipantTowerSnapshot()
{
EnsureInitialized();
BackpackInventoryData inventory = _queryModel.Inventory;
if (inventory?.ParticipantTowerInstanceIds == null || inventory.ParticipantTowerInstanceIds.Count <= 0)
{
return new List<TowerItemData>();
}
List<TowerItemData> result = new List<TowerItemData>(inventory.ParticipantTowerInstanceIds.Count);
for (int i = 0; i < inventory.ParticipantTowerInstanceIds.Count && result.Count < MaxParticipantTowerCount; i++)
{
long towerId = inventory.ParticipantTowerInstanceIds[i];
if (!_queryModel.TryGetTowerById(towerId, out TowerItemData tower) || tower == null)
{
continue;
}
result.Add(InventoryCloneUtility.CloneTower(tower));
}
return result;
}
public void MergeInventory(BackpackInventoryData gainedInventory)
{
EnsureInitialized();
PlayerInventoryMergeSummary summary = _commandModel.MergeInventory(gainedInventory);
if (!summary.HasAnyGain)
{
return;
}
Log.Info(
"PlayerInventory merged reward. Gold+{0}, Tower+{1}, Muzzle+{2}, Bearing+{3}, Base+{4}.",
summary.GainedGold,
summary.GainedTowerCount,
summary.GainedMuzzleCount,
summary.GainedBearingCount,
summary.GainedBaseCount);
}
public bool TryConsumeGold(int costGold)
{
EnsureInitialized();
return _commandModel.TryConsumeGold(costGold);
}
public bool TryPurchaseComponent(TowerCompItemData item, int price)
{
EnsureInitialized();
return _tradeService.TryPurchaseComponent(item, price);
}
public void AddGold(int gainGold)
{
EnsureInitialized();
_commandModel.AddGold(gainGold);
}
public ParticipantTowerAssignResult TryAddParticipantTower(long towerInstanceId, int maxCount = 4)
{
EnsureInitialized();
return _towerRosterService.TryAddParticipantTower(towerInstanceId, maxCount);
}
public bool TryRemoveParticipantTower(long towerInstanceId)
{
EnsureInitialized();
return _towerRosterService.TryRemoveParticipantTower(towerInstanceId);
}
public bool TryAssembleTower(
long muzzleInstanceId,
long bearingInstanceId,
long baseInstanceId,
out TowerItemData assembledTower)
{
EnsureInitialized();
return _towerAssemblyService.TryAssembleTower(
muzzleInstanceId,
bearingInstanceId,
baseInstanceId,
out assembledTower);
}
public int ReduceTowerEndurance(IReadOnlyList<long> towerInstanceIds, float enduranceLoss)
{
EnsureInitialized();
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()
{
if (_queryModel.IsInitialized)
{
return;
}
OnInit();
}
private void EnsureServices()
{
_state ??= new PlayerInventoryState();
_queryModel ??= new PlayerInventoryQueryModel(_state);
_commandModel ??= new PlayerInventoryCommandModel(_state);
_towerRosterService ??= new PlayerInventoryTowerRosterService(_queryModel, MaxParticipantTowerCount);
_towerAssemblyService ??= new PlayerInventoryTowerAssemblyService(_queryModel, _commandModel);
_tradeService ??= new PlayerInventoryTradeService(_queryModel, _commandModel);
}
}
}
@@ -0,0 +1,275 @@
using System;
using System.Collections.Generic;
using GeometryTD.CustomUtility;
using GeometryTD.Definition;
using UnityEngine;
namespace GeometryTD.CustomComponent
{
public struct PlayerInventoryMergeSummary
{
public int GainedGold;
public int GainedMuzzleCount;
public int GainedBearingCount;
public int GainedBaseCount;
public int GainedTowerCount;
public bool HasAnyGain =>
GainedGold > 0 || GainedMuzzleCount > 0 || GainedBearingCount > 0 || GainedBaseCount > 0 ||
GainedTowerCount > 0;
}
public sealed class PlayerInventoryState
{
public BackpackInventoryData Inventory = new BackpackInventoryData();
public long NextInstanceId = 1;
public bool IsInitialized;
}
public sealed class PlayerInventoryQueryModel
{
private readonly PlayerInventoryState _state;
public PlayerInventoryQueryModel(PlayerInventoryState state)
{
_state = state;
}
public BackpackInventoryData Inventory => _state.Inventory;
public bool IsInitialized => _state.IsInitialized;
public int Gold => _state.Inventory.Gold;
public BackpackInventoryData GetSnapshot()
{
return InventoryCloneUtility.CloneInventory(_state.Inventory);
}
public bool TryGetTowerById(long towerInstanceId, out TowerItemData tower)
{
return InventoryParticipantUtility.TryGetTowerById(_state.Inventory, towerInstanceId, out tower);
}
public bool TryGetComponentById<TComp>(List<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;
}
}
public sealed class PlayerInventoryCommandModel
{
private readonly PlayerInventoryState _state;
public PlayerInventoryCommandModel(PlayerInventoryState state)
{
_state = state;
}
public void Initialize(BackpackInventoryData sourceInventory, int maxParticipantTowerCount)
{
_state.Inventory = InventoryCloneUtility.CloneInventory(sourceInventory);
InventoryParticipantUtility.NormalizeParticipantState(_state.Inventory, maxParticipantTowerCount);
RebuildNextInstanceId();
_state.IsInitialized = true;
}
public void ReplaceInventorySnapshot(BackpackInventoryData sourceInventory, int maxParticipantTowerCount)
{
Initialize(sourceInventory, maxParticipantTowerCount);
}
public PlayerInventoryMergeSummary MergeInventory(BackpackInventoryData gainedInventory)
{
PlayerInventoryMergeSummary summary = default;
if (gainedInventory == null)
{
return summary;
}
summary.GainedGold = Mathf.Max(0, gainedInventory.Gold);
if (summary.GainedGold > 0)
{
_state.Inventory.Gold += summary.GainedGold;
}
if (gainedInventory.MuzzleComponents != null)
{
for (int i = 0; i < gainedInventory.MuzzleComponents.Count; i++)
{
MuzzleCompItemData source = gainedInventory.MuzzleComponents[i];
if (source == null)
{
continue;
}
MuzzleCompItemData cloned = InventoryCloneUtility.CloneMuzzleComp(source);
cloned.InstanceId = AllocateInstanceId();
_state.Inventory.MuzzleComponents.Add(cloned);
summary.GainedMuzzleCount++;
}
}
if (gainedInventory.BearingComponents != null)
{
for (int i = 0; i < gainedInventory.BearingComponents.Count; i++)
{
BearingCompItemData source = gainedInventory.BearingComponents[i];
if (source == null)
{
continue;
}
BearingCompItemData cloned = InventoryCloneUtility.CloneBearingComp(source);
cloned.InstanceId = AllocateInstanceId();
_state.Inventory.BearingComponents.Add(cloned);
summary.GainedBearingCount++;
}
}
if (gainedInventory.BaseComponents != null)
{
for (int i = 0; i < gainedInventory.BaseComponents.Count; i++)
{
BaseCompItemData source = gainedInventory.BaseComponents[i];
if (source == null)
{
continue;
}
BaseCompItemData cloned = InventoryCloneUtility.CloneBaseComp(source);
cloned.InstanceId = AllocateInstanceId();
_state.Inventory.BaseComponents.Add(cloned);
summary.GainedBaseCount++;
}
}
if (gainedInventory.Towers != null)
{
for (int i = 0; i < gainedInventory.Towers.Count; i++)
{
TowerItemData source = gainedInventory.Towers[i];
if (source == null)
{
continue;
}
TowerItemData cloned = InventoryCloneUtility.CloneTower(source);
cloned.InstanceId = AllocateInstanceId();
_state.Inventory.Towers.Add(cloned);
summary.GainedTowerCount++;
}
}
return summary;
}
public bool TryConsumeGold(int costGold)
{
int resolvedCost = Mathf.Max(0, costGold);
if (resolvedCost <= 0)
{
return true;
}
if (_state.Inventory.Gold < resolvedCost)
{
return false;
}
_state.Inventory.Gold -= resolvedCost;
return true;
}
public void AddGold(int gainGold)
{
int resolvedGain = Mathf.Max(0, gainGold);
if (resolvedGain <= 0)
{
return;
}
_state.Inventory.Gold += resolvedGain;
}
public long AllocateInstanceId()
{
if (_state.NextInstanceId < 1)
{
_state.NextInstanceId = 1;
}
return _state.NextInstanceId++;
}
private void RebuildNextInstanceId()
{
long maxInstanceId = 0;
BackpackInventoryData inventory = _state.Inventory;
if (inventory.Towers != null)
{
for (int i = 0; i < inventory.Towers.Count; i++)
{
TowerItemData item = inventory.Towers[i];
if (item != null)
{
maxInstanceId = Math.Max(maxInstanceId, item.InstanceId);
}
}
}
if (inventory.MuzzleComponents != null)
{
for (int i = 0; i < inventory.MuzzleComponents.Count; i++)
{
MuzzleCompItemData item = inventory.MuzzleComponents[i];
if (item != null)
{
maxInstanceId = Math.Max(maxInstanceId, item.InstanceId);
}
}
}
if (inventory.BearingComponents != null)
{
for (int i = 0; i < inventory.BearingComponents.Count; i++)
{
BearingCompItemData item = inventory.BearingComponents[i];
if (item != null)
{
maxInstanceId = Math.Max(maxInstanceId, item.InstanceId);
}
}
}
if (inventory.BaseComponents != null)
{
for (int i = 0; i < inventory.BaseComponents.Count; i++)
{
BaseCompItemData item = inventory.BaseComponents[i];
if (item != null)
{
maxInstanceId = Math.Max(maxInstanceId, item.InstanceId);
}
}
}
_state.NextInstanceId = Math.Max(1, maxInstanceId + 1);
}
}
}
@@ -0,0 +1,216 @@
using System.Collections.Generic;
using GameFramework.DataTable;
using GeometryTD.CustomUtility;
using GeometryTD.DataTable;
using GeometryTD.Definition;
using UnityEngine;
namespace GeometryTD.CustomComponent
{
internal sealed class PlayerInventoryTowerAssemblyService
{
private const int TowerLevelCount = 5;
private readonly PlayerInventoryQueryModel _queryModel;
private readonly PlayerInventoryCommandModel _commandModel;
private IDataTable<DRMuzzleComp> _drMuzzleComp;
private IDataTable<DRBearingComp> _drBearingComp;
private IDataTable<DRBaseComp> _drBaseComp;
public PlayerInventoryTowerAssemblyService(
PlayerInventoryQueryModel queryModel,
PlayerInventoryCommandModel commandModel)
{
_queryModel = queryModel;
_commandModel = commandModel;
}
public bool TryAssembleTower(
long muzzleInstanceId,
long bearingInstanceId,
long baseInstanceId,
out TowerItemData assembledTower)
{
assembledTower = null;
BackpackInventoryData inventory = _queryModel.Inventory;
if (muzzleInstanceId <= 0 || bearingInstanceId <= 0 || baseInstanceId <= 0)
{
return false;
}
if (!_queryModel.TryGetComponentById(inventory.MuzzleComponents, muzzleInstanceId, out MuzzleCompItemData muzzleComp) ||
!_queryModel.TryGetComponentById(inventory.BearingComponents, bearingInstanceId, out BearingCompItemData bearingComp) ||
!_queryModel.TryGetComponentById(inventory.BaseComponents, baseInstanceId, out BaseCompItemData baseComp))
{
return false;
}
if (muzzleComp.IsAssembledIntoTower || bearingComp.IsAssembledIntoTower || baseComp.IsAssembledIntoTower)
{
return false;
}
if (!TryBuildTowerStats(muzzleComp, bearingComp, baseComp, out TowerStatsData stats))
{
return false;
}
long towerInstanceId = _commandModel.AllocateInstanceId();
TowerItemData tower = new TowerItemData
{
InstanceId = towerInstanceId,
Name = $"组装防御塔-{towerInstanceId}",
Rarity = InventoryRarityRuleService.ResolveTowerRarity(
muzzleComp.Rarity,
bearingComp.Rarity,
baseComp.Rarity),
MuzzleComponentInstanceId = muzzleComp.InstanceId,
BearingComponentInstanceId = bearingComp.InstanceId,
BaseComponentInstanceId = baseComp.InstanceId,
Stats = stats
};
muzzleComp.IsAssembledIntoTower = true;
bearingComp.IsAssembledIntoTower = true;
baseComp.IsAssembledIntoTower = true;
inventory.Towers.Add(tower);
assembledTower = InventoryCloneUtility.CloneTower(tower);
return true;
}
private bool TryBuildTowerStats(
MuzzleCompItemData muzzleComp,
BearingCompItemData bearingComp,
BaseCompItemData baseComp,
out TowerStatsData stats)
{
stats = null;
if (muzzleComp == null || bearingComp == null || baseComp == null)
{
return false;
}
DRMuzzleComp muzzleConfig = EnsureMuzzleTable()?.GetDataRow(muzzleComp.ConfigId);
DRBearingComp bearingConfig = EnsureBearingTable()?.GetDataRow(bearingComp.ConfigId);
DRBaseComp baseConfig = EnsureBaseTable()?.GetDataRow(baseComp.ConfigId);
if (muzzleConfig == null || bearingConfig == null || baseConfig == null)
{
return false;
}
stats = new TowerStatsData
{
AttackDamage = BuildLevelIntArray(muzzleComp.AttackDamage, muzzleComp.Rarity, muzzleConfig.AttackDamagePerLevel),
DamageRandomRate = Mathf.Max(0f, muzzleComp.DamageRandomRate),
RotateSpeed = BuildLevelFloatArray(bearingComp.RotateSpeed, bearingComp.Rarity, bearingConfig.RotateSpeedPerLevel),
AttackRange = BuildLevelFloatArray(bearingComp.AttackRange, bearingComp.Rarity, bearingConfig.AttackRangePerLevel),
AttackSpeed = BuildLevelFloatArray(baseComp.AttackSpeed, baseComp.Rarity, baseConfig.AttackSpeedPerLevel),
AttackMethodType = muzzleComp.AttackMethodType,
AttackPropertyType = baseComp.AttackPropertyType,
TagRuntimes = TowerTagAggregationService.AggregateTowerTags(
muzzleComp.Tags,
bearingComp.Tags,
baseComp.Tags)
};
stats.Tags = TowerTagAggregationService.FlattenUniqueTags(stats.TagRuntimes);
return true;
}
private static int[] BuildLevelIntArray(int[] rarityBaseArray, RarityType rarity, int perLevel)
{
int baseValue = ResolveRarityBaseValue(rarityBaseArray, rarity);
int[] values = new int[TowerLevelCount];
for (int i = 0; i < values.Length; i++)
{
values[i] = Mathf.Max(0, baseValue + perLevel * i);
}
return values;
}
private static float[] BuildLevelFloatArray(float[] rarityBaseArray, RarityType rarity, float perLevel)
{
float baseValue = ResolveRarityBaseValue(rarityBaseArray, rarity);
float[] values = new float[TowerLevelCount];
for (int i = 0; i < values.Length; i++)
{
values[i] = Mathf.Max(0f, baseValue + perLevel * i);
}
return values;
}
private static int ResolveRarityBaseValue(int[] rarityBaseArray, RarityType rarity)
{
if (rarityBaseArray == null || rarityBaseArray.Length <= 0)
{
return 0;
}
int rarityIndex = Mathf.Clamp((int)rarity - 1, 0, rarityBaseArray.Length - 1);
return rarityBaseArray[rarityIndex];
}
private static float ResolveRarityBaseValue(float[] rarityBaseArray, RarityType rarity)
{
if (rarityBaseArray == null || rarityBaseArray.Length <= 0)
{
return 0f;
}
int rarityIndex = Mathf.Clamp((int)rarity - 1, 0, rarityBaseArray.Length - 1);
return rarityBaseArray[rarityIndex];
}
private IDataTable<DRMuzzleComp> EnsureMuzzleTable()
{
_drMuzzleComp ??= GameEntry.DataTable.GetDataTable<DRMuzzleComp>();
return _drMuzzleComp;
}
private IDataTable<DRBearingComp> EnsureBearingTable()
{
_drBearingComp ??= GameEntry.DataTable.GetDataTable<DRBearingComp>();
return _drBearingComp;
}
private IDataTable<DRBaseComp> EnsureBaseTable()
{
_drBaseComp ??= GameEntry.DataTable.GetDataTable<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;
}
}
@@ -0,0 +1,149 @@
using System.Collections.Generic;
using GeometryTD.CustomUtility;
using GeometryTD.Definition;
using UnityEngine;
namespace GeometryTD.CustomComponent
{
public sealed class PlayerInventoryTowerRosterService
{
private readonly PlayerInventoryQueryModel _queryModel;
private readonly int _maxParticipantTowerCount;
public PlayerInventoryTowerRosterService(PlayerInventoryQueryModel queryModel, int maxParticipantTowerCount)
{
_queryModel = queryModel;
_maxParticipantTowerCount = Mathf.Max(1, maxParticipantTowerCount);
}
public ParticipantTowerAssignResult TryAddParticipantTower(long towerInstanceId, int maxCount)
{
int resolvedMaxCount = Mathf.Max(1, maxCount);
resolvedMaxCount = Mathf.Min(resolvedMaxCount, _maxParticipantTowerCount);
return InventoryParticipantUtility.TryAddParticipantTower(
_queryModel.Inventory,
towerInstanceId,
resolvedMaxCount);
}
public bool TryRemoveParticipantTower(long towerInstanceId)
{
return InventoryParticipantUtility.TryRemoveParticipantTower(
_queryModel.Inventory,
towerInstanceId,
_maxParticipantTowerCount);
}
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);
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;
}
}
}
@@ -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;
}
}
}