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 }