为 RepoForm 添加出售功能,ShopNode 只承载玩家购买组件的逻辑
This commit is contained in:
@@ -123,18 +123,7 @@ namespace GeometryTD.CustomComponent
|
||||
|
||||
private int ResolveRandomPrice(RarityType rarity, Random random)
|
||||
{
|
||||
for (int i = 0; i < _shopPriceRows.Count; i++)
|
||||
{
|
||||
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 random.Next(min, max + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
return ShopPriceRuleService.ResolveRandomBuyPrice(_shopPriceRows, rarity, random);
|
||||
}
|
||||
|
||||
private static IconAreaContext BuildIconAreaContext(TowerCompItemData item)
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace GeometryTD.CustomComponent
|
||||
private PlayerInventoryCommandModel _commandModel;
|
||||
private PlayerInventoryTowerRosterService _towerRosterService;
|
||||
private PlayerInventoryTowerAssemblyService _towerAssemblyService;
|
||||
private PlayerInventoryTradeService _tradeService;
|
||||
|
||||
public int Gold
|
||||
{
|
||||
@@ -100,6 +101,12 @@ namespace GeometryTD.CustomComponent
|
||||
return _commandModel.TryConsumeGold(costGold);
|
||||
}
|
||||
|
||||
public bool TryPurchaseComponent(TowerCompItemData item, int price)
|
||||
{
|
||||
EnsureInitialized();
|
||||
return _tradeService.TryPurchaseComponent(item, price);
|
||||
}
|
||||
|
||||
public void AddGold(int gainGold)
|
||||
{
|
||||
EnsureInitialized();
|
||||
@@ -138,6 +145,18 @@ namespace GeometryTD.CustomComponent
|
||||
return _towerRosterService.ReduceTowerEndurance(towerInstanceIds, enduranceLoss);
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -155,6 +174,7 @@ namespace GeometryTD.CustomComponent
|
||||
_commandModel ??= new PlayerInventoryCommandModel(_state);
|
||||
_towerRosterService ??= new PlayerInventoryTowerRosterService(_queryModel, MaxParticipantTowerCount);
|
||||
_towerAssemblyService ??= new PlayerInventoryTowerAssemblyService(_queryModel, _commandModel);
|
||||
_tradeService ??= new PlayerInventoryTradeService(_queryModel, _commandModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+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:
|
||||
Reference in New Issue
Block a user