github.com/df-mc/dragonfly@v0.9.13/server/item/bucket.go (about)

     1  package item
     2  
     3  import (
     4  	"github.com/df-mc/dragonfly/server/block/cube"
     5  	"github.com/df-mc/dragonfly/server/world"
     6  	"github.com/df-mc/dragonfly/server/world/sound"
     7  	"github.com/go-gl/mathgl/mgl64"
     8  	"time"
     9  )
    10  
    11  // BucketContent is the content of a bucket.
    12  type BucketContent struct {
    13  	liquid world.Liquid
    14  	milk   bool
    15  }
    16  
    17  // LiquidBucketContent returns a new BucketContent with the liquid passed in.
    18  func LiquidBucketContent(l world.Liquid) BucketContent {
    19  	return BucketContent{liquid: l}
    20  }
    21  
    22  // MilkBucketContent returns a new BucketContent with the milk flag set.
    23  func MilkBucketContent() BucketContent {
    24  	return BucketContent{milk: true}
    25  }
    26  
    27  // Liquid returns the world.Liquid that a Bucket with this BucketContent places.
    28  // If this BucketContent does not place a liquid block, false is returned.
    29  func (b BucketContent) Liquid() (world.Liquid, bool) {
    30  	return b.liquid, b.liquid != nil
    31  }
    32  
    33  // String converts the BucketContent to a string.
    34  func (b BucketContent) String() string {
    35  	if b.milk {
    36  		return "milk"
    37  	} else if b.liquid != nil {
    38  		return b.liquid.LiquidType()
    39  	}
    40  	return ""
    41  }
    42  
    43  // LiquidType returns the type of liquid the bucket contains.
    44  func (b BucketContent) LiquidType() string {
    45  	if b.liquid != nil {
    46  		return b.liquid.LiquidType()
    47  	}
    48  	return "milk"
    49  }
    50  
    51  // Bucket is a tool used to carry water, lava and fish.
    52  type Bucket struct {
    53  	// Content is the content that the bucket has. By default, this value resolves to an empty bucket.
    54  	Content BucketContent
    55  }
    56  
    57  // MaxCount returns 16.
    58  func (b Bucket) MaxCount() int {
    59  	if b.Empty() {
    60  		return 16
    61  	}
    62  	return 1
    63  }
    64  
    65  // AlwaysConsumable ...
    66  func (b Bucket) AlwaysConsumable() bool {
    67  	return b.Content.milk
    68  }
    69  
    70  // CanConsume ...
    71  func (b Bucket) CanConsume() bool {
    72  	return b.Content.milk
    73  }
    74  
    75  // ConsumeDuration ...
    76  func (b Bucket) ConsumeDuration() time.Duration {
    77  	return DefaultConsumeDuration
    78  }
    79  
    80  // Consume ...
    81  func (b Bucket) Consume(_ *world.World, c Consumer) Stack {
    82  	for _, effect := range c.Effects() {
    83  		c.RemoveEffect(effect.Type())
    84  	}
    85  	return NewStack(Bucket{}, 1)
    86  }
    87  
    88  // Empty returns true if the bucket is empty.
    89  func (b Bucket) Empty() bool {
    90  	return b.Content.liquid == nil && !b.Content.milk
    91  }
    92  
    93  // FuelInfo ...
    94  func (b Bucket) FuelInfo() FuelInfo {
    95  	if liq := b.Content.liquid; liq != nil && liq.LiquidType() == "lava" {
    96  		return newFuelInfo(time.Second * 1000).WithResidue(NewStack(Bucket{}, 1))
    97  	}
    98  	return FuelInfo{}
    99  }
   100  
   101  // UseOnBlock handles the bucket filling and emptying logic.
   102  func (b Bucket) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, _ User, ctx *UseContext) bool {
   103  	if b.Content.milk {
   104  		return false
   105  	}
   106  	if b.Empty() {
   107  		return b.fillFrom(pos, w, ctx)
   108  	}
   109  	liq := b.Content.liquid.WithDepth(8, false)
   110  	if bl := w.Block(pos); canDisplace(bl, liq) || replaceableWith(bl, liq) {
   111  		w.SetLiquid(pos, liq)
   112  	} else if bl := w.Block(pos.Side(face)); canDisplace(bl, liq) || replaceableWith(bl, liq) {
   113  		w.SetLiquid(pos.Side(face), liq)
   114  	} else {
   115  		return false
   116  	}
   117  
   118  	w.PlaySound(pos.Vec3Centre(), sound.BucketEmpty{Liquid: b.Content.liquid})
   119  	ctx.NewItem = NewStack(Bucket{}, 1)
   120  	ctx.NewItemSurvivalOnly = true
   121  	ctx.SubtractFromCount(1)
   122  	return true
   123  }
   124  
   125  // fillFrom fills a bucket from the liquid at the position passed in the world. If there is no liquid or if
   126  // the liquid is no source, fillFrom returns false.
   127  func (b Bucket) fillFrom(pos cube.Pos, w *world.World, ctx *UseContext) bool {
   128  	liquid, ok := w.Liquid(pos)
   129  	if !ok {
   130  		return false
   131  	}
   132  	if liquid.LiquidDepth() != 8 || liquid.LiquidFalling() {
   133  		// Only allow picking up liquid source blocks.
   134  		return false
   135  	}
   136  	w.SetLiquid(pos, nil)
   137  	w.PlaySound(pos.Vec3Centre(), sound.BucketFill{Liquid: liquid})
   138  
   139  	ctx.NewItem = NewStack(Bucket{Content: LiquidBucketContent(liquid)}, 1)
   140  	ctx.NewItemSurvivalOnly = true
   141  	ctx.SubtractFromCount(1)
   142  	return true
   143  }
   144  
   145  // EncodeItem ...
   146  func (b Bucket) EncodeItem() (name string, meta int16) {
   147  	if !b.Empty() {
   148  		return "minecraft:" + b.Content.String() + "_bucket", 0
   149  	}
   150  	return "minecraft:bucket", 0
   151  }
   152  
   153  type replaceable interface {
   154  	ReplaceableBy(b world.Block) bool
   155  }
   156  
   157  func replaceableWith(b world.Block, with world.Block) bool {
   158  	if r, ok := b.(replaceable); ok {
   159  		return r.ReplaceableBy(with)
   160  	}
   161  	return false
   162  }
   163  
   164  func canDisplace(b world.Block, liq world.Liquid) bool {
   165  	if d, ok := b.(world.LiquidDisplacer); ok {
   166  		return d.CanDisplace(liq)
   167  	}
   168  	return false
   169  }