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
326 lines
10 KiB
Go
326 lines
10 KiB
Go
package block
|
|
|
|
import (
|
|
"github.com/df-mc/dragonfly/server/block/cube"
|
|
"github.com/df-mc/dragonfly/server/block/model"
|
|
"github.com/df-mc/dragonfly/server/item"
|
|
"github.com/df-mc/dragonfly/server/world"
|
|
"github.com/go-gl/mathgl/mgl64"
|
|
"math/rand/v2"
|
|
)
|
|
|
|
// Vines are climbable non-solid vegetation blocks that grow on walls.
|
|
type Vines struct {
|
|
replaceable
|
|
transparent
|
|
empty
|
|
sourceWaterDisplacer
|
|
|
|
// NorthDirection is true if the vines are attached towards north.
|
|
NorthDirection bool
|
|
// EastDirection is true if the vines are attached towards east.
|
|
EastDirection bool
|
|
// SouthDirection is true if the vines are attached towards south.
|
|
SouthDirection bool
|
|
// WestDirection is true if the vines are attached towards west.
|
|
WestDirection bool
|
|
}
|
|
|
|
// CompostChance ...
|
|
func (Vines) CompostChance() float64 {
|
|
return 0.5
|
|
}
|
|
|
|
// SideClosed ...
|
|
func (Vines) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool {
|
|
return false
|
|
}
|
|
|
|
// HasLiquidDrops ...
|
|
func (Vines) HasLiquidDrops() bool {
|
|
return false
|
|
}
|
|
|
|
// FlammabilityInfo ...
|
|
func (Vines) FlammabilityInfo() FlammabilityInfo {
|
|
return newFlammabilityInfo(15, 100, true)
|
|
}
|
|
|
|
// BreakInfo ...
|
|
func (v Vines) BreakInfo() BreakInfo {
|
|
return newBreakInfo(0.2, func(t item.Tool) bool {
|
|
return t.ToolType() == item.TypeShears
|
|
}, axeEffective, oneOf(v))
|
|
}
|
|
|
|
// EntityInside ...
|
|
func (Vines) EntityInside(_ cube.Pos, _ *world.Tx, e world.Entity) {
|
|
if fallEntity, ok := e.(fallDistanceEntity); ok {
|
|
fallEntity.ResetFallDistance()
|
|
}
|
|
}
|
|
|
|
// WithAttachment returns a Vines block with an attachment on the given cube.Direction.
|
|
func (v Vines) WithAttachment(direction cube.Direction, attached bool) Vines {
|
|
switch direction {
|
|
case cube.North:
|
|
v.NorthDirection = attached
|
|
return v
|
|
case cube.East:
|
|
v.EastDirection = attached
|
|
return v
|
|
case cube.South:
|
|
v.SouthDirection = attached
|
|
return v
|
|
case cube.West:
|
|
v.WestDirection = attached
|
|
return v
|
|
}
|
|
panic("should never happen")
|
|
}
|
|
|
|
// Attachment returns the attachment of the vines at the given direction.
|
|
func (v Vines) Attachment(direction cube.Direction) bool {
|
|
switch direction {
|
|
case cube.North:
|
|
return v.NorthDirection
|
|
case cube.East:
|
|
return v.EastDirection
|
|
case cube.South:
|
|
return v.SouthDirection
|
|
case cube.West:
|
|
return v.WestDirection
|
|
}
|
|
panic("should never happen")
|
|
}
|
|
|
|
// Attachments returns all attachments of the vines.
|
|
func (v Vines) Attachments() (attachments []cube.Direction) {
|
|
for _, d := range cube.Directions() {
|
|
if v.Attachment(d) {
|
|
attachments = append(attachments, d)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// UseOnBlock ...
|
|
func (v Vines) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool {
|
|
if _, ok := tx.Block(pos).Model().(model.Solid); !ok || face.Axis() == cube.Y {
|
|
return false
|
|
}
|
|
pos, face, used := firstReplaceable(tx, pos, face, v)
|
|
if !used {
|
|
return false
|
|
}
|
|
if _, ok := tx.Block(pos).(Vines); ok {
|
|
// Do not overwrite existing vine block.
|
|
return false
|
|
}
|
|
//noinspection GoAssignmentToReceiver
|
|
v = v.WithAttachment(face.Direction().Opposite(), true)
|
|
|
|
place(tx, pos, v, user, ctx)
|
|
return placed(ctx)
|
|
}
|
|
|
|
// NeighbourUpdateTick ...
|
|
func (v Vines) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) {
|
|
above, updated := tx.Block(pos.Side(cube.FaceUp)), false
|
|
for _, d := range v.Attachments() {
|
|
if !v.canSpreadTo(tx, pos.Side(d.Face())) {
|
|
if o, ok := above.(Vines); !ok || !o.Attachment(d) {
|
|
//noinspection GoAssignmentToReceiver
|
|
v = v.WithAttachment(d, false)
|
|
updated = true
|
|
}
|
|
}
|
|
}
|
|
if !updated {
|
|
return
|
|
}
|
|
if len(v.Attachments()) == 0 {
|
|
breakBlock(v, pos, tx)
|
|
return
|
|
}
|
|
tx.SetBlock(pos, v, nil)
|
|
}
|
|
|
|
// RandomTick ...
|
|
func (v Vines) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) {
|
|
if r.Float64() > 0.25 {
|
|
// Vines have a 25% chance of spreading.
|
|
return
|
|
}
|
|
|
|
// Choose a random direction to spread.
|
|
face := cube.Face(r.IntN(len(cube.Faces())))
|
|
selectedPos := pos.Side(face)
|
|
|
|
// If a horizontal direction was chosen and the vine block is not already
|
|
// attached in that direction, attempt to spread in that direction.
|
|
if face.Axis() != cube.Y && !v.Attachment(face.Direction()) {
|
|
if !v.canSpread(tx, pos) {
|
|
// No further attempt to spread vertically will be made.
|
|
return
|
|
}
|
|
// Attempt to create a new vine block if there is a neighbouring air block
|
|
// in the chosen direction.
|
|
if _, ok := tx.Block(selectedPos).(Air); ok {
|
|
rightRotatedFace := face.RotateRight()
|
|
leftRotatedFace := face.RotateLeft()
|
|
|
|
attachedOnRight := v.Attachment(rightRotatedFace.Direction())
|
|
attachedOnLeft := v.Attachment(leftRotatedFace.Direction())
|
|
|
|
rightSelectedPos := selectedPos.Side(rightRotatedFace)
|
|
leftSelectedPos := selectedPos.Side(leftRotatedFace)
|
|
|
|
// Four attempts to create a new vine block will be made, in the
|
|
// following order:
|
|
// 1) If the current vine block is attached in the direction towards
|
|
// the right ("clockwise") of the chosen direction, and a solid
|
|
// block can support a vine on that direction in the selected
|
|
// position, create a new vine block attached on that clockwise
|
|
// direction at the selected position.
|
|
// 2) If the clockwise direction fails, try again with the left
|
|
// ("counter-clockwise") direction.
|
|
// 3) If the current vine block is attached in the direction towards
|
|
// the right of the chosen direction, the current vine block is
|
|
// also backed by a solid block in that same direction, and the
|
|
// block neighbouring the selected position in that direction is
|
|
// air, spread into that air block onto the face opposite of the
|
|
// chosen direction. The vine jumps from one face of a block onto
|
|
// another as a result.
|
|
// 4) If the clockwise direction fails, try again with the left
|
|
// direction.
|
|
if attachedOnRight && v.canSpreadTo(tx, rightSelectedPos) {
|
|
tx.SetBlock(selectedPos, (Vines{}).WithAttachment(rightRotatedFace.Direction(), true), nil)
|
|
} else if attachedOnLeft && v.canSpreadTo(tx, leftSelectedPos) {
|
|
tx.SetBlock(selectedPos, (Vines{}).WithAttachment(leftRotatedFace.Direction(), true), nil)
|
|
} else if _, ok = tx.Block(rightSelectedPos).(Air); ok && attachedOnRight && v.canSpreadTo(tx, pos.Side(rightRotatedFace)) {
|
|
tx.SetBlock(rightSelectedPos, (Vines{}).WithAttachment(face.Opposite().Direction(), true), nil)
|
|
} else if _, ok = tx.Block(leftSelectedPos).(Air); ok && attachedOnLeft && v.canSpreadTo(tx, pos.Side(leftRotatedFace)) {
|
|
tx.SetBlock(leftSelectedPos, (Vines{}).WithAttachment(face.Opposite().Direction(), true), nil)
|
|
}
|
|
} else if v.canSpreadTo(tx, selectedPos) {
|
|
// If the neighbouring block is solid, update the vine to be attached in that direction.
|
|
tx.SetBlock(pos, v.WithAttachment(face.Direction(), true), nil)
|
|
}
|
|
return
|
|
}
|
|
|
|
// If the chosen direction is Up and the position above is within the height
|
|
// limit, attempt to spread upwards.
|
|
if face == cube.FaceUp && selectedPos.OutOfBounds(tx.Range()) {
|
|
// Vines can only spread upwards into an air block.
|
|
if _, ok := tx.Block(selectedPos).(Air); ok {
|
|
if !v.canSpread(tx, pos) {
|
|
// No further attempt to spread down will be made.
|
|
return
|
|
}
|
|
newVines := Vines{}
|
|
for _, f := range cube.HorizontalFaces() {
|
|
// For each direction the current vine block is attached on,
|
|
// there is a 50% chance for the new above vine block to
|
|
// attach onto the direction, if there is also a solid block
|
|
// in that direction to support the vine.
|
|
if r.IntN(2) == 0 && v.Attachment(f.Direction()) && v.canSpreadTo(tx, selectedPos.Side(f)) {
|
|
newVines = newVines.WithAttachment(f.Direction(), true)
|
|
}
|
|
}
|
|
if len(newVines.Attachments()) > 0 {
|
|
tx.SetBlock(selectedPos, newVines, nil)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
// If an attempt to spread horizontally or upwards has failed but not exited
|
|
// early, attempt to spread downwards.
|
|
selectedPos = pos.Side(cube.FaceDown)
|
|
if selectedPos.OutOfBounds(tx.Range()) {
|
|
return
|
|
}
|
|
newVines, vines := tx.Block(selectedPos).(Vines)
|
|
if _, ok := tx.Block(selectedPos).(Air); !ok && !vines {
|
|
// The block under the current vine block must be air or a vine block.
|
|
return
|
|
}
|
|
var changed bool
|
|
for _, f := range cube.HorizontalFaces() {
|
|
// For each direction the current vine block is attached on, there is a
|
|
// 50% chance for the below vine block to attach onto the direction if
|
|
// it is not already attached in that direction.
|
|
if r.IntN(2) == 0 && v.Attachment(f.Direction()) && !newVines.Attachment(f.Direction()) {
|
|
newVines, changed = newVines.WithAttachment(f.Direction(), true), true
|
|
}
|
|
}
|
|
if changed {
|
|
tx.SetBlock(selectedPos, newVines, nil)
|
|
}
|
|
}
|
|
|
|
// EncodeItem ...
|
|
func (Vines) EncodeItem() (name string, meta int16) {
|
|
return "minecraft:vine", 0
|
|
}
|
|
|
|
// EncodeBlock ...
|
|
func (v Vines) EncodeBlock() (string, map[string]any) {
|
|
var bits int
|
|
for i, ok := range []bool{v.SouthDirection, v.WestDirection, v.NorthDirection, v.EastDirection} {
|
|
if ok {
|
|
bits |= 1 << i
|
|
}
|
|
}
|
|
return "minecraft:vine", map[string]any{"vine_direction_bits": int32(bits)}
|
|
}
|
|
|
|
// canSpreadTo returns true if the vines can spread onto the block at the
|
|
// given position. Vines may only spread onto fully solid blocks.
|
|
func (Vines) canSpreadTo(tx *world.Tx, pos cube.Pos) bool {
|
|
_, ok := tx.Block(pos).Model().(model.Solid)
|
|
return ok
|
|
}
|
|
|
|
// canSpread returns true if the vines can spread from the given position. Vines
|
|
// may only spread horizontally or upwards if there are fewer than 4 vines within
|
|
// a 9x9x3 area centred around the Vines.
|
|
func (v Vines) canSpread(tx *world.Tx, pos cube.Pos) bool {
|
|
var count int
|
|
for x := -4; x <= 4; x++ {
|
|
for z := -4; z <= 4; z++ {
|
|
for y := -1; y <= 1; y++ {
|
|
if _, ok := tx.Block(pos.Add(cube.Pos{x, y, z})).(Vines); ok {
|
|
count++
|
|
// The centre vine is counted, for a max of 4+1=5.
|
|
if count >= 5 {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// allVines ...
|
|
func allVines() (b []world.Block) {
|
|
for _, north := range []bool{true, false} {
|
|
for _, east := range []bool{true, false} {
|
|
for _, south := range []bool{true, false} {
|
|
for _, west := range []bool{true, false} {
|
|
b = append(b, Vines{
|
|
NorthDirection: north,
|
|
EastDirection: east,
|
|
SouthDirection: south,
|
|
WestDirection: west,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|