package world import ( "image" "math/rand/v2" "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/block/customblock" ) // Block is a block that may be placed or found in a world. In addition, the block may also be added to an // inventory: It is also an item. // Every Block implementation must be able to be hashed as key in a map. type Block interface { // EncodeBlock encodes the block to a string ID such as 'minecraft:grass' and properties associated // with the block. EncodeBlock() (string, map[string]any) // Hash returns two different identifiers for the block. The first is the base hash which is unique for // each type of block at runtime. For vanilla blocks, this is an auto-incrementing constant and for custom // blocks, you can call block.NextHash() to get a unique identifier. The second is the hash of the block's // own state and does not need to worry about colliding with other types of blocks. This is later combined // with the base hash to create a unique identifier for the full block. Hash() (uint64, uint64) // Model returns the BlockModel of the Block. Model() BlockModel } // CustomBlock represents a block that is non-vanilla and requires a resource pack and extra steps to show it to the // client. type CustomBlock interface { Block Properties() customblock.Properties } type CustomBlockBuildable interface { CustomBlock // Name is the name displayed to clients using the block. Name() string // Geometry is the geometries for the block that define the shape of the block. If false is returned, no custom // geometry will be applied. Permutation-specific geometry can be defined by returning a map of permutations to // geometry. Geometry() []byte // Textures is a map of images indexed by their target, used to map textures on to the block. Permutation-specific // textures can be defined by returning a map of permutations to textures. Textures() map[string]image.Image } // Liquid represents a block that can be moved through and which can flow in the world after placement. There // are two liquids in vanilla, which are lava and water. type Liquid interface { Block // LiquidDepth returns the current depth of the liquid. LiquidDepth() int // SpreadDecay returns the amount of depth that is subtracted from the liquid's depth when it spreads to // a next block. SpreadDecay() int // WithDepth returns the liquid with the depth passed. WithDepth(depth int, falling bool) Liquid // LiquidFalling checks if the liquid is currently considered falling down. LiquidFalling() bool // BlastResistance is the blast resistance of the liquid, which influences the liquid's ability to withstand an // explosive blast. BlastResistance() float64 // LiquidType returns an int unique for the liquid, used to check if two liquids are considered to be // of the same type. LiquidType() string // Harden checks if the block should harden when looking at the surrounding blocks and sets the position // to the hardened block when adequate. If the block was hardened, the method returns true. Harden(pos cube.Pos, tx *Tx, flownIntoBy *cube.Pos) bool // LiquidRemoveBlock is called when the liquid flows into and removes the block passed. LiquidRemoveBlock(pos cube.Pos, tx *Tx, removed Block) } // Conductor represents a block that can conduct a redstone signal. type Conductor interface { Block // RedstoneSource returns true if the conductor is a signal source. RedstoneSource() bool // WeakPower returns the weak power level emitted by this conductor toward a neighbouring receiver. // The face argument is relative to the receiving block, not this conductor. // Weak power can pass through a solid block to power redstone components on the other side, but // cannot power solid blocks themselves or travel further. // The accountForDust parameter indicates whether redstone dust should be considered when // calculating power levels. WeakPower(pos cube.Pos, face cube.Face, tx *Tx, accountForDust bool) int // StrongPower returns the strong power level emitted by this conductor toward a neighbouring // receiver. The face argument uses the same convention as WeakPower. // Strong power can be transmitted through solid blocks. When a solid block receives strong power // through one of its faces, it can provide weak power to adjacent redstone components on all other // faces. Strong power can also directly power any redstone component. // The accountForDust parameter indicates whether redstone dust should be considered when // calculating power levels. StrongPower(pos cube.Pos, face cube.Face, tx *Tx, accountForDust bool) int } // WeakBlockPowerer represents a conductor whose weak power may weakly power an adjacent conductive block. Weakly // powered blocks may activate mechanisms and repeaters, but do not power adjacent redstone dust. For example, // dust pointing into a stone block opens a door on the stone's far side, but a second stretch of dust there stays // dark. type WeakBlockPowerer interface { Conductor // WeaklyPowersBlocks returns true if this conductor's WeakPower can make an adjacent conductive block weakly powered. WeaklyPowersBlocks() bool } // RedstonePowerRelayer represents a block with custom behaviour for whether // neighbouring redstone power may be relayed through it by Tx.RedstonePower. type RedstonePowerRelayer interface { Block // RelaysRedstonePowerThrough reports whether this non-conductor block may // relay neighbouring redstone power through itself to receivers on its other sides. RelaysRedstonePowerThrough() bool } // RedstoneUpdater represents a block that reacts to nearby redstone power changes. type RedstoneUpdater interface { Block // RedstoneUpdate is called when a change in redstone signal is computed. RedstoneUpdate(pos cube.Pos, tx *Tx) } // RegisterBlock registers the Block passed in the DefaultBlockRegistry. // // This function exists for backwards compatibility and works well for the common "single server per process" setup, // where all worlds share the global default registry. // // If you run multiple servers/registries in a single process, prefer creating a registry using NewBlockRegistry() and // registering blocks on that instance (e.g. conf.Blocks.RegisterBlock(...)) before calling Finalize(). func RegisterBlock(b Block) { DefaultBlockRegistry.RegisterBlock(b) } // BlockHash returns a unique identifier of the block including the block states using the DefaultBlockRegistry. // This function is used internally to convert a block to a single integer which can be used in map lookups. The hash // produced therefore does not need to match anything in the game, but it must be unique among all registered blocks. // The tool in `/cmd/blockhash` may be used to automatically generate block hashes of blocks in a package. // // If you use a non-default registry (NewBlockRegistry), use your registry instance's BlockHash(...) instead so the // hash is consistent with that registry. func BlockHash(b Block) uint64 { return DefaultBlockRegistry.BlockHash(b) } // BlockRuntimeID attempts to return a runtime ID of a block previously registered using RegisterBlock() on the // DefaultBlockRegistry. // If the runtime ID cannot be found because the Block wasn't registered, BlockRuntimeID will panic. // If you use a non-default registry (NewBlockRegistry), use your registry instance's BlockRuntimeID(...) instead. func BlockRuntimeID(b Block) uint32 { return DefaultBlockRegistry.BlockRuntimeID(b) } // BlockByRuntimeID attempts to return a Block by its runtime ID using the DefaultBlockRegistry. If not found, the bool // returned is false. If found, the block is non-nil and the bool true. // If you use a non-default registry (NewBlockRegistry), use your registry instance's BlockByRuntimeID(...) instead. func BlockByRuntimeID(rid uint32) (Block, bool) { return DefaultBlockRegistry.BlockByRuntimeID(rid) } // BlockByName attempts to return a Block by its name and properties using the DefaultBlockRegistry. If not found, the // bool returned is false. // If you use a non-default registry (NewBlockRegistry), use your registry instance's BlockByName(...) instead. func BlockByName(name string, properties map[string]any) (Block, bool) { return DefaultBlockRegistry.BlockByName(name, properties) } // Blocks returns a slice of all blocks registered in the DefaultBlockRegistry. // If you use a non-default registry (NewBlockRegistry), use your registry instance's Blocks() instead. func Blocks() []Block { return DefaultBlockRegistry.Blocks() } // CustomBlocks returns a map of all custom blocks registered with their names as keys in the DefaultBlockRegistry. // If you use a non-default registry (NewBlockRegistry), use your registry instance's CustomBlocks() instead. func CustomBlocks() map[string]CustomBlock { return DefaultBlockRegistry.CustomBlocks() } // RandomTicker represents a block that executes an action when it is ticked randomly. Every 20th of a second, // one random block in each sub chunk are picked to receive a random tick. type RandomTicker interface { // RandomTick handles a random tick of the block at the position passed. Additionally, a rand.RandSource // instance is passed which may be used to generate values randomly without locking. RandomTick(pos cube.Pos, tx *Tx, r *rand.Rand) } // ScheduledTicker represents a block that executes an action when it has a block update scheduled, such as // when a block adjacent to it is broken. type ScheduledTicker interface { // ScheduledTick handles a scheduled tick initiated by an event in one of the neighbouring blocks, such as // when a block is placed or broken. Additionally, a rand.RandSource instance is passed which may be used to // generate values randomly without locking. ScheduledTick(pos cube.Pos, tx *Tx, r *rand.Rand) } // TickerBlock is an implementation of NBTer with an additional Tick method that is called on every world // tick for loaded blocks that implement this interface. type TickerBlock interface { NBTer Tick(currentTick int64, pos cube.Pos, tx *Tx) } // NeighbourUpdateTicker represents a block that is updated when a block adjacent to it is updated, either // through placement or being broken. type NeighbourUpdateTicker interface { // NeighbourUpdateTick handles a neighbouring block being updated. The position of that block and the // position of this block is passed. NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *Tx) } // NBTer represents either an item or a block which may decode NBT data and encode to NBT data. Typically, // this is done to store additional data. type NBTer interface { // DecodeNBT returns the (new) item, block or Entity, depending on which of those the NBTer was, with the NBT data // decoded into it. DecodeNBT(data map[string]any) any // EncodeNBT encodes the Entity into a map which can then be encoded as NBT to be written. EncodeNBT() map[string]any } // LiquidDisplacer represents a block that is able to displace a liquid to a different world layer, without // fully removing the liquid. type LiquidDisplacer interface { // CanDisplace specifies if the block is able to displace the liquid passed. CanDisplace(b Liquid) bool // SideClosed checks if a position on the side of the block placed in the world at a specific position is // closed. When this returns true (for example, when the side is below the position and the block is a // slab), liquid inside the displacer won't flow from pos into side. SideClosed(pos, side cube.Pos, tx *Tx) bool } // lightEmitter is identical to a block.LightEmitter. type lightEmitter interface { LightEmissionLevel() uint8 } // lightDiffuser is identical to a block.LightDiffuser. type lightDiffuser interface { LightDiffusionLevel() uint8 } // replaceableBlock represents a block that may be replaced by another block automatically. An example is // grass, which may be replaced by clicking it with another block. type replaceableBlock interface { // ReplaceableBy returns a bool which indicates if the block is replaceable by another block. ReplaceableBy(b Block) bool } // replaceable checks if the block at the position passed is replaceable with the block passed. func replaceable(w *World, c *Column, pos cube.Pos, with Block) bool { if r, ok := w.blockInChunk(c, pos).(replaceableBlock); ok { return r.ReplaceableBy(with) } return false } // BlockAction represents an action that may be performed by a block. Typically, these actions are sent to // viewers in a world so that they can see these actions. type BlockAction interface { BlockAction() }