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  }