diff --git a/cmd/blockhash/main.go b/cmd/blockhash/main.go index 25d90a133..d9b434ea8 100644 --- a/cmd/blockhash/main.go +++ b/cmd/blockhash/main.go @@ -256,6 +256,8 @@ func (b *hashBuilder) ftype(structName, s string, expr ast.Expr, directives map[ return "uint64(" + s + ".Uint8())", 2 case "OreType", "FireType", "DoubleTallGrassType": return "uint64(" + s + ".Uint8())", 1 + case "BambooLeafSize": + return "uint64(" + s + ".Uint8())", 2 case "Direction", "Axis": return "uint64(" + s + ")", 2 case "Face": diff --git a/server/block/bamboo.go b/server/block/bamboo.go new file mode 100644 index 000000000..9406cccbd --- /dev/null +++ b/server/block/bamboo.go @@ -0,0 +1,190 @@ +package block + +import ( + "math" + "math/rand/v2" + "time" + + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/block/model" + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/world" + "github.com/go-gl/mathgl/mgl64" +) + +// Bamboo is a versatile, fast-growing plant found primarily in jungles. +type Bamboo struct { + transparent + bass + + Ready bool + Thick bool + LeafSize BambooLeafSize +} + +// FuelInfo ... +func (b Bamboo) FuelInfo() item.FuelInfo { + return newFuelInfo(time.Millisecond * 2500) +} + +// EncodeItem ... +func (b Bamboo) EncodeItem() (name string, meta int16) { + return "minecraft:bamboo", 0 +} + +// BoneMeal ... +func (b Bamboo) BoneMeal(pos cube.Pos, tx *world.Tx) bool { + top := b.top(pos, tx) + return tx.Block(top).(Bamboo).grow(top, rand.IntN(2)+1, b.maxHeight(top), tx) +} + +// BreakInfo ... +func (b Bamboo) BreakInfo() BreakInfo { + return newBreakInfo(1, alwaysHarvestable, axeEffective, oneOf(b)) +} + +// EncodeBlock ... +func (b Bamboo) EncodeBlock() (string, map[string]any) { + thickness := "thin" + if b.Thick { + thickness = "thick" + } + return "minecraft:bamboo", map[string]any{ + "age_bit": boolByte(b.Ready), + "bamboo_leaf_size": b.LeafSize.String(), + "bamboo_stalk_thickness": thickness, + } +} + +// Model ... +func (b Bamboo) Model() world.BlockModel { + return model.Bamboo{Thick: b.Thick} +} + +// RandomTick ... +func (b Bamboo) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { + if b.Ready { + if tx.Light(pos) < 9 || !b.grow(pos, 1, b.maxHeight(pos), tx) { + b.Ready = false + tx.SetBlock(pos, b, nil) + } + } else if replaceableWith(tx, pos.Side(cube.FaceUp), b) { + b.Ready = true + tx.SetBlock(pos, b, nil) + } +} + +// NeighbourUpdateTick ... +func (b Bamboo) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { + down := tx.Block(pos.Side(cube.FaceDown)) + switch down.(type) { + case BambooSapling, Bamboo: + return + } + if supportsVegetation(b, down) { + return + } + breakBlock(b, pos, tx) +} + +// UseOnBlock ... +func (b Bamboo) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + if face == cube.FaceUp { + switch x := tx.Block(pos).(type) { + case Bamboo: + top := x.top(pos, tx) + return b.grow(top, 1, math.MaxInt, tx) + case BambooSapling: + return x.grow(pos, tx) + default: + } + } + + pos, _, used := firstReplaceable(tx, pos, face, b) + if !used { + return false + } + s := BambooSapling{} + if !supportsVegetation(s, tx.Block(pos.Sub(cube.Pos{0, 1}))) { + return false + } + place(tx, pos, s, user, ctx) + return placed(ctx) +} + +// maxHeight ... +func (b Bamboo) maxHeight(pos cube.Pos) int { + // TODO: The RNG algorithm does not match vanilla's. + return 12 + int(rand.NewPCG(uint64(pos.X()), uint64(pos.Z())).Uint64()%5) +} + +// top ... +func (b Bamboo) top(pos cube.Pos, tx *world.Tx) (top cube.Pos) { + top = pos + for { + up := top.Side(cube.FaceUp) + if _, ok := tx.Block(up).(Bamboo); !ok { + return top + } + top = up + } +} + +// grow ... +func (b Bamboo) grow(pos cube.Pos, amount int, maxHeight int, tx *world.Tx) bool { + if !replaceableWith(tx, pos.Side(cube.FaceUp), b) { + return false + } + + height := 1 + for { + if _, ok := tx.Block(pos.Sub(cube.Pos{0, height})).(Bamboo); !ok { + break + } + height++ + if height >= maxHeight { + return false + } + } + + newHeight := height + amount + stemBlock := Bamboo{Thick: b.Thick} + if newHeight >= 4 && !stemBlock.Thick { + stemBlock.Thick = true + } + smallLeavesBlock := Bamboo{Thick: stemBlock.Thick, LeafSize: BambooSizeSmallLeaves()} + bigLeavesBlock := Bamboo{Thick: stemBlock.Thick, LeafSize: BambooSizeLargeLeaves()} + + var newBlocks []world.Block + switch { + case newHeight == 2: + newBlocks = []world.Block{smallLeavesBlock} + case newHeight == 3: + newBlocks = []world.Block{smallLeavesBlock, smallLeavesBlock} + case newHeight == 4: + newBlocks = []world.Block{bigLeavesBlock, smallLeavesBlock, stemBlock, stemBlock} + case newHeight > 4: + newBlocks = []world.Block{bigLeavesBlock, bigLeavesBlock, smallLeavesBlock} + for i, mx := 0, min(amount, newHeight-len(newBlocks)); i < mx; i++ { + newBlocks = append(newBlocks, stemBlock) + } + } + + for i, b := range newBlocks { + tx.SetBlock(pos.Sub(cube.Pos{0, i - amount}), b, nil) + } + + return true +} + +// allBamboos ... +func allBamboos() (bamboos []world.Block) { + for _, thick := range []bool{false, true} { + for _, ready := range []bool{false, true} { + for _, leafSize := range BambooLeafSizes() { + bamboos = append(bamboos, Bamboo{Thick: thick, Ready: ready, LeafSize: leafSize}) + } + } + } + return +} diff --git a/server/block/bamboo_leaf_size.go b/server/block/bamboo_leaf_size.go new file mode 100644 index 000000000..1796e4961 --- /dev/null +++ b/server/block/bamboo_leaf_size.go @@ -0,0 +1,59 @@ +package block + +// BambooLeafSize represents the size of bamboo leaves. +type BambooLeafSize struct { + bamboo +} + +type bamboo uint8 + +// BambooSizeNoLeaves ... +func BambooSizeNoLeaves() BambooLeafSize { + return BambooLeafSize{0} +} + +// BambooSizeSmallLeaves ... +func BambooSizeSmallLeaves() BambooLeafSize { + return BambooLeafSize{1} +} + +// BambooSizeLargeLeaves ... +func BambooSizeLargeLeaves() BambooLeafSize { + return BambooLeafSize{2} +} + +// Uint8 ... +func (b bamboo) Uint8() uint8 { + return uint8(b) +} + +// String ... +func (b bamboo) String() string { + switch b { + case 0: + return "no_leaves" + case 1: + return "small_leaves" + case 2: + return "large_leaves" + } + panic("unknown bamboo leaf size") +} + +// Name ... +func (b bamboo) Name() string { + switch b { + case 0: + return "No Leaves" + case 1: + return "Small Leaves" + case 2: + return "Large Leaves" + } + panic("unknown bamboo leaf size") +} + +// BambooLeafSizes returns all possible bamboo leaf sizes. +func BambooLeafSizes() []BambooLeafSize { + return []BambooLeafSize{BambooSizeNoLeaves(), BambooSizeSmallLeaves(), BambooSizeLargeLeaves()} +} diff --git a/server/block/bamboo_sapling.go b/server/block/bamboo_sapling.go new file mode 100644 index 000000000..3f73cb085 --- /dev/null +++ b/server/block/bamboo_sapling.go @@ -0,0 +1,77 @@ +package block + +import ( + "math/rand/v2" + + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/world" +) + +// BambooSapling ... +type BambooSapling struct { + empty + transparent + bass + + Ready bool +} + +// BoneMeal ... +func (b BambooSapling) BoneMeal(pos cube.Pos, tx *world.Tx) bool { + return b.grow(pos, tx) +} + +// NeighbourUpdateTick ... +func (b BambooSapling) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { + down := tx.Block(pos.Side(cube.FaceDown)) + if supportsVegetation(b, down) { + return + } + breakBlock(b, pos, tx) +} + +// RandomTick ... +func (b BambooSapling) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { + if b.Ready { + if tx.Light(pos) < 9 || !b.grow(pos, tx) { + b.Ready = false + tx.SetBlock(pos, b, nil) + } + } else if replaceableWith(tx, pos.Side(cube.FaceUp), b) { + b.Ready = true + tx.SetBlock(pos, b, nil) + } +} + +// BreakInfo ... +func (b BambooSapling) BreakInfo() BreakInfo { + return newBreakInfo(0, alwaysHarvestable, axeEffective, oneOf(Bamboo{})) +} + +// HasLiquidDrops ... +func (b BambooSapling) HasLiquidDrops() bool { + return true +} + +// EncodeBlock ... +func (b BambooSapling) EncodeBlock() (string, map[string]any) { + return "minecraft:bamboo_sapling", map[string]any{"age_bit": boolByte(b.Ready)} +} + +// grow ... +func (b BambooSapling) grow(pos cube.Pos, tx *world.Tx) bool { + if !replaceableWith(tx, pos.Side(cube.FaceUp), b) { + return false + } + + tx.SetBlock(pos, Bamboo{}, nil) + tx.SetBlock(pos.Side(cube.FaceUp), Bamboo{LeafSize: BambooSizeSmallLeaves()}, nil) + return true +} + +// allBambooSaplings ... +func allBambooSaplings() (saplings []world.Block) { + saplings = append(saplings, BambooSapling{Ready: false}) + saplings = append(saplings, BambooSapling{Ready: true}) + return +} diff --git a/server/block/dirt.go b/server/block/dirt.go index 4ba7a311f..c7573fba7 100644 --- a/server/block/dirt.go +++ b/server/block/dirt.go @@ -19,7 +19,7 @@ func (d Dirt) SoilFor(block world.Block) bool { switch block.(type) { case ShortGrass, Fern, DoubleTallGrass, DeadBush: return !d.Coarse - case Flower, DoubleFlower, NetherSprouts, PinkPetals, SugarCane: + case Flower, DoubleFlower, NetherSprouts, PinkPetals, SugarCane, BambooSapling, Bamboo: return true } return false diff --git a/server/block/grass.go b/server/block/grass.go index 10dd0e1aa..33ce657ed 100644 --- a/server/block/grass.go +++ b/server/block/grass.go @@ -1,9 +1,10 @@ package block import ( + "math/rand/v2" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/world" - "math/rand/v2" ) // Grass blocks generate abundantly across the surface of the world. @@ -37,7 +38,7 @@ func init() { // SoilFor ... func (g Grass) SoilFor(block world.Block) bool { switch block.(type) { - case ShortGrass, Fern, DoubleTallGrass, Flower, DoubleFlower, NetherSprouts, PinkPetals, SugarCane, DeadBush: + case ShortGrass, Fern, DoubleTallGrass, Flower, DoubleFlower, NetherSprouts, PinkPetals, SugarCane, DeadBush, BambooSapling, Bamboo: return true } return false diff --git a/server/block/gravel.go b/server/block/gravel.go index 4326d5988..7c994b310 100644 --- a/server/block/gravel.go +++ b/server/block/gravel.go @@ -15,6 +15,15 @@ type Gravel struct { snare } +// SoilFor ... +func (g Gravel) SoilFor(block world.Block) bool { + switch block.(type) { + case BambooSapling: + return true + } + return false +} + // NeighbourUpdateTick ... func (g Gravel) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { g.fall(g, pos, tx) diff --git a/server/block/hash.go b/server/block/hash.go index cd7e6c8a1..631d9e581 100644 --- a/server/block/hash.go +++ b/server/block/hash.go @@ -10,6 +10,8 @@ const ( hashAncientDebris hashAndesite hashAnvil + hashBamboo + hashBambooSapling hashBanner hashBarrel hashBarrier @@ -227,6 +229,14 @@ func (a Anvil) Hash() (uint64, uint64) { return hashAnvil, uint64(a.Type.Uint8()) | uint64(a.Facing)<<2 } +func (b Bamboo) Hash() (uint64, uint64) { + return hashBamboo, uint64(boolByte(b.Ready)) | uint64(boolByte(b.Thick))<<1 | uint64(b.LeafSize.Uint8())<<2 +} + +func (b BambooSapling) Hash() (uint64, uint64) { + return hashBambooSapling, uint64(boolByte(b.Ready)) +} + func (b Banner) Hash() (uint64, uint64) { return hashBanner, uint64(b.Attach.Uint8()) } diff --git a/server/block/model/bamboo.go b/server/block/model/bamboo.go new file mode 100644 index 000000000..66e33b0d4 --- /dev/null +++ b/server/block/model/bamboo.go @@ -0,0 +1,77 @@ +package model + +import ( + "math" + + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/internal/mcrandom" + "github.com/df-mc/dragonfly/server/world" + "github.com/go-gl/mathgl/mgl64" +) + +// Bamboo is a model used by bamboo. +type Bamboo struct { + Thick bool +} + +// BBox ... +func (b Bamboo) BBox(pos cube.Pos, s world.BlockSource) []cube.BBox { + pixels := 2.0 + if b.Thick { + pixels = 3.0 + } + radius := (pixels / 16.0) / 2.0 + return []cube.BBox{cube.Box(0.5-radius, 0, 0.5-radius, 0.5+radius, 1, 0.5+radius).Translate(b.randomlyModifyPosition(pos))} +} + +// positionHash ... +func (Bamboo) positionHash(x, z int) uint64 { + ux := uint64(uint32(x)) + iz := int64(int32(z)) + part1 := 116129781 * iz + part2 := int64(0x2FC20F00000001*ux) >> 32 + v1 := part1 ^ part2 + calc := v1 * (42317861*v1 + 11) + temp := uint64(calc) >> 16 + signExtended := int64(int32(temp)) + return uint64(signExtended) ^ 0x6A09E667F3BCC909 +} + +// randomToFloat32 convert random long to float in [0, 1). +func (Bamboo) randomToFloat32(random uint64) float32 { + return float32(random>>40) * (1.0 / 16777216.0) +} + +// calculateOffsetValue calculate offset value with quantization to discrete steps. +func (Bamboo) calculateOffsetValue(mn, mx float32, steps int, random float32) float32 { + if mn >= mx { + return mn + } + if steps == 1 { + return (mn + mx) * 0.5 + } else if steps > 1 { + rng := mx - mn + stepSize := rng / float32(steps-1) + val := float32(steps) * random + index := float32(math.Floor(float64(val))) + return mn + index*stepSize + } + return mn + (mx-mn)*random +} + +// randomlyModifyPosition ... +func (b Bamboo) randomlyModifyPosition(pos cube.Pos) mgl64.Vec3 { + seed := b.positionHash(pos.X(), pos.Z()) + s0 := mcrandom.MixStafford13(seed) + s1 := mcrandom.MixStafford13(seed + 0x9e3779b97f4a7c15) + prng := mcrandom.NewXoroshiro128PlusPlus(s0, s1) + offsetX := b.calculateOffsetValue(-0.25, 0.25, 16, b.randomToFloat32(prng.Next())) + prng.Next() + offsetZ := b.calculateOffsetValue(-0.25, 0.25, 16, b.randomToFloat32(prng.Next())) + return mgl64.Vec3{float64(offsetX), 0, float64(offsetZ)} +} + +// FaceSolid ... +func (b Bamboo) FaceSolid(pos cube.Pos, face cube.Face, s world.BlockSource) bool { + return false +} diff --git a/server/block/mud.go b/server/block/mud.go index 13c225ee4..6f6b55e02 100644 --- a/server/block/mud.go +++ b/server/block/mud.go @@ -10,7 +10,7 @@ type Mud struct { // SoilFor ... func (Mud) SoilFor(block world.Block) bool { switch block.(type) { - case ShortGrass, Fern, DoubleTallGrass, Flower, DoubleFlower, NetherSprouts, PinkPetals, DeadBush: + case ShortGrass, Fern, DoubleTallGrass, Flower, DoubleFlower, NetherSprouts, PinkPetals, DeadBush, BambooSapling, Bamboo: return true } return false diff --git a/server/block/register.go b/server/block/register.go index 976374f9f..7b9d74fa4 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -124,6 +124,8 @@ func init() { } registerAll(allAnvils()) + registerAll(allBamboos()) + registerAll(allBambooSaplings()) registerAll(allBanners()) registerAll(allBarrels()) registerAll(allBasalt()) @@ -225,6 +227,7 @@ func init() { world.RegisterItem(AncientDebris{}) world.RegisterItem(Andesite{Polished: true}) world.RegisterItem(Andesite{}) + world.RegisterItem(Bamboo{}) world.RegisterItem(Barrel{}) world.RegisterItem(Barrier{}) world.RegisterItem(Basalt{Polished: true}) diff --git a/server/block/sand.go b/server/block/sand.go index f7ad54c69..d2fa6596f 100644 --- a/server/block/sand.go +++ b/server/block/sand.go @@ -19,7 +19,7 @@ type Sand struct { // SoilFor ... func (s Sand) SoilFor(block world.Block) bool { switch block.(type) { - case Cactus, DeadBush, SugarCane: + case Cactus, DeadBush, SugarCane, BambooSapling, Bamboo: return true } return false diff --git a/server/internal/mcrandom/mix_stafford13.go b/server/internal/mcrandom/mix_stafford13.go new file mode 100644 index 000000000..a8a074036 --- /dev/null +++ b/server/internal/mcrandom/mix_stafford13.go @@ -0,0 +1,9 @@ +package mcrandom + +// MixStafford13 implements the Stafford 13 mixing function, a bijective mixing function +// suitable for use in pseudorandom number generators. +func MixStafford13(seed uint64) uint64 { + seed = (seed ^ (seed >> 30)) * 0xBF58476D1CE4E5B9 + seed = (seed ^ (seed >> 27)) * 0x94D049BB133111EB + return seed ^ (seed >> 31) +} diff --git a/server/internal/mcrandom/xoroshiro128plusplus.go b/server/internal/mcrandom/xoroshiro128plusplus.go new file mode 100644 index 000000000..b965321b7 --- /dev/null +++ b/server/internal/mcrandom/xoroshiro128plusplus.go @@ -0,0 +1,25 @@ +package mcrandom + +import "math/bits" + +// Xoroshiro128PlusPlus is a member of the Xor-Shift-Rotate family of generators. Memory +// footprint is 128 bits and the period is (2^128)-1. +type Xoroshiro128PlusPlus struct { + seed0, seed1 uint64 +} + +// NewXoroshiro128PlusPlus ... +func NewXoroshiro128PlusPlus(seed0, seed1 uint64) *Xoroshiro128PlusPlus { + return &Xoroshiro128PlusPlus{seed0, seed1} +} + +// Next ... +func (x *Xoroshiro128PlusPlus) Next() uint64 { + s0 := x.seed0 + s1 := x.seed1 + result := bits.RotateLeft64(s0+s1, 17) + s0 + s1 ^= s0 + x.seed0 = bits.RotateLeft64(s0, 49) ^ s1 ^ (s1 << 21) + x.seed1 = bits.RotateLeft64(s1, 28) + return result +}