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

     1  package world
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/brentp/intintmap"
     6  	"github.com/df-mc/dragonfly/server/block/cube"
     7  	"github.com/df-mc/dragonfly/server/block/customblock"
     8  	"github.com/df-mc/dragonfly/server/world/chunk"
     9  	"image"
    10  	"math"
    11  	"math/rand"
    12  )
    13  
    14  // Block is a block that may be placed or found in a world. In addition, the block may also be added to an
    15  // inventory: It is also an item.
    16  // Every Block implementation must be able to be hashed as key in a map.
    17  type Block interface {
    18  	// EncodeBlock encodes the block to a string ID such as 'minecraft:grass' and properties associated
    19  	// with the block.
    20  	EncodeBlock() (string, map[string]any)
    21  	// Hash returns a unique identifier of the block including the block states. This function is used internally to
    22  	// convert a block to a single integer which can be used in map lookups. The hash produced therefore does not need
    23  	// to match anything in the game, but it must be unique among all registered blocks.
    24  	// The tool in `/cmd/blockhash` may be used to automatically generate block hashes of blocks in a package.
    25  	Hash() uint64
    26  	// Model returns the BlockModel of the Block.
    27  	Model() BlockModel
    28  }
    29  
    30  // CustomBlock represents a block that is non-vanilla and requires a resource pack and extra steps to show it to the
    31  // client.
    32  type CustomBlock interface {
    33  	Block
    34  	Properties() customblock.Properties
    35  }
    36  
    37  type CustomBlockBuildable interface {
    38  	CustomBlock
    39  	// Name is the name displayed to clients using the block.
    40  	Name() string
    41  	// Geometries is the geometries for the block that define the shape of the block. If false is returned, no custom
    42  	// geometry will be applied. Permutation-specific geometry can be defined by returning a map of permutations to
    43  	// geometry.
    44  	Geometry() []byte
    45  	// Textures is a map of images indexed by their target, used to map textures on to the block. Permutation-specific
    46  	// textures can be defined by returning a map of permutations to textures.
    47  	Textures() map[string]image.Image
    48  }
    49  
    50  // Liquid represents a block that can be moved through and which can flow in the world after placement. There
    51  // are two liquids in vanilla, which are lava and water.
    52  type Liquid interface {
    53  	Block
    54  	// LiquidDepth returns the current depth of the liquid.
    55  	LiquidDepth() int
    56  	// SpreadDecay returns the amount of depth that is subtracted from the liquid's depth when it spreads to
    57  	// a next block.
    58  	SpreadDecay() int
    59  	// WithDepth returns the liquid with the depth passed.
    60  	WithDepth(depth int, falling bool) Liquid
    61  	// LiquidFalling checks if the liquid is currently considered falling down.
    62  	LiquidFalling() bool
    63  	// BlastResistance is the blast resistance of the liquid, which influences the liquid's ability to withstand an
    64  	// explosive blast.
    65  	BlastResistance() float64
    66  	// LiquidType returns an int unique for the liquid, used to check if two liquids are considered to be
    67  	// of the same type.
    68  	LiquidType() string
    69  	// Harden checks if the block should harden when looking at the surrounding blocks and sets the position
    70  	// to the hardened block when adequate. If the block was hardened, the method returns true.
    71  	Harden(pos cube.Pos, w *World, flownIntoBy *cube.Pos) bool
    72  }
    73  
    74  // hashes holds a list of runtime IDs indexed by the hash of the Block that implements the blocks pointed to by those
    75  // runtime IDs. It is used to look up a block's runtime ID quickly.
    76  var hashes = intintmap.New(7000, 0.999)
    77  
    78  // RegisterBlock registers the Block passed. The EncodeBlock method will be used to encode and decode the
    79  // block passed. RegisterBlock panics if the block properties returned were not valid, existing properties.
    80  func RegisterBlock(b Block) {
    81  	name, properties := b.EncodeBlock()
    82  	if _, ok := b.(CustomBlock); ok {
    83  		registerBlockState(blockState{Name: name, Properties: properties}, true)
    84  	}
    85  	rid, ok := stateRuntimeIDs[stateHash{name: name, properties: hashProperties(properties)}]
    86  	if !ok {
    87  		// We assume all blocks must have all their states registered beforehand. Vanilla blocks will have
    88  		// this done through registering of all states present in the block_states.nbt file.
    89  		panic(fmt.Sprintf("block state returned is not registered (%v {%#v})", name, properties))
    90  	}
    91  	if _, ok := blocks[rid].(unknownBlock); !ok {
    92  		panic(fmt.Sprintf("block with name and properties %v {%#v} already registered", name, properties))
    93  	}
    94  	hash := int64(b.Hash())
    95  	if other, ok := hashes.Get(hash); ok {
    96  		panic(fmt.Sprintf("block %#v with hash %v already registered by %#v", b, hash, blocks[other]))
    97  	}
    98  	blocks[rid] = b
    99  	hashes.Put(hash, int64(rid))
   100  
   101  	if diffuser, ok := b.(lightDiffuser); ok {
   102  		chunk.FilteringBlocks[rid] = diffuser.LightDiffusionLevel()
   103  	}
   104  	if emitter, ok := b.(lightEmitter); ok {
   105  		chunk.LightBlocks[rid] = emitter.LightEmissionLevel()
   106  	}
   107  	if _, ok := b.(NBTer); ok {
   108  		nbtBlocks[rid] = true
   109  	}
   110  	if _, ok := b.(RandomTicker); ok {
   111  		randomTickBlocks[rid] = true
   112  	}
   113  	if _, ok := b.(Liquid); ok {
   114  		liquidBlocks[rid] = true
   115  	}
   116  	if _, ok := b.(LiquidDisplacer); ok {
   117  		liquidDisplacingBlocks[rid] = true
   118  	}
   119  	if c, ok := b.(CustomBlock); ok {
   120  		if _, ok := customBlocks[name]; !ok {
   121  			customBlocks[name] = c
   122  		}
   123  	}
   124  }
   125  
   126  // BlockRuntimeID attempts to return a runtime ID of a block previously registered using RegisterBlock().
   127  // If the runtime ID cannot be found because the Block wasn't registered, BlockRuntimeID will panic.
   128  func BlockRuntimeID(b Block) uint32 {
   129  	if b == nil {
   130  		return airRID
   131  	}
   132  	if h := b.Hash(); h != math.MaxUint64 {
   133  		if rid, ok := hashes.Get(int64(h)); ok {
   134  			return uint32(rid)
   135  		}
   136  		panic(fmt.Sprintf("cannot find block by non-0 hash of block %#v", b))
   137  	}
   138  	return slowBlockRuntimeID(b)
   139  }
   140  
   141  // slowBlockRuntimeID finds the runtime ID of a Block by hashing the properties produced by calling the
   142  // Block.EncodeBlock method and looking it up in the stateRuntimeIDs map.
   143  func slowBlockRuntimeID(b Block) uint32 {
   144  	name, properties := b.EncodeBlock()
   145  
   146  	rid, ok := stateRuntimeIDs[stateHash{name: name, properties: hashProperties(properties)}]
   147  	if !ok {
   148  		panic(fmt.Sprintf("cannot find block by (name + properties): %#v", b))
   149  	}
   150  	return rid
   151  }
   152  
   153  // BlockByRuntimeID attempts to return a Block by its runtime ID. If not found, the bool returned is
   154  // false. If found, the block is non-nil and the bool true.
   155  func BlockByRuntimeID(rid uint32) (Block, bool) {
   156  	if rid >= uint32(len(blocks)) {
   157  		return air(), false
   158  	}
   159  	return blocks[rid], true
   160  }
   161  
   162  // BlockByName attempts to return a Block by its name and properties. If not found, the bool returned is
   163  // false.
   164  func BlockByName(name string, properties map[string]any) (Block, bool) {
   165  	rid, ok := stateRuntimeIDs[stateHash{name: name, properties: hashProperties(properties)}]
   166  	if !ok {
   167  		return nil, false
   168  	}
   169  	return blocks[rid], true
   170  }
   171  
   172  // CustomBlocks returns a map of all custom blocks registered with their names as keys.
   173  func CustomBlocks() map[string]CustomBlock {
   174  	return customBlocks
   175  }
   176  
   177  // air returns an air block.
   178  func air() Block {
   179  	b, _ := BlockByRuntimeID(airRID)
   180  	return b
   181  }
   182  
   183  // RandomTicker represents a block that executes an action when it is ticked randomly. Every 20th of a second,
   184  // one random block in each sub chunk are picked to receive a random tick.
   185  type RandomTicker interface {
   186  	// RandomTick handles a random tick of the block at the position passed. Additionally, a rand.Rand
   187  	// instance is passed which may be used to generate values randomly without locking.
   188  	RandomTick(pos cube.Pos, w *World, r *rand.Rand)
   189  }
   190  
   191  // ScheduledTicker represents a block that executes an action when it has a block update scheduled, such as
   192  // when a block adjacent to it is broken.
   193  type ScheduledTicker interface {
   194  	// ScheduledTick handles a scheduled tick initiated by an event in one of the neighbouring blocks, such as
   195  	// when a block is placed or broken. Additionally, a rand.Rand instance is passed which may be used to
   196  	// generate values randomly without locking.
   197  	ScheduledTick(pos cube.Pos, w *World, r *rand.Rand)
   198  }
   199  
   200  // TickerBlock is an implementation of NBTer with an additional Tick method that is called on every world
   201  // tick for loaded blocks that implement this interface.
   202  type TickerBlock interface {
   203  	NBTer
   204  	Tick(currentTick int64, pos cube.Pos, w *World)
   205  }
   206  
   207  // NeighbourUpdateTicker represents a block that is updated when a block adjacent to it is updated, either
   208  // through placement or being broken.
   209  type NeighbourUpdateTicker interface {
   210  	// NeighbourUpdateTick handles a neighbouring block being updated. The position of that block and the
   211  	// position of this block is passed.
   212  	NeighbourUpdateTick(pos, changedNeighbour cube.Pos, w *World)
   213  }
   214  
   215  // NBTer represents either an item or a block which may decode NBT data and encode to NBT data. Typically,
   216  // this is done to store additional data.
   217  type NBTer interface {
   218  	// DecodeNBT returns the (new) item, block or entity, depending on which of those the NBTer was, with the NBT data
   219  	// decoded into it.
   220  	DecodeNBT(data map[string]any) any
   221  	// EncodeNBT encodes the entity into a map which can then be encoded as NBT to be written.
   222  	EncodeNBT() map[string]any
   223  }
   224  
   225  // LiquidDisplacer represents a block that is able to displace a liquid to a different world layer, without
   226  // fully removing the liquid.
   227  type LiquidDisplacer interface {
   228  	// CanDisplace specifies if the block is able to displace the liquid passed.
   229  	CanDisplace(b Liquid) bool
   230  	// SideClosed checks if a position on the side of the block placed in the world at a specific position is
   231  	// closed. When this returns true (for example, when the side is below the position and the block is a
   232  	// slab), liquid inside the displacer won't flow from pos into side.
   233  	SideClosed(pos, side cube.Pos, w *World) bool
   234  }
   235  
   236  // lightEmitter is identical to a block.LightEmitter.
   237  type lightEmitter interface {
   238  	LightEmissionLevel() uint8
   239  }
   240  
   241  // lightDiffuser is identical to a block.LightDiffuser.
   242  type lightDiffuser interface {
   243  	LightDiffusionLevel() uint8
   244  }
   245  
   246  // replaceableBlock represents a block that may be replaced by another block automatically. An example is
   247  // grass, which may be replaced by clicking it with another block.
   248  type replaceableBlock interface {
   249  	// ReplaceableBy returns a bool which indicates if the block is replaceable by another block.
   250  	ReplaceableBy(b Block) bool
   251  }
   252  
   253  // replaceable checks if the block at the position passed is replaceable with the block passed.
   254  func replaceable(w *World, c *Column, pos cube.Pos, with Block) bool {
   255  	if r, ok := w.blockInChunk(c, pos).(replaceableBlock); ok {
   256  		return r.ReplaceableBy(with)
   257  	}
   258  	return false
   259  }
   260  
   261  // BlockAction represents an action that may be performed by a block. Typically, these actions are sent to
   262  // viewers in a world so that they can see these actions.
   263  type BlockAction interface {
   264  	BlockAction()
   265  }