Files
TarnaWijaya 26ed99fda6
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
up3
2026-07-09 08:33:57 +08:00

377 lines
13 KiB
Go

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}
}