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 _shopPriceTable; public PlayerInventoryTradeService( PlayerInventoryQueryModel queryModel, PlayerInventoryCommandModel commandModel, IDataTable 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 itemIds, out PlayerInventorySaleResult result) { result = new PlayerInventorySaleResult(); if (itemIds == null || itemIds.Count <= 0) { result.FailureReason = PlayerInventorySaleFailureReason.InvalidSelection; return false; } HashSet uniqueIds = new HashSet(); List candidates = new List(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(List 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(IReadOnlyList 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(IReadOnlyList 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 EnsureShopPriceTable() { _shopPriceTable ??= GameEntry.DataTable.GetDataTable(); return _shopPriceTable; } } }