401 lines
14 KiB
C#
401 lines
14 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|