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

     1  package block
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/df-mc/dragonfly/server/block/cube"
     6  	"github.com/df-mc/dragonfly/server/internal/nbtconv"
     7  	"github.com/df-mc/dragonfly/server/item"
     8  	"github.com/df-mc/dragonfly/server/item/inventory"
     9  	"github.com/df-mc/dragonfly/server/world"
    10  	"github.com/df-mc/dragonfly/server/world/sound"
    11  	"github.com/go-gl/mathgl/mgl64"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  )
    16  
    17  // Chest is a container block which may be used to store items. Chests may also be paired to create a bigger
    18  // single container.
    19  // The empty value of Chest is not valid. It must be created using block.NewChest().
    20  type Chest struct {
    21  	chest
    22  	transparent
    23  	bass
    24  	sourceWaterDisplacer
    25  
    26  	// Facing is the direction that the chest is facing.
    27  	Facing cube.Direction
    28  	// CustomName is the custom name of the chest. This name is displayed when the chest is opened, and may
    29  	// include colour codes.
    30  	CustomName string
    31  
    32  	inventory *inventory.Inventory
    33  	viewerMu  *sync.RWMutex
    34  	viewers   map[ContainerViewer]struct{}
    35  }
    36  
    37  // NewChest creates a new initialised chest. The inventory is properly initialised.
    38  func NewChest() Chest {
    39  	m := new(sync.RWMutex)
    40  	v := make(map[ContainerViewer]struct{}, 1)
    41  	return Chest{
    42  		inventory: inventory.New(27, func(slot int, _, item item.Stack) {
    43  			m.RLock()
    44  			defer m.RUnlock()
    45  			for viewer := range v {
    46  				viewer.ViewSlotChange(slot, item)
    47  			}
    48  		}),
    49  		viewerMu: m,
    50  		viewers:  v,
    51  	}
    52  }
    53  
    54  // Inventory returns the inventory of the chest. The size of the inventory will be 27 or 54, depending on
    55  // whether the chest is single or double.
    56  func (c Chest) Inventory() *inventory.Inventory {
    57  	return c.inventory
    58  }
    59  
    60  // WithName returns the chest after applying a specific name to the block.
    61  func (c Chest) WithName(a ...any) world.Item {
    62  	c.CustomName = strings.TrimSuffix(fmt.Sprintln(a...), "\n")
    63  	return c
    64  }
    65  
    66  // SideClosed ...
    67  func (Chest) SideClosed(cube.Pos, cube.Pos, *world.World) bool {
    68  	return false
    69  }
    70  
    71  // open opens the chest, displaying the animation and playing a sound.
    72  func (c Chest) open(w *world.World, pos cube.Pos) {
    73  	for _, v := range w.Viewers(pos.Vec3()) {
    74  		v.ViewBlockAction(pos, OpenAction{})
    75  	}
    76  	w.PlaySound(pos.Vec3Centre(), sound.ChestOpen{})
    77  }
    78  
    79  // close closes the chest, displaying the animation and playing a sound.
    80  func (c Chest) close(w *world.World, pos cube.Pos) {
    81  	for _, v := range w.Viewers(pos.Vec3()) {
    82  		v.ViewBlockAction(pos, CloseAction{})
    83  	}
    84  	w.PlaySound(pos.Vec3Centre(), sound.ChestClose{})
    85  }
    86  
    87  // AddViewer adds a viewer to the chest, so that it is updated whenever the inventory of the chest is changed.
    88  func (c Chest) AddViewer(v ContainerViewer, w *world.World, pos cube.Pos) {
    89  	c.viewerMu.Lock()
    90  	defer c.viewerMu.Unlock()
    91  	if len(c.viewers) == 0 {
    92  		c.open(w, pos)
    93  	}
    94  	c.viewers[v] = struct{}{}
    95  }
    96  
    97  // RemoveViewer removes a viewer from the chest, so that slot updates in the inventory are no longer sent to
    98  // it.
    99  func (c Chest) RemoveViewer(v ContainerViewer, w *world.World, pos cube.Pos) {
   100  	c.viewerMu.Lock()
   101  	defer c.viewerMu.Unlock()
   102  	if len(c.viewers) == 0 {
   103  		return
   104  	}
   105  	delete(c.viewers, v)
   106  	if len(c.viewers) == 0 {
   107  		c.close(w, pos)
   108  	}
   109  }
   110  
   111  // Activate ...
   112  func (c Chest) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, _ *item.UseContext) bool {
   113  	if opener, ok := u.(ContainerOpener); ok {
   114  		if d, ok := w.Block(pos.Side(cube.FaceUp)).(LightDiffuser); ok && d.LightDiffusionLevel() <= 2 {
   115  			opener.OpenBlockContainer(pos)
   116  		}
   117  		return true
   118  	}
   119  	return false
   120  }
   121  
   122  // UseOnBlock ...
   123  func (c Chest) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) {
   124  	pos, _, used = firstReplaceable(w, pos, face, c)
   125  	if !used {
   126  		return
   127  	}
   128  	//noinspection GoAssignmentToReceiver
   129  	c = NewChest()
   130  	c.Facing = user.Rotation().Direction().Opposite()
   131  
   132  	place(w, pos, c, user, ctx)
   133  	return placed(ctx)
   134  }
   135  
   136  // BreakInfo ...
   137  func (c Chest) BreakInfo() BreakInfo {
   138  	return newBreakInfo(2.5, alwaysHarvestable, axeEffective, oneOf(c))
   139  }
   140  
   141  // FuelInfo ...
   142  func (Chest) FuelInfo() item.FuelInfo {
   143  	return newFuelInfo(time.Second * 15)
   144  }
   145  
   146  // FlammabilityInfo ...
   147  func (c Chest) FlammabilityInfo() FlammabilityInfo {
   148  	return newFlammabilityInfo(0, 0, true)
   149  }
   150  
   151  // DecodeNBT ...
   152  func (c Chest) DecodeNBT(data map[string]any) any {
   153  	facing := c.Facing
   154  	//noinspection GoAssignmentToReceiver
   155  	c = NewChest()
   156  	c.Facing = facing
   157  	c.CustomName = nbtconv.String(data, "CustomName")
   158  	nbtconv.InvFromNBT(c.inventory, nbtconv.Slice(data, "Items"))
   159  	return c
   160  }
   161  
   162  // EncodeNBT ...
   163  func (c Chest) EncodeNBT() map[string]any {
   164  	if c.inventory == nil {
   165  		facing, customName := c.Facing, c.CustomName
   166  		//noinspection GoAssignmentToReceiver
   167  		c = NewChest()
   168  		c.Facing, c.CustomName = facing, customName
   169  	}
   170  	m := map[string]any{
   171  		"Items": nbtconv.InvToNBT(c.inventory),
   172  		"id":    "Chest",
   173  	}
   174  	if c.CustomName != "" {
   175  		m["CustomName"] = c.CustomName
   176  	}
   177  	return m
   178  }
   179  
   180  // EncodeItem ...
   181  func (Chest) EncodeItem() (name string, meta int16) {
   182  	return "minecraft:chest", 0
   183  }
   184  
   185  // EncodeBlock ...
   186  func (c Chest) EncodeBlock() (name string, properties map[string]any) {
   187  	return "minecraft:chest", map[string]any{"minecraft:cardinal_direction": c.Facing.String()}
   188  }
   189  
   190  // allChests ...
   191  func allChests() (chests []world.Block) {
   192  	for _, direction := range cube.Directions() {
   193  		chests = append(chests, Chest{Facing: direction})
   194  	}
   195  	return
   196  }