From d2ba2d1293b058dc4e1cb04c2c9fdf8b15d9b56e Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 26 Dec 2024 01:51:56 -0800 Subject: [PATCH 01/33] init transaction changes --- cmd/blockhash/main.go | 4 + server/block/button.go | 151 ++++++++++ server/block/button_type.go | 69 +++++ server/block/hash.go | 55 ++++ server/block/hopper.go | 11 + server/block/iron_door.go | 157 +++++++++++ server/block/iron_trapdoor.go | 102 +++++++ server/block/lever.go | 133 +++++++++ server/block/pressure_plate.go | 161 +++++++++++ server/block/prressure_plate_type.go | 87 ++++++ server/block/redstone.go | 394 +++++++++++++++++++++++++++ server/block/redstone_block.go | 58 ++++ server/block/redstone_lamp.go | 77 ++++++ server/block/redstone_ore.go | 39 +++ server/block/redstone_torch.go | 173 ++++++++++++ server/block/redstone_wire.go | 190 +++++++++++++ server/block/register.go | 26 ++ server/block/target.go | 100 +++++++ server/block/tnt.go | 12 + server/block/wood_door.go | 25 ++ server/block/wood_fence_gate.go | 25 ++ server/block/wood_trapdoor.go | 25 ++ server/cmd/parameter.go | 2 +- server/entity/projectile.go | 17 ++ server/session/world.go | 4 + server/world/block.go | 16 ++ server/world/conf.go | 2 +- server/world/entity.go | 4 +- server/world/sound/block.go | 6 + server/world/tx.go | 27 ++ 30 files changed, 2148 insertions(+), 4 deletions(-) create mode 100644 server/block/button.go create mode 100644 server/block/button_type.go create mode 100644 server/block/iron_door.go create mode 100644 server/block/iron_trapdoor.go create mode 100644 server/block/lever.go create mode 100644 server/block/pressure_plate.go create mode 100644 server/block/prressure_plate_type.go create mode 100644 server/block/redstone.go create mode 100644 server/block/redstone_block.go create mode 100644 server/block/redstone_lamp.go create mode 100644 server/block/redstone_ore.go create mode 100644 server/block/redstone_torch.go create mode 100644 server/block/redstone_wire.go create mode 100644 server/block/target.go diff --git a/cmd/blockhash/main.go b/cmd/blockhash/main.go index 8244acfdf..b32248bcf 100644 --- a/cmd/blockhash/main.go +++ b/cmd/blockhash/main.go @@ -235,6 +235,10 @@ func (b *hashBuilder) ftype(structName, s string, expr ast.Expr, directives map[ return "uint64(" + s + ")", 8 case "Block": return "world.BlockHash(" + s + ")", 32 + case "ButtonType": + return "uint64(" + s + ".Uint8())", 6 + case "PressurePlateType": + return "uint64(" + s + ".Uint8())", 8 case "Attachment": if _, ok := directives["facing_only"]; ok { log.Println("Found directive: 'facing_only'") diff --git a/server/block/button.go b/server/block/button.go new file mode 100644 index 000000000..b06a62c61 --- /dev/null +++ b/server/block/button.go @@ -0,0 +1,151 @@ +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/df-mc/dragonfly/server/world/sound" + "github.com/go-gl/mathgl/mgl64" + "math/rand" + "time" +) + +// Button is a non-solid block that can provide temporary redstone power. +type Button struct { + empty + transparent + sourceWaterDisplacer + + // Type is the type of the button. + Type ButtonType + // Facing is the face of the block that the button is on. + Facing cube.Face + // Pressed is whether the button is pressed or not. + Pressed bool +} + +// FuelInfo ... +func (b Button) FuelInfo() item.FuelInfo { + if b.Type == StoneButton() || b.Type == PolishedBlackstoneButton() { + return item.FuelInfo{} + } + return newFuelInfo(time.Second * 5) +} + +// Source ... +func (b Button) RedstoneSource() bool { + return true +} + +// WeakPower ... +func (b Button) WeakPower(cube.Pos, cube.Face, *world.Tx, bool) int { + if b.Pressed { + return 15 + } + return 0 +} + +// StrongPower ... +func (b Button) StrongPower(_ cube.Pos, face cube.Face, _ *world.Tx, _ bool) int { + if b.Pressed && b.Facing == face { + return 15 + } + return 0 +} + +// ScheduledTick ... +func (b Button) ScheduledTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { + if !b.Pressed { + return + } + b.Pressed = false + tx.SetBlock(pos, b, nil) + tx.PlaySound(pos.Vec3Centre(), sound.PowerOff{}) + updateDirectionalRedstone(pos, tx, b.Facing.Opposite()) +} + +// NeighbourUpdateTick ... +func (b Button) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { + if !tx.Block(pos.Side(b.Facing.Opposite())).Model().FaceSolid(pos.Side(b.Facing.Opposite()), b.Facing, tx) { + tx.SetBlock(pos, nil, nil) + dropItem(tx, item.NewStack(b, 1), pos.Vec3Centre()) + updateDirectionalRedstone(pos, tx, b.Facing.Opposite()) + } +} + +// Activate ... +func (b Button) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, _ item.User, _ *item.UseContext) bool { + return b.Click(pos, tx) +} + +// Click ... +func (b Button) Click(pos cube.Pos, tx *world.Tx) bool { + if b.Pressed { + return true + } + b.Pressed = true + tx.SetBlock(pos, b, nil) + tx.PlaySound(pos.Vec3Centre(), sound.PowerOn{}) + updateDirectionalRedstone(pos, tx, b.Facing.Opposite()) + + delay := time.Millisecond * 1500 + if b.Type == StoneButton() || b.Type == PolishedBlackstoneButton() { + delay = time.Millisecond * 1000 + } + tx.ScheduleBlockUpdate(pos, b, delay) + return true +} + +// UseOnBlock ... +func (b Button) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, face, used := firstReplaceable(tx, pos, face, b) + if !used { + return false + } + if !tx.Block(pos.Side(face.Opposite())).Model().FaceSolid(pos.Side(face.Opposite()), face, tx) { + return false + } + + b.Facing = face + place(tx, pos, b, user, ctx) + return placed(ctx) +} + +// BreakInfo ... +func (b Button) BreakInfo() BreakInfo { + harvestTool := alwaysHarvestable + effectiveTool := axeEffective + if b.Type == StoneButton() || b.Type == PolishedBlackstoneButton() { + harvestTool = pickaxeHarvestable + effectiveTool = pickaxeEffective + } + return newBreakInfo(0.5, harvestTool, effectiveTool, oneOf(b)).withBreakHandler(func(pos cube.Pos, tx *world.Tx, _ item.User) { + updateDirectionalRedstone(pos, tx, b.Facing.Opposite()) + }) +} + +// SideClosed ... +func (b Button) SideClosed(cube.Pos, cube.Pos, *world.World) bool { + return false +} + +// EncodeItem ... +func (b Button) EncodeItem() (name string, meta int16) { + return "minecraft:" + b.Type.String() + "_button", 0 +} + +// EncodeBlock ... +func (b Button) EncodeBlock() (string, map[string]any) { + return "minecraft:" + b.Type.String() + "_button", map[string]any{"facing_direction": int32(b.Facing), "button_pressed_bit": b.Pressed} +} + +// allButtons ... +func allButtons() (buttons []world.Block) { + for _, w := range ButtonTypes() { + for _, f := range cube.Faces() { + buttons = append(buttons, Button{Type: w, Facing: f}) + buttons = append(buttons, Button{Type: w, Facing: f, Pressed: true}) + } + } + return +} diff --git a/server/block/button_type.go b/server/block/button_type.go new file mode 100644 index 000000000..05771a699 --- /dev/null +++ b/server/block/button_type.go @@ -0,0 +1,69 @@ +package block + +// ButtonType represents a type of button. +type ButtonType struct { + button + + // Wood is the type of wood of the button. + wood WoodType +} + +type button uint8 + +// WoodButton returns the wood button type. +func WoodButton(w WoodType) ButtonType { + return ButtonType{0, w} +} + +// StoneButton returns the stone button type. +func StoneButton() ButtonType { + return ButtonType{button: 1} +} + +// PolishedBlackstoneButton returns the polished blackstone button type. +func PolishedBlackstoneButton() ButtonType { + return ButtonType{button: 2} +} + +// Uint8 ... +func (b ButtonType) Uint8() uint8 { + return b.wood.Uint8() | uint8(b.button)<<4 +} + +// Name ... +func (b ButtonType) Name() string { + switch b.button { + case 0: + return b.wood.Name() + " Button" + case 1: + return "Stone Button" + case 2: + return "Polished Blackstone Button" + } + panic("unknown button type") +} + +// String ... +func (b ButtonType) String() string { + switch b.button { + case 0: + if b.wood == OakWood() { + return "wooden" + } + return b.wood.String() + case 1: + return "stone" + case 2: + return "polished_blackstone" + } + panic("unknown button type") +} + +// ButtonTypes ... +func ButtonTypes() []ButtonType { + types := []ButtonType{StoneButton(), PolishedBlackstoneButton()} + for _, w := range WoodTypes() { + types = append(types, WoodButton(w)) + } + return types +} diff --git a/server/block/hash.go b/server/block/hash.go index c010760ab..b71872d30 100644 --- a/server/block/hash.go +++ b/server/block/hash.go @@ -24,6 +24,7 @@ const ( hashBookshelf hashBrewingStand hashBricks + hashButton hashCactus hashCake hashCalcite @@ -94,7 +95,9 @@ const ( hashInvisibleBedrock hashIron hashIronBars + hashIronDoor hashIronOre + hashIronTrapDoor hashItemFrame hashJukebox hashKelp @@ -105,6 +108,7 @@ const ( hashLava hashLeaves hashLectern + hashLever hashLight hashLitPumpkin hashLog @@ -134,6 +138,7 @@ const ( hashPolishedBlackstoneBrick hashPolishedTuff hashPotato + hashPressurePlate hashPrismarine hashPumpkin hashPumpkinSeeds @@ -145,6 +150,11 @@ const ( hashRawCopper hashRawGold hashRawIron + hashRedstoneBlock + hashRedstoneLamp + hashRedstoneOre + hashRedstoneTorch + hashRedstoneWire hashReinforcedDeepslate hashResin hashResinBricks @@ -173,6 +183,7 @@ const ( hashStonecutter hashSugarCane hashTNT + hashTarget hashTerracotta hashTorch hashTuff @@ -274,6 +285,10 @@ func (Bricks) Hash() (uint64, uint64) { return hashBricks, 0 } +func (b Button) Hash() (uint64, uint64) { + return hashButton, uint64(b.Type.Uint8()) | uint64(b.Facing)<<6 | uint64(boolByte(b.Pressed))<<9 +} + func (c Cactus) Hash() (uint64, uint64) { return hashCactus, uint64(c.Age) } @@ -554,10 +569,18 @@ func (IronBars) Hash() (uint64, uint64) { return hashIronBars, 0 } +func (d IronDoor) Hash() (uint64, uint64) { + return hashIronDoor, uint64(d.Facing) | uint64(boolByte(d.Open))<<2 | uint64(boolByte(d.Top))<<3 | uint64(boolByte(d.Right))<<4 +} + func (i IronOre) Hash() (uint64, uint64) { return hashIronOre, uint64(i.Type.Uint8()) } +func (t IronTrapDoor) Hash() (uint64, uint64) { + return hashIronTrapDoor, uint64(t.Facing) | uint64(boolByte(t.Open))<<2 | uint64(boolByte(t.Top))<<3 +} + func (i ItemFrame) Hash() (uint64, uint64) { return hashItemFrame, uint64(i.Facing) | uint64(boolByte(i.Glowing))<<3 } @@ -598,6 +621,10 @@ func (l Lectern) Hash() (uint64, uint64) { return hashLectern, uint64(l.Facing) } +func (l Lever) Hash() (uint64, uint64) { + return hashLever, uint64(boolByte(l.Powered)) | uint64(l.Facing)<<1 | uint64(l.Direction)<<4 +} + func (l Light) Hash() (uint64, uint64) { return hashLight, uint64(l.Level) } @@ -714,6 +741,10 @@ func (p Potato) Hash() (uint64, uint64) { return hashPotato, uint64(p.Growth) } +func (p PressurePlate) Hash() (uint64, uint64) { + return hashPressurePlate, uint64(p.Type.Uint8()) | uint64(p.Power)<<8 +} + func (p Prismarine) Hash() (uint64, uint64) { return hashPrismarine, uint64(p.Type.Uint8()) } @@ -758,6 +789,26 @@ func (RawIron) Hash() (uint64, uint64) { return hashRawIron, 0 } +func (RedstoneBlock) Hash() (uint64, uint64) { + return hashRedstoneBlock, 0 +} + +func (l RedstoneLamp) Hash() (uint64, uint64) { + return hashRedstoneLamp, uint64(boolByte(l.Lit)) +} + +func (c RedstoneOre) Hash() (uint64, uint64) { + return hashRedstoneOre, uint64(c.Type.Uint8()) +} + +func (t RedstoneTorch) Hash() (uint64, uint64) { + return hashRedstoneTorch, uint64(t.Facing) | uint64(boolByte(t.Lit))<<3 +} + +func (r RedstoneWire) Hash() (uint64, uint64) { + return hashRedstoneWire, uint64(r.Power) +} + func (ReinforcedDeepslate) Hash() (uint64, uint64) { return hashReinforcedDeepslate, 0 } @@ -870,6 +921,10 @@ func (TNT) Hash() (uint64, uint64) { return hashTNT, 0 } +func (Target) Hash() (uint64, uint64) { + return hashTarget, 0 +} + func (Terracotta) Hash() (uint64, uint64) { return hashTerracotta, 0 } diff --git a/server/block/hopper.go b/server/block/hopper.go index feba5cbe5..a3727a0a0 100644 --- a/server/block/hopper.go +++ b/server/block/hopper.go @@ -128,6 +128,17 @@ func (h Hopper) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world return placed(ctx) } +// RedstoneUpdate ... +func (h Hopper) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { + powered := receivedRedstonePower(pos, tx) + if powered == h.Powered { + return + } + + h.Powered = powered + tx.SetBlock(pos, h, nil) +} + // Tick ... func (h Hopper) Tick(currentTick int64, pos cube.Pos, tx *world.Tx) { h.TransferCooldown-- diff --git a/server/block/iron_door.go b/server/block/iron_door.go new file mode 100644 index 000000000..82c01e1f9 --- /dev/null +++ b/server/block/iron_door.go @@ -0,0 +1,157 @@ +package block + +import ( + "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/df-mc/dragonfly/server/world/particle" + "github.com/df-mc/dragonfly/server/world/sound" + "github.com/go-gl/mathgl/mgl64" + "math/rand" + "time" +) + +// IronDoor is a variant of the door made of iron that can only be opened using redstone. +type IronDoor struct { + transparent + bass + sourceWaterDisplacer + + // Facing is the direction the door is facing. + Facing cube.Direction + // Open is whether the door is open. + Open bool + // Top is whether the block is the top or bottom half of a door + Top bool + // Right is whether the door hinge is on the right side + Right bool +} + +// Model ... +func (d IronDoor) Model() world.BlockModel { + return model.Door{Facing: d.Facing, Open: d.Open, Right: d.Right} +} + +// NeighbourUpdateTick ... +func (d IronDoor) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { + if d.Top { + if _, ok := tx.Block(pos.Side(cube.FaceDown)).(IronDoor); !ok { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) + } + return + } + if solid := tx.Block(pos.Side(cube.FaceDown)).Model().FaceSolid(pos.Side(cube.FaceDown), cube.FaceUp, tx); !solid { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) + } else if _, ok := tx.Block(pos.Side(cube.FaceUp)).(IronDoor); !ok { + tx.SetBlock(pos, nil, nil) + tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) + } +} + +// UseOnBlock handles the directional placing of doors +func (d IronDoor) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + if face != cube.FaceUp { + // Doors can only be placed when clicking the top face. + return false + } + below := pos + pos = pos.Side(cube.FaceUp) + if !replaceableWith(tx, pos, d) || !replaceableWith(tx, pos.Side(cube.FaceUp), d) { + return false + } + if !tx.Block(below).Model().FaceSolid(below, cube.FaceUp, tx) { + return false + } + d.Facing = user.Rotation().Direction() + left := tx.Block(pos.Side(d.Facing.RotateLeft().Face())) + right := tx.Block(pos.Side(d.Facing.RotateRight().Face())) + if _, ok := left.(IronDoor); ok { + d.Right = true + } + // The side the door hinge is on can be affected by the blocks to the left and right of the door. In particular, + // opaque blocks on the right side of the door with transparent blocks on the left side result in a right sided + // door hinge. + if diffuser, ok := right.(LightDiffuser); !ok || diffuser.LightDiffusionLevel() != 0 { + if diffuser, ok := left.(LightDiffuser); ok && diffuser.LightDiffusionLevel() == 0 { + d.Right = true + } + } + + ctx.IgnoreBBox = true + place(tx, pos, d, user, ctx) + place(tx, pos.Side(cube.FaceUp), IronDoor{Facing: d.Facing, Top: true, Right: d.Right}, user, ctx) + ctx.SubtractFromCount(1) + return placed(ctx) +} + +// BreakInfo ... +func (d IronDoor) BreakInfo() BreakInfo { + return newBreakInfo(5, pickaxeHarvestable, pickaxeEffective, oneOf(d)) +} + +// SideClosed ... +func (d IronDoor) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { + return false +} + +// RedstoneUpdate ... +func (d IronDoor) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { + if d.Open == receivedRedstonePower(pos, tx) { + return + } + if !d.Open { + d.Open = true + tx.PlaySound(pos.Vec3Centre(), sound.DoorOpen{Block: d}) + tx.SetBlock(pos, d, &world.SetOpts{DisableBlockUpdates: true}) + } else { + tx.ScheduleBlockUpdate(pos, d, time.Millisecond*50) + } +} + +// ScheduledTick ... +func (d IronDoor) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { + if receivedRedstonePower(pos, tx) { + return + } + d.Open = false + tx.PlaySound(pos.Vec3Centre(), sound.DoorClose{Block: d}) + tx.SetBlock(pos, d, &world.SetOpts{DisableBlockUpdates: true}) +} + +// EncodeItem ... +func (d IronDoor) EncodeItem() (name string, meta int16) { + return "minecraft:iron_door", 0 +} + +// EncodeBlock ... +func (d IronDoor) EncodeBlock() (name string, properties map[string]any) { + direction := 3 + switch d.Facing { + case cube.South: + direction = 1 + case cube.West: + direction = 2 + case cube.East: + direction = 0 + } + + return "minecraft:iron_door", map[string]any{"direction": int32(direction), "door_hinge_bit": d.Right, "open_bit": d.Open, "upper_block_bit": d.Top} +} + +// allIronDoors returns a list of all door types +func allIronDoors() (doors []world.Block) { + for i := cube.Direction(0); i <= 3; i++ { + doors = append(doors, IronDoor{Facing: i, Open: false, Top: false, Right: false}) + doors = append(doors, IronDoor{Facing: i, Open: false, Top: true, Right: false}) + doors = append(doors, IronDoor{Facing: i, Open: true, Top: true, Right: false}) + doors = append(doors, IronDoor{Facing: i, Open: true, Top: false, Right: false}) + doors = append(doors, IronDoor{Facing: i, Open: false, Top: false, Right: true}) + doors = append(doors, IronDoor{Facing: i, Open: false, Top: true, Right: true}) + doors = append(doors, IronDoor{Facing: i, Open: true, Top: true, Right: true}) + doors = append(doors, IronDoor{Facing: i, Open: true, Top: false, Right: true}) + } + return +} diff --git a/server/block/iron_trapdoor.go b/server/block/iron_trapdoor.go new file mode 100644 index 000000000..261cee188 --- /dev/null +++ b/server/block/iron_trapdoor.go @@ -0,0 +1,102 @@ +package block + +import ( + "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/df-mc/dragonfly/server/world/sound" + "github.com/go-gl/mathgl/mgl64" + "math" + "math/rand" + "time" +) + +// IronTrapDoor is a solid, transparent block that can be used as an openable 1×1 barrier +// can only be opened by using redstone. +type IronTrapDoor struct { + transparent + bass + sourceWaterDisplacer + + // Facing is the direction the trapdoor is facing. + Facing cube.Direction + // Open is whether the trapdoor is open. + Open bool + // Top is whether the trapdoor occupies the top or bottom part of a block. + Top bool +} + +// Model ... +func (t IronTrapDoor) Model() world.BlockModel { + return model.Trapdoor{Facing: t.Facing, Top: t.Top, Open: t.Open} +} + +// UseOnBlock handles the directional placing of trapdoors and makes sure they are properly placed upside down +// when needed. +func (t IronTrapDoor) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, face, used := firstReplaceable(tx, pos, face, t) + if !used { + return false + } + t.Facing = user.Rotation().Direction().Opposite() + t.Top = (clickPos.Y() > 0.5 && face != cube.FaceUp) || face == cube.FaceDown + + place(tx, pos, t, user, ctx) + return placed(ctx) +} + +// BreakInfo ... +func (t IronTrapDoor) BreakInfo() BreakInfo { + return newBreakInfo(5, pickaxeHarvestable, pickaxeEffective, oneOf(t)) +} + +// SideClosed ... +func (t IronTrapDoor) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { + return false +} + +// RedstoneUpdate ... +func (t IronTrapDoor) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { + if t.Open == receivedRedstonePower(pos, tx) { + return + } + if !t.Open { + t.Open = true + tx.PlaySound(pos.Vec3Centre(), sound.TrapdoorOpen{Block: t}) + tx.SetBlock(pos, t, &world.SetOpts{DisableBlockUpdates: true}) + } else { + tx.ScheduleBlockUpdate(pos, t, time.Millisecond*50) + } +} + +// ScheduledTick ... +func (t IronTrapDoor) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { + if receivedRedstonePower(pos, tx) { + return + } + t.Open = false + tx.PlaySound(pos.Vec3Centre(), sound.TrapdoorClose{Block: t}) + tx.SetBlock(pos, t, &world.SetOpts{DisableBlockUpdates: true}) +} + +// EncodeItem ... +func (t IronTrapDoor) EncodeItem() (name string, meta int16) { + return "minecraft:iron_trapdoor", 0 +} + +// EncodeBlock ... +func (t IronTrapDoor) EncodeBlock() (name string, properties map[string]any) { + return "minecraft:iron_trapdoor", map[string]any{"direction": int32(math.Abs(float64(t.Facing) - 3)), "open_bit": t.Open, "upside_down_bit": t.Top} +} + +// allIronTrapdoors returns a list of all trapdoor types +func allIronTrapdoors() (trapdoors []world.Block) { + for i := cube.Direction(0); i <= 3; i++ { + trapdoors = append(trapdoors, IronTrapDoor{Facing: i, Open: false, Top: false}) + trapdoors = append(trapdoors, IronTrapDoor{Facing: i, Open: false, Top: true}) + trapdoors = append(trapdoors, IronTrapDoor{Facing: i, Open: true, Top: true}) + trapdoors = append(trapdoors, IronTrapDoor{Facing: i, Open: true, Top: false}) + } + return +} diff --git a/server/block/lever.go b/server/block/lever.go new file mode 100644 index 000000000..88348bf2b --- /dev/null +++ b/server/block/lever.go @@ -0,0 +1,133 @@ +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/df-mc/dragonfly/server/world/sound" + "github.com/go-gl/mathgl/mgl64" +) + +// Lever is a non-solid block that can provide switchable redstone power. +type Lever struct { + empty + transparent + flowingWaterDisplacer + + // Powered is if the lever is switched on. + Powered bool + // Facing is the face of the block that the lever is attached to. + Facing cube.Face + // Direction is the direction the lever is pointing. This is only used for levers that are attached on up or down + // faces. + // TODO: Better handle lever direction on up or down faces—using a `cube.Axis` results in a default `Lever` with an + // axis `Y` and a face `Down` which does not map to an existing block state. + Direction cube.Direction +} + +// Source ... +func (l Lever) RedstoneSource() bool { + return true +} + +// WeakPower ... +func (l Lever) WeakPower(cube.Pos, cube.Face, *world.Tx, bool) int { + if l.Powered { + return 15 + } + return 0 +} + +// StrongPower ... +func (l Lever) StrongPower(_ cube.Pos, face cube.Face, _ *world.Tx, _ bool) int { + if l.Powered && l.Facing == face { + return 15 + } + return 0 +} + +// SideClosed ... +func (l Lever) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { + return false +} + +// NeighbourUpdateTick ... +func (l Lever) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { + if !tx.Block(pos.Side(l.Facing.Opposite())).Model().FaceSolid(pos.Side(l.Facing.Opposite()), l.Facing, tx) { + tx.SetBlock(pos, nil, nil) + dropItem(tx, item.NewStack(l, 1), pos.Vec3Centre()) + updateDirectionalRedstone(pos, tx, l.Facing.Opposite()) + } +} + +// UseOnBlock ... +func (l Lever) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, face, used := firstReplaceable(tx, pos, face, l) + if !used { + return false + } + if !tx.Block(pos.Side(face.Opposite())).Model().FaceSolid(pos.Side(face.Opposite()), face, tx) { + return false + } + + l.Facing = face + l.Direction = cube.North + if face.Axis() == cube.Y && user.Rotation().Direction().Face().Axis() == cube.X { + l.Direction = cube.West + } + place(tx, pos, l, user, ctx) + return placed(ctx) +} + +// Activate ... +func (l Lever) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, _ item.User, _ *item.UseContext) bool { + l.Powered = !l.Powered + tx.SetBlock(pos, l, nil) + if l.Powered { + tx.PlaySound(pos.Vec3Centre(), sound.PowerOn{}) + } else { + tx.PlaySound(pos.Vec3Centre(), sound.PowerOff{}) + } + updateDirectionalRedstone(pos, tx, l.Facing.Opposite()) + return true +} + +// BreakInfo ... +func (l Lever) BreakInfo() BreakInfo { + return newBreakInfo(0.5, alwaysHarvestable, nothingEffective, oneOf(l)).withBreakHandler(func(pos cube.Pos, tx *world.Tx, _ item.User) { + updateDirectionalRedstone(pos, tx, l.Facing.Opposite()) + }) +} + +// EncodeItem ... +func (l Lever) EncodeItem() (name string, meta int16) { + return "minecraft:lever", 0 +} + +// EncodeBlock ... +func (l Lever) EncodeBlock() (string, map[string]any) { + direction := l.Facing.String() + if l.Facing == cube.FaceDown || l.Facing == cube.FaceUp { + axis := "east_west" + if l.Direction == cube.North { + axis = "north_south" + } + direction += "_" + axis + } + return "minecraft:lever", map[string]any{"open_bit": l.Powered, "lever_direction": direction} +} + +// allLevers ... +func allLevers() (all []world.Block) { + f := func(facing cube.Face, direction cube.Direction) { + all = append(all, Lever{Facing: facing, Direction: direction}) + all = append(all, Lever{Facing: facing, Direction: direction, Powered: true}) + } + for _, facing := range cube.Faces() { + f(facing, cube.North) + if facing == cube.FaceDown || facing == cube.FaceUp { + f(facing, cube.West) + } + } + return +} diff --git a/server/block/pressure_plate.go b/server/block/pressure_plate.go new file mode 100644 index 000000000..23557a3f6 --- /dev/null +++ b/server/block/pressure_plate.go @@ -0,0 +1,161 @@ +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/df-mc/dragonfly/server/world/sound" + "github.com/go-gl/mathgl/mgl64" + "math/rand" + "time" +) + +// PressurePlate is a non-solid block that produces a redstone signal when stood on by an entity +type PressurePlate struct { + sourceWaterDisplacer + transparent + empty + + // Type is the type of the pressure plate. + Type PressurePlateType + // Power specifies the redstone power level currently being produced by the pressure plate. + Power int +} + +// BreakInfo ... +func (p PressurePlate) BreakInfo() BreakInfo { + harvestTool := alwaysHarvestable + effectiveTool := axeEffective + if p.Type == StonePressurePlate() || p.Type == PolishedBlackstonePressurePlate() || p.Type == HeavyWeightedPressurePlate() || p.Type == LightWeightedPressurePlate() { + harvestTool = pickaxeHarvestable + effectiveTool = pickaxeEffective + } + return newBreakInfo(0.5, harvestTool, effectiveTool, oneOf(p)).withBreakHandler(func(pos cube.Pos, tx *world.Tx, _ item.User) { + updateAroundRedstone(pos, tx) + }) +} + +// Source ... +func (p PressurePlate) RedstoneSource() bool { + return true +} + +// WeakPower ... +func (p PressurePlate) WeakPower(cube.Pos, cube.Face, *world.Tx, bool) int { + return p.Power +} + +// StrongPower ... +func (p PressurePlate) StrongPower(pos cube.Pos, face cube.Face, w *world.Tx, redstone bool) int { + return p.Power +} + +func (p PressurePlate) EntityInside(pos cube.Pos, tx *world.Tx, e world.Entity) { + var power int + + entitySeq := tx.EntitiesWithin(cube.Box( + float64(pos.X()), float64(pos.Y()), float64(pos.Z()), + float64(pos.X()+1), float64(pos.Y()+1), float64(pos.Z()+1), + )) + + entityCount := 0 + for range entitySeq { + entityCount++ + } + + switch p.Type { + case StonePressurePlate(), PolishedBlackstonePressurePlate(): + //TODO: add a check if its a living entity currently not possible due to import cycle + power = 15 + case HeavyWeightedPressurePlate(): + power = min(entityCount, 15) + case LightWeightedPressurePlate(): + power = min((entityCount+9)/10, 15) + default: + power = 15 + } + + if power > 0 && power != p.Power { + p.Power = power + tx.PlaySound(pos.Vec3Centre(), sound.PowerOn{}) + tx.SetBlock(pos, p, &world.SetOpts{DisableBlockUpdates: false}) + updateAroundRedstone(pos, tx) + } + + tx.ScheduleBlockUpdate(pos, p, time.Millisecond*50) +} + +// ScheduledTick ... +func (p PressurePlate) ScheduledTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { + if p.Power == 0 { + return + } + + entitySeq := tx.EntitiesWithin(cube.Box( + float64(pos.X()), float64(pos.Y()), float64(pos.Z()), + float64(pos.X()+1), float64(pos.Y()+1), float64(pos.Z()+1), + )) + + entityCount := 0 + for range entitySeq { + entityCount++ + } + + if entityCount != 0 { + return + } + + p.Power = 0 + tx.SetBlock(pos, p, &world.SetOpts{DisableBlockUpdates: false}) + tx.PlaySound(pos.Vec3Centre(), sound.PowerOff{}) + updateAroundRedstone(pos, tx) +} + +// NeighbourUpdateTick ... +func (p PressurePlate) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { + if d, ok := tx.Block(pos.Side(cube.FaceDown)).(LightDiffuser); ok && d.LightDiffusionLevel() == 0 { + tx.SetBlock(pos, nil, nil) + dropItem(tx, item.NewStack(p, 1), pos.Vec3Centre()) + } +} + +// UseOnBlock ... +func (p PressurePlate) 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, p) + if !used { + return false + } + + belowPos := pos.Side(cube.FaceDown) + if !tx.Block(belowPos).Model().FaceSolid(belowPos, cube.FaceUp, tx) { + return false + } + + place(tx, pos, p, user, ctx) + return placed(ctx) +} + +// SideClosed ... +func (p PressurePlate) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { + return false +} + +// EncodeItem ... +func (p PressurePlate) EncodeItem() (name string, meta int16) { + return "minecraft:" + p.Type.String() + "_pressure_plate", 0 +} + +// EncodeBlock ... +func (p PressurePlate) EncodeBlock() (string, map[string]any) { + return "minecraft:" + p.Type.String() + "_pressure_plate", map[string]any{"redstone_signal": int32(p.Power)} +} + +// allPressurePlates ... +func allPressurePlates() (pressureplates []world.Block) { + for _, w := range PressurePlateTypes() { + for i := 0; i <= 15; i++ { + pressureplates = append(pressureplates, PressurePlate{Type: w, Power: i}) + } + } + return +} diff --git a/server/block/prressure_plate_type.go b/server/block/prressure_plate_type.go new file mode 100644 index 000000000..8482748b2 --- /dev/null +++ b/server/block/prressure_plate_type.go @@ -0,0 +1,87 @@ +package block + +// PressurePlateType represents a type of pressure plate. +type PressurePlateType struct { + pressureplate + + // Wood is the type of wood of the pressure plate. + wood WoodType +} + +type pressureplate uint8 + +// WoodPressurePlate returns the wood button type. +func WoodPressurePlate(w WoodType) PressurePlateType { + return PressurePlateType{0, w} +} + +// StonePressurePlate returns the stone pressure plate type. +func StonePressurePlate() PressurePlateType { + return PressurePlateType{pressureplate: 1} +} + +// PolishedBlackstonePressurePlate returns the polished blackstone pressure plate type. +func PolishedBlackstonePressurePlate() PressurePlateType { + return PressurePlateType{pressureplate: 2} +} + +// HeavyWeightedPressurePlate returns the heavy weighted pressure plate type. +func HeavyWeightedPressurePlate() PressurePlateType { + return PressurePlateType{pressureplate: 3} +} + +// LightWeightedPressurePlate returns the light weighted pressure plate type. +func LightWeightedPressurePlate() PressurePlateType { + return PressurePlateType{pressureplate: 4} +} + +// Uint8 ... +func (p PressurePlateType) Uint8() uint8 { + return p.wood.Uint8() | uint8(p.pressureplate)<<4 +} + +// Name ... +func (p PressurePlateType) Name() string { + switch p.pressureplate { + case 0: + return p.wood.Name() + " Pressure Plate" + case 1: + return "Stone Pressure Plate" + case 2: + return "Polished Blackstone Pressure Plate" + case 3: + return "Heavy Weighted Pressure Plate" + case 4: + return "Light Weighted Pressure Plate" + } + panic("unknown pressure plate type") +} + +// String ... +func (p PressurePlateType) String() string { + switch p.pressureplate { + case 0: + if p.wood == OakWood() { + return "wooden" + } + return p.wood.String() + case 1: + return "stone" + case 2: + return "polished_blackstone" + case 3: + return "heavy_weighted" + case 4: + return "light_weighted" + } + panic("unknown pressure plate type") +} + +// PressurePlateTypes ... +func PressurePlateTypes() []PressurePlateType { + types := []PressurePlateType{StonePressurePlate(), PolishedBlackstonePressurePlate(), HeavyWeightedPressurePlate(), LightWeightedPressurePlate()} + for _, w := range WoodTypes() { + types = append(types, WoodPressurePlate(w)) + } + return types +} diff --git a/server/block/redstone.go b/server/block/redstone.go new file mode 100644 index 000000000..698682015 --- /dev/null +++ b/server/block/redstone.go @@ -0,0 +1,394 @@ +package block + +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/block/model" + "github.com/df-mc/dragonfly/server/world" + "slices" +) + +// RedstoneUpdater represents a block that can be updated through a change in redstone signal. +type RedstoneUpdater interface { + // RedstoneUpdate is called when a change in redstone signal is computed. + RedstoneUpdate(pos cube.Pos, tx *world.Tx) +} + +// RedstoneBlocking represents a block that blocks redstone signals. +type RedstoneBlocking interface { + // RedstoneBlocking returns true if the block blocks redstone signals. + RedstoneBlocking() bool +} + +// wireNetwork implements a minimally-invasive bolt-on accelerator that performs a breadth-first search through redstone +// wires in order to more efficiently and compute new redstone wire power levels and determine the order in which other +// blocks should be updated. This implementation is heavily based off of RedstoneWireTurbo and MCHPRS. +type wireNetwork struct { + nodes []*wireNode + nodeCache map[cube.Pos]*wireNode + updateQueue [3][]*wireNode + currentWalkLayer uint32 +} + +// wireNode is a data structure to keep track of redstone wires and neighbours that will receive updates. +type wireNode struct { + visited bool + + pos cube.Pos + block world.Block + + neighbours []*wireNode + oriented bool + + xBias int32 + zBias int32 + + layer uint32 +} + +const ( + wireHeadingNorth = 0 + wireHeadingEast = 1 + wireHeadingSouth = 2 + wireHeadingWest = 3 +) + +// updateStrongRedstone sets off the breadth-first walk through all redstone wires connected to the initial position +// triggered. This is the main entry point for the redstone update algorithm. +func updateStrongRedstone(pos cube.Pos, tx *world.Tx) { + n := &wireNetwork{ + nodeCache: make(map[cube.Pos]*wireNode), + updateQueue: [3][]*wireNode{}, + } + + root := &wireNode{ + block: tx.Block(pos), + pos: pos, + visited: true, + } + n.nodeCache[pos] = root + n.nodes = append(n.nodes, root) + + n.propagateChanges(tx, root, 0) + n.breadthFirstWalk(tx) +} + +// updateAroundRedstone updates redstone components around the given centre position. It will also ignore any faces +// provided within the ignoredFaces parameter. This implementation is based off of RedstoneCircuit and Java 1.19. +func updateAroundRedstone(centre cube.Pos, tx *world.Tx, ignoredFaces ...cube.Face) { + for _, face := range []cube.Face{ + cube.FaceWest, + cube.FaceEast, + cube.FaceDown, + cube.FaceUp, + cube.FaceNorth, + cube.FaceSouth, + } { + if slices.Contains(ignoredFaces, face) { + continue + } + + pos := centre.Side(face) + if r, ok := tx.Block(pos).(RedstoneUpdater); ok { + r.RedstoneUpdate(pos, tx) + } + } +} + +// updateDirectionalRedstone updates redstone components through the given face. This implementation is based off of +// RedstoneCircuit and Java 1.19. +func updateDirectionalRedstone(pos cube.Pos, tx *world.Tx, face cube.Face) { + updateAroundRedstone(pos, tx) + updateAroundRedstone(pos.Side(face), tx, face.Opposite()) +} + +// receivedRedstonePower returns true if the given position is receiving power from any faces that aren't ignored. +func receivedRedstonePower(pos cube.Pos, w *world.Tx, ignoredFaces ...cube.Face) bool { + for _, face := range cube.Faces() { + if slices.Contains(ignoredFaces, face) { + continue + } + if w.RedstonePower(pos.Side(face), face, true) > 0 { + return true + } + } + return false +} + +// identifyNeighbours identifies the neighbouring positions of a given node, determines their types, and links them into +// the graph. After that, based on what nodes in the graph have been visited, the neighbours are reordered left-to-right +// relative to the direction of information flow. +func (n *wireNetwork) identifyNeighbours(tx *world.Tx, node *wireNode) { + neighbours := computeRedstoneNeighbours(node.pos) + neighboursVisited := make([]bool, 0, 24) + neighbourNodes := make([]*wireNode, 0, 24) + for _, neighbourPos := range neighbours[:24] { + neighbour, ok := n.nodeCache[neighbourPos] + if !ok { + neighbour = &wireNode{ + pos: neighbourPos, + block: tx.Block(neighbourPos), + } + n.nodeCache[neighbourPos] = neighbour + n.nodes = append(n.nodes, neighbour) + } + + neighbourNodes = append(neighbourNodes, neighbour) + neighboursVisited = append(neighboursVisited, neighbour.visited) + } + + fromWest := neighboursVisited[0] || neighboursVisited[7] || neighboursVisited[8] + fromEast := neighboursVisited[1] || neighboursVisited[12] || neighboursVisited[13] + fromNorth := neighboursVisited[4] || neighboursVisited[17] || neighboursVisited[20] + fromSouth := neighboursVisited[5] || neighboursVisited[18] || neighboursVisited[21] + + var cX, cZ int32 + if fromWest { + cX++ + } + if fromEast { + cX-- + } + if fromNorth { + cZ++ + } + if fromSouth { + cZ-- + } + + var heading uint32 + if cX == 0 && cZ == 0 { + heading = computeRedstoneHeading(node.xBias, node.zBias) + for _, neighbourNode := range neighbourNodes { + neighbourNode.xBias = node.xBias + neighbourNode.zBias = node.zBias + } + } else { + if cX != 0 && cZ != 0 { + if node.xBias != 0 { + cZ = 0 + } + if node.zBias != 0 { + cX = 0 + } + } + heading = computeRedstoneHeading(cX, cZ) + for _, neighbourNode := range neighbourNodes { + neighbourNode.xBias = cX + neighbourNode.zBias = cZ + } + } + + n.orientNeighbours(&neighbourNodes, node, heading) +} + +// reordering contains lookup tables that completely remap neighbour positions into a left-to-right ordering, based on +// the cardinal direction that is determined to be forward. +var redstoneReordering = [][]uint32{ + {2, 3, 16, 19, 0, 4, 1, 5, 7, 8, 17, 20, 12, 13, 18, 21, 6, 9, 22, 14, 11, 10, 23, 15}, + {2, 3, 16, 19, 4, 1, 5, 0, 17, 20, 12, 13, 18, 21, 7, 8, 22, 14, 11, 15, 23, 9, 6, 10}, + {2, 3, 16, 19, 1, 5, 0, 4, 12, 13, 18, 21, 7, 8, 17, 20, 11, 15, 23, 10, 6, 14, 22, 9}, + {2, 3, 16, 19, 5, 0, 4, 1, 18, 21, 7, 8, 17, 20, 12, 13, 23, 10, 6, 9, 22, 15, 11, 14}, +} + +// orientNeighbours reorders the neighbours of a node based on the direction that is determined to be forward. +func (n *wireNetwork) orientNeighbours(src *[]*wireNode, dst *wireNode, heading uint32) { + dst.oriented = true + dst.neighbours = make([]*wireNode, 0, 24) + for _, i := range redstoneReordering[heading] { + dst.neighbours = append(dst.neighbours, (*src)[i]) + } +} + +// propagateChanges propagates changes for any redstone wire in layer N, informing the neighbours to recompute their +// states in layers N + 1 and N + 2. +func (n *wireNetwork) propagateChanges(tx *world.Tx, node *wireNode, layer uint32) { + if !node.oriented { + n.identifyNeighbours(tx, node) + } + + layerOne := layer + 1 + for _, neighbour := range node.neighbours[:24] { + if layerOne > neighbour.layer { + neighbour.layer = layerOne + n.updateQueue[1] = append(n.updateQueue[1], neighbour) + } + } + + layerTwo := layer + 2 + for _, neighbour := range node.neighbours[:4] { + if layerTwo > neighbour.layer { + neighbour.layer = layerTwo + n.updateQueue[2] = append(n.updateQueue[2], neighbour) + } + } +} + +// breadthFirstWalk performs a breadth-first (layer by layer) traversal through redstone wires, propagating value +// changes to neighbours in the order that they are visited. +func (n *wireNetwork) breadthFirstWalk(tx *world.Tx) { + n.shiftQueue() + n.currentWalkLayer = 1 + + for len(n.updateQueue[0]) > 0 || len(n.updateQueue[1]) > 0 { + for _, node := range n.updateQueue[0] { + if _, ok := node.block.(RedstoneWire); ok { + n.updateNode(tx, node, n.currentWalkLayer) + continue + } + if t, ok := node.block.(RedstoneUpdater); ok { + t.RedstoneUpdate(node.pos, tx) + } + } + + n.shiftQueue() + n.currentWalkLayer++ + } + + n.currentWalkLayer = 0 +} + +// shiftQueue shifts the update queue, moving all nodes from the current layer to the next layer. The last queue is then +// simply invalidated. +func (n *wireNetwork) shiftQueue() { + n.updateQueue[0] = n.updateQueue[1] + n.updateQueue[1] = n.updateQueue[2] + n.updateQueue[2] = nil +} + +// updateNode processes a node which has had neighbouring redstone wires that have experienced value changes. +func (n *wireNetwork) updateNode(tx *world.Tx, node *wireNode, layer uint32) { + node.visited = true + + oldWire := node.block.(RedstoneWire) + newWire := n.calculateCurrentChanges(tx, node) + if oldWire.Power != newWire.Power { + node.block = newWire + + n.propagateChanges(tx, node, layer) + } +} + +var ( + rsNeighbours = [...]uint32{4, 5, 6, 7} + rsNeighboursUp = [...]uint32{9, 11, 13, 15} + rsNeighboursDn = [...]uint32{8, 10, 12, 14} +) + +// calculateCurrentChanges computes redstone wire power levels from neighboring blocks. Modifications cut the number of +// power level changes by about 45% from vanilla, and also synergies well with the breadth-first search implementation. +func (n *wireNetwork) calculateCurrentChanges(tx *world.Tx, node *wireNode) RedstoneWire { + wire := node.block.(RedstoneWire) + i := wire.Power + + var blockPower int + if !node.oriented { + n.identifyNeighbours(tx, node) + } + + var wirePower int + for _, face := range cube.Faces() { + wirePower = max(wirePower, tx.RedstonePower(node.pos.Side(face), face, false)) + } + + if wirePower < 15 { + centerUp := node.neighbours[1].block + _, centerUpSolid := centerUp.Model().(model.Solid) + for m := 0; m < 4; m++ { + neighbour := node.neighbours[rsNeighbours[m]].block + _, neighbourSolid := neighbour.Model().(model.Solid) + + blockPower = n.maxCurrentStrength(neighbour, blockPower) + if !neighbourSolid { + neighbourDown := node.neighbours[rsNeighboursDn[m]].block + blockPower = n.maxCurrentStrength(neighbourDown, blockPower) + } else if d, ok := neighbour.(LightDiffuser); (!ok || d.LightDiffusionLevel() > 0) && !centerUpSolid { + neighbourUp := node.neighbours[rsNeighboursUp[m]].block + blockPower = n.maxCurrentStrength(neighbourUp, blockPower) + } + } + } + + j := blockPower - 1 + if wirePower > j { + j = wirePower + } + + if i != j { + wire.Power = j + tx.SetBlock(node.pos, wire, &world.SetOpts{DisableBlockUpdates: true}) + } + return wire +} + +// maxCurrentStrength computes a redstone wire's power level based on a cached state. +func (n *wireNetwork) maxCurrentStrength(neighbour world.Block, strength int) int { + if wire, ok := neighbour.(RedstoneWire); ok { + return max(wire.Power, strength) + } + return strength +} + +// computeRedstoneNeighbours computes the neighbours of a redstone wire node, ignoring neighbours that don't necessarily +// need to be updated, but are in vanilla. +func computeRedstoneNeighbours(pos cube.Pos) []cube.Pos { + return []cube.Pos{ + // Immediate neighbours, in the order of west, east, down, up, north, and finally south. + pos.Side(cube.FaceWest), + pos.Side(cube.FaceEast), + pos.Side(cube.FaceDown), + pos.Side(cube.FaceUp), + pos.Side(cube.FaceNorth), + pos.Side(cube.FaceSouth), + + // Neighbours of neighbours, in the same order, except that duplicates are not included. + pos.Side(cube.FaceWest).Side(cube.FaceWest), + pos.Side(cube.FaceWest).Side(cube.FaceDown), + pos.Side(cube.FaceWest).Side(cube.FaceUp), + pos.Side(cube.FaceWest).Side(cube.FaceNorth), + pos.Side(cube.FaceWest).Side(cube.FaceSouth), + + pos.Side(cube.FaceEast).Side(cube.FaceEast), + pos.Side(cube.FaceEast).Side(cube.FaceDown), + pos.Side(cube.FaceEast).Side(cube.FaceUp), + pos.Side(cube.FaceEast).Side(cube.FaceNorth), + pos.Side(cube.FaceEast).Side(cube.FaceSouth), + + pos.Side(cube.FaceDown).Side(cube.FaceDown), + pos.Side(cube.FaceDown).Side(cube.FaceNorth), + pos.Side(cube.FaceDown).Side(cube.FaceSouth), + + pos.Side(cube.FaceUp).Side(cube.FaceUp), + pos.Side(cube.FaceUp).Side(cube.FaceNorth), + pos.Side(cube.FaceUp).Side(cube.FaceSouth), + + pos.Side(cube.FaceNorth).Side(cube.FaceNorth), + pos.Side(cube.FaceSouth).Side(cube.FaceSouth), + } +} + +// computeRedstoneHeading computes the cardinal direction that is "forward" given which redstone wires have been visited +// and which have not around the position currently being processed. +func computeRedstoneHeading(rX, rZ int32) uint32 { + code := (rX + 1) + 3*(rZ+1) + switch code { + case 0: + return wireHeadingNorth + case 1: + return wireHeadingNorth + case 2: + return wireHeadingEast + case 3: + return wireHeadingWest + case 4: + return wireHeadingWest + case 5: + return wireHeadingEast + case 6: + return wireHeadingSouth + case 7: + return wireHeadingSouth + case 8: + return wireHeadingSouth + } + panic("should never happen") +} diff --git a/server/block/redstone_block.go b/server/block/redstone_block.go new file mode 100644 index 000000000..46d8f17b2 --- /dev/null +++ b/server/block/redstone_block.go @@ -0,0 +1,58 @@ +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" +) + +type RedstoneBlock struct { + solid +} + +// BreakInfo ... +func (r RedstoneBlock) BreakInfo() BreakInfo { + return newBreakInfo(5, pickaxeHarvestable, pickaxeEffective, oneOf(r)).withBlastResistance(30).withBreakHandler(func(pos cube.Pos, tx *world.Tx, _ item.User) { + updateAroundRedstone(pos, tx) + }) +} + +// EncodeItem ... +func (r RedstoneBlock) EncodeItem() (name string, meta int16) { + return "minecraft:redstone_block", 0 +} + +// EncodeBlock ... +func (r RedstoneBlock) EncodeBlock() (string, map[string]any) { + return "minecraft:redstone_block", nil +} + +// UseOnBlock ... +func (r RedstoneBlock) 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, r) + if !used { + return false + } + place(tx, pos, r, user, ctx) + if placed(ctx) { + updateAroundRedstone(pos, tx) + return true + } + return false +} + +// Source ... +func (r RedstoneBlock) RedstoneSource() bool { + return true +} + +// WeakPower ... +func (r RedstoneBlock) WeakPower(_ cube.Pos, _ cube.Face, _ *world.Tx, _ bool) int { + return 15 +} + +// StrongPower ... +func (r RedstoneBlock) StrongPower(_ cube.Pos, _ cube.Face, _ *world.Tx, _ bool) int { + return 0 +} diff --git a/server/block/redstone_lamp.go b/server/block/redstone_lamp.go new file mode 100644 index 000000000..736763d8b --- /dev/null +++ b/server/block/redstone_lamp.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" + "math/rand" + "time" +) + +// RedstoneLamp is a block that produces light when activated with a redstone signal. +type RedstoneLamp struct { + solid + + // Lit is if the redstone lamp is lit and emitting light. + Lit bool +} + +// BreakInfo ... +func (l RedstoneLamp) BreakInfo() BreakInfo { + return newBreakInfo(0.3, alwaysHarvestable, nothingEffective, oneOf(l)) +} + +// LightEmissionLevel ... +func (l RedstoneLamp) LightEmissionLevel() uint8 { + if l.Lit { + return 15 + } + return 0 +} + +// EncodeItem ... +func (l RedstoneLamp) EncodeItem() (name string, meta int16) { + return "minecraft:redstone_lamp", 0 +} + +// EncodeBlock ... +func (l RedstoneLamp) EncodeBlock() (string, map[string]any) { + if l.Lit { + return "minecraft:lit_redstone_lamp", nil + } + return "minecraft:redstone_lamp", nil +} + +// UseOnBlock ... +func (l RedstoneLamp) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, l) + if !used { + return + } + l.Lit = receivedRedstonePower(pos, tx) + place(tx, pos, l, user, ctx) + return placed(ctx) +} + +// RedstoneUpdate ... +func (l RedstoneLamp) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { + if l.Lit == receivedRedstonePower(pos, tx) { + return + } + if !l.Lit { + l.Lit = true + tx.SetBlock(pos, l, &world.SetOpts{DisableBlockUpdates: true}) + } else { + tx.ScheduleBlockUpdate(pos, l, time.Millisecond*50) + } +} + +// ScheduledTick ... +func (l RedstoneLamp) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { + if receivedRedstonePower(pos, tx) { + return + } + l.Lit = false + tx.SetBlock(pos, l, &world.SetOpts{DisableBlockUpdates: true}) +} diff --git a/server/block/redstone_ore.go b/server/block/redstone_ore.go new file mode 100644 index 000000000..82a62f960 --- /dev/null +++ b/server/block/redstone_ore.go @@ -0,0 +1,39 @@ +package block + +import "github.com/df-mc/dragonfly/server/item" + +// RedstoneOre is a common ore. +type RedstoneOre struct { + solid + bassDrum + + // Type is the type of redstone ore. + Type OreType +} + +// BreakInfo ... +func (c RedstoneOre) BreakInfo() BreakInfo { + i := newBreakInfo(c.Type.Hardness(), func(t item.Tool) bool { + return t.ToolType() == item.TypePickaxe && t.HarvestLevel() >= item.ToolTierIron.HarvestLevel + }, pickaxeEffective, silkTouchOneOf(RedstoneWire{}, c)).withXPDropRange(1, 5) + if c.Type == DeepslateOre() { + i = i.withBlastResistance(9) + } + return i +} + +// SmeltInfo ... +func (RedstoneOre) SmeltInfo() item.SmeltInfo { + return newOreSmeltInfo(item.NewStack(RedstoneWire{}, 1), 0.7) +} + +// EncodeItem ... +func (c RedstoneOre) EncodeItem() (name string, meta int16) { + return "minecraft:" + c.Type.Prefix() + "redstone_ore", 0 +} + +// EncodeBlock ... +func (c RedstoneOre) EncodeBlock() (string, map[string]any) { + return "minecraft:" + c.Type.Prefix() + "redstone_ore", nil + +} diff --git a/server/block/redstone_torch.go b/server/block/redstone_torch.go new file mode 100644 index 000000000..62e588127 --- /dev/null +++ b/server/block/redstone_torch.go @@ -0,0 +1,173 @@ +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" + "math/rand" + "time" +) + +// RedstoneTorch is a non-solid blocks that emits little light and also a full-strength redstone signal when lit. +// TODO: Redstone torches should burn out when used too recently and excessively. +type RedstoneTorch struct { + transparent + empty + + // Facing is the direction from the torch to the block. + Facing cube.Face + // Lit is if the redstone torch is lit and emitting power. + Lit bool +} + +// HasLiquidDrops ... +func (RedstoneTorch) HasLiquidDrops() bool { + return true +} + +// LightEmissionLevel ... +func (t RedstoneTorch) LightEmissionLevel() uint8 { + if t.Lit { + return 7 + } + return 0 +} + +// BreakInfo ... +func (t RedstoneTorch) BreakInfo() BreakInfo { + return newBreakInfo(0, alwaysHarvestable, nothingEffective, oneOf(t)).withBreakHandler(func(pos cube.Pos, tx *world.Tx, _ item.User) { + updateDirectionalRedstone(pos, tx, t.Facing.Opposite()) + }) +} + +// UseOnBlock ... +func (t RedstoneTorch) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, face, used := firstReplaceable(tx, pos, face, t) + if !used { + return false + } + if face == cube.FaceDown { + return false + } + if _, ok := tx.Block(pos).(world.Liquid); ok { + return false + } + if !tx.Block(pos.Side(face.Opposite())).Model().FaceSolid(pos.Side(face.Opposite()), face, tx) { + found := false + for _, i := range []cube.Face{cube.FaceSouth, cube.FaceWest, cube.FaceNorth, cube.FaceEast, cube.FaceDown} { + if tx.Block(pos.Side(i)).Model().FaceSolid(pos.Side(i), i.Opposite(), tx) { + found = true + face = i.Opposite() + break + } + } + if !found { + return false + } + } + t.Facing = face.Opposite() + t.Lit = true + + place(tx, pos, t, user, ctx) + if placed(ctx) { + t.RedstoneUpdate(pos, tx) + updateDirectionalRedstone(pos, tx, t.Facing.Opposite()) + return true + } + return false +} + +// NeighbourUpdateTick ... +func (t RedstoneTorch) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { + if !tx.Block(pos.Side(t.Facing)).Model().FaceSolid(pos.Side(t.Facing), t.Facing.Opposite(), tx) { + tx.SetBlock(pos, nil, nil) + dropItem(tx, item.NewStack(t, 1), pos.Vec3Centre()) + updateDirectionalRedstone(pos, tx, t.Facing.Opposite()) + } +} + +// RedstoneUpdate ... +func (t RedstoneTorch) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { + if t.inputStrength(pos, tx) > 0 != t.Lit { + return + } + tx.ScheduleBlockUpdate(pos, t, time.Millisecond*100) +} + +// ScheduledTick ... +func (t RedstoneTorch) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { + if t.inputStrength(pos, tx) > 0 != t.Lit { + return + } + t.Lit = !t.Lit + tx.SetBlock(pos, t, nil) + updateDirectionalRedstone(pos, tx, t.Facing.Opposite()) +} + +// EncodeItem ... +func (RedstoneTorch) EncodeItem() (name string, meta int16) { + return "minecraft:redstone_torch", 0 +} + +// EncodeBlock ... +func (t RedstoneTorch) EncodeBlock() (name string, properties map[string]any) { + face := "unknown" + if t.Facing != unknownFace { + face = t.Facing.String() + if t.Facing == cube.FaceDown { + face = "top" + } + } + if t.Lit { + return "minecraft:redstone_torch", map[string]any{"torch_facing_direction": face} + } + return "minecraft:unlit_redstone_torch", map[string]any{"torch_facing_direction": face} +} + +// Source ... +func (t RedstoneTorch) RedstoneSource() bool { + return t.Lit +} + +// WeakPower ... +func (t RedstoneTorch) WeakPower(_ cube.Pos, face cube.Face, _ *world.Tx, _ bool) int { + if !t.Lit { + return 0 + } + if face == cube.FaceDown { + if t.Facing.Opposite() != cube.FaceDown { + return 15 + } + return 0 + } + if face != t.Facing.Opposite() { + return 15 + } + return 0 +} + +// StrongPower ... +func (t RedstoneTorch) StrongPower(_ cube.Pos, face cube.Face, _ *world.Tx, _ bool) int { + if t.Lit && face == cube.FaceDown { + return 15 + } + return 0 +} + +// inputStrength ... +func (t RedstoneTorch) inputStrength(pos cube.Pos, tx *world.Tx) int { + return tx.RedstonePower(pos.Side(t.Facing), t.Facing, true) +} + +// allRedstoneTorches ... +func allRedstoneTorches() (all []world.Block) { + for _, f := range append(cube.Faces(), unknownFace) { + if f == cube.FaceUp { + continue + } + all = append(all, RedstoneTorch{Facing: f, Lit: true}) + all = append(all, RedstoneTorch{Facing: f}) + } + return +} diff --git a/server/block/redstone_wire.go b/server/block/redstone_wire.go new file mode 100644 index 000000000..1e88b2b43 --- /dev/null +++ b/server/block/redstone_wire.go @@ -0,0 +1,190 @@ +package block + +import ( + "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" +) + +// RedstoneWire is a block that is used to transfer a charge between objects. Charged objects can be used to open doors +// or activate certain items. This block is the placed form of redstone which can be found by mining Redstone Ore with +// an Iron Pickaxe or better. Deactivated redstone wire will appear dark red, but activated redstone wire will appear +// bright red with a sparkling particle effect. +type RedstoneWire struct { + empty + transparent + + // Power is the current power level of the redstone wire. It ranges from 0 to 15. + Power int +} + +// HasLiquidDrops ... +func (RedstoneWire) HasLiquidDrops() bool { + return true +} + +// BreakInfo ... +func (r RedstoneWire) BreakInfo() BreakInfo { + return newBreakInfo(0, alwaysHarvestable, nothingEffective, oneOf(r)).withBreakHandler(func(pos cube.Pos, tx *world.Tx, _ item.User) { + updateStrongRedstone(pos, tx) + }) +} + +// EncodeItem ... +func (RedstoneWire) EncodeItem() (name string, meta int16) { + return "minecraft:redstone", 0 +} + +// EncodeBlock ... +func (r RedstoneWire) EncodeBlock() (string, map[string]any) { + return "minecraft:redstone_wire", map[string]any{ + "redstone_signal": int32(r.Power), + } +} + +// UseOnBlock ... +func (r RedstoneWire) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, r) + if !used { + return + } + if _, ok := tx.Liquid(pos); ok { + return false + } + belowPos := pos.Side(cube.FaceDown) + if !tx.Block(belowPos).Model().FaceSolid(belowPos, cube.FaceUp, tx) { + return + } + r.Power = r.calculatePower(pos, tx) + place(tx, pos, r, user, ctx) + if placed(ctx) { + updateStrongRedstone(pos, tx) + return true + } + return false +} + +// NeighbourUpdateTick ... +func (r RedstoneWire) NeighbourUpdateTick(pos, neighbour cube.Pos, tx *world.Tx) { + if pos == neighbour { + // Ignore neighbour updates on ourself. + return + } + if _, ok := tx.Block(pos.Side(cube.FaceDown)).(Air); ok { + tx.SetBlock(pos, nil, nil) + dropItem(tx, item.NewStack(r, 1), pos.Vec3Centre()) + return + } + r.RedstoneUpdate(pos, tx) +} + +// RedstoneUpdate ... +func (r RedstoneWire) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { + if power := r.calculatePower(pos, tx); r.Power != power { + r.Power = power + tx.SetBlock(pos, r, &world.SetOpts{DisableBlockUpdates: true}) + updateStrongRedstone(pos, tx) + } +} + +// Source ... +func (RedstoneWire) RedstoneSource() bool { + return false +} + +// WeakPower ... +func (r RedstoneWire) WeakPower(pos cube.Pos, face cube.Face, tx *world.Tx, accountForDust bool) int { + if !accountForDust { + return 0 + } + if face == cube.FaceUp { + return r.Power + } + if face == cube.FaceDown { + return 0 + } + if r.connection(pos, face.Opposite(), tx) { + return r.Power + } + if r.connection(pos, face, tx) && !r.connection(pos, face.RotateLeft(), tx) && !r.connection(pos, face.RotateRight(), tx) { + return r.Power + } + return 0 +} + +// StrongPower ... +func (r RedstoneWire) StrongPower(pos cube.Pos, face cube.Face, tx *world.Tx, accountForDust bool) int { + return r.WeakPower(pos, face, tx, accountForDust) +} + +// calculatePower returns the highest level of received redstone power at the provided position. +func (r RedstoneWire) calculatePower(pos cube.Pos, tx *world.Tx) int { + aboveBlock := tx.Block(pos.Side(cube.FaceUp)) + _, aboveSolid := aboveBlock.Model().(model.Solid) + + var blockPower, wirePower int + for _, side := range cube.Faces() { + neighbourPos := pos.Side(side) + neighbour := tx.Block(neighbourPos) + + wirePower = r.maxCurrentStrength(wirePower, neighbourPos, tx) + blockPower = max(blockPower, tx.RedstonePower(neighbourPos, side, false)) + + if side.Axis() == cube.Y { + // Only check horizontal neighbours from here on. + continue + } + + if d, ok := neighbour.(LightDiffuser); (!ok || d.LightDiffusionLevel() > 0) && !aboveSolid { + wirePower = r.maxCurrentStrength(wirePower, neighbourPos.Side(cube.FaceUp), tx) + } + if _, neighbourSolid := neighbour.Model().(model.Solid); !neighbourSolid { + wirePower = r.maxCurrentStrength(wirePower, neighbourPos.Side(cube.FaceDown), tx) + } + } + return max(blockPower, wirePower-1) +} + +// maxCurrentStrength ... +func (RedstoneWire) maxCurrentStrength(power int, pos cube.Pos, tx *world.Tx) int { + if wire, ok := tx.Block(pos).(RedstoneWire); ok { + return max(wire.Power, power) + } + return power +} + +// connection returns true if the dust connects to the given face. +func (r RedstoneWire) connection(pos cube.Pos, face cube.Face, tx *world.Tx) bool { + sidePos := pos.Side(face) + sideBlock := tx.Block(sidePos) + if _, solidAbove := tx.Block(pos.Side(cube.FaceUp)).Model().(model.Solid); !solidAbove && r.canRunOnTop(tx, sidePos, sideBlock) && r.connectsTo(tx.Block(sidePos.Side(cube.FaceUp)), face, false) { + return true + } + _, sideSolid := sideBlock.Model().(model.Solid) + return r.connectsTo(sideBlock, face, true) || !sideSolid && r.connectsTo(tx.Block(sidePos.Side(cube.FaceDown)), face, false) +} + +// connectsTo ... +func (RedstoneWire) connectsTo(block world.Block, face cube.Face, allowDirectSources bool) bool { + if _, ok := block.(RedstoneWire); ok { + return true + } + // TODO: Account for other redstone blocks. + c, ok := block.(world.Conductor) + return ok && allowDirectSources && c.RedstoneSource() +} + +// canRunOnTop ... +func (RedstoneWire) canRunOnTop(tx *world.Tx, pos cube.Pos, block world.Block) bool { + return block.Model().FaceSolid(pos, cube.FaceUp, tx) +} + +// allRedstoneWires returns a list of all redstone dust states. +func allRedstoneWires() (all []world.Block) { + for i := 0; i < 16; i++ { + all = append(all, RedstoneWire{Power: i}) + } + return +} diff --git a/server/block/register.go b/server/block/register.go index a482dab43..b8cbc6b13 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -84,6 +84,9 @@ func init() { world.RegisterBlock(RawCopper{}) world.RegisterBlock(RawGold{}) world.RegisterBlock(RawIron{}) + world.RegisterBlock(RedstoneBlock{}) + world.RegisterBlock(RedstoneLamp{}) + world.RegisterBlock(RedstoneLamp{Lit: true}) world.RegisterBlock(ReinforcedDeepslate{}) world.RegisterBlock(ResinBricks{Chiseled: true}) world.RegisterBlock(ResinBricks{}) @@ -101,6 +104,7 @@ func init() { world.RegisterBlock(SporeBlossom{}) world.RegisterBlock(Stone{Smooth: true}) world.RegisterBlock(Stone{}) + world.RegisterBlock(Target{}) world.RegisterBlock(TNT{}) world.RegisterBlock(Terracotta{}) world.RegisterBlock(Tuff{}) @@ -119,6 +123,7 @@ func init() { world.RegisterBlock(GoldOre{Type: ore}) world.RegisterBlock(IronOre{Type: ore}) world.RegisterBlock(LapisOre{Type: ore}) + world.RegisterBlock(RedstoneOre{Type: ore}) } registerAll(allAnvils()) @@ -130,6 +135,7 @@ func init() { registerAll(allBlastFurnaces()) registerAll(allBoneBlock()) registerAll(allBrewingStands()) + registerAll(allButtons()) registerAll(allCactus()) registerAll(allCake()) registerAll(allCampfires()) @@ -160,6 +166,8 @@ func init() { registerAll(allGrindstones()) registerAll(allHayBales()) registerAll(allHoppers()) + registerAll(allIronDoors()) + registerAll(allIronTrapdoors()) registerAll(allItemFrames()) registerAll(allKelp()) registerAll(allLadders()) @@ -167,6 +175,7 @@ func init() { registerAll(allLava()) registerAll(allLeaves()) registerAll(allLecterns()) + registerAll(allLevers()) registerAll(allLight()) registerAll(allLitPumpkins()) registerAll(allLogs()) @@ -178,11 +187,14 @@ func init() { registerAll(allPinkPetals()) registerAll(allPlanks()) registerAll(allPotato()) + registerAll(allPressurePlates()) registerAll(allPrismarine()) registerAll(allPumpkinStems()) registerAll(allPumpkins()) registerAll(allPurpurs()) registerAll(allQuartz()) + registerAll(allRedstoneTorches()) + registerAll(allRedstoneWires()) registerAll(allSandstones()) registerAll(allSeaPickles()) registerAll(allSigns()) @@ -283,11 +295,14 @@ func init() { world.RegisterItem(Iron{}) world.RegisterItem(ItemFrame{Glowing: true}) world.RegisterItem(ItemFrame{}) + world.RegisterItem(IronDoor{}) + world.RegisterItem(IronTrapDoor{}) world.RegisterItem(Jukebox{}) world.RegisterItem(Kelp{}) world.RegisterItem(Ladder{}) world.RegisterItem(Lapis{}) world.RegisterItem(Lectern{}) + world.RegisterItem(Lever{}) world.RegisterItem(LitPumpkin{}) world.RegisterItem(Loom{}) world.RegisterItem(MelonSeeds{}) @@ -327,6 +342,10 @@ func init() { world.RegisterItem(RawCopper{}) world.RegisterItem(RawGold{}) world.RegisterItem(RawIron{}) + world.RegisterItem(RedstoneBlock{}) + world.RegisterItem(RedstoneLamp{}) + world.RegisterItem(RedstoneTorch{}) + world.RegisterItem(RedstoneWire{}) world.RegisterItem(ReinforcedDeepslate{}) world.RegisterItem(ResinBricks{Chiseled: true}) world.RegisterItem(ResinBricks{}) @@ -348,6 +367,7 @@ func init() { world.RegisterItem(Stone{Smooth: true}) world.RegisterItem(Stone{}) world.RegisterItem(SugarCane{}) + world.RegisterItem(Target{}) world.RegisterItem(TNT{}) world.RegisterItem(Terracotta{}) world.RegisterItem(Tuff{}) @@ -382,6 +402,12 @@ func init() { for _, t := range AnvilTypes() { world.RegisterItem(Anvil{Type: t}) } + for _, t := range ButtonTypes() { + world.RegisterItem(Button{Type: t}) + } + for _, t := range PressurePlateTypes() { + world.RegisterItem(PressurePlate{Type: t}) + } for _, c := range item.Colours() { world.RegisterItem(Banner{Colour: c}) world.RegisterItem(Carpet{Colour: c}) diff --git a/server/block/target.go b/server/block/target.go new file mode 100644 index 000000000..f220d89e5 --- /dev/null +++ b/server/block/target.go @@ -0,0 +1,100 @@ +package block + +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/internal/nbtconv" + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/world" + "github.com/df-mc/dragonfly/server/world/sound" + "github.com/go-gl/mathgl/mgl64" + "math" + "math/rand" + "time" +) + +// Target is a block that provides redstone power based on how close a projectile hits its center. +type Target struct { + solid + + // Power is the redstone power level emitted by the target block, ranging from 0 to 15. + Power int +} + +// BreakInfo ... +func (t Target) BreakInfo() BreakInfo { + return newBreakInfo(0.5, alwaysHarvestable, hoeEffective, oneOf(t)).withBreakHandler(func(pos cube.Pos, tx *world.Tx, _ item.User) { + updateAroundRedstone(pos, tx) + }) +} + +// Source ... +func (t Target) RedstoneSource() bool { + return true +} + +// WeakPower ... +func (t Target) WeakPower(cube.Pos, cube.Face, *world.Tx, bool) int { + return t.Power +} + +// StrongPower ... +func (t Target) StrongPower(_ cube.Pos, _ cube.Face, _ *world.Tx, _ bool) int { + return t.Power +} + +// HitByProjectile handles when a projectile hits the target block. +func (t Target) HitByProjectile(pos mgl64.Vec3, blockPos cube.Pos, tx *world.Tx, delay time.Duration) { + center := blockPos.Vec3Centre() + distance := pos.Sub(center).Len() + + maxDistance := math.Sqrt(0.75) + normalizedDistance := math.Min(distance/maxDistance, 1.0) + + var rawPower float64 + if normalizedDistance <= 0.58 { + rawPower = 15 + } else if normalizedDistance > 0.9 { + rawPower = 0 + } else { + rawPower = 15 * (1 - math.Pow(normalizedDistance, 4.5)) + } + + t.Power = int(math.Max(math.Round(rawPower), 0)) + tx.SetBlock(blockPos, t, &world.SetOpts{DisableBlockUpdates: false}) + tx.PlaySound(blockPos.Vec3Centre(), sound.PowerOn{}) + + tx.ScheduleBlockUpdate(blockPos, t, delay) +} + +// ScheduledTick ... +func (t Target) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { + if t.Power > 0 { + t.Power = 0 + tx.SetBlock(pos, t, nil) + tx.PlaySound(pos.Vec3Centre(), sound.PowerOff{}) + } +} + +// DecodeNBT ... +func (t Target) DecodeNBT(data map[string]any) any { + t.Power = int(nbtconv.Int32(data, "Power")) + return t +} + +// EncodeNBT ... +func (t Target) EncodeNBT() map[string]any { + m := map[string]any{ + "Power": int32(t.Power), + } + return m +} + +// EncodeItem ... +func (t Target) EncodeItem() (name string, meta int16) { + return "minecraft:target", 0 +} + +// EncodeBlock ... +func (t Target) EncodeBlock() (name string, properties map[string]interface{}) { + return "minecraft:target", nil +} diff --git a/server/block/tnt.go b/server/block/tnt.go index b7f6ae06c..92737d950 100644 --- a/server/block/tnt.go +++ b/server/block/tnt.go @@ -16,6 +16,18 @@ type TNT struct { solid } +// NeighbourUpdateTick ... +func (t TNT) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { + t.RedstoneUpdate(pos, tx) +} + +// RedstoneUpdate ... +func (t TNT) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { + if receivedRedstonePower(pos, tx) { + t.Ignite(pos, tx, nil) + } +} + // Activate ... func (t TNT) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { held, _ := u.HeldItems() diff --git a/server/block/wood_door.go b/server/block/wood_door.go index 592b27ce7..1aefe501c 100644 --- a/server/block/wood_door.go +++ b/server/block/wood_door.go @@ -7,6 +7,7 @@ import ( "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" + "math/rand" "time" ) @@ -127,6 +128,30 @@ func (d WoodDoor) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } +// RedstoneUpdate ... +func (d WoodDoor) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { + if d.Open == receivedRedstonePower(pos, tx) { + return + } + if !d.Open { + d.Open = true + tx.PlaySound(pos.Vec3Centre(), sound.DoorOpen{Block: d}) + tx.SetBlock(pos, d, &world.SetOpts{DisableBlockUpdates: true}) + } else { + tx.ScheduleBlockUpdate(pos, d, time.Millisecond*50) + } +} + +// ScheduledTick ... +func (d WoodDoor) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { + if receivedRedstonePower(pos, tx) { + return + } + d.Open = false + tx.PlaySound(pos.Vec3Centre(), sound.DoorClose{Block: d}) + tx.SetBlock(pos, d, &world.SetOpts{DisableBlockUpdates: true}) +} + // EncodeItem ... func (d WoodDoor) EncodeItem() (name string, meta int16) { if d.Wood == OakWood() { diff --git a/server/block/wood_fence_gate.go b/server/block/wood_fence_gate.go index d4708b65b..316acbe9a 100644 --- a/server/block/wood_fence_gate.go +++ b/server/block/wood_fence_gate.go @@ -7,6 +7,7 @@ import ( "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" + "math/rand" "time" ) @@ -94,6 +95,30 @@ func (f WoodFenceGate) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } +// RedstoneUpdate ... +func (f WoodFenceGate) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { + if f.Open == receivedRedstonePower(pos, tx) { + return + } + if !f.Open { + f.Open = true + tx.PlaySound(pos.Vec3Centre(), sound.FenceGateOpen{Block: f}) + tx.SetBlock(pos, f, nil) + } else { + tx.ScheduleBlockUpdate(pos, f, time.Millisecond*50) + } +} + +// ScheduledTick ... +func (f WoodFenceGate) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { + if receivedRedstonePower(pos, tx) { + return + } + f.Open = false + tx.PlaySound(pos.Vec3Centre(), sound.FenceGateClose{Block: f}) + tx.SetBlock(pos, f, &world.SetOpts{DisableBlockUpdates: true}) +} + // EncodeItem ... func (f WoodFenceGate) EncodeItem() (name string, meta int16) { if f.Wood == OakWood() { diff --git a/server/block/wood_trapdoor.go b/server/block/wood_trapdoor.go index 48e745e1c..f7e360e82 100644 --- a/server/block/wood_trapdoor.go +++ b/server/block/wood_trapdoor.go @@ -8,6 +8,7 @@ import ( "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" "math" + "math/rand" "time" ) @@ -82,6 +83,30 @@ func (t WoodTrapdoor) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } +// RedstoneUpdate ... +func (t WoodTrapdoor) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { + if t.Open == receivedRedstonePower(pos, tx) { + return + } + if !t.Open { + t.Open = true + tx.PlaySound(pos.Vec3Centre(), sound.TrapdoorOpen{Block: t}) + tx.SetBlock(pos, t, &world.SetOpts{DisableBlockUpdates: true}) + } else { + tx.ScheduleBlockUpdate(pos, t, time.Millisecond*50) + } +} + +// ScheduledTick ... +func (t WoodTrapdoor) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { + if receivedRedstonePower(pos, tx) { + return + } + t.Open = false + tx.PlaySound(pos.Vec3Centre(), sound.TrapdoorClose{Block: t}) + tx.SetBlock(pos, t, &world.SetOpts{DisableBlockUpdates: true}) +} + // EncodeItem ... func (t WoodTrapdoor) EncodeItem() (name string, meta int16) { if t.Wood == OakWood() { diff --git a/server/cmd/parameter.go b/server/cmd/parameter.go index d386a090c..84a1ec900 100644 --- a/server/cmd/parameter.go +++ b/server/cmd/parameter.go @@ -23,7 +23,7 @@ type Parameter interface { // // type GameMode string // func (GameMode) Type() string { return "GameMode" } -// func (GameMode) Options(Source) []string { return []string{"survival", "creative"} } +// func (GameMode) Options(RedstoneSource) []string { return []string{"survival", "creative"} } // // Their values will then automatically be set to whichever option returned in Enum.Options is selected by // the user. diff --git a/server/entity/projectile.go b/server/entity/projectile.go index 80a3be352..7b4e2399b 100644 --- a/server/entity/projectile.go +++ b/server/entity/projectile.go @@ -175,6 +175,23 @@ func (lt *ProjectileBehaviour) Tick(e *Ent, tx *world.Tx) *Movement { if t, ok := tx.Block(bpos).(block.TNT); ok && e.OnFireDuration() > 0 { t.Ignite(bpos, tx, nil) } + if b, ok := tx.Block(bpos).(block.Button); ok { + if b.Type != block.StoneButton() || b.Type != block.PolishedBlackstoneButton() { + b.Click(bpos, tx) + } + } + if t, ok := tx.Block(bpos).(block.Target); ok { + if _, ok := e.H().Type().(enderPearlType); !ok { + delay := time.Millisecond * 400 + + //TODO: account for trident when added. + if _, ok := e.H().Type().(arrowType); !ok { + delay = time.Millisecond * 1000 + } + + t.HitByProjectile(r.Position(), r.BlockPosition(), tx, delay) + } + } if lt.conf.SurviveBlockCollision { lt.hitBlockSurviving(e, r, m, tx) return m diff --git a/server/session/world.go b/server/session/world.go index c56502561..4d8e094c9 100644 --- a/server/session/world.go +++ b/server/session/world.go @@ -597,6 +597,10 @@ func (s *Session) playSound(pos mgl64.Vec3, t world.Sound, disableRelative bool) pk.SoundType = packet.SoundEventSmokerUse case sound.PotionBrewed: pk.SoundType = packet.SoundEventPotionBrewed + case sound.PowerOn: + pk.SoundType = packet.SoundEventPowerOn + case sound.PowerOff: + pk.SoundType = packet.SoundEventPowerOff case sound.UseSpyglass: pk.SoundType = packet.SoundEventUseSpyglass case sound.StopUsingSpyglass: diff --git a/server/world/block.go b/server/world/block.go index 88c9328f2..95c7688c4 100644 --- a/server/world/block.go +++ b/server/world/block.go @@ -75,6 +75,17 @@ type Liquid interface { Harden(pos cube.Pos, tx *Tx, flownIntoBy *cube.Pos) bool } +// Conductor represents a block that can conduct a redstone signal. +type Conductor interface { + Block + // RedstoneSource returns true if the conductor is a signal source. + RedstoneSource() bool + // WeakPower returns the power from a partial source and has limited usage. + WeakPower(pos cube.Pos, face cube.Face, tx *Tx, accountForDust bool) int + // StrongPower returns the power from a full source and can be passed to any redstone component. + StrongPower(pos cube.Pos, face cube.Face, tx *Tx, accountForDust bool) int +} + // hashes holds a list of runtime IDs indexed by the hash of the Block that implements the blocks pointed to by those // runtime IDs. It is used to look up a block's runtime ID quickly. var ( @@ -300,6 +311,11 @@ type lightDiffuser interface { LightDiffusionLevel() uint8 } +// redstoneBlocking is identical to a block.RedstoneBlocking. +type redstoneBlocking interface { + RedstoneBlocking() bool +} + // replaceableBlock represents a block that may be replaced by another block automatically. An example is // grass, which may be replaced by clicking it with another block. type replaceableBlock interface { diff --git a/server/world/conf.go b/server/world/conf.go index 5f4a82f21..e26348eb1 100644 --- a/server/world/conf.go +++ b/server/world/conf.go @@ -44,7 +44,7 @@ type Config struct { // will stop random ticking altogether, while setting it higher results in // faster ticking. RandomTickSpeed int - // RandSource is the rand.Source used for generation of random numbers in a + // RandSource is the rand.RedstoneSource used for generation of random numbers in a // World, such as when selecting blocks to tick or when deciding where to // strike lightning. If set to nil, `rand.NewSource(time.Now().Unix())` will // be used to generate a new source. diff --git a/server/world/entity.go b/server/world/entity.go index aa15e4d10..42c440a7b 100644 --- a/server/world/entity.go +++ b/server/world/entity.go @@ -331,11 +331,11 @@ type DamageSource interface { // ReducedByArmour checks if the source of damage may be reduced if the // receiver of the damage is wearing armour. ReducedByArmour() bool - // ReducedByResistance specifies if the Source is affected by the resistance + // ReducedByResistance specifies if the RedstoneSource is affected by the resistance // effect. If false, damage dealt to an Entity with this source will not be // lowered if the Entity has the resistance effect. ReducedByResistance() bool - // Fire specifies if the Source is fire related and should be ignored when + // Fire specifies if the RedstoneSource is fire related and should be ignored when // an Entity has the fire resistance effect. Fire() bool } diff --git a/server/world/sound/block.go b/server/world/sound/block.go index d9a35c73d..cdb1b9558 100644 --- a/server/world/sound/block.go +++ b/server/world/sound/block.go @@ -185,6 +185,12 @@ type ComposterReady struct{ sound } // PotionBrewed is a sound played when a potion is brewed. type PotionBrewed struct{ sound } +// PowerOn is a sound played when a redstone component is powered on. +type PowerOn struct{ sound } + +// PowerOff is a sound played when a redstone component is powered off. +type PowerOff struct{ sound } + // LecternBookPlace is a sound played when a book is placed in a lectern. type LecternBookPlace struct{ sound } diff --git a/server/world/tx.go b/server/world/tx.go index 1422bfa1f..dbf95fc79 100644 --- a/server/world/tx.go +++ b/server/world/tx.go @@ -224,6 +224,33 @@ func (tx *Tx) World() *World { return tx.w } +// RedstonePower returns the level of redstone power being emitted from a position to the provided face. +func (tx *Tx) RedstonePower(pos cube.Pos, face cube.Face, accountForDust bool) (power int) { + b := tx.Block(pos) + if c, ok := b.(Conductor); ok { + power = c.WeakPower(pos, face, tx, accountForDust) + } + if b, ok := b.(redstoneBlocking); ok && b.RedstoneBlocking() { + return power + } + if d, ok := b.(lightDiffuser); ok && d.LightDiffusionLevel() == 0 { + return power + } + for _, f := range cube.Faces() { + if !b.Model().FaceSolid(pos, f, tx) { + return power + } + } + for _, f := range cube.Faces() { + c, ok := tx.Block(pos.Side(f)).(Conductor) + if !ok { + continue + } + power = max(power, c.StrongPower(pos.Side(f), f, tx, accountForDust)) + } + return power +} + // close finishes the Tx, causing any following call on the Tx to panic. func (tx *Tx) close() { tx.closed = true From a75402a1eccfc8623590e3e98548a6127fefac2d Mon Sep 17 00:00:00 2001 From: Jon Date: Sat, 4 Jan 2025 00:56:00 -0800 Subject: [PATCH 02/33] changes --- server/block/button.go | 2 +- .../{prressure_plate_type.go => pressure_plate_type.go} | 0 server/block/redstone_block.go | 2 +- server/block/redstone_torch.go | 9 ++------- server/block/redstone_wire.go | 2 +- 5 files changed, 5 insertions(+), 10 deletions(-) rename server/block/{prressure_plate_type.go => pressure_plate_type.go} (100%) diff --git a/server/block/button.go b/server/block/button.go index b06a62c61..d454d30cc 100644 --- a/server/block/button.go +++ b/server/block/button.go @@ -32,7 +32,7 @@ func (b Button) FuelInfo() item.FuelInfo { return newFuelInfo(time.Second * 5) } -// Source ... +// RedstoneSource ... func (b Button) RedstoneSource() bool { return true } diff --git a/server/block/prressure_plate_type.go b/server/block/pressure_plate_type.go similarity index 100% rename from server/block/prressure_plate_type.go rename to server/block/pressure_plate_type.go diff --git a/server/block/redstone_block.go b/server/block/redstone_block.go index 46d8f17b2..0fd40bd58 100644 --- a/server/block/redstone_block.go +++ b/server/block/redstone_block.go @@ -42,7 +42,7 @@ func (r RedstoneBlock) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx return false } -// Source ... +// RedstoneSource ... func (r RedstoneBlock) RedstoneSource() bool { return true } diff --git a/server/block/redstone_torch.go b/server/block/redstone_torch.go index 62e588127..5cdc0eaea 100644 --- a/server/block/redstone_torch.go +++ b/server/block/redstone_torch.go @@ -125,7 +125,7 @@ func (t RedstoneTorch) EncodeBlock() (name string, properties map[string]any) { return "minecraft:unlit_redstone_torch", map[string]any{"torch_facing_direction": face} } -// Source ... +// RedstoneSource ... func (t RedstoneTorch) RedstoneSource() bool { return t.Lit } @@ -135,12 +135,7 @@ func (t RedstoneTorch) WeakPower(_ cube.Pos, face cube.Face, _ *world.Tx, _ bool if !t.Lit { return 0 } - if face == cube.FaceDown { - if t.Facing.Opposite() != cube.FaceDown { - return 15 - } - return 0 - } + if face != t.Facing.Opposite() { return 15 } diff --git a/server/block/redstone_wire.go b/server/block/redstone_wire.go index 1e88b2b43..3cdb9edb0 100644 --- a/server/block/redstone_wire.go +++ b/server/block/redstone_wire.go @@ -89,7 +89,7 @@ func (r RedstoneWire) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { } } -// Source ... +// RedstoneSource ... func (RedstoneWire) RedstoneSource() bool { return false } From a857388ccded3d6a97bf79b69516828bbb194278 Mon Sep 17 00:00:00 2001 From: Jon Date: Sat, 4 Jan 2025 01:09:39 -0800 Subject: [PATCH 03/33] update --- server/block/lever.go | 2 +- server/block/pressure_plate.go | 2 +- server/block/target.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/block/lever.go b/server/block/lever.go index 88348bf2b..5b290c190 100644 --- a/server/block/lever.go +++ b/server/block/lever.go @@ -25,7 +25,7 @@ type Lever struct { Direction cube.Direction } -// Source ... +// RedstoneSource ... func (l Lever) RedstoneSource() bool { return true } diff --git a/server/block/pressure_plate.go b/server/block/pressure_plate.go index 23557a3f6..5ae82cbb3 100644 --- a/server/block/pressure_plate.go +++ b/server/block/pressure_plate.go @@ -35,7 +35,7 @@ func (p PressurePlate) BreakInfo() BreakInfo { }) } -// Source ... +// RedstoneSource ... func (p PressurePlate) RedstoneSource() bool { return true } diff --git a/server/block/target.go b/server/block/target.go index f220d89e5..1fddd310a 100644 --- a/server/block/target.go +++ b/server/block/target.go @@ -27,7 +27,7 @@ func (t Target) BreakInfo() BreakInfo { }) } -// Source ... +// RedstoneSource ... func (t Target) RedstoneSource() bool { return true } From 693b06bc02bd9164ee6f616c7c2e98fe56362c5f Mon Sep 17 00:00:00 2001 From: Jon Date: Sat, 4 Jan 2025 10:52:13 -0800 Subject: [PATCH 04/33] use math.rand v2 --- server/block/button.go | 2 +- server/block/iron_door.go | 2 +- server/block/iron_trapdoor.go | 2 +- server/block/redstone_lamp.go | 2 +- server/block/redstone_torch.go | 3 +-- server/block/wood_door.go | 2 +- server/block/wood_fence_gate.go | 2 +- server/block/wood_trapdoor.go | 2 +- 8 files changed, 8 insertions(+), 9 deletions(-) diff --git a/server/block/button.go b/server/block/button.go index d454d30cc..9b9262abc 100644 --- a/server/block/button.go +++ b/server/block/button.go @@ -6,7 +6,7 @@ import ( "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" - "math/rand" + "math/rand/v2" "time" ) diff --git a/server/block/iron_door.go b/server/block/iron_door.go index 82c01e1f9..1eaa86f60 100644 --- a/server/block/iron_door.go +++ b/server/block/iron_door.go @@ -8,7 +8,7 @@ import ( "github.com/df-mc/dragonfly/server/world/particle" "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" - "math/rand" + "math/rand/v2" "time" ) diff --git a/server/block/iron_trapdoor.go b/server/block/iron_trapdoor.go index 261cee188..9f3a30426 100644 --- a/server/block/iron_trapdoor.go +++ b/server/block/iron_trapdoor.go @@ -8,7 +8,7 @@ import ( "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" "math" - "math/rand" + "math/rand/v2" "time" ) diff --git a/server/block/redstone_lamp.go b/server/block/redstone_lamp.go index 736763d8b..2d299cc16 100644 --- a/server/block/redstone_lamp.go +++ b/server/block/redstone_lamp.go @@ -5,7 +5,7 @@ import ( "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "math/rand" + "math/rand/v2" "time" ) diff --git a/server/block/redstone_torch.go b/server/block/redstone_torch.go index 5cdc0eaea..4942649eb 100644 --- a/server/block/redstone_torch.go +++ b/server/block/redstone_torch.go @@ -5,7 +5,7 @@ import ( "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "math/rand" + "math/rand/v2" "time" ) @@ -135,7 +135,6 @@ func (t RedstoneTorch) WeakPower(_ cube.Pos, face cube.Face, _ *world.Tx, _ bool if !t.Lit { return 0 } - if face != t.Facing.Opposite() { return 15 } diff --git a/server/block/wood_door.go b/server/block/wood_door.go index 1aefe501c..aa3724e25 100644 --- a/server/block/wood_door.go +++ b/server/block/wood_door.go @@ -7,7 +7,7 @@ import ( "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" - "math/rand" + "math/rand/v2" "time" ) diff --git a/server/block/wood_fence_gate.go b/server/block/wood_fence_gate.go index 316acbe9a..8bff145b2 100644 --- a/server/block/wood_fence_gate.go +++ b/server/block/wood_fence_gate.go @@ -7,7 +7,7 @@ import ( "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" - "math/rand" + "math/rand/v2" "time" ) diff --git a/server/block/wood_trapdoor.go b/server/block/wood_trapdoor.go index f7e360e82..3abf07057 100644 --- a/server/block/wood_trapdoor.go +++ b/server/block/wood_trapdoor.go @@ -8,7 +8,7 @@ import ( "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" "math" - "math/rand" + "math/rand/v2" "time" ) From 1837f7f01840d499767900184becd2c992ec41fe Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 6 Jan 2025 18:21:56 -0800 Subject: [PATCH 05/33] improve RedstoneUpdate for certain blocks --- server/block/iron_door.go | 28 ++++++++++++---------------- server/block/iron_trapdoor.go | 22 ++++++---------------- server/block/wood_door.go | 27 ++++++++++++--------------- server/block/wood_fence_gate.go | 21 ++++++--------------- server/block/wood_trapdoor.go | 21 ++++++--------------- 5 files changed, 42 insertions(+), 77 deletions(-) diff --git a/server/block/iron_door.go b/server/block/iron_door.go index 1eaa86f60..cec72ff73 100644 --- a/server/block/iron_door.go +++ b/server/block/iron_door.go @@ -8,8 +8,6 @@ import ( "github.com/df-mc/dragonfly/server/world/particle" "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" - "math/rand/v2" - "time" ) // IronDoor is a variant of the door made of iron that can only be opened using redstone. @@ -102,23 +100,21 @@ func (d IronDoor) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { if d.Open == receivedRedstonePower(pos, tx) { return } - if !d.Open { - d.Open = true - tx.PlaySound(pos.Vec3Centre(), sound.DoorOpen{Block: d}) - tx.SetBlock(pos, d, &world.SetOpts{DisableBlockUpdates: true}) - } else { - tx.ScheduleBlockUpdate(pos, d, time.Millisecond*50) + + d.Open = receivedRedstonePower(pos, tx) + tx.SetBlock(pos, d, nil) + + otherPos := pos.Side(cube.Face(boolByte(!d.Top))) + if other, ok := tx.Block(otherPos).(IronDoor); ok { + other.Open = d.Open + tx.SetBlock(otherPos, other, nil) } -} -// ScheduledTick ... -func (d IronDoor) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { - if receivedRedstonePower(pos, tx) { - return + if d.Open { + tx.PlaySound(pos.Vec3Centre(), sound.DoorOpen{Block: d}) + } else { + tx.PlaySound(pos.Vec3Centre(), sound.DoorClose{Block: d}) } - d.Open = false - tx.PlaySound(pos.Vec3Centre(), sound.DoorClose{Block: d}) - tx.SetBlock(pos, d, &world.SetOpts{DisableBlockUpdates: true}) } // EncodeItem ... diff --git a/server/block/iron_trapdoor.go b/server/block/iron_trapdoor.go index 9f3a30426..71e88ae5c 100644 --- a/server/block/iron_trapdoor.go +++ b/server/block/iron_trapdoor.go @@ -8,8 +8,6 @@ import ( "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" "math" - "math/rand/v2" - "time" ) // IronTrapDoor is a solid, transparent block that can be used as an openable 1×1 barrier @@ -61,23 +59,15 @@ func (t IronTrapDoor) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { if t.Open == receivedRedstonePower(pos, tx) { return } - if !t.Open { - t.Open = true + + t.Open = receivedRedstonePower(pos, tx) + tx.SetBlock(pos, t, nil) + + if t.Open { tx.PlaySound(pos.Vec3Centre(), sound.TrapdoorOpen{Block: t}) - tx.SetBlock(pos, t, &world.SetOpts{DisableBlockUpdates: true}) } else { - tx.ScheduleBlockUpdate(pos, t, time.Millisecond*50) - } -} - -// ScheduledTick ... -func (t IronTrapDoor) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { - if receivedRedstonePower(pos, tx) { - return + tx.PlaySound(pos.Vec3Centre(), sound.TrapdoorClose{Block: t}) } - t.Open = false - tx.PlaySound(pos.Vec3Centre(), sound.TrapdoorClose{Block: t}) - tx.SetBlock(pos, t, &world.SetOpts{DisableBlockUpdates: true}) } // EncodeItem ... diff --git a/server/block/wood_door.go b/server/block/wood_door.go index aa3724e25..c8260d3ab 100644 --- a/server/block/wood_door.go +++ b/server/block/wood_door.go @@ -7,7 +7,6 @@ import ( "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" - "math/rand/v2" "time" ) @@ -133,23 +132,21 @@ func (d WoodDoor) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { if d.Open == receivedRedstonePower(pos, tx) { return } - if !d.Open { - d.Open = true - tx.PlaySound(pos.Vec3Centre(), sound.DoorOpen{Block: d}) - tx.SetBlock(pos, d, &world.SetOpts{DisableBlockUpdates: true}) - } else { - tx.ScheduleBlockUpdate(pos, d, time.Millisecond*50) + + d.Open = receivedRedstonePower(pos, tx) + tx.SetBlock(pos, d, nil) + + otherPos := pos.Side(cube.Face(boolByte(!d.Top))) + if other, ok := tx.Block(otherPos).(WoodDoor); ok { + other.Open = d.Open + tx.SetBlock(otherPos, other, nil) } -} -// ScheduledTick ... -func (d WoodDoor) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { - if receivedRedstonePower(pos, tx) { - return + if d.Open { + tx.PlaySound(pos.Vec3Centre(), sound.DoorOpen{Block: d}) + } else { + tx.PlaySound(pos.Vec3Centre(), sound.DoorClose{Block: d}) } - d.Open = false - tx.PlaySound(pos.Vec3Centre(), sound.DoorClose{Block: d}) - tx.SetBlock(pos, d, &world.SetOpts{DisableBlockUpdates: true}) } // EncodeItem ... diff --git a/server/block/wood_fence_gate.go b/server/block/wood_fence_gate.go index 8bff145b2..46de7c7e9 100644 --- a/server/block/wood_fence_gate.go +++ b/server/block/wood_fence_gate.go @@ -7,7 +7,6 @@ import ( "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" - "math/rand/v2" "time" ) @@ -100,25 +99,17 @@ func (f WoodFenceGate) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { if f.Open == receivedRedstonePower(pos, tx) { return } - if !f.Open { - f.Open = true + + f.Open = receivedRedstonePower(pos, tx) + tx.SetBlock(pos, f, nil) + + if f.Open { tx.PlaySound(pos.Vec3Centre(), sound.FenceGateOpen{Block: f}) - tx.SetBlock(pos, f, nil) } else { - tx.ScheduleBlockUpdate(pos, f, time.Millisecond*50) + tx.PlaySound(pos.Vec3Centre(), sound.FenceGateClose{Block: f}) } } -// ScheduledTick ... -func (f WoodFenceGate) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { - if receivedRedstonePower(pos, tx) { - return - } - f.Open = false - tx.PlaySound(pos.Vec3Centre(), sound.FenceGateClose{Block: f}) - tx.SetBlock(pos, f, &world.SetOpts{DisableBlockUpdates: true}) -} - // EncodeItem ... func (f WoodFenceGate) EncodeItem() (name string, meta int16) { if f.Wood == OakWood() { diff --git a/server/block/wood_trapdoor.go b/server/block/wood_trapdoor.go index 3abf07057..53c1431c1 100644 --- a/server/block/wood_trapdoor.go +++ b/server/block/wood_trapdoor.go @@ -8,7 +8,6 @@ import ( "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" "math" - "math/rand/v2" "time" ) @@ -88,25 +87,17 @@ func (t WoodTrapdoor) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { if t.Open == receivedRedstonePower(pos, tx) { return } - if !t.Open { - t.Open = true + + t.Open = receivedRedstonePower(pos, tx) + tx.SetBlock(pos, t, nil) + + if t.Open { tx.PlaySound(pos.Vec3Centre(), sound.TrapdoorOpen{Block: t}) - tx.SetBlock(pos, t, &world.SetOpts{DisableBlockUpdates: true}) } else { - tx.ScheduleBlockUpdate(pos, t, time.Millisecond*50) + tx.PlaySound(pos.Vec3Centre(), sound.TrapdoorClose{Block: t}) } } -// ScheduledTick ... -func (t WoodTrapdoor) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { - if receivedRedstonePower(pos, tx) { - return - } - t.Open = false - tx.PlaySound(pos.Vec3Centre(), sound.TrapdoorClose{Block: t}) - tx.SetBlock(pos, t, &world.SetOpts{DisableBlockUpdates: true}) -} - // EncodeItem ... func (t WoodTrapdoor) EncodeItem() (name string, meta int16) { if t.Wood == OakWood() { From f9dd6d72cb34ec4ba40a6da3b835dce6ed9353a6 Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 6 Jan 2025 18:54:56 -0800 Subject: [PATCH 06/33] implement lit redstone ore --- server/block/hash.go | 4 +-- server/block/redstone_ore.go | 58 ++++++++++++++++++++++++++++-------- server/block/register.go | 3 ++ 3 files changed, 51 insertions(+), 14 deletions(-) diff --git a/server/block/hash.go b/server/block/hash.go index f271334f5..0381c1ca7 100644 --- a/server/block/hash.go +++ b/server/block/hash.go @@ -798,8 +798,8 @@ func (l RedstoneLamp) Hash() (uint64, uint64) { return hashRedstoneLamp, uint64(boolByte(l.Lit)) } -func (c RedstoneOre) Hash() (uint64, uint64) { - return hashRedstoneOre, uint64(c.Type.Uint8()) +func (r RedstoneOre) Hash() (uint64, uint64) { + return hashRedstoneOre, uint64(r.Type.Uint8()) | uint64(boolByte(r.Lit))<<1 } func (t RedstoneTorch) Hash() (uint64, uint64) { diff --git a/server/block/redstone_ore.go b/server/block/redstone_ore.go index 82a62f960..1728a60d0 100644 --- a/server/block/redstone_ore.go +++ b/server/block/redstone_ore.go @@ -1,6 +1,11 @@ package block -import "github.com/df-mc/dragonfly/server/item" +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/world" + "math/rand/v2" +) // RedstoneOre is a common ore. type RedstoneOre struct { @@ -9,31 +14,60 @@ type RedstoneOre struct { // Type is the type of redstone ore. Type OreType + // Lit returns if the redstone ore is lit. + Lit bool } // BreakInfo ... -func (c RedstoneOre) BreakInfo() BreakInfo { - i := newBreakInfo(c.Type.Hardness(), func(t item.Tool) bool { +func (r RedstoneOre) BreakInfo() BreakInfo { + i := newBreakInfo(r.Type.Hardness(), func(t item.Tool) bool { return t.ToolType() == item.TypePickaxe && t.HarvestLevel() >= item.ToolTierIron.HarvestLevel - }, pickaxeEffective, silkTouchOneOf(RedstoneWire{}, c)).withXPDropRange(1, 5) - if c.Type == DeepslateOre() { - i = i.withBlastResistance(9) - } + }, pickaxeEffective, silkTouchOneOf(RedstoneWire{}, r)).withXPDropRange(1, 5) return i } +// Activate ... +func (r RedstoneOre) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, _ item.User, _ *item.UseContext) bool { + if !r.Lit { + r.Lit = true + tx.SetBlock(pos, r, nil) + } + return false +} + +// RandomTick ... +func (r RedstoneOre) RandomTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { + if r.Lit { + r.Lit = false + tx.SetBlock(pos, r, nil) + } +} + +// LightEmissionLevel ... +func (r RedstoneOre) LightEmissionLevel() uint8 { + if r.Lit { + return 9 + } + return 0 +} + // SmeltInfo ... func (RedstoneOre) SmeltInfo() item.SmeltInfo { return newOreSmeltInfo(item.NewStack(RedstoneWire{}, 1), 0.7) } // EncodeItem ... -func (c RedstoneOre) EncodeItem() (name string, meta int16) { - return "minecraft:" + c.Type.Prefix() + "redstone_ore", 0 +func (r RedstoneOre) EncodeItem() (name string, meta int16) { + if r.Lit { + return "minecraft:lit_" + r.Type.Prefix() + "redstone_ore", 0 + } + return "minecraft:" + r.Type.Prefix() + "redstone_ore", 0 } // EncodeBlock ... -func (c RedstoneOre) EncodeBlock() (string, map[string]any) { - return "minecraft:" + c.Type.Prefix() + "redstone_ore", nil - +func (r RedstoneOre) EncodeBlock() (string, map[string]any) { + if r.Lit { + return "minecraft:lit_" + r.Type.Prefix() + "redstone_ore", nil + } + return "minecraft:" + r.Type.Prefix() + "redstone_ore", nil } diff --git a/server/block/register.go b/server/block/register.go index 72bf1ad95..4cb436c21 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -124,6 +124,7 @@ func init() { world.RegisterBlock(IronOre{Type: ore}) world.RegisterBlock(LapisOre{Type: ore}) world.RegisterBlock(RedstoneOre{Type: ore}) + world.RegisterBlock(RedstoneOre{Type: ore, Lit: true}) } registerAll(allAnvils()) @@ -345,6 +346,8 @@ func init() { world.RegisterItem(RawIron{}) world.RegisterItem(RedstoneBlock{}) world.RegisterItem(RedstoneLamp{}) + world.RegisterItem(RedstoneOre{}) + world.RegisterItem(RedstoneOre{Type: DeepslateOre()}) world.RegisterItem(RedstoneTorch{}) world.RegisterItem(RedstoneWire{}) world.RegisterItem(ReinforcedDeepslate{}) From eeecf901d486a8e36c92449af9bb4895fd48390c Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 6 Jan 2025 19:57:18 -0800 Subject: [PATCH 07/33] implement dropper block --- server/block/barrel.go | 2 +- server/block/block.go | 8 +- server/block/cube/face.go | 9 ++ server/block/dropper.go | 241 +++++++++++++++++++++++++++++++++ server/block/hash.go | 5 + server/block/register.go | 2 + server/session/world.go | 11 ++ server/world/particle/block.go | 3 + server/world/sound/block.go | 6 + 9 files changed, 284 insertions(+), 3 deletions(-) create mode 100644 server/block/dropper.go diff --git a/server/block/barrel.go b/server/block/barrel.go index fb1e39d72..d80932030 100644 --- a/server/block/barrel.go +++ b/server/block/barrel.go @@ -116,7 +116,7 @@ func (b Barrel) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world } //noinspection GoAssignmentToReceiver b = NewBarrel() - b.Facing = calculateFace(user, pos) + b.Facing = calculateFace(user, pos, false) place(tx, pos, b, user, ctx) return placed(ctx) diff --git a/server/block/block.go b/server/block/block.go index 164b02eb2..41da4f0aa 100644 --- a/server/block/block.go +++ b/server/block/block.go @@ -98,7 +98,7 @@ var unknownFace = cube.Face(len(cube.Faces())) // unknownDirection is a direction that is used for certain block items. This should not be exposed in the API. var unknownDirection = cube.Direction(len(cube.Directions())) -func calculateFace(user item.User, placePos cube.Pos) cube.Face { +func calculateFace(user item.User, placePos cube.Pos, swapHorizontal bool) cube.Face { userPos := user.Position() pos := cube.PosFromVec3(userPos) if abs(pos[0]-placePos[0]) < 2 && abs(pos[2]-placePos[2]) < 2 { @@ -113,7 +113,11 @@ func calculateFace(user item.User, placePos cube.Pos) cube.Face { return cube.FaceDown } } - return user.Rotation().Direction().Opposite().Face() + face := user.Rotation().Direction().Face() + if swapHorizontal { + face = face.Opposite() + } + return face } func abs(x int) int { diff --git a/server/block/cube/face.go b/server/block/cube/face.go index d08286b64..23f854959 100644 --- a/server/block/cube/face.go +++ b/server/block/cube/face.go @@ -24,6 +24,15 @@ func (f Face) Direction() Direction { return Direction(f - 2) } +// Positive returns whether the face is the positive of its axis. For example, FaceEast is positive, FaceWest is not. +func (f Face) Positive() bool { + switch f { + case FaceUp, FaceSouth, FaceEast: + return true + } + return false +} + // Opposite returns the opposite Face. FaceDown will return FaceUp, FaceNorth // will return FaceSouth, FaceWest will return FaceEast, and vice versa. func (f Face) Opposite() Face { diff --git a/server/block/dropper.go b/server/block/dropper.go new file mode 100644 index 000000000..452a42c50 --- /dev/null +++ b/server/block/dropper.go @@ -0,0 +1,241 @@ +package block + +import ( + "fmt" + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/internal/nbtconv" + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/item/inventory" + "github.com/df-mc/dragonfly/server/world" + "github.com/df-mc/dragonfly/server/world/particle" + "github.com/df-mc/dragonfly/server/world/sound" + "github.com/go-gl/mathgl/mgl64" + "math/rand/v2" + "strings" + "sync" + "time" +) + +// Dropper is a low-capacity storage block that can eject its contents into the world or into other containers when +// given a redstone signal. +type Dropper struct { + solid + + // Facing is the direction the dropper is facing. + Facing cube.Face + // Powered is whether the dropper is powered or not. + Powered bool + // CustomName is the custom name of the dropper. This name is displayed when the dropper is opened, and may include + // colour codes. + CustomName string + + inventory *inventory.Inventory + viewerMu *sync.RWMutex + viewers map[ContainerViewer]struct{} +} + +// NewDropper creates a new initialised dropper. The inventory is properly initialised. +func NewDropper() Dropper { + d := Dropper{ + viewerMu: new(sync.RWMutex), + viewers: make(map[ContainerViewer]struct{}), + } + + d.inventory = inventory.New(9, func(slot int, before, after item.Stack) { + d.viewerMu.RLock() + defer d.viewerMu.RUnlock() + for viewer := range d.viewers { + viewer.ViewSlotChange(slot, after) + } + }) + return d +} + +// BreakInfo ... +func (d Dropper) BreakInfo() BreakInfo { + return newBreakInfo(3.5, pickaxeHarvestable, pickaxeEffective, oneOf(d)).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { + for _, i := range d.Inventory(tx, pos).Clear() { + dropItem(tx, i, pos.Vec3()) + } + }) +} + +// WithName returns the dropper after applying a specific name to the block. +func (d Dropper) WithName(a ...any) world.Item { + d.CustomName = strings.TrimSuffix(fmt.Sprintln(a...), "\n") + return d +} + +// Inventory returns the inventory of the dropper. The size of the inventory will be 9. +func (d Dropper) Inventory(*world.Tx, cube.Pos) *inventory.Inventory { + return d.inventory +} + +// AddViewer adds a viewer to the dropper, so that it is updated whenever the inventory of the dropper is changed. +func (d Dropper) AddViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) { + d.viewerMu.Lock() + defer d.viewerMu.Unlock() + d.viewers[v] = struct{}{} +} + +// RemoveViewer removes a viewer from the dropper, so that slot updates in the inventory are no longer sent to +// it. +func (d Dropper) RemoveViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) { + d.viewerMu.Lock() + defer d.viewerMu.Unlock() + if len(d.viewers) == 0 { + return + } + delete(d.viewers, v) +} + +// Activate ... +func (d Dropper) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.User, _ *item.UseContext) bool { + if opener, ok := u.(ContainerOpener); ok { + opener.OpenBlockContainer(pos, tx) + return true + } + return false +} + +// UseOnBlock ... +func (d Dropper) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, d) + if !used { + return + } + //noinspection GoAssignmentToReceiver + d = NewDropper() + d.Facing = calculateFace(user, pos, true) + + place(tx, pos, d, user, ctx) + return placed(ctx) +} + +// RedstoneUpdate ... +func (d Dropper) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { + powered := receivedRedstonePower(pos, tx) + if powered == d.Powered { + return + } + + d.Powered = powered + tx.SetBlock(pos, d, nil) + if d.Powered { + tx.ScheduleBlockUpdate(pos, d, time.Millisecond*200) + } +} + +// ScheduledTick ... +func (d Dropper) ScheduledTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { + slot, ok := d.randomSlotFromInventory(r) + if !ok { + tx.PlaySound(pos.Vec3(), sound.DispenseFail{}) + return + } + + it, _ := d.Inventory(tx, pos).Item(slot) + if c, ok := tx.Block(pos.Side(d.Facing)).(Container); ok { + if _, err := c.Inventory(tx, pos).AddItem(it.Grow(-it.Count() + 1)); err != nil { + return + } + _ = d.Inventory(tx, pos).SetItem(slot, it.Grow(-1)) + return + } + + _ = d.Inventory(tx, pos).SetItem(slot, it.Grow(-1)) + + dist := r.Float64()/10 + 0.2 + sourcePos := pos.Vec3Centre().Add(cube.Pos{}.Side(d.Facing).Vec3().Mul(0.7)) + + xOffset, zOffset := 0.0, 0.0 + if axis := d.Facing.Axis(); axis == cube.X { + xOffset = 1.0 + } else if axis == cube.Z { + zOffset = 1.0 + } + + xMultiplier, zMultiplier := -1.0, -1.0 + if d.Facing.Positive() { + xMultiplier, zMultiplier = 1.0, 1.0 + } + + create := tx.World().EntityRegistry().Config().Item + opts := world.EntitySpawnOpts{Position: sourcePos, Velocity: mgl64.Vec3{ + (r.Float64()*2-1)*6*0.0075 + xOffset*xMultiplier*dist, + (r.Float64()*2-1)*6*0.0075 + 0.2, + (r.Float64()*2-1)*6*0.0075 + zOffset*zMultiplier*dist, + }} + + tx.AddEntity(create(opts, it.Grow(-it.Count()+1))) + tx.AddParticle(pos.Vec3(), particle.Dispense{}) + tx.PlaySound(pos.Vec3(), sound.Dispense{}) +} + +// randomSlotFromInventory returns a random slot from the inventory of the dropper. If the inventory is empty, the +// second return value is false. +func (d Dropper) randomSlotFromInventory(r *rand.Rand) (int, bool) { + slots := make([]int, 0, d.inventory.Size()) + for slot, it := range d.inventory.Slots() { + if !it.Empty() { + slots = append(slots, slot) + } + } + if len(slots) == 0 { + return 0, false + } + return slots[r.IntN(len(slots))], true +} + +// EncodeItem ... +func (Dropper) EncodeItem() (name string, meta int16) { + return "minecraft:dropper", 0 +} + +// EncodeBlock ... +func (d Dropper) EncodeBlock() (string, map[string]any) { + return "minecraft:dropper", map[string]any{ + "facing_direction": int32(d.Facing), + "triggered_bit": d.Powered, + } +} + +// EncodeNBT ... +func (d Dropper) EncodeNBT() map[string]any { + if d.inventory == nil { + facing, powered, customName := d.Facing, d.Powered, d.CustomName + //noinspection GoAssignmentToReceiver + d = NewDropper() + d.Facing, d.Powered, d.CustomName = facing, powered, customName + } + m := map[string]any{ + "Items": nbtconv.InvToNBT(d.inventory), + "id": "Dropper", + } + if d.CustomName != "" { + m["CustomName"] = d.CustomName + } + return m +} + +// DecodeNBT ... +func (d Dropper) DecodeNBT(data map[string]any) any { + facing, powered := d.Facing, d.Powered + //noinspection GoAssignmentToReceiver + d = NewDropper() + d.Facing = facing + d.Powered = powered + d.CustomName = nbtconv.String(data, "CustomName") + nbtconv.InvFromNBT(d.inventory, nbtconv.Slice(data, "Items")) + return d +} + +// allDroppers ... +func allDroppers() (droppers []world.Block) { + for _, f := range cube.Faces() { + for _, p := range []bool{false, true} { + droppers = append(droppers, Dropper{Facing: f, Powered: p}) + } + } + return droppers +} diff --git a/server/block/hash.go b/server/block/hash.go index 0381c1ca7..43810ad27 100644 --- a/server/block/hash.go +++ b/server/block/hash.go @@ -65,6 +65,7 @@ const ( hashDragonEgg hashDriedKelp hashDripstone + hashDropper hashEmerald hashEmeraldOre hashEnchantingTable @@ -450,6 +451,10 @@ func (Dripstone) Hash() (uint64, uint64) { return hashDripstone, 0 } +func (d Dropper) Hash() (uint64, uint64) { + return hashDropper, uint64(d.Facing) | uint64(boolByte(d.Powered))<<3 +} + func (Emerald) Hash() (uint64, uint64) { return hashEmerald, 0 } diff --git a/server/block/register.go b/server/block/register.go index 4cb436c21..6e7a1a677 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -154,6 +154,7 @@ func init() { registerAll(allDoors()) registerAll(allDoubleFlowers()) registerAll(allDoubleTallGrass()) + registerAll(allDroppers()) registerAll(allEndRods()) registerAll(allEnderChests()) registerAll(allFarmland()) @@ -271,6 +272,7 @@ func init() { world.RegisterItem(DragonEgg{}) world.RegisterItem(DriedKelp{}) world.RegisterItem(Dripstone{}) + world.RegisterItem(Dropper{}) world.RegisterItem(Emerald{}) world.RegisterItem(EnchantingTable{}) world.RegisterItem(EndBricks{}) diff --git a/server/session/world.go b/server/session/world.go index bb0f9e97a..c36300eee 100644 --- a/server/session/world.go +++ b/server/session/world.go @@ -421,6 +421,11 @@ func (s *Session) ViewParticle(pos mgl64.Vec3, p world.Particle) { EventData: (int32(pa.Colour.A) << 24) | (int32(pa.Colour.R) << 16) | (int32(pa.Colour.G) << 8) | int32(pa.Colour.B), Position: vec64To32(pos), }) + case particle.Dispense: + s.writePacket(&packet.LevelEvent{ + EventType: packet.LevelEventParticlesShoot, + Position: vec64To32(pos), + }) case particle.Effect: s.writePacket(&packet.LevelEvent{ EventType: packet.LevelEventParticleLegacyEvent | 33, @@ -807,6 +812,10 @@ func (s *Session) playSound(pos mgl64.Vec3, t world.Sound, disableRelative bool) pk.SoundType = packet.SoundEventComposterFillLayer case sound.ComposterReady: pk.SoundType = packet.SoundEventComposterReady + case sound.DispenseFail: + pk.SoundType = packet.SoundEventBlockClickFail + case sound.Dispense: + pk.SoundType = packet.SoundEventBlockClick case sound.LecternBookPlace: pk.SoundType = packet.SoundEventLecternBookPlace case sound.Totem: @@ -1096,6 +1105,8 @@ func (s *Session) openNormalContainer(b block.Container, pos cube.Pos, tx *world containerType = protocol.ContainerTypeSmoker case block.Hopper: containerType = protocol.ContainerTypeHopper + case block.Dropper: + containerType = protocol.ContainerTypeDropper } s.writePacket(&packet.ContainerOpen{ diff --git a/server/world/particle/block.go b/server/world/particle/block.go index d6b02b052..75229e370 100644 --- a/server/world/particle/block.go +++ b/server/world/particle/block.go @@ -81,6 +81,9 @@ type LavaDrip struct{ particle } // Lava is a particle that shows up randomly above lava. type Lava struct{ particle } +// Dispense is a particle that shows up when a dispenser or dropper dispenses an item. +type Dispense struct{ particle } + // particle serves as a base for all particles in this package. type particle struct{} diff --git a/server/world/sound/block.go b/server/world/sound/block.go index cdb1b9558..8ec4735fb 100644 --- a/server/world/sound/block.go +++ b/server/world/sound/block.go @@ -191,6 +191,12 @@ type PowerOn struct{ sound } // PowerOff is a sound played when a redstone component is powered off. type PowerOff struct{ sound } +// DispenseFail is a sound played when a dispenser fails to dispense an item. +type DispenseFail struct{ sound } + +// Dispense is a sound played when a dispenser dispenses an item. +type Dispense struct{ sound } + // LecternBookPlace is a sound played when a book is placed in a lectern. type LecternBookPlace struct{ sound } From 65efd3a3acd8b4e12c5cf500d3a882cc17139213 Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 6 Jan 2025 21:27:17 -0800 Subject: [PATCH 08/33] update to rand v2 --- server/block/target.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/block/target.go b/server/block/target.go index 1fddd310a..1700b8dcf 100644 --- a/server/block/target.go +++ b/server/block/target.go @@ -8,7 +8,7 @@ import ( "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" "math" - "math/rand" + "math/rand/v2" "time" ) From 71c7f66bad7435687c9797537c77aa70eaee3204 Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 6 Jan 2025 23:13:08 -0800 Subject: [PATCH 09/33] add jukebox logic --- server/block/jukebox.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/server/block/jukebox.go b/server/block/jukebox.go index 20520bebe..edfb7604e 100644 --- a/server/block/jukebox.go +++ b/server/block/jukebox.go @@ -92,6 +92,7 @@ func (j Jukebox) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.User, } } } + updateAroundRedstone(pos, tx) return true } @@ -105,6 +106,24 @@ func (j Jukebox) Disc() (sound.DiscType, bool) { return sound.DiscType{}, false } +// WeakPower ... +func (j Jukebox) WeakPower(pos cube.Pos, face cube.Face, tx *world.Tx, accountForDust bool) int { + if !j.Item.Empty() { + return 15 + } + return 0 +} + +// StrongPower ... +func (j Jukebox) StrongPower(pos cube.Pos, face cube.Face, tx *world.Tx, accountForDust bool) int { + return j.WeakPower(pos, face, tx, accountForDust) +} + +// RedstoneSource ... +func (j Jukebox) RedstoneSource() bool { + return !j.Item.Empty() +} + // EncodeNBT ... func (j Jukebox) EncodeNBT() map[string]any { m := map[string]any{"id": "Jukebox"} From d753c60b49e5269b47fd563ea12d078e89de210e Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 6 Jan 2025 23:26:43 -0800 Subject: [PATCH 10/33] remove refactored code --- server/cmd/parameter.go | 2 +- server/world/conf.go | 2 +- server/world/entity.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/cmd/parameter.go b/server/cmd/parameter.go index 84a1ec900..d386a090c 100644 --- a/server/cmd/parameter.go +++ b/server/cmd/parameter.go @@ -23,7 +23,7 @@ type Parameter interface { // // type GameMode string // func (GameMode) Type() string { return "GameMode" } -// func (GameMode) Options(RedstoneSource) []string { return []string{"survival", "creative"} } +// func (GameMode) Options(Source) []string { return []string{"survival", "creative"} } // // Their values will then automatically be set to whichever option returned in Enum.Options is selected by // the user. diff --git a/server/world/conf.go b/server/world/conf.go index 1775b6089..3514b3a5c 100644 --- a/server/world/conf.go +++ b/server/world/conf.go @@ -44,7 +44,7 @@ type Config struct { // will stop random ticking altogether, while setting it higher results in // faster ticking. RandomTickSpeed int - // RandSource is the rand.RedstoneSource used for generation of random numbers in a + // RandSource is the rand.Source used for generation of random numbers in a // World, such as when selecting blocks to tick or when deciding where to // strike lightning. If set to nil, RandSource defaults to a `rand.PCG` // source seeded with `time.Now().UnixNano()`. PCG is significantly faster diff --git a/server/world/entity.go b/server/world/entity.go index 378dd4f86..7beaf125e 100644 --- a/server/world/entity.go +++ b/server/world/entity.go @@ -332,11 +332,11 @@ type DamageSource interface { // ReducedByArmour checks if the source of damage may be reduced if the // receiver of the damage is wearing armour. ReducedByArmour() bool - // ReducedByResistance specifies if the RedstoneSource is affected by the resistance + // ReducedByResistance specifies if the Source is affected by the resistance // effect. If false, damage dealt to an Entity with this source will not be // lowered if the Entity has the resistance effect. ReducedByResistance() bool - // Fire specifies if the RedstoneSource is fire related and should be ignored when + // Fire specifies if the Source is fire related and should be ignored when // an Entity has the fire resistance effect. Fire() bool } From 3eaa037a2e9173c6b8691bab62f0efe4b63f4162 Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 6 Jan 2025 23:27:53 -0800 Subject: [PATCH 11/33] add note block logic --- server/block/note.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server/block/note.go b/server/block/note.go index 0d1c40bae..17cf5cce0 100644 --- a/server/block/note.go +++ b/server/block/note.go @@ -35,6 +35,13 @@ func (n Note) instrument(pos cube.Pos, tx *world.Tx) sound.Instrument { return sound.Piano() } +// RedstoneUpdate ... +func (n Note) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { + if receivedRedstonePower(pos, tx) { + n.playNote(pos, tx) + } +} + // DecodeNBT ... func (n Note) DecodeNBT(data map[string]any) any { n.Pitch = int(nbtconv.Uint8(data, "note")) From 536b5d81d0946dadf2ea95a6c9c1f38a31bd71da Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 6 Jan 2025 23:44:28 -0800 Subject: [PATCH 12/33] Don't allow note to play with block on top --- server/block/note.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/block/note.go b/server/block/note.go index 17cf5cce0..5e6878a4d 100644 --- a/server/block/note.go +++ b/server/block/note.go @@ -38,6 +38,10 @@ func (n Note) instrument(pos cube.Pos, tx *world.Tx) sound.Instrument { // RedstoneUpdate ... func (n Note) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { if receivedRedstonePower(pos, tx) { + if _, ok := tx.Block(pos.Side(cube.FaceUp)).(Air); !ok { + return + } + n.playNote(pos, tx) } } From a07805fe5a8556a5f982b29aea1e246e54d10212 Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 7 Jan 2025 00:54:39 -0800 Subject: [PATCH 13/33] implement more redstone components --- server/block/hash.go | 25 ++ server/block/lava.go | 5 + server/block/melon.go | 5 + server/block/model/diode.go | 19 ++ server/block/moving.go | 71 ++++++ server/block/observer.go | 105 ++++++++ server/block/obsidian.go | 5 + server/block/piston.go | 344 +++++++++++++++++++++++++++ server/block/piston_arm_collision.go | 70 ++++++ server/block/piston_resolver.go | 142 +++++++++++ server/block/pumpkin.go | 5 + server/block/redstone.go | 10 + server/block/redstone_repeater.go | 166 +++++++++++++ server/block/redstone_wire.go | 7 +- server/block/register.go | 9 + server/block/sign.go | 5 + server/block/water.go | 5 + server/block/wood_door.go | 5 + server/session/world.go | 4 + server/world/sound/block.go | 6 + 20 files changed, 1012 insertions(+), 1 deletion(-) create mode 100644 server/block/model/diode.go create mode 100644 server/block/moving.go create mode 100644 server/block/observer.go create mode 100644 server/block/piston.go create mode 100644 server/block/piston_arm_collision.go create mode 100644 server/block/piston_resolver.go create mode 100644 server/block/redstone_repeater.go diff --git a/server/block/hash.go b/server/block/hash.go index 43810ad27..2145ecae5 100644 --- a/server/block/hash.go +++ b/server/block/hash.go @@ -117,6 +117,7 @@ const ( hashMelon hashMelonSeeds hashMossCarpet + hashMoving hashMud hashMudBricks hashMuddyMangroveRoots @@ -130,10 +131,13 @@ const ( hashNetherite hashNetherrack hashNote + hashObserver hashObsidian hashPackedIce hashPackedMud hashPinkPetals + hashPiston + hashPistonArmCollision hashPlanks hashPodzol hashPolishedBlackstoneBrick @@ -154,6 +158,7 @@ const ( hashRedstoneBlock hashRedstoneLamp hashRedstoneOre + hashRedstoneRepeater hashRedstoneTorch hashRedstoneWire hashReinforcedDeepslate @@ -659,6 +664,10 @@ func (MossCarpet) Hash() (uint64, uint64) { return hashMossCarpet, 0 } +func (Moving) Hash() (uint64, uint64) { + return hashMoving, 0 +} + func (Mud) Hash() (uint64, uint64) { return hashMud, 0 } @@ -711,6 +720,10 @@ func (Note) Hash() (uint64, uint64) { return hashNote, 0 } +func (o Observer) Hash() (uint64, uint64) { + return hashObserver, uint64(o.Facing) | uint64(boolByte(o.Powered))<<3 +} + func (o Obsidian) Hash() (uint64, uint64) { return hashObsidian, uint64(boolByte(o.Crying)) } @@ -727,6 +740,14 @@ func (p PinkPetals) Hash() (uint64, uint64) { return hashPinkPetals, uint64(p.AdditionalCount) | uint64(p.Facing)<<8 } +func (p Piston) Hash() (uint64, uint64) { + return hashPiston, uint64(p.Facing) | uint64(boolByte(p.Sticky))<<3 +} + +func (c PistonArmCollision) Hash() (uint64, uint64) { + return hashPistonArmCollision, uint64(c.Facing) +} + func (p Planks) Hash() (uint64, uint64) { return hashPlanks, uint64(p.Wood.Uint8()) } @@ -807,6 +828,10 @@ func (r RedstoneOre) Hash() (uint64, uint64) { return hashRedstoneOre, uint64(r.Type.Uint8()) | uint64(boolByte(r.Lit))<<1 } +func (r RedstoneRepeater) Hash() (uint64, uint64) { + return hashRedstoneRepeater, uint64(r.Facing) | uint64(boolByte(r.Powered))<<2 | uint64(r.Delay)<<3 +} + func (t RedstoneTorch) Hash() (uint64, uint64) { return hashRedstoneTorch, uint64(t.Facing) | uint64(boolByte(t.Lit))<<3 } diff --git a/server/block/lava.go b/server/block/lava.go index 239b0ac0e..6134f9b6b 100644 --- a/server/block/lava.go +++ b/server/block/lava.go @@ -87,6 +87,11 @@ func (Lava) LightEmissionLevel() uint8 { return 15 } +// PistonBreakable ... +func (Lava) PistonBreakable() bool { + return true +} + // NeighbourUpdateTick ... func (l Lava) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { if !l.Harden(pos, tx, nil) { diff --git a/server/block/melon.go b/server/block/melon.go index 142d15f9e..5ccc9e77e 100644 --- a/server/block/melon.go +++ b/server/block/melon.go @@ -20,6 +20,11 @@ func (Melon) CompostChance() float64 { return 0.65 } +// PistonBreakable ... +func (Melon) PistonBreakable() bool { + return true +} + // EncodeItem ... func (Melon) EncodeItem() (name string, meta int16) { return "minecraft:melon_block", 0 diff --git a/server/block/model/diode.go b/server/block/model/diode.go new file mode 100644 index 000000000..b2c08a352 --- /dev/null +++ b/server/block/model/diode.go @@ -0,0 +1,19 @@ +package model + +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/world" +) + +// Diode is a model used by redstone gates, such as repeaters and comparators. +type Diode struct{} + +// BBox ... +func (Diode) BBox(cube.Pos, world.BlockSource) []cube.BBox { + return []cube.BBox{full.ExtendTowards(cube.FaceDown, 0.875)} +} + +// FaceSolid ... +func (Diode) FaceSolid(cube.Pos, cube.Face, world.BlockSource) bool { + return false +} diff --git a/server/block/moving.go b/server/block/moving.go new file mode 100644 index 000000000..2efb8e40b --- /dev/null +++ b/server/block/moving.go @@ -0,0 +1,71 @@ +package block + +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/internal/nbtconv" + "github.com/df-mc/dragonfly/server/world" +) + +// Moving ... +type Moving struct { + empty + transparent + + // Moving represents the block that is moving. + Moving world.Block + // Extra represents an extra block that is moving with the main block. + Extra world.Block + // Piston is the position of the piston that is moving the block. + Piston cube.Pos + // Expanding is true if the moving block is expanding, false if it is contracting. + Expanding bool +} + +// EncodeBlock ... +func (Moving) EncodeBlock() (string, map[string]any) { + return "minecraft:moving_block", nil +} + +// PistonImmovable ... +func (Moving) PistonImmovable() bool { + return true +} + +// EncodeNBT ... +func (b Moving) EncodeNBT() map[string]any { + if b.Moving == nil { + b.Moving = Air{} + } + if b.Extra == nil { + b.Extra = Air{} + } + data := map[string]any{ + "id": "MovingBlock", + "expanding": b.Expanding, + "movingBlock": nbtconv.WriteBlock(b.Moving), + "movingBlockExtra": nbtconv.WriteBlock(b.Extra), + "pistonPosX": int32(b.Piston.X()), + "pistonPosY": int32(b.Piston.Y()), + "pistonPosZ": int32(b.Piston.Z()), + } + if nbt, ok := b.Moving.(world.NBTer); ok { + data["movingEntity"] = nbt.EncodeNBT() + } + return data +} + +// DecodeNBT ... +func (b Moving) DecodeNBT(m map[string]any) any { + b.Expanding = nbtconv.Bool(m, "expanding") + b.Moving = nbtconv.Block(m, "movingBlock") + b.Extra = nbtconv.Block(m, "movingBlockExtra") + b.Piston = cube.Pos{ + int(nbtconv.Int32(m, "pistonPosX")), + int(nbtconv.Int32(m, "pistonPosY")), + int(nbtconv.Int32(m, "pistonPosZ")), + } + if nbt, ok := b.Moving.(world.NBTer); ok { + b.Moving = nbt.DecodeNBT(m["movingEntity"].(map[string]any)).(world.Block) + } + return b +} diff --git a/server/block/observer.go b/server/block/observer.go new file mode 100644 index 000000000..f26ff3c04 --- /dev/null +++ b/server/block/observer.go @@ -0,0 +1,105 @@ +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" + "math/rand/v2" + "time" +) + +// Observer is a block that emits a redstone signal when the block or fluid it faces experiences a change. +type Observer struct { + solid + + // Facing is the direction the observer is observing. + Facing cube.Face + // Powered is whether the observer is powered or not. + Powered bool +} + +// RedstoneSource ... +func (Observer) RedstoneSource() bool { + return true +} + +// RedstoneBlocking ... +func (Observer) RedstoneBlocking() bool { + return true +} + +// WeakPower ... +func (o Observer) WeakPower(pos cube.Pos, face cube.Face, tx *world.Tx, accountForDust bool) int { + return o.StrongPower(pos, face, tx, accountForDust) +} + +// StrongPower ... +func (o Observer) StrongPower(_ cube.Pos, face cube.Face, _ *world.Tx, _ bool) int { + if !o.Powered || face != o.Facing { + return 0 + } + return 15 +} + +// ScheduledTick ... +func (o Observer) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { + o.Powered = !o.Powered + if o.Powered { + tx.ScheduleBlockUpdate(pos, o, time.Millisecond*100) + } + tx.SetBlock(pos, o, nil) + updateDirectionalRedstone(pos, tx, o.Facing.Opposite()) +} + +// NeighbourUpdateTick ... +func (o Observer) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, tx *world.Tx) { + if pos.Side(o.Facing) != changedNeighbour { + return + } + if o.Powered { + return + } + tx.ScheduleBlockUpdate(pos, o, time.Millisecond*100) +} + +// UseOnBlock ... +func (o Observer) 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, o) + if !used { + return false + } + o.Facing = calculateFace(user, pos, false) + if o.Facing.Axis() == cube.Y { + o.Facing = o.Facing.Opposite() + } + + place(tx, pos, o, user, ctx) + return placed(ctx) +} + +// BreakInfo ... +func (o Observer) BreakInfo() BreakInfo { + return newBreakInfo(3, pickaxeHarvestable, pickaxeEffective, oneOf(o)).withBreakHandler(func(pos cube.Pos, tx *world.Tx, _ item.User) { + updateDirectionalRedstone(pos, tx, o.Facing.Opposite()) + }) +} + +// EncodeItem ... +func (o Observer) EncodeItem() (name string, meta int16) { + return "minecraft:observer", 0 +} + +// EncodeBlock ... +func (o Observer) EncodeBlock() (string, map[string]any) { + return "minecraft:observer", map[string]any{"minecraft:facing_direction": o.Facing.String(), "powered_bit": o.Powered} +} + +// allObservers ... +func allObservers() (observers []world.Block) { + for _, f := range cube.Faces() { + observers = append(observers, Observer{Facing: f}) + observers = append(observers, Observer{Facing: f, Powered: true}) + } + return +} diff --git a/server/block/obsidian.go b/server/block/obsidian.go index 5d99bd4a0..0e68d7b81 100644 --- a/server/block/obsidian.go +++ b/server/block/obsidian.go @@ -13,6 +13,11 @@ type Obsidian struct { Crying bool } +// PistonImmovable ... +func (o Obsidian) PistonImmovable() bool { + return !o.Crying +} + // LightEmissionLevel ... func (o Obsidian) LightEmissionLevel() uint8 { if o.Crying { diff --git a/server/block/piston.go b/server/block/piston.go new file mode 100644 index 000000000..0c774d24e --- /dev/null +++ b/server/block/piston.go @@ -0,0 +1,344 @@ +package block + +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/internal/nbtconv" + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/world" + "github.com/df-mc/dragonfly/server/world/sound" + "github.com/go-gl/mathgl/mgl64" + "math/rand/v2" + "time" +) + +// Piston is a block capable of pushing blocks, players, and mobs when given a redstone pulse. +type Piston struct { + solid + transparent + sourceWaterDisplacer + + // Facing represents the direction the piston is facing. + Facing cube.Face + // Sticky is true if the piston is sticky, false if not. + Sticky bool + + // AttachedBlocks ... + AttachedBlocks []cube.Pos + // BreakBlocks ... + BreakBlocks []cube.Pos + + // Progress is how far the block has been moved. It can either be 0.0, 0.5, or 1.0. + Progress float64 + // LastProgress ... + LastProgress float64 + + // State ... + State int + // NewState ... + NewState int +} + +// PistonImmovable represents a block that cannot be moved by a piston. +type PistonImmovable interface { + // PistonImmovable returns whether the block is immovable. + PistonImmovable() bool +} + +// PistonBreakable represents a block that can be broken by a piston. +type PistonBreakable interface { + // PistonBreakable returns whether the block can be broken by a piston. + PistonBreakable() bool +} + +// PistonUpdater represents a block that can be updated through a piston movement. +type PistonUpdater interface { + // PistonUpdate is called when a piston moves the block. + PistonUpdate(pos cube.Pos, tx *world.Tx) +} + +// BreakInfo ... +func (p Piston) BreakInfo() BreakInfo { + return newBreakInfo(1.5, alwaysHarvestable, pickaxeEffective, oneOf(Piston{Sticky: p.Sticky})) +} + +// SideClosed ... +func (Piston) SideClosed(cube.Pos, cube.Pos, *world.World) bool { + return false +} + +// EncodeItem ... +func (p Piston) EncodeItem() (name string, meta int16) { + if p.Sticky { + return "minecraft:sticky_piston", 0 + } + return "minecraft:piston", 0 +} + +// EncodeBlock ... +func (p Piston) EncodeBlock() (string, map[string]any) { + if p.Sticky { + return "minecraft:sticky_piston", map[string]any{"facing_direction": int32(p.Facing)} + } + return "minecraft:piston", map[string]any{"facing_direction": int32(p.Facing)} +} + +// UseOnBlock ... +func (p Piston) 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, p) + if !used { + return false + } + p.Facing = calculateFace(user, pos, false) + + place(tx, pos, p, user, ctx) + if placed(ctx) { + p.RedstoneUpdate(pos, tx) + return true + } + return false +} + +// EncodeNBT ... +func (p Piston) EncodeNBT() map[string]any { + attachedBlocks := make([]int32, 0, len(p.AttachedBlocks)*3) + for _, pos := range p.AttachedBlocks { + attachedBlocks = append(attachedBlocks, int32(pos[0]), int32(pos[1]), int32(pos[2])) + } + breakBlocks := make([]int32, 0, len(p.BreakBlocks)*3) + for _, pos := range p.BreakBlocks { + breakBlocks = append(breakBlocks, int32(pos[0]), int32(pos[1]), int32(pos[2])) + } + return map[string]any{ + "AttachedBlocks": attachedBlocks, + "BreakBlocks": breakBlocks, + + "LastProgress": float32(p.LastProgress), + "Progress": float32(p.Progress), + + "NewState": uint8(p.NewState), + "State": uint8(p.State), + + "Sticky": p.Sticky, + + "id": "PistonArm", + } +} + +// DecodeNBT ... +func (p Piston) DecodeNBT(m map[string]any) any { + if attached := nbtconv.Slice(m, "AttachedBlocks"); attached != nil { + p.AttachedBlocks = make([]cube.Pos, 0, len(attached)/3) + for i := 0; i < len(attached); i += 3 { + p.AttachedBlocks = append(p.AttachedBlocks, cube.Pos{ + int(attached[i].(int32)), + int(attached[i+1].(int32)), + int(attached[i+2].(int32)), + }) + } + } + if breakBlocks := nbtconv.Slice(m, "BreakBlocks"); breakBlocks != nil { + p.BreakBlocks = make([]cube.Pos, 0, len(breakBlocks)/3) + for i := 0; i < len(breakBlocks); i += 3 { + p.BreakBlocks = append(p.BreakBlocks, cube.Pos{ + int(breakBlocks[i].(int32)), + int(breakBlocks[i+1].(int32)), + int(breakBlocks[i+2].(int32)), + }) + } + } + p.LastProgress = float64(nbtconv.Float32(m, "LastProgress")) + p.Progress = float64(nbtconv.Float32(m, "Progress")) + p.NewState = int(nbtconv.Uint8(m, "NewState")) + p.State = int(nbtconv.Uint8(m, "State")) + p.Sticky = nbtconv.Bool(m, "Sticky") + return p +} + +// RedstoneUpdate ... +func (p Piston) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { + tx.ScheduleBlockUpdate(pos, p, time.Millisecond*50) +} + +// ScheduledTick ... +func (p Piston) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { + if receivedRedstonePower(pos, tx, p.armFace()) { + if !p.push(pos, tx) { + return + } + } else if !p.pull(pos, tx) { + return + } + tx.ScheduleBlockUpdate(pos, p, time.Millisecond*50) +} + +// armFace ... +func (p Piston) armFace() cube.Face { + if p.Facing.Axis() == cube.Y { + return p.Facing + } + return p.Facing.Opposite() +} + +// push ... +func (p Piston) push(pos cube.Pos, tx *world.Tx) bool { + if p.State == 0 { + resolver := pistonResolve(tx, pos, p, true) + if !resolver.success { + return false + } + + for _, breakPos := range resolver.breakPositions { + p.BreakBlocks = append(p.BreakBlocks, breakPos) + if b, ok := tx.Block(breakPos).(Breakable); ok { + tx.SetBlock(breakPos, nil, nil) + for _, drop := range b.BreakInfo().Drops(item.ToolNone{}, nil) { + dropItem(tx, drop, breakPos.Vec3Centre()) + } + } + } + + face := p.armFace() + for _, attachedPos := range resolver.attachedPositions { + side := attachedPos.Side(face) + p.AttachedBlocks = append(p.AttachedBlocks, attachedPos) + + tx.SetBlock(side, Moving{Piston: pos, Moving: tx.Block(attachedPos), Expanding: true}, nil) + tx.SetBlock(attachedPos, nil, nil) + updateAroundRedstone(attachedPos, tx) + } + + p.State = 1 + tx.SetBlock(pos.Side(face), PistonArmCollision{Facing: p.Facing}, nil) + } else if p.State == 1 { + if p.Progress == 1 { + p.State = 2 + } + p.LastProgress = p.Progress + + if p.State == 1 { + p.Progress += 0.5 + if p.Progress == 0.5 { + tx.PlaySound(pos.Vec3Centre(), sound.PistonExtend{}) + } + } + + if p.State == 2 { + face := p.armFace() + for _, attachedPos := range p.AttachedBlocks { + side := attachedPos.Side(face) + moving, ok := tx.Block(side).(Moving) + if !ok { + continue + } + + tx.SetBlock(side, moving.Moving, nil) + if u, ok := moving.Moving.(RedstoneUpdater); ok { + u.RedstoneUpdate(side, tx) + } + if u, ok := moving.Moving.(PistonUpdater); ok { + u.PistonUpdate(side, tx) + } + updateAroundRedstone(side, tx) + } + + p.AttachedBlocks = nil + p.BreakBlocks = nil + } + } else if p.State == 3 { + return p.pull(pos, tx) + } else { + return false + } + + p.NewState = p.State + tx.SetBlock(pos, p, nil) + return true +} + +// pull ... +func (p Piston) pull(pos cube.Pos, tx *world.Tx) bool { + if p.State == 2 { + face := p.armFace() + tx.SetBlock(pos.Side(face), nil, nil) + + resolver := pistonResolve(tx, pos, p, false) + if !resolver.success { + return false + } + + for _, breakPos := range resolver.breakPositions { + p.BreakBlocks = append(p.BreakBlocks, breakPos) + if b, ok := tx.Block(breakPos).(Breakable); ok { + tx.SetBlock(breakPos, nil, nil) + for _, drop := range b.BreakInfo().Drops(item.ToolNone{}, nil) { + dropItem(tx, drop, breakPos.Vec3Centre()) + } + } + } + + face = face.Opposite() + for _, attachedPos := range resolver.attachedPositions { + side := attachedPos.Side(face) + p.AttachedBlocks = append(p.AttachedBlocks, attachedPos) + + tx.SetBlock(side, Moving{Piston: pos, Moving: tx.Block(attachedPos)}, nil) + tx.SetBlock(attachedPos, nil, nil) + updateAroundRedstone(attachedPos, tx) + } + + p.State = 3 + } else if p.State == 3 { + if p.Progress == 0 { + p.State = 0 + } + p.LastProgress = p.Progress + + if p.State == 3 { + p.Progress -= 0.5 + if p.Progress == 0.5 { + tx.PlaySound(pos.Vec3Centre(), sound.PistonRetract{}) + } + } + + if p.State == 0 { + face := p.armFace() + for _, attachedPos := range p.AttachedBlocks { + side := attachedPos.Side(face.Opposite()) + moving, ok := tx.Block(side).(Moving) + if !ok { + continue + } + + tx.SetBlock(side, moving.Moving, nil) + if r, ok := moving.Moving.(RedstoneUpdater); ok { + r.RedstoneUpdate(side, tx) + } + if r, ok := moving.Moving.(PistonUpdater); ok { + r.PistonUpdate(side, tx) + } + updateAroundRedstone(side, tx) + } + + p.AttachedBlocks = nil + p.BreakBlocks = nil + } + } else if p.State == 1 { + return p.push(pos, tx) + } else { + return false + } + + p.NewState = p.State + tx.SetBlock(pos, p, nil) + return true +} + +// allPistons ... +func allPistons() (pistons []world.Block) { + for _, f := range cube.Faces() { + for _, s := range []bool{false, true} { + pistons = append(pistons, Piston{Facing: f, Sticky: s}) + } + } + return +} diff --git a/server/block/piston_arm_collision.go b/server/block/piston_arm_collision.go new file mode 100644 index 000000000..33f6cd481 --- /dev/null +++ b/server/block/piston_arm_collision.go @@ -0,0 +1,70 @@ +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" +) + +// PistonArmCollision is a block that is used when a piston is extended and colliding with a block. +type PistonArmCollision struct { + empty + transparent + sourceWaterDisplacer + + // Facing represents the direction the piston is facing. + Facing cube.Face +} + +// PistonImmovable ... +func (PistonArmCollision) PistonImmovable() bool { + return true +} + +// SideClosed ... +func (PistonArmCollision) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { + return false +} + +// EncodeBlock ... +func (c PistonArmCollision) EncodeBlock() (string, map[string]any) { + return "minecraft:piston_arm_collision", map[string]any{"facing_direction": int32(c.Facing)} +} + +// BreakInfo ... +func (c PistonArmCollision) BreakInfo() BreakInfo { + return newBreakInfo(1.5, alwaysHarvestable, pickaxeEffective, simpleDrops()).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { + pistonPos := pos.Side(c.pistonFace()) + if p, ok := tx.Block(pistonPos).(Piston); ok { + tx.SetBlock(pistonPos, nil, nil) + if g, ok := u.(interface { + GameMode() world.GameMode + }); !ok || !g.GameMode().CreativeInventory() { + dropItem(tx, item.NewStack(Piston{Sticky: p.Sticky}, 1), pos.Vec3Centre()) + } + } + }) +} + +// NeighbourUpdateTick ... +func (c PistonArmCollision) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { + if _, ok := tx.Block(pos.Side(c.pistonFace())).(Piston); !ok { + tx.SetBlock(pos, nil, nil) + } +} + +// pistonFace ... +func (c PistonArmCollision) pistonFace() cube.Face { + if c.Facing.Axis() != cube.Y { + return c.Facing + } + return c.Facing.Opposite() +} + +// allPistonArmCollisions ... +func allPistonArmCollisions() (pistonArmCollisions []world.Block) { + for _, f := range cube.Faces() { + pistonArmCollisions = append(pistonArmCollisions, PistonArmCollision{Facing: f}) + } + return +} diff --git a/server/block/piston_resolver.go b/server/block/piston_resolver.go new file mode 100644 index 000000000..a252185cb --- /dev/null +++ b/server/block/piston_resolver.go @@ -0,0 +1,142 @@ +package block + +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/world" + "sort" +) + +// pistonResolver ... +type pistonResolver struct { + tx *world.Tx + pos cube.Pos + + attachedPositions []cube.Pos + breakPositions []cube.Pos + + history map[cube.Pos]struct{} + + success bool +} + +// pistonResolve ... +func pistonResolve(tx *world.Tx, pos cube.Pos, piston Piston, push bool) *pistonResolver { + r := &pistonResolver{ + tx: tx, + pos: pos, + + history: make(map[cube.Pos]struct{}), + } + + face := piston.armFace() + if push { + if r.calculateBlocks(r.pos.Side(face), face, face) { + r.success = true + } + } else { + if piston.Sticky { + r.calculateBlocks(r.pos.Side(face).Side(face), face, face.Opposite()) + } + r.success = true + } + + sort.SliceStable(r.attachedPositions, func(i, j int) bool { + posOne := r.attachedPositions[i] + posTwo := r.attachedPositions[j] + + pushI := 1 + if !push { + pushI = -1 + } + + positive := 1 + if !face.Positive() { + positive = -1 + } + + offset := posOne.Sub(posTwo) + direction := pushI * positive + switch face.Axis() { + case cube.Y: + return offset.Y()*direction > 0 + case cube.Z: + return offset.Z()*direction > 0 + case cube.X: + return offset.X()*direction > 0 + } + panic("should never happen") + }) + return r +} + +// calculateBlocks ... +func (r *pistonResolver) calculateBlocks(pos cube.Pos, face cube.Face, breakFace cube.Face) bool { + if pos.Side(breakFace).OutOfBounds(r.tx.Range()) { + r.breakPositions = nil + r.attachedPositions = nil + return false + } + if _, ok := r.history[pos]; ok { + return true + } + r.history[pos] = struct{}{} + + block := r.tx.Block(pos) + if _, ok := block.(Air); ok { + return true + } + if !r.canMove(pos, block) { + if face == breakFace { + r.breakPositions = nil + r.attachedPositions = nil + return false + } + return true + } + if r.canBreak(block) { + if face == breakFace { + r.breakPositions = append(r.breakPositions, pos) + } + return true + } + + if _, ok := block.(GlazedTerracotta); ok && face != breakFace { + // Glazed terracotta can't be pushed, but can be pulled. + return true + } + + r.attachedPositions = append(r.attachedPositions, pos) + if len(r.attachedPositions) >= 13 { + r.breakPositions = nil + r.attachedPositions = nil + return false + } + + // account for slime. + if !r.calculateBlocks(pos.Side(breakFace), breakFace, breakFace) { + return false + } + + return true +} + +// canMove ... +func (r *pistonResolver) canMove(pos cube.Pos, block world.Block) bool { + if p, ok := block.(Piston); ok { + if r.pos == pos { + return false + } + return p.State == 0 + } + p, ok := block.(PistonImmovable) + return !ok || !p.PistonImmovable() +} + +// canBreak ... +func (r *pistonResolver) canBreak(block world.Block) bool { + if l, ok := block.(LiquidRemovable); ok && l.HasLiquidDrops() { + return true + } + p, ok := block.(PistonBreakable) + return ok && p.PistonBreakable() +} diff --git a/server/block/pumpkin.go b/server/block/pumpkin.go index 51777ef38..e67e98c59 100644 --- a/server/block/pumpkin.go +++ b/server/block/pumpkin.go @@ -73,6 +73,11 @@ func (p Pumpkin) KnockBackResistance() float64 { return 0 } +// PistonBreakable ... +func (Pumpkin) PistonBreakable() bool { + return true +} + // EncodeItem ... func (p Pumpkin) EncodeItem() (name string, meta int16) { if p.Carved { diff --git a/server/block/redstone.go b/server/block/redstone.go index 698682015..4ad1a15ac 100644 --- a/server/block/redstone.go +++ b/server/block/redstone.go @@ -101,6 +101,16 @@ func updateDirectionalRedstone(pos cube.Pos, tx *world.Tx, face cube.Face) { updateAroundRedstone(pos.Side(face), tx, face.Opposite()) } +// updateGateRedstone is used to update redstone gates on each face of the given offset centre position. +func updateGateRedstone(centre cube.Pos, tx *world.Tx, face cube.Face) { + pos := centre.Side(face.Opposite()) + if r, ok := tx.Block(pos).(RedstoneUpdater); ok { + r.RedstoneUpdate(pos, tx) + } + + updateAroundRedstone(pos, tx, face) +} + // receivedRedstonePower returns true if the given position is receiving power from any faces that aren't ignored. func receivedRedstonePower(pos cube.Pos, w *world.Tx, ignoredFaces ...cube.Face) bool { for _, face := range cube.Faces() { diff --git a/server/block/redstone_repeater.go b/server/block/redstone_repeater.go new file mode 100644 index 000000000..50f2f25cb --- /dev/null +++ b/server/block/redstone_repeater.go @@ -0,0 +1,166 @@ +package block + +import ( + "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" + "math/rand/v2" + "time" +) + +// RedstoneRepeater is a block used in redstone circuits to "repeat" redstone signals back to full strength, delay +// signals, prevent signals moving backwards, or to "lock" signals in one state. +type RedstoneRepeater struct { + transparent + flowingWaterDisplacer + + // Facing is the direction from the torch to the block. + Facing cube.Direction + // Powered is true if the repeater is powered by a redstone signal. + Powered bool + // Delay represents the delay of the repeater in redstone ticks. It is between the range of one to four. + Delay int +} + +// SideClosed ... +func (RedstoneRepeater) SideClosed(cube.Pos, cube.Pos, *world.World) bool { + return false +} + +// Model ... +func (RedstoneRepeater) Model() world.BlockModel { + return model.Diode{} +} + +// BreakInfo ... +func (r RedstoneRepeater) BreakInfo() BreakInfo { + return newBreakInfo(0, alwaysHarvestable, nothingEffective, oneOf(r)).withBreakHandler(func(pos cube.Pos, tx *world.Tx, _ item.User) { + updateGateRedstone(pos, tx, r.Facing.Face()) + }) +} + +// EncodeItem ... +func (RedstoneRepeater) EncodeItem() (name string, meta int16) { + return "minecraft:repeater", 0 +} + +// EncodeBlock ... +func (r RedstoneRepeater) EncodeBlock() (string, map[string]any) { + name := "minecraft:unpowered_repeater" + if r.Powered { + name = "minecraft:powered_repeater" + } + return name, map[string]any{ + "minecraft:cardinal_direction": r.Facing.String(), + "repeater_delay": int32(r.Delay), + } +} + +// UseOnBlock ... +func (r RedstoneRepeater) 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, r) + if !used { + return false + } + if d, ok := tx.Block(pos.Side(cube.FaceDown)).(LightDiffuser); ok && d.LightDiffusionLevel() == 0 { + return false + } + r.Facing = user.Rotation().Direction().Opposite() + + place(tx, pos, r, user, ctx) + if placed(ctx) { + r.RedstoneUpdate(pos, tx) + return true + } + return false +} + +// NeighbourUpdateTick ... +func (r RedstoneRepeater) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { + if d, ok := tx.Block(pos.Side(cube.FaceDown)).(LightDiffuser); ok && d.LightDiffusionLevel() == 0 { + tx.SetBlock(pos, nil, nil) + dropItem(tx, item.NewStack(r, 1), pos.Vec3Centre()) + } +} + +// Activate ... +func (r RedstoneRepeater) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, _ item.User, _ *item.UseContext) bool { + if r.Delay++; r.Delay > 3 { + r.Delay = 0 + } + tx.SetBlock(pos, r, nil) + return true +} + +// RedstoneUpdate ... +func (r RedstoneRepeater) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { + if r.Locked() { + // Ignore this update; the repeater is locked. + return + } + if r.inputStrength(pos, tx) > 0 != r.Powered { + tx.ScheduleBlockUpdate(pos, r, time.Duration(r.Delay+1)*time.Millisecond*100) + } +} + +// ScheduledTick ... +func (r RedstoneRepeater) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { + if r.Locked() { + // Ignore this tick; the repeater is locked. + return + } + + r.Powered = !r.Powered + tx.SetBlock(pos, r, nil) + updateGateRedstone(pos, tx, r.Facing.Face().Opposite()) + + if r.Powered && r.inputStrength(pos, tx) <= 0 { + tx.ScheduleBlockUpdate(pos, r, time.Duration(r.Delay+1)*time.Millisecond*100) + } + tx.SetBlock(pos, r, nil) + updateGateRedstone(pos, tx, r.Facing.Face()) +} + +// Locked ... +func (RedstoneRepeater) Locked() bool { + //TODO implement me + return false +} + +// RedstoneSource ... +func (r RedstoneRepeater) RedstoneSource() bool { + return r.Powered +} + +// WeakPower ... +func (r RedstoneRepeater) WeakPower(_ cube.Pos, face cube.Face, _ *world.Tx, _ bool) int { + if r.Powered && face == r.Facing.Face() { + return 15 + } + return 0 +} + +// StrongPower ... +func (r RedstoneRepeater) StrongPower(pos cube.Pos, face cube.Face, tx *world.Tx, accountForDust bool) int { + return r.WeakPower(pos, face, tx, accountForDust) +} + +// inputStrength ... +func (r RedstoneRepeater) inputStrength(pos cube.Pos, tx *world.Tx) int { + face := r.Facing.Face() + return tx.RedstonePower(pos.Side(face), face, true) +} + +// allRedstoneRepeaters ... +func allRedstoneRepeaters() (repeaters []world.Block) { + for _, d := range cube.Directions() { + for _, p := range []bool{false, true} { + for i := 0; i < 4; i++ { + repeaters = append(repeaters, RedstoneRepeater{Facing: d, Delay: i, Powered: p}) + } + } + } + return +} diff --git a/server/block/redstone_wire.go b/server/block/redstone_wire.go index 3cdb9edb0..6b5fa8ed3 100644 --- a/server/block/redstone_wire.go +++ b/server/block/redstone_wire.go @@ -171,7 +171,12 @@ func (RedstoneWire) connectsTo(block world.Block, face cube.Face, allowDirectSou if _, ok := block.(RedstoneWire); ok { return true } - // TODO: Account for other redstone blocks. + if r, ok := block.(RedstoneRepeater); ok { + return r.Facing.Face() == face || r.Facing.Face().Opposite() == face + } + if _, ok := block.(Piston); ok { + return true + } c, ok := block.(world.Conductor) return ok && allowDirectSources && c.RedstoneSource() } diff --git a/server/block/register.go b/server/block/register.go index 6e7a1a677..b4e837e0d 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -62,6 +62,7 @@ func init() { world.RegisterBlock(Lapis{}) world.RegisterBlock(Melon{}) world.RegisterBlock(MossCarpet{}) + world.RegisterBlock(Moving{}) world.RegisterBlock(MudBricks{}) world.RegisterBlock(Mud{}) world.RegisterBlock(NetherBrickFence{}) @@ -186,6 +187,9 @@ func init() { registerAll(allMuddyMangroveRoots()) registerAll(allNetherBricks()) registerAll(allNetherWart()) + registerAll(allObservers()) + registerAll(allPistonArmCollisions()) + registerAll(allPistons()) registerAll(allPinkPetals()) registerAll(allPlanks()) registerAll(allPotato()) @@ -195,6 +199,7 @@ func init() { registerAll(allPumpkins()) registerAll(allPurpurs()) registerAll(allQuartz()) + registerAll(allRedstoneRepeaters()) registerAll(allRedstoneTorches()) registerAll(allRedstoneWires()) registerAll(allSandstones()) @@ -325,10 +330,13 @@ func init() { world.RegisterItem(Netherite{}) world.RegisterItem(Netherrack{}) world.RegisterItem(Note{Pitch: 24}) + world.RegisterItem(Observer{}) world.RegisterItem(Obsidian{Crying: true}) world.RegisterItem(Obsidian{}) world.RegisterItem(PackedIce{}) world.RegisterItem(PackedMud{}) + world.RegisterItem(Piston{}) + world.RegisterItem(Piston{Sticky: true}) world.RegisterItem(PinkPetals{}) world.RegisterItem(Podzol{}) world.RegisterItem(PolishedBlackstoneBrick{Cracked: true}) @@ -351,6 +359,7 @@ func init() { world.RegisterItem(RedstoneOre{}) world.RegisterItem(RedstoneOre{Type: DeepslateOre()}) world.RegisterItem(RedstoneTorch{}) + world.RegisterItem(RedstoneRepeater{}) world.RegisterItem(RedstoneWire{}) world.RegisterItem(ReinforcedDeepslate{}) world.RegisterItem(ResinBricks{Chiseled: true}) diff --git a/server/block/sign.go b/server/block/sign.go index 2bf423bc9..5f6c3faae 100644 --- a/server/block/sign.go +++ b/server/block/sign.go @@ -56,6 +56,11 @@ func (s Sign) MaxCount() int { return 16 } +// PistonBreakable ... +func (Sign) PistonBreakable() bool { + return true +} + // FlammabilityInfo ... func (s Sign) FlammabilityInfo() FlammabilityInfo { return newFlammabilityInfo(0, 0, true) diff --git a/server/block/water.go b/server/block/water.go index cc830d66c..13919457e 100644 --- a/server/block/water.go +++ b/server/block/water.go @@ -62,6 +62,11 @@ func (w Water) WithDepth(depth int, falling bool) world.Liquid { return w } +// PistonBreakable ... +func (Water) PistonBreakable() bool { + return true +} + // LiquidFalling returns Water.Falling. func (w Water) LiquidFalling() bool { return w.Falling diff --git a/server/block/wood_door.go b/server/block/wood_door.go index c8260d3ab..f8e423682 100644 --- a/server/block/wood_door.go +++ b/server/block/wood_door.go @@ -47,6 +47,11 @@ func (d WoodDoor) Model() world.BlockModel { return model.Door{Facing: d.Facing, Open: d.Open, Right: d.Right} } +// PistonBreakable ... +func (WoodDoor) PistonBreakable() bool { + return true +} + // NeighbourUpdateTick ... func (d WoodDoor) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { if d.Top { diff --git a/server/session/world.go b/server/session/world.go index c36300eee..deb5c0b09 100644 --- a/server/session/world.go +++ b/server/session/world.go @@ -812,6 +812,10 @@ func (s *Session) playSound(pos mgl64.Vec3, t world.Sound, disableRelative bool) pk.SoundType = packet.SoundEventComposterFillLayer case sound.ComposterReady: pk.SoundType = packet.SoundEventComposterReady + case sound.PistonExtend: + pk.SoundType = packet.SoundEventPistonOut + case sound.PistonRetract: + pk.SoundType = packet.SoundEventPistonIn case sound.DispenseFail: pk.SoundType = packet.SoundEventBlockClickFail case sound.Dispense: diff --git a/server/world/sound/block.go b/server/world/sound/block.go index 8ec4735fb..49f3d55ec 100644 --- a/server/world/sound/block.go +++ b/server/world/sound/block.go @@ -191,6 +191,12 @@ type PowerOn struct{ sound } // PowerOff is a sound played when a redstone component is powered off. type PowerOff struct{ sound } +// PistonExtend is a sound played when a piston extends. +type PistonExtend struct{ sound } + +// PistonRetract is a sound played when a piston retracts. +type PistonRetract struct{ sound } + // DispenseFail is a sound played when a dispenser fails to dispense an item. type DispenseFail struct{ sound } From 441efced4e2a8db43b4519937bdd9b261056e28a Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 7 Jan 2025 17:38:18 -0800 Subject: [PATCH 14/33] merge conflicts --- server/block/tnt.go | 12 ++++++++++++ server/world/particle/block.go | 3 +++ 2 files changed, 15 insertions(+) diff --git a/server/block/tnt.go b/server/block/tnt.go index 4606f4512..158690504 100644 --- a/server/block/tnt.go +++ b/server/block/tnt.go @@ -40,6 +40,18 @@ func (t TNT) Ignite(pos cube.Pos, tx *world.Tx, _ world.Entity) bool { return true } +// NeighbourUpdateTick ... +func (t TNT) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { + t.RedstoneUpdate(pos, tx) +} + +// RedstoneUpdate ... +func (t TNT) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { + if receivedRedstonePower(pos, tx) { + t.Ignite(pos, tx, nil) + } +} + // Explode ... func (t TNT) Explode(_ mgl64.Vec3, pos cube.Pos, tx *world.Tx, _ ExplosionConfig) { spawnTnt(pos, tx, time.Second/2+time.Duration(rand.IntN(int(time.Second+time.Second/2)))) diff --git a/server/world/particle/block.go b/server/world/particle/block.go index 43130e2b3..1a2684584 100644 --- a/server/world/particle/block.go +++ b/server/world/particle/block.go @@ -84,6 +84,9 @@ type Lava struct{ particle } // DustPlume is a particle that shows up when an item is successfully inserted into a decorated pot. type DustPlume struct{ particle } +// Dispense is a particle that shows up when a dispenser or dropper dispenses an item. +type Dispense struct{ particle } + // particle serves as a base for all particles in this package. type particle struct{} From 25ab0a49f21a768bb65a1d50ae8b53e461fe43de Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 8 Jan 2025 00:04:04 -0800 Subject: [PATCH 15/33] fix dropper logic --- server/block/dropper.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/server/block/dropper.go b/server/block/dropper.go index 452a42c50..f384198f6 100644 --- a/server/block/dropper.go +++ b/server/block/dropper.go @@ -128,7 +128,7 @@ func (d Dropper) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { // ScheduledTick ... func (d Dropper) ScheduledTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { - slot, ok := d.randomSlotFromInventory(r) + slot, ok := d.firstSlotAvailableInventory() if !ok { tx.PlaySound(pos.Vec3(), sound.DispenseFail{}) return @@ -172,19 +172,15 @@ func (d Dropper) ScheduledTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { tx.PlaySound(pos.Vec3(), sound.Dispense{}) } -// randomSlotFromInventory returns a random slot from the inventory of the dropper. If the inventory is empty, the +// firstSlotAvailableInventory returns the first available item from the inventory of the dropper. If the inventory is empty, the // second return value is false. -func (d Dropper) randomSlotFromInventory(r *rand.Rand) (int, bool) { - slots := make([]int, 0, d.inventory.Size()) +func (d Dropper) firstSlotAvailableInventory() (int, bool) { for slot, it := range d.inventory.Slots() { if !it.Empty() { - slots = append(slots, slot) + return slot, true } } - if len(slots) == 0 { - return 0, false - } - return slots[r.IntN(len(slots))], true + return 0, false } // EncodeItem ... From f8b21098ffb7a29feec96f2fb74353b8b6c411a3 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 8 Jan 2025 00:32:24 -0800 Subject: [PATCH 16/33] wip dispensers --- server/block/dispenser.go | 262 ++++++++++++++++++++++++++++++++++++++ server/block/hash.go | 5 + server/block/register.go | 2 + server/session/world.go | 2 + 4 files changed, 271 insertions(+) create mode 100644 server/block/dispenser.go diff --git a/server/block/dispenser.go b/server/block/dispenser.go new file mode 100644 index 000000000..ba41abd0c --- /dev/null +++ b/server/block/dispenser.go @@ -0,0 +1,262 @@ +package block + +import ( + "fmt" + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/internal/nbtconv" + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/item/inventory" + "github.com/df-mc/dragonfly/server/item/potion" + "github.com/df-mc/dragonfly/server/world" + "github.com/df-mc/dragonfly/server/world/particle" + "github.com/df-mc/dragonfly/server/world/sound" + "github.com/go-gl/mathgl/mgl64" + "math/rand/v2" + "strings" + "sync" + "time" +) + +// Dispenser is a low-capacity storage block that can fire projectiles, use certain items or tools or place certain blocks, +// fluids or entities when given a redstone signal. Items that do not have unique dispenser functions are instead ejected into the world. +type Dispenser struct { + solid + + // Facing is the direction the dispenser is facing. + Facing cube.Face + // Powered is whether the dispenser is powered or not. + Powered bool + // CustomName is the custom name of the dispenser. This name is displayed when the dispenser is opened, and may include + // colour codes. + CustomName string + + inventory *inventory.Inventory + viewerMu *sync.RWMutex + viewers map[ContainerViewer]struct{} +} + +// NewDispenser creates a new initialised dispenser. The inventory is properly initialised. +func NewDispenser() Dispenser { + d := Dispenser{ + viewerMu: new(sync.RWMutex), + viewers: make(map[ContainerViewer]struct{}), + } + + d.inventory = inventory.New(9, func(slot int, before, after item.Stack) { + d.viewerMu.RLock() + defer d.viewerMu.RUnlock() + for viewer := range d.viewers { + viewer.ViewSlotChange(slot, after) + } + }) + return d +} + +// BreakInfo ... +func (d Dispenser) BreakInfo() BreakInfo { + return newBreakInfo(3.5, pickaxeHarvestable, pickaxeEffective, oneOf(d)).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { + for _, i := range d.Inventory(tx, pos).Clear() { + dropItem(tx, i, pos.Vec3()) + } + }) +} + +// WithName returns the dispenser after applying a specific name to the block. +func (d Dispenser) WithName(a ...any) world.Item { + d.CustomName = strings.TrimSuffix(fmt.Sprintln(a...), "\n") + return d +} + +// Inventory returns the inventory of the dispenser. The size of the inventory will be 9. +func (d Dispenser) Inventory(*world.Tx, cube.Pos) *inventory.Inventory { + return d.inventory +} + +// AddViewer adds a viewer to the dropper, so that it is updated whenever the inventory of the dispenser is changed. +func (d Dispenser) AddViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) { + d.viewerMu.Lock() + defer d.viewerMu.Unlock() + d.viewers[v] = struct{}{} +} + +// RemoveViewer removes a viewer from the dispenser, so that slot updates in the inventory are no longer sent to +// it. +func (d Dispenser) RemoveViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) { + d.viewerMu.Lock() + defer d.viewerMu.Unlock() + if len(d.viewers) == 0 { + return + } + delete(d.viewers, v) +} + +// Activate ... +func (d Dispenser) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.User, _ *item.UseContext) bool { + if opener, ok := u.(ContainerOpener); ok { + opener.OpenBlockContainer(pos, tx) + return true + } + return false +} + +// UseOnBlock ... +func (d Dispenser) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, d) + if !used { + return + } + //noinspection GoAssignmentToReceiver + d = NewDispenser() + d.Facing = calculateFace(user, pos, true) + + place(tx, pos, d, user, ctx) + return placed(ctx) +} + +// RedstoneUpdate ... +func (d Dispenser) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { + powered := receivedRedstonePower(pos, tx) + if powered == d.Powered { + return + } + + d.Powered = powered + tx.SetBlock(pos, d, nil) + if d.Powered { + tx.ScheduleBlockUpdate(pos, d, time.Millisecond*200) + } +} + +// ScheduledTick ... +func (d Dispenser) ScheduledTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { + slot, ok := d.randomSlotFromInventory(r) + if !ok { + tx.PlaySound(pos.Vec3(), sound.DispenseFail{}) + return + } + + it, _ := d.Inventory(tx, pos).Item(slot) + sidePos := pos.Add(cube.Pos{}.Side(d.Facing)) + + switch it.Item().(type) { + case item.FlintAndSteel: + if t, ok := tx.Block(sidePos).(TNT); ok { + t.Ignite(pos, tx, nil) + } else if _, ok := tx.Block(sidePos).(Air); ok { + tx.SetBlock(sidePos, Fire{}, nil) + } + it.Damage(1) + case item.GlassBottle: + if _, ok := tx.Block(sidePos).(Water); ok { + d.Inventory(tx, sidePos).AddItem(item.NewStack(item.Potion{Type: potion.Water()}, 1)) + } + _ = d.Inventory(tx, pos).SetItem(slot, it.Grow(-1)) + case TNT: + tx.PlaySound(sidePos.Vec3Centre(), sound.TNT{}) + opts := world.EntitySpawnOpts{Position: sidePos.Vec3Centre()} + tx.AddEntity(tx.World().EntityRegistry().Config().TNT(opts, time.Second*4)) + + _ = d.Inventory(tx, pos).SetItem(slot, it.Grow(-1)) + case item.BoneMeal: + if b, ok := tx.Block(sidePos).(item.BoneMealAffected); ok { + b.BoneMeal(pos, tx) + } + default: + create := tx.World().EntityRegistry().Config().Item + + dist := r.Float64()/10 + 0.2 + sourcePos := pos.Vec3Centre().Add(cube.Pos{}.Side(d.Facing).Vec3().Mul(0.7)) + + xOffset, zOffset := 0.0, 0.0 + if axis := d.Facing.Axis(); axis == cube.X { + xOffset = 1.0 + } else if axis == cube.Z { + zOffset = 1.0 + } + + xMultiplier, zMultiplier := -1.0, -1.0 + if d.Facing.Positive() { + xMultiplier, zMultiplier = 1.0, 1.0 + } + + opts := world.EntitySpawnOpts{Position: sourcePos, Velocity: mgl64.Vec3{ + (r.Float64()*2-1)*6*0.0075 + xOffset*xMultiplier*dist, + (r.Float64()*2-1)*6*0.0075 + 0.2, + (r.Float64()*2-1)*6*0.0075 + zOffset*zMultiplier*dist, + }} + + tx.AddEntity(create(opts, it.Grow(-it.Count()+1))) + _ = d.Inventory(tx, pos).SetItem(slot, it.Grow(-1)) + } + + tx.AddParticle(pos.Vec3(), particle.Dispense{}) + tx.PlaySound(pos.Vec3(), sound.Dispense{}) +} + +// randomSlotFromInventory returns a random slot from the inventory of the dispenser. If the inventory is empty, the +// second return value is false. +func (d Dispenser) randomSlotFromInventory(r *rand.Rand) (int, bool) { + slots := make([]int, 0, d.inventory.Size()) + for slot, it := range d.inventory.Slots() { + if !it.Empty() { + slots = append(slots, slot) + } + } + if len(slots) == 0 { + return 0, false + } + return slots[r.IntN(len(slots))], true +} + +// EncodeItem ... +func (Dispenser) EncodeItem() (name string, meta int16) { + return "minecraft:dispenser", 0 +} + +// EncodeBlock ... +func (d Dispenser) EncodeBlock() (string, map[string]any) { + return "minecraft:dispenser", map[string]any{ + "facing_direction": int32(d.Facing), + "triggered_bit": d.Powered, + } +} + +// EncodeNBT ... +func (d Dispenser) EncodeNBT() map[string]any { + if d.inventory == nil { + facing, powered, customName := d.Facing, d.Powered, d.CustomName + //noinspection GoAssignmentToReceiver + d = NewDispenser() + d.Facing, d.Powered, d.CustomName = facing, powered, customName + } + m := map[string]any{ + "Items": nbtconv.InvToNBT(d.inventory), + "id": "Dispenser", + } + if d.CustomName != "" { + m["CustomName"] = d.CustomName + } + return m +} + +// DecodeNBT ... +func (d Dispenser) DecodeNBT(data map[string]any) any { + facing, powered := d.Facing, d.Powered + //noinspection GoAssignmentToReceiver + d = NewDispenser() + d.Facing = facing + d.Powered = powered + d.CustomName = nbtconv.String(data, "CustomName") + nbtconv.InvFromNBT(d.inventory, nbtconv.Slice(data, "Items")) + return d +} + +// allDispensers ... +func allDispensers() (dispensers []world.Block) { + for _, f := range cube.Faces() { + for _, p := range []bool{false, true} { + dispensers = append(dispensers, Dispenser{Facing: f, Powered: p}) + } + } + return dispensers +} diff --git a/server/block/hash.go b/server/block/hash.go index 2145ecae5..a1888e672 100644 --- a/server/block/hash.go +++ b/server/block/hash.go @@ -60,6 +60,7 @@ const ( hashDiorite hashDirt hashDirtPath + hashDispenser hashDoubleFlower hashDoubleTallGrass hashDragonEgg @@ -436,6 +437,10 @@ func (DirtPath) Hash() (uint64, uint64) { return hashDirtPath, 0 } +func (d Dispenser) Hash() (uint64, uint64) { + return hashDispenser, uint64(d.Facing) | uint64(boolByte(d.Powered))<<3 +} + func (d DoubleFlower) Hash() (uint64, uint64) { return hashDoubleFlower, uint64(boolByte(d.UpperPart)) | uint64(d.Type.Uint8())<<1 } diff --git a/server/block/register.go b/server/block/register.go index b4e837e0d..231c88284 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -152,6 +152,7 @@ func init() { registerAll(allCoral()) registerAll(allCoralBlocks()) registerAll(allDeepslate()) + registerAll(allDispensers()) registerAll(allDoors()) registerAll(allDoubleFlowers()) registerAll(allDoubleTallGrass()) @@ -274,6 +275,7 @@ func init() { world.RegisterItem(DirtPath{}) world.RegisterItem(Dirt{Coarse: true}) world.RegisterItem(Dirt{}) + world.RegisterItem(Dispenser{}) world.RegisterItem(DragonEgg{}) world.RegisterItem(DriedKelp{}) world.RegisterItem(Dripstone{}) diff --git a/server/session/world.go b/server/session/world.go index 4f70d284a..2d421c1c9 100644 --- a/server/session/world.go +++ b/server/session/world.go @@ -1124,6 +1124,8 @@ func (s *Session) openNormalContainer(b block.Container, pos cube.Pos, tx *world containerType = protocol.ContainerTypeSmoker case block.Hopper: containerType = protocol.ContainerTypeHopper + case block.Dispenser: + containerType = protocol.ContainerTypeDispenser case block.Dropper: containerType = protocol.ContainerTypeDropper } From 1ae64caae9428ea7fa2cac97f10807c2e33e3910 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 8 Jan 2025 00:32:39 -0800 Subject: [PATCH 17/33] use new ProjectileHit interface --- server/block/button.go | 9 +++++++++ server/entity/projectile.go | 5 ----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/server/block/button.go b/server/block/button.go index 9b9262abc..de2f69377 100644 --- a/server/block/button.go +++ b/server/block/button.go @@ -78,6 +78,15 @@ func (b Button) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, _ item.User, _ return b.Click(pos, tx) } +// ProjectileHit ... +func (b Button) ProjectileHit(pos cube.Pos, tx *world.Tx, e world.Entity, face cube.Face) { + if b.Type == StoneButton() || b.Type == PolishedBlackstoneButton() { + return + } + + b.Click(pos, tx) +} + // Click ... func (b Button) Click(pos cube.Pos, tx *world.Tx) bool { if b.Pressed { diff --git a/server/entity/projectile.go b/server/entity/projectile.go index cd402df22..19682cf4b 100644 --- a/server/entity/projectile.go +++ b/server/entity/projectile.go @@ -175,11 +175,6 @@ func (lt *ProjectileBehaviour) Tick(e *Ent, tx *world.Tx) *Movement { if h, ok := tx.Block(bpos).(block.ProjectileHitter); ok { h.ProjectileHit(bpos, tx, e, r.Face()) } - if b, ok := tx.Block(bpos).(block.Button); ok { - if b.Type != block.StoneButton() || b.Type != block.PolishedBlackstoneButton() { - b.Click(bpos, tx) - } - } if t, ok := tx.Block(bpos).(block.Target); ok { if _, ok := e.H().Type().(enderPearlType); !ok { delay := time.Millisecond * 400 From 6d6f1ee66aec5f34bd20d0d71bbd06e57b7ec105 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 8 Jan 2025 18:02:10 -0800 Subject: [PATCH 18/33] implement repeater locking and wip comparators --- server/block/hash.go | 5 ++ server/block/redstone_comparator.go | 121 ++++++++++++++++++++++++++++ server/block/redstone_repeater.go | 25 ++++-- server/block/register.go | 2 + 4 files changed, 148 insertions(+), 5 deletions(-) create mode 100644 server/block/redstone_comparator.go diff --git a/server/block/hash.go b/server/block/hash.go index a1888e672..a0fb62da3 100644 --- a/server/block/hash.go +++ b/server/block/hash.go @@ -157,6 +157,7 @@ const ( hashRawGold hashRawIron hashRedstoneBlock + hashRedstoneComparator hashRedstoneLamp hashRedstoneOre hashRedstoneRepeater @@ -825,6 +826,10 @@ func (RedstoneBlock) Hash() (uint64, uint64) { return hashRedstoneBlock, 0 } +func (r RedstoneComparator) Hash() (uint64, uint64) { + return hashRedstoneComparator, uint64(r.Facing) | uint64(boolByte(r.Subtract))<<2 | uint64(boolByte(r.Powered))<<3 | uint64(r.Power)<<4 +} + func (l RedstoneLamp) Hash() (uint64, uint64) { return hashRedstoneLamp, uint64(boolByte(l.Lit)) } diff --git a/server/block/redstone_comparator.go b/server/block/redstone_comparator.go new file mode 100644 index 000000000..5a00ee1e2 --- /dev/null +++ b/server/block/redstone_comparator.go @@ -0,0 +1,121 @@ +package block + +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/block/model" + "github.com/df-mc/dragonfly/server/internal/nbtconv" + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/world" + "github.com/go-gl/mathgl/mgl64" +) + +// RedstoneComparator is a redstone component used to maintain, compare, or subtract signal strength, or to measure +// certain block states (primarily the fullness of containers). +type RedstoneComparator struct { + transparent + + // Facing is the direction from the torch to the block. + Facing cube.Direction + // Subtract is true if the comparator is in subtract mode. + Subtract bool + // Powered is true if the repeater is powered by a redstone signal. + Powered bool + // Power is the current power level of the redstone comparator. It ranges from 0 to 15. + Power int +} + +// HasLiquidDrops ... +func (RedstoneComparator) HasLiquidDrops() bool { + return true +} + +// Model ... +func (RedstoneComparator) Model() world.BlockModel { + return model.Diode{} +} + +// EncodeItem ... +func (RedstoneComparator) EncodeItem() (name string, meta int16) { + return "minecraft:comparator", 0 +} + +// EncodeBlock ... +func (r RedstoneComparator) EncodeBlock() (string, map[string]any) { + name := "minecraft:unpowered_comparator" + if r.Powered { + name = "minecraft:powered_comparator" + } + return name, map[string]any{ + "minecraft:cardinal_direction": r.Facing.String(), + "output_lit_bit": boolByte(r.Powered), + "output_subtract_bit": boolByte(r.Subtract), + } +} + +// UseOnBlock ... +func (r RedstoneComparator) 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, r) + if !used { + return false + } + if d, ok := tx.Block(pos.Side(cube.FaceDown)).(LightDiffuser); ok && d.LightDiffusionLevel() == 0 { + return false + } + r.Facing = user.Rotation().Direction().Opposite() + + place(tx, pos, r, user, ctx) + return placed(ctx) +} + +// NeighbourUpdateTick ... +func (r RedstoneComparator) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { + if d, ok := tx.Block(pos.Side(cube.FaceDown)).(LightDiffuser); ok && d.LightDiffusionLevel() == 0 { + tx.SetBlock(pos, nil, nil) + dropItem(tx, item.NewStack(r, 1), pos.Vec3Centre()) + } +} + +// Activate ... +func (r RedstoneComparator) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, _ item.User, _ *item.UseContext) bool { + r.Subtract = !r.Subtract + tx.SetBlock(pos, r, nil) + return false +} + +// RedstoneSource ... +func (r RedstoneComparator) RedstoneSource() bool { + return r.Powered +} + +// WeakPower ... +func (r RedstoneComparator) WeakPower(_ cube.Pos, face cube.Face, _ *world.Tx, _ bool) int { + return 0 +} + +// StrongPower ... +func (r RedstoneComparator) StrongPower(pos cube.Pos, face cube.Face, tx *world.Tx, accountForDust bool) int { + return r.WeakPower(pos, face, tx, accountForDust) +} + +// EncodeNBT ... +func (r RedstoneComparator) EncodeNBT() map[string]any { + return map[string]any{"OutputSignal": int32(r.Power)} +} + +// DecodeNBT ... +func (r RedstoneComparator) DecodeNBT(data map[string]any) any { + r.Power = int(nbtconv.Int32(data, "OutputSignal")) + return r +} + +// allRedstoneComparators ... +func allRedstoneComparators() (comparators []world.Block) { + for _, d := range cube.Directions() { + for _, s := range []bool{false, true} { + for _, p := range []bool{false, true} { + comparators = append(comparators, RedstoneComparator{Facing: d, Subtract: s, Powered: p}) + } + } + } + return +} diff --git a/server/block/redstone_repeater.go b/server/block/redstone_repeater.go index 50f2f25cb..087128c87 100644 --- a/server/block/redstone_repeater.go +++ b/server/block/redstone_repeater.go @@ -1,6 +1,7 @@ package block import ( + "fmt" "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/block/model" "github.com/df-mc/dragonfly/server/item" @@ -96,7 +97,7 @@ func (r RedstoneRepeater) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, _ it // RedstoneUpdate ... func (r RedstoneRepeater) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { - if r.Locked() { + if r.Locked(pos, tx) { // Ignore this update; the repeater is locked. return } @@ -107,7 +108,7 @@ func (r RedstoneRepeater) RedstoneUpdate(pos cube.Pos, tx *world.Tx) { // ScheduledTick ... func (r RedstoneRepeater) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { - if r.Locked() { + if r.Locked(pos, tx) { // Ignore this tick; the repeater is locked. return } @@ -123,9 +124,23 @@ func (r RedstoneRepeater) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand updateGateRedstone(pos, tx, r.Facing.Face()) } -// Locked ... -func (RedstoneRepeater) Locked() bool { - //TODO implement me +// Locked checks if the repeater is locked. +func (r RedstoneRepeater) Locked(pos cube.Pos, tx *world.Tx) bool { + return r.locking(pos.Side(r.Facing.RotateLeft().Face()), r.Facing.RotateLeft(), tx) || r.locking(pos.Side(r.Facing.RotateRight().Face()), r.Facing.RotateRight(), tx) +} + +// locking checks of the block at the given position is a powered repeater or comparator facing the repeater +func (r RedstoneRepeater) locking(pos cube.Pos, direction cube.Direction, tx *world.Tx) bool { + block := tx.Block(pos) + + fmt.Println(block.EncodeBlock()) + if repeater, ok := block.(RedstoneRepeater); ok { + return repeater.Powered && repeater.Facing == direction + } + + if comparator, ok := block.(RedstoneComparator); ok { + return comparator.Powered && comparator.Facing == direction + } return false } diff --git a/server/block/register.go b/server/block/register.go index 231c88284..ca9599df8 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -200,6 +200,7 @@ func init() { registerAll(allPumpkins()) registerAll(allPurpurs()) registerAll(allQuartz()) + registerAll(allRedstoneComparators()) registerAll(allRedstoneRepeaters()) registerAll(allRedstoneTorches()) registerAll(allRedstoneWires()) @@ -357,6 +358,7 @@ func init() { world.RegisterItem(RawGold{}) world.RegisterItem(RawIron{}) world.RegisterItem(RedstoneBlock{}) + world.RegisterItem(RedstoneComparator{}) world.RegisterItem(RedstoneLamp{}) world.RegisterItem(RedstoneOre{}) world.RegisterItem(RedstoneOre{Type: DeepslateOre()}) From ab0024baa6b85ca3831bcffa1cee3f2ab3bbd68b Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 8 Jan 2025 19:58:58 -0800 Subject: [PATCH 19/33] implement trapped chests --- server/block/chest.go | 91 ++++++++++++++++++++++++++++++++++++---- server/block/hash.go | 2 +- server/block/register.go | 1 + 3 files changed, 86 insertions(+), 8 deletions(-) diff --git a/server/block/chest.go b/server/block/chest.go index 136b37ea1..5daea284e 100644 --- a/server/block/chest.go +++ b/server/block/chest.go @@ -9,6 +9,8 @@ import ( "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" + "math" + "math/rand/v2" "strings" "sync" "time" @@ -28,6 +30,8 @@ type Chest struct { // CustomName is the custom name of the chest. This name is displayed when the chest is opened, and may // include colour codes. CustomName string + // Trapped is the chest type if returns true, the chest is a trapped chest. + Trapped bool paired bool pairX, pairZ int @@ -126,6 +130,14 @@ func (c Chest) AddViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) { c.open(tx, pos) } c.viewers[v] = struct{}{} + + // Schedule redstone update for trapped chests. + if c.Trapped { + tx.ScheduleBlockUpdate(pos, c, time.Millisecond*100) + if c.Paired() { + tx.ScheduleBlockUpdate(c.pairPos(pos), c, time.Millisecond*100) + } + } } // RemoveViewer removes a viewer from the chest, so that slot updates in the inventory are no longer sent to @@ -143,6 +155,14 @@ func (c Chest) RemoveViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) { if len(c.viewers) == 0 { c.close(tx, pos) } + + // Schedule redstone update for trapped chests. + if c.Trapped { + tx.ScheduleBlockUpdate(pos, c, time.Millisecond*100) + if c.Paired() { + tx.ScheduleBlockUpdate(c.pairPos(pos), c, time.Millisecond*100) + } + } } // Activate ... @@ -153,6 +173,7 @@ func (c Chest) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.User, _ return false } } + if d, ok := tx.Block(pos.Side(cube.FaceUp)).(LightDiffuser); ok && d.LightDiffusionLevel() <= 2 { opener.OpenBlockContainer(pos, tx) } @@ -167,10 +188,17 @@ func (c Chest) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world. if !used { return } + //noinspection GoAssignmentToReceiver c = NewChest() c.Facing = user.Rotation().Direction().Opposite() + // check if the held item is a trapped chest or regular chest + h, _ := user.HeldItems() + if heldChest, ok := h.Item().(Chest); ok { + c.Trapped = heldChest.Trapped + } + // Check both sides of the chest to see if it is possible to pair with another chest. for _, dir := range []cube.Direction{c.Facing.RotateLeft(), c.Facing.RotateRight()} { if ch, pair, ok := c.pair(tx, pos, pos.Side(dir.Face())); ok { @@ -195,12 +223,50 @@ func (c Chest) BreakInfo() BreakInfo { } } + if c.Trapped { + updateAroundRedstone(pos, tx) + } + for _, i := range c.Inventory(tx, pos).Clear() { dropItem(tx, i, pos.Vec3Centre()) } }) } +// RedstoneSource ... +func (c Chest) RedstoneSource() bool { + if c.Trapped { + c.viewerMu.RLock() + defer c.viewerMu.RUnlock() + return len(c.viewers) > 0 + } + return false +} + +// WeakPower ... +func (c Chest) WeakPower(_ cube.Pos, _ cube.Face, _ *world.Tx, _ bool) int { + if !c.Trapped { + return 0 + } + c.viewerMu.RLock() + defer c.viewerMu.RUnlock() + return int(math.Min(float64(len(c.viewers)), 15)) +} + +// StrongPower ... +func (c Chest) StrongPower(pos cube.Pos, face cube.Face, tx *world.Tx, accountForDust bool) int { + return c.WeakPower(pos, face, tx, accountForDust) +} + +// ScheduledTick ... +func (c Chest) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { + if !c.Trapped { + return + } + + updateAroundRedstone(pos, tx) +} + // FuelInfo ... func (Chest) FuelInfo() item.FuelInfo { return newFuelInfo(time.Second * 15) @@ -219,7 +285,7 @@ func (c Chest) Paired() bool { // pair pairs this chest with the given chest position. func (c Chest) pair(tx *world.Tx, pos, pairPos cube.Pos) (ch, pair Chest, ok bool) { pair, ok = tx.Block(pairPos).(Chest) - if !ok || c.Facing != pair.Facing || pair.paired && (pair.pairX != pos[0] || pair.pairZ != pos[2]) { + if !ok || c.Facing != pair.Facing || pair.paired && (pair.pairX != pos[0] || pair.pairZ != pos[2]) || c.Trapped != pair.Trapped { return c, pair, false } m := new(sync.RWMutex) @@ -260,7 +326,7 @@ func (c Chest) unpair(tx *world.Tx, pos cube.Pos) (ch, pair Chest, ok bool) { } pair, ok = tx.Block(c.pairPos(pos)).(Chest) - if !ok || c.Facing != pair.Facing || pair.paired && (pair.pairX != pos[0] || pair.pairZ != pos[2]) { + if !ok || c.Facing != pair.Facing || pair.paired && (pair.pairX != pos[0] || pair.pairZ != pos[2]) || c.Trapped != pair.Trapped { return c, pair, false } @@ -302,6 +368,8 @@ func (c Chest) DecodeNBT(data map[string]any) any { c.Facing = facing c.CustomName = nbtconv.String(data, "CustomName") + c.Trapped = nbtconv.Bool(data, "Trapped") + pairX, ok := data["pairx"] pairZ, ok2 := data["pairz"] if ok && ok2 { @@ -320,15 +388,17 @@ func (c Chest) DecodeNBT(data map[string]any) any { // EncodeNBT ... func (c Chest) EncodeNBT() map[string]any { if c.inventory == nil { - facing, customName := c.Facing, c.CustomName + facing, customName, trapped := c.Facing, c.CustomName, c.Trapped //noinspection GoAssignmentToReceiver c = NewChest() - c.Facing, c.CustomName = facing, customName + c.Facing, c.CustomName, c.Trapped = facing, customName, trapped } m := map[string]any{ - "Items": nbtconv.InvToNBT(c.inventory), - "id": "Chest", + "Items": nbtconv.InvToNBT(c.inventory), + "id": "Chest", + "Trapped": c.Trapped, } + if c.CustomName != "" { m["CustomName"] = c.CustomName } @@ -341,12 +411,18 @@ func (c Chest) EncodeNBT() map[string]any { } // EncodeItem ... -func (Chest) EncodeItem() (name string, meta int16) { +func (c Chest) EncodeItem() (name string, meta int16) { + if c.Trapped { + return "minecraft:trapped_chest", 0 + } return "minecraft:chest", 0 } // EncodeBlock ... func (c Chest) EncodeBlock() (name string, properties map[string]any) { + if c.Trapped { + return "minecraft:trapped_chest", map[string]any{"minecraft:cardinal_direction": c.Facing.String()} + } return "minecraft:chest", map[string]any{"minecraft:cardinal_direction": c.Facing.String()} } @@ -354,6 +430,7 @@ func (c Chest) EncodeBlock() (name string, properties map[string]any) { func allChests() (chests []world.Block) { for _, direction := range cube.Directions() { chests = append(chests, Chest{Facing: direction}) + chests = append(chests, Chest{Facing: direction, Trapped: true}) } return } diff --git a/server/block/hash.go b/server/block/hash.go index a0fb62da3..77d451bfa 100644 --- a/server/block/hash.go +++ b/server/block/hash.go @@ -327,7 +327,7 @@ func (c Chain) Hash() (uint64, uint64) { } func (c Chest) Hash() (uint64, uint64) { - return hashChest, uint64(c.Facing) + return hashChest, uint64(c.Facing) | uint64(boolByte(c.Trapped))<<2 } func (ChiseledQuartz) Hash() (uint64, uint64) { diff --git a/server/block/register.go b/server/block/register.go index ca9599df8..d285a2240 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -257,6 +257,7 @@ func init() { world.RegisterItem(Carrot{}) world.RegisterItem(Chain{}) world.RegisterItem(Chest{}) + world.RegisterItem(Chest{Trapped: true}) world.RegisterItem(ChiseledQuartz{}) world.RegisterItem(Clay{}) world.RegisterItem(Coal{}) From 5e0d84f620f0c877c11c6caa632e20a3a8d814d4 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 8 Jan 2025 20:03:05 -0800 Subject: [PATCH 20/33] trapped chest changes --- server/block/chest.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/server/block/chest.go b/server/block/chest.go index 5daea284e..0a9d4b65d 100644 --- a/server/block/chest.go +++ b/server/block/chest.go @@ -134,9 +134,6 @@ func (c Chest) AddViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) { // Schedule redstone update for trapped chests. if c.Trapped { tx.ScheduleBlockUpdate(pos, c, time.Millisecond*100) - if c.Paired() { - tx.ScheduleBlockUpdate(c.pairPos(pos), c, time.Millisecond*100) - } } } @@ -159,9 +156,6 @@ func (c Chest) RemoveViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) { // Schedule redstone update for trapped chests. if c.Trapped { tx.ScheduleBlockUpdate(pos, c, time.Millisecond*100) - if c.Paired() { - tx.ScheduleBlockUpdate(c.pairPos(pos), c, time.Millisecond*100) - } } } @@ -265,6 +259,9 @@ func (c Chest) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { } updateAroundRedstone(pos, tx) + if c.Paired() { + updateAroundRedstone(c.pairPos(pos), tx) + } } // FuelInfo ... From 2596838ebfbc8121ef1f9012c9410568172820c8 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 8 Jan 2025 21:47:16 -0800 Subject: [PATCH 21/33] allow repeaters to be placed on pistons --- server/block/redstone_repeater.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/server/block/redstone_repeater.go b/server/block/redstone_repeater.go index 087128c87..ad9a136c7 100644 --- a/server/block/redstone_repeater.go +++ b/server/block/redstone_repeater.go @@ -1,7 +1,6 @@ package block import ( - "fmt" "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/block/model" "github.com/df-mc/dragonfly/server/item" @@ -65,9 +64,13 @@ func (r RedstoneRepeater) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, if !used { return false } - if d, ok := tx.Block(pos.Side(cube.FaceDown)).(LightDiffuser); ok && d.LightDiffusionLevel() == 0 { - return false + b := tx.Block(pos.Side(cube.FaceDown)) + if d, ok := b.(LightDiffuser); ok && d.LightDiffusionLevel() == 0 { + if _, isPiston := b.(Piston); !isPiston { + return false + } } + r.Facing = user.Rotation().Direction().Opposite() place(tx, pos, r, user, ctx) @@ -80,9 +83,12 @@ func (r RedstoneRepeater) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, // NeighbourUpdateTick ... func (r RedstoneRepeater) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { - if d, ok := tx.Block(pos.Side(cube.FaceDown)).(LightDiffuser); ok && d.LightDiffusionLevel() == 0 { - tx.SetBlock(pos, nil, nil) - dropItem(tx, item.NewStack(r, 1), pos.Vec3Centre()) + b := tx.Block(pos.Side(cube.FaceDown)) + if d, ok := b.(LightDiffuser); ok && d.LightDiffusionLevel() == 0 { + if _, piston := b.(Piston); !piston { + tx.SetBlock(pos, nil, nil) + dropItem(tx, item.NewStack(r, 1), pos.Vec3Centre()) + } } } @@ -133,7 +139,6 @@ func (r RedstoneRepeater) Locked(pos cube.Pos, tx *world.Tx) bool { func (r RedstoneRepeater) locking(pos cube.Pos, direction cube.Direction, tx *world.Tx) bool { block := tx.Block(pos) - fmt.Println(block.EncodeBlock()) if repeater, ok := block.(RedstoneRepeater); ok { return repeater.Powered && repeater.Facing == direction } From ee7b2a4c2f2645b2087b5ba713e8e88753fb9696 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 8 Jan 2025 23:25:26 -0800 Subject: [PATCH 22/33] fix bug with torches --- server/block/redstone_torch.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/block/redstone_torch.go b/server/block/redstone_torch.go index 4942649eb..799e67177 100644 --- a/server/block/redstone_torch.go +++ b/server/block/redstone_torch.go @@ -102,7 +102,7 @@ func (t RedstoneTorch) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { } t.Lit = !t.Lit tx.SetBlock(pos, t, nil) - updateDirectionalRedstone(pos, tx, t.Facing.Opposite()) + updateStrongRedstone(pos, tx) } // EncodeItem ... From 39a3cab74675f19064edbdde897d629d65368a77 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 8 Jan 2025 23:27:44 -0800 Subject: [PATCH 23/33] fix bug with trapped chest --- server/block/chest.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/block/chest.go b/server/block/chest.go index 0a9d4b65d..3d551d1fe 100644 --- a/server/block/chest.go +++ b/server/block/chest.go @@ -258,9 +258,9 @@ func (c Chest) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { return } - updateAroundRedstone(pos, tx) + updateStrongRedstone(pos, tx) if c.Paired() { - updateAroundRedstone(c.pairPos(pos), tx) + updateStrongRedstone(c.pairPos(pos), tx) } } From 745813223238a8647a0fa8f130b823725099ab02 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 8 Jan 2025 23:44:40 -0800 Subject: [PATCH 24/33] fix bug with torches --- server/block/redstone_torch.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/block/redstone_torch.go b/server/block/redstone_torch.go index 799e67177..f89e9a2ed 100644 --- a/server/block/redstone_torch.go +++ b/server/block/redstone_torch.go @@ -37,7 +37,7 @@ func (t RedstoneTorch) LightEmissionLevel() uint8 { // BreakInfo ... func (t RedstoneTorch) BreakInfo() BreakInfo { return newBreakInfo(0, alwaysHarvestable, nothingEffective, oneOf(t)).withBreakHandler(func(pos cube.Pos, tx *world.Tx, _ item.User) { - updateDirectionalRedstone(pos, tx, t.Facing.Opposite()) + updateStrongRedstone(pos, tx) }) } @@ -72,7 +72,7 @@ func (t RedstoneTorch) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx place(tx, pos, t, user, ctx) if placed(ctx) { t.RedstoneUpdate(pos, tx) - updateDirectionalRedstone(pos, tx, t.Facing.Opposite()) + updateStrongRedstone(pos, tx) return true } return false From b8bd8a907be3c490f43f171f9567ffd75406ba07 Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 9 Jan 2025 00:32:55 -0800 Subject: [PATCH 25/33] implement slime block --- server/block/hash.go | 5 ++++ server/block/register.go | 2 ++ server/block/slime.go | 51 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 server/block/slime.go diff --git a/server/block/hash.go b/server/block/hash.go index 77d451bfa..725f77947 100644 --- a/server/block/hash.go +++ b/server/block/hash.go @@ -175,6 +175,7 @@ const ( hashSign hashSkull hashSlab + hashSlime hashSmithingTable hashSmoker hashSnow @@ -898,6 +899,10 @@ func (s Slab) Hash() (uint64, uint64) { return hashSlab, world.BlockHash(s.Block) | uint64(boolByte(s.Top))<<32 | uint64(boolByte(s.Double))<<33 } +func (Slime) Hash() (uint64, uint64) { + return hashSlime, 0 +} + func (SmithingTable) Hash() (uint64, uint64) { return hashSmithingTable, 0 } diff --git a/server/block/register.go b/server/block/register.go index d285a2240..2ac89a891 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -96,6 +96,7 @@ func init() { world.RegisterBlock(Sand{}) world.RegisterBlock(SeaLantern{}) world.RegisterBlock(Shroomlight{}) + world.RegisterBlock(Slime{}) world.RegisterBlock(SmithingTable{}) world.RegisterBlock(Snow{}) world.RegisterBlock(SoulSand{}) @@ -375,6 +376,7 @@ func init() { world.RegisterItem(SeaLantern{}) world.RegisterItem(SeaPickle{}) world.RegisterItem(Shroomlight{}) + world.RegisterItem(Slime{}) world.RegisterItem(SmithingTable{}) world.RegisterItem(Smoker{}) world.RegisterItem(Snow{}) diff --git a/server/block/slime.go b/server/block/slime.go new file mode 100644 index 000000000..828080fcf --- /dev/null +++ b/server/block/slime.go @@ -0,0 +1,51 @@ +package block + +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/world" + "github.com/go-gl/mathgl/mgl64" +) + +// Slime is a storage block equivalent to nine slimeballs. It has both sticky and bouncy properties making it useful in +// conjunction with pistons to move both blocks and entities. +type Slime struct { + solid + transparent +} + +// BreakInfo ... +func (s Slime) BreakInfo() BreakInfo { + return newBreakInfo(0, alwaysHarvestable, nothingEffective, oneOf(s)) +} + +// Friction ... +func (Slime) Friction() float64 { + return 0.8 +} + +// EncodeItem ... +func (Slime) EncodeItem() (name string, meta int16) { + return "minecraft:slime", 0 +} + +// EncodeBlock ... +func (Slime) EncodeBlock() (name string, properties map[string]any) { + return "minecraft:slime", nil +} + +// EntityLand ... +func (Slime) EntityLand(pos cube.Pos, tx *world.Tx, e world.Entity, distance *float64) { + if s, ok := e.(interface { + Sneaking() bool + }); !ok || !s.Sneaking() { + *distance = 0 + } + if v, ok := e.(interface { + Velocity() mgl64.Vec3 + SetVelocity(mgl64.Vec3) + }); ok { + vel := v.Velocity() + vel[1] = -vel[1] + v.SetVelocity(vel) + } +} From a7a95b90410d4e0464bad2f31f58fa4ec010371f Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 9 Jan 2025 00:34:50 -0800 Subject: [PATCH 26/33] add slime block support to pistons --- server/block/piston_resolver.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/server/block/piston_resolver.go b/server/block/piston_resolver.go index a252185cb..3e71e12f6 100644 --- a/server/block/piston_resolver.go +++ b/server/block/piston_resolver.go @@ -112,11 +112,18 @@ func (r *pistonResolver) calculateBlocks(pos cube.Pos, face cube.Face, breakFace return false } - // account for slime. - if !r.calculateBlocks(pos.Side(breakFace), breakFace, breakFace) { + if _, ok := block.(Slime); ok { + for _, otherFace := range cube.Faces() { + if face.Opposite() == otherFace { + continue + } + if !r.calculateBlocks(pos.Side(otherFace), otherFace, breakFace) { + return false + } + } + } else if !r.calculateBlocks(pos.Side(breakFace), breakFace, breakFace) { return false } - return true } From e11d3b77556e001bb458010421beb7a9e090f0a3 Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 9 Jan 2025 01:01:27 -0800 Subject: [PATCH 27/33] make certain blocks PistonImmovable --- server/block/barrier.go | 5 +++++ server/block/beacon.go | 5 +++++ server/block/bedrock.go | 5 +++++ server/block/enchanting_table.go | 5 +++++ server/block/ender_chest.go | 5 +++++ server/block/invisible_bedrock.go | 5 +++++ 6 files changed, 30 insertions(+) diff --git a/server/block/barrier.go b/server/block/barrier.go index b74d4b1ab..5eabd0cdb 100644 --- a/server/block/barrier.go +++ b/server/block/barrier.go @@ -17,6 +17,11 @@ func (Barrier) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } +// PistonImmovable ... +func (Barrier) PistonImmovable() bool { + return true +} + // EncodeItem ... func (Barrier) EncodeItem() (name string, meta int16) { return "minecraft:barrier", 0 diff --git a/server/block/beacon.go b/server/block/beacon.go index b4d7040c5..825aa4f0e 100644 --- a/server/block/beacon.go +++ b/server/block/beacon.go @@ -89,6 +89,11 @@ func (b Beacon) Level() int { return b.level } +// PistonImmovable ... +func (Beacon) PistonImmovable() bool { + return true +} + // Tick recalculates level, recalculates the active state of the beacon, and powers players, // once every 80 ticks (4 seconds). func (b Beacon) Tick(currentTick int64, pos cube.Pos, tx *world.Tx) { diff --git a/server/block/bedrock.go b/server/block/bedrock.go index a152bcf66..ca06bd53d 100644 --- a/server/block/bedrock.go +++ b/server/block/bedrock.go @@ -20,3 +20,8 @@ func (b Bedrock) EncodeBlock() (name string, properties map[string]any) { //noinspection SpellCheckingInspection return "minecraft:bedrock", map[string]any{"infiniburn_bit": b.InfiniteBurning} } + +// PistonImmovable ... +func (Bedrock) PistonImmovable() bool { + return true +} diff --git a/server/block/enchanting_table.go b/server/block/enchanting_table.go index 8ccdb626c..d9d542328 100644 --- a/server/block/enchanting_table.go +++ b/server/block/enchanting_table.go @@ -35,6 +35,11 @@ func (EnchantingTable) LightEmissionLevel() uint8 { return 7 } +// PistonImmovable ... +func (EnchantingTable) PistonImmovable() bool { + return true +} + // Activate ... func (EnchantingTable) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.User, _ *item.UseContext) bool { if opener, ok := u.(ContainerOpener); ok { diff --git a/server/block/ender_chest.go b/server/block/ender_chest.go index 054b075ca..42ec14eff 100644 --- a/server/block/ender_chest.go +++ b/server/block/ender_chest.go @@ -45,6 +45,11 @@ func (c EnderChest) LightEmissionLevel() uint8 { return 7 } +// PistonImmovable ... +func (EnderChest) PistonImmovable() bool { + return true +} + // SideClosed ... func (EnderChest) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false diff --git a/server/block/invisible_bedrock.go b/server/block/invisible_bedrock.go index 54a6aeceb..6556e00f9 100644 --- a/server/block/invisible_bedrock.go +++ b/server/block/invisible_bedrock.go @@ -7,6 +7,11 @@ type InvisibleBedrock struct { solid } +// PistonImmovable ... +func (InvisibleBedrock) PistonImmovable() bool { + return true +} + // EncodeItem ... func (InvisibleBedrock) EncodeItem() (name string, meta int16) { return "minecraft:invisible_bedrock", 0 From a7ced05411a5294bee686b9871e3874c57a15d3a Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 9 Jan 2025 01:06:59 -0800 Subject: [PATCH 28/33] make certain blocks PistonBreakable --- server/block/dragon_egg.go | 5 +++++ server/block/ladder.go | 5 +++++ server/block/leaves.go | 5 +++++ server/block/lit_pumpkin.go | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/server/block/dragon_egg.go b/server/block/dragon_egg.go index 56b184f9a..11a7d42f3 100644 --- a/server/block/dragon_egg.go +++ b/server/block/dragon_egg.go @@ -27,6 +27,11 @@ func (d DragonEgg) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } +// PistonBreakable ... +func (DragonEgg) PistonBreakable() bool { + return true +} + // teleport ... func (d DragonEgg) teleport(pos cube.Pos, tx *world.Tx) { for i := 0; i < 1000; i++ { diff --git a/server/block/ladder.go b/server/block/ladder.go index 91a520095..b3c62030f 100644 --- a/server/block/ladder.go +++ b/server/block/ladder.go @@ -66,6 +66,11 @@ func (l Ladder) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } +// PistonBreakable ... +func (Ladder) PistonBreakable() bool { + return true +} + // BreakInfo ... func (l Ladder) BreakInfo() BreakInfo { return newBreakInfo(0.4, alwaysHarvestable, axeEffective, oneOf(l)) diff --git a/server/block/leaves.go b/server/block/leaves.go index 9fa236c22..e0eab0b64 100644 --- a/server/block/leaves.go +++ b/server/block/leaves.go @@ -135,6 +135,11 @@ func (Leaves) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } +// PistonBreakable ... +func (Leaves) PistonBreakable() bool { + return true +} + // EncodeBlock ... func (l Leaves) EncodeBlock() (name string, properties map[string]any) { return "minecraft:" + l.Wood.String() + "_leaves", map[string]any{"persistent_bit": l.Persistent, "update_bit": l.ShouldUpdate} diff --git a/server/block/lit_pumpkin.go b/server/block/lit_pumpkin.go index 5cd7ac750..48a8d82f2 100644 --- a/server/block/lit_pumpkin.go +++ b/server/block/lit_pumpkin.go @@ -20,6 +20,11 @@ func (l LitPumpkin) LightEmissionLevel() uint8 { return 15 } +// PistonBreakable ... +func (LitPumpkin) PistonBreakable() bool { + return true +} + // UseOnBlock ... func (l LitPumpkin) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { pos, _, used = firstReplaceable(tx, pos, face, l) From 59d035abb362f56fd9cab2b163c27d9d1863afb7 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 17 Jan 2025 19:01:12 -0800 Subject: [PATCH 29/33] use breakBlock func --- main.go | 2 ++ server/block/button.go | 3 +-- server/block/lever.go | 3 +-- server/block/pressure_plate.go | 3 +-- server/block/redstone_comparator.go | 3 +-- server/block/redstone_repeater.go | 3 +-- server/block/redstone_torch.go | 3 +-- server/block/redstone_wire.go | 3 +-- 8 files changed, 9 insertions(+), 14 deletions(-) diff --git a/main.go b/main.go index 03c5def92..4c19af21c 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/df-mc/dragonfly/server" "github.com/df-mc/dragonfly/server/player/chat" + "github.com/df-mc/dragonfly/server/world" "github.com/pelletier/go-toml" "log/slog" "os" @@ -22,6 +23,7 @@ func main() { srv.Listen() for p := range srv.Accept() { + p.SetGameMode(world.GameModeCreative) _ = p } } diff --git a/server/block/button.go b/server/block/button.go index de2f69377..06889a05f 100644 --- a/server/block/button.go +++ b/server/block/button.go @@ -67,8 +67,7 @@ func (b Button) ScheduledTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { // NeighbourUpdateTick ... func (b Button) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { if !tx.Block(pos.Side(b.Facing.Opposite())).Model().FaceSolid(pos.Side(b.Facing.Opposite()), b.Facing, tx) { - tx.SetBlock(pos, nil, nil) - dropItem(tx, item.NewStack(b, 1), pos.Vec3Centre()) + breakBlock(b, pos, tx) updateDirectionalRedstone(pos, tx, b.Facing.Opposite()) } } diff --git a/server/block/lever.go b/server/block/lever.go index 5b290c190..1edb1da12 100644 --- a/server/block/lever.go +++ b/server/block/lever.go @@ -54,8 +54,7 @@ func (l Lever) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { // NeighbourUpdateTick ... func (l Lever) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { if !tx.Block(pos.Side(l.Facing.Opposite())).Model().FaceSolid(pos.Side(l.Facing.Opposite()), l.Facing, tx) { - tx.SetBlock(pos, nil, nil) - dropItem(tx, item.NewStack(l, 1), pos.Vec3Centre()) + breakBlock(l, pos, tx) updateDirectionalRedstone(pos, tx, l.Facing.Opposite()) } } diff --git a/server/block/pressure_plate.go b/server/block/pressure_plate.go index 5ae82cbb3..a95140f72 100644 --- a/server/block/pressure_plate.go +++ b/server/block/pressure_plate.go @@ -114,8 +114,7 @@ func (p PressurePlate) ScheduledTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { // NeighbourUpdateTick ... func (p PressurePlate) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { if d, ok := tx.Block(pos.Side(cube.FaceDown)).(LightDiffuser); ok && d.LightDiffusionLevel() == 0 { - tx.SetBlock(pos, nil, nil) - dropItem(tx, item.NewStack(p, 1), pos.Vec3Centre()) + breakBlock(p, pos, tx) } } diff --git a/server/block/redstone_comparator.go b/server/block/redstone_comparator.go index 5a00ee1e2..aa1c907d6 100644 --- a/server/block/redstone_comparator.go +++ b/server/block/redstone_comparator.go @@ -70,8 +70,7 @@ func (r RedstoneComparator) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec // NeighbourUpdateTick ... func (r RedstoneComparator) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { if d, ok := tx.Block(pos.Side(cube.FaceDown)).(LightDiffuser); ok && d.LightDiffusionLevel() == 0 { - tx.SetBlock(pos, nil, nil) - dropItem(tx, item.NewStack(r, 1), pos.Vec3Centre()) + breakBlock(r, pos, tx) } } diff --git a/server/block/redstone_repeater.go b/server/block/redstone_repeater.go index ad9a136c7..059467af4 100644 --- a/server/block/redstone_repeater.go +++ b/server/block/redstone_repeater.go @@ -86,8 +86,7 @@ func (r RedstoneRepeater) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { b := tx.Block(pos.Side(cube.FaceDown)) if d, ok := b.(LightDiffuser); ok && d.LightDiffusionLevel() == 0 { if _, piston := b.(Piston); !piston { - tx.SetBlock(pos, nil, nil) - dropItem(tx, item.NewStack(r, 1), pos.Vec3Centre()) + breakBlock(r, pos, tx) } } } diff --git a/server/block/redstone_torch.go b/server/block/redstone_torch.go index f89e9a2ed..0e29929df 100644 --- a/server/block/redstone_torch.go +++ b/server/block/redstone_torch.go @@ -81,8 +81,7 @@ func (t RedstoneTorch) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx // NeighbourUpdateTick ... func (t RedstoneTorch) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { if !tx.Block(pos.Side(t.Facing)).Model().FaceSolid(pos.Side(t.Facing), t.Facing.Opposite(), tx) { - tx.SetBlock(pos, nil, nil) - dropItem(tx, item.NewStack(t, 1), pos.Vec3Centre()) + breakBlock(t, pos, tx) updateDirectionalRedstone(pos, tx, t.Facing.Opposite()) } } diff --git a/server/block/redstone_wire.go b/server/block/redstone_wire.go index 6b5fa8ed3..6defa9d38 100644 --- a/server/block/redstone_wire.go +++ b/server/block/redstone_wire.go @@ -73,8 +73,7 @@ func (r RedstoneWire) NeighbourUpdateTick(pos, neighbour cube.Pos, tx *world.Tx) return } if _, ok := tx.Block(pos.Side(cube.FaceDown)).(Air); ok { - tx.SetBlock(pos, nil, nil) - dropItem(tx, item.NewStack(r, 1), pos.Vec3Centre()) + breakBlock(r, pos, tx) return } r.RedstoneUpdate(pos, tx) From 842c5078ef9cec3711aac5a8dd2364540e5b93b0 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 12 Feb 2025 22:48:34 -0800 Subject: [PATCH 30/33] fix merge conflict --- server/block/register.go | 45 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/server/block/register.go b/server/block/register.go index 88e618573..89a01f078 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -60,8 +60,10 @@ func init() { world.RegisterBlock(Iron{}) world.RegisterBlock(Jukebox{}) world.RegisterBlock(Lapis{}) + world.RegisterBlock(LilyPad{}) world.RegisterBlock(Melon{}) world.RegisterBlock(MossCarpet{}) + world.RegisterBlock(Moving{}) world.RegisterBlock(MudBricks{}) world.RegisterBlock(Mud{}) world.RegisterBlock(NetherBrickFence{}) @@ -84,6 +86,9 @@ func init() { world.RegisterBlock(RawCopper{}) world.RegisterBlock(RawGold{}) world.RegisterBlock(RawIron{}) + world.RegisterBlock(RedstoneBlock{}) + world.RegisterBlock(RedstoneLamp{}) + world.RegisterBlock(RedstoneLamp{Lit: true}) world.RegisterBlock(ReinforcedDeepslate{}) world.RegisterBlock(ResinBricks{Chiseled: true}) world.RegisterBlock(ResinBricks{}) @@ -101,6 +106,7 @@ func init() { world.RegisterBlock(SporeBlossom{}) world.RegisterBlock(Stone{Smooth: true}) world.RegisterBlock(Stone{}) + world.RegisterBlock(Target{}) world.RegisterBlock(TNT{}) world.RegisterBlock(Terracotta{}) world.RegisterBlock(Tuff{}) @@ -119,6 +125,8 @@ func init() { world.RegisterBlock(GoldOre{Type: ore}) world.RegisterBlock(IronOre{Type: ore}) world.RegisterBlock(LapisOre{Type: ore}) + world.RegisterBlock(RedstoneOre{Type: ore}) + world.RegisterBlock(RedstoneOre{Type: ore, Lit: true}) } registerAll(allAnvils()) @@ -130,6 +138,7 @@ func init() { registerAll(allBlastFurnaces()) registerAll(allBoneBlock()) registerAll(allBrewingStands()) + registerAll(allButtons()) registerAll(allCactus()) registerAll(allCake()) registerAll(allCampfires()) @@ -144,6 +153,7 @@ func init() { registerAll(allCoral()) registerAll(allCoralBlocks()) registerAll(allDeepslate()) + registerAll(allDispensers()) registerAll(allDoors()) registerAll(allDoubleFlowers()) registerAll(allDoubleTallGrass()) @@ -160,6 +170,8 @@ func init() { registerAll(allGrindstones()) registerAll(allHayBales()) registerAll(allHoppers()) + registerAll(allIronDoors()) + registerAll(allIronTrapdoors()) registerAll(allItemFrames()) registerAll(allKelp()) registerAll(allLadders()) @@ -175,14 +187,22 @@ func init() { registerAll(allMuddyMangroveRoots()) registerAll(allNetherBricks()) registerAll(allNetherWart()) + registerAll(allObservers()) + registerAll(allPistonArmCollisions()) + registerAll(allPistons()) registerAll(allPinkPetals()) registerAll(allPlanks()) registerAll(allPotato()) + registerAll(allPressurePlates()) registerAll(allPrismarine()) registerAll(allPumpkinStems()) registerAll(allPumpkins()) registerAll(allPurpurs()) registerAll(allQuartz()) + registerAll(allRedstoneComparators()) + registerAll(allRedstoneRepeaters()) + registerAll(allRedstoneTorches()) + registerAll(allRedstoneWires()) registerAll(allSandstones()) registerAll(allSeaPickles()) registerAll(allSigns()) @@ -236,6 +256,7 @@ func init() { world.RegisterItem(Carrot{}) world.RegisterItem(Chain{}) world.RegisterItem(Chest{}) + world.RegisterItem(Chest{Trapped: true}) world.RegisterItem(ChiseledQuartz{}) world.RegisterItem(Clay{}) world.RegisterItem(Coal{}) @@ -255,9 +276,11 @@ func init() { world.RegisterItem(DirtPath{}) world.RegisterItem(Dirt{Coarse: true}) world.RegisterItem(Dirt{}) + world.RegisterItem(Dispenser{}) world.RegisterItem(DragonEgg{}) world.RegisterItem(DriedKelp{}) world.RegisterItem(Dripstone{}) + world.RegisterItem(Dropper{}) world.RegisterItem(Emerald{}) world.RegisterItem(EnchantingTable{}) world.RegisterItem(EndBricks{}) @@ -284,11 +307,14 @@ func init() { world.RegisterItem(Iron{}) world.RegisterItem(ItemFrame{Glowing: true}) world.RegisterItem(ItemFrame{}) + world.RegisterItem(IronDoor{}) + world.RegisterItem(IronTrapDoor{}) world.RegisterItem(Jukebox{}) world.RegisterItem(Kelp{}) world.RegisterItem(Ladder{}) world.RegisterItem(Lapis{}) world.RegisterItem(Lectern{}) + world.RegisterItem(LilyPad{}) world.RegisterItem(LitPumpkin{}) world.RegisterItem(Loom{}) world.RegisterItem(MelonSeeds{}) @@ -307,10 +333,13 @@ func init() { world.RegisterItem(Netherite{}) world.RegisterItem(Netherrack{}) world.RegisterItem(Note{Pitch: 24}) + world.RegisterItem(Observer{}) world.RegisterItem(Obsidian{Crying: true}) world.RegisterItem(Obsidian{}) world.RegisterItem(PackedIce{}) world.RegisterItem(PackedMud{}) + world.RegisterItem(Piston{}) + world.RegisterItem(Piston{Sticky: true}) world.RegisterItem(PinkPetals{}) world.RegisterItem(Podzol{}) world.RegisterItem(PolishedBlackstoneBrick{Cracked: true}) @@ -328,6 +357,14 @@ func init() { world.RegisterItem(RawCopper{}) world.RegisterItem(RawGold{}) world.RegisterItem(RawIron{}) + world.RegisterItem(RedstoneBlock{}) + world.RegisterItem(RedstoneComparator{}) + world.RegisterItem(RedstoneLamp{}) + world.RegisterItem(RedstoneOre{}) + world.RegisterItem(RedstoneOre{Type: DeepslateOre()}) + world.RegisterItem(RedstoneTorch{}) + world.RegisterItem(RedstoneRepeater{}) + world.RegisterItem(RedstoneWire{}) world.RegisterItem(ReinforcedDeepslate{}) world.RegisterItem(ResinBricks{Chiseled: true}) world.RegisterItem(ResinBricks{}) @@ -337,6 +374,7 @@ func init() { world.RegisterItem(SeaLantern{}) world.RegisterItem(SeaPickle{}) world.RegisterItem(Shroomlight{}) + world.RegisterItem(Slime{}) world.RegisterItem(SmithingTable{}) world.RegisterItem(Smoker{}) world.RegisterItem(Snow{}) @@ -349,6 +387,7 @@ func init() { world.RegisterItem(Stone{Smooth: true}) world.RegisterItem(Stone{}) world.RegisterItem(SugarCane{}) + world.RegisterItem(Target{}) world.RegisterItem(TNT{}) world.RegisterItem(Terracotta{}) world.RegisterItem(Tuff{}) @@ -384,6 +423,12 @@ func init() { for _, t := range AnvilTypes() { world.RegisterItem(Anvil{Type: t}) } + for _, t := range ButtonTypes() { + world.RegisterItem(Button{Type: t}) + } + for _, t := range PressurePlateTypes() { + world.RegisterItem(PressurePlate{Type: t}) + } for _, c := range item.Colours() { world.RegisterItem(Banner{Colour: c}) world.RegisterItem(Carpet{Colour: c}) From 063a5c626b01178d635056705834795a77c202bf Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 12 Feb 2025 23:16:07 -0800 Subject: [PATCH 31/33] changes --- server/block/iron_door.go | 19 +++++-------------- server/block/register.go | 3 +++ 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/server/block/iron_door.go b/server/block/iron_door.go index cec72ff73..b74705ae3 100644 --- a/server/block/iron_door.go +++ b/server/block/iron_door.go @@ -16,13 +16,14 @@ type IronDoor struct { bass sourceWaterDisplacer - // Facing is the direction the door is facing. + // Facing is the direction that the door opens towards. When closed, the door sits on the side of its + // block on the opposite direction. Facing cube.Direction // Open is whether the door is open. Open bool - // Top is whether the block is the top or bottom half of a door + // Top is whether the block is the top or bottom half of a door. Top bool - // Right is whether the door hinge is on the right side + // Right is whether the door hinge is on the right side. Right bool } @@ -124,17 +125,7 @@ func (d IronDoor) EncodeItem() (name string, meta int16) { // EncodeBlock ... func (d IronDoor) EncodeBlock() (name string, properties map[string]any) { - direction := 3 - switch d.Facing { - case cube.South: - direction = 1 - case cube.West: - direction = 2 - case cube.East: - direction = 0 - } - - return "minecraft:iron_door", map[string]any{"direction": int32(direction), "door_hinge_bit": d.Right, "open_bit": d.Open, "upper_block_bit": d.Top} + return "minecraft:iron_door", map[string]any{"minecraft:cardinal_direction": d.Facing.RotateRight().String(), "door_hinge_bit": d.Right, "open_bit": d.Open, "upper_block_bit": d.Top} } // allIronDoors returns a list of all door types diff --git a/server/block/register.go b/server/block/register.go index 89a01f078..45d7143ff 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -97,6 +97,7 @@ func init() { world.RegisterBlock(Sand{}) world.RegisterBlock(SeaLantern{}) world.RegisterBlock(Shroomlight{}) + world.RegisterBlock(Slime{}) world.RegisterBlock(SmithingTable{}) world.RegisterBlock(Snow{}) world.RegisterBlock(SoulSand{}) @@ -154,6 +155,7 @@ func init() { registerAll(allCoralBlocks()) registerAll(allDeepslate()) registerAll(allDispensers()) + registerAll(allDroppers()) registerAll(allDoors()) registerAll(allDoubleFlowers()) registerAll(allDoubleTallGrass()) @@ -178,6 +180,7 @@ func init() { registerAll(allLanterns()) registerAll(allLava()) registerAll(allLeaves()) + registerAll(allLevers()) registerAll(allLecterns()) registerAll(allLight()) registerAll(allLitPumpkins()) From 2f29eca7f60006d43f24597641c31f73e58f94c4 Mon Sep 17 00:00:00 2001 From: Jon Date: Sun, 16 Feb 2025 18:32:21 -0800 Subject: [PATCH 32/33] fix glowstone being able to receive redstone power --- server/block/glowstone.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/block/glowstone.go b/server/block/glowstone.go index 905ff5b58..5b56a2dc9 100644 --- a/server/block/glowstone.go +++ b/server/block/glowstone.go @@ -35,3 +35,8 @@ func (Glowstone) EncodeBlock() (string, map[string]any) { func (Glowstone) LightEmissionLevel() uint8 { return 15 } + +// RedstoneBlocking ... +func (Glowstone) RedstoneBlocking() bool { + return true +} From f651230912e790136e8ff2e87025c53104ca1f39 Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 24 Feb 2025 18:00:35 -0800 Subject: [PATCH 33/33] fix: panic when middle clicking lever --- server/block/register.go | 1 + 1 file changed, 1 insertion(+) diff --git a/server/block/register.go b/server/block/register.go index 45d7143ff..76585b87f 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -317,6 +317,7 @@ func init() { world.RegisterItem(Ladder{}) world.RegisterItem(Lapis{}) world.RegisterItem(Lectern{}) + world.RegisterItem(Lever{}) world.RegisterItem(LilyPad{}) world.RegisterItem(LitPumpkin{}) world.RegisterItem(Loom{})