373 lines
13 KiB
C#
373 lines
13 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Unity.VisualScripting;
|
|
using Unity.VisualScripting.Antlr3.Runtime.Misc;
|
|
using UnityEngine;
|
|
|
|
public class InventoryManager : MonoBehaviour
|
|
{
|
|
[SerializeField] private GameObject _inventoryContentRoot;
|
|
[SerializeField] private GameObject _slotPrefab;
|
|
|
|
private ItemInstance[] Items;
|
|
private InventorySlot[] Slots;
|
|
public int Capacity = 30; // 인벤토리 칸수
|
|
|
|
private bool _isUpdating = false;
|
|
|
|
private void Awake()
|
|
{
|
|
// Items가 아예 없으면 새로 만들기
|
|
if (Items == null)
|
|
{
|
|
Items = new ItemInstance[Capacity];
|
|
}
|
|
// 기존 데이터가 있는데 Capacity와 다르다면
|
|
else if (Items.Length != Capacity)
|
|
{
|
|
// 기존 요소를 새 배열로 복사
|
|
System.Array.Resize(ref Items, Capacity);
|
|
}
|
|
|
|
Slots = new InventorySlot[Capacity];
|
|
|
|
//기존 슬롯들 지우기
|
|
foreach (Transform child in _inventoryContentRoot.transform) Destroy(child.gameObject);
|
|
|
|
for (int i = 0; i < Capacity; i++) //설정된 인벤토리 칸수만큼 추가
|
|
{
|
|
GameObject invenSlotObject = Instantiate(_slotPrefab, _inventoryContentRoot.transform);
|
|
InventorySlot invenSlot = invenSlotObject.GetComponent<InventorySlot>();
|
|
if (invenSlot != null)
|
|
{
|
|
Slots[i] = invenSlot;
|
|
invenSlot.SlotIndex = i;
|
|
invenSlot.SetItem(Items[i]);
|
|
}
|
|
}
|
|
}
|
|
private void Start()
|
|
{
|
|
|
|
}
|
|
|
|
public void UpdateUI()
|
|
{
|
|
for (int i = 0; i < Slots.Length; i++)
|
|
{
|
|
Slots[i].SetItem(Items[i]);
|
|
}
|
|
}
|
|
|
|
public void MoveItem(int sourceIndex, int targetIndex)
|
|
{
|
|
// 동일한 슬롯이면 계산할 필요 없이 종료
|
|
if (sourceIndex == targetIndex) return;
|
|
|
|
// source가 null인 경우(빈 슬롯 드래그)
|
|
if (Items[sourceIndex] == null) return;
|
|
|
|
// 타겟 슬롯이 비어있으면 그냥 이동
|
|
if (Items[targetIndex] == null)
|
|
{
|
|
Items[targetIndex] = Items[sourceIndex];
|
|
Items[sourceIndex] = null;
|
|
}
|
|
// 타겟 슬롯에 같은 아이템이 있고 중첩 가능하다면 병합 시도
|
|
else if (Items[sourceIndex].Data.ItemId == Items[targetIndex].Data.ItemId && Items[targetIndex].Data.IsStackable)
|
|
{
|
|
int canAdd = Items[targetIndex].Data.MaxStack - Items[targetIndex].CurrentStack;
|
|
int actualAdd = Mathf.Min(canAdd, Items[sourceIndex].CurrentStack);
|
|
|
|
Items[targetIndex].CurrentStack += actualAdd;
|
|
Items[sourceIndex].CurrentStack -= actualAdd;
|
|
|
|
// 원본 슬롯 아이템이 0개가 되면 제거
|
|
if (Items[sourceIndex].CurrentStack <= 0)
|
|
{
|
|
Items[sourceIndex] = null;
|
|
}
|
|
|
|
}
|
|
// 아이템이 다르거나 중첩 불가라면 스왑
|
|
else
|
|
{
|
|
ItemInstance temp = Items[targetIndex];
|
|
Items[targetIndex] = Items[sourceIndex];
|
|
Items[sourceIndex] = temp;
|
|
}
|
|
|
|
UpdateUI();
|
|
}
|
|
|
|
public void ExecuteSplit(int sourceIndex, int targetIndex, int splitCount)
|
|
{
|
|
|
|
// 대상 슬롯이 비어있는 경우
|
|
if (Items[targetIndex] == null)
|
|
{
|
|
// 전체를 다 옮기는 경우 (이동과 동일)
|
|
if (splitCount >= Items[sourceIndex].CurrentStack)
|
|
{
|
|
// 대상 슬롯에 원본 그대로 배치
|
|
Items[targetIndex] = Items[sourceIndex];
|
|
Slots[targetIndex].SetItem(Items[sourceIndex]);
|
|
|
|
// 원본 슬롯은 비우기
|
|
Items[sourceIndex] = null;
|
|
Slots[sourceIndex].ClearSlot(); // 기존 아이템 파괴 및 UI 초기화 함수
|
|
}
|
|
// 일부만 떼어내는 경우 (분할)
|
|
else
|
|
{
|
|
//원본 아이템 개수 차감
|
|
Items[sourceIndex].CurrentStack -= splitCount;
|
|
Slots[sourceIndex].UpdateSlotUI();
|
|
|
|
//새로운 아이템 인스턴스 생성 (복제)
|
|
ItemInstance newItem = new ItemInstance(Items[sourceIndex].Data, splitCount);
|
|
|
|
// 대상 슬롯에 새 아이템 배치
|
|
Items[targetIndex] = newItem;
|
|
Slots[targetIndex].SetItem(newItem);
|
|
}
|
|
}
|
|
// 대상 슬롯에 같은 아이템이 있는 경우 (합치기)
|
|
else if (Items[targetIndex].Data == Items[sourceIndex].Data)
|
|
{
|
|
int maxStack = Items[targetIndex].Data.MaxStack;
|
|
int targetRoom = maxStack - Items[targetIndex].CurrentStack; // 대상 슬롯의 남은 공간
|
|
|
|
// 옮기려는 양(splitCount)이 남은 공간보다 작거나 같으면 -> 다 들어감
|
|
if (splitCount <= targetRoom)
|
|
{
|
|
Items[targetIndex].CurrentStack += splitCount;
|
|
Items[sourceIndex].CurrentStack -= splitCount;
|
|
|
|
// 원본이 0개가 되면 슬롯 비우기
|
|
if (Items[sourceIndex].CurrentStack <= 0)
|
|
{
|
|
Items[sourceIndex] = null;
|
|
Slots[sourceIndex].ClearSlot();
|
|
}
|
|
else
|
|
{
|
|
Slots[sourceIndex].UpdateSlotUI();
|
|
}
|
|
Slots[targetIndex].UpdateSlotUI();
|
|
}
|
|
// 옮기려는 양이 남은 공간보다 많으면 -> 공간만큼만 채우고 나머지는 원본에 남김
|
|
else
|
|
{
|
|
Items[targetIndex].CurrentStack = maxStack; // 대상은 풀스택
|
|
Items[sourceIndex].CurrentStack -= targetRoom; // 원본에서는 들어간 만큼만 뺌
|
|
|
|
Slots[sourceIndex].UpdateSlotUI();
|
|
Slots[targetIndex].UpdateSlotUI();
|
|
}
|
|
}
|
|
}
|
|
|
|
// 수동 정렬
|
|
public void SortInventory()
|
|
{
|
|
// null이 아닌 아이템만 모아서 (필요시 이름/ID 등으로 정렬)
|
|
List<ItemInstance> validItems = Items.Where(i => i != null)
|
|
.OrderBy(i => i.Data.ItemType) // 1순위: 아이템 타입
|
|
.ThenBy(i => i.Data.SortId) // 2순위: 고유 정렬 번호
|
|
.ToList();
|
|
|
|
// 배열 초기화 후 앞에서부터 다시 채워넣기
|
|
Items = new ItemInstance[Capacity];
|
|
for (int i = 0; i < validItems.Count; i++)
|
|
{
|
|
Items[i] = validItems[i];
|
|
}
|
|
|
|
UpdateUI();
|
|
}
|
|
|
|
private int CalculateCanAddAmount(ItemInstance item)
|
|
{
|
|
int totalCanAdd = 0;
|
|
|
|
// 기존 슬롯의 여유 공간 합산 (중첩 가능한 경우만)
|
|
if (item.Data.IsStackable)
|
|
{
|
|
for (int i = 0; i < Items.Length; i++)
|
|
{
|
|
if (Items[i] != null && Items[i].Data.ItemId == item.Data.ItemId)
|
|
{
|
|
totalCanAdd += (Items[i].Data.MaxStack - Items[i].CurrentStack);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 빈 슬롯이 제공할 수 있는 최대 공간 합산
|
|
int emptySlotCount = Items.Count(x => x == null);
|
|
|
|
if (item.Data.IsStackable)
|
|
{
|
|
// 빈 슬롯당 MaxStack만큼 수용 가능
|
|
totalCanAdd += (emptySlotCount * item.Data.MaxStack);
|
|
}
|
|
else
|
|
{
|
|
// 중첩 불가는 빈 슬롯 하나당 1개
|
|
totalCanAdd += emptySlotCount;
|
|
}
|
|
|
|
return totalCanAdd;
|
|
}
|
|
|
|
//서버 통신 AddItem
|
|
public async Awaitable<bool> AddItemAsync(ItemInstance item)
|
|
{
|
|
if (_isUpdating) return false;
|
|
_isUpdating = true;
|
|
|
|
// 현재 가방에 총 몇 개까지 더 들어가는가?
|
|
int possibleAmount = CalculateCanAddAmount(item);
|
|
|
|
if (possibleAmount <= 0)
|
|
{
|
|
Debug.LogWarning("인벤토리에 공간이 없습니다!");
|
|
_isUpdating = false;
|
|
return false;
|
|
}
|
|
|
|
// 습득량 결정
|
|
int originStack = item.CurrentStack;
|
|
int actualAmountToTake = Mathf.Min(item.CurrentStack, possibleAmount);
|
|
int remainingAmount = item.CurrentStack - actualAmountToTake;
|
|
|
|
// 서버 통신 (실제로 가방에 들어올 양만 설정해서 보냄)
|
|
// 원래 item의 수량을 조절해서 보냄
|
|
item.CurrentStack = actualAmountToTake;
|
|
|
|
// bool serverResult = await DataManager.Instance.SaveItemToServer(item);
|
|
bool serverResult = true; // 테스트용
|
|
|
|
// 서버 저장 성공 시에만 실제 로컬 배열에 데이터 기입
|
|
if (serverResult)
|
|
{
|
|
bool result = AddItem(item); //함수 내에서 item.CurrentStack 에 추가하고 남은양이 기록됨
|
|
|
|
// 이미 습득량을 계산해서 AddItem에 넣어서 보통의 경우 남는게 없지만, 만약 AddItem을 하고 남은 분량이 있다면 그 값을 습득량 계산하고 남은값(remainingAmount)에 더해줘야 실제 남은값임
|
|
item.CurrentStack += remainingAmount;
|
|
|
|
_isUpdating = false;
|
|
return result;
|
|
}
|
|
else
|
|
item.CurrentStack = originStack;//추가된게 없으므로 원래 수량
|
|
|
|
Debug.LogError("서버 저장 실패로 아이템 획득이 취소되었습니다.");
|
|
_isUpdating = false;
|
|
return false;
|
|
}
|
|
|
|
public bool AddItem(ItemInstance item)
|
|
{
|
|
int amountToAdd = item.CurrentStack;
|
|
|
|
// 기존에 같은 아이템이 있는 모든 슬롯을 먼저 채우기
|
|
if (item.Data.IsStackable)
|
|
{
|
|
for (int i = 0; i < Items.Length; i++)
|
|
{
|
|
// 같은 ID이고, 아직 더 넣을 공간이 있는 슬롯 탐색
|
|
if (Items[i] != null && Items[i].Data.ItemId == item.Data.ItemId && Items[i].CurrentStack < Items[i].Data.MaxStack)
|
|
{
|
|
int canAdd = Items[i].Data.MaxStack - Items[i].CurrentStack; // 슬롯의 여유 공간
|
|
int actualAdd = Mathf.Min(canAdd, amountToAdd); // 실제로 넣을 수 있는 양
|
|
|
|
Items[i].CurrentStack += actualAdd;
|
|
amountToAdd -= actualAdd;
|
|
|
|
// 다 채웠으면 종료
|
|
if (amountToAdd <= 0)
|
|
{
|
|
UpdateUI();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 기존 슬롯을 다 채우고 남은 양이 있을 경우, 혹은 스택형 아이템이 아닌 경우
|
|
while (amountToAdd > 0)
|
|
{
|
|
int emptyIndex = System.Array.FindIndex(Items, x => x == null);
|
|
|
|
if (emptyIndex == -1)
|
|
{
|
|
// 더 이상 빈칸이 없는데 아이템이 남았다면 나머지는 버리도록
|
|
item.CurrentStack = amountToAdd; // 남은 양을 아이템 객체에 업데이트
|
|
|
|
// 남은 양만큼의 아이템 데이터를 새로 만들어서 필드에 버립니다.
|
|
ItemInstance leftOver = new ItemInstance(item.Data, item.CurrentStack);
|
|
leftOver.EnhancementLevel = item.EnhancementLevel;
|
|
leftOver.Durability = item.Durability;
|
|
|
|
SpawnItemToWorld(leftOver); // 필드에 버림 처리
|
|
|
|
item.CurrentStack = 0; // 원본에서는 이제 남은 양이 없도록 처리
|
|
|
|
UpdateUI();
|
|
Debug.LogWarning("인벤토리가 가득 찼습니다.");
|
|
return false;
|
|
}
|
|
|
|
// 새 슬롯에 들어갈 양 결정 (MaxStack까지만)
|
|
int addAtOnce = Mathf.Min(amountToAdd, item.Data.MaxStack);
|
|
|
|
// 여러 칸을 추가해야 될수 있으므로 복사본으로 추가
|
|
Items[emptyIndex] = new ItemInstance(item.Data, addAtOnce);
|
|
|
|
// 만약 아이템에 강화 정보 등이 있다면
|
|
Items[emptyIndex].EnhancementLevel = item.EnhancementLevel;
|
|
|
|
amountToAdd -= addAtOnce;
|
|
}
|
|
|
|
UpdateUI();
|
|
return true;
|
|
}
|
|
|
|
public bool AddItem(Item itemData)
|
|
{
|
|
ItemInstance Item = new ItemInstance(itemData);
|
|
return AddItem(Item);
|
|
}
|
|
|
|
// 월드에 아이템을 버리는(생성하는) 함수
|
|
public void SpawnItemToWorld(ItemInstance dropItem)
|
|
{
|
|
if (dropItem == null || dropItem.CurrentStack <= 0) return;
|
|
|
|
GameObject player = GameManager.Instance.Level.CurrentCharacter;
|
|
Vector3 dropPosition = player != null ? player.transform.position + player.transform.forward * 1.5f + Vector3.up * (dropItem.Data.WorldScale.y * 0.5f + 0.25f) : Vector3.zero;
|
|
|
|
// 빈 게임오브젝트를 생성하고 WorldItem 컴포넌트를 붙임
|
|
GameObject dropObj = new GameObject($"{dropItem.Data.ItemName}_Dropped");
|
|
dropObj.transform.position = dropPosition;
|
|
|
|
WorldItem worldItem = dropObj.AddComponent<WorldItem>();
|
|
worldItem.SetItem(dropItem); // SetItem으로 데이터 주입 및 모델 생성
|
|
|
|
Debug.Log($"{dropItem.Data.ItemName} {dropItem.CurrentStack}개를 필드에 버렸습니다.");
|
|
}
|
|
|
|
public void DropItemFromSlot(int slotIndex)
|
|
{
|
|
ItemInstance itemToDrop = Items[slotIndex];
|
|
if (itemToDrop == null) return;
|
|
|
|
SpawnItemToWorld(itemToDrop); // 월드에 소환
|
|
Items[slotIndex] = null; // 인벤토리에서는 제거
|
|
|
|
UpdateUI();
|
|
}
|
|
}
|