diff --git a/server/block/hash.go b/server/block/hash.go index a46a00eb5e..3552ef5a48 100644 --- a/server/block/hash.go +++ b/server/block/hash.go @@ -106,6 +106,7 @@ const ( hashHayBale hashHoneycomb hashHopper + hashIce hashInfestedCobblestone hashInfestedDeepslate hashInfestedStone @@ -640,6 +641,10 @@ func (h Hopper) Hash() (uint64, uint64) { return hashHopper, uint64(h.Facing) | uint64(boolByte(h.Powered))<<3 } +func (Ice) Hash() (uint64, uint64) { + return hashIce, 0 +} + func (InfestedCobblestone) Hash() (uint64, uint64) { return hashInfestedCobblestone, 0 } diff --git a/server/block/ice.go b/server/block/ice.go new file mode 100644 index 0000000000..ebcbcf852f --- /dev/null +++ b/server/block/ice.go @@ -0,0 +1,76 @@ +package block + +import ( + "math/rand/v2" + + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/item/enchantment" + "github.com/df-mc/dragonfly/server/world" +) + +// Ice is a translucent solid block that melts into water when a bright light source is placed next to it. +type Ice struct { + solid +} + +// Friction ... +func (Ice) Friction() float64 { + return 0.98 +} + +// LightDiffusionLevel ... +func (Ice) LightDiffusionLevel() uint8 { + return 2 +} + +// RandomTick ... +func (i Ice) RandomTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { + for _, f := range cube.Faces() { + if tx.BlockLight(pos.Side(f)) > 11 { + i.melt(pos, tx) + return + } + } +} + +// BreakInfo ... +func (i Ice) BreakInfo() BreakInfo { + return newBreakInfo(0.5, alwaysHarvestable, pickaxeEffective, silkTouchOnlyDrop(i)).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { + if u == nil { + return + } + if gm, ok := u.(interface{ GameMode() world.GameMode }); ok && gm.GameMode().CreativeInventory() { + return + } + held, _ := u.HeldItems() + if _, ok := held.Enchantment(enchantment.SilkTouch); ok { + return + } + below := pos.Side(cube.FaceDown) + b := tx.Block(below) + if _, liquid := b.(world.Liquid); !liquid && !b.Model().FaceSolid(below, cube.FaceUp, tx) { + return + } + i.melt(pos, tx) + }) +} + +// melt ... +func (Ice) melt(pos cube.Pos, tx *world.Tx) { + if tx.World().Dimension().WaterEvaporates() { + tx.SetBlock(pos, nil, nil) + return + } + tx.SetBlock(pos, Water{Still: true, Depth: 8}, nil) +} + +// EncodeItem ... +func (Ice) EncodeItem() (name string, meta int16) { + return "minecraft:ice", 0 +} + +// EncodeBlock ... +func (Ice) EncodeBlock() (string, map[string]any) { + return "minecraft:ice", nil +} diff --git a/server/block/register.go b/server/block/register.go index 084c586a1a..d2300029c5 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -67,6 +67,7 @@ func init() { world.RegisterBlock(Grass{}) world.RegisterBlock(Gravel{}) world.RegisterBlock(Honeycomb{}) + world.RegisterBlock(Ice{}) world.RegisterBlock(InfestedStone{}) world.RegisterBlock(InfestedCobblestone{}) for _, b := range allInfestedStoneBricks() { @@ -339,6 +340,7 @@ func init() { world.RegisterItem(HayBale{}) world.RegisterItem(Honeycomb{}) world.RegisterItem(Hopper{}) + world.RegisterItem(Ice{}) world.RegisterItem(InfestedStone{}) world.RegisterItem(InfestedCobblestone{}) for _, t := range StoneBricksTypes() { diff --git a/server/world/chunk/chunk.go b/server/world/chunk/chunk.go index ddee338b51..23e44c7cf5 100644 --- a/server/world/chunk/chunk.go +++ b/server/world/chunk/chunk.go @@ -150,6 +150,11 @@ func (chunk *Chunk) SkyLight(x uint8, y int16, z uint8) uint8 { return chunk.SubChunk(y).SkyLight(x&15, uint8(y&15), z&15) } +// BlockLight returns the block light level at a specific position in the chunk. +func (chunk *Chunk) BlockLight(x uint8, y int16, z uint8) uint8 { + return chunk.SubChunk(y).BlockLight(x&15, uint8(y&15), z&15) +} + // HighestLightBlocker iterates from the highest non-empty sub chunk downwards to find the Y value of the // highest block that completely blocks any light from going through. If none is found, the value returned is // the minimum height. diff --git a/server/world/tx.go b/server/world/tx.go index abb25fc689..20d207a13f 100644 --- a/server/world/tx.go +++ b/server/world/tx.go @@ -179,6 +179,14 @@ func (tx *Tx) SkyLight(pos cube.Pos) uint8 { return tx.skyLight(pos) } +// BlockLight returns the block light level at the position passed. This light +// level is only influenced by blocks that emit light, such as torches, and +// ignores the light of the sky. Like SkyLight, BlockLight loads or generates +// the chunk at the position if it is not currently loaded. +func (tx *Tx) BlockLight(pos cube.Pos) uint8 { + return tx.blockLight(pos) +} + // SetBiome sets the Biome at the position passed. If a chunk is not yet loaded // at that position, the chunk is first loaded or generated if it could not be // found in the world save. diff --git a/server/world/world.go b/server/world/world.go index 7a2ee283dc..3f340fa530 100644 --- a/server/world/world.go +++ b/server/world/world.go @@ -736,6 +736,16 @@ func (tx *Tx) skyLight(pos cube.Pos) uint8 { return tx.chunk(chunkPosFromBlockPos(pos)).SkyLight(uint8(pos[0]), int16(pos[1]), uint8(pos[2])) } +// blockLight returns the block light level at the position passed. Unlike light, this level is not +// influenced by the light of the sky. +func (tx *Tx) blockLight(pos cube.Pos) uint8 { + w := tx.World() + if pos[1] < w.ra[0] || pos[1] > w.ra[1] { + return 0 + } + return tx.chunk(chunkPosFromBlockPos(pos)).BlockLight(uint8(pos[0]), int16(pos[1]), uint8(pos[2])) +} + // Time returns the current time of the world. The time is incremented every // 1/20th of a second, unless World.StopTime() is called. func (w *World) Time() int {