diff --git a/cmd/blockhash/main.go b/cmd/blockhash/main.go index 1f4084a06..8244acfdf 100644 --- a/cmd/blockhash/main.go +++ b/cmd/blockhash/main.go @@ -248,7 +248,8 @@ func (b *hashBuilder) ftype(structName, s string, expr ast.Expr, directives map[ return "uint64(" + s + ".Uint8())", 4 case "CoralType", "SkullType": return "uint64(" + s + ".Uint8())", 3 - case "AnvilType", "SandstoneType", "PrismarineType", "StoneBricksType", "NetherBricksType", "FroglightType", "WallConnectionType", "BlackstoneType", "DeepslateType", "TallGrassType": + case "AnvilType", "SandstoneType", "PrismarineType", "StoneBricksType", "NetherBricksType", "FroglightType", + "WallConnectionType", "BlackstoneType", "DeepslateType", "TallGrassType", "CopperType", "OxidationType": return "uint64(" + s + ".Uint8())", 2 case "OreType", "FireType", "DoubleTallGrassType": return "uint64(" + s + ".Uint8())", 1 diff --git a/server/block/block.go b/server/block/block.go index 4fa16ba14..5a1234376 100644 --- a/server/block/block.go +++ b/server/block/block.go @@ -21,6 +21,15 @@ type Activatable interface { Activate(pos cube.Pos, clickedFace cube.Face, w *world.World, u item.User, ctx *item.UseContext) bool } +// SneakingActivatable represents a block that may be activated by a viewer of the world while sneaking. When +// activated, the block will execute some specific logic. +type SneakingActivatable interface { + // SneakingActivate activates the block at a specific block position while sneaking. The face clicked is + // passed, as well as the world in which the block was activated and the viewer that activated it. + // SneakingActivate returns a bool indicating if activating the block was used successfully. + SneakingActivate(pos cube.Pos, clickedFace cube.Face, w *world.World, u item.User, ctx *item.UseContext) bool +} + // Pickable represents a block that may give a different item then the block itself when picked. type Pickable interface { // Pick returns the item that is picked when the block is picked. diff --git a/server/block/copper.go b/server/block/copper.go new file mode 100644 index 000000000..09adb70bd --- /dev/null +++ b/server/block/copper.go @@ -0,0 +1,118 @@ +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" +) + +// Copper is a solid block commonly found in deserts and beaches underneath sand. +type Copper struct { + solid + bassDrum + + // Type is the type of copper of the block. + Type CopperType + // Oxidation is the level of oxidation of the copper block. + Oxidation OxidationType + // Waxed bool is whether the copper block has been waxed with honeycomb. + Waxed bool +} + +// BreakInfo ... +func (c Copper) BreakInfo() BreakInfo { + return newBreakInfo(3, pickaxeHarvestable, pickaxeEffective, oneOf(c)) +} + +// Wax waxes the copper block to stop it from oxidising further. +func (c Copper) Wax(cube.Pos, mgl64.Vec3) (world.Block, bool) { + if c.Waxed { + return c, false + } + c.Waxed = true + return c, true +} + +func (c Copper) CanOxidate() bool { + return !c.Waxed +} + +func (c Copper) OxidationLevel() OxidationType { + return c.Oxidation +} + +func (c Copper) WithOxidationLevel(o OxidationType) Oxidizable { + c.Oxidation = o + return c +} + +func (c Copper) Activate(pos cube.Pos, _ cube.Face, w *world.World, user item.User, _ *item.UseContext) bool { + var ok bool + c.Oxidation, c.Waxed, ok = activateOxidizable(pos, w, user, c.Oxidation, c.Waxed) + if ok { + w.SetBlock(pos, c, nil) + return true + } + return false +} + +func (c Copper) SneakingActivate(pos cube.Pos, face cube.Face, w *world.World, user item.User, ctx *item.UseContext) bool { + // Sneaking should still trigger axe functionality. + return c.Activate(pos, face, w, user, ctx) +} + +func (c Copper) RandomTick(pos cube.Pos, w *world.World, r *rand.Rand) { + attemptOxidation(pos, w, r, c) +} + +// EncodeItem ... +func (c Copper) EncodeItem() (name string, meta int16) { + if c.Type == NormalCopper() && c.Oxidation == NormalOxidation() && !c.Waxed { + return "minecraft:copper_block", 0 + } + name = "copper" + if c.Type != NormalCopper() { + name = c.Type.String() + "_" + name + } + if c.Oxidation != NormalOxidation() { + name = c.Oxidation.String() + "_" + name + } + if c.Waxed { + name = "waxed_" + name + } + return "minecraft:" + name, 0 +} + +// EncodeBlock ... +func (c Copper) EncodeBlock() (string, map[string]any) { + if c.Type == NormalCopper() && c.Oxidation == NormalOxidation() && !c.Waxed { + return "minecraft:copper_block", nil + } + name := "copper" + if c.Type != NormalCopper() { + name = c.Type.String() + "_" + name + } + if c.Oxidation != NormalOxidation() { + name = c.Oxidation.String() + "_" + name + } + if c.Waxed { + name = "waxed_" + name + } + return "minecraft:" + name, nil +} + +// allCopper returns a list of all copper block variants. +func allCopper() (c []world.Block) { + f := func(waxed bool) { + for _, t := range CopperTypes() { + for _, o := range OxidationTypes() { + c = append(c, Copper{Type: t, Oxidation: o, Waxed: waxed}) + } + } + } + f(true) + f(false) + return +} diff --git a/server/block/copper_door.go b/server/block/copper_door.go new file mode 100644 index 000000000..1f90421e5 --- /dev/null +++ b/server/block/copper_door.go @@ -0,0 +1,225 @@ +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" + "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" +) + +// CopperDoor is a block that can be used as an openable 1x2 barrier. +type CopperDoor struct { + transparent + bass + sourceWaterDisplacer + + // Oxidation is the level of oxidation of the copper door. + Oxidation OxidationType + // Waxed bool is whether the copper door has been waxed with honeycomb. + Waxed bool + // 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 CopperDoor) Model() world.BlockModel { + return model.Door{Facing: d.Facing, Open: d.Open, Right: d.Right} +} + +// Wax waxes the copper door to stop it from oxidising further. +func (d CopperDoor) Wax(cube.Pos, mgl64.Vec3) (world.Block, bool) { + if d.Waxed { + return d, false + } + d.Waxed = true + return d, true +} + +func (d CopperDoor) CanOxidate() bool { + return !d.Waxed +} + +func (d CopperDoor) OxidationLevel() OxidationType { + return d.Oxidation +} + +func (d CopperDoor) WithOxidationLevel(o OxidationType) Oxidizable { + d.Oxidation = o + return d +} + +// NeighbourUpdateTick ... +func (d CopperDoor) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, w *world.World) { + if pos == changedNeighbour { + return + } + if d.Top { + if b, ok := w.Block(pos.Side(cube.FaceDown)).(CopperDoor); !ok { + w.SetBlock(pos, nil, nil) + w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) + } else if d.Oxidation != b.Oxidation || d.Waxed != b.Waxed { + d.Oxidation = b.Oxidation + d.Waxed = b.Waxed + fmt.Println("NeighbourUpdateTick 1", d, b) + w.SetBlock(pos, d, nil) + } + return + } + if solid := w.Block(pos.Side(cube.FaceDown)).Model().FaceSolid(pos.Side(cube.FaceDown), cube.FaceUp, w); !solid { + w.SetBlock(pos, nil, nil) + w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) + } else if b, ok := w.Block(pos.Side(cube.FaceUp)).(CopperDoor); !ok { + w.SetBlock(pos, nil, nil) + w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: d}) + } else if d.Oxidation != b.Oxidation || d.Waxed != b.Waxed { + d.Oxidation = b.Oxidation + d.Waxed = b.Waxed + fmt.Println("NeighbourUpdateTick 2", d, b) + w.SetBlock(pos, d, nil) + } +} + +// UseOnBlock handles the directional placing of doors +func (d CopperDoor) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, 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(w, pos, d) || !replaceableWith(w, pos.Side(cube.FaceUp), d) { + return false + } + if !w.Block(below).Model().FaceSolid(below, cube.FaceUp, w) { + return false + } + d.Facing = user.Rotation().Direction() + left := w.Block(pos.Side(d.Facing.RotateLeft().Face())) + right := w.Block(pos.Side(d.Facing.RotateRight().Face())) + if _, ok := left.(CopperDoor); 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(w, pos, d, user, ctx) + place(w, pos.Side(cube.FaceUp), CopperDoor{Oxidation: d.Oxidation, Waxed: d.Waxed, Facing: d.Facing, Top: true, Right: d.Right}, user, ctx) + ctx.SubtractFromCount(1) + return placed(ctx) +} + +func (d CopperDoor) Activate(pos cube.Pos, _ cube.Face, w *world.World, _ item.User, _ *item.UseContext) bool { + d.Open = !d.Open + w.SetBlock(pos, d, nil) + + otherPos := pos.Side(cube.Face(boolByte(!d.Top))) + other := w.Block(otherPos) + if door, ok := other.(CopperDoor); ok { + door.Open = d.Open + w.SetBlock(otherPos, door, nil) + } + if d.Open { + w.PlaySound(pos.Vec3Centre(), sound.DoorOpen{Block: d}) + return true + } + w.PlaySound(pos.Vec3Centre(), sound.DoorClose{Block: d}) + return true +} + +func (d CopperDoor) SneakingActivate(pos cube.Pos, _ cube.Face, w *world.World, user item.User, _ *item.UseContext) bool { + var ok bool + d.Oxidation, d.Waxed, ok = activateOxidizable(pos, w, user, d.Oxidation, d.Waxed) + if ok { + fmt.Println("SneakingActivate", d) + w.SetBlock(pos, d, nil) + return true + } + return false +} + +func (d CopperDoor) RandomTick(pos cube.Pos, w *world.World, r *rand.Rand) { + attemptOxidation(pos, w, r, d) +} + +// BreakInfo ... +func (d CopperDoor) BreakInfo() BreakInfo { + return newBreakInfo(3, alwaysHarvestable, axeEffective, oneOf(d)) +} + +// SideClosed ... +func (d CopperDoor) SideClosed(cube.Pos, cube.Pos, *world.World) bool { + return false +} + +// EncodeItem ... +func (d CopperDoor) EncodeItem() (name string, meta int16) { + name = "copper_door" + if d.Oxidation != NormalOxidation() { + name = d.Oxidation.String() + "_" + name + } + if d.Waxed { + name = "waxed_" + name + } + return "minecraft:" + name, 0 +} + +// EncodeBlock ... +func (d CopperDoor) 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 + } + + name = "copper_door" + if d.Oxidation != NormalOxidation() { + name = d.Oxidation.String() + "_" + name + } + if d.Waxed { + name = "waxed_" + name + } + return "minecraft:" + name, map[string]any{"direction": int32(direction), "door_hinge_bit": d.Right, "open_bit": d.Open, "upper_block_bit": d.Top} +} + +// allCopperDoors returns a list of all copper door types +func allCopperDoors() (doors []world.Block) { + f := func(waxed bool) { + for _, o := range OxidationTypes() { + for i := cube.Direction(0); i <= 3; i++ { + doors = append(doors, CopperDoor{Oxidation: o, Waxed: waxed, Facing: i, Open: false, Top: false, Right: false}) + doors = append(doors, CopperDoor{Oxidation: o, Waxed: waxed, Facing: i, Open: false, Top: true, Right: false}) + doors = append(doors, CopperDoor{Oxidation: o, Waxed: waxed, Facing: i, Open: true, Top: true, Right: false}) + doors = append(doors, CopperDoor{Oxidation: o, Waxed: waxed, Facing: i, Open: true, Top: false, Right: false}) + doors = append(doors, CopperDoor{Oxidation: o, Waxed: waxed, Facing: i, Open: false, Top: false, Right: true}) + doors = append(doors, CopperDoor{Oxidation: o, Waxed: waxed, Facing: i, Open: false, Top: true, Right: true}) + doors = append(doors, CopperDoor{Oxidation: o, Waxed: waxed, Facing: i, Open: true, Top: true, Right: true}) + doors = append(doors, CopperDoor{Oxidation: o, Waxed: waxed, Facing: i, Open: true, Top: false, Right: true}) + } + } + } + f(false) + f(true) + return +} diff --git a/server/block/copper_grate.go b/server/block/copper_grate.go new file mode 100644 index 000000000..41aec17fc --- /dev/null +++ b/server/block/copper_grate.go @@ -0,0 +1,103 @@ +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" +) + +// CopperGrate is a solid block commonly found in deserts and beaches underneath sand. +type CopperGrate struct { + solid + transparent + bassDrum + + // Oxidation is the level of oxidation of the copper grate. + Oxidation OxidationType + // Waxed bool is whether the copper grate has been waxed with honeycomb. + Waxed bool +} + +// BreakInfo ... +func (c CopperGrate) BreakInfo() BreakInfo { + return newBreakInfo(3, pickaxeHarvestable, pickaxeEffective, oneOf(c)).withBlastResistance(30) +} + +// Wax waxes the copper grate to stop it from oxidising further. +func (c CopperGrate) Wax(cube.Pos, mgl64.Vec3) (world.Block, bool) { + if c.Waxed { + return c, false + } + c.Waxed = true + return c, true +} + +func (c CopperGrate) CanOxidate() bool { + return !c.Waxed +} + +func (c CopperGrate) OxidationLevel() OxidationType { + return c.Oxidation +} + +func (c CopperGrate) WithOxidationLevel(o OxidationType) Oxidizable { + c.Oxidation = o + return c +} + +func (c CopperGrate) Activate(pos cube.Pos, _ cube.Face, w *world.World, user item.User, _ *item.UseContext) bool { + var ok bool + c.Oxidation, c.Waxed, ok = activateOxidizable(pos, w, user, c.Oxidation, c.Waxed) + if ok { + w.SetBlock(pos, c, nil) + return true + } + return false +} + +func (c CopperGrate) SneakingActivate(pos cube.Pos, face cube.Face, w *world.World, user item.User, ctx *item.UseContext) bool { + // Sneaking should still trigger axe functionality. + return c.Activate(pos, face, w, user, ctx) +} + +func (c CopperGrate) RandomTick(pos cube.Pos, w *world.World, r *rand.Rand) { + attemptOxidation(pos, w, r, c) +} + +// EncodeItem ... +func (c CopperGrate) EncodeItem() (name string, meta int16) { + name = "copper_grate" + if c.Oxidation != NormalOxidation() { + name = c.Oxidation.String() + "_" + name + } + if c.Waxed { + name = "waxed_" + name + } + return "minecraft:" + name, 0 +} + +// EncodeBlock ... +func (c CopperGrate) EncodeBlock() (string, map[string]any) { + name := "copper_grate" + if c.Oxidation != NormalOxidation() { + name = c.Oxidation.String() + "_" + name + } + if c.Waxed { + name = "waxed_" + name + } + return "minecraft:" + name, nil +} + +// allCopperGrates returns a list of all copper grate variants. +func allCopperGrates() (c []world.Block) { + f := func(waxed bool) { + for _, o := range OxidationTypes() { + c = append(c, CopperGrate{Oxidation: o, Waxed: waxed}) + } + } + f(true) + f(false) + return +} diff --git a/server/block/copper_trapdoor.go b/server/block/copper_trapdoor.go new file mode 100644 index 000000000..38f54e753 --- /dev/null +++ b/server/block/copper_trapdoor.go @@ -0,0 +1,147 @@ +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" +) + +// CopperTrapdoor is a block that can be used as an openable 1x1 barrier. +type CopperTrapdoor struct { + transparent + bass + sourceWaterDisplacer + + // Oxidation is the level of oxidation of the copper trapdoor. + Oxidation OxidationType + // Waxed bool is whether the copper trapdoor has been waxed with honeycomb. + Waxed bool + // 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 CopperTrapdoor) 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 CopperTrapdoor) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) bool { + pos, face, used := firstReplaceable(w, 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(w, pos, t, user, ctx) + return placed(ctx) +} + +// Wax waxes the copper trapdoor to stop it from oxidising further. +func (t CopperTrapdoor) Wax(cube.Pos, mgl64.Vec3) (world.Block, bool) { + if t.Waxed { + return t, false + } + t.Waxed = true + return t, true +} + +func (t CopperTrapdoor) CanOxidate() bool { + return !t.Waxed +} + +func (t CopperTrapdoor) OxidationLevel() OxidationType { + return t.Oxidation +} + +func (t CopperTrapdoor) WithOxidationLevel(o OxidationType) Oxidizable { + t.Oxidation = o + return t +} + +func (t CopperTrapdoor) Activate(pos cube.Pos, _ cube.Face, w *world.World, _ item.User, _ *item.UseContext) bool { + t.Open = !t.Open + w.SetBlock(pos, t, nil) + if t.Open { + w.PlaySound(pos.Vec3Centre(), sound.TrapdoorOpen{Block: t}) + return true + } + w.PlaySound(pos.Vec3Centre(), sound.TrapdoorClose{Block: t}) + return true +} + +func (t CopperTrapdoor) SneakingActivate(pos cube.Pos, _ cube.Face, w *world.World, user item.User, _ *item.UseContext) bool { + var ok bool + t.Oxidation, t.Waxed, ok = activateOxidizable(pos, w, user, t.Oxidation, t.Waxed) + if ok { + w.SetBlock(pos, t, nil) + return true + } + return false +} + +func (t CopperTrapdoor) RandomTick(pos cube.Pos, w *world.World, r *rand.Rand) { + attemptOxidation(pos, w, r, t) +} + +// BreakInfo ... +func (t CopperTrapdoor) BreakInfo() BreakInfo { + return newBreakInfo(3, alwaysHarvestable, axeEffective, oneOf(t)).withBlastResistance(15.0) +} + +// SideClosed ... +func (t CopperTrapdoor) SideClosed(cube.Pos, cube.Pos, *world.World) bool { + return false +} + +// EncodeItem ... +func (t CopperTrapdoor) EncodeItem() (name string, meta int16) { + name = "copper_trapdoor" + if t.Oxidation != NormalOxidation() { + name = t.Oxidation.String() + "_" + name + } + if t.Waxed { + name = "waxed_" + name + } + return "minecraft:" + name, 0 +} + +// EncodeBlock ... +func (t CopperTrapdoor) EncodeBlock() (name string, properties map[string]any) { + name = "copper_trapdoor" + if t.Oxidation != NormalOxidation() { + name = t.Oxidation.String() + "_" + name + } + if t.Waxed { + name = "waxed_" + name + } + return "minecraft:" + name, map[string]any{"direction": int32(math.Abs(float64(t.Facing) - 3)), "open_bit": t.Open, "upside_down_bit": t.Top} +} + +// allCopperTrapdoors returns a list of all copper trapdoor types +func allCopperTrapdoors() (trapdoors []world.Block) { + f := func(waxed bool) { + for _, o := range OxidationTypes() { + for i := cube.Direction(0); i <= 3; i++ { + trapdoors = append(trapdoors, CopperTrapdoor{Oxidation: o, Waxed: waxed, Facing: i, Open: false, Top: false}) + trapdoors = append(trapdoors, CopperTrapdoor{Oxidation: o, Waxed: waxed, Facing: i, Open: false, Top: true}) + trapdoors = append(trapdoors, CopperTrapdoor{Oxidation: o, Waxed: waxed, Facing: i, Open: true, Top: true}) + trapdoors = append(trapdoors, CopperTrapdoor{Oxidation: o, Waxed: waxed, Facing: i, Open: true, Top: false}) + } + } + } + f(false) + f(true) + return +} diff --git a/server/block/copper_type.go b/server/block/copper_type.go new file mode 100644 index 000000000..aeac557d5 --- /dev/null +++ b/server/block/copper_type.go @@ -0,0 +1,59 @@ +package block + +// CopperType represents a type of copper. +type CopperType struct { + copper +} + +type copper uint8 + +// NormalCopper is the normal variant of copper. +func NormalCopper() CopperType { + return CopperType{0} +} + +// CutCopper is the cut variant of copper. +func CutCopper() CopperType { + return CopperType{1} +} + +// ChiseledCopper is the chiseled variant of copper. +func ChiseledCopper() CopperType { + return CopperType{2} +} + +// Uint8 returns the copper as a uint8. +func (s copper) Uint8() uint8 { + return uint8(s) +} + +// Name ... +func (s copper) Name() string { + switch s { + case 0: + return "Copper" + case 1: + return "Cut Copper" + case 2: + return "Chiseled Copper" + } + panic("unknown copper type") +} + +// String ... +func (s copper) String() string { + switch s { + case 0: + return "default" + case 1: + return "cut" + case 2: + return "chiseled" + } + panic("unknown copper type") +} + +// CopperTypes ... +func CopperTypes() []CopperType { + return []CopperType{NormalCopper(), CutCopper(), ChiseledCopper()} +} diff --git a/server/block/hash.go b/server/block/hash.go index 0c1375a22..197099232 100644 --- a/server/block/hash.go +++ b/server/block/hash.go @@ -40,7 +40,11 @@ const ( hashComposter hashConcrete hashConcretePowder + hashCopper + hashCopperDoor + hashCopperGrate hashCopperOre + hashCopperTrapdoor hashCoral hashCoralBlock hashCraftingTable @@ -329,10 +333,26 @@ func (c ConcretePowder) Hash() (uint64, uint64) { return hashConcretePowder, uint64(c.Colour.Uint8()) } +func (c Copper) Hash() (uint64, uint64) { + return hashCopper, uint64(c.Type.Uint8()) | uint64(c.Oxidation.Uint8())<<2 | uint64(boolByte(c.Waxed))<<4 +} + +func (d CopperDoor) Hash() (uint64, uint64) { + return hashCopperDoor, uint64(d.Oxidation.Uint8()) | uint64(boolByte(d.Waxed))<<2 | uint64(d.Facing)<<3 | uint64(boolByte(d.Open))<<5 | uint64(boolByte(d.Top))<<6 | uint64(boolByte(d.Right))<<7 +} + +func (c CopperGrate) Hash() (uint64, uint64) { + return hashCopperGrate, uint64(c.Oxidation.Uint8()) | uint64(boolByte(c.Waxed))<<2 +} + func (c CopperOre) Hash() (uint64, uint64) { return hashCopperOre, uint64(c.Type.Uint8()) } +func (t CopperTrapdoor) Hash() (uint64, uint64) { + return hashCopperTrapdoor, uint64(t.Oxidation.Uint8()) | uint64(boolByte(t.Waxed))<<2 | uint64(t.Facing)<<3 | uint64(boolByte(t.Open))<<5 | uint64(boolByte(t.Top))<<6 +} + func (c Coral) Hash() (uint64, uint64) { return hashCoral, uint64(c.Type.Uint8()) | uint64(boolByte(c.Dead))<<3 } diff --git a/server/block/oxidation_type.go b/server/block/oxidation_type.go new file mode 100644 index 000000000..30e34c94e --- /dev/null +++ b/server/block/oxidation_type.go @@ -0,0 +1,86 @@ +package block + +// OxidationType represents a type of oxidation. +type OxidationType struct { + oxidation +} + +type oxidation uint8 + +// NormalOxidation is the normal variant of oxidation. +func NormalOxidation() OxidationType { + return OxidationType{0} +} + +// ExposedOxidation is the exposed variant of oxidation. +func ExposedOxidation() OxidationType { + return OxidationType{1} +} + +// WeatheredOxidation is the weathered variant of oxidation. +func WeatheredOxidation() OxidationType { + return OxidationType{2} +} + +// OxidizedOxidation is the oxidized variant of oxidation. +func OxidizedOxidation() OxidationType { + return OxidationType{3} +} + +// Uint8 returns the oxidation as a uint8. +func (s oxidation) Uint8() uint8 { + return uint8(s) +} + +// Name ... +func (s oxidation) Name() string { + switch s { + case 0: + return "" + case 1: + return "Exposed" + case 2: + return "Weathered" + case 3: + return "Oxidized" + } + panic("unknown oxidation type") +} + +// Decrease attempts to decrease the oxidation level by one. It returns the new oxidation level and if the +// decrease was successful. +func (s oxidation) Decrease() (OxidationType, bool) { + if s > 0 { + return OxidationType{s - 1}, true + } + return NormalOxidation(), false +} + +// Increase attempts to increase the oxidation level by one. It returns the new oxidation level and if the +// increase was successful. +func (s oxidation) Increase() (OxidationType, bool) { + if s < 3 { + return OxidationType{s + 1}, true + } + return OxidizedOxidation(), false +} + +// String ... +func (s oxidation) String() string { + switch s { + case 0: + return "" + case 1: + return "exposed" + case 2: + return "weathered" + case 3: + return "oxidized" + } + panic("unknown oxidation type") +} + +// OxidationTypes ... +func OxidationTypes() []OxidationType { + return []OxidationType{NormalOxidation(), ExposedOxidation(), WeatheredOxidation(), OxidizedOxidation()} +} diff --git a/server/block/oxidizable.go b/server/block/oxidizable.go new file mode 100644 index 000000000..6491623f5 --- /dev/null +++ b/server/block/oxidizable.go @@ -0,0 +1,89 @@ +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" + "math/rand" +) + +// Oxidizable is a block that can naturally oxidise over time, such as copper. +type Oxidizable interface { + world.Block + // CanOxidate returns whether the block can oxidate, i.e. if it's not waxed. + CanOxidate() bool + // OxidationLevel returns the currently level of oxidation of the block. + OxidationLevel() OxidationType + // WithOxidationLevel returns the oxidizable block with the oxidation level passed. + WithOxidationLevel(OxidationType) Oxidizable +} + +// activateOxidizable performs the logic for activating an oxidizable block, returning the updated oxidation +// level and wax state of the block, as well as whether the block was successfully activated. This function +// will not handle the setting of the block if it has been modified. +func activateOxidizable(pos cube.Pos, w *world.World, user item.User, o OxidationType, waxed bool) (OxidationType, bool, bool) { + mainHand, _ := user.HeldItems() + // TODO: Immediately return false if holding shield in offhand (https://bugs.mojang.com/browse/MC-270047). + if _, ok := mainHand.Item().(item.Axe); !ok { + return o, waxed, false + } else if waxed { + w.PlaySound(pos.Vec3Centre(), sound.WaxRemoved{}) + return o, false, true + } + + if ox, ok := o.Decrease(); ok { + w.PlaySound(pos.Vec3Centre(), sound.CopperScraped{}) + return ox, false, true + } + return o, false, true +} + +// attemptOxidation attempts to oxidise the block at the position passed. The details for this logic is +// described on the Minecraft Wiki: https://minecraft.wiki/w/Oxidation. +func attemptOxidation(pos cube.Pos, w *world.World, r *rand.Rand, o Oxidizable) { + level := o.OxidationLevel() + if level == OxidizedOxidation() || !o.CanOxidate() { + return + } else if r.Float64() > 64/1125 { + return + } + + var all, higher int + for x := -4; x <= 4; x++ { + for y := -4; y <= 4; y++ { + for z := -4; z <= 4; z++ { + if x == 0 && y == 0 && z == 0 { + continue + } + nPos := pos.Add(cube.Pos{x, y, z}) + dist := abs(nPos.X()-pos.X()) + abs(nPos.Y()-pos.Y()) + abs(nPos.Z()-pos.Z()) + if dist > 4 { + continue + } + + b, ok := w.Block(nPos).(Oxidizable) + if !ok || !b.CanOxidate() { + continue + } else if b.OxidationLevel().Uint8() < level.Uint8() { + return + } + all++ + if b.OxidationLevel().Uint8() > level.Uint8() { + higher++ + } + } + } + } + + chance := float64(higher+1) / float64(all+1) + if level == NormalOxidation() { + chance *= chance * 0.75 + } else { + chance *= chance + } + if r.Float64() < chance { + level, _ = level.Increase() + w.SetBlock(pos, o.WithOxidationLevel(level), nil) + } +} diff --git a/server/block/register.go b/server/block/register.go index 169e72b3e..81b126cfa 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -198,6 +198,10 @@ func init() { registerAll(allWood()) registerAll(allWool()) registerAll(allDecoratedPots()) + registerAll(allCopper()) + registerAll(allCopperDoors()) + registerAll(allCopperGrates()) + registerAll(allCopperTrapdoors()) } func init() { @@ -442,6 +446,19 @@ func init() { for _, t := range DeepslateTypes() { world.RegisterItem(Deepslate{Type: t}) } + for _, o := range OxidationTypes() { + world.RegisterItem(CopperDoor{Oxidation: o}) + world.RegisterItem(CopperDoor{Oxidation: o, Waxed: true}) + world.RegisterItem(CopperGrate{Oxidation: o}) + world.RegisterItem(CopperGrate{Oxidation: o, Waxed: true}) + world.RegisterItem(CopperTrapdoor{Oxidation: o}) + world.RegisterItem(CopperTrapdoor{Oxidation: o, Waxed: true}) + + for _, c := range CopperTypes() { + world.RegisterItem(Copper{Type: c, Oxidation: o}) + world.RegisterItem(Copper{Type: c, Oxidation: o, Waxed: true}) + } + } } func registerAll(blocks []world.Block) { diff --git a/server/block/slab.go b/server/block/slab.go index 0867b6a48..f7e1583b4 100644 --- a/server/block/slab.go +++ b/server/block/slab.go @@ -108,7 +108,8 @@ func (s Slab) BreakInfo() BreakInfo { hardness, blastResistance, harvestable, effective := 2.0, 30.0, pickaxeHarvestable, pickaxeEffective switch block := s.Block.(type) { - // TODO: Copper + case Copper: + hardness = 3.0 case Deepslate, DeepslateBricks, DeepslateTiles: hardness = 3.5 case EndBricks: @@ -154,7 +155,8 @@ func (s Slab) Model() world.BlockModel { // EncodeItem ... func (s Slab) EncodeItem() (string, int16) { - return "minecraft:" + encodeSlabBlock(s.Block) + "_slab", 0 + name, suffix := encodeSlabBlock(s.Block, false) + return "minecraft:" + name + suffix, 0 } // EncodeBlock ... @@ -163,11 +165,8 @@ func (s Slab) EncodeBlock() (string, map[string]any) { if s.Top { side = "top" } - suffix := "_slab" - if s.Double { - suffix = "_double_slab" - } - return "minecraft:" + encodeSlabBlock(s.Block) + suffix, map[string]any{"minecraft:vertical_half": side} + name, suffix := encodeSlabBlock(s.Block, s.Double) + return "minecraft:" + name + suffix, map[string]any{"minecraft:vertical_half": side} } // allSlabs ... diff --git a/server/block/slab_type.go b/server/block/slab_type.go index 0b179f8d4..a1f25fcf0 100644 --- a/server/block/slab_type.go +++ b/server/block/slab_type.go @@ -7,121 +7,140 @@ import ( // encodeSlabBlock encodes the provided block in to an identifier and meta value that can be used to encode the slab. // halfFlattened is a temporary hack for a stone_block_slab which has been flattened but double_stone_block_slab // has not. This can be removed in 1.21.10 where they have flattened all slab types. -func encodeSlabBlock(block world.Block) (id string) { +func encodeSlabBlock(block world.Block, double bool) (id string, suffix string) { + suffix = "_slab" + if double { + suffix = "_double_slab" + } + switch block := block.(type) { - // TODO: Copper case Andesite: if block.Polished { - return "polished_andesite" + return "polished_andesite", suffix } - return "andesite" + return "andesite", suffix case Blackstone: if block.Type == NormalBlackstone() { - return "blackstone" + return "blackstone", suffix } else if block.Type == PolishedBlackstone() { - return "polished_blackstone" + return "polished_blackstone", suffix } case Bricks: - return "brick" + return "brick", suffix case Cobblestone: if block.Mossy { - return "mossy_cobblestone" + return "mossy_cobblestone", suffix + } + return "cobblestone", suffix + case Copper: + if block.Type == CutCopper() { + suffix = "cut_copper_slab" + if double { + suffix = "double_" + suffix + } + var name string + if block.Oxidation != NormalOxidation() { + name = block.Oxidation.String() + "_" + } + if block.Waxed { + name = "waxed_" + name + } + return name, suffix } - return "cobblestone" case Deepslate: if block.Type == CobbledDeepslate() { - return "cobbled_deepslate" + return "cobbled_deepslate", suffix } else if block.Type == PolishedDeepslate() { - return "polished_deepslate" + return "polished_deepslate", suffix } case DeepslateBricks: if !block.Cracked { - return "deepslate_brick" + return "deepslate_brick", suffix } case DeepslateTiles: if !block.Cracked { - return "deepslate_tile" + return "deepslate_tile", suffix } case Diorite: if block.Polished { - return "polished_diorite" + return "polished_diorite", suffix } - return "diorite" + return "diorite", suffix case EndBricks: - return "end_stone_brick" + return "end_stone_brick", suffix case Granite: if block.Polished { - return "polished_granite" + return "polished_granite", suffix } - return "granite" + return "granite", suffix case MudBricks: - return "mud_brick" + return "mud_brick", suffix case NetherBricks: if block.Type == RedNetherBricks() { - return "nether_brick" + return "nether_brick", suffix } - return "red_nether_brick" + return "red_nether_brick", suffix case Planks: - return block.Wood.String() + return block.Wood.String(), suffix case PolishedBlackstoneBrick: if !block.Cracked { - return "polished_blackstone_brick" + return "polished_blackstone_brick", suffix } case PolishedTuff: - return "polished_tuff" + return "polished_tuff", suffix case Prismarine: switch block.Type { case NormalPrismarine(): - return "prismarine" + return "prismarine", suffix case DarkPrismarine(): - return "dark_prismarine" + return "dark_prismarine", suffix case BrickPrismarine(): - return "prismarine_brick" + return "prismarine_brick", suffix } panic("invalid prismarine type") case Purpur: - return "purpur" + return "purpur", suffix case Quartz: if block.Smooth { - return "smooth_quartz" + return "smooth_quartz", suffix } - return "quartz" + return "quartz", suffix case Sandstone: switch block.Type { case NormalSandstone(): if block.Red { - return "red_sandstone" + return "red_sandstone", suffix } - return "sandstone" + return "sandstone", suffix case CutSandstone(): if block.Red { - return "cut_red_sandstone" + return "cut_red_sandstone", suffix } - return "cut_sandstone" + return "cut_sandstone", suffix case SmoothSandstone(): if block.Red { - return "smooth_red_sandstone" + return "smooth_red_sandstone", suffix } - return "smooth_sandstone" + return "smooth_sandstone", suffix } panic("invalid sandstone type") case Stone: if block.Smooth { - return "smooth_stone" + return "smooth_stone", suffix } - return "normal_stone" + return "normal_stone", suffix case StoneBricks: if block.Type == MossyStoneBricks() { - return "mossy_stone_brick" + return "mossy_stone_brick", suffix } - return "stone_brick" + return "stone_brick", suffix case Tuff: if !block.Chiseled { - return "tuff" + return "tuff", suffix } case TuffBricks: if !block.Chiseled { - return "tuff_brick" + return "tuff_brick", suffix } } panic("invalid block used for slab") @@ -130,7 +149,6 @@ func encodeSlabBlock(block world.Block) (id string) { // SlabBlocks returns a list of all possible blocks for a slab. func SlabBlocks() []world.Block { b := []world.Block{ - // TODO: Copper Andesite{Polished: true}, Andesite{}, Blackstone{Type: PolishedBlackstone()}, @@ -174,5 +192,9 @@ func SlabBlocks() []world.Block { for _, w := range WoodTypes() { b = append(b, Planks{Wood: w}) } + for _, o := range OxidationTypes() { + b = append(b, Copper{Type: CutCopper(), Oxidation: o}) + b = append(b, Copper{Type: CutCopper(), Oxidation: o, Waxed: true}) + } return b } diff --git a/server/block/stairs.go b/server/block/stairs.go index f7fc5abe6..e5f7c0437 100644 --- a/server/block/stairs.go +++ b/server/block/stairs.go @@ -50,9 +50,10 @@ func (s Stairs) BreakInfo() BreakInfo { hardness, blastResistance, harvestable, effective := 2.0, 30.0, pickaxeHarvestable, pickaxeEffective switch block := s.Block.(type) { - // TODO: Copper case Blackstone: hardness = 1.5 + case Copper: + hardness = 3.0 case Deepslate, DeepslateBricks, DeepslateTiles: hardness = 3.5 case Planks: diff --git a/server/block/stairs_type.go b/server/block/stairs_type.go index 6198cf4f4..2dbef33ad 100644 --- a/server/block/stairs_type.go +++ b/server/block/stairs_type.go @@ -26,6 +26,17 @@ func encodeStairsBlock(block world.Block) string { return "mossy_cobblestone" } return "stone" + case Copper: + if block.Type == CutCopper() { + name := "cut_copper" + if block.Oxidation != NormalOxidation() { + name = block.Oxidation.String() + "_" + name + } + if block.Waxed { + name = "waxed_" + name + } + return name + } case Deepslate: if block.Type == CobbledDeepslate() { return "cobbled_deepslate" @@ -122,7 +133,6 @@ func encodeStairsBlock(block world.Block) string { // StairsBlocks returns a list of all possible blocks for stairs. func StairsBlocks() []world.Block { b := []world.Block{ - // TODO: Copper Andesite{Polished: true}, Andesite{}, Blackstone{Type: PolishedBlackstone()}, @@ -165,5 +175,9 @@ func StairsBlocks() []world.Block { for _, w := range WoodTypes() { b = append(b, Planks{Wood: w}) } + for _, o := range OxidationTypes() { + b = append(b, Copper{Type: CutCopper(), Oxidation: o}) + b = append(b, Copper{Type: CutCopper(), Oxidation: o, Waxed: true}) + } return b } diff --git a/server/player/player.go b/server/player/player.go index 3f630ee3c..f751358dd 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -1466,6 +1466,15 @@ func (p *Player) UseItemOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec } } } + if p.Sneaking() { + if act, ok := b.(block.SneakingActivatable); ok { + if useCtx := p.useContext(); act.SneakingActivate(pos, face, p.World(), p, useCtx) { + p.SetHeldItems(p.subtractItem(p.damageItem(i, useCtx.Damage), useCtx.CountSub), left) + p.addNewItem(useCtx) + return + } + } + } if i.Empty() { return } diff --git a/server/session/world.go b/server/session/world.go index 953efc485..91177bcc6 100644 --- a/server/session/world.go +++ b/server/session/world.go @@ -535,6 +535,16 @@ func (s *Session) playSound(pos mgl64.Vec3, t world.Sound, disableRelative bool) }) case sound.WaxedSignFailedInteraction: pk.SoundType = packet.SoundEventWaxedSignInteractFail + case sound.WaxRemoved: + s.writePacket(&packet.LevelEvent{ + EventType: packet.LevelEventWaxOff, + Position: vec64To32(pos), + }) + case sound.CopperScraped: + s.writePacket(&packet.LevelEvent{ + EventType: packet.LevelEventScrape, + Position: vec64To32(pos), + }) case sound.Pop: s.writePacket(&packet.LevelEvent{ EventType: packet.LevelEventSoundInfinityArrowPickup, diff --git a/server/world/sound/block.go b/server/world/sound/block.go index 1518bd2ac..a3afb1951 100644 --- a/server/world/sound/block.go +++ b/server/world/sound/block.go @@ -191,6 +191,12 @@ type SignWaxed struct{ sound } // WaxedSignFailedInteraction is a sound played when a player tries to interact with a waxed sign. type WaxedSignFailedInteraction struct{ sound } +// WaxRemoved is a sound played when wax is removed from a block. +type WaxRemoved struct{ sound } + +// CopperScraped is a sound played when a player scrapes a copper block to reduce its oxidation level. +type CopperScraped struct{ sound } + // sound implements the world.Sound interface. type sound struct{}