Files

273 lines
11 KiB
Go
Raw Permalink Normal View History

2026-07-09 08:33:57 +08:00
package item
import (
"encoding/binary"
"image/color"
"time"
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/entity/effect"
"github.com/df-mc/dragonfly/server/world"
"github.com/go-gl/mathgl/mgl64"
)
// MaxCounter represents an item that has a specific max count. By default, each item will be expected to have
// a maximum count of 64. MaxCounter may be implemented to change this behaviour.
type MaxCounter interface {
// MaxCount returns the maximum number of items that a stack may be composed of. The number returned must
// be positive.
MaxCount() int
}
// UsableOnBlock represents an item that may be used on a block. If an item implements this interface, the
// UseOnBlock method is called whenever the item is used on a block.
type UsableOnBlock interface {
// UseOnBlock is called when an item is used on a block. The world passed is the world that the item was
// used in. The user passed is the entity that used the item. Usually this entity is a player.
// The position of the block that was clicked, along with the clicked face and the position clicked
// relative to the corner of the block are passed.
// UseOnBlock returns a bool indicating if the item was used successfully.
UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user User, ctx *UseContext) bool
}
// UsableOnEntity represents an item that may be used on an entity. If an item implements this interface, the
// UseOnEntity method is called whenever the item is used on an entity.
type UsableOnEntity interface {
// UseOnEntity is called when an item is used on an entity. The world passed is the world that the item is
// used in, and the entity clicked and the user of the item are also passed.
// UseOnEntity returns a bool indicating if the item was used successfully.
UseOnEntity(e world.Entity, tx *world.Tx, user User, ctx *UseContext) bool
}
// Usable represents an item that may be used 'in the air'. If an item implements this interface, the Use
// method is called whenever the item is used while pointing at the air. (For example, when throwing an egg.)
type Usable interface {
// Use is called when the item is used in the air. The user that used the item and the world that the item
// was used in are passed to the method.
// Use returns a bool indicating if the item was used successfully.
Use(tx *world.Tx, user User, ctx *UseContext) bool
}
// Throwable represents a custom item that can be thrown such as a projectile. This will only have an effect on
// non-vanilla items.
type Throwable interface {
// SwingAnimation returns true if the client should cause the player's arm to swing when the item is thrown.
SwingAnimation() bool
}
// OffHand represents an item that can be held in the off hand.
type OffHand interface {
// OffHand returns true if the item can be held in the off hand.
OffHand() bool
}
// Consumable represents an item that may be consumed by a player. If an item implements this interface, a player
// may use and hold the item to consume it.
type Consumable interface {
// AlwaysConsumable specifies if the item is always consumable. Normal food can generally only be consumed
// when the food bar is not full or when in creative mode. Returning true here means the item can always
// be consumed, like golden apples or potions.
AlwaysConsumable() bool
// ConsumeDuration is the duration consuming the item takes. If the player is using the item for at least
// this duration, the item will be consumed and have its Consume method called.
ConsumeDuration() time.Duration
// Consume consumes one item of the Stack that the Consumable is in. The Stack returned is added back to
// the inventory after consuming the item. For potions, for example, an empty bottle is returned.
Consume(tx *world.Tx, c Consumer) Stack
}
// Consumer represents a User that is able to consume Consumable items.
type Consumer interface {
User
// Saturate saturates the Consumer's food bar by the amount of food points passed and the saturation by
// up to as many saturation points as passed. The final saturation will never exceed the final food level.
Saturate(food int, saturation float64)
// AddEffect adds an effect.Effect to the Consumer. If the effect is instant, it is applied to the Consumer
// immediately. If not, the effect is applied to the consumer every time the Tick method is called.
// AddEffect will overwrite any effects present if the level of the effect is higher than the existing one, or
// if the effects' levels are equal and the new effect has a longer duration.
AddEffect(e effect.Effect)
// RemoveEffect removes any effect that might currently be active on the Consumer.
RemoveEffect(e effect.Type)
// Effects returns any effect currently applied to the Consumer. The returned effects are guaranteed not to have
// expired when returned.
Effects() []effect.Effect
// Absorption returns the absorption health of the Consumer.
Absorption() float64
// SetAbsorption sets the absorption health of the Consumer. Absorption health is shown as golden hearts and does
// not regenerate naturally.
SetAbsorption(health float64)
}
// DefaultConsumeDuration is the default duration that consuming an item takes. Dried kelp takes half this
// time to be consumed.
const DefaultConsumeDuration = (time.Second * 161) / 100
// Drinkable represents a custom item that can be drunk. It is used to make the client show the correct drinking
// animation when a player is using an item. This will only have an effect on non-vanilla items.
type Drinkable interface {
// Drinkable returns if the item can be drunk or not.
Drinkable() bool
}
// Glinted represents a custom item that can have a permanent enchantment glint, this glint is purely cosmetic and
// will show regardless of whether it is actually enchanted. An example of this is the enchanted golden apple.
type Glinted interface {
// Glinted returns whether the item has an enchantment glint.
Glinted() bool
}
// HandEquipped represents an item that can be 'hand equipped'. This means the item will show up in third person like
// a tool, sword or stick would, giving them a different orientation in the hand and making them slightly bigger.
type HandEquipped interface {
// HandEquipped returns whether the item is hand equipped.
HandEquipped() bool
}
// Weapon is an item that may be used as a weapon. It has an attack damage which may be different to the 2
// damage that attacking with an empty hand deals.
type Weapon interface {
// AttackDamage returns the custom attack damage to the weapon. The damage returned must not be negative.
AttackDamage() float64
}
// Cooldown represents an item that has a cooldown.
type Cooldown interface {
// Cooldown is the duration of the cooldown.
Cooldown() time.Duration
}
// nameable represents a block that may be named. These are often containers such as chests, which have a
// name displayed in their interface.
type nameable interface {
// WithName returns the block itself, except with a custom name applied to it.
WithName(a ...any) world.Item
}
// Releaser represents an entity that can release items, such as bows.
type Releaser interface {
User
// GameMode returns the gamemode of the releaser.
GameMode() world.GameMode
// PlaySound plays a world.Sound that only this Releaser can hear.
PlaySound(sound world.Sound)
}
// Releasable represents an item that can be released.
type Releasable interface {
// Release is called when an item is released.
Release(releaser Releaser, tx *world.Tx, ctx *UseContext, duration time.Duration)
// Requirements returns the required items to release this item.
Requirements() []Stack
}
// Chargeable represents an item that can be charged.
type Chargeable interface {
// Charge is called when an item is being used.
Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, duration time.Duration) bool
// ContinueCharge continues the charge.
ContinueCharge(releaser Releaser, tx *world.Tx, ctx *UseContext, duration time.Duration)
// ReleaseCharge is called when an item is being released.
ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext) bool
// CanCharge returns whether the item can currently be charged.
CanCharge(releaser Releaser, tx *world.Tx, ctx *UseContext) bool
}
// User represents an entity that is able to use an item in the world, typically entities such as players,
// which interact with the world using an item.
type User interface {
Carrier
SetHeldItems(mainHand, offHand Stack)
UsingItem() bool
ReleaseItem()
UseItem()
}
// Carrier represents an entity that is able to carry an item.
type Carrier interface {
world.Entity
// HeldItems returns the items currently held by the entity. Viewers of the entity will be able to see
// these items.
HeldItems() (mainHand, offHand Stack)
}
// BeaconPayment represents an item that may be used as payment for a beacon to select effects to be broadcast
// to surrounding players.
type BeaconPayment interface {
PayableForBeacon() bool
}
// Compostable represents an item that may be used to fill up a composter.
type Compostable interface {
// CompostChance returns the chance the item will produce a layer of compost in the range of 0-1.
CompostChance() float64
}
// nopReleasable represents a releasable item that does nothing.
type nopReleasable struct{}
func (nopReleasable) Release(Releaser, *world.Tx, *UseContext, time.Duration) {}
func (nopReleasable) Requirements() []Stack {
return []Stack{}
}
// defaultFood represents a consumable item with a default consumption duration.
type defaultFood struct{}
// AlwaysConsumable ...
func (defaultFood) AlwaysConsumable() bool {
return false
}
// ConsumeDuration ...
func (d defaultFood) ConsumeDuration() time.Duration {
return DefaultConsumeDuration
}
// eyePosition returns the position of the eyes of the entity if the entity implements entity.Eyed, or the
// actual position if it doesn't.
func eyePosition(e world.Entity) mgl64.Vec3 {
pos := e.Position()
if eyed, ok := e.(interface{ EyeHeight() float64 }); ok {
pos = pos.Add(mgl64.Vec3{0, eyed.EyeHeight()})
}
return pos
}
// torsoPosition returns the position of the torso of the entity if the entity implements entity.Torsoed, or the
// actual position if it doesn't.
func torsoPosition(e world.Entity) mgl64.Vec3 {
pos := e.Position()
if torso, ok := e.(interface{ TorsoHeight() float64 }); ok {
pos = pos.Add(mgl64.Vec3{0, torso.TorsoHeight()})
}
return pos
}
// Int32FromRGBA converts a color.RGBA into an int32. These int32s are present in things such as signs and dyed leather armour.
func int32FromRGBA(x color.RGBA) int32 {
if x.R == 0 && x.G == 0 && x.B == 0 {
// Default to black colour. The default (0x000000) is a transparent colour. Text with this colour will not show
// up on the sign.
return int32(-0x1000000)
}
return int32(binary.BigEndian.Uint32([]byte{x.A, x.R, x.G, x.B}))
}
// rgbaFromInt32 converts an int32 into a color.RGBA. These int32s are present in things such as signs and dyed leather armour.
func rgbaFromInt32(x int32) color.RGBA {
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, uint32(x))
return color.RGBA{A: b[0], R: b[1], G: b[2], B: b[3]}
}
// boolByte returns 1 if the bool passed is true, or 0 if it is false.
func boolByte(b bool) uint8 {
if b {
return 1
}
return 0
}