first commit
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OTGIntegrated.Core;
|
||||
using OTGIntegrated.Items;
|
||||
using UnityEngine;
|
||||
|
||||
namespace OTGIntegrated.Inventory
|
||||
{
|
||||
public sealed class Inventory : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private int maxSlots = 20;
|
||||
[SerializeField] private List<InventorySlot> slots = new List<InventorySlot>();
|
||||
|
||||
public int MaxSlots => maxSlots;
|
||||
public event Action OnInventoryChanged;
|
||||
|
||||
public bool AddItem(ItemData item, int amount)
|
||||
{
|
||||
if (!CanAddItem(item, amount))
|
||||
{
|
||||
GameEvents.RaiseNotification("Недостаточно места в инвентаре");
|
||||
return false;
|
||||
}
|
||||
|
||||
AddItemInternal(item, amount);
|
||||
RaiseChanged();
|
||||
GameEvents.RaiseItemAdded(item, amount);
|
||||
GameEvents.RaiseNotification($"+{amount} {item.displayName}");
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RemoveItem(ItemData item, int amount)
|
||||
{
|
||||
if (item == null || amount <= 0 || !HasItem(item, amount))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int remaining = amount;
|
||||
for (int i = slots.Count - 1; i >= 0 && remaining > 0; i--)
|
||||
{
|
||||
InventorySlot slot = slots[i];
|
||||
if (slot == null || slot.item != item || slot.amount <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int removed = Mathf.Min(slot.amount, remaining);
|
||||
slot.amount -= removed;
|
||||
remaining -= removed;
|
||||
|
||||
if (slot.IsEmpty())
|
||||
{
|
||||
slots.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
RaiseChanged();
|
||||
GameEvents.RaiseItemRemoved(item, amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool HasItem(ItemData item, int amount)
|
||||
{
|
||||
return item != null && amount > 0 && GetAmount(item) >= amount;
|
||||
}
|
||||
|
||||
public int GetAmount(ItemData item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int total = 0;
|
||||
for (int i = 0; i < slots.Count; i++)
|
||||
{
|
||||
InventorySlot slot = slots[i];
|
||||
if (slot != null && slot.item == item && slot.amount > 0)
|
||||
{
|
||||
total += slot.amount;
|
||||
}
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
public IReadOnlyList<InventorySlot> GetAllSlots()
|
||||
{
|
||||
return slots;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
slots.Clear();
|
||||
RaiseChanged();
|
||||
}
|
||||
|
||||
public bool CanAddItem(ItemData item, int amount)
|
||||
{
|
||||
if (item == null || amount <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int remaining = amount;
|
||||
int stackSize = Mathf.Max(1, item.maxStack);
|
||||
for (int i = 0; i < slots.Count; i++)
|
||||
{
|
||||
InventorySlot slot = slots[i];
|
||||
if (slot != null && slot.item == item && slot.amount > 0 && slot.amount < stackSize)
|
||||
{
|
||||
remaining -= stackSize - slot.amount;
|
||||
if (remaining <= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int freeSlots = Mathf.Max(0, maxSlots - slots.Count);
|
||||
int requiredSlots = Mathf.CeilToInt(remaining / (float)stackSize);
|
||||
return requiredSlots <= freeSlots;
|
||||
}
|
||||
|
||||
public List<InventorySlotSaveData> SaveData()
|
||||
{
|
||||
List<InventorySlotSaveData> saveSlots = new List<InventorySlotSaveData>();
|
||||
for (int i = 0; i < slots.Count; i++)
|
||||
{
|
||||
InventorySlot slot = slots[i];
|
||||
if (slot == null || slot.IsEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
saveSlots.Add(new InventorySlotSaveData
|
||||
{
|
||||
itemId = slot.item.itemId,
|
||||
amount = slot.amount
|
||||
});
|
||||
}
|
||||
|
||||
return saveSlots;
|
||||
}
|
||||
|
||||
public void LoadData(List<InventorySlotSaveData> saveSlots, IEnumerable<ItemData> itemCatalog)
|
||||
{
|
||||
slots.Clear();
|
||||
Dictionary<string, ItemData> lookup = new Dictionary<string, ItemData>(StringComparer.Ordinal);
|
||||
if (itemCatalog != null)
|
||||
{
|
||||
foreach (ItemData item in itemCatalog)
|
||||
{
|
||||
if (item != null && !string.IsNullOrWhiteSpace(item.itemId))
|
||||
{
|
||||
lookup[item.itemId] = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (saveSlots != null)
|
||||
{
|
||||
for (int i = 0; i < saveSlots.Count; i++)
|
||||
{
|
||||
InventorySlotSaveData slot = saveSlots[i];
|
||||
if (slot == null || string.IsNullOrWhiteSpace(slot.itemId) || slot.amount <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lookup.TryGetValue(slot.itemId, out ItemData item))
|
||||
{
|
||||
AddItemInternal(item, slot.amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RaiseChanged();
|
||||
}
|
||||
|
||||
private void AddItemInternal(ItemData item, int amount)
|
||||
{
|
||||
int remaining = amount;
|
||||
int stackSize = Mathf.Max(1, item.maxStack);
|
||||
|
||||
for (int i = 0; i < slots.Count && remaining > 0; i++)
|
||||
{
|
||||
InventorySlot slot = slots[i];
|
||||
if (slot == null || slot.item != item || slot.amount >= stackSize)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int added = Mathf.Min(stackSize - slot.amount, remaining);
|
||||
slot.amount += added;
|
||||
remaining -= added;
|
||||
}
|
||||
|
||||
while (remaining > 0 && slots.Count < maxSlots)
|
||||
{
|
||||
int added = Mathf.Min(stackSize, remaining);
|
||||
slots.Add(new InventorySlot(item, added));
|
||||
remaining -= added;
|
||||
}
|
||||
}
|
||||
|
||||
private void RaiseChanged()
|
||||
{
|
||||
OnInventoryChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 828686a030f167d54974e4f844de7052
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using OTGIntegrated.Items;
|
||||
|
||||
namespace OTGIntegrated.Inventory
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class InventorySlot
|
||||
{
|
||||
public ItemData item;
|
||||
public int amount;
|
||||
|
||||
public InventorySlot(ItemData item, int amount)
|
||||
{
|
||||
this.item = item;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public bool IsEmpty()
|
||||
{
|
||||
return item == null || amount <= 0;
|
||||
}
|
||||
|
||||
public string GetDisplayText()
|
||||
{
|
||||
return IsEmpty() ? string.Empty : $"{item.displayName} x{amount}";
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class InventorySlotSaveData
|
||||
{
|
||||
public string itemId;
|
||||
public int amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e8e76c6c6c64018fbfed8c77e04af66
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,161 @@
|
||||
using System.Collections.Generic;
|
||||
using OTGIntegrated.Items;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace OTGIntegrated.Inventory
|
||||
{
|
||||
public sealed class InventoryUI : MonoBehaviour
|
||||
{
|
||||
public GameObject panelRoot;
|
||||
public Transform slotGrid;
|
||||
public Text titleText;
|
||||
public Text descriptionText;
|
||||
public Text hintText;
|
||||
public Button closeButton;
|
||||
public Font uiFont;
|
||||
|
||||
private Inventory inventory;
|
||||
private readonly List<GameObject> spawnedSlots = new List<GameObject>();
|
||||
|
||||
public void Initialize(Inventory inventory)
|
||||
{
|
||||
this.inventory = inventory;
|
||||
if (inventory != null)
|
||||
{
|
||||
inventory.OnInventoryChanged -= Refresh;
|
||||
inventory.OnInventoryChanged += Refresh;
|
||||
}
|
||||
|
||||
if (closeButton != null)
|
||||
{
|
||||
closeButton.onClick.RemoveAllListeners();
|
||||
closeButton.onClick.AddListener(() => FindObjectOfType<OTGIntegrated.UI.UIManager>()?.CloseCurrentWindow());
|
||||
}
|
||||
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (inventory != null)
|
||||
{
|
||||
inventory.OnInventoryChanged -= Refresh;
|
||||
}
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
if (slotGrid == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ClearSlots();
|
||||
IReadOnlyList<InventorySlot> slots = inventory != null ? inventory.GetAllSlots() : null;
|
||||
int maxSlots = inventory != null ? inventory.MaxSlots : 20;
|
||||
|
||||
for (int i = 0; i < maxSlots; i++)
|
||||
{
|
||||
InventorySlot slot = slots != null && i < slots.Count ? slots[i] : null;
|
||||
CreateSlot(i, slot);
|
||||
}
|
||||
|
||||
if (titleText != null)
|
||||
{
|
||||
titleText.text = "Инвентарь";
|
||||
}
|
||||
|
||||
if (hintText != null)
|
||||
{
|
||||
hintText.text = "I — закрыть F5 — сохранить F9 — загрузить";
|
||||
}
|
||||
|
||||
if (descriptionText != null && string.IsNullOrEmpty(descriptionText.text))
|
||||
{
|
||||
descriptionText.text = "Выберите предмет.";
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateSlot(int index, InventorySlot slot)
|
||||
{
|
||||
GameObject root = new GameObject($"InventorySlot_{index}", typeof(RectTransform), typeof(Image), typeof(Button));
|
||||
root.transform.SetParent(slotGrid, false);
|
||||
spawnedSlots.Add(root);
|
||||
|
||||
RectTransform rect = root.GetComponent<RectTransform>();
|
||||
rect.sizeDelta = new Vector2(90f, 90f);
|
||||
|
||||
Image background = root.GetComponent<Image>();
|
||||
background.color = slot != null && !slot.IsEmpty()
|
||||
? Color.Lerp(new Color(0.09f, 0.1f, 0.12f, 0.95f), slot.item.itemColor, 0.35f)
|
||||
: new Color(0.07f, 0.08f, 0.1f, 0.82f);
|
||||
|
||||
Text label = CreateText(root.transform, "Label", slot != null && !slot.IsEmpty() ? slot.item.displayName : string.Empty, 14, TextAnchor.MiddleCenter);
|
||||
label.rectTransform.anchorMin = new Vector2(0.08f, 0.24f);
|
||||
label.rectTransform.anchorMax = new Vector2(0.92f, 0.78f);
|
||||
label.rectTransform.offsetMin = Vector2.zero;
|
||||
label.rectTransform.offsetMax = Vector2.zero;
|
||||
|
||||
Text count = CreateText(root.transform, "Count", slot != null && !slot.IsEmpty() ? slot.amount.ToString() : string.Empty, 14, TextAnchor.LowerRight);
|
||||
count.rectTransform.anchorMin = new Vector2(0.1f, 0.05f);
|
||||
count.rectTransform.anchorMax = new Vector2(0.95f, 0.35f);
|
||||
count.rectTransform.offsetMin = Vector2.zero;
|
||||
count.rectTransform.offsetMax = Vector2.zero;
|
||||
|
||||
Button button = root.GetComponent<Button>();
|
||||
button.onClick.AddListener(() => SelectSlot(slot));
|
||||
}
|
||||
|
||||
private void SelectSlot(InventorySlot slot)
|
||||
{
|
||||
if (descriptionText == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (slot == null || slot.IsEmpty())
|
||||
{
|
||||
descriptionText.text = "Пустой слот.";
|
||||
return;
|
||||
}
|
||||
|
||||
ItemData item = slot.item;
|
||||
descriptionText.text = $"{item.displayName}\n\n{item.description}\n\nТип: {item.itemType}\nКоличество: {slot.amount}";
|
||||
}
|
||||
|
||||
private Text CreateText(Transform parent, string name, string value, int fontSize, TextAnchor anchor)
|
||||
{
|
||||
GameObject textObject = new GameObject(name, typeof(RectTransform), typeof(Text));
|
||||
textObject.transform.SetParent(parent, false);
|
||||
Text text = textObject.GetComponent<Text>();
|
||||
text.text = value;
|
||||
text.font = uiFont != null ? uiFont : Resources.GetBuiltinResource<Font>("Arial.ttf");
|
||||
text.fontSize = fontSize;
|
||||
text.alignment = anchor;
|
||||
text.color = new Color(0.94f, 0.93f, 0.88f, 1f);
|
||||
text.resizeTextForBestFit = true;
|
||||
text.resizeTextMinSize = 10;
|
||||
text.resizeTextMaxSize = fontSize;
|
||||
return text;
|
||||
}
|
||||
|
||||
private void ClearSlots()
|
||||
{
|
||||
for (int i = 0; i < spawnedSlots.Count; i++)
|
||||
{
|
||||
if (spawnedSlots[i] != null)
|
||||
{
|
||||
Destroy(spawnedSlots[i]);
|
||||
}
|
||||
}
|
||||
|
||||
spawnedSlots.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 414a98535c9cc47948e3da48ecba4e03
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user