Browse Source

Set up proper trading between inventories, plus transactions

master
Devin 4 years ago
parent
commit
03f63ac945
  1. 19
      Assets/Scenes/NewShop.unity
  2. 45
      Assets/Scripts/Shop/Components/InventoryMoneyDisplay.cs
  3. 11
      Assets/Scripts/Shop/Components/InventoryMoneyDisplay.cs.meta
  4. 26
      Assets/Scripts/Shop/Model/BuyModel.cs
  5. 38
      Assets/Scripts/Shop/Model/Inventory.cs
  6. 14
      Assets/Scripts/Shop/Model/SellModel.cs
  7. 7
      Assets/Scripts/Shop/Model/ShopModel.cs
  8. 1
      Assets/Scripts/Shop/View/IShopModelObservable.cs
  9. 4
      Assets/Scripts/Shop/View/IShopModelObserver.cs
  10. 21
      Assets/Scripts/Shop/View/ShopView.cs
  11. 5
      Assets/Scripts/Shop/View/ShopViewList.cs
  12. 7
      Assets/Scripts/Shop/View/ViewItemContainer.cs

19
Assets/Scenes/NewShop.unity

@ -3601,6 +3601,7 @@ MonoBehaviour:
instructionText: {fileID: 360948192}
layoutGroup: {fileID: 2049417198}
ownModel: {fileID: 1237921498}
tradePartner: {fileID: 1237921497}
--- !u!114 &336428457
MonoBehaviour:
m_ObjectHideFlags: 0
@ -8397,6 +8398,7 @@ MonoBehaviour:
instructionText: {fileID: 1416129356}
layoutGroup: {fileID: 1865708447}
ownModel: {fileID: 132445007}
tradePartner: {fileID: 1237921497}
infoPanel: {fileID: 78292608}
--- !u!1 &831899927
GameObject:
@ -14665,6 +14667,7 @@ MonoBehaviour:
instructionText: {fileID: 403716179}
layoutGroup: {fileID: 981903572}
ownModel: {fileID: 1237921498}
tradePartner: {fileID: 132445008}
infoPanel: {fileID: 1800161527}
--- !u!114 &1296916391
MonoBehaviour:
@ -15287,6 +15290,7 @@ MonoBehaviour:
instructionText: {fileID: 1242189692}
layoutGroup: {fileID: 312492670}
ownModel: {fileID: 842666264}
tradePartner: {fileID: 1237921497}
infoPanel: {fileID: 2013250540}
--- !u!114 &1373665447
MonoBehaviour:
@ -15701,6 +15705,7 @@ MonoBehaviour:
instructionText: {fileID: 1881459943}
layoutGroup: {fileID: 1764603517}
ownModel: {fileID: 842666264}
tradePartner: {fileID: 1237921497}
--- !u!114 &1396837743
MonoBehaviour:
m_ObjectHideFlags: 0
@ -22708,6 +22713,7 @@ GameObject:
- component: {fileID: 1947464187}
- component: {fileID: 1947464189}
- component: {fileID: 1947464188}
- component: {fileID: 1947464190}
m_Layer: 5
m_Name: Number
m_TagString: Untagged
@ -22855,6 +22861,18 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1947464186}
m_CullTransparentMesh: 0
--- !u!114 &1947464190
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1947464186}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1161ecc75d6ee4d44836c2efab2a0fd4, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1 &1964503989
GameObject:
m_ObjectHideFlags: 0
@ -24649,6 +24667,7 @@ MonoBehaviour:
instructionText: {fileID: 1352417142}
layoutGroup: {fileID: 180905780}
ownModel: {fileID: 132445007}
tradePartner: {fileID: 1237921497}
--- !u!1 &2141857229
GameObject:
m_ObjectHideFlags: 0

45
Assets/Scripts/Shop/Components/InventoryMoneyDisplay.cs

@ -0,0 +1,45 @@
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
/// <summary>
/// This class' only purpose is to set the class of an item view prototype. Could be the item itself, or the infobox, or anything!
/// </summary>
[RequireComponent(typeof(TextMeshProUGUI))]
public class InventoryMoneyDisplay : MonoBehaviour,IShopModelObserver<Item>
{
private TextMeshProUGUI text;
private void Start()
{
text = GetComponent<TextMeshProUGUI>(); // Because of the meta tag, Unity will make sure this exists. No sanity checks
}
public void SetMoney(int balance)
{
if(text == null) text = GetComponent<TextMeshProUGUI>(); // Just in case this gets called before Start()
text.text = balance.ToString();
}
// This is why it's probably better to split this into two interfaces, but oh well
public void OnSelected(Item item)
{
//throw new NotImplementedException();
}
public void OnRemoved(Item item)
{
//throw new NotImplementedException();
}
public void OnAdded(Item item)
{
//throw new NotImplementedException();
}
public void OnTransaction(int balance)
{
SetMoney(balance);
}
}

11
Assets/Scripts/Shop/Components/InventoryMoneyDisplay.cs.meta

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1161ecc75d6ee4d44836c2efab2a0fd4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

