up3
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
package inventory
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/df-mc/dragonfly/server/item"
|
||||
"github.com/df-mc/dragonfly/server/item/enchantment"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"math"
|
||||
"math/rand/v2"
|
||||
)
|
||||
|
||||
// Armour represents an inventory for armour. It has 4 slots, one for a helmet, chestplate, leggings and
|
||||
// boots respectively. NewArmour() must be used to create a valid armour inventory.
|
||||
// Armour inventories, like normal Inventories, are safe for concurrent usage.
|
||||
type Armour struct {
|
||||
inv *Inventory
|
||||
}
|
||||
|
||||
// NewArmour returns an armour inventory that is ready to be used. The zero value of an inventory.Armour is
|
||||
// not valid for usage.
|
||||
// The function passed is called when a slot is changed. It may be nil to not call anything.
|
||||
func NewArmour(f func(slot int, before, after item.Stack)) *Armour {
|
||||
inv := New(4, f)
|
||||
inv.validator = canAddArmour
|
||||
return &Armour{inv: inv}
|
||||
}
|
||||
|
||||
// canAddArmour checks if the item passed can be worn as armour in the slot passed.
|
||||
func canAddArmour(s item.Stack, slot int) bool {
|
||||
if s.Empty() {
|
||||
return true
|
||||
}
|
||||
switch slot {
|
||||
case 0:
|
||||
if h, ok := s.Item().(item.HelmetType); ok {
|
||||
return h.Helmet()
|
||||
}
|
||||
case 1:
|
||||
if c, ok := s.Item().(item.ChestplateType); ok {
|
||||
return c.Chestplate()
|
||||
}
|
||||
case 2:
|
||||
if l, ok := s.Item().(item.LeggingsType); ok {
|
||||
return l.Leggings()
|
||||
}
|
||||
case 3:
|
||||
if b, ok := s.Item().(item.BootsType); ok {
|
||||
return b.Boots()
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Set sets all individual pieces of armour in one go. It is equivalent to calling SetHelmet, SetChestplate, SetLeggings
|
||||
// and SetBoots sequentially.
|
||||
func (a *Armour) Set(helmet, chestplate, leggings, boots item.Stack) {
|
||||
a.SetHelmet(helmet)
|
||||
a.SetChestplate(chestplate)
|
||||
a.SetLeggings(leggings)
|
||||
a.SetBoots(boots)
|
||||
}
|
||||
|
||||
// SetHelmet sets the item stack passed as the helmet in the inventory.
|
||||
func (a *Armour) SetHelmet(helmet item.Stack) {
|
||||
_ = a.inv.SetItem(0, helmet)
|
||||
}
|
||||
|
||||
// Helmet returns the item stack set as helmet in the inventory.
|
||||
func (a *Armour) Helmet() item.Stack {
|
||||
i, _ := a.inv.Item(0)
|
||||
return i
|
||||
}
|
||||
|
||||
// SetChestplate sets the item stack passed as the chestplate in the inventory.
|
||||
func (a *Armour) SetChestplate(chestplate item.Stack) {
|
||||
_ = a.inv.SetItem(1, chestplate)
|
||||
}
|
||||
|
||||
// Chestplate returns the item stack set as chestplate in the inventory.
|
||||
func (a *Armour) Chestplate() item.Stack {
|
||||
i, _ := a.inv.Item(1)
|
||||
return i
|
||||
}
|
||||
|
||||
// SetLeggings sets the item stack passed as the leggings in the inventory.
|
||||
func (a *Armour) SetLeggings(leggings item.Stack) {
|
||||
_ = a.inv.SetItem(2, leggings)
|
||||
}
|
||||
|
||||
// Leggings returns the item stack set as leggings in the inventory.
|
||||
func (a *Armour) Leggings() item.Stack {
|
||||
i, _ := a.inv.Item(2)
|
||||
return i
|
||||
}
|
||||
|
||||
// SetBoots sets the item stack passed as the boots in the inventory.
|
||||
func (a *Armour) SetBoots(boots item.Stack) {
|
||||
_ = a.inv.SetItem(3, boots)
|
||||
}
|
||||
|
||||
// Boots returns the item stack set as boots in the inventory.
|
||||
func (a *Armour) Boots() item.Stack {
|
||||
i, _ := a.inv.Item(3)
|
||||
return i
|
||||
}
|
||||
|
||||
// DamageReduction returns the amount of damage that is reduced by the Armour for
|
||||
// an amount of damage and damage source. The value returned takes into account
|
||||
// the armour itself and its enchantments.
|
||||
func (a *Armour) DamageReduction(dmg float64, src world.DamageSource) float64 {
|
||||
var (
|
||||
original = dmg
|
||||
defencePoints, toughness float64
|
||||
enchantments []item.Enchantment
|
||||
)
|
||||
|
||||
for _, it := range a.Items() {
|
||||
enchantments = append(enchantments, it.Enchantments()...)
|
||||
if armour, ok := it.Item().(item.Armour); ok {
|
||||
defencePoints += armour.DefencePoints()
|
||||
toughness += armour.Toughness()
|
||||
}
|
||||
}
|
||||
|
||||
dmg -= dmg * enchantment.ProtectionFactor(src, enchantments)
|
||||
if src.ReducedByArmour() {
|
||||
// Armour in Bedrock edition reduces the damage taken by 4% for each effective armour point. Effective
|
||||
// armour point decreases as damage increases, with 1 point lost for every 2 HP of damage. The defence
|
||||
// reduction is decreased by the toughness armour value. Effective armour points will at minimum be 20% of
|
||||
// armour points.
|
||||
dmg -= dmg * 0.04 * math.Max(defencePoints*0.2, defencePoints-dmg/(2+toughness/4))
|
||||
}
|
||||
return original - dmg
|
||||
}
|
||||
|
||||
// HighestEnchantmentLevel looks up the highest level of an item.EnchantmentType
|
||||
// that any of the Armour items have and returns it, or 0 if none of the items
|
||||
// have the enchantment.
|
||||
func (a *Armour) HighestEnchantmentLevel(t item.EnchantmentType) int {
|
||||
lvl := 0
|
||||
for _, it := range a.Items() {
|
||||
if e, ok := it.Enchantment(t); ok && e.Level() > lvl {
|
||||
lvl = e.Level()
|
||||
}
|
||||
}
|
||||
return lvl
|
||||
}
|
||||
|
||||
// DamageFunc is a function that deals d damage points to an item stack s. The
|
||||
// resulting item.Stack is returned. Depending on the game mode of a player,
|
||||
// damage may not be dealt at all.
|
||||
type DamageFunc func(s item.Stack, d int) item.Stack
|
||||
|
||||
// Damage deals damage (hearts) to Armour. The resulting item damage depends on the
|
||||
// dmg passed and the DamageFunc used.
|
||||
func (a *Armour) Damage(dmg float64, f DamageFunc) {
|
||||
armourDamage := int(math.Max(math.Floor(dmg/4), 1))
|
||||
for slot, it := range a.Slots() {
|
||||
_ = a.inv.SetItem(slot, f(it, armourDamage))
|
||||
}
|
||||
}
|
||||
|
||||
// ThornsDamage checks if any of the Armour items are enchanted with thorns. If
|
||||
// this is the case and the thorns enchantment activates (15% chance per level),
|
||||
// a random Armour piece is damaged. The damage to be dealt to the attacker is
|
||||
// returned.
|
||||
func (a *Armour) ThornsDamage(f DamageFunc) float64 {
|
||||
slots := a.Slots()
|
||||
dmg := 0.0
|
||||
|
||||
for _, i := range slots {
|
||||
thorns, _ := i.Enchantment(enchantment.Thorns)
|
||||
if level := float64(thorns.Level()); rand.Float64() < level*0.15 {
|
||||
// 15%/level chance of thorns activation per item. Total damage from
|
||||
// normal thorns armour (max thorns III) should never exceed 4.0 in
|
||||
// total.
|
||||
dmg = math.Min(dmg+float64(1+rand.IntN(4)), 4.0)
|
||||
}
|
||||
}
|
||||
if highest := a.HighestEnchantmentLevel(enchantment.Thorns); highest > 10 {
|
||||
// When we find an armour piece with thorns XI or above, the logic
|
||||
// changes: We have to find the armour piece with the highest level
|
||||
// of thorns and subtract 10 from its level to calculate the final
|
||||
// damage.
|
||||
dmg = float64(highest - 10)
|
||||
}
|
||||
if dmg > 0 {
|
||||
// Deal 2 damage to one random thorns item. Bedrock Edition and Java Edition
|
||||
// both have different behaviour here and neither seem to match the expected
|
||||
// behaviour. Java Edition deals 2 damage to a random thorns item for every
|
||||
// thorns armour item worn, while Bedrock Edition deals 1 additional damage
|
||||
// for every thorns item and another 2 for every thorns item when it
|
||||
// activates.
|
||||
slot := rand.IntN(len(slots))
|
||||
_ = a.Inventory().SetItem(slot, f(slots[slot], 2))
|
||||
}
|
||||
return dmg
|
||||
}
|
||||
|
||||
// KnockBackResistance returns the combined knock back resistance of all Armour
|
||||
// items. A value of 0 means normal knock back force, while a value of 1 means
|
||||
// all knock back is ignored.
|
||||
func (a *Armour) KnockBackResistance() float64 {
|
||||
resistance := 0.0
|
||||
for _, i := range a.Items() {
|
||||
if a, ok := i.Item().(item.Armour); ok {
|
||||
resistance += a.KnockBackResistance()
|
||||
}
|
||||
}
|
||||
return resistance
|
||||
}
|
||||
|
||||
// Slots returns all items (including) air of the armour inventory in the order of helmet, chestplate, leggings,
|
||||
// boots.
|
||||
func (a *Armour) Slots() []item.Stack {
|
||||
return a.inv.Slots()
|
||||
}
|
||||
|
||||
// Items returns a slice of all non-empty armour items equipped.
|
||||
func (a *Armour) Items() []item.Stack {
|
||||
return a.inv.Items()
|
||||
}
|
||||
|
||||
// Clear clears the armour inventory, removing all items currently present.
|
||||
func (a *Armour) Clear() []item.Stack {
|
||||
return a.inv.Clear()
|
||||
}
|
||||
|
||||
// String converts the armour to a readable string representation.
|
||||
func (a *Armour) String() string {
|
||||
return fmt.Sprintf("(helmet: %v, chestplate: %v, leggings: %v, boots: %v)", a.Helmet(), a.Chestplate(), a.Leggings(), a.Boots())
|
||||
}
|
||||
|
||||
// Inventory returns the underlying Inventory instance.
|
||||
func (a *Armour) Inventory() *Inventory {
|
||||
return a.inv
|
||||
}
|
||||
|
||||
// Handle assigns a Handler to an Armour inventory so that its methods are called for the respective events. Nil may be
|
||||
// passed to set the default NopHandler.
|
||||
// Handle is the equivalent of calling (*Armour).Inventory().H.
|
||||
func (a *Armour) Handle(h Handler) {
|
||||
a.inv.Handle(h)
|
||||
}
|
||||
|
||||
// Close closes the armour inventory, removing the slot change function.
|
||||
func (a *Armour) Close() error {
|
||||
return a.inv.Close()
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package inventory
|
||||
|
||||
import (
|
||||
"github.com/df-mc/dragonfly/server/event"
|
||||
"github.com/df-mc/dragonfly/server/item"
|
||||
)
|
||||
|
||||
type Holder interface{}
|
||||
|
||||
type Context = event.Context[Holder]
|
||||
|
||||
// Handler is a type that may be used to handle actions performed on an inventory by a player.
|
||||
type Handler interface {
|
||||
// HandleTake handles an item.Stack being taken from a slot in the inventory. This item might be the whole stack or
|
||||
// part of the stack currently present in that slot.
|
||||
HandleTake(ctx *Context, slot int, it item.Stack)
|
||||
// HandlePlace handles an item.Stack being placed in a slot of the inventory. It might either be added to an empty
|
||||
// slot or a slot that contains an item of the same type.
|
||||
HandlePlace(ctx *Context, slot int, it item.Stack)
|
||||
// HandleDrop handles the dropping of an item.Stack in a slot out of the inventory.
|
||||
HandleDrop(ctx *Context, slot int, it item.Stack)
|
||||
}
|
||||
|
||||
// Check to make sure NopHandler implements Handler.
|
||||
var _ Handler = NopHandler{}
|
||||
|
||||
// NopHandler is an implementation of Handler that does not run any code in any of its methods. It is the default
|
||||
// Handler of an Inventory.
|
||||
type NopHandler struct{}
|
||||
|
||||
func (NopHandler) HandleTake(*Context, int, item.Stack) {}
|
||||
func (NopHandler) HandlePlace(*Context, int, item.Stack) {}
|
||||
func (NopHandler) HandleDrop(*Context, int, item.Stack) {}
|
||||
@@ -0,0 +1,433 @@
|
||||
package inventory
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/df-mc/dragonfly/server/item"
|
||||
"math"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Inventory represents an inventory containing items. These inventories may be carried by entities or may be
|
||||
// held by blocks such as chests.
|
||||
// The size of an inventory may be specified upon construction, but cannot be changed after. The zero value of
|
||||
// an inventory is invalid. Use New() to obtain a new inventory.
|
||||
// Inventory is safe for concurrent usage: Its values are protected by a mutex.
|
||||
type Inventory struct {
|
||||
mu sync.RWMutex
|
||||
h Handler
|
||||
slots []item.Stack
|
||||
|
||||
f SlotFunc
|
||||
validator SlotValidatorFunc
|
||||
}
|
||||
|
||||
// SlotFunc is a function called for each item changed in an Inventory.
|
||||
type SlotFunc func(slot int, before, after item.Stack)
|
||||
|
||||
// SlotValidatorFunc is a function that limits changes in the Inventory slot.
|
||||
type SlotValidatorFunc func(s item.Stack, slot int) bool
|
||||
|
||||
// ErrSlotOutOfRange is returned by any methods on inventory when a slot is passed which is not within the
|
||||
// range of valid values for the inventory.
|
||||
var ErrSlotOutOfRange = errors.New("slot is out of range: must be in range 0 <= slot < inventory.Size()")
|
||||
|
||||
// New creates a new inventory with the size passed. The inventory size cannot be changed after it has been
|
||||
// constructed.
|
||||
// A function may be passed which is called every time a slot is changed. The function may also be nil, if
|
||||
// nothing needs to be done.
|
||||
func New(size int, f SlotFunc) *Inventory {
|
||||
if size <= 0 {
|
||||
panic("inventory size must be at least 1")
|
||||
}
|
||||
if f == nil {
|
||||
f = func(slot int, before, after item.Stack) {}
|
||||
}
|
||||
return &Inventory{h: NopHandler{}, slots: make([]item.Stack, size), f: f, validator: func(s item.Stack, slot int) bool { return true }}
|
||||
}
|
||||
|
||||
// Clone copies an Inventory and returns it, calling the SlotFunc passed for any
|
||||
// slots changed in the new inventory.
|
||||
func (inv *Inventory) Clone(f SlotFunc) *Inventory {
|
||||
if f == nil {
|
||||
f = func(slot int, before, after item.Stack) {}
|
||||
}
|
||||
return &Inventory{h: NopHandler{}, slots: inv.Slots(), f: f, validator: func(s item.Stack, slot int) bool { return true }}
|
||||
}
|
||||
|
||||
// SlotFunc changes the function called when a slot in the inventory is changed.
|
||||
func (inv *Inventory) SlotFunc(f SlotFunc) {
|
||||
inv.mu.Lock()
|
||||
defer inv.mu.Unlock()
|
||||
inv.f = f
|
||||
}
|
||||
|
||||
// SlotValidatorFunc changes the function that limits item placement in the inventory slot.
|
||||
func (inv *Inventory) SlotValidatorFunc(f SlotValidatorFunc) {
|
||||
inv.mu.Lock()
|
||||
defer inv.mu.Unlock()
|
||||
inv.validator = f
|
||||
}
|
||||
|
||||
// Item attempts to obtain an item from a specific slot in the inventory. If an item was present in that slot,
|
||||
// the item is returned and the error is nil. If no item was present in the slot, a Stack with air as its item
|
||||
// and a count of 0 is returned. Stack.Empty() may be called to check if this is the case.
|
||||
// Item only returns an error if the slot passed is out of range. (0 <= slot < inventory.Size())
|
||||
func (inv *Inventory) Item(slot int) (item.Stack, error) {
|
||||
inv.mu.RLock()
|
||||
defer inv.mu.RUnlock()
|
||||
|
||||
inv.check()
|
||||
if !inv.validSlot(slot) {
|
||||
return item.Stack{}, ErrSlotOutOfRange
|
||||
}
|
||||
return inv.slots[slot], nil
|
||||
}
|
||||
|
||||
// SetItem sets a stack of items to a specific slot in the inventory. If an item is already present in the
|
||||
// slot, that item will be overwritten.
|
||||
// SetItem will return an error if the slot passed is out of range. (0 <= slot < inventory.Size())
|
||||
func (inv *Inventory) SetItem(slot int, item item.Stack) error {
|
||||
inv.mu.Lock()
|
||||
|
||||
inv.check()
|
||||
if !inv.validSlot(slot) {
|
||||
inv.mu.Unlock()
|
||||
return ErrSlotOutOfRange
|
||||
}
|
||||
f := inv.setItem(slot, item)
|
||||
|
||||
inv.mu.Unlock()
|
||||
|
||||
f()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Slots returns the all slots in the inventory as a slice. The index in the slice is the slot of the inventory that a
|
||||
// specific item.Stack is in. Note that this item.Stack might be empty.
|
||||
func (inv *Inventory) Slots() []item.Stack {
|
||||
inv.mu.RLock()
|
||||
defer inv.mu.RUnlock()
|
||||
return slices.Clone(inv.slots)
|
||||
}
|
||||
|
||||
// Items returns a list of all contents of the inventory. This method excludes air items, so the method
|
||||
// only ever returns item stacks which actually represent an item.
|
||||
func (inv *Inventory) Items() []item.Stack {
|
||||
inv.mu.RLock()
|
||||
defer inv.mu.RUnlock()
|
||||
|
||||
items := make([]item.Stack, 0, len(inv.slots))
|
||||
for _, it := range inv.slots {
|
||||
if !it.Empty() {
|
||||
items = append(items, it)
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
// First returns the first slot with an item if found. Second return value describes whether the item was found.
|
||||
func (inv *Inventory) First(item item.Stack) (int, bool) {
|
||||
return inv.FirstFunc(item.Comparable)
|
||||
}
|
||||
|
||||
// FirstFunc finds the first slot with an item.Stack that results in the comparable function passed returning true. The
|
||||
// function returns false if no such item was found.
|
||||
func (inv *Inventory) FirstFunc(comparable func(stack item.Stack) bool) (int, bool) {
|
||||
for slot, it := range inv.Slots() {
|
||||
if !it.Empty() && comparable(it) {
|
||||
return slot, true
|
||||
}
|
||||
}
|
||||
return -1, false
|
||||
}
|
||||
|
||||
// FirstEmpty returns the first empty slot if found. Second return value describes whether an empty slot was found.
|
||||
func (inv *Inventory) FirstEmpty() (int, bool) {
|
||||
for slot, it := range inv.Slots() {
|
||||
if it.Empty() {
|
||||
return slot, true
|
||||
}
|
||||
}
|
||||
return -1, false
|
||||
}
|
||||
|
||||
// Swap swaps the items between two slots. Returns an error if either slot A or B are invalid.
|
||||
func (inv *Inventory) Swap(slotA, slotB int) error {
|
||||
inv.mu.Lock()
|
||||
|
||||
inv.check()
|
||||
if !inv.validSlot(slotA) || !inv.validSlot(slotB) {
|
||||
inv.mu.Unlock()
|
||||
return ErrSlotOutOfRange
|
||||
}
|
||||
a, b := inv.slots[slotA], inv.slots[slotB]
|
||||
fa, fb := inv.setItem(slotA, b), inv.setItem(slotB, a)
|
||||
|
||||
inv.mu.Unlock()
|
||||
|
||||
fa()
|
||||
fb()
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddItem attempts to add an item to the inventory. It does so in a couple of steps: It first iterates over
|
||||
// the inventory to make sure no existing stacks of the same type exist. If these stacks do exist, the item
|
||||
// added is first added on top of those stacks to make sure they are fully filled.
|
||||
// If no existing stacks with leftover space are left, empty slots will be filled up with the remainder of the
|
||||
// item added.
|
||||
// If the item could not be fully added to the inventory, an error is returned along with the count that was
|
||||
// added to the inventory.
|
||||
func (inv *Inventory) AddItem(it item.Stack) (n int, err error) {
|
||||
if it.Empty() {
|
||||
return 0, nil
|
||||
}
|
||||
first := it.Count()
|
||||
emptySlots := make([]int, 0, 16)
|
||||
|
||||
inv.mu.Lock()
|
||||
|
||||
inv.check()
|
||||
for slot, invIt := range inv.slots {
|
||||
if invIt.Empty() {
|
||||
// This slot was empty, and we should first try to add the item stack to existing stacks.
|
||||
emptySlots = append(emptySlots, slot)
|
||||
continue
|
||||
}
|
||||
a, b := invIt.AddStack(it)
|
||||
if it.Count() == b.Count() {
|
||||
// Count stayed the same, meaning this slot either wasn't equal to this stack or was max size.
|
||||
continue
|
||||
}
|
||||
f := inv.setItem(slot, a)
|
||||
//noinspection GoDeferInLoop
|
||||
defer f()
|
||||
|
||||
if it = b; it.Empty() {
|
||||
inv.mu.Unlock()
|
||||
// We were able to add the entire stack to existing stacks in the inventory.
|
||||
return first, nil
|
||||
}
|
||||
}
|
||||
for _, slot := range emptySlots {
|
||||
a, b := it.Grow(-math.MaxInt32).AddStack(it)
|
||||
|
||||
f := inv.setItem(slot, a)
|
||||
//noinspection GoDeferInLoop
|
||||
defer f()
|
||||
|
||||
if it = b; it.Empty() {
|
||||
inv.mu.Unlock()
|
||||
// We were able to add the entire stack to empty slots.
|
||||
return first, nil
|
||||
}
|
||||
}
|
||||
inv.mu.Unlock()
|
||||
// We were unable to clear out the entire stack to be added to the inventory: There wasn't enough space.
|
||||
return first - it.Count(), fmt.Errorf("could not add full item stack to inventory")
|
||||
}
|
||||
|
||||
// RemoveItem attempts to remove an item from the inventory. It will visit all slots in the inventory and
|
||||
// empties them until it.Count() items have been removed from the inventory.
|
||||
// If less than it.Count() items were removed from the inventory, an error is returned.
|
||||
func (inv *Inventory) RemoveItem(it item.Stack) error {
|
||||
return inv.RemoveItemFunc(it.Count(), it.Comparable)
|
||||
}
|
||||
|
||||
// RemoveItemFunc removes up to n items from the Inventory. It will visit all slots in the inventory and empties them
|
||||
// until n items have been removed from the inventory, assuming the comparable function returns true for the slots
|
||||
// visited. No items will be deducted from slots if the comparable function returns false.
|
||||
// If less than n items were removed, an error is returned.
|
||||
func (inv *Inventory) RemoveItemFunc(n int, comparable func(stack item.Stack) bool) error {
|
||||
inv.mu.Lock()
|
||||
inv.check()
|
||||
for slot, slotIt := range inv.slots {
|
||||
if slotIt.Empty() || !comparable(slotIt) {
|
||||
continue
|
||||
}
|
||||
c := slotIt.Count() - n
|
||||
|
||||
var f func()
|
||||
if c <= 0 {
|
||||
f = inv.setItem(slot, item.Stack{})
|
||||
} else {
|
||||
f = inv.setItem(slot, slotIt.Grow(-n))
|
||||
}
|
||||
|
||||
//noinspection GoDeferInLoop
|
||||
defer f()
|
||||
|
||||
if n -= slotIt.Count(); n <= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
inv.mu.Unlock()
|
||||
|
||||
if n > 0 {
|
||||
return fmt.Errorf("could not remove all items from the inventory")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContainsItem checks if the Inventory contains an item.Stack. It will visit all slots in the Inventory until it finds
|
||||
// at enough items. If enough were found, true is returned.
|
||||
func (inv *Inventory) ContainsItem(it item.Stack) bool {
|
||||
return inv.ContainsItemFunc(it.Count(), it.Comparable)
|
||||
}
|
||||
|
||||
// ContainsItemFunc checks if the Inventory contains at least n items. It will visit all slots in the Inventory until it
|
||||
// finds n items on which the comparable function returns true. ContainsItemFunc returns true if this is the case.
|
||||
func (inv *Inventory) ContainsItemFunc(n int, comparable func(stack item.Stack) bool) bool {
|
||||
inv.mu.Lock()
|
||||
defer inv.mu.Unlock()
|
||||
|
||||
inv.check()
|
||||
for _, slotIt := range inv.slots {
|
||||
if !slotIt.Empty() && comparable(slotIt) {
|
||||
if n -= slotIt.Count(); n <= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return n <= 0
|
||||
}
|
||||
|
||||
// Merge merges two inventories into one. The function passed is called for every slot change in the new inventory.
|
||||
func (inv *Inventory) Merge(inv2 *Inventory, f func(int, item.Stack, item.Stack)) *Inventory {
|
||||
inv.mu.RLock()
|
||||
defer inv.mu.RUnlock()
|
||||
inv2.mu.RLock()
|
||||
defer inv2.mu.RUnlock()
|
||||
|
||||
n := New(len(inv.slots)+len(inv2.slots), f)
|
||||
n.slots = make([]item.Stack, 0, len(inv.slots)+len(inv2.slots))
|
||||
n.slots = append(n.slots, inv.slots...)
|
||||
n.slots = append(n.slots, inv2.slots...)
|
||||
return n
|
||||
}
|
||||
|
||||
// Empty checks if the inventory is fully empty: It iterates over the inventory and makes sure every stack in
|
||||
// it is empty.
|
||||
func (inv *Inventory) Empty() bool {
|
||||
inv.mu.RLock()
|
||||
defer inv.mu.RUnlock()
|
||||
|
||||
inv.check()
|
||||
for _, it := range inv.slots {
|
||||
if !it.Empty() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Clear clears the entire inventory. All non-zero items are returned.
|
||||
func (inv *Inventory) Clear() []item.Stack {
|
||||
inv.mu.Lock()
|
||||
|
||||
inv.check()
|
||||
|
||||
items := make([]item.Stack, 0, inv.size())
|
||||
for slot, i := range inv.slots {
|
||||
if !i.Empty() {
|
||||
items = append(items, i)
|
||||
f := inv.setItem(slot, item.Stack{})
|
||||
//noinspection GoDeferInLoop
|
||||
defer f()
|
||||
}
|
||||
}
|
||||
inv.mu.Unlock()
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
// Handle assigns a Handler to an Inventory so that its methods are called for the respective events. Nil may be passed
|
||||
// to set the default NopHandler.
|
||||
func (inv *Inventory) Handle(h Handler) {
|
||||
inv.mu.Lock()
|
||||
defer inv.mu.Unlock()
|
||||
|
||||
inv.check()
|
||||
if h == nil {
|
||||
h = NopHandler{}
|
||||
}
|
||||
inv.h = h
|
||||
}
|
||||
|
||||
// Handler returns the Handler currently assigned to the Inventory. This is the NopHandler by default.
|
||||
func (inv *Inventory) Handler() Handler {
|
||||
inv.mu.RLock()
|
||||
defer inv.mu.RUnlock()
|
||||
|
||||
inv.check()
|
||||
return inv.h
|
||||
}
|
||||
|
||||
// setItem sets an item to a specific slot and overwrites the existing item. It calls the function which is
|
||||
// called for every item change and does so without locking the inventory.
|
||||
func (inv *Inventory) setItem(slot int, it item.Stack) func() {
|
||||
if !inv.validator(it, slot) {
|
||||
return func() {}
|
||||
}
|
||||
if it.Count() > it.MaxCount() {
|
||||
it = it.Grow(it.MaxCount() - it.Count())
|
||||
}
|
||||
before := inv.slots[slot]
|
||||
inv.slots[slot] = it
|
||||
return func() {
|
||||
inv.f(slot, before, it)
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns the size of the inventory. It is always the same value as that passed in the call to New() and
|
||||
// is always at least 1.
|
||||
func (inv *Inventory) Size() int {
|
||||
inv.mu.RLock()
|
||||
defer inv.mu.RUnlock()
|
||||
return inv.size()
|
||||
}
|
||||
|
||||
// size returns the size of the inventory without locking.
|
||||
func (inv *Inventory) size() int {
|
||||
return len(inv.slots)
|
||||
}
|
||||
|
||||
// Close closes the inventory, freeing the function called for every slot change. It also clears any items
|
||||
// that may currently be in the inventory.
|
||||
// The returned error is always nil.
|
||||
func (inv *Inventory) Close() error {
|
||||
inv.mu.Lock()
|
||||
defer inv.mu.Unlock()
|
||||
|
||||
inv.check()
|
||||
inv.f = func(int, item.Stack, item.Stack) {}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements the fmt.Stringer interface.
|
||||
func (inv *Inventory) String() string {
|
||||
inv.mu.RLock()
|
||||
defer inv.mu.RUnlock()
|
||||
|
||||
s := make([]string, 0, inv.size())
|
||||
for _, it := range inv.slots {
|
||||
s = append(s, it.String())
|
||||
}
|
||||
return "(" + strings.Join(s, ", ") + ")"
|
||||
}
|
||||
|
||||
// validSlot checks if the slot passed is valid for the inventory. It returns false if the slot is either
|
||||
// smaller than 0 or bigger/equal to the size of the inventory's size.
|
||||
func (inv *Inventory) validSlot(slot int) bool {
|
||||
return slot >= 0 && slot < inv.size()
|
||||
}
|
||||
|
||||
// check panics if the inventory is valid, and panics if it is not. This typically happens if the inventory
|
||||
// was not created using New().
|
||||
func (inv *Inventory) check() {
|
||||
if inv.size() == 0 {
|
||||
panic("uninitialised inventory: inventory must be constructed using inventory.New()")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user