409 lines
18 KiB
C#
409 lines
18 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using CultMath;
|
|
using static CultMath.math;
|
|
|
|
public class TradeMenu : MonoBehaviour
|
|
{
|
|
public ActionGameManager GameManager;
|
|
public ContextMenu ContextMenu;
|
|
public ConfirmationDialog Dialog;
|
|
public Button NewFilterButton;
|
|
public Prototype FilterPrototype;
|
|
public SizeFilter MinimumSizeFilter;
|
|
public SizeFilter MaximumSizeFilter;
|
|
public PropertiesPanel Properties;
|
|
public Spreadsheet Spreadsheet;
|
|
public TextMeshProUGUI TargetCargoLabel;
|
|
public Button FoldoutButton;
|
|
public TextMeshProUGUI CreditsLabel;
|
|
|
|
private EquippedCargoBay _targetCargo;
|
|
// False lists the station's wares to buy; true lists the selected bay's goods to sell.
|
|
private bool _selling;
|
|
private (ItemFilter filter, HardpointType type) _hardpointFilter;
|
|
private List<(ItemFilter filter, Type type)> _behaviorFilters = new List<(ItemFilter filter, Type type)>();
|
|
|
|
public EquippedCargoBay Inventory { get; set; }
|
|
|
|
private void OnEnable()
|
|
{
|
|
if (GameManager.DockedEntity == null) return;
|
|
_targetCargo = GameManager.DockingBay;
|
|
_selling = false;
|
|
TargetCargoLabel.text = "Docking Bay";
|
|
Properties.GameManager = GameManager;
|
|
UpdateCreditsLabel();
|
|
|
|
MinimumSizeFilter.Width.onEndEdit.RemoveAllListeners();
|
|
MinimumSizeFilter.Width.onEndEdit.AddListener(_ => Populate());
|
|
|
|
MinimumSizeFilter.Height.onEndEdit.RemoveAllListeners();
|
|
MinimumSizeFilter.Height.onEndEdit.AddListener(_ => Populate());
|
|
|
|
MaximumSizeFilter.Width.onEndEdit.RemoveAllListeners();
|
|
MaximumSizeFilter.Width.onEndEdit.AddListener(_ => Populate());
|
|
|
|
MaximumSizeFilter.Height.onEndEdit.RemoveAllListeners();
|
|
MaximumSizeFilter.Height.onEndEdit.AddListener(_ => Populate());
|
|
|
|
NewFilterButton.onClick.AddListener(() =>
|
|
{
|
|
ContextMenu.Clear();
|
|
IEnumerable<HardpointType> hardpointTypes = (HardpointType[]) Enum.GetValues(typeof(HardpointType));
|
|
if (_hardpointFilter.filter != null)
|
|
hardpointTypes = hardpointTypes.Where(x => x != _hardpointFilter.type);
|
|
|
|
ContextMenu.AddDropdown("Gear Type", hardpointTypes
|
|
.Select<HardpointType, (string, Action, bool)>(x => (Enum.GetName(typeof(HardpointType), x), () =>
|
|
{
|
|
if (!_hardpointFilter.filter)
|
|
{
|
|
_hardpointFilter.filter = FilterPrototype.Instantiate<ItemFilter>();
|
|
_hardpointFilter.filter.OnDisable += () =>
|
|
{
|
|
_hardpointFilter.filter = null;
|
|
Populate();
|
|
};
|
|
}
|
|
_hardpointFilter.filter.Label.text = $"Hardpoint: {Enum.GetName(typeof(HardpointType), x)}";
|
|
_hardpointFilter.type = x;
|
|
Populate();
|
|
}, true)));
|
|
ContextMenu.AddDropdown("Item Behavior", typeof(BehaviorData).GetAllChildClasses()
|
|
.Where(x=> x.GetCustomAttribute<RuntimeInspectable>() != null && _behaviorFilters.All(f => f.type != x))
|
|
.Select<Type, (string, Action, bool)>(x=> (x.Name.FormatTypeName(), () =>
|
|
{
|
|
var matchingType = _behaviorFilters.FirstOrDefault(y => y.type.IsAssignableFrom(x) || x.IsAssignableFrom(y.type));
|
|
if (matchingType.filter != null) matchingType.filter.DisableButton.onClick.Invoke();
|
|
var filter = FilterPrototype.Instantiate<ItemFilter>();
|
|
filter.Label.text = x.Name.FormatTypeName();
|
|
filter.OnDisable += () =>
|
|
{
|
|
_behaviorFilters.Remove((filter, x));
|
|
Populate();
|
|
};
|
|
_behaviorFilters.Add((filter, x));
|
|
Populate();
|
|
}, true)));
|
|
if(!MinimumSizeFilter.gameObject.activeSelf)
|
|
ContextMenu.AddOption("Minimum Size",
|
|
() =>
|
|
{
|
|
MinimumSizeFilter.gameObject.SetActive(true);
|
|
MinimumSizeFilter.OnDisable += () => Populate();
|
|
});
|
|
if(!MaximumSizeFilter.gameObject.activeSelf)
|
|
ContextMenu.AddOption("Maximum Size",
|
|
() =>
|
|
{
|
|
MaximumSizeFilter.gameObject.SetActive(true);
|
|
MaximumSizeFilter.OnDisable += () => Populate();
|
|
});
|
|
ContextMenu.Show();
|
|
});
|
|
Populate();
|
|
}
|
|
|
|
void Populate()
|
|
{
|
|
var columns = new List<(string name, int size, Func<(ItemInstance item, ItemData data), Func<string>> output, Func<(ItemInstance item, ItemData data), IComparable> sortKey)>();
|
|
|
|
columns.Add(("Name", 3,
|
|
x => () => x.item is CraftedItemInstance craftedItemInstance ?
|
|
$"<color=#{ColorUtility.ToHtmlStringRGB(GameManager.ItemManager.GetTier(craftedItemInstance).tier.Color.ToColor())}>{GameManager.ItemManager.Title(x.item)}" :
|
|
GameManager.ItemManager.Title(x.item),
|
|
x => GameManager.ItemManager.Title(x.item)));
|
|
if(_hardpointFilter.filter==null)
|
|
columns.Add(("Type", 2,
|
|
x => () =>
|
|
{
|
|
if (x.data is SimpleCommodityData || x.data is CompoundCommodityData) return "Commodity";
|
|
if(x.data is EquippableItemData e) return Enum.GetName(typeof(HardpointType), e.HardpointType);
|
|
return "None";
|
|
},
|
|
x =>
|
|
{
|
|
var data = x.data;
|
|
if (data is SimpleCommodityData || data is CompoundCommodityData) return 0;
|
|
if (data is EquippableItemData e) return 1 + (int) e.HardpointType;
|
|
return 0;
|
|
}));
|
|
columns.Add(("Mass", 1,
|
|
x => () => ActionGameManager.PlayerSettings.Format(x.data.Mass),
|
|
x => x.data.Mass));
|
|
columns.Add(("Price", 1,
|
|
x => () => ShownPrice(x.item).ToString("N0"),
|
|
x => ShownPrice(x.item)));
|
|
columns.Add(("Size", 1,
|
|
x => () => $"{x.data.Shape.Width}x{x.data.Shape.Height}",
|
|
x => x.data.Shape.Width*x.data.Shape.Height));
|
|
|
|
var items = (_selling ? _targetCargo : Inventory).Cargo.Keys
|
|
.Select<ItemInstance, (ItemInstance item, ItemData data)>(ii=>(ii, GameManager.ItemManager.GetData(ii)));
|
|
|
|
if (MinimumSizeFilter.gameObject.activeSelf)
|
|
items = items.Where(i =>
|
|
!(MinimumSizeFilter.Width.text.Length > 0 && i.data.Shape.Width < int.Parse(MinimumSizeFilter.Width.text) ||
|
|
MinimumSizeFilter.Height.text.Length > 0 && i.data.Shape.Height < int.Parse(MinimumSizeFilter.Height.text)));
|
|
|
|
if (MaximumSizeFilter.gameObject.activeSelf)
|
|
items = items.Where(i =>
|
|
!(MaximumSizeFilter.Width.text.Length > 0 && i.data.Shape.Width > int.Parse(MaximumSizeFilter.Width.text) ||
|
|
MaximumSizeFilter.Height.text.Length > 0 && i.data.Shape.Height > int.Parse(MaximumSizeFilter.Height.text)));
|
|
|
|
if (_hardpointFilter.filter != null)
|
|
items = items.Where(i => i.data is EquippableItemData e && e.HardpointType == _hardpointFilter.type);
|
|
|
|
foreach (var (_, type) in _behaviorFilters)
|
|
{
|
|
items = items.Where(i => i.data is EquippableItemData e && e.Behaviors.Any(b => type.IsInstanceOfType(b)));
|
|
|
|
foreach (var field in type.GetFields().Where(f => f.GetCustomAttribute<RuntimeInspectable>() != null))
|
|
{
|
|
var fieldType = field.FieldType;
|
|
if (fieldType == typeof(float))
|
|
columns.Add((field.Name, 1, x =>
|
|
{
|
|
var behavior = ((EquippableItemData) x.data).Behaviors.FirstOrDefault(b => type.IsInstanceOfType(b));
|
|
return () => ActionGameManager.PlayerSettings.Format((float) field.GetValue(behavior));
|
|
}, x =>
|
|
{
|
|
var data = x.data;
|
|
var behavior = ((EquippableItemData) data).Behaviors.FirstOrDefault(b => type.IsInstanceOfType(b));
|
|
return (float) field.GetValue(behavior);
|
|
}));
|
|
else if (fieldType == typeof(int))
|
|
columns.Add((field.Name, 1, x =>
|
|
{
|
|
var behavior = ((EquippableItemData) x.data).Behaviors.FirstOrDefault(b => type.IsInstanceOfType(b));
|
|
return () => ((int) field.GetValue(behavior)).ToString();
|
|
}, x =>
|
|
{
|
|
var data = x.data;
|
|
var behavior = ((EquippableItemData) data).Behaviors.FirstOrDefault(b => type.IsInstanceOfType(b));
|
|
return (int) field.GetValue(behavior);
|
|
}));
|
|
else if (fieldType == typeof(PerformanceStat))
|
|
{
|
|
columns.Add((field.Name, 1, x =>
|
|
{
|
|
var behavior = ((EquippableItemData) x.data).Behaviors.FirstOrDefault(b => type.IsInstanceOfType(b));
|
|
return () => ActionGameManager.PlayerSettings.Format(((PerformanceStat) field.GetValue(behavior)).Max);
|
|
}, x =>
|
|
{
|
|
var data = x.data;
|
|
var behavior = ((EquippableItemData) data).Behaviors.FirstOrDefault(b => type.IsInstanceOfType(b));
|
|
return ((PerformanceStat) field.GetValue(behavior)).Max;
|
|
}));
|
|
}
|
|
}
|
|
}
|
|
|
|
columns.Add(("Owned", 1,
|
|
x => () =>
|
|
{
|
|
if (x.data is HullData)
|
|
return GameManager.DockedEntity.Children.Count(s => s.Hull.Data.Key.Equals(GameManager.ItemManager.ItemData.RefOf(x.data).Key) && s is Ship {IsPlayerShip: true}).ToString();
|
|
if(x.data is SimpleCommodityData)
|
|
return (_targetCargo.ItemsOfType.ContainsKey(GameManager.ItemManager.ItemData.RefOf(x.data).Key) ? _targetCargo.ItemsOfType[GameManager.ItemManager.ItemData.RefOf(x.data).Key].Cast<SimpleCommodity>().Sum(s=>s.Quantity) : 0).ToString();
|
|
return (_targetCargo.ItemsOfType.ContainsKey(GameManager.ItemManager.ItemData.RefOf(x.data).Key) ? _targetCargo.ItemsOfType[GameManager.ItemManager.ItemData.RefOf(x.data).Key].Count : 0).ToString();
|
|
},
|
|
x =>
|
|
{
|
|
var data = x.data;
|
|
if (data is HullData)
|
|
return GameManager.DockedEntity.Children.Count(s => s.Hull.Data.Key.Equals(GameManager.ItemManager.ItemData.RefOf(data).Key) && s is Ship {IsPlayerShip: true});
|
|
if(data is SimpleCommodityData)
|
|
return _targetCargo.ItemsOfType.ContainsKey(GameManager.ItemManager.ItemData.RefOf(data).Key) ? _targetCargo.ItemsOfType[GameManager.ItemManager.ItemData.RefOf(data).Key].Cast<SimpleCommodity>().Sum(s=>s.Quantity) : 0;
|
|
return _targetCargo.ItemsOfType.ContainsKey(GameManager.ItemManager.ItemData.RefOf(data).Key) ? _targetCargo.ItemsOfType[GameManager.ItemManager.ItemData.RefOf(data).Key].Count : 0;
|
|
}));
|
|
|
|
Spreadsheet.ShowData(
|
|
columns.Select(x => x.name).ToArray(),
|
|
columns.Select(x => x.size).ToArray(),
|
|
items.Select(i => new SpreadsheetEntryRow
|
|
{
|
|
Columns = columns.Select(x => new SpreadsheetEntryColumn
|
|
{
|
|
Output = x.output(i),
|
|
SortKey = x.sortKey(i)
|
|
}).ToArray(),
|
|
OnClick = () => Properties.Inspect(i.item),
|
|
OnDoubleClick = () =>
|
|
{
|
|
if (_selling) return;
|
|
switch (i.item)
|
|
{
|
|
case CraftedItemInstance c:
|
|
Buy(c);
|
|
break;
|
|
case SimpleCommodity s:
|
|
Buy(s, 1);
|
|
break;
|
|
}
|
|
|
|
Populate();
|
|
},
|
|
OnRightClick = () =>
|
|
{
|
|
if (_selling)
|
|
{
|
|
ContextMenu.Clear();
|
|
ContextMenu.AddOption($"Sell ({StationServices.SellPrice(GameManager.ItemManager, i.item):N0})", () => Sell(i.item));
|
|
ContextMenu.Show();
|
|
}
|
|
else if (i.item is SimpleCommodity s)
|
|
{
|
|
ContextMenu.Clear();
|
|
ContextMenu.AddOption("Buy Quantity",
|
|
() =>
|
|
{
|
|
int quantity = 1;
|
|
Dialog.Clear();
|
|
Dialog.Title.text = $"Buying {GameManager.ItemManager.Title(i.item)}";
|
|
Dialog.AddField("Quantity",
|
|
() => quantity,
|
|
q => quantity = StationServices.BuyableQuantity(GameManager.ItemManager, s, q, GameManager.Credits));
|
|
Dialog.Show(() =>
|
|
{
|
|
Buy(s,quantity);
|
|
|
|
Populate();
|
|
});
|
|
Dialog.MoveToCursor();
|
|
});
|
|
ContextMenu.Show();
|
|
}
|
|
}
|
|
}));
|
|
}
|
|
|
|
// The price the list shows and sorts by: what the station pays for the player's goods, what it charges otherwise.
|
|
private int ShownPrice(ItemInstance item) => _selling
|
|
? StationServices.SellPrice(GameManager.ItemManager, item)
|
|
: StationServices.BuyPrice(GameManager.ItemManager, item);
|
|
|
|
private void UpdateCreditsLabel()
|
|
{
|
|
if (CreditsLabel != null)
|
|
{
|
|
CreditsLabel.text = GameManager.Credits.ToString("N0");
|
|
}
|
|
}
|
|
|
|
private void Sell(ItemInstance item)
|
|
{
|
|
if (StationServices.TrySell(_targetCargo, GameManager.DockedEntity, item, ref GameManager.Credits))
|
|
{
|
|
UpdateCreditsLabel();
|
|
Populate();
|
|
return;
|
|
}
|
|
Dialog.Clear();
|
|
Dialog.Title.text = "Unable to sell: Station Cargo Full!";
|
|
Dialog.Show();
|
|
Dialog.MoveToCursor();
|
|
}
|
|
|
|
private void Repair()
|
|
{
|
|
if (StationServices.TryRepair(GameManager.CurrentEntity, ref GameManager.Credits))
|
|
{
|
|
UpdateCreditsLabel();
|
|
return;
|
|
}
|
|
Dialog.Clear();
|
|
Dialog.Title.text = "Unable to repair: Insufficient Credits!";
|
|
Dialog.Show();
|
|
Dialog.MoveToCursor();
|
|
}
|
|
|
|
private void Buy(CraftedItemInstance item)
|
|
{
|
|
var data = GameManager.ItemManager.GetData(item);
|
|
if (data is HullData hullData && hullData.HullType != HullType.Ship)
|
|
throw new ArgumentException("Attempted to buy non-ship hull from station, WTF are you doing?!");
|
|
Bought(StationServices.TryBuy(GameManager.ItemManager, item, 1, ref GameManager.Credits, () =>
|
|
{
|
|
if (data is HullData)
|
|
{
|
|
GameManager.CommissionShip(item as EquippableItem);
|
|
return true;
|
|
}
|
|
return Inventory.TryTransferItem(_targetCargo, item);
|
|
}));
|
|
}
|
|
|
|
private void Buy(SimpleCommodity simpleCommodity, int quantity)
|
|
{
|
|
Bought(StationServices.TryBuyLots(GameManager.ItemManager, simpleCommodity, quantity, ref GameManager.Credits,
|
|
lot => Inventory.TryTransferItem(_targetCargo, simpleCommodity, lot)));
|
|
}
|
|
|
|
// Refreshes the credits after a purchase, or tells the player why the station refused it.
|
|
private bool Bought(BuyResult result)
|
|
{
|
|
if (result == BuyResult.Bought)
|
|
{
|
|
UpdateCreditsLabel();
|
|
return true;
|
|
}
|
|
Dialog.Clear();
|
|
Dialog.Title.text = result == BuyResult.NoRoom
|
|
? "Unable to buy: Insufficient Cargo Space!"
|
|
: "Unable to buy: Insufficient Credits!";
|
|
Dialog.Show();
|
|
Dialog.MoveToCursor();
|
|
return false;
|
|
}
|
|
|
|
void Start()
|
|
{
|
|
FoldoutButton.onClick.AddListener(() =>
|
|
{
|
|
ContextMenu.Clear();
|
|
if(_targetCargo != GameManager.DockingBay)
|
|
ContextMenu.AddOption("Docking Bay",
|
|
() =>
|
|
{
|
|
_targetCargo = GameManager.DockingBay;
|
|
TargetCargoLabel.text = "Docking Bay";
|
|
});
|
|
foreach (var ship in GameManager.CurrentEntity.Parent.Children.Where(e => e is Ship {IsPlayerShip: true}))
|
|
{
|
|
foreach (var bay in ship.CargoBays.Select((bay, index) => (bay, index)))
|
|
{
|
|
if(_targetCargo != bay.bay)
|
|
{
|
|
ContextMenu.AddOption($"{ship.Name} Bay {bay.index+1}",
|
|
() =>
|
|
{
|
|
_targetCargo = bay.bay;
|
|
TargetCargoLabel.text = $"{ship.Name} Bay {bay.index+1}";
|
|
});
|
|
}
|
|
}
|
|
}
|
|
ContextMenu.AddOption(_selling ? "Buy Goods" : "Sell Goods", () =>
|
|
{
|
|
_selling = !_selling;
|
|
Populate();
|
|
});
|
|
var repairCost = StationServices.RepairCost(GameManager.ItemManager, GameManager.CurrentEntity);
|
|
if (repairCost > 0) ContextMenu.AddOption($"Repair ({repairCost:N0})", Repair);
|
|
ContextMenu.Show();
|
|
});
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
|
|
}
|
|
}
|