26
Assets/Scripts/Shop/Model/BuyModel.cs

@ -1,11 +1,13 @@
using System;
using System.Configuration;
using UnityEngine;
/// <summary>
/// This is a concrete, empty model for the buy state of the shop for you to implement
/// </summary>
public class BuyModel : ShopModel
{
private Inventory tradePartner;
public BuyModel(float pPriceModifier, int pItemCount, int pMoney) : base(pPriceModifier, pItemCount, pMoney)
{
@ -27,9 +29,31 @@ public class BuyModel : ShopModel
public override void ConfirmSelectedItem()
{
OnRemove(GetSelectedItem()); // If there's a view subscribed, this will probably remove the item from it
if (tradePartner == null)
{
Debug.Assert(false,"Could not make trade because the shop has no trade partner");
return;
}
// if (tradePartner.Money < GetSelectedItem().basePrice * priceModifier)
// {
// var up = new NotEnoughMoneyException();
// throw up; // If you find this, you can keep it!
// }
var item = GetSelectedItem();
tradePartner.ChangeBalance((int) (-item.basePrice * priceModifier));
OnRemove(item); // If there's a view subscribed, this will probably remove the item from it
inventory.RemoveItemByIndex(selectedItemIndex); // Before removing the item from the model's actual inventory
tradePartner.AddItem(item);
SelectItemByIndex(selectedItemIndex >= inventory.GetItemCount() ? --selectedItemIndex : selectedItemIndex);
}
public override void SetTradePartner(Inventory tradePartner)
{
this.tradePartner = tradePartner;
}
}
public class NotEnoughMoneyException : Exception
{
}

38
Assets/Scripts/Shop/Model/Inventory.cs

@ -5,10 +5,11 @@ using System.Collections.Generic;
/// <summary>
/// This class defines a basic inventory
/// </summary>
public class Inventory
public class Inventory : IModelObservable<Item> // Borrowing the model observable here. Probably nothing will sub for items removed and such, but transactions are important! Lazy though, should split the observable
{
public int Money { get; }//Getter for the money, the views need it to display the amount of money.
public int Money { get; private set; } //Getter for the money, the views need it to display the amount of money.
private List<Item> itemList = new List<Item>(); //Items in the inventory
private List<IShopModelObserver<Item>> observers; // Mostly here for things that want to be up to date on transactions
//Set up the inventory with item count and money
public Inventory(int pItemCount, int pMoney) : this(pMoney)
@ -19,6 +20,7 @@ public class Inventory
public Inventory(int pMoney)
{
Money = pMoney;
observers = new List<IShopModelObserver<Item>>();
}
//------------------------------------------------------------------------------------------------------------------------
@ -108,5 +110,37 @@ public class Inventory
itemList.Add(item);
}
}
public void ChangeBalance(int change)
{
if (Money + change < 0)
throw new NotEnoughMoneyException(); // We can't change the balance if it's too expensive
Money += change;
NotifyObservers();
}
//Think of other necessary functions for the inventory based on your design of the shop. Don't forget to unit test all the functions.
public IDisposable RegisterObserver(IShopModelObserver<Item> observer)
{
// Check whether observer is already registered. If not, add it
if (! observers.Contains(observer)) {
observers.Add(observer);
// Provide observer with existing data.
observer.OnTransaction(Money);
}
return new Unsubscriber<Item>(observers, observer);
}
public void RemoveObserver(IShopModelObserver<Item> observer)
{
if (observers.Contains(observer))
observers.Remove(observer);
}
private void NotifyObservers()
{
foreach (var observer in observers)
{
observer.OnTransaction(Money);
}
}
}

14
Assets/Scripts/Shop/Model/SellModel.cs

@ -1,11 +1,14 @@
using System;
using System.Configuration;
using UnityEngine;
/// <summary>
/// This is a concrete, empty model for the sell state of the shop for you to implement
/// This is a concrete, empty model for the sell state of the shop for you to implement. Technically this is the same as the buy model, as one inventory "buys" from the other.
/// Except the shop doesn't pay because we don't want it to run out of money!
/// </summary>
public class SellModel : ShopModel
{
private Inventory tradePartner;
public SellModel(float pPriceModifier, int pItemCount, int pMoney) : base(pPriceModifier, pItemCount, pMoney)
{
@ -27,9 +30,16 @@ public class SellModel : ShopModel
public override void ConfirmSelectedItem()
{
OnRemove(GetSelectedItem()); // If there's a view subscribed, this will probably remove the item from it
var item = GetSelectedItem();
inventory.ChangeBalance((int) (item.basePrice * priceModifier)); // We actually *add* the money to the player inventory...
OnRemove(item); // If there's a view subscribed, this will probably remove the item from it
inventory.RemoveItemByIndex(selectedItemIndex); // Before removing the item from the model's actual inventory
tradePartner?.AddItem(item); // Unlike the shop, we don't actually need a trade partner here. There might be shops that don't resell!
SelectItemByIndex(selectedItemIndex >= inventory.GetItemCount() ? --selectedItemIndex : selectedItemIndex);
}
public override void SetTradePartner(Inventory tradePartner)
{
this.tradePartner = tradePartner;
}
}

7
Assets/Scripts/Shop/Model/ShopModel.cs

@ -93,6 +93,13 @@ public abstract class ShopModel : IModelObservable<Item>
//------------------------------------------------------------------------------------------------------------------------
//Concrete classes to implement
public abstract void ConfirmSelectedItem();
public void ChangeBalance(int change)
{
inventory.ChangeBalance(change);
}
public abstract void SetTradePartner(Inventory tradePartner);
// Observer pattern. Anything that wants to know what happens regarding item selection subscribes to this.
// This could mean all items of this model and its views do it, or all views do it.

1
Assets/Scripts/Shop/View/IShopModelObservable.cs

@ -10,6 +10,7 @@ public interface IShopModelObserver<T>
void OnSelected(T item);
void OnRemoved(T item);
void OnAdded(T item);
void OnTransaction(int balance); // Called when a transaction happens that changes the balance of the model, contains total new balance
}
// Unsubscriber, so the observer can self-unsubscribe from this observable without any coupling

