Files
mc/server/block/water.go
T
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

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
}