package block import ( "math/rand/v2" "time" "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/block/customblock" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" ) // Activatable represents a block that may be activated by a viewer of the world. When activated, the block // will execute some specific logic. type Activatable interface { // Activate activates the block at a specific block position. The face clicked is passed, as well as the // world in which the block was activated and the viewer that activated it. // Activate returns a bool indicating if activating the block was used successfully. Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool } // Pickable represents a block that may give a different item then the block itself when picked. type Pickable interface { // Pick returns the item that is picked when the block is picked. Pick() item.Stack } // Punchable represents a block that may be punched by a viewer of the world. When punched, the block // will execute some specific logic. type Punchable interface { // Punch punches the block at a specific block position. The face clicked is passed, as well as the // world in which the block was punched and the viewer that punched it. Punch(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User) } // LightEmitter represents a block that emits light when placed. Blocks such as torches or lanterns implement // this interface. type LightEmitter interface { // LightEmissionLevel returns the light emission level of the block, a number from 0-15 where 15 is the // brightest and 0 means it doesn't emit light at all. LightEmissionLevel() uint8 } // LightDiffuser represents a block that diffuses light. This means that a specific amount of light levels // will be subtracted when light passes through the block. // Blocks that do not implement LightDiffuser will be assumed to be solid: Light will not be able to pass // through these blocks. type LightDiffuser interface { // LightDiffusionLevel returns the amount of light levels that is subtracted when light passes through // this block. Some blocks, such as leaves, have this behaviour. A diffusion level of 15 means that all // light will be completely blocked when it passes through the block. LightDiffusionLevel() uint8 } // RedstoneWireStepDowner represents a block with custom behaviour for redstone wire providing power when travelling // down it. type RedstoneWireStepDowner interface { // CanRedstoneWireStepDown returns whether redstone wire may provide power while travelling down the block at // pos from the wire position passed. CanRedstoneWireStepDown(pos, from cube.Pos, tx *world.Tx) bool } // Replaceable 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 Replaceable interface { // ReplaceableBy returns a bool which indicates if the block is replaceableWith by another block. ReplaceableBy(b world.Block) bool } // EntityLander represents a block that reacts to an entity landing on it after falling. type EntityLander interface { // EntityLand is called when an entity lands on the block. EntityLand(pos cube.Pos, tx *world.Tx, e world.Entity, distance *float64) } // EntityInsider represents a block that reacts to an entity going inside its 1x1x1 axis // aligned bounding box. type EntityInsider interface { // EntityInside is called when an entity goes inside the block's 1x1x1 axis aligned bounding box. EntityInside(pos cube.Pos, tx *world.Tx, e world.Entity) } // EntityStepper represents a block that reacts to an entity standing on top of it. type EntityStepper interface { // EntityStepOn is called every tick while an entity is standing on the top face of the block. EntityStepOn(pos cube.Pos, tx *world.Tx, e world.Entity) } // ProjectileHitter represents a block that handles being hit by a projectile. type ProjectileHitter interface { // ProjectileHit is called when a projectile hits the block. ProjectileHit(pos cube.Pos, tx *world.Tx, e world.Entity, face cube.Face) } // Frictional represents a block that may have a custom friction value. Friction is used for entity drag when the // entity is on ground. If a block does not implement this interface, it should be assumed that its friction is 0.6. type Frictional interface { // Friction returns the block's friction value. Friction() float64 } // Permutable represents a custom block that can have more permutations than its default state. type Permutable interface { // States returns a map of all the different properties for the block. The key is the property name, and the value // is a slice of all the possible values for that property. It is important that a block is registered in dragonfly // for each of the possible combinations of properties and values. States() map[string][]any // Permutations returns a slice of all the different permutations for the block. Multiple permutations can be // applied at once if their conditions are met. Permutations() []customblock.Permutation } // unknownFace is a face that is used for certain block items. This should not be exposed in the API. var unknownFace = cube.Face(len(cube.Faces())) // unknownDirection is a direction that is used for certain block items. This should not be exposed in the API. var unknownDirection = cube.Direction(len(cube.Directions())) func calculateFace(user item.User, placePos cube.Pos) cube.Face { userPos := user.Position() pos := cube.PosFromVec3(userPos) if abs(pos[0]-placePos[0]) < 2 && abs(pos[2]-placePos[2]) < 2 { y := userPos[1] if eyed, ok := user.(interface{ EyeHeight() float64 }); ok { y += eyed.EyeHeight() } if y-float64(placePos[1]) > 2.0 { return cube.FaceUp } else if float64(placePos[1])-y > 0.0 { return cube.FaceDown } } return user.Rotation().Direction().Opposite().Face() } func abs(x int) int { if x > 0 { return x } return -x } // replaceableWith checks if the block at the position passed is replaceable with the block passed. func replaceableWith(tx *world.Tx, pos cube.Pos, with world.Block) bool { if pos.OutOfBounds(tx.Range()) { return false } b := tx.Block(pos) if replaceable, ok := b.(Replaceable); ok { if !replaceable.ReplaceableBy(with) || b == with { return false } if liquid, ok := tx.Liquid(pos); ok { replaceable, ok := liquid.(Replaceable) return ok && replaceable.ReplaceableBy(with) } return true } return false } // firstReplaceable finds the first replaceable block position eligible to have a block placed on it after // clicking on the position and face passed. // If none can be found, the bool returned is false. func firstReplaceable(tx *world.Tx, pos cube.Pos, face cube.Face, with world.Block) (cube.Pos, cube.Face, bool) { if replaceableWith(tx, pos, with) { // A replaceableWith block was clicked, so we can replace it. This will then be assumed to be placed on // the top face. (Torches, for example, will get attached to the floor when clicking tall grass.) return pos, cube.FaceUp, true } side := pos.Side(face) if replaceableWith(tx, side, with) { return side, face, true } return pos, face, false } // place places the block passed at the position passed. If the user implements the block.Placer interface, it // will use its PlaceBlock method. If not, the block is placed without interaction from the user. func place(tx *world.Tx, pos cube.Pos, b world.Block, user item.User, ctx *item.UseContext) { if placer, ok := user.(Placer); ok { placer.PlaceBlock(pos, b, ctx) return } tx.SetBlock(pos, b, nil) tx.PlaySound(pos.Vec3(), sound.BlockPlace{Block: b}) } // horizontalDirection returns the horizontal direction of the given direction. This is a legacy type still used in // various blocks. func horizontalDirection(d cube.Direction) cube.Direction { switch d { case cube.South: return cube.North case cube.West: return cube.South case cube.North: return cube.West case cube.East: return cube.East } panic("invalid direction") } // placed checks if an item was placed with the use context passed. func placed(ctx *item.UseContext) bool { return ctx.CountSub > 0 } // boolByte returns 1 if the bool passed is true, or 0 if it is false. func boolByte(b bool) uint8 { if b { return 1 } return 0 } // replaceable is a struct that may be embedded to make a block replaceable by any other block. type replaceable struct{} // ReplaceableBy ... func (replaceable) ReplaceableBy(world.Block) bool { return true } // transparent is a struct that may be embedded to make a block transparent to light. Light will be able to // pass through this block freely. type transparent struct{} // LightDiffusionLevel ... func (transparent) LightDiffusionLevel() uint8 { return 0 } // gravityAffected is a struct that may be embedded for blocks affected by gravity. type gravityAffected struct{} // Solidifies ... func (g gravityAffected) Solidifies(cube.Pos, *world.Tx) bool { return false } // fall spawns a falling block entity at the given position. func (g gravityAffected) fall(b world.Block, pos cube.Pos, tx *world.Tx) { if replaceableWith(tx, pos.Side(cube.FaceDown), b) { tx.SetBlock(pos, nil, nil) opts := world.EntitySpawnOpts{Position: pos.Vec3Centre()} tx.AddEntity(tx.World().EntityRegistry().Config().FallingBlock(opts, b)) } } // Flammable is an interface for blocks that can catch on fire. type Flammable interface { // FlammabilityInfo returns information about a block's behaviour involving fire. FlammabilityInfo() FlammabilityInfo } // FlammabilityInfo contains values related to block behaviours involving fire. type FlammabilityInfo struct { // Encouragement is the chance a block will catch on fire during attempted fire spread. Encouragement int // Flammability is the chance a block will burn away during a fire block tick. Flammability int // LavaFlammable returns whether it can catch on fire from lava. LavaFlammable bool } // newFlammabilityInfo creates a FlammabilityInfo struct with the properties passed. func newFlammabilityInfo(encouragement, flammability int, lavaFlammable bool) FlammabilityInfo { return FlammabilityInfo{ Encouragement: encouragement, Flammability: flammability, LavaFlammable: lavaFlammable, } } // livingEntity ... type livingEntity interface { // Hurt hurts the entity for a given amount of damage. The source passed represents the cause of the // damage, for example damage.SourceEntityAttack if the entity is attacked by another entity. // If the final damage exceeds the health that the entity currently has, the entity is killed. // Hurt returns the final amount of damage dealt to the Living entity and returns whether the Living entity // was vulnerable to the damage at all. Hurt(damage float64, src world.DamageSource) (n float64, vulnerable bool) } // flammableEntity ... type flammableEntity interface { // OnFireDuration returns duration of fire in ticks. OnFireDuration() time.Duration // SetOnFire sets the entity on fire for the specified duration. SetOnFire(duration time.Duration) // Extinguish extinguishes the entity. Extinguish() } // dropItem ... func dropItem(tx *world.Tx, it item.Stack, pos mgl64.Vec3) { create := tx.World().EntityRegistry().Config().Item opts := world.EntitySpawnOpts{Position: pos, Velocity: mgl64.Vec3{rand.Float64()*0.2 - 0.1, 0.2, rand.Float64()*0.2 - 0.1}} tx.AddEntity(create(opts, it)) } // bass is a struct that may be embedded for blocks that create a bass sound. type bass struct{} // Instrument ... func (bass) Instrument() sound.Instrument { return sound.Bass() } // snare is a struct that may be embedded for blocks that create a snare drum sound. type snare struct{} // Instrument ... func (snare) Instrument() sound.Instrument { return sound.Snare() } // clicksAndSticks is a struct that may be embedded for blocks that create a clicks and sticks sound. type clicksAndSticks struct{} // Instrument ... func (clicksAndSticks) Instrument() sound.Instrument { return sound.ClicksAndSticks() } // bassDrum is a struct that may be embedded for blocks that create a bass drum sound. type bassDrum struct{} // Instrument ... func (bassDrum) Instrument() sound.Instrument { return sound.BassDrum() } // flute is a struct that may be embedded for blocks that create a flute sound. type flute struct{} // Instrument ... func (flute) Instrument() sound.Instrument { return sound.Flute() } // newSmeltInfo returns a new SmeltInfo with the given values. func newSmeltInfo(product item.Stack, experience float64) item.SmeltInfo { return item.SmeltInfo{ Product: product, Experience: experience, } } // newFoodSmeltInfo returns a new SmeltInfo with the given values that allows smelting in a smelter. func newFoodSmeltInfo(product item.Stack, experience float64) item.SmeltInfo { return item.SmeltInfo{ Product: product, Experience: experience, Food: true, } } // newOreSmeltInfo returns a new SmeltInfo with the given values that allows smelting in a blast furnace. func newOreSmeltInfo(product item.Stack, experience float64) item.SmeltInfo { return item.SmeltInfo{ Product: product, Experience: experience, Ores: true, } } // newFuelInfo returns a new FuelInfo with the given values. func newFuelInfo(duration time.Duration) item.FuelInfo { return item.FuelInfo{Duration: duration} }