为 RepoForm 添加出售功能,ShopNode 只承载玩家购买组件的逻辑
This commit is contained in:
parent
dc2aa59d58
commit
2e54acbc85
|
|
@ -123,18 +123,7 @@ namespace GeometryTD.CustomComponent
|
||||||
|
|
||||||
private int ResolveRandomPrice(RarityType rarity, Random random)
|
private int ResolveRandomPrice(RarityType rarity, Random random)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _shopPriceRows.Count; i++)
|
return ShopPriceRuleService.ResolveRandomBuyPrice(_shopPriceRows, rarity, random);
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IconAreaContext BuildIconAreaContext(TowerCompItemData item)
|
private static IconAreaContext BuildIconAreaContext(TowerCompItemData item)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ namespace GeometryTD.CustomComponent
|
||||||
private PlayerInventoryCommandModel _commandModel;
|
private PlayerInventoryCommandModel _commandModel;
|
||||||
private PlayerInventoryTowerRosterService _towerRosterService;
|
private PlayerInventoryTowerRosterService _towerRosterService;
|
||||||
private PlayerInventoryTowerAssemblyService _towerAssemblyService;
|
private PlayerInventoryTowerAssemblyService _towerAssemblyService;
|
||||||
|
private PlayerInventoryTradeService _tradeService;
|
||||||
|
|
||||||
public int Gold
|
public int Gold
|
||||||
{
|
{
|
||||||
|
|
@ -100,6 +101,12 @@ namespace GeometryTD.CustomComponent
|
||||||
return _commandModel.TryConsumeGold(costGold);
|
return _commandModel.TryConsumeGold(costGold);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool TryPurchaseComponent(TowerCompItemData item, int price)
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
return _tradeService.TryPurchaseComponent(item, price);
|
||||||
|
}
|
||||||
|
|
||||||
public void AddGold(int gainGold)
|
public void AddGold(int gainGold)
|
||||||
{
|
{
|
||||||
EnsureInitialized();
|
EnsureInitialized();
|
||||||
|
|
@ -138,6 +145,18 @@ namespace GeometryTD.CustomComponent
|
||||||
return _towerRosterService.ReduceTowerEndurance(towerInstanceIds, enduranceLoss);
|
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()
|
private void EnsureInitialized()
|
||||||
{
|
{
|
||||||
if (_queryModel.IsInitialized)
|
if (_queryModel.IsInitialized)
|
||||||
|
|
@ -155,6 +174,7 @@ namespace GeometryTD.CustomComponent
|
||||||
_commandModel ??= new PlayerInventoryCommandModel(_state);
|
_commandModel ??= new PlayerInventoryCommandModel(_state);
|
||||||
_towerRosterService ??= new PlayerInventoryTowerRosterService(_queryModel, MaxParticipantTowerCount);
|
_towerRosterService ??= new PlayerInventoryTowerRosterService(_queryModel, MaxParticipantTowerCount);
|
||||||
_towerAssemblyService ??= new PlayerInventoryTowerAssemblyService(_queryModel, _commandModel);
|
_towerAssemblyService ??= new PlayerInventoryTowerAssemblyService(_queryModel, _commandModel);
|
||||||
|
_tradeService ??= new PlayerInventoryTradeService(_queryModel, _commandModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 298cf9214556453cb5e7ec7c0605c350
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
using GameFramework;
|
||||||
|
using GameFramework.Event;
|
||||||
|
|
||||||
|
namespace GeometryTD.CustomEvent
|
||||||
|
{
|
||||||
|
public sealed class RepoSellCancelRequestedEventArgs : GameEventArgs
|
||||||
|
{
|
||||||
|
public static int EventId => typeof(RepoSellCancelRequestedEventArgs).GetHashCode();
|
||||||
|
|
||||||
|
public override int Id => EventId;
|
||||||
|
|
||||||
|
public static RepoSellCancelRequestedEventArgs Create()
|
||||||
|
{
|
||||||
|
return ReferencePool.Acquire<RepoSellCancelRequestedEventArgs>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Clear()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2cc34a22ea6347619b065c5874e0b715
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
using GameFramework;
|
||||||
|
using GameFramework.Event;
|
||||||
|
|
||||||
|
namespace GeometryTD.CustomEvent
|
||||||
|
{
|
||||||
|
public sealed class RepoSellConfirmRequestedEventArgs : GameEventArgs
|
||||||
|
{
|
||||||
|
public static int EventId => typeof(RepoSellConfirmRequestedEventArgs).GetHashCode();
|
||||||
|
|
||||||
|
public override int Id => EventId;
|
||||||
|
|
||||||
|
public static RepoSellConfirmRequestedEventArgs Create()
|
||||||
|
{
|
||||||
|
return ReferencePool.Acquire<RepoSellConfirmRequestedEventArgs>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Clear()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 598b87b9cb8e4783bc73db5e255d31f6
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
using GameFramework;
|
||||||
|
using GameFramework.Event;
|
||||||
|
|
||||||
|
namespace GeometryTD.CustomEvent
|
||||||
|
{
|
||||||
|
public sealed class RepoSellModeToggleRequestedEventArgs : GameEventArgs
|
||||||
|
{
|
||||||
|
public static int EventId => typeof(RepoSellModeToggleRequestedEventArgs).GetHashCode();
|
||||||
|
|
||||||
|
public override int Id => EventId;
|
||||||
|
|
||||||
|
public static RepoSellModeToggleRequestedEventArgs Create()
|
||||||
|
{
|
||||||
|
return ReferencePool.Acquire<RepoSellModeToggleRequestedEventArgs>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Clear()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 44692273ce6a417a875dea9675663d82
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
using GeometryTD.UI;
|
|
||||||
|
|
||||||
namespace GeometryTD.UI
|
namespace GeometryTD.UI
|
||||||
{
|
{
|
||||||
public class RepoFormContext : UIContext
|
public class RepoFormContext : UIContext
|
||||||
{
|
{
|
||||||
public string GoldText;
|
public string GoldText;
|
||||||
|
public RepoFormState State;
|
||||||
|
public bool ShowSellModeButton;
|
||||||
|
public string SellModeButtonText;
|
||||||
|
public bool ShowCombineArea;
|
||||||
|
public bool ShowSellArea;
|
||||||
public CombineAreaContext CombineAreaContext;
|
public CombineAreaContext CombineAreaContext;
|
||||||
|
public SellAreaContext SellAreaContext;
|
||||||
public CompAreaContext CompAreaContext;
|
public CompAreaContext CompAreaContext;
|
||||||
public ParticipantAreaContext ParticipantAreaContext;
|
public ParticipantAreaContext ParticipantAreaContext;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace GeometryTD.UI
|
||||||
|
{
|
||||||
|
public enum RepoFormState
|
||||||
|
{
|
||||||
|
Assemble = 0,
|
||||||
|
Sell = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9110fc764f3044bda40df4a8c02f320b
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -11,6 +11,9 @@ namespace GeometryTD.UI
|
||||||
public float EnduranceRate01;
|
public float EnduranceRate01;
|
||||||
public RepoItemClickActionType ClickActionType;
|
public RepoItemClickActionType ClickActionType;
|
||||||
public TowerCompSlotType ComponentSlotType;
|
public TowerCompSlotType ComponentSlotType;
|
||||||
|
public bool IsSellMode;
|
||||||
|
public bool IsSellable = true;
|
||||||
|
public bool IsSellSelected;
|
||||||
public IconAreaContext IconAreaContext;
|
public IconAreaContext IconAreaContext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
namespace GeometryTD.UI
|
||||||
|
{
|
||||||
|
public class SellAreaContext
|
||||||
|
{
|
||||||
|
public string TotalPriceText;
|
||||||
|
public bool CanConfirmSell;
|
||||||
|
public RepoItemContext[] ComponentItems;
|
||||||
|
public TowerRepoItemContext[] TowerItems;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f153d1a509df4bb69c6d231df5b60db9
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -23,21 +23,44 @@ namespace GeometryTD.UI
|
||||||
Dictionary<long, BearingCompItemData> bearingMap = BuildComponentMap(rawData.Inventory.BearingComponents);
|
Dictionary<long, BearingCompItemData> bearingMap = BuildComponentMap(rawData.Inventory.BearingComponents);
|
||||||
Dictionary<long, BaseCompItemData> baseMap = BuildComponentMap(rawData.Inventory.BaseComponents);
|
Dictionary<long, BaseCompItemData> baseMap = BuildComponentMap(rawData.Inventory.BaseComponents);
|
||||||
Dictionary<long, TowerItemData> towerMap = BuildTowerMap(rawData.Inventory.Towers);
|
Dictionary<long, TowerItemData> towerMap = BuildTowerMap(rawData.Inventory.Towers);
|
||||||
|
HashSet<long> selectedSellItemIds = BuildSelectedSellItemSet(rawData.SelectedSellItemIds);
|
||||||
|
bool isSellState = rawData.State == RepoFormState.Sell;
|
||||||
|
|
||||||
List<RepoItemContext> componentItems = new List<RepoItemContext>();
|
List<RepoItemContext> componentItems = new List<RepoItemContext>();
|
||||||
List<TowerRepoItemContext> towerItems = new List<TowerRepoItemContext>();
|
List<TowerRepoItemContext> towerItems = new List<TowerRepoItemContext>();
|
||||||
|
List<RepoItemContext> selectedSellComponentItems = new List<RepoItemContext>();
|
||||||
|
List<TowerRepoItemContext> selectedSellTowerItems = new List<TowerRepoItemContext>();
|
||||||
|
|
||||||
if (rawData.Inventory.Towers != null)
|
if (rawData.Inventory.Towers != null)
|
||||||
{
|
{
|
||||||
foreach (var tower in rawData.Inventory.Towers)
|
foreach (TowerItemData tower in rawData.Inventory.Towers)
|
||||||
{
|
{
|
||||||
if (tower == null)
|
if (tower == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
TowerRepoItemContext towerContext = BuildTowerRepoItemContext(tower, muzzleMap, bearingMap, baseMap,
|
bool isParticipantTower = rawData.Inventory.ParticipantTowerInstanceIds != null &&
|
||||||
RepoItemClickActionType.OpenDetail, true);
|
rawData.Inventory.ParticipantTowerInstanceIds.Contains(tower.InstanceId);
|
||||||
|
bool isSellSelected = isSellState && selectedSellItemIds.Contains(tower.InstanceId);
|
||||||
|
|
||||||
|
TowerRepoItemContext towerContext = BuildTowerRepoItemContext(
|
||||||
|
tower,
|
||||||
|
muzzleMap,
|
||||||
|
bearingMap,
|
||||||
|
baseMap,
|
||||||
|
RepoItemClickActionType.OpenDetail,
|
||||||
|
!isSellState,
|
||||||
|
isSellState,
|
||||||
|
!isParticipantTower,
|
||||||
|
isSellSelected,
|
||||||
|
false);
|
||||||
AddTowerItemContext(towerItems, towerContext);
|
AddTowerItemContext(towerItems, towerContext);
|
||||||
|
if (isSellSelected)
|
||||||
|
{
|
||||||
|
selectedSellTowerItems.Add(towerContext);
|
||||||
|
}
|
||||||
|
|
||||||
AddItemDescSeed(
|
AddItemDescSeed(
|
||||||
tower.InstanceId,
|
tower.InstanceId,
|
||||||
tower.Name,
|
tower.Name,
|
||||||
|
|
@ -50,23 +73,24 @@ namespace GeometryTD.UI
|
||||||
|
|
||||||
if (rawData.Inventory.MuzzleComponents != null)
|
if (rawData.Inventory.MuzzleComponents != null)
|
||||||
{
|
{
|
||||||
foreach (var item in rawData.Inventory.MuzzleComponents)
|
foreach (MuzzleCompItemData item in rawData.Inventory.MuzzleComponents)
|
||||||
{
|
{
|
||||||
if (item == null || item.IsAssembledIntoTower)
|
if (item == null || item.IsAssembledIntoTower)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
RepoItemContext componentContext = new RepoItemContext
|
RepoItemContext componentContext = BuildSellableComponentContext(
|
||||||
{
|
item,
|
||||||
InstanceId = item.InstanceId,
|
TowerCompSlotType.Muzzle,
|
||||||
CanDrag = true,
|
isSellState,
|
||||||
EnduranceRate01 = ItemDescUtility.ResolveComponentEnduranceRate(item),
|
selectedSellItemIds.Contains(item.InstanceId));
|
||||||
ClickActionType = RepoItemClickActionType.OpenDetail,
|
|
||||||
ComponentSlotType = TowerCompSlotType.Muzzle,
|
|
||||||
IconAreaContext = BuildIconAreaContext(item)
|
|
||||||
};
|
|
||||||
AddComponentItemContext(componentItems, componentContext);
|
AddComponentItemContext(componentItems, componentContext);
|
||||||
|
if (componentContext.IsSellSelected)
|
||||||
|
{
|
||||||
|
selectedSellComponentItems.Add(componentContext);
|
||||||
|
}
|
||||||
|
|
||||||
AddItemDescSeed(
|
AddItemDescSeed(
|
||||||
item.InstanceId,
|
item.InstanceId,
|
||||||
item.Name,
|
item.Name,
|
||||||
|
|
@ -86,16 +110,17 @@ namespace GeometryTD.UI
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
RepoItemContext componentContext = new RepoItemContext
|
RepoItemContext componentContext = BuildSellableComponentContext(
|
||||||
{
|
item,
|
||||||
InstanceId = item.InstanceId,
|
TowerCompSlotType.Bearing,
|
||||||
CanDrag = true,
|
isSellState,
|
||||||
EnduranceRate01 = ItemDescUtility.ResolveComponentEnduranceRate(item),
|
selectedSellItemIds.Contains(item.InstanceId));
|
||||||
ClickActionType = RepoItemClickActionType.OpenDetail,
|
|
||||||
ComponentSlotType = TowerCompSlotType.Bearing,
|
|
||||||
IconAreaContext = BuildIconAreaContext(item)
|
|
||||||
};
|
|
||||||
AddComponentItemContext(componentItems, componentContext);
|
AddComponentItemContext(componentItems, componentContext);
|
||||||
|
if (componentContext.IsSellSelected)
|
||||||
|
{
|
||||||
|
selectedSellComponentItems.Add(componentContext);
|
||||||
|
}
|
||||||
|
|
||||||
AddItemDescSeed(
|
AddItemDescSeed(
|
||||||
item.InstanceId,
|
item.InstanceId,
|
||||||
item.Name,
|
item.Name,
|
||||||
|
|
@ -115,16 +140,17 @@ namespace GeometryTD.UI
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
RepoItemContext componentContext = new RepoItemContext
|
RepoItemContext componentContext = BuildSellableComponentContext(
|
||||||
{
|
item,
|
||||||
InstanceId = item.InstanceId,
|
TowerCompSlotType.Base,
|
||||||
CanDrag = true,
|
isSellState,
|
||||||
EnduranceRate01 = ItemDescUtility.ResolveComponentEnduranceRate(item),
|
selectedSellItemIds.Contains(item.InstanceId));
|
||||||
ClickActionType = RepoItemClickActionType.OpenDetail,
|
|
||||||
ComponentSlotType = TowerCompSlotType.Base,
|
|
||||||
IconAreaContext = BuildIconAreaContext(item)
|
|
||||||
};
|
|
||||||
AddComponentItemContext(componentItems, componentContext);
|
AddComponentItemContext(componentItems, componentContext);
|
||||||
|
if (componentContext.IsSellSelected)
|
||||||
|
{
|
||||||
|
selectedSellComponentItems.Add(componentContext);
|
||||||
|
}
|
||||||
|
|
||||||
AddItemDescSeed(
|
AddItemDescSeed(
|
||||||
item.InstanceId,
|
item.InstanceId,
|
||||||
item.Name,
|
item.Name,
|
||||||
|
|
@ -141,7 +167,19 @@ namespace GeometryTD.UI
|
||||||
return new RepoFormContext
|
return new RepoFormContext
|
||||||
{
|
{
|
||||||
GoldText = $"金币: {rawData.Inventory.Gold}",
|
GoldText = $"金币: {rawData.Inventory.Gold}",
|
||||||
|
State = rawData.State,
|
||||||
|
ShowSellModeButton = rawData.State == RepoFormState.Assemble,
|
||||||
|
SellModeButtonText = "出售模式",
|
||||||
|
ShowCombineArea = rawData.State == RepoFormState.Assemble,
|
||||||
|
ShowSellArea = rawData.State == RepoFormState.Sell,
|
||||||
CombineAreaContext = new CombineAreaContext(),
|
CombineAreaContext = new CombineAreaContext(),
|
||||||
|
SellAreaContext = new SellAreaContext
|
||||||
|
{
|
||||||
|
TotalPriceText = $"总价值: {rawData.SelectedSellTotalPrice}",
|
||||||
|
CanConfirmSell = rawData.State == RepoFormState.Sell && rawData.SelectedSellItemCount > 0,
|
||||||
|
ComponentItems = selectedSellComponentItems.ToArray(),
|
||||||
|
TowerItems = selectedSellTowerItems.ToArray()
|
||||||
|
},
|
||||||
CompAreaContext = new CompAreaContext
|
CompAreaContext = new CompAreaContext
|
||||||
{
|
{
|
||||||
ComponentItems = componentItems.ToArray(),
|
ComponentItems = componentItems.ToArray(),
|
||||||
|
|
@ -151,6 +189,26 @@ namespace GeometryTD.UI
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static RepoItemContext BuildSellableComponentContext(
|
||||||
|
TowerCompItemData item,
|
||||||
|
TowerCompSlotType slotType,
|
||||||
|
bool isSellState,
|
||||||
|
bool isSellSelected)
|
||||||
|
{
|
||||||
|
return new RepoItemContext
|
||||||
|
{
|
||||||
|
InstanceId = item.InstanceId,
|
||||||
|
CanDrag = !isSellState,
|
||||||
|
EnduranceRate01 = ItemDescUtility.ResolveComponentEnduranceRate(item),
|
||||||
|
ClickActionType = RepoItemClickActionType.OpenDetail,
|
||||||
|
ComponentSlotType = slotType,
|
||||||
|
IsSellMode = isSellState,
|
||||||
|
IsSellable = true,
|
||||||
|
IsSellSelected = isSellState && isSellSelected,
|
||||||
|
IconAreaContext = BuildIconAreaContext(item)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private void AddComponentItemContext(List<RepoItemContext> items, RepoItemContext itemContext)
|
private void AddComponentItemContext(List<RepoItemContext> items, RepoItemContext itemContext)
|
||||||
{
|
{
|
||||||
if (itemContext == null)
|
if (itemContext == null)
|
||||||
|
|
@ -218,7 +276,7 @@ namespace GeometryTD.UI
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var item in items)
|
foreach (TComp item in items)
|
||||||
{
|
{
|
||||||
if (item == null || item.InstanceId <= 0)
|
if (item == null || item.InstanceId <= 0)
|
||||||
{
|
{
|
||||||
|
|
@ -231,6 +289,26 @@ namespace GeometryTD.UI
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static HashSet<long> BuildSelectedSellItemSet(IReadOnlyList<long> selectedSellItemIds)
|
||||||
|
{
|
||||||
|
HashSet<long> selectedIds = new HashSet<long>();
|
||||||
|
if (selectedSellItemIds == null)
|
||||||
|
{
|
||||||
|
return selectedIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < selectedSellItemIds.Count; i++)
|
||||||
|
{
|
||||||
|
long itemId = selectedSellItemIds[i];
|
||||||
|
if (itemId > 0)
|
||||||
|
{
|
||||||
|
selectedIds.Add(itemId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedIds;
|
||||||
|
}
|
||||||
|
|
||||||
private static Dictionary<long, TowerItemData> BuildTowerMap(IReadOnlyList<TowerItemData> towers)
|
private static Dictionary<long, TowerItemData> BuildTowerMap(IReadOnlyList<TowerItemData> towers)
|
||||||
{
|
{
|
||||||
Dictionary<long, TowerItemData> map = new Dictionary<long, TowerItemData>();
|
Dictionary<long, TowerItemData> map = new Dictionary<long, TowerItemData>();
|
||||||
|
|
@ -239,7 +317,7 @@ namespace GeometryTD.UI
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var tower in towers)
|
foreach (TowerItemData tower in towers)
|
||||||
{
|
{
|
||||||
if (tower == null || tower.InstanceId <= 0)
|
if (tower == null || tower.InstanceId <= 0)
|
||||||
{
|
{
|
||||||
|
|
@ -263,7 +341,7 @@ namespace GeometryTD.UI
|
||||||
List<TowerRepoItemContext> participantItems = new List<TowerRepoItemContext>();
|
List<TowerRepoItemContext> participantItems = new List<TowerRepoItemContext>();
|
||||||
if (inventory?.ParticipantTowerInstanceIds != null && towerMap != null)
|
if (inventory?.ParticipantTowerInstanceIds != null && towerMap != null)
|
||||||
{
|
{
|
||||||
foreach (var towerId in inventory.ParticipantTowerInstanceIds)
|
foreach (long towerId in inventory.ParticipantTowerInstanceIds)
|
||||||
{
|
{
|
||||||
if (towerId <= 0)
|
if (towerId <= 0)
|
||||||
{
|
{
|
||||||
|
|
@ -280,8 +358,17 @@ namespace GeometryTD.UI
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
TowerRepoItemContext towerContext = BuildTowerRepoItemContext(tower, muzzleMap, bearingMap, baseMap,
|
TowerRepoItemContext towerContext = BuildTowerRepoItemContext(
|
||||||
RepoItemClickActionType.RemoveParticipant, false);
|
tower,
|
||||||
|
muzzleMap,
|
||||||
|
bearingMap,
|
||||||
|
baseMap,
|
||||||
|
RepoItemClickActionType.RemoveParticipant,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false);
|
||||||
if (towerContext != null)
|
if (towerContext != null)
|
||||||
{
|
{
|
||||||
participantItems.Add(towerContext);
|
participantItems.Add(towerContext);
|
||||||
|
|
@ -304,6 +391,11 @@ namespace GeometryTD.UI
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Context != null && Context.State == RepoFormState.Sell)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (long towerId in _compAreaTowerIds)
|
foreach (long towerId in _compAreaTowerIds)
|
||||||
{
|
{
|
||||||
Form.SetRepoItemSelected(towerId, _participantTowerIds.Contains(towerId));
|
Form.SetRepoItemSelected(towerId, _participantTowerIds.Contains(towerId));
|
||||||
|
|
@ -334,7 +426,11 @@ namespace GeometryTD.UI
|
||||||
IReadOnlyDictionary<long, BearingCompItemData> bearingMap,
|
IReadOnlyDictionary<long, BearingCompItemData> bearingMap,
|
||||||
IReadOnlyDictionary<long, BaseCompItemData> baseMap,
|
IReadOnlyDictionary<long, BaseCompItemData> baseMap,
|
||||||
RepoItemClickActionType clickActionType,
|
RepoItemClickActionType clickActionType,
|
||||||
bool canDrag)
|
bool canDrag,
|
||||||
|
bool isSellMode,
|
||||||
|
bool isSellable,
|
||||||
|
bool isSellSelected,
|
||||||
|
bool highlightSelected)
|
||||||
{
|
{
|
||||||
if (tower == null)
|
if (tower == null)
|
||||||
{
|
{
|
||||||
|
|
@ -348,6 +444,9 @@ namespace GeometryTD.UI
|
||||||
EnduranceRate01 = ItemDescUtility.ResolveTowerEnduranceRate(tower, muzzleMap, bearingMap, baseMap),
|
EnduranceRate01 = ItemDescUtility.ResolveTowerEnduranceRate(tower, muzzleMap, bearingMap, baseMap),
|
||||||
ClickActionType = clickActionType,
|
ClickActionType = clickActionType,
|
||||||
ComponentSlotType = TowerCompSlotType.None,
|
ComponentSlotType = TowerCompSlotType.None,
|
||||||
|
IsSellMode = isSellMode,
|
||||||
|
IsSellable = isSellable,
|
||||||
|
IsSellSelected = isSellSelected || highlightSelected,
|
||||||
IconAreaContext = new TowerIconAreaContext
|
IconAreaContext = new TowerIconAreaContext
|
||||||
{
|
{
|
||||||
Rarity = tower.Rarity,
|
Rarity = tower.Rarity,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using GeometryTD.CustomEvent;
|
||||||
using GeometryTD.CustomUtility;
|
using GeometryTD.CustomUtility;
|
||||||
using GeometryTD.Definition;
|
using GeometryTD.Definition;
|
||||||
using GameFramework.Event;
|
using GameFramework.Event;
|
||||||
|
using GeometryTD.CustomComponent;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityGameFramework.Runtime;
|
using UnityGameFramework.Runtime;
|
||||||
|
|
||||||
|
|
@ -43,6 +44,9 @@ namespace GeometryTD.UI
|
||||||
GameEntry.Event.Subscribe(RepoCombineRequestedEventArgs.EventId, OnRepoCombineRequested);
|
GameEntry.Event.Subscribe(RepoCombineRequestedEventArgs.EventId, OnRepoCombineRequested);
|
||||||
GameEntry.Event.Subscribe(RepoParticipantAssignRequestedEventArgs.EventId, OnRepoParticipantAssignRequested);
|
GameEntry.Event.Subscribe(RepoParticipantAssignRequestedEventArgs.EventId, OnRepoParticipantAssignRequested);
|
||||||
GameEntry.Event.Subscribe(RepoFormReturnEventArgs.EventId, OnRepoFormReturn);
|
GameEntry.Event.Subscribe(RepoFormReturnEventArgs.EventId, OnRepoFormReturn);
|
||||||
|
GameEntry.Event.Subscribe(RepoSellModeToggleRequestedEventArgs.EventId, OnRepoSellModeToggleRequested);
|
||||||
|
GameEntry.Event.Subscribe(RepoSellCancelRequestedEventArgs.EventId, OnRepoSellCancelRequested);
|
||||||
|
GameEntry.Event.Subscribe(RepoSellConfirmRequestedEventArgs.EventId, OnRepoSellConfirmRequested);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UnsubscribeCustomEvents()
|
protected override void UnsubscribeCustomEvents()
|
||||||
|
|
@ -53,6 +57,9 @@ namespace GeometryTD.UI
|
||||||
GameEntry.Event.Unsubscribe(RepoCombineRequestedEventArgs.EventId, OnRepoCombineRequested);
|
GameEntry.Event.Unsubscribe(RepoCombineRequestedEventArgs.EventId, OnRepoCombineRequested);
|
||||||
GameEntry.Event.Unsubscribe(RepoParticipantAssignRequestedEventArgs.EventId, OnRepoParticipantAssignRequested);
|
GameEntry.Event.Unsubscribe(RepoParticipantAssignRequestedEventArgs.EventId, OnRepoParticipantAssignRequested);
|
||||||
GameEntry.Event.Unsubscribe(RepoFormReturnEventArgs.EventId, OnRepoFormReturn);
|
GameEntry.Event.Unsubscribe(RepoFormReturnEventArgs.EventId, OnRepoFormReturn);
|
||||||
|
GameEntry.Event.Unsubscribe(RepoSellModeToggleRequestedEventArgs.EventId, OnRepoSellModeToggleRequested);
|
||||||
|
GameEntry.Event.Unsubscribe(RepoSellCancelRequestedEventArgs.EventId, OnRepoSellCancelRequested);
|
||||||
|
GameEntry.Event.Unsubscribe(RepoSellConfirmRequestedEventArgs.EventId, OnRepoSellConfirmRequested);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int? OpenUI(object userData = null)
|
public override int? OpenUI(object userData = null)
|
||||||
|
|
@ -125,6 +132,24 @@ namespace GeometryTD.UI
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_useCase != null && _useCase.State == RepoFormState.Sell)
|
||||||
|
{
|
||||||
|
if (_useCase.TryToggleSellSelection(
|
||||||
|
args.ItemId,
|
||||||
|
out RepoFormRawData sellModeRawData,
|
||||||
|
out PlayerInventorySaleFailureReason failureReason))
|
||||||
|
{
|
||||||
|
SetContext(BuildContext(sellModeRawData));
|
||||||
|
RefreshCurrentUI();
|
||||||
|
}
|
||||||
|
else if (failureReason == PlayerInventorySaleFailureReason.ParticipantTower)
|
||||||
|
{
|
||||||
|
OpenSellBlockedDialog("参战防御塔不能直接出售,请先移出参战区。");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (clickActionType == RepoItemClickActionType.RemoveParticipant)
|
if (clickActionType == RepoItemClickActionType.RemoveParticipant)
|
||||||
{
|
{
|
||||||
if (_useCase == null || Form == null)
|
if (_useCase == null || Form == null)
|
||||||
|
|
@ -165,6 +190,11 @@ namespace GeometryTD.UI
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Context != null && Context.State == RepoFormState.Sell)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(e is RepoItemDragEndedEventArgs args))
|
if (!(e is RepoItemDragEndedEventArgs args))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -188,6 +218,11 @@ namespace GeometryTD.UI
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Context != null && Context.State == RepoFormState.Sell)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(e is CombineSlotClickedEventArgs args))
|
if (!(e is CombineSlotClickedEventArgs args))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -257,6 +292,11 @@ namespace GeometryTD.UI
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Context != null && Context.State == RepoFormState.Sell)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(e is RepoParticipantAssignRequestedEventArgs args))
|
if (!(e is RepoParticipantAssignRequestedEventArgs args))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -288,6 +328,65 @@ namespace GeometryTD.UI
|
||||||
RefreshParticipantAreaOnly();
|
RefreshParticipantAreaOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnRepoSellModeToggleRequested(object sender, GameEventArgs e)
|
||||||
|
{
|
||||||
|
if (!IsEventFromCurrentForm(sender) || !(e is RepoSellModeToggleRequestedEventArgs) || _useCase == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_useCase.State != RepoFormState.Assemble)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RepoFormRawData rawData = _useCase.EnterSellState();
|
||||||
|
SetContext(BuildContext(rawData));
|
||||||
|
RefreshCurrentUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRepoSellCancelRequested(object sender, GameEventArgs e)
|
||||||
|
{
|
||||||
|
if (!IsEventFromCurrentForm(sender) || !(e is RepoSellCancelRequestedEventArgs) || _useCase == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_useCase.State != RepoFormState.Sell)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetContext(BuildContext(_useCase.ExitSellState()));
|
||||||
|
RefreshCurrentUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRepoSellConfirmRequested(object sender, GameEventArgs e)
|
||||||
|
{
|
||||||
|
if (!IsEventFromCurrentForm(sender) || !(e is RepoSellConfirmRequestedEventArgs) || _useCase == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_useCase.TryConfirmSellSelection(out RepoFormRawData rawData, out PlayerInventorySaleResult result))
|
||||||
|
{
|
||||||
|
if (result != null && result.FailureReason == PlayerInventorySaleFailureReason.ParticipantTower)
|
||||||
|
{
|
||||||
|
OpenSellBlockedDialog("参战防御塔不能直接出售,请先移出参战区。");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetContext(BuildContext(rawData));
|
||||||
|
RefreshCurrentUI();
|
||||||
|
|
||||||
|
if (GameEntry.UI.GetUIForm(UIFormType.ShopForm) != null)
|
||||||
|
{
|
||||||
|
GameEntry.UIRouter.OpenUI(UIFormType.ShopForm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool IsEventFromCurrentForm(object sender)
|
private bool IsEventFromCurrentForm(object sender)
|
||||||
{
|
{
|
||||||
if (Form == null)
|
if (Form == null)
|
||||||
|
|
@ -309,6 +408,18 @@ namespace GeometryTD.UI
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void OpenSellBlockedDialog(string message)
|
||||||
|
{
|
||||||
|
GameEntry.UIRouter.OpenUI(UIFormType.DialogForm, new DialogFormRawData
|
||||||
|
{
|
||||||
|
Mode = 1,
|
||||||
|
Title = "无法出售",
|
||||||
|
Message = message,
|
||||||
|
PauseGame = false,
|
||||||
|
ConfirmText = "知道了"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,9 @@ namespace GeometryTD.UI
|
||||||
public class RepoFormRawData
|
public class RepoFormRawData
|
||||||
{
|
{
|
||||||
public BackpackInventoryData Inventory;
|
public BackpackInventoryData Inventory;
|
||||||
|
public RepoFormState State;
|
||||||
|
public long[] SelectedSellItemIds;
|
||||||
|
public int SelectedSellItemCount;
|
||||||
|
public int SelectedSellTotalPrice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
using GeometryTD.CustomUtility;
|
using System.Collections.Generic;
|
||||||
|
using GeometryTD.CustomComponent;
|
||||||
|
using GeometryTD.CustomUtility;
|
||||||
using GeometryTD.Definition;
|
using GeometryTD.Definition;
|
||||||
|
|
||||||
namespace GeometryTD.UI
|
namespace GeometryTD.UI
|
||||||
|
|
@ -7,6 +9,8 @@ namespace GeometryTD.UI
|
||||||
{
|
{
|
||||||
private const int MaxParticipantCount = 4;
|
private const int MaxParticipantCount = 4;
|
||||||
private BackpackInventoryData _fallbackInventory;
|
private BackpackInventoryData _fallbackInventory;
|
||||||
|
private readonly HashSet<long> _selectedSellItemIds = new HashSet<long>();
|
||||||
|
private RepoFormState _state = RepoFormState.Assemble;
|
||||||
|
|
||||||
public RepoFormRawData CreateInitialModel()
|
public RepoFormRawData CreateInitialModel()
|
||||||
{
|
{
|
||||||
|
|
@ -15,12 +19,23 @@ namespace GeometryTD.UI
|
||||||
: GetOrCreateFallbackInventory();
|
: GetOrCreateFallbackInventory();
|
||||||
return new RepoFormRawData
|
return new RepoFormRawData
|
||||||
{
|
{
|
||||||
Inventory = sample
|
Inventory = sample,
|
||||||
|
State = _state,
|
||||||
|
SelectedSellItemIds = BuildSelectedSellItemArray(),
|
||||||
|
SelectedSellItemCount = _selectedSellItemIds.Count,
|
||||||
|
SelectedSellTotalPrice = ResolveSelectedSellTotalPrice()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RepoFormState State => _state;
|
||||||
|
|
||||||
public bool TryAssembleTower(long muzzleItemId, long bearingItemId, long baseItemId)
|
public bool TryAssembleTower(long muzzleItemId, long bearingItemId, long baseItemId)
|
||||||
{
|
{
|
||||||
|
if (_state == RepoFormState.Sell)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (GameEntry.PlayerInventory == null)
|
if (GameEntry.PlayerInventory == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -35,6 +50,15 @@ namespace GeometryTD.UI
|
||||||
|
|
||||||
public ParticipantTowerAssignResult TryAddParticipantTower(long towerItemId)
|
public ParticipantTowerAssignResult TryAddParticipantTower(long towerItemId)
|
||||||
{
|
{
|
||||||
|
if (_state == RepoFormState.Sell)
|
||||||
|
{
|
||||||
|
return new ParticipantTowerAssignResult
|
||||||
|
{
|
||||||
|
TowerInstanceId = towerItemId,
|
||||||
|
FailureReason = ParticipantTowerAssignFailureReason.ParticipantAreaFull
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (GameEntry.PlayerInventory == null)
|
if (GameEntry.PlayerInventory == null)
|
||||||
{
|
{
|
||||||
BackpackInventoryData fallbackInventory = GetOrCreateFallbackInventory();
|
BackpackInventoryData fallbackInventory = GetOrCreateFallbackInventory();
|
||||||
|
|
@ -44,11 +68,16 @@ namespace GeometryTD.UI
|
||||||
MaxParticipantCount);
|
MaxParticipantCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
return GameEntry.PlayerInventory.TryAddParticipantTower(towerItemId, 4);
|
return GameEntry.PlayerInventory.TryAddParticipantTower(towerItemId, MaxParticipantCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryRemoveParticipantTower(long towerItemId)
|
public bool TryRemoveParticipantTower(long towerItemId)
|
||||||
{
|
{
|
||||||
|
if (_state == RepoFormState.Sell)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (GameEntry.PlayerInventory == null)
|
if (GameEntry.PlayerInventory == null)
|
||||||
{
|
{
|
||||||
BackpackInventoryData fallbackInventory = GetOrCreateFallbackInventory();
|
BackpackInventoryData fallbackInventory = GetOrCreateFallbackInventory();
|
||||||
|
|
@ -61,12 +90,120 @@ namespace GeometryTD.UI
|
||||||
return GameEntry.PlayerInventory.TryRemoveParticipantTower(towerItemId);
|
return GameEntry.PlayerInventory.TryRemoveParticipantTower(towerItemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RepoFormRawData EnterSellState()
|
||||||
|
{
|
||||||
|
_state = RepoFormState.Sell;
|
||||||
|
_selectedSellItemIds.Clear();
|
||||||
|
return CreateInitialModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RepoFormRawData ExitSellState()
|
||||||
|
{
|
||||||
|
_state = RepoFormState.Assemble;
|
||||||
|
_selectedSellItemIds.Clear();
|
||||||
|
return CreateInitialModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryToggleSellSelection(
|
||||||
|
long itemId,
|
||||||
|
out RepoFormRawData rawData,
|
||||||
|
out PlayerInventorySaleFailureReason failureReason)
|
||||||
|
{
|
||||||
|
rawData = null;
|
||||||
|
failureReason = PlayerInventorySaleFailureReason.None;
|
||||||
|
if (_state != RepoFormState.Sell || itemId <= 0 || GameEntry.PlayerInventory == null)
|
||||||
|
{
|
||||||
|
failureReason = PlayerInventorySaleFailureReason.InvalidSelection;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_selectedSellItemIds.Contains(itemId))
|
||||||
|
{
|
||||||
|
_selectedSellItemIds.Remove(itemId);
|
||||||
|
rawData = CreateInitialModel();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!GameEntry.PlayerInventory.TryGetSaleCandidate(itemId, out PlayerInventorySaleCandidate candidate) ||
|
||||||
|
candidate == null)
|
||||||
|
{
|
||||||
|
failureReason = PlayerInventorySaleFailureReason.ItemNotFound;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!candidate.IsSellable)
|
||||||
|
{
|
||||||
|
failureReason = candidate.FailureReason;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_selectedSellItemIds.Add(itemId);
|
||||||
|
rawData = CreateInitialModel();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryConfirmSellSelection(out RepoFormRawData rawData, out PlayerInventorySaleResult result)
|
||||||
|
{
|
||||||
|
rawData = null;
|
||||||
|
result = null;
|
||||||
|
if (_state != RepoFormState.Sell || _selectedSellItemIds.Count <= 0 || GameEntry.PlayerInventory == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!GameEntry.PlayerInventory.TrySellItems(_selectedSellItemIds, out PlayerInventorySaleResult saleResult) ||
|
||||||
|
saleResult == null ||
|
||||||
|
!saleResult.IsSuccess)
|
||||||
|
{
|
||||||
|
result = saleResult;
|
||||||
|
rawData = CreateInitialModel();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_selectedSellItemIds.Clear();
|
||||||
|
result = saleResult;
|
||||||
|
rawData = CreateInitialModel();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private BackpackInventoryData GetOrCreateFallbackInventory()
|
private BackpackInventoryData GetOrCreateFallbackInventory()
|
||||||
{
|
{
|
||||||
_fallbackInventory ??= InventorySeedUtility.CreateSampleInventory();
|
_fallbackInventory ??= InventorySeedUtility.CreateSampleInventory();
|
||||||
InventoryParticipantUtility.NormalizeParticipantState(_fallbackInventory, MaxParticipantCount);
|
InventoryParticipantUtility.NormalizeParticipantState(_fallbackInventory, MaxParticipantCount);
|
||||||
return _fallbackInventory;
|
return _fallbackInventory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private long[] BuildSelectedSellItemArray()
|
||||||
|
{
|
||||||
|
if (_selectedSellItemIds.Count <= 0)
|
||||||
|
{
|
||||||
|
return System.Array.Empty<long>();
|
||||||
|
}
|
||||||
|
|
||||||
|
long[] itemIds = new long[_selectedSellItemIds.Count];
|
||||||
|
_selectedSellItemIds.CopyTo(itemIds);
|
||||||
|
return itemIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ResolveSelectedSellTotalPrice()
|
||||||
|
{
|
||||||
|
if (_state != RepoFormState.Sell || _selectedSellItemIds.Count <= 0 || GameEntry.PlayerInventory == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int total = 0;
|
||||||
|
foreach (long itemId in _selectedSellItemIds)
|
||||||
|
{
|
||||||
|
if (GameEntry.PlayerInventory.TryGetSaleCandidate(itemId, out PlayerInventorySaleCandidate candidate) &&
|
||||||
|
candidate != null &&
|
||||||
|
candidate.IsSellable)
|
||||||
|
{
|
||||||
|
total += candidate.Price;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ namespace GeometryTD.UI
|
||||||
public class CombineArea : MonoBehaviour, IDropHandler
|
public class CombineArea : MonoBehaviour, IDropHandler
|
||||||
{
|
{
|
||||||
[SerializeField] private CombineSlotItem[] _slots;
|
[SerializeField] private CombineSlotItem[] _slots;
|
||||||
|
private bool _isInteractionEnabled = true;
|
||||||
|
|
||||||
public void OnInit(CombineAreaContext context)
|
public void OnInit(CombineAreaContext context)
|
||||||
{
|
{
|
||||||
|
|
@ -43,6 +44,11 @@ namespace GeometryTD.UI
|
||||||
|
|
||||||
public void OnCombineClick()
|
public void OnCombineClick()
|
||||||
{
|
{
|
||||||
|
if (!_isInteractionEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!TryGetBoundItemId(TowerCompSlotType.Muzzle, out long muzzleItemId) ||
|
if (!TryGetBoundItemId(TowerCompSlotType.Muzzle, out long muzzleItemId) ||
|
||||||
!TryGetBoundItemId(TowerCompSlotType.Bearing, out long bearingItemId) ||
|
!TryGetBoundItemId(TowerCompSlotType.Bearing, out long bearingItemId) ||
|
||||||
!TryGetBoundItemId(TowerCompSlotType.Base, out long baseItemId))
|
!TryGetBoundItemId(TowerCompSlotType.Base, out long baseItemId))
|
||||||
|
|
@ -55,6 +61,11 @@ namespace GeometryTD.UI
|
||||||
|
|
||||||
public bool TryAssignItem(RepoItemContext itemContext)
|
public bool TryAssignItem(RepoItemContext itemContext)
|
||||||
{
|
{
|
||||||
|
if (!_isInteractionEnabled)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (itemContext == null || itemContext.ComponentSlotType == TowerCompSlotType.None)
|
if (itemContext == null || itemContext.ComponentSlotType == TowerCompSlotType.None)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -91,6 +102,11 @@ namespace GeometryTD.UI
|
||||||
|
|
||||||
public void OnDrop(PointerEventData eventData)
|
public void OnDrop(PointerEventData eventData)
|
||||||
{
|
{
|
||||||
|
if (!_isInteractionEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (eventData == null)
|
if (eventData == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -113,6 +129,11 @@ namespace GeometryTD.UI
|
||||||
dragItem.SetDropResult(assigned);
|
dragItem.SetDropResult(assigned);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetInteractionEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
_isInteractionEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
private CombineSlotItem FindSlot(TowerCompSlotType slotType)
|
private CombineSlotItem FindSlot(TowerCompSlotType slotType)
|
||||||
{
|
{
|
||||||
if (_slots == null)
|
if (_slots == null)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ namespace GeometryTD.UI
|
||||||
[SerializeField] private Transform _content;
|
[SerializeField] private Transform _content;
|
||||||
[SerializeField] private TowerRepoItem _towerItemTemplate;
|
[SerializeField] private TowerRepoItem _towerItemTemplate;
|
||||||
[SerializeField] private int _instancePoolCapacity = 8;
|
[SerializeField] private int _instancePoolCapacity = 8;
|
||||||
|
private bool _isInteractionEnabled = true;
|
||||||
|
|
||||||
private readonly List<TowerRepoItem> _activeItems = new List<TowerRepoItem>();
|
private readonly List<TowerRepoItem> _activeItems = new List<TowerRepoItem>();
|
||||||
private readonly HashSet<long> _boundItemIds = new HashSet<long>();
|
private readonly HashSet<long> _boundItemIds = new HashSet<long>();
|
||||||
|
|
@ -81,6 +82,11 @@ namespace GeometryTD.UI
|
||||||
|
|
||||||
public bool CanAssign(IRepoDragItemView dragItem)
|
public bool CanAssign(IRepoDragItemView dragItem)
|
||||||
{
|
{
|
||||||
|
if (!_isInteractionEnabled)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (dragItem == null || dragItem.InstanceId <= 0)
|
if (dragItem == null || dragItem.InstanceId <= 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -106,6 +112,11 @@ namespace GeometryTD.UI
|
||||||
|
|
||||||
public void OnDrop(PointerEventData eventData)
|
public void OnDrop(PointerEventData eventData)
|
||||||
{
|
{
|
||||||
|
if (!_isInteractionEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (eventData == null)
|
if (eventData == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -134,6 +145,11 @@ namespace GeometryTD.UI
|
||||||
GameEntry.Event.Fire(this, RepoParticipantAssignRequestedEventArgs.Create(dragItem.InstanceId));
|
GameEntry.Event.Fire(this, RepoParticipantAssignRequestedEventArgs.Create(dragItem.InstanceId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetInteractionEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
_isInteractionEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
private void EnsurePool()
|
private void EnsurePool()
|
||||||
{
|
{
|
||||||
if (_itemPool != null)
|
if (_itemPool != null)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using GeometryTD.CustomEvent;
|
using GeometryTD.CustomEvent;
|
||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
using UnityGameFramework.Runtime;
|
using UnityGameFramework.Runtime;
|
||||||
|
|
||||||
namespace GeometryTD.UI
|
namespace GeometryTD.UI
|
||||||
|
|
@ -8,12 +9,12 @@ namespace GeometryTD.UI
|
||||||
public class RepoForm : UGuiForm
|
public class RepoForm : UGuiForm
|
||||||
{
|
{
|
||||||
[SerializeField] private CombineArea _combineArea;
|
[SerializeField] private CombineArea _combineArea;
|
||||||
|
|
||||||
[SerializeField] private CompArea _compArea;
|
[SerializeField] private CompArea _compArea;
|
||||||
|
[SerializeField] private SellArea _sellArea;
|
||||||
[SerializeField] private ParticipantArea _participantArea;
|
[SerializeField] private ParticipantArea _participantArea;
|
||||||
|
|
||||||
[SerializeField] private TMP_Text _goldText;
|
[SerializeField] private TMP_Text _goldText;
|
||||||
|
[SerializeField] private CommonButton _sellModeButton;
|
||||||
|
[SerializeField] private TMP_Text _sellModeButtonText;
|
||||||
|
|
||||||
public void RefreshUI(RepoFormContext context)
|
public void RefreshUI(RepoFormContext context)
|
||||||
{
|
{
|
||||||
|
|
@ -23,10 +24,25 @@ namespace GeometryTD.UI
|
||||||
}
|
}
|
||||||
|
|
||||||
RefreshGoldText(context.GoldText);
|
RefreshGoldText(context.GoldText);
|
||||||
|
RefreshStateUI(context);
|
||||||
|
|
||||||
_combineArea?.OnInit(context.CombineAreaContext);
|
_combineArea?.OnInit(context.CombineAreaContext);
|
||||||
_compArea?.OnInit(context.CompAreaContext);
|
_compArea?.OnInit(context.CompAreaContext);
|
||||||
|
_sellArea?.OnInit(context.SellAreaContext);
|
||||||
_participantArea?.OnInit(context.ParticipantAreaContext);
|
_participantArea?.OnInit(context.ParticipantAreaContext);
|
||||||
|
|
||||||
|
if (_combineArea != null)
|
||||||
|
{
|
||||||
|
_combineArea.gameObject.SetActive(context.ShowCombineArea);
|
||||||
|
_combineArea.SetInteractionEnabled(context.State == RepoFormState.Assemble);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_sellArea != null)
|
||||||
|
{
|
||||||
|
_sellArea.gameObject.SetActive(context.ShowSellArea);
|
||||||
|
}
|
||||||
|
|
||||||
|
_participantArea?.SetInteractionEnabled(context.State == RepoFormState.Assemble);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RefreshGoldText(string goldText)
|
public void RefreshGoldText(string goldText)
|
||||||
|
|
@ -89,6 +105,7 @@ namespace GeometryTD.UI
|
||||||
|
|
||||||
_combineArea?.OnReset();
|
_combineArea?.OnReset();
|
||||||
_compArea?.OnReset();
|
_compArea?.OnReset();
|
||||||
|
_sellArea?.OnReset();
|
||||||
_participantArea?.OnReset();
|
_participantArea?.OnReset();
|
||||||
base.OnClose(isShutdown, userData);
|
base.OnClose(isShutdown, userData);
|
||||||
}
|
}
|
||||||
|
|
@ -97,5 +114,23 @@ namespace GeometryTD.UI
|
||||||
{
|
{
|
||||||
GameEntry.Event.Fire(this, RepoFormReturnEventArgs.Create());
|
GameEntry.Event.Fire(this, RepoFormReturnEventArgs.Create());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnSellModeButtonClick()
|
||||||
|
{
|
||||||
|
GameEntry.Event.Fire(this, RepoSellModeToggleRequestedEventArgs.Create());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshStateUI(RepoFormContext context)
|
||||||
|
{
|
||||||
|
if (_sellModeButton != null)
|
||||||
|
{
|
||||||
|
_sellModeButton.gameObject.SetActive(context != null && context.ShowSellModeButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_sellModeButtonText != null)
|
||||||
|
{
|
||||||
|
_sellModeButtonText.text = context?.SellModeButtonText ?? "出售模式";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ namespace GeometryTD.UI
|
||||||
[SerializeField] private IconArea _iconArea;
|
[SerializeField] private IconArea _iconArea;
|
||||||
|
|
||||||
private static readonly Color SelectedColor = new Color32(255, 216, 102, 255);
|
private static readonly Color SelectedColor = new Color32(255, 216, 102, 255);
|
||||||
|
private static readonly Color DisabledSellColor = new Color32(96, 96, 96, 255);
|
||||||
private static readonly Color EmptyEnduranceColor = new Color(0.8f, 0f, 0f, 1f);
|
private static readonly Color EmptyEnduranceColor = new Color(0.8f, 0f, 0f, 1f);
|
||||||
private static readonly Color FullEnduranceColor = new Color(0f, 0.8f, 0f, 1f);
|
private static readonly Color FullEnduranceColor = new Color(0f, 0.8f, 0f, 1f);
|
||||||
private static readonly Vector2 DefaultDragGhostSize = new Vector2(64f, 64f);
|
private static readonly Vector2 DefaultDragGhostSize = new Vector2(64f, 64f);
|
||||||
|
|
@ -72,7 +73,7 @@ namespace GeometryTD.UI
|
||||||
_context = context;
|
_context = context;
|
||||||
_iconArea.OnInit(context.IconAreaContext);
|
_iconArea.OnInit(context.IconAreaContext);
|
||||||
|
|
||||||
SetSelected(false);
|
SetSelected(context.IsSellSelected);
|
||||||
ResetDragState();
|
ResetDragState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,6 +105,12 @@ namespace GeometryTD.UI
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_context != null && _context.IsSellMode && !_context.IsSellable)
|
||||||
|
{
|
||||||
|
_bgImage.color = DisabledSellColor;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
float enduranceRate = _context != null ? Mathf.Clamp01(_context.EnduranceRate01) : 1f;
|
float enduranceRate = _context != null ? Mathf.Clamp01(_context.EnduranceRate01) : 1f;
|
||||||
_bgImage.color = Color.Lerp(EmptyEnduranceColor, FullEnduranceColor, enduranceRate);
|
_bgImage.color = Color.Lerp(EmptyEnduranceColor, FullEnduranceColor, enduranceRate);
|
||||||
}
|
}
|
||||||
|
|
@ -212,6 +219,11 @@ namespace GeometryTD.UI
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_context.IsSellMode)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (_isSelected && _context.ComponentSlotType != TowerCompSlotType.None)
|
if (_isSelected && _context.ComponentSlotType != TowerCompSlotType.None)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using GeometryTD.CustomEvent;
|
||||||
|
using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace GeometryTD.UI
|
||||||
|
{
|
||||||
|
public class SellArea : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private Transform _content;
|
||||||
|
[SerializeField] private RepoItem _itemTemplate;
|
||||||
|
[SerializeField] private TowerRepoItem _towerItemTemplate;
|
||||||
|
[SerializeField] private TMP_Text _totalPrice;
|
||||||
|
|
||||||
|
private readonly List<RepoItem> _activeComponentItems = new List<RepoItem>();
|
||||||
|
private readonly List<TowerRepoItem> _activeTowerItems = new List<TowerRepoItem>();
|
||||||
|
private SellAreaContext _context;
|
||||||
|
|
||||||
|
public void OnInit(SellAreaContext context)
|
||||||
|
{
|
||||||
|
OnReset();
|
||||||
|
_context = context;
|
||||||
|
|
||||||
|
if (_totalPrice != null)
|
||||||
|
{
|
||||||
|
_totalPrice.text = context?.TotalPriceText ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context == null || _content == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.ComponentItems != null)
|
||||||
|
{
|
||||||
|
foreach (RepoItemContext itemContext in context.ComponentItems)
|
||||||
|
{
|
||||||
|
if (itemContext == null || _itemTemplate == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
RepoItem item = Instantiate(_itemTemplate, _content);
|
||||||
|
item.gameObject.SetActive(true);
|
||||||
|
item.OnInit(itemContext);
|
||||||
|
_activeComponentItems.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.TowerItems != null)
|
||||||
|
{
|
||||||
|
foreach (TowerRepoItemContext itemContext in context.TowerItems)
|
||||||
|
{
|
||||||
|
if (itemContext == null || _towerItemTemplate == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
TowerRepoItem item = Instantiate(_towerItemTemplate, _content);
|
||||||
|
item.gameObject.SetActive(true);
|
||||||
|
item.OnInit(itemContext);
|
||||||
|
_activeTowerItems.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnReset()
|
||||||
|
{
|
||||||
|
for (int i = _activeComponentItems.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
RepoItem item = _activeComponentItems[i];
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
Destroy(item.gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = _activeTowerItems.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
TowerRepoItem item = _activeTowerItems[i];
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
Destroy(item.gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_activeComponentItems.Clear();
|
||||||
|
_activeTowerItems.Clear();
|
||||||
|
_context = null;
|
||||||
|
|
||||||
|
if (_totalPrice != null)
|
||||||
|
{
|
||||||
|
_totalPrice.text = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnCancelButtonClick()
|
||||||
|
{
|
||||||
|
GameEntry.Event.Fire(this, RepoSellCancelRequestedEventArgs.Create());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnConfirmButtonClick()
|
||||||
|
{
|
||||||
|
if (_context == null || !_context.CanConfirmSell)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameEntry.Event.Fire(this, RepoSellConfirmRequestedEventArgs.Create());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 548fcad4dc8202f41b67f48aab7bed13
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -61,37 +61,16 @@ namespace GeometryTD.UI
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!GameEntry.PlayerInventory.TryConsumeGold(goodsItem.Price))
|
if (!GameEntry.PlayerInventory.TryPurchaseComponent(goodsItem.SourceItem, goodsItem.Price))
|
||||||
{
|
{
|
||||||
Log.Warning("ShopFormUseCase.TryPurchase() failed. Not enough gold for goods item {0}.", goodsIndex);
|
Log.Warning("ShopFormUseCase.TryPurchase() failed. Purchase command was rejected for goods item {0}.", goodsIndex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
BackpackInventoryData inventoryDelta = WrapSingleItem(goodsItem.SourceItem);
|
|
||||||
GameEntry.PlayerInventory.MergeInventory(inventoryDelta);
|
|
||||||
goodsItem.IsPurchased = true;
|
goodsItem.IsPurchased = true;
|
||||||
updatedRawData = CreateInitialModel();
|
updatedRawData = CreateInitialModel();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using GameFramework.DataTable;
|
||||||
|
using GeometryTD.DataTable;
|
||||||
|
using GeometryTD.Definition;
|
||||||
|
using UnityEngine;
|
||||||
|
using Random = System.Random;
|
||||||
|
|
||||||
|
namespace GeometryTD.CustomUtility
|
||||||
|
{
|
||||||
|
public static class ShopPriceRuleService
|
||||||
|
{
|
||||||
|
public static int ResolveRandomBuyPrice(IReadOnlyList<DRShopPrice> shopPriceRows, RarityType rarity, Random random)
|
||||||
|
{
|
||||||
|
if (!TryFindPriceRow(shopPriceRows, rarity, out DRShopPrice row) || row == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int min = Mathf.Max(0, row.MinPrice);
|
||||||
|
int max = Mathf.Max(min, row.MaxPrice);
|
||||||
|
return random != null ? random.Next(min, max + 1) : min;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int ResolveComponentSalePrice(TowerCompItemData component, IDataTable<DRShopPrice> shopPriceTable = null)
|
||||||
|
{
|
||||||
|
if (component == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResolveBasePrice(component.Rarity, shopPriceTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryResolveTowerSalePrice(
|
||||||
|
TowerItemData tower,
|
||||||
|
BackpackInventoryData inventory,
|
||||||
|
out int price,
|
||||||
|
IDataTable<DRShopPrice> shopPriceTable = null)
|
||||||
|
{
|
||||||
|
price = 0;
|
||||||
|
if (tower == null || inventory == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryGetComponentById(inventory.MuzzleComponents, tower.MuzzleComponentInstanceId, out MuzzleCompItemData muzzleComp) ||
|
||||||
|
!TryGetComponentById(inventory.BearingComponents, tower.BearingComponentInstanceId, out BearingCompItemData bearingComp) ||
|
||||||
|
!TryGetComponentById(inventory.BaseComponents, tower.BaseComponentInstanceId, out BaseCompItemData baseComp))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
price = ResolveComponentSalePrice(muzzleComp, shopPriceTable) +
|
||||||
|
ResolveComponentSalePrice(bearingComp, shopPriceTable) +
|
||||||
|
ResolveComponentSalePrice(baseComp, shopPriceTable);
|
||||||
|
return price > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int ResolveBasePrice(RarityType rarity, IDataTable<DRShopPrice> shopPriceTable = null)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int min = Mathf.Max(0, row.MinPrice);
|
||||||
|
int max = Mathf.Max(min, row.MaxPrice);
|
||||||
|
return Mathf.RoundToInt((min + max) * 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryFindPriceRow(IReadOnlyList<DRShopPrice> rows, RarityType rarity, out DRShopPrice result)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
if (rows == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < rows.Count; i++)
|
||||||
|
{
|
||||||
|
DRShopPrice row = rows[i];
|
||||||
|
if (row != null && row.Rarity == rarity)
|
||||||
|
{
|
||||||
|
result = row;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetComponentById<TComp>(IReadOnlyList<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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b1bcf5c75bea4faaacdb4ffc5adfaf36
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,298 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using GameFramework.DataTable;
|
||||||
|
using GeometryTD.CustomComponent;
|
||||||
|
using GeometryTD.DataTable;
|
||||||
|
using GeometryTD.Definition;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace GeometryTD.Tests.EditMode
|
||||||
|
{
|
||||||
|
public sealed class PlayerInventoryTradeServiceTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TrySellItems_Sells_Selected_Component_And_Tower_Atomically()
|
||||||
|
{
|
||||||
|
BackpackInventoryData inventory = CreateInventory();
|
||||||
|
PlayerInventoryState state = new PlayerInventoryState();
|
||||||
|
PlayerInventoryQueryModel queryModel = new PlayerInventoryQueryModel(state);
|
||||||
|
PlayerInventoryCommandModel commandModel = new PlayerInventoryCommandModel(state);
|
||||||
|
commandModel.Initialize(inventory, maxParticipantTowerCount: 4);
|
||||||
|
|
||||||
|
PlayerInventoryTradeService tradeService = new PlayerInventoryTradeService(
|
||||||
|
queryModel,
|
||||||
|
commandModel,
|
||||||
|
CreatePriceTable(
|
||||||
|
CreateShopPriceRow(1, RarityType.Blue, 30, 34),
|
||||||
|
CreateShopPriceRow(2, RarityType.Purple, 60, 64)));
|
||||||
|
|
||||||
|
bool success = tradeService.TrySellItems(new long[] { 101, 201 }, out PlayerInventorySaleResult result);
|
||||||
|
|
||||||
|
Assert.That(success, Is.True);
|
||||||
|
Assert.That(result, Is.Not.Null);
|
||||||
|
Assert.That(result.IsSuccess, Is.True);
|
||||||
|
Assert.That(result.SoldComponentCount, Is.EqualTo(1));
|
||||||
|
Assert.That(result.SoldTowerCount, Is.EqualTo(1));
|
||||||
|
Assert.That(queryModel.Inventory.Gold, Is.EqualTo(138));
|
||||||
|
Assert.That(queryModel.Inventory.MuzzleComponents.Exists(item => item.InstanceId == 101), Is.False);
|
||||||
|
Assert.That(queryModel.Inventory.Towers.Exists(item => item.InstanceId == 201), Is.False);
|
||||||
|
Assert.That(queryModel.Inventory.MuzzleComponents.Exists(item => item.InstanceId == 202), Is.False);
|
||||||
|
Assert.That(queryModel.Inventory.BearingComponents.Exists(item => item.InstanceId == 203), Is.False);
|
||||||
|
Assert.That(queryModel.Inventory.BaseComponents.Exists(item => item.InstanceId == 204), Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TryGetSaleCandidate_Rejects_Participant_Tower()
|
||||||
|
{
|
||||||
|
BackpackInventoryData inventory = CreateInventory();
|
||||||
|
inventory.ParticipantTowerInstanceIds.Add(201);
|
||||||
|
|
||||||
|
PlayerInventoryState state = new PlayerInventoryState();
|
||||||
|
PlayerInventoryQueryModel queryModel = new PlayerInventoryQueryModel(state);
|
||||||
|
PlayerInventoryCommandModel commandModel = new PlayerInventoryCommandModel(state);
|
||||||
|
commandModel.Initialize(inventory, maxParticipantTowerCount: 4);
|
||||||
|
|
||||||
|
PlayerInventoryTradeService tradeService = new PlayerInventoryTradeService(
|
||||||
|
queryModel,
|
||||||
|
commandModel,
|
||||||
|
CreatePriceTable(CreateShopPriceRow(1, RarityType.Blue, 30, 34)));
|
||||||
|
|
||||||
|
bool resolved = tradeService.TryGetSaleCandidate(201, out PlayerInventorySaleCandidate candidate);
|
||||||
|
|
||||||
|
Assert.That(resolved, Is.True);
|
||||||
|
Assert.That(candidate, Is.Not.Null);
|
||||||
|
Assert.That(candidate.IsSellable, Is.False);
|
||||||
|
Assert.That(candidate.FailureReason, Is.EqualTo(PlayerInventorySaleFailureReason.ParticipantTower));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BackpackInventoryData CreateInventory()
|
||||||
|
{
|
||||||
|
BackpackInventoryData inventory = new BackpackInventoryData
|
||||||
|
{
|
||||||
|
Gold = 10
|
||||||
|
};
|
||||||
|
|
||||||
|
inventory.MuzzleComponents.Add(new MuzzleCompItemData
|
||||||
|
{
|
||||||
|
InstanceId = 101,
|
||||||
|
ConfigId = 1,
|
||||||
|
Name = "Loose Muzzle",
|
||||||
|
Rarity = RarityType.Blue,
|
||||||
|
Endurance = 100f,
|
||||||
|
IsAssembledIntoTower = false
|
||||||
|
});
|
||||||
|
inventory.MuzzleComponents.Add(new MuzzleCompItemData
|
||||||
|
{
|
||||||
|
InstanceId = 202,
|
||||||
|
ConfigId = 2,
|
||||||
|
Name = "Tower Muzzle",
|
||||||
|
Rarity = RarityType.Blue,
|
||||||
|
Endurance = 100f,
|
||||||
|
IsAssembledIntoTower = true
|
||||||
|
});
|
||||||
|
inventory.BearingComponents.Add(new BearingCompItemData
|
||||||
|
{
|
||||||
|
InstanceId = 203,
|
||||||
|
ConfigId = 3,
|
||||||
|
Name = "Tower Bearing",
|
||||||
|
Rarity = RarityType.Blue,
|
||||||
|
Endurance = 100f,
|
||||||
|
IsAssembledIntoTower = true
|
||||||
|
});
|
||||||
|
inventory.BaseComponents.Add(new BaseCompItemData
|
||||||
|
{
|
||||||
|
InstanceId = 204,
|
||||||
|
ConfigId = 4,
|
||||||
|
Name = "Tower Base",
|
||||||
|
Rarity = RarityType.Blue,
|
||||||
|
Endurance = 100f,
|
||||||
|
IsAssembledIntoTower = true
|
||||||
|
});
|
||||||
|
|
||||||
|
inventory.Towers.Add(new TowerItemData
|
||||||
|
{
|
||||||
|
InstanceId = 201,
|
||||||
|
Name = "Tower #201",
|
||||||
|
Rarity = RarityType.Blue,
|
||||||
|
MuzzleComponentInstanceId = 202,
|
||||||
|
BearingComponentInstanceId = 203,
|
||||||
|
BaseComponentInstanceId = 204
|
||||||
|
});
|
||||||
|
|
||||||
|
return inventory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IDataTable<DRShopPrice> CreatePriceTable(params DRShopPrice[] rows)
|
||||||
|
{
|
||||||
|
return new FakeDataTable<DRShopPrice>(rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DRShopPrice CreateShopPriceRow(int id, RarityType rarity, int minPrice, int maxPrice)
|
||||||
|
{
|
||||||
|
DRShopPrice row = new DRShopPrice();
|
||||||
|
Assert.That(row.ParseDataRow($"\t{id}\t\t{rarity}\t{minPrice}\t{maxPrice}", null), Is.True);
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class FakeDataTable<TRow> : IDataTable<TRow> where TRow : class, IDataRow
|
||||||
|
{
|
||||||
|
private readonly Dictionary<int, TRow> _rowsById = new();
|
||||||
|
|
||||||
|
public FakeDataTable(params TRow[] rows)
|
||||||
|
{
|
||||||
|
if (rows == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < rows.Length; i++)
|
||||||
|
{
|
||||||
|
TRow row = rows[i];
|
||||||
|
if (row != null)
|
||||||
|
{
|
||||||
|
_rowsById[row.Id] = row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name => typeof(TRow).Name;
|
||||||
|
public string FullName => typeof(TRow).FullName;
|
||||||
|
public Type Type => typeof(TRow);
|
||||||
|
public int Count => _rowsById.Count;
|
||||||
|
public TRow this[int id] => GetDataRow(id);
|
||||||
|
public TRow MinIdDataRow => null;
|
||||||
|
public TRow MaxIdDataRow => null;
|
||||||
|
|
||||||
|
public bool HasDataRow(int id) => _rowsById.ContainsKey(id);
|
||||||
|
public bool HasDataRow(Predicate<TRow> condition) => GetDataRow(condition) != null;
|
||||||
|
public TRow GetDataRow(int id) => _rowsById.TryGetValue(id, out TRow row) ? row : null;
|
||||||
|
|
||||||
|
public TRow GetDataRow(Predicate<TRow> condition)
|
||||||
|
{
|
||||||
|
foreach (TRow row in _rowsById.Values)
|
||||||
|
{
|
||||||
|
if (row != null && condition != null && condition(row))
|
||||||
|
{
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TRow[] GetDataRows(Predicate<TRow> condition)
|
||||||
|
{
|
||||||
|
List<TRow> results = new();
|
||||||
|
GetDataRows(condition, results);
|
||||||
|
return results.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetDataRows(Predicate<TRow> condition, List<TRow> results)
|
||||||
|
{
|
||||||
|
results?.Clear();
|
||||||
|
if (condition == null || results == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (TRow row in _rowsById.Values)
|
||||||
|
{
|
||||||
|
if (row != null && condition(row))
|
||||||
|
{
|
||||||
|
results.Add(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TRow[] GetDataRows(Comparison<TRow> comparison)
|
||||||
|
{
|
||||||
|
List<TRow> results = new();
|
||||||
|
GetDataRows(comparison, results);
|
||||||
|
return results.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetDataRows(Comparison<TRow> comparison, List<TRow> results)
|
||||||
|
{
|
||||||
|
results?.Clear();
|
||||||
|
if (results == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
results.AddRange(_rowsById.Values);
|
||||||
|
if (comparison != null)
|
||||||
|
{
|
||||||
|
results.Sort(comparison);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TRow[] GetDataRows(Predicate<TRow> condition, Comparison<TRow> comparison)
|
||||||
|
{
|
||||||
|
List<TRow> results = new();
|
||||||
|
GetDataRows(condition, comparison, results);
|
||||||
|
return results.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetDataRows(Predicate<TRow> condition, Comparison<TRow> comparison, List<TRow> results)
|
||||||
|
{
|
||||||
|
GetDataRows(condition, results);
|
||||||
|
if (results != null && comparison != null)
|
||||||
|
{
|
||||||
|
results.Sort(comparison);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TRow[] GetAllDataRows()
|
||||||
|
{
|
||||||
|
List<TRow> results = new();
|
||||||
|
GetAllDataRows(results);
|
||||||
|
return results.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetAllDataRows(List<TRow> results)
|
||||||
|
{
|
||||||
|
results?.Clear();
|
||||||
|
if (results == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (int id in GetOrderedIds())
|
||||||
|
{
|
||||||
|
results.Add(_rowsById[id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AddDataRow(string dataRowString, object userData) => throw new NotSupportedException();
|
||||||
|
public bool AddDataRow(byte[] dataRowBytes, int startIndex, int length, object userData) => throw new NotSupportedException();
|
||||||
|
public bool RemoveDataRow(int id) => _rowsById.Remove(id);
|
||||||
|
|
||||||
|
public void RemoveAllDataRows()
|
||||||
|
{
|
||||||
|
_rowsById.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<TRow> GetEnumerator()
|
||||||
|
{
|
||||||
|
foreach (int id in GetOrderedIds())
|
||||||
|
{
|
||||||
|
yield return _rowsById[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int[] GetOrderedIds()
|
||||||
|
{
|
||||||
|
int[] ids = new int[_rowsById.Count];
|
||||||
|
_rowsById.Keys.CopyTo(ids, 0);
|
||||||
|
Array.Sort(ids);
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: eae86223cf354e758fad4be62efd678d
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -90,20 +90,24 @@
|
||||||
- 三个示例事件在局内都能完整触发、结算、回写状态。
|
- 三个示例事件在局内都能完整触发、结算、回写状态。
|
||||||
- 不会出现组件被扣掉但奖励未发、或耐久扣成负值这种半成功状态。
|
- 不会出现组件被扣掉但奖励未发、或耐久扣成负值这种半成功状态。
|
||||||
|
|
||||||
### P1-03 商店节点:购买 / 出售组件
|
### P1-03 商店节点购买 + 背包出售组件 / 防御塔
|
||||||
|
|
||||||
- 核心目标:把当前“仅购买组件”的商店补成完整交易节点。
|
- 核心目标:保留商店节点的购买职责,把出售入口迁到背包 `RepoForm`,形成完整交易闭环。
|
||||||
- 交付物:
|
- 交付物:
|
||||||
- `Assets/GameMain/Scripts/UI/Shop/`
|
- `Assets/GameMain/Scripts/UI/Shop/`
|
||||||
|
- `Assets/GameMain/Scripts/UI/Game/`
|
||||||
- `Assets/GameMain/Scripts/CustomComponent/ShopNodeComponent.cs`
|
- `Assets/GameMain/Scripts/CustomComponent/ShopNodeComponent.cs`
|
||||||
- 必要时新增 `Assets/GameMain/Scripts/Utility/` 下的交易规则工具
|
- `Assets/GameMain/Scripts/CustomComponent/PlayerInventory/`
|
||||||
- 具体拆分:
|
- 具体拆分:
|
||||||
- Shop UI 增加玩家库存展示与出售入口,至少支持出售三类组件。
|
- Shop UI 保持“4 个商品 + 购买 + 打开背包”主链,不再承担库存展示与出售交互。
|
||||||
- 交易结算统一走一个买卖接口,不要在 UI 层分别修改金币和库存。
|
- Repo UI 增加明确的出售模式入口;进入后可批量选择未组装组件与非参战防御塔,并显示当前选择总价值。
|
||||||
|
- 参战防御塔不能直接出售,必须先从参战区移出;出售整塔时,需要连同其绑定的 3 个组件一起从库存移除。
|
||||||
|
- 交易结算统一走库存域的显式买/卖命令,不要在 UI 层分别修改金币和库存。
|
||||||
- 商品购买后状态、库存变化、金币变化要同时刷新,不允许 UI 还显示可买但实际已买。
|
- 商品购买后状态、库存变化、金币变化要同时刷新,不允许 UI 还显示可买但实际已买。
|
||||||
- 明确商店节点退出条件与节点完成时机,避免出现开店即完成或出售后不回流的问题。
|
- 明确商店节点退出条件与节点完成时机,避免出现开店即完成或出售后不回流的问题。
|
||||||
- 验收补充:
|
- 验收补充:
|
||||||
- 买卖后库存与金币实时正确更新。
|
- 商店购买、背包出售后库存与金币实时正确更新。
|
||||||
|
- 从商店里打开背包出售后,返回商店时金币显示与商品状态正确刷新。
|
||||||
- 关闭商店后重新打开同一节点时,商品状态与本节点交易结果一致。
|
- 关闭商店后重新打开同一节点时,商品状态与本节点交易结果一致。
|
||||||
|
|
||||||
### P1-04 商店定价规则:买价、半价回收、卖塔 +10%、耐久折价
|
### P1-04 商店定价规则:买价、半价回收、卖塔 +10%、耐久折价
|
||||||
|
|
@ -114,7 +118,7 @@
|
||||||
- `Assets/GameMain/Scripts/Entity/` 或库存相关域对象
|
- `Assets/GameMain/Scripts/Entity/` 或库存相关域对象
|
||||||
- 视需要调整 `Assets/GameMain/DataTables/ShopPrice.txt`
|
- 视需要调整 `Assets/GameMain/DataTables/ShopPrice.txt`
|
||||||
- 具体拆分:
|
- 具体拆分:
|
||||||
- 抽出统一价格解析入口:基础买价、出售价、塔出售加成、耐久折价都走同一公式服务。
|
- 抽出统一价格解析入口:商店买价、背包组件出售价、背包防御塔出售价、塔出售加成、耐久折价都走同一公式服务。
|
||||||
- 明确“组件价格”和“整塔价格”关系,不能在 UI 里临时把三组件价格相加后再乘一个魔法数。
|
- 明确“组件价格”和“整塔价格”关系,不能在 UI 里临时把三组件价格相加后再乘一个魔法数。
|
||||||
- 明确耐久折价规则:按当前耐久比例折价,还是按损坏阈值分段折价;先写死规则文档,再落代码。
|
- 明确耐久折价规则:按当前耐久比例折价,还是按损坏阈值分段折价;先写死规则文档,再落代码。
|
||||||
- 如果 `ShopPrice.txt` 只够表达品质区间,新增字段或新表承载倍率,不要继续在代码里硬编码。
|
- 如果 `ShopPrice.txt` 只够表达品质区间,新增字段或新表承载倍率,不要继续在代码里硬编码。
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue