func (i *Inventory) RemoveByID(id, amt int) int {
	if i.CountID(id) < amt {
		return -1
	}
	index := i.GetIndex(id)
	if i.stackEverything || definitions.Items[id].Stackable {
		if i.Get(index).Amount == amt {
			i.Remove(index)
		} else {
			i.Get(index).Amount -= amt
		}
	} else {
		for j := 0; j < amt; j++ {
			i.Remove(i.GetIndex(id))
		}
	}
	if i.Owner != nil && i.Owner.Connected() {
		i.Owner.SendInventory()
	}
	return index
}
RemoveByID Removes amt items from this inventory by ID, returns the items index if successful, otherwise returns -1
package world
import (
	"fmt"
	"math"
	"sort"
	"strconv"
	"sync"
	"time"
	"go.uber.org/atomic"
	"github.com/spkaeros/rscgo/pkg/definitions"
	"github.com/spkaeros/rscgo/pkg/errors"
	"github.com/spkaeros/rscgo/pkg/game/entity"
	"github.com/spkaeros/rscgo/pkg/log"
	"github.com/spkaeros/rscgo/pkg/strutil"
	"github.com/spkaeros/rscgo/pkg/tasks"
)
const DefaultDrop = 20
type ItemBubble struct {
	Owner *Player
	Item  int
}
type Item struct {
	ID     int
	Amount int
	Worn   bool
	Index  int
}
func NewItem(id, amt, idx int, worn bool) *Item {
	return &Item { id, amt, worn, idx }
}
func (i *Item) Name() string {
	if i.ID >= len(definitions.Items) || i.ID < 0 {
		return "nil"
	}
	return definitions.Items[i.ID].Name
}
type Price int
func (i *Item) Price() Price {
	if i.ID >= len(definitions.Items) || i.ID < 0 {
		return -1
	}
	return Price(definitions.Items[i.ID].BasePrice)
}
func (i *Item) DeltaAmount(o *Item) int {
	return o.Amount - i.Amount
}
func (i *Item) ScalePrice(percent int) int {
	return int(i.Price().Scale(percent))
}
func (p Price) Scale(percent int) Price {
	return Price(int(math.Max(10, float64(percent)))*int(p)) / 100
}
func (i *Item) Command() string {
	if i.ID >= len(definitions.Items) || i.ID < 0 {
		return "nil"
	}
	return definitions.Items[i.ID].Command
}
func (i *Item) WieldPos() int {
	if i.ID >= len(definitions.Items) || i.ID < 0 {
		return -1
	}
	def := definitions.Equip(i.ID)
	if def == nil {
		return -1
	}
	return def.Position
}
func (i *Item) Stackable() bool {
	if i.ID >= len(definitions.Items) || i.ID < 0 {
		return false
	}
	return definitions.Items[i.ID].Stackable
}
type GroundItem struct {
	ID, Amount int
	Owner      string
	*entity.AttributeList
	Entity
}
var ItemIndexer = atomic.NewUint32(0)
func (i *GroundItem) Visibility() int {
	return i.VarInt("visibility", 0)
}
func (i *GroundItem) SpawnedTime() time.Time {
	return i.VarTime("spawnTime")
}
func NewPersistentGroundItem(id, amount, x, y, respawn int) *GroundItem {
	item := &GroundItem{ID: id, Amount: amount,
		AttributeList: entity.NewAttributeList(),
		Entity: Entity{
			Location: NewLocation(x, y),
			Index:    int(ItemIndexer.Swap(ItemIndexer.Load() + 1)),
		},
	}
	item.SetVar("visibility", 2)
	item.SetVar("respawnTime", respawn)
	item.SetVar("persistent", true)
	return item
}
func NewGroundItem(id, amount, x, y int) *GroundItem {
	item := &GroundItem{ID: id, Amount: amount,
		AttributeList: entity.NewAttributeList(),
		Entity: Entity{
			Location: NewLocation(x, y),
			Index:    int(ItemIndexer.Swap(ItemIndexer.Load() + 1)),
		},
	}
	item.SetVar("visibility", 1)
	tasks.TickList.Add(func() bool {
		item.Inc("ticker", 1)
		curTick := item.VarInt("ticker", 0)
		if item.Visibility() == 1 && len(item.Owner) == 0 {
			item.SetVar("visibility", 2)
		}
		stage := curTick / 110
		if curTick%110 == 0 {
			if item.Visibility() == 1 {
				if stage == 1 {
					if Chance(25) {
						return false
					}
					item.SetVar("visibility", 2)
					return false
				}
			}
		}
		if stage >= 3 {
			item.Remove()
		}
		return item.Visibility() == 0 || stage >= 3
	})
	item.SetVar("spawnedTime", time.Now())
	return item
}
func NewGroundItemFor(owner uint64, id, amount, x, y int) *GroundItem {
	item := NewGroundItem(id, amount, x, y)
	item.Owner = strutil.Base37.Decode(owner)
	return item
}
func (i *GroundItem) Name() string {
	if i.ID >= len(definitions.Items) || i.ID < 0 {
		return "nil"
	}
	return definitions.Items[i.ID].Name
}
func (i *GroundItem) Price() Price {
	if i.ID >= len(definitions.Items) || i.ID < 0 {
		return -1
	}
	return Price(definitions.Items[i.ID].BasePrice)
}
func (i *GroundItem) DeltaAmount(o *Item) int {
	return o.Amount - i.Amount
}
func (i *GroundItem) ScalePrice(percent int) int {
	return int(i.Price().Scale(percent))
}
func (i *GroundItem) Command() string {
	if i.ID >= len(definitions.Items) || i.ID < 0 {
		return "nil"
	}
	return definitions.Items[i.ID].Command
}
func (i *GroundItem) WieldPos() int {
	if i.ID >= len(definitions.Items) || i.ID < 0 {
		return -1
	}
	def := definitions.Equip(i.ID)
	if def == nil {
		return -1
	}
	return def.Position
}
func (i *GroundItem) Stackable() bool {
	if i.ID >= len(definitions.Items) || i.ID < 0 {
		return false
	}
	return definitions.Items[i.ID].Stackable
}
func (i *GroundItem) Remove() {
	i.UnsetVar("visibility")
	RemoveItem(i)
	if i.VarBool("persistent", false) {
		go func() {
			time.Sleep(time.Second * time.Duration(i.VarInt("respawnTime", 10)))
			i.SetVar("visibility", 2)
			AddItem(i)
		}()
	}
}
func (i *GroundItem) VisibleTo(p *Player) bool {
	if i.Visibility() == 0 {
		return false
	}
	if p.Rank() == 2 {
		return true
	}
	if i.Visibility() == 1 {
		return i.Owner == p.Username()
	}
	return i.Visibility() == 2
}
type Inventory struct {
	List            []*Item
	Owner           *Player
	Capacity        int
	stackEverything bool
	Lock            sync.RWMutex
}
type itemSorter []*Item
func (s itemSorter) Len() int {
	return len(s)
}
func (s itemSorter) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}
func (s itemSorter) Less(i, j int) bool {
	if !s[j].Stackable() && !s[i].Stackable() {
		return s[i].Price() > s[j].Price()
	}
	return !s[i].Stackable() && s[j].Stackable()
}
func (i *Inventory) Clone() *Inventory {
	var newList []*Item
	i.Range(func(item *Item) bool {
		newList = append(newList, item)
		return true
	})
	return &Inventory{List: newList, Capacity: i.Capacity, stackEverything: i.stackEverything}
}
func (i *Inventory) DeathDrops(keep int) []*GroundItem {
	var pile []*GroundItem
	if keep <= 0 {
		i.Lock.RLock()
		for _, item := range i.List {
			pile = append(pile, NewGroundItem(item.ID, item.Amount, i.Owner.X(), i.Owner.Y()))
		}
		i.Lock.RUnlock()
	} else {
		deathItems := i.Clone()
		sort.Sort(itemSorter(deathItems.List))
		if len(deathItems.List) < keep {
			keep = len(deathItems.List)
		}
		for idx := keep; idx > 0; idx-- {
			if deathItems.List[idx-1].Stackable() {
				keep--
			}
		}
		for _, item := range deathItems.List[keep:] {
			pile = append(pile, NewGroundItem(item.ID, item.Amount, i.Owner.X(), i.Owner.Y()))
		}
	}
	return pile
}
func (i *Inventory) Range(fn func(*Item) bool) int {
	i.Lock.RLock()
	defer i.Lock.RUnlock()
	for idx, item := range i.List {
		if !fn(item) {
			return idx
		}
	}
	return -1
}
func (i *Inventory) RangeRev(fn func(*Item) bool) int {
	i.Lock.RLock()
	defer i.Lock.RUnlock()
	for idx := len(i.List) - 1; idx >= 0; idx-- {
		if !fn(i.List[idx]) {
			return idx
		}
	}
	return -1
}
func (i *Inventory) Equipped(id int) bool {
	i.Lock.RLock()
	defer i.Lock.RUnlock()
	for _, item := range i.List {
		if item.ID == id && item.Worn {
			return true
		}
	}
	return false
}
func (i *Inventory) Size() int {
	i.Lock.RLock()
	defer i.Lock.RUnlock()
	return len(i.List)
}
func (i *Inventory) CanHold(id, amount int) bool {
	var slotsReq int
	if definitions.Items[id].Stackable || i.stackEverything {
		if i.GetByID(id) == nil {
			slotsReq += 1 + (amount / math.MaxInt32)
		} else {
			for i.GetByID(id).Amount+amount > math.MaxInt32 {
				slotsReq++
				amount -= math.MaxInt32
			}
		}
	} else {
		slotsReq++
	}
	return i.Size()+slotsReq-1 < i.Capacity
}
func (i *Inventory) Add(id int, qty int) int {
	if qty == 0 {
		return -1
	}
	if !i.CanHold(id, qty) {
		AddItem(NewGroundItemFor(i.Owner.UsernameHash(), id, qty, i.Owner.X(), i.Owner.Y()))
		i.Owner.Message("Your inventory is full, the " + definitions.Items[id].Name + " drops to the ground!")
		return -1
	}
	if item := i.GetByID(id); (i.stackEverything || definitions.Items[id].Stackable) && item != nil {
		if item.Amount < 0 {
			log.Suspicious.Println(errors.NewArgsError("*Inventory.Add(id,amt) Resulting item amount less than zero: " + strconv.FormatUint(uint64(item.Amount+qty), 10)))
		}
		if item.Amount+qty > math.MaxInt32 {
			item.Amount = math.MaxInt32
			return i.GetIndex(id)
		}
		item.Amount += qty
		return i.GetIndex(id)
	}
	if qty < 0 {
		return -1
	}
	newItem := &Item{ID: id, Amount: qty}
	i.Lock.Lock()
	i.List = append(i.List, newItem)
	i.Lock.Unlock()
	return i.Size() - 1
}
func (i *Inventory) Remove(index int) bool {
	item := i.Get(index)
	if item == nil {
		log.Cheatf("Attempted removing non-existent. item:%v\n", index)
		return false
	}
	if i.Owner != nil && i.Owner.Connected() {
		i.Owner.DequipItem(item)
	}
	i.Lock.Lock()
	defer i.Lock.Unlock()
	size := len(i.List)
	if index >= size {
		log.Cheatf("Attempted removing item out of inventory bounds.  index:%d,size:%d,capacity:%d\n", index, size, i.Capacity)
		return false
	}
	if index >= size-1 {
		i.List = i.List[:index]
		return true
	}
	i.List = append(i.List[:index], i.List[index+1:]...)
	return true
}