4
Assets/Scripts/Shop/View/IShopModelObserver.cs

@ -4,12 +4,10 @@ using System.Collections.Generic;
/// <summary>
/// This interface defines an observable. Since registering those is fairly generic, we also just keep a generic one around.
/// Works somewhat similar to IObservable<T>, except that it's our own really.
/// While we use generics here, theoretically hardcoding Item types would do the job
/// While we use generics here, theoretically hardcoding Item types would do the job. Could make this 100% generic too...
/// </summary>
public interface IModelObservable<T>
{
IDisposable RegisterObserver(IShopModelObserver<T> observer);
void RemoveObserver(IShopModelObserver<T> observer);
void OnRemove(T val);
void OnSelect(T val);
}

21
Assets/Scripts/Shop/View/ShopView.cs

@ -28,6 +28,7 @@ public abstract class ShopView : MonoBehaviour
[SerializeField] protected LayoutGroup layoutGroup; // The layout group that represents this view visually
[SerializeField] private ModelComponent ownModel; // Reference to the model this view technically belongs to
[SerializeField] private InventoryComponent tradePartner; // Reference to the model this view technically belongs to
protected ShopModel model; // Model in MVC pattern
protected ShopModel other; // Other model in MVC pattern (our own inventory)
@ -40,10 +41,12 @@ public abstract class ShopView : MonoBehaviour
{
//model = new BuyModel(2f, 16, 500); //Right now use magic values to set up the shop
Debug.Assert(ownModel != null,"No shop model assigned!",this);
Debug.Assert(tradePartner != null,"No trade partner assigned!",this);
model = ownModel.Model;
shopController = gameObject.AddComponent<MouseController>().Initialize(model);//Set the default controller to be the mouse controller
SetupItemIconView(); //Setup the grid view's properties
InitializeButtons(); //Connect the buttons to the controller
//model.Subscribe(this);
}
@ -51,12 +54,30 @@ public abstract class ShopView : MonoBehaviour
{
// sanity check
if (model == null) model = ownModel.Model;
RegisterMoneyObserver(); // Make sure if this thing's got a money component somewhere we register it as an observer for the inventory we're looking at
model.SetTradePartner(tradePartner.Inventory);
PopulateItemIconView(); //Display items
}
private void OnDisable()
{
ClearIconView();
model.SetTradePartner(null);
UnregisterMoneyObserver();
}
// If view is generic, and shopview was just an implementation, this would be more flexible in theory. But for now it's enough to just manually assign "ourselves" as trade partner if we know the model works on just one inventory
private void RegisterMoneyObserver()
{
var moneyComp = GetComponentInChildren<InventoryMoneyDisplay>();
if (moneyComp != null) tradePartner.Inventory.RegisterObserver(moneyComp);
}
// Undo the above
private void UnregisterMoneyObserver()
{
var moneyComp = GetComponentInChildren<InventoryMoneyDisplay>();
if (moneyComp != null) tradePartner.Inventory.RemoveObserver(moneyComp);
}
//------------------------------------------------------------------------------------------------------------------------

5
Assets/Scripts/Shop/View/ShopViewList.cs

@ -117,4 +117,9 @@ public class ShopViewList : ShopView, IShopModelObserver<Item>
{
throw new NotImplementedException();
}
public void OnTransaction(int moneyDelta)
{
}
}

7
Assets/Scripts/Shop/View/ViewItemContainer.cs

@ -79,7 +79,12 @@ public abstract class ViewItemContainer : MonoBehaviour, IItemContainer, IShopMo
{
throw new NotImplementedException();
}
public void OnTransaction(int cost)
{
//throw new NotImplementedException(); Irrelevant for the view containers
}
// The reason we do this in OnDestroy() is so we don't have a memory leak when this gets removed externally somehow
private void OnDestroy()
{

Loading…
Cancel
Save