213 lines
5.7 KiB
Go
213 lines
5.7 KiB
Go
|
|
package block
|
||
|
|
|
||
|
|
import (
|
||
|
|
"math/rand/v2"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/df-mc/dragonfly/server/block/cube"
|
||
|
|
"github.com/df-mc/dragonfly/server/event"
|
||
|
|
"github.com/df-mc/dragonfly/server/item"
|
||
|
|
"github.com/df-mc/dragonfly/server/item/potion"
|
||
|
|
"github.com/df-mc/dragonfly/server/world"
|
||
|
|
"github.com/df-mc/dragonfly/server/world/sound"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Water is a natural fluid that generates abundantly in the world.
|
||
|
|
type Water struct {
|
||
|
|
empty
|
||
|
|
|
||
|
|
// Still makes the water appear as if it is not flowing.
|
||
|
|
Still bool
|
||
|
|
// Depth is the depth of the water. This is a number from 1-8, where 8 is a source block and 1 is the
|
||
|
|
// smallest possible water block.
|
||
|
|
Depth int
|
||
|
|
// Falling specifies if the water is falling. Falling water will always appear as a source block, but its
|
||
|
|
// behaviour differs when it starts spreading.
|
||
|
|
Falling bool
|
||
|
|
}
|
||
|
|
|
||
|
|
// ReplaceableBy ...
|
||
|
|
func (w Water) ReplaceableBy(b world.Block) bool {
|
||
|
|
if _, ok := b.(LiquidRemovable); ok {
|
||
|
|
_, displacer := b.(world.LiquidDisplacer)
|
||
|
|
_, liquid := b.(world.Liquid)
|
||
|
|
return displacer || liquid
|
||
|
|
}
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
|
||
|
|
// EntityInside ...
|
||
|
|
func (w Water) EntityInside(_ cube.Pos, _ *world.Tx, e world.Entity) {
|
||
|
|
if fallEntity, ok := e.(fallDistanceEntity); ok {
|
||
|
|
fallEntity.ResetFallDistance()
|
||
|
|
}
|
||
|
|
if flammable, ok := e.(flammableEntity); ok {
|
||
|
|
flammable.Extinguish()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// FillBottle ...
|
||
|
|
func (w Water) FillBottle() (world.Block, item.Stack, bool) {
|
||
|
|
if w.Depth == 8 {
|
||
|
|
return w, item.NewStack(item.Potion{Type: potion.Water()}, 1), true
|
||
|
|
}
|
||
|
|
return nil, item.Stack{}, false
|
||
|
|
}
|
||
|
|
|
||
|
|
// LiquidDepth returns the depth of the water.
|
||
|
|
func (w Water) LiquidDepth() int {
|
||
|
|
return w.Depth
|
||
|
|
}
|
||
|
|
|
||
|
|
// SpreadDecay returns 1 - The amount of levels decreased upon spreading.
|
||
|
|
func (Water) SpreadDecay() int {
|
||
|
|
return 1
|
||
|
|
}
|
||
|
|
|
||
|
|
// WithDepth returns the water with the depth passed.
|
||
|
|
func (w Water) WithDepth(depth int, falling bool) world.Liquid {
|
||
|
|
w.Depth = depth
|
||
|
|
w.Falling = falling
|
||
|
|
w.Still = false
|
||
|
|
return w
|
||
|
|
}
|
||
|
|
|
||
|
|
// LiquidFalling returns Water.Falling.
|
||
|
|
func (w Water) LiquidFalling() bool {
|
||
|
|
return w.Falling
|
||
|
|
}
|
||
|
|
|
||
|
|
// BlastResistance always returns 500.
|
||
|
|
func (Water) BlastResistance() float64 {
|
||
|
|
return 500
|
||
|
|
}
|
||
|
|
|
||
|
|
// HasLiquidDrops ...
|
||
|
|
func (Water) HasLiquidDrops() bool {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
// LiquidRemoveBlock drops the items of the removed block at the position passed if it has liquid drops.
|
||
|
|
func (Water) LiquidRemoveBlock(pos cube.Pos, tx *world.Tx, removed world.Block) {
|
||
|
|
r, ok := removed.(LiquidRemovable)
|
||
|
|
if !ok || !r.HasLiquidDrops() {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
b, ok := removed.(Breakable)
|
||
|
|
if !ok {
|
||
|
|
panic("liquid drops should always implement breakable")
|
||
|
|
}
|
||
|
|
for _, d := range b.BreakInfo().Drops(item.ToolNone{}, nil) {
|
||
|
|
dropItem(tx, d, pos.Vec3Centre())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// LightDiffusionLevel ...
|
||
|
|
func (Water) LightDiffusionLevel() uint8 {
|
||
|
|
return 2
|
||
|
|
}
|
||
|
|
|
||
|
|
// ScheduledTick ...
|
||
|
|
func (w Water) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) {
|
||
|
|
if w.Depth == 7 {
|
||
|
|
// Attempt to form new water source blocks.
|
||
|
|
count := 0
|
||
|
|
pos.Neighbours(func(neighbour cube.Pos) {
|
||
|
|
if neighbour[1] == pos[1] {
|
||
|
|
if liquid, ok := tx.Liquid(neighbour); ok {
|
||
|
|
if water, ok := liquid.(Water); ok && water.Depth == 8 && !water.Falling {
|
||
|
|
count++
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}, tx.Range())
|
||
|
|
if count >= 2 {
|
||
|
|
if !canFlowInto(w, tx, pos.Side(cube.FaceDown), true) {
|
||
|
|
// Only form a new source block if there either is no water below this block, or if the water
|
||
|
|
// below this is not falling (full source block).
|
||
|
|
res := Water{Depth: 8, Still: true}
|
||
|
|
ctx := event.C(tx)
|
||
|
|
if tx.World().Handler().HandleLiquidFlow(ctx, pos, pos, res, w); ctx.Cancelled() {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
tx.SetLiquid(pos, res)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
tickLiquid(w, pos, tx)
|
||
|
|
}
|
||
|
|
|
||
|
|
// NeighbourUpdateTick ...
|
||
|
|
func (w Water) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) {
|
||
|
|
if tx.World().Dimension().WaterEvaporates() {
|
||
|
|
// Particles are spawned client-side.
|
||
|
|
tx.SetLiquid(pos, nil)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
tx.ScheduleBlockUpdate(pos, w, time.Second/4)
|
||
|
|
}
|
||
|
|
|
||
|
|
// LiquidType ...
|
||
|
|
func (Water) LiquidType() string {
|
||
|
|
return "water"
|
||
|
|
}
|
||
|
|
|
||
|
|
// Harden hardens the water if lava flows into it.
|
||
|
|
func (w Water) Harden(pos cube.Pos, tx *world.Tx, flownIntoBy *cube.Pos) bool {
|
||
|
|
if flownIntoBy == nil {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
if lava, ok := tx.Block(pos.Side(cube.FaceUp)).(Lava); ok {
|
||
|
|
ctx := event.C(tx)
|
||
|
|
if tx.World().Handler().HandleLiquidHarden(ctx, pos, w, lava, Stone{}); ctx.Cancelled() {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
tx.SetBlock(pos, Stone{}, nil)
|
||
|
|
tx.PlaySound(pos.Vec3Centre(), sound.Fizz{})
|
||
|
|
return true
|
||
|
|
} else if lava, ok := tx.Block(*flownIntoBy).(Lava); ok {
|
||
|
|
ctx := event.C(tx)
|
||
|
|
if tx.World().Handler().HandleLiquidHarden(ctx, pos, w, lava, Cobblestone{}); ctx.Cancelled() {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
tx.SetBlock(*flownIntoBy, Cobblestone{}, nil)
|
||
|
|
tx.PlaySound(pos.Vec3Centre(), sound.Fizz{})
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
// EncodeBlock ...
|
||
|
|
func (w Water) EncodeBlock() (name string, properties map[string]any) {
|
||
|
|
if w.Depth < 1 || w.Depth > 8 {
|
||
|
|
panic("invalid water depth, must be between 1 and 8")
|
||
|
|
}
|
||
|
|
v := 8 - w.Depth
|
||
|
|
if w.Falling {
|
||
|
|
v += 8
|
||
|
|
}
|
||
|
|
if w.Still {
|
||
|
|
return "minecraft:water", map[string]any{"liquid_depth": int32(v)}
|
||
|
|
}
|
||
|
|
return "minecraft:flowing_water", map[string]any{"liquid_depth": int32(v)}
|
||
|
|
}
|
||
|
|
|
||
|
|
// allWater returns a list of all water states.
|
||
|
|
func allWater() (b []world.Block) {
|
||
|
|
f := func(still, falling bool) {
|
||
|
|
b = append(b, Water{Still: still, Falling: falling, Depth: 8})
|
||
|
|
b = append(b, Water{Still: still, Falling: falling, Depth: 7})
|
||
|
|
b = append(b, Water{Still: still, Falling: falling, Depth: 6})
|
||
|
|
b = append(b, Water{Still: still, Falling: falling, Depth: 5})
|
||
|
|
b = append(b, Water{Still: still, Falling: falling, Depth: 4})
|
||
|
|
b = append(b, Water{Still: still, Falling: falling, Depth: 3})
|
||
|
|
b = append(b, Water{Still: still, Falling: falling, Depth: 2})
|
||
|
|
b = append(b, Water{Still: still, Falling: falling, Depth: 1})
|
||
|
|
}
|
||
|
|
f(true, true)
|
||
|
|
f(true, false)
|
||
|
|
f(false, false)
|
||
|
|
f(false, true)
|
||
|
|
return
|
||
|
|
}
|