Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/blockhash/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand Down
190 changes: 190 additions & 0 deletions server/block/bamboo.go
Original file line number Diff line number Diff line change
@@ -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
}
59 changes: 59 additions & 0 deletions server/block/bamboo_leaf_size.go
Original file line number Diff line number Diff line change
@@ -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()}
}
77 changes: 77 additions & 0 deletions server/block/bamboo_sapling.go
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 1 addition & 1 deletion server/block/dirt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions server/block/grass.go
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions server/block/gravel.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading