1000 lines
23 KiB
Go
1000 lines
23 KiB
Go
package items
|
|
|
|
import (
|
|
"fmt"
|
|
)
|
|
|
|
// NewPlayerItemList creates a new player item list
|
|
func NewPlayerItemList() *PlayerItemList {
|
|
return &PlayerItemList{
|
|
indexedItems: make(map[int32]*Item),
|
|
items: make(map[int32]map[int8]map[int16]*Item),
|
|
overflowItems: make([]*Item, 0),
|
|
}
|
|
}
|
|
|
|
// SetMaxItemIndex sets and returns the maximum saved item index
|
|
func (pil *PlayerItemList) SetMaxItemIndex() int32 {
|
|
pil.mutex.Lock()
|
|
defer pil.mutex.Unlock()
|
|
|
|
maxIndex := int32(0)
|
|
for index := range pil.indexedItems {
|
|
if index > maxIndex {
|
|
maxIndex = index
|
|
}
|
|
}
|
|
|
|
pil.maxSavedIndex = maxIndex
|
|
return maxIndex
|
|
}
|
|
|
|
// SharedBankAddAllowed checks if an item can be added to shared bank
|
|
func (pil *PlayerItemList) SharedBankAddAllowed(item *Item) bool {
|
|
if item == nil {
|
|
return false
|
|
}
|
|
|
|
// Check item flags that prevent shared bank storage
|
|
if item.CheckFlag(NoTrade) || item.CheckFlag(Attuned) || item.CheckFlag(LoreEquip) {
|
|
return false
|
|
}
|
|
|
|
// Check heirloom flag
|
|
if item.CheckFlag2(Heirloom) {
|
|
return true // Heirloom items can go in shared bank
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// GetItemsFromBagID gets all items from a specific bag
|
|
func (pil *PlayerItemList) GetItemsFromBagID(bagID int32) []*Item {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
|
|
var bagItems []*Item
|
|
|
|
if bagMap, exists := pil.items[bagID]; exists {
|
|
for _, slotMap := range bagMap {
|
|
for _, item := range slotMap {
|
|
if item != nil {
|
|
bagItems = append(bagItems, item)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return bagItems
|
|
}
|
|
|
|
// GetItemsInBag gets all items inside a bag item
|
|
func (pil *PlayerItemList) GetItemsInBag(bag *Item) []*Item {
|
|
if bag == nil || !bag.IsBag() {
|
|
return nil
|
|
}
|
|
|
|
return pil.GetItemsFromBagID(bag.Details.BagID)
|
|
}
|
|
|
|
// GetBag gets a bag from an inventory slot
|
|
func (pil *PlayerItemList) GetBag(inventorySlot int8, lock bool) *Item {
|
|
if lock {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
}
|
|
|
|
// Check main inventory slots
|
|
for bagID := int32(0); bagID < NumInvSlots; bagID++ {
|
|
if bagMap, exists := pil.items[bagID]; exists {
|
|
if slot0Map, exists := bagMap[0]; exists {
|
|
if item, exists := slot0Map[int16(inventorySlot)]; exists && item != nil && item.IsBag() {
|
|
return item
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// HasItem checks if the player has a specific item
|
|
func (pil *PlayerItemList) HasItem(itemID int32, includeBank bool) bool {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
|
|
for bagID, bagMap := range pil.items {
|
|
// Skip bank slots if not including bank
|
|
if !includeBank && bagID >= BankSlot1 && bagID <= BankSlot8 {
|
|
continue
|
|
}
|
|
|
|
for _, slotMap := range bagMap {
|
|
for _, item := range slotMap {
|
|
if item != nil && item.Details.ItemID == itemID {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// GetItemFromIndex gets an item by its index
|
|
func (pil *PlayerItemList) GetItemFromIndex(index int32) *Item {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
|
|
if item, exists := pil.indexedItems[index]; exists {
|
|
return item
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// MoveItem moves an item to a new location
|
|
func (pil *PlayerItemList) MoveItem(item *Item, invSlot int32, slot int16, appearanceType int8, eraseOld bool) {
|
|
if item == nil {
|
|
return
|
|
}
|
|
|
|
pil.mutex.Lock()
|
|
defer pil.mutex.Unlock()
|
|
|
|
// Remove from old location if requested
|
|
if eraseOld {
|
|
pil.eraseItemInternal(item)
|
|
}
|
|
|
|
// Update item location
|
|
item.Details.InvSlotID = invSlot
|
|
item.Details.SlotID = slot
|
|
item.Details.AppearanceType = int16(appearanceType)
|
|
|
|
// Add to new location
|
|
pil.addItemToLocationInternal(item, invSlot, appearanceType, slot)
|
|
}
|
|
|
|
// MoveItemByIndex moves an item by index to a new location
|
|
func (pil *PlayerItemList) MoveItemByIndex(toBagID int32, fromIndex int16, to int8, appearanceType int8, charges int8) bool {
|
|
pil.mutex.Lock()
|
|
defer pil.mutex.Unlock()
|
|
|
|
// Find item by index
|
|
var item *Item
|
|
for _, bagMap := range pil.items {
|
|
for _, slotMap := range bagMap {
|
|
for _, foundItem := range slotMap {
|
|
if foundItem != nil && foundItem.Details.NewIndex == fromIndex {
|
|
item = foundItem
|
|
break
|
|
}
|
|
}
|
|
if item != nil {
|
|
break
|
|
}
|
|
}
|
|
if item != nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
if item == nil {
|
|
return false
|
|
}
|
|
|
|
// Remove from old location
|
|
pil.eraseItemInternal(item)
|
|
|
|
// Update item properties
|
|
item.Details.BagID = toBagID
|
|
item.Details.SlotID = int16(to)
|
|
item.Details.AppearanceType = int16(appearanceType)
|
|
if charges > 0 {
|
|
item.Details.Count = int16(charges)
|
|
}
|
|
|
|
// Add to new location
|
|
pil.addItemToLocationInternal(item, toBagID, appearanceType, int16(to))
|
|
|
|
return true
|
|
}
|
|
|
|
// EraseItem removes an item from the inventory
|
|
func (pil *PlayerItemList) EraseItem(item *Item) {
|
|
if item == nil {
|
|
return
|
|
}
|
|
|
|
pil.mutex.Lock()
|
|
defer pil.mutex.Unlock()
|
|
|
|
pil.eraseItemInternal(item)
|
|
}
|
|
|
|
// eraseItemInternal removes an item from internal storage (assumes lock is held)
|
|
func (pil *PlayerItemList) eraseItemInternal(item *Item) {
|
|
if item == nil {
|
|
return
|
|
}
|
|
|
|
// Remove from indexed items
|
|
for index, indexedItem := range pil.indexedItems {
|
|
if indexedItem == item {
|
|
delete(pil.indexedItems, index)
|
|
break
|
|
}
|
|
}
|
|
|
|
// Remove from location-based storage
|
|
if bagMap, exists := pil.items[item.Details.BagID]; exists {
|
|
if slotMap, exists := bagMap[int8(item.Details.AppearanceType)]; exists {
|
|
delete(slotMap, item.Details.SlotID)
|
|
|
|
// Clean up empty maps
|
|
if len(slotMap) == 0 {
|
|
delete(bagMap, int8(item.Details.AppearanceType))
|
|
if len(bagMap) == 0 {
|
|
delete(pil.items, item.Details.BagID)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove from overflow items
|
|
for i, overflowItem := range pil.overflowItems {
|
|
if overflowItem == item {
|
|
pil.overflowItems = append(pil.overflowItems[:i], pil.overflowItems[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetItemFromUniqueID gets an item by its unique ID
|
|
func (pil *PlayerItemList) GetItemFromUniqueID(uniqueID int32, includeBank bool, lock bool) *Item {
|
|
if lock {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
}
|
|
|
|
for bagID, bagMap := range pil.items {
|
|
// Skip bank slots if not including bank
|
|
if !includeBank && bagID >= BankSlot1 && bagID <= BankSlot8 {
|
|
continue
|
|
}
|
|
|
|
for _, slotMap := range bagMap {
|
|
for _, item := range slotMap {
|
|
if item != nil && int32(item.Details.UniqueID) == uniqueID {
|
|
return item
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check overflow items
|
|
for _, item := range pil.overflowItems {
|
|
if item != nil && int32(item.Details.UniqueID) == uniqueID {
|
|
return item
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetItemFromID gets an item by its template ID
|
|
func (pil *PlayerItemList) GetItemFromID(itemID int32, count int8, includeBank bool, lock bool) *Item {
|
|
if lock {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
}
|
|
|
|
for bagID, bagMap := range pil.items {
|
|
// Skip bank slots if not including bank
|
|
if !includeBank && bagID >= BankSlot1 && bagID <= BankSlot8 {
|
|
continue
|
|
}
|
|
|
|
for _, slotMap := range bagMap {
|
|
for _, item := range slotMap {
|
|
if item != nil && item.Details.ItemID == itemID {
|
|
if count == 0 || item.Details.Count >= int16(count) {
|
|
return item
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetAllStackCountItemFromID gets the total count of all stacks of an item
|
|
func (pil *PlayerItemList) GetAllStackCountItemFromID(itemID int32, count int8, includeBank bool, lock bool) int32 {
|
|
if lock {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
}
|
|
|
|
totalCount := int32(0)
|
|
|
|
for bagID, bagMap := range pil.items {
|
|
// Skip bank slots if not including bank
|
|
if !includeBank && bagID >= BankSlot1 && bagID <= BankSlot8 {
|
|
continue
|
|
}
|
|
|
|
for _, slotMap := range bagMap {
|
|
for _, item := range slotMap {
|
|
if item != nil && item.Details.ItemID == itemID {
|
|
totalCount += int32(item.Details.Count)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return totalCount
|
|
}
|
|
|
|
// AssignItemToFreeSlot assigns an item to the first available free slot
|
|
func (pil *PlayerItemList) AssignItemToFreeSlot(item *Item, inventoryOnly bool) bool {
|
|
if item == nil {
|
|
return false
|
|
}
|
|
|
|
pil.mutex.Lock()
|
|
defer pil.mutex.Unlock()
|
|
|
|
var bagID int32
|
|
var slot int16
|
|
|
|
if pil.getFirstFreeSlotInternal(&bagID, &slot, inventoryOnly) {
|
|
item.Details.BagID = bagID
|
|
item.Details.SlotID = slot
|
|
item.Details.AppearanceType = BaseEquipment
|
|
|
|
pil.addItemToLocationInternal(item, bagID, BaseEquipment, slot)
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// GetNumberOfFreeSlots returns the number of free inventory slots
|
|
func (pil *PlayerItemList) GetNumberOfFreeSlots() int16 {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
|
|
freeSlots := int16(0)
|
|
|
|
// Check main inventory slots
|
|
for bagID := int32(0); bagID < NumInvSlots; bagID++ {
|
|
bag := pil.GetBag(int8(bagID), false)
|
|
if bag != nil && bag.BagInfo != nil {
|
|
// Count free slots in this bag
|
|
usedSlots := 0
|
|
if bagMap, exists := pil.items[bagID]; exists {
|
|
for _, slotMap := range bagMap {
|
|
usedSlots += len(slotMap)
|
|
}
|
|
}
|
|
freeSlots += int16(bag.BagInfo.NumSlots) - int16(usedSlots)
|
|
}
|
|
}
|
|
|
|
return freeSlots
|
|
}
|
|
|
|
// GetNumberOfItems returns the total number of items in inventory
|
|
func (pil *PlayerItemList) GetNumberOfItems() int16 {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
|
|
itemCount := int16(0)
|
|
|
|
for _, bagMap := range pil.items {
|
|
for _, slotMap := range bagMap {
|
|
itemCount += int16(len(slotMap))
|
|
}
|
|
}
|
|
|
|
return itemCount
|
|
}
|
|
|
|
// GetWeight returns the total weight of all items
|
|
func (pil *PlayerItemList) GetWeight() int32 {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
|
|
totalWeight := int32(0)
|
|
|
|
for _, bagMap := range pil.items {
|
|
for _, slotMap := range bagMap {
|
|
for _, item := range slotMap {
|
|
if item != nil {
|
|
totalWeight += item.GenericInfo.Weight * int32(item.Details.Count)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return totalWeight
|
|
}
|
|
|
|
// HasFreeSlot checks if there's at least one free slot
|
|
func (pil *PlayerItemList) HasFreeSlot() bool {
|
|
return pil.GetNumberOfFreeSlots() > 0
|
|
}
|
|
|
|
// HasFreeBagSlot checks if there's a free bag slot in main inventory
|
|
func (pil *PlayerItemList) HasFreeBagSlot() bool {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
|
|
// Check main inventory bag slots
|
|
for bagSlot := int8(0); bagSlot < NumInvSlots; bagSlot++ {
|
|
bag := pil.GetBag(bagSlot, false)
|
|
if bag == nil {
|
|
return true // Empty bag slot
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// DestroyItem destroys an item by index
|
|
func (pil *PlayerItemList) DestroyItem(index int16) {
|
|
pil.mutex.Lock()
|
|
defer pil.mutex.Unlock()
|
|
|
|
// Find and remove item by index
|
|
for _, bagMap := range pil.items {
|
|
for _, slotMap := range bagMap {
|
|
for _, item := range slotMap {
|
|
if item != nil && item.Details.NewIndex == index {
|
|
pil.eraseItemInternal(item)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// CanStack checks if an item can be stacked with existing items
|
|
func (pil *PlayerItemList) CanStack(item *Item, includeBank bool) *Item {
|
|
if item == nil {
|
|
return nil
|
|
}
|
|
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
|
|
return pil.canStackInternal(item, includeBank)
|
|
}
|
|
|
|
// canStackInternal checks if an item can be stacked - internal version without locking
|
|
func (pil *PlayerItemList) canStackInternal(item *Item, includeBank bool) *Item {
|
|
if item == nil {
|
|
return nil
|
|
}
|
|
|
|
for bagID, bagMap := range pil.items {
|
|
// Skip bank slots if not including bank
|
|
if !includeBank && bagID >= BankSlot1 && bagID <= BankSlot8 {
|
|
continue
|
|
}
|
|
|
|
for _, slotMap := range bagMap {
|
|
for _, existingItem := range slotMap {
|
|
if existingItem != nil &&
|
|
existingItem.Details.ItemID == item.Details.ItemID &&
|
|
existingItem.Details.Count < existingItem.StackCount &&
|
|
existingItem.Details.UniqueID != item.Details.UniqueID {
|
|
return existingItem
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetAllItemsFromID gets all items with a specific ID
|
|
func (pil *PlayerItemList) GetAllItemsFromID(itemID int32, includeBank bool, lock bool) []*Item {
|
|
if lock {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
}
|
|
|
|
var matchingItems []*Item
|
|
|
|
for bagID, bagMap := range pil.items {
|
|
// Skip bank slots if not including bank
|
|
if !includeBank && bagID >= BankSlot1 && bagID <= BankSlot8 {
|
|
continue
|
|
}
|
|
|
|
for _, slotMap := range bagMap {
|
|
for _, item := range slotMap {
|
|
if item != nil && item.Details.ItemID == itemID {
|
|
matchingItems = append(matchingItems, item)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return matchingItems
|
|
}
|
|
|
|
// RemoveItem removes an item from inventory
|
|
func (pil *PlayerItemList) RemoveItem(item *Item, deleteItem bool, lock bool) {
|
|
if item == nil {
|
|
return
|
|
}
|
|
|
|
if lock {
|
|
pil.mutex.Lock()
|
|
defer pil.mutex.Unlock()
|
|
}
|
|
|
|
pil.eraseItemInternal(item)
|
|
|
|
if deleteItem {
|
|
// Mark item for deletion
|
|
item.NeedsDeletion = true
|
|
}
|
|
}
|
|
|
|
// AddItem adds an item to the inventory
|
|
func (pil *PlayerItemList) AddItem(item *Item) bool {
|
|
if item == nil {
|
|
return false
|
|
}
|
|
|
|
pil.mutex.Lock()
|
|
defer pil.mutex.Unlock()
|
|
|
|
// Try to stack with existing items first
|
|
stackableItem := pil.canStackInternal(item, false)
|
|
if stackableItem != nil {
|
|
// Stack with existing item
|
|
stackableItem.Details.Count += item.Details.Count
|
|
if stackableItem.Details.Count > stackableItem.StackCount {
|
|
// Handle overflow
|
|
overflow := stackableItem.Details.Count - stackableItem.StackCount
|
|
stackableItem.Details.Count = stackableItem.StackCount
|
|
item.Details.Count = overflow
|
|
// Continue to add the overflow as a new item
|
|
} else {
|
|
return true // Successfully stacked
|
|
}
|
|
}
|
|
|
|
// Try to assign to free slot
|
|
var bagID int32
|
|
var slot int16
|
|
if pil.getFirstFreeSlotInternal(&bagID, &slot, true) {
|
|
item.Details.BagID = bagID
|
|
item.Details.SlotID = slot
|
|
item.Details.AppearanceType = BaseEquipment
|
|
|
|
pil.addItemToLocationInternal(item, bagID, BaseEquipment, slot)
|
|
return true
|
|
}
|
|
|
|
// Add to overflow if no free slots
|
|
return pil.addOverflowItemInternal(item)
|
|
}
|
|
|
|
// GetItem gets an item from a specific location
|
|
func (pil *PlayerItemList) GetItem(bagSlot int32, slot int16, appearanceType int8) *Item {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
|
|
if bagMap, exists := pil.items[bagSlot]; exists {
|
|
if slotMap, exists := bagMap[appearanceType]; exists {
|
|
if item, exists := slotMap[slot]; exists {
|
|
return item
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetAllItems returns all items in the inventory
|
|
func (pil *PlayerItemList) GetAllItems() map[int32]*Item {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
|
|
// Return a copy of indexed items
|
|
allItems := make(map[int32]*Item)
|
|
for index, item := range pil.indexedItems {
|
|
allItems[index] = item
|
|
}
|
|
|
|
return allItems
|
|
}
|
|
|
|
// HasFreeBankSlot checks if there's a free bank slot
|
|
func (pil *PlayerItemList) HasFreeBankSlot() bool {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
|
|
// Check bank bag slots
|
|
for bagSlot := int32(BankSlot1); bagSlot <= BankSlot8; bagSlot++ {
|
|
if _, exists := pil.items[bagSlot]; !exists {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// FindFreeBankSlot finds the first free bank slot
|
|
func (pil *PlayerItemList) FindFreeBankSlot() int8 {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
|
|
for bagSlot := int32(BankSlot1); bagSlot <= BankSlot8; bagSlot++ {
|
|
if _, exists := pil.items[bagSlot]; !exists {
|
|
return int8(bagSlot - BankSlot1)
|
|
}
|
|
}
|
|
|
|
return -1
|
|
}
|
|
|
|
// GetFirstFreeSlot gets the first free slot coordinates
|
|
func (pil *PlayerItemList) GetFirstFreeSlot(bagID *int32, slot *int16) bool {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
|
|
return pil.getFirstFreeSlotInternal(bagID, slot, true)
|
|
}
|
|
|
|
// getFirstFreeSlotInternal gets the first free slot (assumes lock is held)
|
|
func (pil *PlayerItemList) getFirstFreeSlotInternal(bagID *int32, slot *int16, inventoryOnly bool) bool {
|
|
// Check main inventory bags first
|
|
for bagSlotID := int32(0); bagSlotID < NumInvSlots; bagSlotID++ {
|
|
bag := pil.GetBag(int8(bagSlotID), false)
|
|
if bag != nil && bag.BagInfo != nil {
|
|
// Check slots in this bag
|
|
bagMap := pil.items[bagSlotID]
|
|
if bagMap == nil {
|
|
bagMap = make(map[int8]map[int16]*Item)
|
|
pil.items[bagSlotID] = bagMap
|
|
}
|
|
|
|
slotMap := bagMap[BaseEquipment]
|
|
if slotMap == nil {
|
|
slotMap = make(map[int16]*Item)
|
|
bagMap[BaseEquipment] = slotMap
|
|
}
|
|
|
|
for slotID := int16(0); slotID < int16(bag.BagInfo.NumSlots); slotID++ {
|
|
if _, exists := slotMap[slotID]; !exists {
|
|
*bagID = bagSlotID
|
|
*slot = slotID
|
|
return true
|
|
}
|
|
}
|
|
} else {
|
|
// No bag in this slot, check if we can place items directly (for testing)
|
|
bagMap := pil.items[bagSlotID]
|
|
if bagMap == nil {
|
|
bagMap = make(map[int8]map[int16]*Item)
|
|
pil.items[bagSlotID] = bagMap
|
|
}
|
|
|
|
slotMap := bagMap[BaseEquipment]
|
|
if slotMap == nil {
|
|
slotMap = make(map[int16]*Item)
|
|
bagMap[BaseEquipment] = slotMap
|
|
}
|
|
|
|
// Check if slot 0 is free (allow one item per bag slot without a bag)
|
|
if _, exists := slotMap[0]; !exists {
|
|
*bagID = bagSlotID
|
|
*slot = 0
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check bank bags if not inventory only
|
|
if !inventoryOnly {
|
|
for bagSlotID := int32(BankSlot1); bagSlotID <= BankSlot8; bagSlotID++ {
|
|
bag := pil.GetBankBag(int8(bagSlotID-BankSlot1), false)
|
|
if bag != nil && bag.BagInfo != nil {
|
|
bagMap := pil.items[bagSlotID]
|
|
if bagMap == nil {
|
|
bagMap = make(map[int8]map[int16]*Item)
|
|
pil.items[bagSlotID] = bagMap
|
|
}
|
|
|
|
slotMap := bagMap[BaseEquipment]
|
|
if slotMap == nil {
|
|
slotMap = make(map[int16]*Item)
|
|
bagMap[BaseEquipment] = slotMap
|
|
}
|
|
|
|
for slotID := int16(0); slotID < int16(bag.BagInfo.NumSlots); slotID++ {
|
|
if _, exists := slotMap[slotID]; !exists {
|
|
*bagID = bagSlotID
|
|
*slot = slotID
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// GetFirstFreeBankSlot gets the first free bank slot coordinates
|
|
func (pil *PlayerItemList) GetFirstFreeBankSlot(bagID *int32, slot *int16) bool {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
|
|
return pil.getFirstFreeSlotInternal(bagID, slot, false)
|
|
}
|
|
|
|
// GetBankBag gets a bank bag by slot
|
|
func (pil *PlayerItemList) GetBankBag(inventorySlot int8, lock bool) *Item {
|
|
if lock {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
}
|
|
|
|
bagID := int32(BankSlot1) + int32(inventorySlot)
|
|
if bagMap, exists := pil.items[bagID]; exists {
|
|
if slotMap, exists := bagMap[0]; exists {
|
|
if item, exists := slotMap[0]; exists && item != nil && item.IsBag() {
|
|
return item
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AddOverflowItem adds an item to overflow storage
|
|
func (pil *PlayerItemList) AddOverflowItem(item *Item) bool {
|
|
if item == nil {
|
|
return false
|
|
}
|
|
|
|
pil.mutex.Lock()
|
|
defer pil.mutex.Unlock()
|
|
|
|
return pil.addOverflowItemInternal(item)
|
|
}
|
|
|
|
// addOverflowItemInternal adds an item to overflow storage - internal version without locking
|
|
func (pil *PlayerItemList) addOverflowItemInternal(item *Item) bool {
|
|
if item == nil {
|
|
return false
|
|
}
|
|
|
|
pil.overflowItems = append(pil.overflowItems, item)
|
|
return true
|
|
}
|
|
|
|
// GetOverflowItem gets the first overflow item
|
|
func (pil *PlayerItemList) GetOverflowItem() *Item {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
|
|
if len(pil.overflowItems) > 0 {
|
|
return pil.overflowItems[0]
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RemoveOverflowItem removes an item from overflow storage
|
|
func (pil *PlayerItemList) RemoveOverflowItem(item *Item) {
|
|
if item == nil {
|
|
return
|
|
}
|
|
|
|
pil.mutex.Lock()
|
|
defer pil.mutex.Unlock()
|
|
|
|
for i, overflowItem := range pil.overflowItems {
|
|
if overflowItem == item {
|
|
pil.overflowItems = append(pil.overflowItems[:i], pil.overflowItems[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetOverflowItemList returns all overflow items
|
|
func (pil *PlayerItemList) GetOverflowItemList() []*Item {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
|
|
// Return a copy of the overflow list
|
|
overflowCopy := make([]*Item, len(pil.overflowItems))
|
|
copy(overflowCopy, pil.overflowItems)
|
|
|
|
return overflowCopy
|
|
}
|
|
|
|
// ResetPackets resets packet data
|
|
func (pil *PlayerItemList) ResetPackets() {
|
|
pil.mutex.Lock()
|
|
defer pil.mutex.Unlock()
|
|
|
|
pil.xorPacket = nil
|
|
pil.origPacket = nil
|
|
pil.packetCount = 0
|
|
}
|
|
|
|
// CheckSlotConflict checks for slot conflicts (lore items, etc.)
|
|
func (pil *PlayerItemList) CheckSlotConflict(item *Item, checkLoreOnly bool, lockMutex bool, loreStackCount *int16) int32 {
|
|
if item == nil {
|
|
return 0
|
|
}
|
|
|
|
if lockMutex {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
}
|
|
|
|
// Check for lore conflicts
|
|
if item.CheckFlag(Lore) || item.CheckFlag(LoreEquip) {
|
|
stackCount := int16(0)
|
|
|
|
for _, bagMap := range pil.items {
|
|
for _, slotMap := range bagMap {
|
|
for _, existingItem := range slotMap {
|
|
if existingItem != nil && existingItem.Details.ItemID == item.Details.ItemID {
|
|
stackCount++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if loreStackCount != nil {
|
|
*loreStackCount = stackCount
|
|
}
|
|
|
|
if stackCount > 0 {
|
|
return 1 // Lore conflict
|
|
}
|
|
}
|
|
|
|
return 0 // No conflict
|
|
}
|
|
|
|
// GetItemCountInBag returns the number of items in a bag
|
|
func (pil *PlayerItemList) GetItemCountInBag(bag *Item) int32 {
|
|
if bag == nil || !bag.IsBag() {
|
|
return 0
|
|
}
|
|
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
|
|
count := int32(0)
|
|
if bagMap, exists := pil.items[bag.Details.BagID]; exists {
|
|
for _, slotMap := range bagMap {
|
|
count += int32(len(slotMap))
|
|
}
|
|
}
|
|
|
|
return count
|
|
}
|
|
|
|
// GetFirstNewItem gets the index of the first new item
|
|
func (pil *PlayerItemList) GetFirstNewItem() int16 {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
|
|
for _, bagMap := range pil.items {
|
|
for _, slotMap := range bagMap {
|
|
for _, item := range slotMap {
|
|
if item != nil && item.Details.NewItem {
|
|
return item.Details.NewIndex
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1
|
|
}
|
|
|
|
// GetNewItemByIndex gets a new item by its index
|
|
func (pil *PlayerItemList) GetNewItemByIndex(index int16) int16 {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
|
|
for _, bagMap := range pil.items {
|
|
for _, slotMap := range bagMap {
|
|
for _, item := range slotMap {
|
|
if item != nil && item.Details.NewItem && item.Details.NewIndex == index {
|
|
return index
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1
|
|
}
|
|
|
|
// addItemToLocationInternal adds an item to a specific location (assumes lock is held)
|
|
func (pil *PlayerItemList) addItemToLocationInternal(item *Item, bagID int32, appearanceType int8, slot int16) {
|
|
if item == nil {
|
|
return
|
|
}
|
|
|
|
// Ensure bag map exists
|
|
if pil.items[bagID] == nil {
|
|
pil.items[bagID] = make(map[int8]map[int16]*Item)
|
|
}
|
|
|
|
// Ensure appearance type map exists
|
|
if pil.items[bagID][appearanceType] == nil {
|
|
pil.items[bagID][appearanceType] = make(map[int16]*Item)
|
|
}
|
|
|
|
// Add item to location
|
|
pil.items[bagID][appearanceType][slot] = item
|
|
|
|
// Add to indexed items
|
|
if item.Details.Index > 0 {
|
|
pil.indexedItems[int32(item.Details.Index)] = item
|
|
}
|
|
}
|
|
|
|
// IsItemInSlotType checks if an item is in a specific slot type
|
|
func (pil *PlayerItemList) IsItemInSlotType(item *Item, slotType InventorySlotType, lockItems bool) bool {
|
|
if item == nil {
|
|
return false
|
|
}
|
|
|
|
if lockItems {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
}
|
|
|
|
bagID := item.Details.BagID
|
|
|
|
switch slotType {
|
|
case BaseInventory:
|
|
return bagID >= 0 && bagID < NumInvSlots
|
|
case Bank:
|
|
return bagID >= BankSlot1 && bagID <= BankSlot8
|
|
case SharedBank:
|
|
// TODO: Implement shared bank slot detection
|
|
return false
|
|
case Overflow:
|
|
// Check if item is in overflow list
|
|
for _, overflowItem := range pil.overflowItems {
|
|
if overflowItem == item {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// String returns a string representation of the player item list
|
|
func (pil *PlayerItemList) String() string {
|
|
pil.mutex.RLock()
|
|
defer pil.mutex.RUnlock()
|
|
|
|
return fmt.Sprintf("PlayerItemList{Items: %d, Overflow: %d, MaxIndex: %d}",
|
|
len(pil.indexedItems), len(pil.overflowItems), pil.maxSavedIndex)
|
|
}
|
|
|
|
func init() {
|
|
// Player item list system initialized
|
|
}
|