From d6805533df9dbc57150da5caacd03172a567b40e Mon Sep 17 00:00:00 2001 From: MEMOxiiii <189345814+MEMOxiiii@users.noreply.github.com> Date: Sun, 16 Aug 2026 14:14:05 +0300 Subject: [PATCH] add rooted dirt and hanging roots --- server/block/hanging_roots.go | 77 +++++++++++++++++++++++++++++++++++ server/block/hash.go | 10 +++++ server/block/register.go | 4 ++ server/block/rooted_dirt.go | 61 +++++++++++++++++++++++++++ server/item/hoe.go | 16 ++++++++ 5 files changed, 168 insertions(+) create mode 100644 server/block/hanging_roots.go create mode 100644 server/block/rooted_dirt.go diff --git a/server/block/hanging_roots.go b/server/block/hanging_roots.go new file mode 100644 index 0000000000..42cf797916 --- /dev/null +++ b/server/block/hanging_roots.go @@ -0,0 +1,77 @@ +package block + +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/world" + "github.com/go-gl/mathgl/mgl64" +) + +// HangingRoots is a decorative block that hangs from the underside of blocks in lush caves. +type HangingRoots struct { + empty + replaceable + transparent + sourceWaterDisplacer +} + +// NeighbourUpdateTick ... +func (h HangingRoots) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { + if up := pos.Side(cube.FaceUp); !tx.Block(up).Model().FaceSolid(up, cube.FaceDown, tx) { + breakBlock(h, pos, tx) + } +} + +// UseOnBlock ... +func (h HangingRoots) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, _, used := firstReplaceable(tx, pos, face, h) + if !used { + return false + } + if up := pos.Side(cube.FaceUp); !tx.Block(up).Model().FaceSolid(up, cube.FaceDown, tx) { + return false + } + + place(tx, pos, h, user, ctx) + return placed(ctx) +} + +// SideClosed ... +func (HangingRoots) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { + return false +} + +// HasLiquidDrops ... +func (HangingRoots) HasLiquidDrops() bool { + return true +} + +// FlammabilityInfo ... +func (HangingRoots) FlammabilityInfo() FlammabilityInfo { + return newFlammabilityInfo(15, 100, true) +} + +// CompostChance ... +func (HangingRoots) CompostChance() float64 { + return 0.3 +} + +// BreakInfo ... +func (h HangingRoots) BreakInfo() BreakInfo { + return newBreakInfo(0, alwaysHarvestable, shearsEffective, func(t item.Tool, enchantments []item.Enchantment) []item.Stack { + if t.ToolType() == item.TypeShears || hasSilkTouch(enchantments) { + return []item.Stack{item.NewStack(h, 1)} + } + return nil + }) +} + +// EncodeItem ... +func (HangingRoots) EncodeItem() (name string, meta int16) { + return "minecraft:hanging_roots", 0 +} + +// EncodeBlock ... +func (HangingRoots) EncodeBlock() (string, map[string]any) { + return "minecraft:hanging_roots", nil +} diff --git a/server/block/hash.go b/server/block/hash.go index a46a00eb5e..259df74964 100644 --- a/server/block/hash.go +++ b/server/block/hash.go @@ -103,6 +103,7 @@ const ( hashGrass hashGravel hashGrindstone + hashHangingRoots hashHayBale hashHoneycomb hashHopper @@ -178,6 +179,7 @@ const ( hashReinforcedDeepslate hashResin hashResinBricks + hashRootedDirt hashSand hashSandstone hashSeaLantern @@ -628,6 +630,10 @@ func (g Grindstone) Hash() (uint64, uint64) { return hashGrindstone, uint64(g.Attach.Uint8()) | uint64(g.Facing)<<2 } +func (HangingRoots) Hash() (uint64, uint64) { + return hashHangingRoots, 0 +} + func (h HayBale) Hash() (uint64, uint64) { return hashHayBale, uint64(h.Axis) } @@ -928,6 +934,10 @@ func (r ResinBricks) Hash() (uint64, uint64) { return hashResinBricks, uint64(boolByte(r.Chiseled)) } +func (RootedDirt) Hash() (uint64, uint64) { + return hashRootedDirt, 0 +} + func (s Sand) Hash() (uint64, uint64) { return hashSand, uint64(boolByte(s.Red)) } diff --git a/server/block/register.go b/server/block/register.go index 084c586a1a..69f8dd44e7 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -66,6 +66,7 @@ func init() { world.RegisterBlock(Granite{}) world.RegisterBlock(Grass{}) world.RegisterBlock(Gravel{}) + world.RegisterBlock(HangingRoots{}) world.RegisterBlock(Honeycomb{}) world.RegisterBlock(InfestedStone{}) world.RegisterBlock(InfestedCobblestone{}) @@ -113,6 +114,7 @@ func init() { world.RegisterBlock(ResinBricks{Chiseled: true}) world.RegisterBlock(ResinBricks{}) world.RegisterBlock(Resin{}) + world.RegisterBlock(RootedDirt{}) world.RegisterBlock(Sand{Red: true}) world.RegisterBlock(Sand{}) world.RegisterBlock(SeaLantern{}) @@ -336,6 +338,7 @@ func init() { world.RegisterItem(Grass{}) world.RegisterItem(Gravel{}) world.RegisterItem(Grindstone{}) + world.RegisterItem(HangingRoots{}) world.RegisterItem(HayBale{}) world.RegisterItem(Honeycomb{}) world.RegisterItem(Hopper{}) @@ -404,6 +407,7 @@ func init() { world.RegisterItem(ResinBricks{Chiseled: true}) world.RegisterItem(ResinBricks{}) world.RegisterItem(Resin{}) + world.RegisterItem(RootedDirt{}) world.RegisterItem(Sand{Red: true}) world.RegisterItem(Sand{}) world.RegisterItem(SeaLantern{}) diff --git a/server/block/rooted_dirt.go b/server/block/rooted_dirt.go new file mode 100644 index 0000000000..c920bde65a --- /dev/null +++ b/server/block/rooted_dirt.go @@ -0,0 +1,61 @@ +package block + +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/world" +) + +// RootedDirt is a variant of dirt that generates in lush caves underneath azalea trees. +type RootedDirt struct { + solid +} + +// SoilFor ... +func (r RootedDirt) SoilFor(block world.Block) bool { + switch block.(type) { + case ShortGrass, Fern, DoubleTallGrass, Flower, DoubleFlower, NetherSprouts, PinkPetals, SugarCane, DeadBush, BambooSapling, Bamboo: + return true + } + return false +} + +// BoneMeal ... +func (r RootedDirt) BoneMeal(pos cube.Pos, tx *world.Tx) item.BoneMealResult { + below := pos.Side(cube.FaceDown) + if _, ok := tx.Block(below).(Air); !ok { + return item.BoneMealResultNone + } + tx.SetBlock(below, HangingRoots{}, nil) + return item.BoneMealResultSmall +} + +// Till returns dirt, as using a hoe on rooted dirt strips its roots instead of tilling it into farmland. +func (RootedDirt) Till() (world.Block, bool) { + return Dirt{}, true +} + +// TillDrops ... +func (RootedDirt) TillDrops() []item.Stack { + return []item.Stack{item.NewStack(HangingRoots{}, 1)} +} + +// Shovel ... +func (RootedDirt) Shovel() (world.Block, bool) { + return DirtPath{}, true +} + +// BreakInfo ... +func (r RootedDirt) BreakInfo() BreakInfo { + return newBreakInfo(0.5, alwaysHarvestable, shovelEffective, oneOf(r)) +} + +// EncodeItem ... +func (RootedDirt) EncodeItem() (name string, meta int16) { + return "minecraft:dirt_with_roots", 0 +} + +// EncodeBlock ... +func (RootedDirt) EncodeBlock() (string, map[string]any) { + return "minecraft:dirt_with_roots", nil +} diff --git a/server/item/hoe.go b/server/item/hoe.go index 057190249f..75783ccda1 100644 --- a/server/item/hoe.go +++ b/server/item/hoe.go @@ -28,6 +28,16 @@ func (h Hoe) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx } tx.SetBlock(pos, res, nil) tx.PlaySound(pos.Vec3(), sound.ItemUseOn{Block: res}) + if d, ok := b.(tillDropper); ok { + dir := pos.Side(face).Vec3().Sub(pos.Vec3()) + vel := dir.Mul(0.1) + vel[1] += 0.1 + create := tx.World().EntityRegistry().Config().Item + for _, drop := range d.TillDrops() { + opts := world.EntitySpawnOpts{Position: pos.Vec3Centre().Add(dir.Mul(0.625)), Velocity: vel} + tx.AddEntity(create(opts, drop)) + } + } ctx.DamageItem(1) return true } @@ -42,6 +52,12 @@ type tillable interface { Till() (world.Block, bool) } +// tillDropper represents a block that drops items when a hoe is used on it. +type tillDropper interface { + // TillDrops returns the items dropped when a hoe is used on the block. + TillDrops() []Stack +} + // MaxCount ... func (h Hoe) MaxCount() int { return 1