github.com/df-mc/dragonfly@v0.9.13/server/entity/item_behaviour.go (about) 1 package entity 2 3 import ( 4 "github.com/df-mc/dragonfly/server/internal/nbtconv" 5 "github.com/df-mc/dragonfly/server/item" 6 "github.com/df-mc/dragonfly/server/world" 7 "github.com/go-gl/mathgl/mgl64" 8 "math" 9 "time" 10 ) 11 12 // ItemBehaviourConfig holds optional parameters for an ItemBehaviour. 13 type ItemBehaviourConfig struct { 14 // Gravity is the amount of Y velocity subtracted every tick. 15 Gravity float64 16 // Drag is used to reduce all axes of the velocity every tick. Velocity is 17 // multiplied with (1-Drag) every tick. 18 Drag float64 19 // ExistenceDuration specifies how long the item stack should last. The 20 // default is time.Minute * 5. 21 ExistenceDuration time.Duration 22 // PickupDelay specifies how much time must expire before the item can be 23 // picked up by collectors. The default is time.Second / 2. 24 PickupDelay time.Duration 25 } 26 27 // New creates an ItemBehaviour using i and the optional parameters in conf. 28 func (conf ItemBehaviourConfig) New(i item.Stack) *ItemBehaviour { 29 if i.Count() > i.MaxCount() { 30 i = i.Grow(i.MaxCount() - i.Count()) 31 } 32 i = nbtconv.Item(nbtconv.WriteItem(i, true), nil) 33 34 if conf.PickupDelay == 0 { 35 conf.PickupDelay = time.Second / 2 36 } 37 if conf.ExistenceDuration == 0 { 38 conf.ExistenceDuration = time.Minute * 5 39 } 40 41 b := &ItemBehaviour{conf: conf, i: i, pickupDelay: conf.PickupDelay} 42 b.passive = PassiveBehaviourConfig{ 43 Gravity: conf.Gravity, 44 Drag: conf.Drag, 45 ExistenceDuration: conf.ExistenceDuration, 46 Tick: b.tick, 47 }.New() 48 return b 49 } 50 51 // ItemBehaviour implements the behaviour of item entities. 52 type ItemBehaviour struct { 53 conf ItemBehaviourConfig 54 passive *PassiveBehaviour 55 i item.Stack 56 57 pickupDelay time.Duration 58 } 59 60 // Item returns the item.Stack held by the entity. 61 func (i *ItemBehaviour) Item() item.Stack { 62 return i.i 63 } 64 65 // Tick moves the entity, checks if it should be picked up by a nearby collector 66 // or if it should merge with nearby item entities. 67 func (i *ItemBehaviour) Tick(e *Ent) *Movement { 68 return i.passive.Tick(e) 69 } 70 71 // tick checks if the item can be picked up or merged with nearby item stacks. 72 func (i *ItemBehaviour) tick(e *Ent) { 73 if i.pickupDelay == 0 { 74 i.checkNearby(e) 75 } else if i.pickupDelay < math.MaxInt16*(time.Second/20) { 76 i.pickupDelay -= time.Second / 20 77 } 78 } 79 80 // checkNearby checks the nearby entities for item collectors and other item 81 // stacks. If a collector is found in range, the item will be picked up. If 82 // another item stack with the same item type is found in range, the item 83 // stacks will merge. 84 func (i *ItemBehaviour) checkNearby(e *Ent) { 85 w, pos := e.World(), e.Position() 86 bbox := e.Type().BBox(e) 87 grown := bbox.GrowVec3(mgl64.Vec3{1, 0.5, 1}).Translate(pos) 88 nearby := w.EntitiesWithin(bbox.Translate(pos).Grow(2), func(entity world.Entity) bool { 89 return entity == e 90 }) 91 for _, other := range nearby { 92 if !other.Type().BBox(other).Translate(other.Position()).IntersectsWith(grown) { 93 continue 94 } 95 if collector, ok := other.(Collector); ok { 96 // A collector was within range to pick up the entity. 97 i.collect(e, collector) 98 return 99 } else if _, ok := other.Type().(ItemType); ok { 100 // Another item entity was in range to merge with. 101 if i.merge(e, other.(*Ent)) { 102 return 103 } 104 } 105 } 106 } 107 108 // merge merges the item entity with another item entity. 109 func (i *ItemBehaviour) merge(e *Ent, other *Ent) bool { 110 w, pos := e.World(), e.Position() 111 otherBehaviour := other.Behaviour().(*ItemBehaviour) 112 if otherBehaviour.i.Count() == otherBehaviour.i.MaxCount() || i.i.Count() == i.i.MaxCount() || !i.i.Comparable(otherBehaviour.i) { 113 // Either stack is already filled up to the maximum, meaning we can't 114 // change anything any way, other the stack types weren't comparable. 115 return false 116 } 117 a, b := otherBehaviour.i.AddStack(i.i) 118 119 newA := NewItem(a, other.Position()) 120 newA.SetVelocity(other.Velocity()) 121 w.AddEntity(newA) 122 123 if !b.Empty() { 124 newB := NewItem(b, pos) 125 newB.SetVelocity(e.Velocity()) 126 w.AddEntity(newB) 127 } 128 _ = e.Close() 129 _ = other.Close() 130 return true 131 } 132 133 // collect makes a collector collect the item (or at least part of it). 134 func (i *ItemBehaviour) collect(e *Ent, collector Collector) { 135 w, pos := e.World(), e.Position() 136 n := collector.Collect(i.i) 137 if n == 0 { 138 return 139 } 140 for _, viewer := range w.Viewers(pos) { 141 viewer.ViewEntityAction(e, PickedUpAction{Collector: collector}) 142 } 143 144 if n == i.i.Count() { 145 // The collector picked up the entire stack. 146 _ = e.Close() 147 return 148 } 149 // Create a new item entity and shrink it by the amount of items that the 150 // collector collected. 151 w.AddEntity(NewItem(i.i.Grow(-n), pos)) 152 _ = e.Close() 153 } 154 155 // Collector represents an entity in the world that is able to collect an item, typically an entity such as 156 // a player or a zombie. 157 type Collector interface { 158 world.Entity 159 // Collect collects the stack passed. It is called if the Collector is standing near an item entity that 160 // may be picked up. 161 // The count of items collected from the stack n is returned. 162 Collect(stack item.Stack) (n int) 163 }