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 }