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
377 lines
13 KiB
Go
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}
|
|
}
|