From b15b4dabdceb08fd848cd87358f1030d2eac1a83 Mon Sep 17 00:00:00 2001 From: DaPigGuy Date: Sat, 16 Nov 2024 00:46:09 -0800 Subject: [PATCH 01/28] block/purpur.go: Fix invalid purpur pillar item id Fixes db26a06cc6a058700a596d0008271015a419cfb4 --- server/block/purpur.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/block/purpur.go b/server/block/purpur.go index e7fce21c5..dfcf34b5c 100644 --- a/server/block/purpur.go +++ b/server/block/purpur.go @@ -57,7 +57,7 @@ func (p PurpurPillar) BreakInfo() BreakInfo { // EncodeItem ... func (p PurpurPillar) EncodeItem() (name string, meta int16) { - return "minecraft:purpur_block", 1 + return "minecraft:purpur_pillar", 0 } // EncodeBlock ... From a2258b1e24a897e746ad8aaa747d7ae9a06c37e6 Mon Sep 17 00:00:00 2001 From: DaPigGuy Date: Sat, 16 Nov 2024 01:10:21 -0800 Subject: [PATCH 02/28] block/sandstone.go: Update smooth sandstone hardness & blast resistance to match vanilla --- server/block/sandstone.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/block/sandstone.go b/server/block/sandstone.go index 1254cc0a6..0c983a346 100644 --- a/server/block/sandstone.go +++ b/server/block/sandstone.go @@ -20,6 +20,9 @@ type Sandstone struct { // BreakInfo ... func (s Sandstone) BreakInfo() BreakInfo { + if s.Type == SmoothSandstone() { + return newBreakInfo(2, pickaxeHarvestable, pickaxeEffective, oneOf(s)).withBlastResistance(30) + } return newBreakInfo(0.8, pickaxeHarvestable, pickaxeEffective, oneOf(s)) } From 9f0f192412c72bbff841a8935aa1a76142356b8c Mon Sep 17 00:00:00 2001 From: DaPigGuy Date: Sat, 16 Nov 2024 01:19:34 -0800 Subject: [PATCH 03/28] block/mud_bricks.go: Update hardness & blast resistance to match vanilla --- server/block/mud_bricks.go | 2 +- server/block/wall.go | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/server/block/mud_bricks.go b/server/block/mud_bricks.go index d466eac44..1acf700c7 100644 --- a/server/block/mud_bricks.go +++ b/server/block/mud_bricks.go @@ -8,7 +8,7 @@ type MudBricks struct { // BreakInfo ... func (m MudBricks) BreakInfo() BreakInfo { - return newBreakInfo(2, alwaysHarvestable, nothingEffective, oneOf(m)) + return newBreakInfo(1.5, alwaysHarvestable, nothingEffective, oneOf(m)).withBlastResistance(15) } // EncodeItem ... diff --git a/server/block/wall.go b/server/block/wall.go index 3bfd32ace..cfed92dca 100644 --- a/server/block/wall.go +++ b/server/block/wall.go @@ -67,12 +67,7 @@ func (w Wall) BreakInfo() BreakInfo { if !ok { panic("wall block is not breakable") } - blastResistance := breakable.BreakInfo().BlastResistance - switch w.Block.(type) { - case MudBricks: - blastResistance = 30 - } - return newBreakInfo(breakable.BreakInfo().Hardness, pickaxeHarvestable, pickaxeEffective, oneOf(w)).withBlastResistance(blastResistance) + return newBreakInfo(breakable.BreakInfo().Hardness, pickaxeHarvestable, pickaxeEffective, oneOf(w)).withBlastResistance(breakable.BreakInfo().BlastResistance) } // NeighbourUpdateTick ... From 650e4f7296379265f2282f9d599871ab57e3e97b Mon Sep 17 00:00:00 2001 From: DaPigGuy Date: Sat, 16 Nov 2024 01:21:09 -0800 Subject: [PATCH 04/28] block/wall_type.go: Add missing Tuff Wall block --- server/block/wall_type.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/block/wall_type.go b/server/block/wall_type.go index 40f005cc6..14ea54d5e 100644 --- a/server/block/wall_type.go +++ b/server/block/wall_type.go @@ -75,6 +75,8 @@ func encodeWallBlock(block world.Block) string { } else if block.Type == MossyStoneBricks() { return "mossy_stone_brick" } + case Tuff: + return "tuff" } panic("invalid block used for wall") } @@ -104,5 +106,6 @@ func WallBlocks() []world.Block { Sandstone{}, StoneBricks{Type: MossyStoneBricks()}, StoneBricks{}, + Tuff{}, } } From c2680207b9ed084f37292dc0653db63676b4055c Mon Sep 17 00:00:00 2001 From: DaPigGuy Date: Sat, 16 Nov 2024 01:25:56 -0800 Subject: [PATCH 05/28] block/end_bricks.go: Update hardness & blast resistance to match vanilla --- server/block/end_bricks.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/block/end_bricks.go b/server/block/end_bricks.go index 7d7cba763..c5b847495 100644 --- a/server/block/end_bricks.go +++ b/server/block/end_bricks.go @@ -8,7 +8,7 @@ type EndBricks struct { // BreakInfo ... func (e EndBricks) BreakInfo() BreakInfo { - return newBreakInfo(0.8, pickaxeHarvestable, pickaxeEffective, oneOf(e)) + return newBreakInfo(3, pickaxeHarvestable, pickaxeEffective, oneOf(e)).withBlastResistance(45) } // EncodeItem ... From dbfbed6187f96589f8177a3a99eb7403c7ed0403 Mon Sep 17 00:00:00 2001 From: DaPigGuy Date: Sat, 16 Nov 2024 01:44:35 -0800 Subject: [PATCH 06/28] block/register.go: Fix Fletching Table item not being registered Fixes a43d250ce06c44f191c0e597936b44cf12ca9abf --- server/block/register.go | 1 + 1 file changed, 1 insertion(+) diff --git a/server/block/register.go b/server/block/register.go index 2ce11330e..a0c5f3319 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -248,6 +248,7 @@ func init() { world.RegisterItem(EndStone{}) world.RegisterItem(EnderChest{}) world.RegisterItem(Farmland{}) + world.RegisterItem(FletchingTable{}) world.RegisterItem(Furnace{}) world.RegisterItem(GlassPane{}) world.RegisterItem(Glass{}) From 43645da8855c24acfe35a65a98416ecb9b14939f Mon Sep 17 00:00:00 2001 From: DaPigGuy Date: Sat, 16 Nov 2024 02:01:58 -0800 Subject: [PATCH 07/28] block/sandstone.go: Fix invalid sandstone variant item ids Blame Seb! --- server/block/sandstone.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/server/block/sandstone.go b/server/block/sandstone.go index 0c983a346..466cf2547 100644 --- a/server/block/sandstone.go +++ b/server/block/sandstone.go @@ -28,10 +28,14 @@ func (s Sandstone) BreakInfo() BreakInfo { // EncodeItem ... func (s Sandstone) EncodeItem() (name string, meta int16) { + var prefix string + if s.Type != NormalSandstone() { + prefix = s.Type.String() + "_" + } if s.Red { - return "minecraft:red_sandstone", int16(s.Type.Uint8()) + return "minecraft:" + prefix + "red_sandstone", 0 } - return "minecraft:sandstone", int16(s.Type.Uint8()) + return "minecraft:" + prefix + "sandstone", 0 } // EncodeBlock ... @@ -40,11 +44,10 @@ func (s Sandstone) EncodeBlock() (string, map[string]any) { if s.Type != NormalSandstone() { prefix = s.Type.String() + "_" } - suffix := "sandstone" if s.Red { - suffix = "red_sandstone" + return "minecraft:" + prefix + "red_sandstone", nil } - return "minecraft:" + prefix + suffix, nil + return "minecraft:" + prefix + "sandstone", nil } // SmeltInfo ... From 5743097a5a3a69376dfc153ceef4823290f37591 Mon Sep 17 00:00:00 2001 From: DaPigGuy Date: Sat, 16 Nov 2024 02:31:23 -0800 Subject: [PATCH 08/28] block/wool.go: Fix invalid item ids --- server/block/wool.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/block/wool.go b/server/block/wool.go index 21cc0572f..9d0ada494 100644 --- a/server/block/wool.go +++ b/server/block/wool.go @@ -31,7 +31,7 @@ func (w Wool) BreakInfo() BreakInfo { // EncodeItem ... func (w Wool) EncodeItem() (name string, meta int16) { - return "minecraft:wool", int16(w.Colour.Uint8()) + return "minecraft:" + w.Colour.String() + "_wool", 0 } // EncodeBlock ... From 16b4c618281d6229b236f697f61f1d965d1b05a1 Mon Sep 17 00:00:00 2001 From: DaPigGuy Date: Sat, 16 Nov 2024 03:55:36 -0800 Subject: [PATCH 09/28] session/handler_loom.go: Fix cost of creating multiple banners (#934) --- go.mod | 2 +- go.sum | 4 ++-- server/session/handler_loom.go | 17 +++++++++++------ 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index b634c1190..553ca420e 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/go-gl/mathgl v1.1.0 github.com/google/uuid v1.6.0 github.com/pelletier/go-toml v1.9.5 - github.com/sandertv/gophertunnel v1.42.0 + github.com/sandertv/gophertunnel v1.42.2 github.com/segmentio/fasthash v1.0.3 golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c golang.org/x/mod v0.21.0 diff --git a/go.sum b/go.sum index 2d531cf1f..e581e5a30 100644 --- a/go.sum +++ b/go.sum @@ -41,8 +41,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sandertv/go-raknet v1.14.2 h1:UZLyHn5yQU2Dq2GVq/LlxwAUikaq4q4AA1rl/Pf3AXQ= github.com/sandertv/go-raknet v1.14.2/go.mod h1:/yysjwfCXm2+2OY8mBazLzcxJ3irnylKCyG3FLgUPVU= -github.com/sandertv/gophertunnel v1.42.0 h1:qOc/Dht/Kvh67uxJ6sgkL0oP+jhuXkVwk7KEf3d1p9M= -github.com/sandertv/gophertunnel v1.42.0/go.mod h1:krvLSeRUNQ2iEYJNEgzrKtWO8W5ybZxN5lFfSCkHoNk= +github.com/sandertv/gophertunnel v1.42.2 h1:YWi4vAkq9IpNFIDxOrSMeGh2wkWMCMO8Kg45/R7VTQs= +github.com/sandertv/gophertunnel v1.42.2/go.mod h1:krvLSeRUNQ2iEYJNEgzrKtWO8W5ybZxN5lFfSCkHoNk= github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/server/session/handler_loom.go b/server/session/handler_loom.go index 4d73ee14e..cb3d98c77 100644 --- a/server/session/handler_loom.go +++ b/server/session/handler_loom.go @@ -26,13 +26,18 @@ func (h *ItemStackRequestHandler) handleLoomCraft(a *protocol.CraftLoomRecipeSta return fmt.Errorf("no loom container opened") } + timesCrafted := int(a.TimesCrafted) + if timesCrafted < 1 { + return fmt.Errorf("times crafted must be least 1") + } + // Next, check if the input slot has a valid banner item. input, _ := h.itemInSlot(protocol.StackRequestSlotInfo{ Container: protocol.FullContainerName{ContainerID: protocol.ContainerLoomInput}, Slot: loomInputSlot, }, s) - if input.Empty() { - return fmt.Errorf("input item is empty") + if input.Count() < timesCrafted { + return fmt.Errorf("input item count is less than times crafted") } b, ok := input.Item().(block.Banner) if !ok { @@ -47,8 +52,8 @@ func (h *ItemStackRequestHandler) handleLoomCraft(a *protocol.CraftLoomRecipeSta Container: protocol.FullContainerName{ContainerID: protocol.ContainerLoomDye}, Slot: loomDyeSlot, }, s) - if dye.Empty() { - return fmt.Errorf("dye item is empty") + if dye.Count() < timesCrafted { + return fmt.Errorf("dye item count is less than times crafted") } d, ok := dye.Item().(item.Dye) if !ok { @@ -86,10 +91,10 @@ func (h *ItemStackRequestHandler) handleLoomCraft(a *protocol.CraftLoomRecipeSta h.setItemInSlot(protocol.StackRequestSlotInfo{ Container: protocol.FullContainerName{ContainerID: protocol.ContainerLoomInput}, Slot: loomInputSlot, - }, input.Grow(-1), s) + }, input.Grow(-timesCrafted), s) h.setItemInSlot(protocol.StackRequestSlotInfo{ Container: protocol.FullContainerName{ContainerID: protocol.ContainerLoomDye}, Slot: loomDyeSlot, - }, dye.Grow(-1), s) + }, dye.Grow(-timesCrafted), s) return h.createResults(s, duplicateStack(input, b)) } From 4064d7359a33811954efe0db78fba2b5a00ed0b1 Mon Sep 17 00:00:00 2001 From: TwistedAsylumMC Date: Sat, 16 Nov 2024 14:53:15 +0000 Subject: [PATCH 10/28] server/block: Implement missing tuff blocks & fix some slab/stairs break info --- server/block/hash.go | 14 ++++++++++++-- server/block/polished_tuff.go | 22 ++++++++++++++++++++++ server/block/register.go | 8 ++++++++ server/block/slab.go | 5 ++++- server/block/slab_type.go | 12 +++++++++++- server/block/stairs.go | 8 ++++++-- server/block/stairs_type.go | 12 +++++++++++- server/block/tuff.go | 9 +++++++++ server/block/tuff_bricks.go | 31 +++++++++++++++++++++++++++++++ server/block/wall_type.go | 12 +++++++++++- 10 files changed, 125 insertions(+), 8 deletions(-) create mode 100644 server/block/polished_tuff.go create mode 100644 server/block/tuff_bricks.go diff --git a/server/block/hash.go b/server/block/hash.go index 05bf16d68..0c1375a22 100644 --- a/server/block/hash.go +++ b/server/block/hash.go @@ -125,6 +125,7 @@ const ( hashPlanks hashPodzol hashPolishedBlackstoneBrick + hashPolishedTuff hashPotato hashPrismarine hashPumpkin @@ -166,6 +167,7 @@ const ( hashTerracotta hashTorch hashTuff + hashTuffBricks hashWall hashWater hashWheatSeeds @@ -667,6 +669,10 @@ func (b PolishedBlackstoneBrick) Hash() (uint64, uint64) { return hashPolishedBlackstoneBrick, uint64(boolByte(b.Cracked)) } +func (PolishedTuff) Hash() (uint64, uint64) { + return hashPolishedTuff, 0 +} + func (p Potato) Hash() (uint64, uint64) { return hashPotato, uint64(p.Growth) } @@ -827,8 +833,12 @@ func (t Torch) Hash() (uint64, uint64) { return hashTorch, uint64(t.Facing) | uint64(t.Type.Uint8())<<3 } -func (Tuff) Hash() (uint64, uint64) { - return hashTuff, 0 +func (t Tuff) Hash() (uint64, uint64) { + return hashTuff, uint64(boolByte(t.Chiseled)) +} + +func (t TuffBricks) Hash() (uint64, uint64) { + return hashTuffBricks, uint64(boolByte(t.Chiseled)) } func (w Wall) Hash() (uint64, uint64) { diff --git a/server/block/polished_tuff.go b/server/block/polished_tuff.go new file mode 100644 index 000000000..cb0d07dad --- /dev/null +++ b/server/block/polished_tuff.go @@ -0,0 +1,22 @@ +package block + +// PolishedTuff is a decorational variant of Tuff that can be crafted or found naturally in Trial Chambers. +type PolishedTuff struct { + solid + bassDrum +} + +// BreakInfo ... +func (t PolishedTuff) BreakInfo() BreakInfo { + return newBreakInfo(1.5, pickaxeHarvestable, pickaxeEffective, oneOf(t)).withBlastResistance(30) +} + +// EncodeItem ... +func (t PolishedTuff) EncodeItem() (name string, meta int16) { + return "minecraft:polished_tuff", 0 +} + +// EncodeBlock ... +func (t PolishedTuff) EncodeBlock() (string, map[string]any) { + return "minecraft:polished_tuff", nil +} diff --git a/server/block/register.go b/server/block/register.go index a0c5f3319..169e72b3e 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -101,6 +101,10 @@ func init() { world.RegisterBlock(TNT{}) world.RegisterBlock(Terracotta{}) world.RegisterBlock(Tuff{}) + world.RegisterBlock(Tuff{Chiseled: true}) + world.RegisterBlock(TuffBricks{}) + world.RegisterBlock(TuffBricks{Chiseled: true}) + world.RegisterBlock(PolishedTuff{}) world.RegisterBlock(ShortGrass{}) world.RegisterBlock(Fern{}) @@ -331,6 +335,10 @@ func init() { world.RegisterItem(TNT{}) world.RegisterItem(Terracotta{}) world.RegisterItem(Tuff{}) + world.RegisterItem(Tuff{Chiseled: true}) + world.RegisterItem(TuffBricks{}) + world.RegisterItem(TuffBricks{Chiseled: true}) + world.RegisterItem(PolishedTuff{}) world.RegisterItem(WheatSeeds{}) world.RegisterItem(DecoratedPot{}) world.RegisterItem(ShortGrass{}) diff --git a/server/block/slab.go b/server/block/slab.go index 25b41a856..0867b6a48 100644 --- a/server/block/slab.go +++ b/server/block/slab.go @@ -109,7 +109,8 @@ func (s Slab) BreakInfo() BreakInfo { switch block := s.Block.(type) { // TODO: Copper - // TODO: Deepslate + case Deepslate, DeepslateBricks, DeepslateTiles: + hardness = 3.5 case EndBricks: hardness = 3.0 blastResistance = 45.0 @@ -135,6 +136,8 @@ func (s Slab) BreakInfo() BreakInfo { harvestable = alwaysHarvestable effective = axeEffective blastResistance = 15.0 + case Tuff, PolishedTuff: + hardness = 1.5 } return newBreakInfo(hardness, harvestable, effective, func(tool item.Tool, enchantments []item.Enchantment) []item.Stack { if s.Double { diff --git a/server/block/slab_type.go b/server/block/slab_type.go index 4f4a87c98..0b179f8d4 100644 --- a/server/block/slab_type.go +++ b/server/block/slab_type.go @@ -67,6 +67,8 @@ func encodeSlabBlock(block world.Block) (id string) { if !block.Cracked { return "polished_blackstone_brick" } + case PolishedTuff: + return "polished_tuff" case Prismarine: switch block.Type { case NormalPrismarine(): @@ -114,7 +116,13 @@ func encodeSlabBlock(block world.Block) (id string) { } return "stone_brick" case Tuff: - return "tuff" + if !block.Chiseled { + return "tuff" + } + case TuffBricks: + if !block.Chiseled { + return "tuff_brick" + } } panic("invalid block used for slab") } @@ -143,6 +151,7 @@ func SlabBlocks() []world.Block { NetherBricks{Type: RedNetherBricks()}, NetherBricks{}, PolishedBlackstoneBrick{}, + PolishedTuff{}, Purpur{}, Quartz{Smooth: true}, Quartz{}, @@ -151,6 +160,7 @@ func SlabBlocks() []world.Block { Stone{Smooth: true}, Stone{}, Tuff{}, + TuffBricks{}, } for _, p := range PrismarineTypes() { b = append(b, Prismarine{Type: p}) diff --git a/server/block/stairs.go b/server/block/stairs.go index 33a3bf695..f7fc5abe6 100644 --- a/server/block/stairs.go +++ b/server/block/stairs.go @@ -51,8 +51,10 @@ func (s Stairs) BreakInfo() BreakInfo { switch block := s.Block.(type) { // TODO: Copper - // TODO: Blackstone - // TODO: Deepslate + case Blackstone: + hardness = 1.5 + case Deepslate, DeepslateBricks, DeepslateTiles: + hardness = 3.5 case Planks: harvestable = alwaysHarvestable effective = axeEffective @@ -75,6 +77,8 @@ func (s Stairs) BreakInfo() BreakInfo { if block.Type == NormalStoneBricks() { hardness = 1.5 } + case Tuff, PolishedTuff: + hardness = 1.5 } return newBreakInfo(hardness, harvestable, effective, oneOf(s)).withBlastResistance(blastResistance) } diff --git a/server/block/stairs_type.go b/server/block/stairs_type.go index b22f87adc..6198cf4f4 100644 --- a/server/block/stairs_type.go +++ b/server/block/stairs_type.go @@ -65,6 +65,8 @@ func encodeStairsBlock(block world.Block) string { if !block.Cracked { return "polished_blackstone_brick" } + case PolishedTuff: + return "polished_tuff" case Prismarine: switch block.Type { case NormalPrismarine(): @@ -106,7 +108,13 @@ func encodeStairsBlock(block world.Block) string { } return "stone_brick" case Tuff: - return "tuff" + if !block.Chiseled { + return "tuff" + } + case TuffBricks: + if !block.Chiseled { + return "tuff_brick" + } } panic("invalid block used for stairs") } @@ -135,6 +143,7 @@ func StairsBlocks() []world.Block { NetherBricks{Type: RedNetherBricks()}, NetherBricks{}, PolishedBlackstoneBrick{}, + PolishedTuff{}, Purpur{}, Quartz{Smooth: true}, Quartz{}, @@ -142,6 +151,7 @@ func StairsBlocks() []world.Block { StoneBricks{}, Stone{}, Tuff{}, + TuffBricks{}, } for _, p := range PrismarineTypes() { b = append(b, Prismarine{Type: p}) diff --git a/server/block/tuff.go b/server/block/tuff.go index 24457951a..5ea668908 100644 --- a/server/block/tuff.go +++ b/server/block/tuff.go @@ -4,6 +4,9 @@ package block type Tuff struct { solid bassDrum + + // Chiseled specifies if the tuff is chiseled. + Chiseled bool } // BreakInfo ... @@ -13,10 +16,16 @@ func (t Tuff) BreakInfo() BreakInfo { // EncodeItem ... func (t Tuff) EncodeItem() (name string, meta int16) { + if t.Chiseled { + return "minecraft:chiseled_tuff", 0 + } return "minecraft:tuff", 0 } // EncodeBlock ... func (t Tuff) EncodeBlock() (string, map[string]any) { + if t.Chiseled { + return "minecraft:chiseled_tuff", nil + } return "minecraft:tuff", nil } diff --git a/server/block/tuff_bricks.go b/server/block/tuff_bricks.go new file mode 100644 index 000000000..a990983cb --- /dev/null +++ b/server/block/tuff_bricks.go @@ -0,0 +1,31 @@ +package block + +// TuffBricks are a decorational variant of Tuff that can be crafted or found naturally in Trial Chambers. +type TuffBricks struct { + solid + bassDrum + + // Chiseled specifies if the tuff bricks are chiseled. + Chiseled bool +} + +// BreakInfo ... +func (t TuffBricks) BreakInfo() BreakInfo { + return newBreakInfo(1.5, pickaxeHarvestable, pickaxeEffective, oneOf(t)).withBlastResistance(30) +} + +// EncodeItem ... +func (t TuffBricks) EncodeItem() (name string, meta int16) { + if t.Chiseled { + return "minecraft:chiseled_tuff_bricks", 0 + } + return "minecraft:tuff_bricks", 0 +} + +// EncodeBlock ... +func (t TuffBricks) EncodeBlock() (string, map[string]any) { + if t.Chiseled { + return "minecraft:chiseled_tuff_bricks", nil + } + return "minecraft:tuff_bricks", nil +} diff --git a/server/block/wall_type.go b/server/block/wall_type.go index 14ea54d5e..16d368a40 100644 --- a/server/block/wall_type.go +++ b/server/block/wall_type.go @@ -58,6 +58,8 @@ func encodeWallBlock(block world.Block) string { if !block.Cracked { return "polished_blackstone_brick" } + case PolishedTuff: + return "polished_tuff" case Prismarine: if block.Type == NormalPrismarine() { return "prismarine" @@ -76,7 +78,13 @@ func encodeWallBlock(block world.Block) string { return "mossy_stone_brick" } case Tuff: - return "tuff" + if !block.Chiseled { + return "tuff" + } + case TuffBricks: + if !block.Chiseled { + return "tuff_brick" + } } panic("invalid block used for wall") } @@ -101,11 +109,13 @@ func WallBlocks() []world.Block { NetherBricks{Type: RedNetherBricks()}, NetherBricks{}, PolishedBlackstoneBrick{}, + PolishedTuff{}, Prismarine{}, Sandstone{Red: true}, Sandstone{}, StoneBricks{Type: MossyStoneBricks()}, StoneBricks{}, Tuff{}, + TuffBricks{}, } } From a6d2f0ea092e81fbd3c2d6e359a577a38a8b5c9f Mon Sep 17 00:00:00 2001 From: TwistedAsylumMC Date: Sat, 16 Nov 2024 22:50:32 +0000 Subject: [PATCH 11/28] server/block: Implement copper & oxidation --- cmd/blockhash/main.go | 3 +- server/block/block.go | 9 ++ server/block/copper.go | 118 +++++++++++++++++ server/block/copper_door.go | 225 ++++++++++++++++++++++++++++++++ server/block/copper_grate.go | 103 +++++++++++++++ server/block/copper_trapdoor.go | 147 +++++++++++++++++++++ server/block/copper_type.go | 59 +++++++++ server/block/hash.go | 20 +++ server/block/oxidation_type.go | 86 ++++++++++++ server/block/oxidizable.go | 89 +++++++++++++ server/block/register.go | 17 +++ server/block/slab.go | 13 +- server/block/slab_type.go | 108 +++++++++------ server/block/stairs.go | 3 +- server/block/stairs_type.go | 16 ++- server/player/player.go | 9 ++ server/session/world.go | 10 ++ server/world/sound/block.go | 6 + 18 files changed, 988 insertions(+), 53 deletions(-) create mode 100644 server/block/copper.go create mode 100644 server/block/copper_door.go create mode 100644 server/block/copper_grate.go create mode 100644 server/block/copper_trapdoor.go create mode 100644 server/block/copper_type.go create mode 100644 server/block/oxidation_type.go create mode 100644 server/block/oxidizable.go 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{} From a2037f7773bb7e3bf43aaf4887626e4cfc12fadc Mon Sep 17 00:00:00 2001 From: TwistedAsylumMC Date: Sat, 16 Nov 2024 23:08:12 +0000 Subject: [PATCH 12/28] block/wood_type.go: Rename Mangrove() and Cherry() to be consistent --- server/block/wood_type.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/block/wood_type.go b/server/block/wood_type.go index dcd562edb..4f2b383a4 100644 --- a/server/block/wood_type.go +++ b/server/block/wood_type.go @@ -46,19 +46,19 @@ func WarpedWood() WoodType { return WoodType{7} } -// Mangrove returns mangrove wood material. -func Mangrove() WoodType { +// MangroveWood returns mangrove wood material. +func MangroveWood() WoodType { return WoodType{8} } -// Cherry returns cherry wood material. -func Cherry() WoodType { +// CherryWood returns cherry wood material. +func CherryWood() WoodType { return WoodType{9} } // WoodTypes returns a list of all wood types func WoodTypes() []WoodType { - return []WoodType{OakWood(), SpruceWood(), BirchWood(), JungleWood(), AcaciaWood(), DarkOakWood(), CrimsonWood(), WarpedWood(), Mangrove(), Cherry()} + return []WoodType{OakWood(), SpruceWood(), BirchWood(), JungleWood(), AcaciaWood(), DarkOakWood(), CrimsonWood(), WarpedWood(), MangroveWood(), CherryWood()} } type wood uint8 From ba837da08ac13844d5a94409e6c82e9c8c51fb1c Mon Sep 17 00:00:00 2001 From: TwistedAsylumMC Date: Sun, 17 Nov 2024 00:04:15 +0000 Subject: [PATCH 13/28] block/oxidizable.go: Fix int division --- server/block/oxidizable.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/block/oxidizable.go b/server/block/oxidizable.go index 6491623f5..2a921f6fa 100644 --- a/server/block/oxidizable.go +++ b/server/block/oxidizable.go @@ -45,7 +45,7 @@ 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 { + } else if r.Float64() > 64.0/1125.0 { return } From 32946c9d117afb8273e97304327a08a68970c754 Mon Sep 17 00:00:00 2001 From: Alexander <123891354+FDUTCH@users.noreply.github.com> Date: Sun, 17 Nov 2024 01:22:35 +0100 Subject: [PATCH 14/28] player/player.go: Implement crawling (#936) --- server/block/cube/bbox.go | 26 +++++++++++++ server/player/player.go | 41 +++++++++++++++++++-- server/player/type.go | 4 +- server/session/controllable.go | 3 ++ server/session/entity_metadata.go | 7 ++++ server/session/handler_player_auth_input.go | 6 +++ 6 files changed, 81 insertions(+), 6 deletions(-) diff --git a/server/block/cube/bbox.go b/server/block/cube/bbox.go index f68e4d55d..9690cd099 100644 --- a/server/block/cube/bbox.go +++ b/server/block/cube/bbox.go @@ -282,3 +282,29 @@ func (box BBox) ZOffset(nearby BBox, deltaZ float64) float64 { } return deltaZ } + +// Corners returns slice of all hitbox corners. +func (box BBox) Corners() []mgl64.Vec3 { + min := box.min + max := box.max + return []mgl64.Vec3{ + box.min, + box.max, + mgl64.Vec3{min[0], min[1], max[2]}, + mgl64.Vec3{min[0], max[1], min[2]}, + mgl64.Vec3{min[0], max[1], max[2]}, + mgl64.Vec3{max[0], max[1], min[2]}, + mgl64.Vec3{max[0], min[1], max[2]}, + mgl64.Vec3{max[0], min[1], min[2]}, + } +} + +// Mul performs a scalar multiplication for min, max points of BBox. +func (box BBox) Mul(val float64) BBox { + return BBox{min: box.min.Mul(val), max: box.max.Mul(val)} +} + +// Volume calculates the volume of the BBox. +func (box BBox) Volume() float64 { + return box.Height() * box.Length() * box.Width() +} diff --git a/server/player/player.go b/server/player/player.go index f751358dd..e10b11123 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -63,7 +63,7 @@ type Player struct { armour *inventory.Armour heldSlot *atomic.Uint32 - sneaking, sprinting, swimming, gliding, flying, + sneaking, sprinting, swimming, gliding, crawling, flying, invisible, immobile, onGround, usingItem atomic.Bool usingSince atomic.Int64 @@ -951,7 +951,7 @@ func (p *Player) Respawn() { // particles show up under the feet. The player will only start sprinting if its food level is high enough. // If the player is sneaking when calling StartSprinting, it is stopped from sneaking. func (p *Player) StartSprinting() { - if !p.hunger.canSprint() && p.GameMode().AllowsTakingDamage() { + if !p.hunger.canSprint() && p.GameMode().AllowsTakingDamage() || p.crawling.Load() { return } ctx := event.C() @@ -1044,6 +1044,35 @@ func (p *Player) StopSwimming() { p.updateState() } +// StartCrawling makes the player start crawling if it is not currently doing so. If the player is sneaking +// while StartCrawling is called, the sneaking is stopped. +func (p *Player) StartCrawling() { + for _, corner := range p.Type().BBox(p).Translate(p.Position()).Corners() { + _, isAir := p.World().Block(cube.PosFromVec3(corner).Add(cube.Pos{0, 1, 0})).(block.Air) + if !isAir { + if !p.crawling.CompareAndSwap(false, true) { + return + } + break + } + } + p.StopSneaking() + p.updateState() +} + +// StopCrawling makes the player stop crawling if it is currently doing so. +func (p *Player) StopCrawling() { + if !p.crawling.CompareAndSwap(true, false) { + return + } + p.updateState() +} + +// Crawling checks if the player is currently crawling. +func (p *Player) Crawling() bool { + return p.crawling.Load() +} + // StartGliding makes the player start gliding if it is not currently doing so. func (p *Player) StartGliding() { if !p.gliding.CompareAndSwap(false, true) { @@ -2624,10 +2653,14 @@ func (p *Player) OnGround() bool { return p.onGround.Load() } -// EyeHeight returns the eye height of the player: 1.62, or 0.52 if the player is swimming. +// EyeHeight returns the eye height of the player: 1.62, 1.26 if player is sneaking or 0.52 if the player is +// swimming, gliding or crawling. func (p *Player) EyeHeight() float64 { - if p.swimming.Load() { + switch { + case p.swimming.Load() || p.crawling.Load() || p.Gliding(): return 0.52 + case p.sneaking.Load(): + return 1.26 } return 1.62 } diff --git a/server/player/type.go b/server/player/type.go index 1b3f294df..b3174384d 100644 --- a/server/player/type.go +++ b/server/player/type.go @@ -14,10 +14,10 @@ func (Type) BBox(e world.Entity) cube.BBox { p := e.(*Player) s := p.Scale() switch { + case p.Gliding(), p.Swimming(), p.Crawling(): + return cube.Box(-0.3*s, 0, -0.3*s, 0.3*s, 0.6*s, 0.3*s) case p.Sneaking(): return cube.Box(-0.3*s, 0, -0.3*s, 0.3*s, 1.5*s, 0.3*s) - case p.Gliding(), p.Swimming(): - return cube.Box(-0.3*s, 0, -0.3*s, 0.3*s, 0.6*s, 0.3*s) default: return cube.Box(-0.3*s, 0, -0.3*s, 0.3*s, 1.8*s, 0.3*s) } diff --git a/server/session/controllable.go b/server/session/controllable.go index bb144aa27..c6919ea1e 100644 --- a/server/session/controllable.go +++ b/server/session/controllable.go @@ -69,6 +69,9 @@ type Controllable interface { StartSwimming() Swimming() bool StopSwimming() + StartCrawling() + Crawling() bool + StopCrawling() StartFlying() Flying() bool StopFlying() diff --git a/server/session/entity_metadata.go b/server/session/entity_metadata.go index 421d8a455..4aee51c59 100644 --- a/server/session/entity_metadata.go +++ b/server/session/entity_metadata.go @@ -50,6 +50,9 @@ func (s *Session) addSpecificMetadata(e any, m protocol.EntityMetadata) { if sw, ok := e.(swimmer); ok && sw.Swimming() { m.SetFlag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagSwimming) } + if cr, ok := e.(crawler); ok && cr.Crawling() { + m.SetFlag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagCrawling) + } if gl, ok := e.(glider); ok && gl.Gliding() { m.SetFlag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagGliding) } @@ -178,6 +181,10 @@ type swimmer interface { Swimming() bool } +type crawler interface { + Crawling() bool +} + type glider interface { Gliding() bool } diff --git a/server/session/handler_player_auth_input.go b/server/session/handler_player_auth_input.go index bb8fa9d4e..454a484c7 100644 --- a/server/session/handler_player_auth_input.go +++ b/server/session/handler_player_auth_input.go @@ -122,6 +122,12 @@ func (h PlayerAuthInputHandler) handleInputFlags(flags uint64, s *Session) { if flags&packet.InputFlagStartJumping != 0 { s.c.Jump() } + if flags&packet.InputFlagStartCrawling != 0 { + s.c.StartCrawling() + } + if flags&packet.InputFlagStopCrawling != 0 { + s.c.StopCrawling() + } if flags&packet.InputFlagMissedSwing != 0 { s.swingingArm.Store(true) defer s.swingingArm.Store(false) From 8daad9923c0fd4218f6fe227d02a6f47ed5e9d41 Mon Sep 17 00:00:00 2001 From: DaPigGuy Date: Sat, 16 Nov 2024 18:38:27 -0800 Subject: [PATCH 15/28] server/block: Fix new copper block break infos --- server/block/copper.go | 4 +++- server/block/copper_door.go | 8 +++----- server/block/copper_grate.go | 4 +++- server/block/copper_trapdoor.go | 4 +++- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/server/block/copper.go b/server/block/copper.go index 09adb70bd..e9be118f7 100644 --- a/server/block/copper.go +++ b/server/block/copper.go @@ -23,7 +23,9 @@ type Copper struct { // BreakInfo ... func (c Copper) BreakInfo() BreakInfo { - return newBreakInfo(3, pickaxeHarvestable, pickaxeEffective, oneOf(c)) + return newBreakInfo(3, func(t item.Tool) bool { + return t.ToolType() == item.TypePickaxe && t.HarvestLevel() >= item.ToolTierStone.HarvestLevel + }, pickaxeEffective, oneOf(c)).withBlastResistance(30) } // Wax waxes the copper block to stop it from oxidising further. diff --git a/server/block/copper_door.go b/server/block/copper_door.go index 1f90421e5..7ca1aabdf 100644 --- a/server/block/copper_door.go +++ b/server/block/copper_door.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" @@ -71,7 +70,6 @@ func (d CopperDoor) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, w *world } 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 @@ -85,7 +83,6 @@ func (d CopperDoor) NeighbourUpdateTick(pos, changedNeighbour cube.Pos, w *world } 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) } } @@ -148,7 +145,6 @@ func (d CopperDoor) SneakingActivate(pos cube.Pos, _ cube.Face, w *world.World, 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 } @@ -161,7 +157,9 @@ func (d CopperDoor) RandomTick(pos cube.Pos, w *world.World, r *rand.Rand) { // BreakInfo ... func (d CopperDoor) BreakInfo() BreakInfo { - return newBreakInfo(3, alwaysHarvestable, axeEffective, oneOf(d)) + return newBreakInfo(3, func(t item.Tool) bool { + return t.ToolType() == item.TypePickaxe && t.HarvestLevel() >= item.ToolTierStone.HarvestLevel + }, pickaxeEffective, oneOf(d)) } // SideClosed ... diff --git a/server/block/copper_grate.go b/server/block/copper_grate.go index 41aec17fc..e19bb26b6 100644 --- a/server/block/copper_grate.go +++ b/server/block/copper_grate.go @@ -22,7 +22,9 @@ type CopperGrate struct { // BreakInfo ... func (c CopperGrate) BreakInfo() BreakInfo { - return newBreakInfo(3, pickaxeHarvestable, pickaxeEffective, oneOf(c)).withBlastResistance(30) + return newBreakInfo(3, func(t item.Tool) bool { + return t.ToolType() == item.TypePickaxe && t.HarvestLevel() >= item.ToolTierStone.HarvestLevel + }, pickaxeEffective, oneOf(c)).withBlastResistance(30) } // Wax waxes the copper grate to stop it from oxidising further. diff --git a/server/block/copper_trapdoor.go b/server/block/copper_trapdoor.go index 38f54e753..91cfc6fd5 100644 --- a/server/block/copper_trapdoor.go +++ b/server/block/copper_trapdoor.go @@ -97,7 +97,9 @@ func (t CopperTrapdoor) RandomTick(pos cube.Pos, w *world.World, r *rand.Rand) { // BreakInfo ... func (t CopperTrapdoor) BreakInfo() BreakInfo { - return newBreakInfo(3, alwaysHarvestable, axeEffective, oneOf(t)).withBlastResistance(15.0) + return newBreakInfo(3, func(t item.Tool) bool { + return t.ToolType() == item.TypePickaxe && t.HarvestLevel() >= item.ToolTierStone.HarvestLevel + }, pickaxeEffective, oneOf(t)) } // SideClosed ... From 1797af8e49e7a4917b70c6665dd1d1670265855b Mon Sep 17 00:00:00 2001 From: DaPigGuy Date: Sun, 17 Nov 2024 02:38:53 +0000 Subject: [PATCH 16/28] updated contributor list --- server/session/enchantment_texts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/session/enchantment_texts.go b/server/session/enchantment_texts.go index 453b4ddfe..6a712e90c 100644 --- a/server/session/enchantment_texts.go +++ b/server/session/enchantment_texts.go @@ -4,4 +4,4 @@ package session // enchantNames are names translated to the 'Standard Galactic Alphabet' client-side. The names generally have no meaning // on the vanilla server implementation, so we can sneak some easter eggs in here without anyone noticing. -var enchantNames = []string{"aabstractt", "abimek", "aericio", "aimjel", "alvin0319", "andreas hgk", "atm85", "blackjack200", "cetfu", "cjmustard1452", "cqdetdev", "da pig guy", "daft0175", "dasciam", "deniel world", "didntpot", "eminarican", "endermanbugzjfc", "erkam246", "ethaniccc", "flonja", "gewinum", "hashim the arab", "hochbaum", "hyper flare mc", "im da real ani", "ipad54", "its zodia x", "ivan craft623", "javier leon9966", "just tal develops", "liatoast", "mmm545", "mohamed587100", "myma qc", "natuyasai natuo", "neutronic mc", "nonono697", "provsalt", "restart fu", "riccskn", "robertdudaa", "royal mcpe", "sallypemdas", "sandertv", "sculas", "sqmatheus", "ssaini123456", "t14 raptor", "tadhunt", "theaddonn", "thunder33345", "tristanmorgan", "twisted asylum mc", "unickorn", "unknown ore", "uramnoil", "wqrro", "x natsuri", "x4caa", "xd-pro"} +var enchantNames = []string{"aabstractt", "abimek", "aericio", "aimjel", "alvin0319", "andreas hgk", "atm85", "blackjack200", "cetfu", "cjmustard1452", "cqdetdev", "da pig guy", "daft0175", "dasciam", "deniel world", "didntpot", "eminarican", "endermanbugzjfc", "erkam246", "ethaniccc", "fdutch", "flonja", "gewinum", "hashim the arab", "hochbaum", "hyper flare mc", "im da real ani", "ipad54", "its zodia x", "ivan craft623", "javier leon9966", "just tal develops", "liatoast", "mmm545", "mohamed587100", "myma qc", "natuyasai natuo", "neutronic mc", "nonono697", "provsalt", "restart fu", "riccskn", "robertdudaa", "royal mcpe", "sallypemdas", "sandertv", "sculas", "sqmatheus", "ssaini123456", "t14 raptor", "tadhunt", "theaddonn", "thunder33345", "tristanmorgan", "twisted asylum mc", "unickorn", "unknown ore", "uramnoil", "wqrro", "x natsuri", "x4caa", "xd-pro"} From 9c02199865a431207c3cfc376c44ebb959cdcebd Mon Sep 17 00:00:00 2001 From: DaPigGuy Date: Sat, 16 Nov 2024 18:39:23 -0800 Subject: [PATCH 17/28] item/enchanted_apple.go: Update Regeneration effect to match vanilla --- server/item/enchanted_apple.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/item/enchanted_apple.go b/server/item/enchanted_apple.go index 562d53528..b1c7f975e 100644 --- a/server/item/enchanted_apple.go +++ b/server/item/enchanted_apple.go @@ -23,7 +23,7 @@ func (EnchantedApple) ConsumeDuration() time.Duration { func (EnchantedApple) Consume(_ *world.World, c Consumer) Stack { c.Saturate(4, 9.6) c.AddEffect(effect.New(effect.Absorption{}, 4, 2*time.Minute)) - c.AddEffect(effect.New(effect.Regeneration{}, 5, 30*time.Second)) + c.AddEffect(effect.New(effect.Regeneration{}, 2, 30*time.Second)) c.AddEffect(effect.New(effect.FireResistance{}, 1, 5*time.Minute)) c.AddEffect(effect.New(effect.Resistance{}, 1, 5*time.Minute)) return Stack{} From cc8575cb4a7ee0ea74367065822d48cabe617a40 Mon Sep 17 00:00:00 2001 From: DaPigGuy Date: Sat, 16 Nov 2024 19:04:43 -0800 Subject: [PATCH 18/28] block/stairs.go: Match stair break info to its type's break info --- server/block/stairs.go | 37 ++----------------------------------- 1 file changed, 2 insertions(+), 35 deletions(-) diff --git a/server/block/stairs.go b/server/block/stairs.go index e5f7c0437..45cd3938f 100644 --- a/server/block/stairs.go +++ b/server/block/stairs.go @@ -47,41 +47,8 @@ func (s Stairs) Model() world.BlockModel { // BreakInfo ... func (s Stairs) BreakInfo() BreakInfo { - hardness, blastResistance, harvestable, effective := 2.0, 30.0, pickaxeHarvestable, pickaxeEffective - - switch block := s.Block.(type) { - case Blackstone: - hardness = 1.5 - case Copper: - hardness = 3.0 - case Deepslate, DeepslateBricks, DeepslateTiles: - hardness = 3.5 - case Planks: - harvestable = alwaysHarvestable - effective = axeEffective - blastResistance = 15.0 - case Prismarine: - hardness = 1.5 - case Purpur: - hardness = 1.5 - case Quartz: - hardness = 0.8 - blastResistance = 4 - case Sandstone: - if block.Type != SmoothSandstone() { - hardness = 0.8 - blastResistance = 4 - } - case Stone: - hardness = 1.5 - case StoneBricks: - if block.Type == NormalStoneBricks() { - hardness = 1.5 - } - case Tuff, PolishedTuff: - hardness = 1.5 - } - return newBreakInfo(hardness, harvestable, effective, oneOf(s)).withBlastResistance(blastResistance) + breakInfo := s.Block.(Breakable).BreakInfo() + return newBreakInfo(breakInfo.Hardness, breakInfo.Harvestable, breakInfo.Effective, oneOf(s)).withBlastResistance(breakInfo.BlastResistance) } // Instrument ... From 2b55bfefb24755d71094b2072db971781e953196 Mon Sep 17 00:00:00 2001 From: DaPigGuy Date: Sat, 16 Nov 2024 19:04:55 -0800 Subject: [PATCH 19/28] block/slab.go: Match slab break info to its type's break info --- server/block/slab.go | 32 +++++--------------------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/server/block/slab.go b/server/block/slab.go index f7e1583b4..942d7ab0f 100644 --- a/server/block/slab.go +++ b/server/block/slab.go @@ -108,37 +108,15 @@ func (s Slab) BreakInfo() BreakInfo { hardness, blastResistance, harvestable, effective := 2.0, 30.0, pickaxeHarvestable, pickaxeEffective switch block := s.Block.(type) { - case Copper: - hardness = 3.0 - case Deepslate, DeepslateBricks, DeepslateTiles: - hardness = 3.5 - case EndBricks: - hardness = 3.0 - blastResistance = 45.0 + case Stone, Sandstone, Quartz, Purpur: + //These slab types do not match their block's hardness or blast resistance case StoneBricks: if block.Type == MossyStoneBricks() { hardness = 1.5 } - case Andesite: - if block.Polished { - hardness = 1.5 - } - case Diorite: - if block.Polished { - hardness = 1.5 - } - case Granite: - if block.Polished { - hardness = 1.5 - } - case Prismarine: - hardness = 1.5 - case Planks: - harvestable = alwaysHarvestable - effective = axeEffective - blastResistance = 15.0 - case Tuff, PolishedTuff: - hardness = 1.5 + case Breakable: + breakInfo := s.BreakInfo() + hardness, blastResistance, harvestable, effective = breakInfo.Hardness, breakInfo.BlastResistance, breakInfo.Harvestable, breakInfo.Effective } return newBreakInfo(hardness, harvestable, effective, func(tool item.Tool, enchantments []item.Enchantment) []item.Stack { if s.Double { From 44f13f278b36fcccc645fb741e72a18008a53d0a Mon Sep 17 00:00:00 2001 From: DaPigGuy Date: Sat, 16 Nov 2024 19:17:10 -0800 Subject: [PATCH 20/28] block/copper_grate.go: Make waterloggable --- server/block/copper_grate.go | 1 + 1 file changed, 1 insertion(+) diff --git a/server/block/copper_grate.go b/server/block/copper_grate.go index e19bb26b6..62dd9581d 100644 --- a/server/block/copper_grate.go +++ b/server/block/copper_grate.go @@ -10,6 +10,7 @@ import ( // CopperGrate is a solid block commonly found in deserts and beaches underneath sand. type CopperGrate struct { + sourceWaterDisplacer solid transparent bassDrum From 9dde24c7f7b0cc564777c94ec1a703b355623ffe Mon Sep 17 00:00:00 2001 From: Jon <86489758+xNatsuri@users.noreply.github.com> Date: Sat, 16 Nov 2024 22:46:58 -0800 Subject: [PATCH 21/28] server/block: Implement brewing stand (#916) Co-authored-by: TwistedAsylumMC Co-authored-by: DaPigGuy --- server/block/brewer.go | 253 ++++++++++++++++++++++++++++ server/block/brewing_stand.go | 148 ++++++++++++++++ server/block/hash.go | 5 + server/block/model/brewing_stand.go | 22 +++ server/block/register.go | 2 + server/conf.go | 7 + server/item/creative/creative.go | 7 +- server/item/recipe/potion_data.nbt | Bin 0 -> 33442 bytes server/item/recipe/recipe.go | 34 +++- server/item/recipe/register.go | 101 ++++++++++- server/item/recipe/vanilla.go | 68 +++++++- server/session/player.go | 35 +++- server/session/world.go | 31 ++++ server/world/sound/block.go | 3 + server/world/viewer.go | 45 ++--- 15 files changed, 730 insertions(+), 31 deletions(-) create mode 100644 server/block/brewer.go create mode 100644 server/block/brewing_stand.go create mode 100644 server/block/model/brewing_stand.go create mode 100644 server/item/recipe/potion_data.nbt diff --git a/server/block/brewer.go b/server/block/brewer.go new file mode 100644 index 000000000..90211ed8b --- /dev/null +++ b/server/block/brewer.go @@ -0,0 +1,253 @@ +package block + +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/item/inventory" + "github.com/df-mc/dragonfly/server/item/recipe" + "github.com/df-mc/dragonfly/server/world" + "github.com/df-mc/dragonfly/server/world/sound" + "sync" + "time" +) + +// brewer is a struct that may be embedded by blocks that can brew potions, such as brewing stands. +type brewer struct { + mu sync.Mutex + + viewers map[ContainerViewer]struct{} + inventory *inventory.Inventory + + duration time.Duration + fuelAmount int32 + fuelTotal int32 +} + +// newBrewer creates a new initialised brewer. The inventory is properly initialised. +func newBrewer() *brewer { + b := &brewer{viewers: make(map[ContainerViewer]struct{})} + b.inventory = inventory.New(5, func(slot int, _, item item.Stack) { + b.mu.Lock() + defer b.mu.Unlock() + for viewer := range b.viewers { + viewer.ViewSlotChange(slot, item) + } + }) + return b +} + +// InsertItem ... +func (b *brewer) InsertItem(h Hopper, pos cube.Pos, w *world.World) bool { + for sourceSlot, sourceStack := range h.inventory.Slots() { + var slot int + + if sourceStack.Empty() { + continue + } + + if h.Facing == cube.FaceDown { + if !recipe.ValidBrewingReagent(sourceStack.Item()) { + // This item is not a valid brewing reagent. + continue + } + slot = 0 + } else if _, ok := sourceStack.Item().(item.BlazePowder); ok { + slot = 4 + } else { + _, okPotion := sourceStack.Item().(item.Potion) + _, okSplash := sourceStack.Item().(item.SplashPotion) + _, okLingering := sourceStack.Item().(item.LingeringPotion) + _, okBottle := sourceStack.Item().(item.GlassBottle) + if !okPotion && !okSplash && !okLingering && !okBottle { + continue + } + for brewingSlot, brewingStack := range b.inventory.Slots() { + if brewingSlot == 0 || brewingSlot == 4 { + continue + } + if brewingStack.Count() == brewingStack.MaxCount() || !brewingStack.Comparable(sourceStack) { + continue + } + + slot = brewingSlot + break + } + // Could not find an empty slot + if slot == 0 { + continue + } + } + + stack := sourceStack.Grow(-sourceStack.Count() + 1) + it, _ := b.Inventory(w, pos).Item(slot) + + if !sourceStack.Comparable(it) { + // The items are not the same. + continue + } + if it.Count() == it.MaxCount() { + // The item has the maximum count that the stack is able to hold. + continue + } + if !it.Empty() { + stack = it.Grow(1) + } + + _ = b.Inventory(w, pos).SetItem(slot, stack) + _ = h.inventory.SetItem(sourceSlot, sourceStack.Grow(-1)) + return true + + } + return false +} + +// ExtractItem ... +func (b *brewer) ExtractItem(h Hopper, pos cube.Pos, w *world.World) bool { + for sourceSlot, sourceStack := range b.inventory.Slots() { + if sourceStack.Empty() { + continue + } + + if sourceSlot == 0 || sourceSlot == 4 { + continue + } + + _, err := h.inventory.AddItem(sourceStack.Grow(-sourceStack.Count() + 1)) + if err != nil { + // The hopper is full. + continue + } + + _ = b.Inventory(w, pos).SetItem(sourceSlot, sourceStack.Grow(-1)) + return true + } + return false +} + +// Duration returns the remaining duration of the brewing process. +func (b *brewer) Duration() time.Duration { + b.mu.Lock() + defer b.mu.Unlock() + return b.duration +} + +// Fuel returns the fuel and maximum fuel of the brewer. +func (b *brewer) Fuel() (fuel, maxFuel int32) { + b.mu.Lock() + defer b.mu.Unlock() + return b.fuelAmount, b.fuelTotal +} + +// Inventory returns the inventory of the brewer. +func (b *brewer) Inventory(*world.World, cube.Pos) *inventory.Inventory { + return b.inventory +} + +// AddViewer adds a viewer to the brewer, so that it is updated whenever the inventory of the brewer is changed. +func (b *brewer) AddViewer(v ContainerViewer, _ *world.World, _ cube.Pos) { + b.mu.Lock() + defer b.mu.Unlock() + b.viewers[v] = struct{}{} +} + +// RemoveViewer removes a viewer from the brewer, so that slot updates in the inventory are no longer sent to +// it. +func (b *brewer) RemoveViewer(v ContainerViewer, _ *world.World, _ cube.Pos) { + b.mu.Lock() + defer b.mu.Unlock() + delete(b.viewers, v) +} + +// setDuration sets the brew duration of the brewer to the given duration. +func (b *brewer) setDuration(duration time.Duration) { + b.mu.Lock() + defer b.mu.Unlock() + b.duration = duration +} + +// setFuel sets the fuel of the brewer to the given fuel and maximum fuel. +func (b *brewer) setFuel(fuel, maxFuel int32) { + b.mu.Lock() + defer b.mu.Unlock() + b.fuelAmount, b.fuelTotal = fuel, maxFuel +} + +// tickBrewing ticks the brewer, ensuring the necessary items exist in the brewer, and then processing all inputted +// items for the necessary duration. +func (b *brewer) tickBrewing(block string, pos cube.Pos, w *world.World) { + b.mu.Lock() + + // Get each item in the brewer. We don't need to validate errors here since we know the bounds of the brewer. + left, _ := b.inventory.Item(1) + middle, _ := b.inventory.Item(2) + right, _ := b.inventory.Item(3) + + // Keep track of our past durations, since if any of them change, we need to be able to tell they did and then + // update the viewers on the change. + prevDuration := b.duration + prevFuelAmount := b.fuelAmount + prevFuelTotal := b.fuelTotal + + // If we need fuel, try and burn some. + fuel, _ := b.inventory.Item(4) + + if _, ok := fuel.Item().(item.BlazePowder); ok && b.fuelAmount <= 0 { + defer b.inventory.SetItem(4, fuel.Grow(-1)) + b.fuelAmount, b.fuelTotal = 20, 20 + } + + // Now get the ingredient item. + ingredient, _ := b.inventory.Item(0) + + // Check each input and see if it is affected by the ingredient. + leftOutput, leftAffected := recipe.Perform(block, left.Item(), ingredient.Item()) + middleOutput, middleAffected := recipe.Perform(block, middle.Item(), ingredient.Item()) + rightOutput, rightAffected := recipe.Perform(block, right.Item(), ingredient.Item()) + + // Ensure that we have enough fuel to continue. + if b.fuelAmount > 0 { + // Now make sure that we have at least one potion that is affected by the ingredient. + if leftAffected || middleAffected || rightAffected { + // Tick our duration. If we have no brew duration, set it to the default of twenty seconds. + if b.duration == 0 { + b.duration = time.Second * 20 + } + b.duration -= time.Millisecond * 50 + + // If we have no duration, we are done. + if b.duration <= 0 { + // Create the output items. + if leftAffected { + defer b.inventory.SetItem(1, leftOutput[0]) + } + if middleAffected { + defer b.inventory.SetItem(2, middleOutput[0]) + } + if rightAffected { + defer b.inventory.SetItem(3, rightOutput[0]) + } + + // Reduce the ingredient by one. + defer b.inventory.SetItem(0, ingredient.Grow(-1)) + w.PlaySound(pos.Vec3Centre(), sound.PotionBrewed{}) + + // Decrement the fuel, and reset the duration. + b.fuelAmount-- + b.duration = 0 + } + } else { + // None of the potions are affected by the ingredient, so reset the duration. + b.duration = 0 + } + } else { + // We don't have enough fuel, so reset our progress. + b.duration, b.fuelAmount, b.fuelTotal = 0, 0, 0 + } + + // Update the viewers on the new durations. + for v := range b.viewers { + v.ViewBrewingUpdate(prevDuration, b.duration, prevFuelAmount, b.fuelAmount, prevFuelTotal, b.fuelTotal) + } + + b.mu.Unlock() +} diff --git a/server/block/brewing_stand.go b/server/block/brewing_stand.go new file mode 100644 index 000000000..d729e58a6 --- /dev/null +++ b/server/block/brewing_stand.go @@ -0,0 +1,148 @@ +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" + "time" +) + +// BrewingStand is a block used for brewing potions, splash potions, and lingering potions. It also serves as a cleric's +// job site block generated in village churches. +type BrewingStand struct { + sourceWaterDisplacer + transparent + *brewer + + // LeftSlot is true if the left slot is filled. + LeftSlot bool + // MiddleSlot is true if the middle slot is filled. + MiddleSlot bool + // RightSlot is true if the right slot is filled. + RightSlot bool +} + +// NewBrewingStand creates a new initialised brewing stand. The inventory is properly initialised. +func NewBrewingStand() BrewingStand { + return BrewingStand{brewer: newBrewer()} +} + +// Model ... +func (b BrewingStand) Model() world.BlockModel { + return model.BrewingStand{} +} + +// SideClosed ... +func (b BrewingStand) SideClosed(cube.Pos, cube.Pos, *world.World) bool { + return false +} + +// Tick is called to check if the brewing stand should update and start or stop brewing. +func (b BrewingStand) Tick(_ int64, pos cube.Pos, w *world.World) { + // Get each item in the brewing stand. We don't need to validate errors here since we know the bounds of the stand. + left, _ := b.inventory.Item(1) + middle, _ := b.inventory.Item(2) + right, _ := b.inventory.Item(3) + + // If any of the slots in the inventory got updated, update the appearance of the brewing stand. + displayLeft, displayMiddle, displayRight := b.LeftSlot, b.MiddleSlot, b.RightSlot + b.LeftSlot, b.MiddleSlot, b.RightSlot = !left.Empty(), !middle.Empty(), !right.Empty() + if b.LeftSlot != displayLeft || b.MiddleSlot != displayMiddle || b.RightSlot != displayRight { + w.SetBlock(pos, b, nil) + } + + // Tick brewing. + b.tickBrewing("brewing_stand", pos, w) +} + +// Activate ... +func (b BrewingStand) Activate(pos cube.Pos, _ cube.Face, _ *world.World, u item.User, _ *item.UseContext) bool { + if opener, ok := u.(ContainerOpener); ok { + opener.OpenBlockContainer(pos) + return true + } + return false +} + +// UseOnBlock ... +func (b BrewingStand) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(w, pos, face, b) + if !used { + return + } + + //noinspection GoAssignmentToReceiver + b = NewBrewingStand() + place(w, pos, b, user, ctx) + return placed(ctx) +} + +// EncodeNBT ... +func (b BrewingStand) EncodeNBT() map[string]any { + if b.brewer == nil { + //noinspection GoAssignmentToReceiver + b = NewBrewingStand() + } + duration := b.Duration() + fuel, totalFuel := b.Fuel() + return map[string]any{ + "id": "BrewingStand", + "Items": nbtconv.InvToNBT(b.inventory), + "CookTime": int16(duration.Milliseconds() / 50), + "FuelTotal": int16(totalFuel), + "FuelAmount": int16(fuel), + } +} + +// DecodeNBT ... +func (b BrewingStand) DecodeNBT(data map[string]any) any { + brew := time.Duration(nbtconv.Int16(data, "CookTime")) * time.Millisecond * 50 + + fuel := int32(nbtconv.Int16(data, "FuelAmount")) + maxFuel := int32(nbtconv.Int16(data, "FuelTotal")) + + //noinspection GoAssignmentToReceiver + b = NewBrewingStand() + b.setDuration(brew) + b.setFuel(fuel, maxFuel) + nbtconv.InvFromNBT(b.inventory, nbtconv.Slice(data, "Items")) + return b +} + +// BreakInfo ... +func (b BrewingStand) BreakInfo() BreakInfo { + return newBreakInfo(0.5, alwaysHarvestable, pickaxeEffective, oneOf(b)).withBreakHandler(func(pos cube.Pos, w *world.World, u item.User) { + for _, i := range b.Inventory(w, pos).Clear() { + dropItem(w, i, pos.Vec3Centre()) + } + }) +} + +// EncodeBlock ... +func (b BrewingStand) EncodeBlock() (string, map[string]any) { + return "minecraft:brewing_stand", map[string]any{ + "brewing_stand_slot_a_bit": b.LeftSlot, + "brewing_stand_slot_b_bit": b.MiddleSlot, + "brewing_stand_slot_c_bit": b.RightSlot, + } +} + +// EncodeItem ... +func (b BrewingStand) EncodeItem() (name string, meta int16) { + return "minecraft:brewing_stand", 0 +} + +// allBrewingStands ... +func allBrewingStands() (stands []world.Block) { + for _, left := range []bool{false, true} { + for _, middle := range []bool{false, true} { + for _, right := range []bool{false, true} { + stands = append(stands, BrewingStand{LeftSlot: left, MiddleSlot: middle, RightSlot: right}) + } + } + } + return +} diff --git a/server/block/hash.go b/server/block/hash.go index 197099232..098786c88 100644 --- a/server/block/hash.go +++ b/server/block/hash.go @@ -22,6 +22,7 @@ const ( hashBlueIce hashBone hashBookshelf + hashBrewingStand hashBricks hashCactus hashCake @@ -261,6 +262,10 @@ func (Bookshelf) Hash() (uint64, uint64) { return hashBookshelf, 0 } +func (b BrewingStand) Hash() (uint64, uint64) { + return hashBrewingStand, uint64(boolByte(b.LeftSlot)) | uint64(boolByte(b.MiddleSlot))<<1 | uint64(boolByte(b.RightSlot))<<2 +} + func (Bricks) Hash() (uint64, uint64) { return hashBricks, 0 } diff --git a/server/block/model/brewing_stand.go b/server/block/model/brewing_stand.go new file mode 100644 index 000000000..f32f83eb0 --- /dev/null +++ b/server/block/model/brewing_stand.go @@ -0,0 +1,22 @@ +package model + +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/world" +) + +// BrewingStand is a model used by brewing stands. +type BrewingStand struct{} + +// BBox ... +func (b BrewingStand) BBox(cube.Pos, world.BlockSource) []cube.BBox { + return []cube.BBox{ + full.ExtendTowards(cube.FaceDown, 0.875), + full.Stretch(cube.X, -0.4375).Stretch(cube.Z, -0.4375).ExtendTowards(cube.FaceDown, 0.125), + } +} + +// FaceSolid ... +func (b BrewingStand) FaceSolid(cube.Pos, cube.Face, world.BlockSource) bool { + return false +} diff --git a/server/block/register.go b/server/block/register.go index 81b126cfa..1ce675a6c 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -126,6 +126,7 @@ func init() { registerAll(allBlackstone()) registerAll(allBlastFurnaces()) registerAll(allBoneBlock()) + registerAll(allBrewingStands()) registerAll(allCactus()) registerAll(allCake()) registerAll(allCampfires()) @@ -221,6 +222,7 @@ func init() { world.RegisterItem(BlueIce{}) world.RegisterItem(Bone{}) world.RegisterItem(Bookshelf{}) + world.RegisterItem(BrewingStand{}) world.RegisterItem(Bricks{}) world.RegisterItem(Cactus{}) world.RegisterItem(Cake{}) diff --git a/server/conf.go b/server/conf.go index c9385b8e3..2f93c2786 100644 --- a/server/conf.go +++ b/server/conf.go @@ -144,6 +144,8 @@ func (conf Config) New() *Server { world: &world.World{}, nether: &world.World{}, end: &world.World{}, } world_finaliseBlockRegistry() + recipe_registerVanilla() + srv.world = srv.createWorld(world.Overworld, &srv.nether, &srv.end) srv.nether = srv.createWorld(world.Nether, &srv.world, &srv.end) srv.end = srv.createWorld(world.End, &srv.nether, &srv.world) @@ -314,6 +316,11 @@ func DefaultConfig() UserConfig { return c } +// noinspection ALL +// +//go:linkname recipe_registerVanilla github.com/df-mc/dragonfly/server/item/recipe.registerVanilla +func recipe_registerVanilla() + // noinspection ALL // //go:linkname world_finaliseBlockRegistry github.com/df-mc/dragonfly/server/world.finaliseBlockRegistry diff --git a/server/item/creative/creative.go b/server/item/creative/creative.go index e2e62ba60..8beddd887 100644 --- a/server/item/creative/creative.go +++ b/server/item/creative/creative.go @@ -3,9 +3,10 @@ package creative import ( _ "embed" "github.com/df-mc/dragonfly/server/internal/nbtconv" - // The following three imports are essential for this package: They make sure this package is loaded after - // all these imports. This ensures that all items are registered before the creative items are registered - // in the init function in this package. + // The following four imports are essential for this package: They make sure this package is loaded after + // all these imports. This ensures that all blocks and items are registered before the creative items are + // registered in the init function in this package. + _ "github.com/df-mc/dragonfly/server/block" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/sandertv/gophertunnel/minecraft/nbt" diff --git a/server/item/recipe/potion_data.nbt b/server/item/recipe/potion_data.nbt new file mode 100644 index 0000000000000000000000000000000000000000..42d7951ae83a0e9badf8e8d01b0ed268b8e3463a GIT binary patch literal 33442 zcmd6wZ;KOG6o=FO=Nhb4YpsZgNRc8UA|fJ1M5HgI6e*>YQV5gmY&JB>gk;jC{S5sW zebJB8kJK^iZe}O*oO933$<#N#adOXdf6x6hGs(`hTNf`(vV4$@XYIeY+q;ADWS;Nu zjFVA%Wi%M4y=iiozc@K!duNp9$^Q0kFPo3^tycTOG)?;H_`~tnm&bRfY5Hf{nPvye zgZHv|uC@Ks)=Ar+v|6omEMG3$o=t|y?5N|8B$q(4TT8OxVBAlq#XtUVY8B+OTKsI; zpV@FQN>2s0ce}%^_uI+Ps2fVl@5r?>|=yhau8`ah6_%bL@dU zMzABQJ)Bb;?28J8-C1Wy<=I#tQtuC1Ye@aN!68-CgL>B0sq+4#)kz@_DeQ=9FK9^B z^rfDyY|%xk??U-O3EFhdt@o`43h*02n_z52y1G75X~P`W{v3<)MU~ zP_5xm68mv(D-b(X?S~44U0LAFJj9=xg@8Q2M(4dzC?E zMrCp6O5X2@&w5u49#P~TNWPlyghV6vW7Qnci`$&Ivz@b3jeoVi%!|63#YPO7Uh>pz z2P6`?4`2+>k8Ui_QoF;XxFIshjt|o5=>`yXdHm9Rp0VhJ6O?Ql+0=}k@YFg;XmxpK z>GCk0j*7cp=|N{U85BM{=^tm;v|&!)I6IAlliebtnmM|S!&K`bsnv1m4rc!-ndO~4 zO^mbkxwBadlI#=7)O^vW1x~Gkd{&F482Z`pARTvl$#k0Kwq>{NEYe6-I=tqFH^QkY zqsvmkkkr&dh-lUM#d6^tCH+y-=@s8PjBGM*I?FUMm6EMdiD+uVsM18ImO((P$Wo=r zQ8Lc6QD>Bny3=HNM>uTveCKS_4kjx_zEUCMcSWU724+|KM13u!w0bS=0+ab+adLDx zm>t>X=Y40R79`mvlBxOP^t8aKRglkWv9xGRlWupAcMdb_o_Wn#qlroOh-hlU=+Q){ zmO((P$h*t^vOgTm@{@0*N9iydcZzRxd&WzZ2hL7o@npS7spgJ)V?ou5$Z9oRT1a9q zM4vd@wIInxkxb1OjauN;D#&NGSpIro-nU+F-gXukNF%#LC^c1d7s#a+Ks2kiQUdvW znh(>?Q93MsA7wl7dF!mtNL4z#=F$Y=)Ra+UsbENIY9T}foi~?y&i{Dr+H%*NrU$d~ zmqcL~CAZ>CIslb2t&xaaYPJq&Y7L|Ve~-xD&85HoFgsod>>SKz#`*X-&Y~NZ>=NnJ zoZSG`O2`OCACS?wo5fJ&QJhz2IN2^Ts+l`us`Zc*(nmh0S^Lo0t`$r6igaquZn>$I zkP(btrdhl8K6)k2r2~*`5xLZC9njPoNC*C|kw4c}_gv&(QG*&=eO**avU)<8P&cd3z87xw}= zYG*jP7&5AvJ7cQ#kQ5x>X~gkYar4y~PIiopYUa+EYCR+c$Jak*$Ift@a_o%h?s%sW z$KS@yV`n(oF*2%|J7cQ#kQC5#4(OMId*3_ zxfn94nLA^u^^g=CZ#Ci=e`YMl?hGdvLq;`oXH2yol7i!QBaSb}?Z?h=vSVaaGk3;R z>mjMtaro^7X!(+}*Ki=&C330RqRVhJwFc5b%1;`Zqj&}&N9_zJ7ehuhb7xGo9+Fxe zTknoS`_JNLD#m8TlD#6GnzI|AS_v5;aU=_0NucGY&Mw1&WQ)k9X6ulZS_A1I<>$2O zT8BQg|4p1lH!RsD(y2MS0jiad5fVqT@Cg7dzi@UL4kTMdE;U<+tkfDv2Pxm%@QnO1 z&Y~NZ>=NnJoZSG`O2}w68a@G_W&G|z4jH2g2a*dQmzu2unpy+tAmw*7Bg5l@mVbzw ziw;1tKjc!gbwE>VARVOqlKi>;UJ36>$x%DQ$;FUS&Dank`xkM^kGc9r%0L$g2A|&ZRS)>=hZ+%$+gS zdPoY6pMA`Zo#8g+*csE^@#{t$<9;v4?hGdvLq;`oXH2yolEOT`-H2oS4Z0k=Gn`xu z8P&|4G1YoVYISTq6@~Wi#_hxyn-xp;igaquZh&efWQ4?#Ec~nwTE64#G8{;@h+Jy6 z4q2%+kPcGbYh=~My+DrI8BQ*SjB4i2m})&F1;=m6v2}Ta_P>vtscu-ZSEN&Ob^}x^ zAtNM?WZ{b(wEUg3%WxpsB66wOI%K8RK)ThG;Ze~pPg>qG%X6WT%^{SOstcD?AnG~U z_h}&wALXk9VnOn?+1%n|4nVRzTl6M+>2 zJR7A^61FB-5^SnA1}^JFkCE!8GX?@1N)vuWrhZ&W6@$J)!rl)_-fc B1fc)` literal 0 HcmV?d00001 diff --git a/server/item/recipe/recipe.go b/server/item/recipe/recipe.go index 7dce90a04..3332b1a96 100644 --- a/server/item/recipe/recipe.go +++ b/server/item/recipe/recipe.go @@ -1,6 +1,9 @@ package recipe -import "github.com/df-mc/dragonfly/server/item" +import ( + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/world" +) // Recipe is implemented by all recipe types. type Recipe interface { @@ -73,6 +76,35 @@ func NewFurnace(input Item, output item.Stack, block string) Furnace { }} } +// PotionContainerChange is a recipe to convert a potion from one type to another, such as from a drinkable potion to a +// splash potion, or from a splash potion to a lingering potion. +type PotionContainerChange struct { + recipe +} + +// NewPotionContainerChange creates a new potion container change recipe and returns it. +func NewPotionContainerChange(input, output world.Item, reagent item.Stack) PotionContainerChange { + return PotionContainerChange{recipe: recipe{ + input: []Item{item.NewStack(input, 1), reagent}, + output: []item.Stack{item.NewStack(output, 1)}, + block: "brewing_stand", + }} +} + +// Potion is a potion mixing recipe which may be used in the brewing stand. +type Potion struct { + recipe +} + +// NewPotion creates a new potion recipe and returns it. +func NewPotion(input, reagent Item, output item.Stack) Potion { + return Potion{recipe: recipe{ + input: []Item{input, reagent}, + output: []item.Stack{output}, + block: "brewing_stand", + }} +} + // Shaped is a recipe that has a specific shape that must be used to craft the output of the recipe. type Shaped struct { recipe diff --git a/server/item/recipe/register.go b/server/item/recipe/register.go index 7fa66069a..3cebd850a 100644 --- a/server/item/recipe/register.go +++ b/server/item/recipe/register.go @@ -1,11 +1,23 @@ package recipe import ( + "github.com/df-mc/dragonfly/server/internal/sliceutil" + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/world" "slices" + "sort" + "strings" + "unsafe" ) // recipes is a list of each recipe. -var recipes []Recipe +var ( + recipes []Recipe + // index maps an input hash to output stacks for each PotionContainerChange and Potion recipe. + index = make(map[string]map[string]Recipe) + // reagent maps the item name and an item.Stack. + reagent = make(map[string]item.Stack) +) // Recipes returns each recipe in a slice. func Recipes() []Recipe { @@ -15,4 +27,91 @@ func Recipes() []Recipe { // Register registers a new recipe. func Register(recipe Recipe) { recipes = append(recipes, recipe) + + _, ok := recipe.(PotionContainerChange) + p, okTwo := recipe.(Potion) + + if okTwo { + stack := p.Input()[1].(item.Stack) + name, _ := stack.Item().EncodeItem() + reagent[name] = stack + } + + if ok || okTwo { + input := make([]world.Item, len(recipe.Input())) + for i, stack := range recipe.Input() { + if s, ok := stack.(item.Stack); ok { + input[i] = s.Item() + } + } + hash := hashItems(input, !ok) + + block := recipe.Block() + if index[block] == nil { + index[block] = make(map[string]Recipe) + } + index[block][hash] = recipe + } +} + +// Perform performs the recipe with the given block and inputs and returns the outputs. If the inputs do not map to +// any outputs, false is returned for the second return value. +func Perform(block string, input ...world.Item) (output []item.Stack, ok bool) { + blockInd, ok := index[block] + if !ok { + // Block specific index didn't exist. + return nil, false + } + r, ok := blockInd[hashItems(input, true)] + if !ok { + r, ok = blockInd[hashItems(input, false)] + if !ok { + return nil, false + } + } + _, containerChange := r.(PotionContainerChange) + for ind, it := range r.Output() { + if containerChange { + name, _ := it.Item().EncodeItem() + _, meta := input[ind].EncodeItem() + if i, ok := world.ItemByName(name, meta); ok { + it = item.NewStack(i, it.Count()) + } + } + output = append(output, it) + } + return output, ok +} + +// hashItems hashes the given list of item types and returns it. +func hashItems(items []world.Item, useMeta bool) string { + items = sliceutil.Filter(items, func(it world.Item) bool { + return it != nil + }) + sort.Slice(items, func(i, j int) bool { + nameOne, metaOne := items[i].EncodeItem() + nameTwo, metaTwo := items[j].EncodeItem() + if nameOne == nameTwo { + return metaOne < metaTwo + } + return nameOne < nameTwo + }) + + var b strings.Builder + for _, it := range items { + name, meta := it.EncodeItem() + b.WriteString(name) + if useMeta { + a := *(*[2]byte)(unsafe.Pointer(&meta)) + b.Write(a[:]) + } + } + return b.String() +} + +// ValidBrewingReagent checks if the world.Item is a brewing reagent. +func ValidBrewingReagent(i world.Item) bool { + name, _ := i.EncodeItem() + _, exists := reagent[name] + return exists } diff --git a/server/item/recipe/vanilla.go b/server/item/recipe/vanilla.go index aa1f079bb..33121f5eb 100644 --- a/server/item/recipe/vanilla.go +++ b/server/item/recipe/vanilla.go @@ -2,10 +2,9 @@ package recipe import ( _ "embed" - - // Ensure all blocks and items are registered before trying to load vanilla recipes. - _ "github.com/df-mc/dragonfly/server/block" "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/world" + "github.com/sandertv/gophertunnel/minecraft/nbt" ) @@ -18,6 +17,8 @@ var ( vanillaSmithingTrimData []byte //go:embed furnace_data.nbt furnaceData []byte + //go:embed potion_data.nbt + vanillaPotionData []byte ) // shapedRecipe is a recipe that must be crafted in a specific shape. @@ -45,7 +46,25 @@ type furnaceRecipe struct { Block string `nbt:"block"` } -func init() { +// potionRecipe is a recipe that may be crafted in a brewing stand. +type potionRecipe struct { + Input inputItem `nbt:"input"` + Reagent inputItem `nbt:"reagent"` + Output outputItem `nbt:"output"` +} + +// potionContainerChangeRecipe is a recipe that may be crafted in a brewing stand. +type potionContainerChangeRecipe struct { + Input string `nbt:"input"` + Reagent inputItem `nbt:"reagent"` + Output string `nbt:"output"` +} + +// registerVanilla can be called to register all vanilla recipes from the generated data files. +// noinspection GoUnusedFunction +// +//lint:ignore U1000 Function is used through compiler directives. +func registerVanilla() { var craftingRecipes struct { Shaped []shapedRecipe `nbt:"shaped"` Shapeless []shapelessRecipe `nbt:"shapeless"` @@ -144,4 +163,45 @@ func init() { block: s.Block, }}) } + + var potionRecipes struct { + Potions []potionRecipe `nbt:"potions"` + ContainerChanges []potionContainerChangeRecipe `nbt:"container_changes"` + } + + if err := nbt.Unmarshal(vanillaPotionData, &potionRecipes); err != nil { + panic(err) + } + + for _, r := range potionRecipes.Potions { + input, ok := r.Input.Item() + reagent, okTwo := r.Reagent.Item() + output, okThree := r.Output.Stack() + if !ok || !okTwo || !okThree { + // This can be expected to happen - refer to the comment above. + continue + } + + Register(Potion{recipe{ + input: []Item{input, reagent}, + output: []item.Stack{output}, + block: "brewing_stand", + }}) + } + + for _, c := range potionRecipes.ContainerChanges { + input, ok := world.ItemByName(c.Input, 0) + reagent, okTwo := c.Reagent.Item() + output, okThree := world.ItemByName(c.Output, 0) + if !ok || !okTwo || !okThree { + // This can be expected to happen - refer to the comment above. + continue + } + + Register(PotionContainerChange{recipe{ + input: []Item{item.NewStack(input, 1), reagent}, + output: []item.Stack{item.NewStack(output, 1)}, + block: "brewing_stand", + }}) + } } diff --git a/server/session/player.go b/server/session/player.go index 27f878c9b..40230c9a7 100644 --- a/server/session/player.go +++ b/server/session/player.go @@ -106,6 +106,9 @@ func (s *Session) SendRespawn(pos mgl64.Vec3) { // sendRecipes sends the current crafting recipes to the session. func (s *Session) sendRecipes() { recipes := make([]protocol.Recipe, 0, len(recipe.Recipes())) + potionRecipes := make([]protocol.PotionRecipe, 0) + potionContainerChange := make([]protocol.PotionContainerChangeRecipe, 0) + for index, i := range recipe.Recipes() { networkID := uint32(index) + 1 s.recipes[networkID] = i @@ -158,9 +161,33 @@ func (s *Session) sendRecipes() { Output: stackFromItem(i.Output()[0]), Block: i.Block(), }) + case recipe.Potion: + inputRuntimeID, inputMeta, _ := world.ItemRuntimeID(i.Input()[0].(item.Stack).Item()) + reagentRuntimeID, reagentMeta, _ := world.ItemRuntimeID(i.Input()[1].(item.Stack).Item()) + outputRuntimeID, outputMeta, _ := world.ItemRuntimeID(i.Output()[0].Item()) + + potionRecipes = append(potionRecipes, protocol.PotionRecipe{ + InputPotionID: inputRuntimeID, + InputPotionMetadata: int32(inputMeta), + ReagentItemID: reagentRuntimeID, + ReagentItemMetadata: int32(reagentMeta), + OutputPotionID: outputRuntimeID, + OutputPotionMetadata: int32(outputMeta), + }) + + case recipe.PotionContainerChange: + inputRuntimeID, _, _ := world.ItemRuntimeID(i.Input()[0].(item.Stack).Item()) + reagentRuntimeID, _, _ := world.ItemRuntimeID(i.Input()[1].(item.Stack).Item()) + outputRuntimeID, _, _ := world.ItemRuntimeID(i.Output()[0].Item()) + + potionContainerChange = append(potionContainerChange, protocol.PotionContainerChangeRecipe{ + InputItemID: inputRuntimeID, + ReagentItemID: reagentRuntimeID, + OutputItemID: outputRuntimeID, + }) } } - s.writePacket(&packet.CraftingData{Recipes: recipes, ClearRecipes: true}) + s.writePacket(&packet.CraftingData{Recipes: recipes, PotionRecipes: potionRecipes, PotionContainerChangeRecipes: potionContainerChange, ClearRecipes: true}) } // sendArmourTrimData sends the armour trim data. @@ -260,6 +287,12 @@ func (s *Session) invByID(id int32) (*inventory.Inventory, bool) { return s.ui, true } } + case protocol.ContainerBrewingStandInput, protocol.ContainerBrewingStandResult, protocol.ContainerBrewingStandFuel: + if s.containerOpened.Load() { + if _, brewingStand := s.c.World().Block(*s.openedPos.Load()).(block.BrewingStand); brewingStand { + return s.openedWindow.Load(), true + } + } case protocol.ContainerAnvilInput, protocol.ContainerAnvilMaterial: if s.containerOpened.Load() { if _, anvil := s.c.World().Block(*s.openedPos.Load()).(block.Anvil); anvil { diff --git a/server/session/world.go b/server/session/world.go index 91177bcc6..cc6188569 100644 --- a/server/session/world.go +++ b/server/session/world.go @@ -605,6 +605,8 @@ func (s *Session) playSound(pos mgl64.Vec3, t world.Sound, disableRelative bool) pk.SoundType = packet.SoundEventBlastFurnaceUse case sound.SmokerCrackle: pk.SoundType = packet.SoundEventSmokerUse + case sound.PotionBrewed: + pk.SoundType = packet.SoundEventPotionBrewed case sound.UseSpyglass: pk.SoundType = packet.SoundEventUseSpyglass case sound.StopUsingSpyglass: @@ -850,6 +852,33 @@ func (s *Session) ViewFurnaceUpdate(prevCookTime, cookTime, prevRemainingFuelTim } } +// ViewBrewingUpdate updates a brewing stand for the associated session based on previous times. +func (s *Session) ViewBrewingUpdate(prevBrewTime, brewTime time.Duration, prevFuelAmount, fuelAmount, prevFuelTotal, fuelTotal int32) { + if prevBrewTime != brewTime { + s.writePacket(&packet.ContainerSetData{ + WindowID: byte(s.openedWindowID.Load()), + Key: packet.ContainerDataBrewingStandBrewTime, + Value: int32(brewTime.Milliseconds() / 50), + }) + } + + if prevFuelAmount != fuelAmount { + s.writePacket(&packet.ContainerSetData{ + WindowID: byte(s.openedWindowID.Load()), + Key: packet.ContainerDataBrewingStandFuelAmount, + Value: fuelAmount, + }) + } + + if prevFuelTotal != fuelTotal { + s.writePacket(&packet.ContainerSetData{ + WindowID: byte(s.openedWindowID.Load()), + Key: packet.ContainerDataBrewingStandFuelTotal, + Value: fuelTotal, + }) + } +} + // ViewBlockUpdate ... func (s *Session) ViewBlockUpdate(pos cube.Pos, b world.Block, layer int) { blockPos := protocol.BlockPos{int32(pos[0]), int32(pos[1]), int32(pos[2])} @@ -1036,6 +1065,8 @@ func (s *Session) openNormalContainer(b block.Container, pos cube.Pos) { switch b.(type) { case block.Furnace: containerType = protocol.ContainerTypeFurnace + case block.BrewingStand: + containerType = protocol.ContainerTypeBrewingStand case block.BlastFurnace: containerType = protocol.ContainerTypeBlastFurnace case block.Smoker: diff --git a/server/world/sound/block.go b/server/world/sound/block.go index a3afb1951..d9a35c73d 100644 --- a/server/world/sound/block.go +++ b/server/world/sound/block.go @@ -182,6 +182,9 @@ type ComposterFillLayer struct{ sound } // ComposterReady is a sound played when a composter has produced bone meal and is ready to be collected. type ComposterReady struct{ sound } +// PotionBrewed is a sound played when a potion is brewed. +type PotionBrewed struct{ sound } + // LecternBookPlace is a sound played when a book is placed in a lectern. type LecternBookPlace struct{ sound } diff --git a/server/world/viewer.go b/server/world/viewer.go index eae4c8562..eee356db7 100644 --- a/server/world/viewer.go +++ b/server/world/viewer.go @@ -31,6 +31,8 @@ type Viewer interface { ViewEntityTeleport(e Entity, pos mgl64.Vec3) // ViewFurnaceUpdate updates a furnace for the associated session based on previous times. ViewFurnaceUpdate(prevCookTime, cookTime, prevRemainingFuelTime, remainingFuelTime, prevMaxFuelTime, maxFuelTime time.Duration) + // ViewBrewingUpdate updates a brewing stand for the associated session based on previous times. + ViewBrewingUpdate(prevBrewTime, brewTime time.Duration, prevFuelAmount, fuelAmount, prevFuelTotal, fuelTotal int32) // ViewChunk views the chunk passed at a particular position. It is called for every chunk loaded using // the world.Loader. ViewChunk(pos ChunkPos, c *chunk.Chunk, blockEntities map[cube.Pos]Block) @@ -77,26 +79,27 @@ type NopViewer struct{} // Compile time check to make sure NopViewer implements Viewer. var _ Viewer = NopViewer{} -func (NopViewer) ViewEntity(Entity) {} -func (NopViewer) HideEntity(Entity) {} -func (NopViewer) ViewEntityGameMode(Entity) {} -func (NopViewer) ViewEntityMovement(Entity, mgl64.Vec3, cube.Rotation, bool) {} -func (NopViewer) ViewEntityVelocity(Entity, mgl64.Vec3) {} -func (NopViewer) ViewEntityTeleport(Entity, mgl64.Vec3) {} -func (NopViewer) ViewChunk(ChunkPos, *chunk.Chunk, map[cube.Pos]Block) {} -func (NopViewer) ViewTime(int) {} -func (NopViewer) ViewEntityItems(Entity) {} -func (NopViewer) ViewEntityArmour(Entity) {} -func (NopViewer) ViewEntityAction(Entity, EntityAction) {} -func (NopViewer) ViewEntityState(Entity) {} -func (NopViewer) ViewEntityAnimation(Entity, string) {} -func (NopViewer) ViewParticle(mgl64.Vec3, Particle) {} -func (NopViewer) ViewSound(mgl64.Vec3, Sound) {} -func (NopViewer) ViewBlockUpdate(cube.Pos, Block, int) {} -func (NopViewer) ViewBlockAction(cube.Pos, BlockAction) {} -func (NopViewer) ViewEmote(Entity, uuid.UUID) {} -func (NopViewer) ViewSkin(Entity) {} -func (NopViewer) ViewWorldSpawn(cube.Pos) {} -func (NopViewer) ViewWeather(bool, bool) {} +func (NopViewer) ViewEntity(Entity) {} +func (NopViewer) HideEntity(Entity) {} +func (NopViewer) ViewEntityGameMode(Entity) {} +func (NopViewer) ViewEntityMovement(Entity, mgl64.Vec3, cube.Rotation, bool) {} +func (NopViewer) ViewEntityVelocity(Entity, mgl64.Vec3) {} +func (NopViewer) ViewEntityTeleport(Entity, mgl64.Vec3) {} +func (NopViewer) ViewChunk(ChunkPos, *chunk.Chunk, map[cube.Pos]Block) {} +func (NopViewer) ViewTime(int) {} +func (NopViewer) ViewEntityItems(Entity) {} +func (NopViewer) ViewEntityArmour(Entity) {} +func (NopViewer) ViewEntityAction(Entity, EntityAction) {} +func (NopViewer) ViewEntityState(Entity) {} +func (NopViewer) ViewEntityAnimation(Entity, string) {} +func (NopViewer) ViewParticle(mgl64.Vec3, Particle) {} +func (NopViewer) ViewSound(mgl64.Vec3, Sound) {} +func (NopViewer) ViewBlockUpdate(cube.Pos, Block, int) {} +func (NopViewer) ViewBlockAction(cube.Pos, BlockAction) {} +func (NopViewer) ViewEmote(Entity, uuid.UUID) {} +func (NopViewer) ViewSkin(Entity) {} +func (NopViewer) ViewWorldSpawn(cube.Pos) {} +func (NopViewer) ViewWeather(bool, bool) {} +func (NopViewer) ViewBrewingUpdate(time.Duration, time.Duration, int32, int32, int32, int32) {} func (NopViewer) ViewFurnaceUpdate(time.Duration, time.Duration, time.Duration, time.Duration, time.Duration, time.Duration) { } From b4e2e0d7568add07c1d0e9c2f7bd59f24245cea7 Mon Sep 17 00:00:00 2001 From: Sergittos Date: Mon, 2 Sep 2024 15:05:14 +0200 Subject: [PATCH 22/28] server/player.go: Add flight speed --- server/player/player.go | 19 ++++++++++++++++++- server/session/controllable.go | 2 ++ server/session/handler_request_ability.go | 2 +- server/session/player.go | 12 ++++++------ 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/server/player/player.go b/server/player/player.go index e10b11123..69d0ec591 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -80,7 +80,9 @@ type Player struct { // lastTickedWorld holds the world that the player was in, in the last tick. lastTickedWorld *world.World - speed atomic.Uint64 + speed atomic.Uint64 + flightSpeed atomic.Uint64 + health *entity.HealthManager experience *entity.ExperienceManager effects *entity.EffectManager @@ -141,6 +143,7 @@ func New(name string, skin skin.Skin, pos mgl64.Vec3) *Player { p.Handle(nil) p.skin.Store(&skin) p.speed.Store(math.Float64bits(0.1)) + p.flightSpeed.Store(math.Float64bits(0.05)) p.nameTag.Store(&name) p.scoreTag.Store(&scoreTag) p.airSupplyTicks.Store(300) @@ -491,6 +494,20 @@ func (p *Player) Speed() float64 { return math.Float64frombits(p.speed.Load()) } +// SetFlightSpeed sets the flight speed of the player. The value passed represents the base speed, which is +// multiplied by 10 to obtain the actual blocks/tick speed that the player will then obtain while flying. +func (p *Player) SetFlightSpeed(flightSpeed float64) { + p.flightSpeed.Store(math.Float64bits(flightSpeed)) + p.session().SendAbilities() +} + +// FlightSpeed returns the flight speed of the player, with the value representing the base speed. The actual +// blocks/tick speed is this value multiplied by 10. The default flight speed of a player is 0.05, which +// corresponds to 0.5 blocks/tick. +func (p *Player) FlightSpeed() float64 { + return math.Float64frombits(p.flightSpeed.Load()) +} + // Health returns the current health of the player. It will always be lower than Player.MaxHealth(). func (p *Player) Health() float64 { return p.health.Health() diff --git a/server/session/controllable.go b/server/session/controllable.go index c6919ea1e..7919218d3 100644 --- a/server/session/controllable.go +++ b/server/session/controllable.go @@ -32,7 +32,9 @@ type Controllable interface { SetHeldItems(right, left item.Stack) Move(deltaPos mgl64.Vec3, deltaYaw, deltaPitch float64) + Speed() float64 + FlightSpeed() float64 Chat(msg ...any) ExecuteCommand(commandLine string) diff --git a/server/session/handler_request_ability.go b/server/session/handler_request_ability.go index 244bd9998..e0a7c5403 100644 --- a/server/session/handler_request_ability.go +++ b/server/session/handler_request_ability.go @@ -13,7 +13,7 @@ func (a RequestAbilityHandler) Handle(p packet.Packet, s *Session) error { if pk.Ability == packet.AbilityFlying { if !s.c.GameMode().AllowsFlying() { s.log.Debug("process packet: RequestAbility: flying flag enabled while unable to fly") - s.sendAbilities() + s.SendAbilities() return nil } s.c.StartFlying() diff --git a/server/session/player.go b/server/session/player.go index 40230c9a7..7dac2f3b5 100644 --- a/server/session/player.go +++ b/server/session/player.go @@ -449,11 +449,11 @@ func (s *Session) SendGameMode(mode world.GameMode) { return } s.writePacket(&packet.SetPlayerGameType{GameType: gameTypeFromMode(mode)}) - s.sendAbilities() + s.SendAbilities() } -// sendAbilities sends the abilities of the Controllable entity of the session to the client. -func (s *Session) sendAbilities() { +// SendAbilities sends the abilities of the Controllable entity of the session to the client. +func (s *Session) SendAbilities() { mode, abilities := s.c.GameMode(), uint32(0) if mode.AllowsFlying() { abilities |= protocol.AbilityMayFly @@ -484,13 +484,13 @@ func (s *Session) sendAbilities() { EntityUniqueID: selfEntityRuntimeID, PlayerPermissions: packet.PermissionLevelMember, CommandPermissions: packet.CommandPermissionLevelNormal, - Layers: []protocol.AbilityLayer{ // TODO: Support customization of fly and walk speeds. + Layers: []protocol.AbilityLayer{ { Type: protocol.AbilityLayerTypeBase, Abilities: protocol.AbilityCount - 1, Values: abilities, - FlySpeed: protocol.AbilityBaseFlySpeed, - WalkSpeed: protocol.AbilityBaseWalkSpeed, + FlySpeed: float32(s.c.FlightSpeed()), + WalkSpeed: float32(s.c.Speed()), }, }, }}) From a2ea5412ae3294083d308ae4dc5e4d3d7dc06276 Mon Sep 17 00:00:00 2001 From: DaPigGuy Date: Sun, 17 Nov 2024 11:58:02 +0000 Subject: [PATCH 23/28] updated contributor list --- server/session/enchantment_texts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/session/enchantment_texts.go b/server/session/enchantment_texts.go index 6a712e90c..da1ef07fc 100644 --- a/server/session/enchantment_texts.go +++ b/server/session/enchantment_texts.go @@ -4,4 +4,4 @@ package session // enchantNames are names translated to the 'Standard Galactic Alphabet' client-side. The names generally have no meaning // on the vanilla server implementation, so we can sneak some easter eggs in here without anyone noticing. -var enchantNames = []string{"aabstractt", "abimek", "aericio", "aimjel", "alvin0319", "andreas hgk", "atm85", "blackjack200", "cetfu", "cjmustard1452", "cqdetdev", "da pig guy", "daft0175", "dasciam", "deniel world", "didntpot", "eminarican", "endermanbugzjfc", "erkam246", "ethaniccc", "fdutch", "flonja", "gewinum", "hashim the arab", "hochbaum", "hyper flare mc", "im da real ani", "ipad54", "its zodia x", "ivan craft623", "javier leon9966", "just tal develops", "liatoast", "mmm545", "mohamed587100", "myma qc", "natuyasai natuo", "neutronic mc", "nonono697", "provsalt", "restart fu", "riccskn", "robertdudaa", "royal mcpe", "sallypemdas", "sandertv", "sculas", "sqmatheus", "ssaini123456", "t14 raptor", "tadhunt", "theaddonn", "thunder33345", "tristanmorgan", "twisted asylum mc", "unickorn", "unknown ore", "uramnoil", "wqrro", "x natsuri", "x4caa", "xd-pro"} +var enchantNames = []string{"aabstractt", "abimek", "aericio", "aimjel", "alvin0319", "andreas hgk", "atm85", "blackjack200", "cetfu", "cjmustard1452", "cqdetdev", "da pig guy", "daft0175", "dasciam", "deniel world", "didntpot", "eminarican", "endermanbugzjfc", "erkam246", "ethaniccc", "fdutch", "flonja", "gewinum", "hashim the arab", "hochbaum", "hyper flare mc", "im da real ani", "ipad54", "its zodia x", "ivan craft623", "javier leon9966", "just tal develops", "liatoast", "mmm545", "mohamed587100", "myma qc", "natuyasai natuo", "neutronic mc", "nonono697", "provsalt", "restart fu", "riccskn", "robertdudaa", "royal mcpe", "sallypemdas", "sandertv", "sculas", "sergittos", "sqmatheus", "ssaini123456", "t14 raptor", "tadhunt", "theaddonn", "thunder33345", "tristanmorgan", "twisted asylum mc", "unickorn", "unknown ore", "uramnoil", "wqrro", "x natsuri", "x4caa", "xd-pro"} From 6a5c2fdc5049be6b005ee572a431c3a5b5eb7aa8 Mon Sep 17 00:00:00 2001 From: Jon <86489758+xNatsuri@users.noreply.github.com> Date: Sun, 17 Nov 2024 10:45:34 -0800 Subject: [PATCH 24/28] player/player.go: Extinguish player when splashed with water (#935) --- server/player/player.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/server/player/player.go b/server/player/player.go index 69d0ec591..9ceb5b150 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -1061,6 +1061,14 @@ func (p *Player) StopSwimming() { p.updateState() } +// Splash is called when a water bottle splashes onto the player. +func (p *Player) Splash(*world.World, mgl64.Vec3) { + if d := p.OnFireDuration(); d.Seconds() <= 0 { + return + } + p.Extinguish() +} + // StartCrawling makes the player start crawling if it is not currently doing so. If the player is sneaking // while StartCrawling is called, the sneaking is stopped. func (p *Player) StartCrawling() { From 77eb52bc628deebcf3a4bd91063ed9d7eb0cd575 Mon Sep 17 00:00:00 2001 From: Flonja <20887403+Flonja@users.noreply.github.com> Date: Sun, 17 Nov 2024 19:46:30 +0100 Subject: [PATCH 25/28] player/player.go: Fix explosion damage (#869) --- server/block/explosion.go | 6 +++--- server/player/player.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/block/explosion.go b/server/block/explosion.go index b24e4bd1a..1485ad224 100644 --- a/server/block/explosion.go +++ b/server/block/explosion.go @@ -95,12 +95,12 @@ func (c ExplosionConfig) Explode(w *world.World, explosionPos mgl64.Vec3) { if !e.Type().BBox(e).Translate(pos).IntersectsWith(box) { continue } - dist := pos.Sub(pos).Len() - if dist >= d { + dist := pos.Sub(explosionPos).Len() + if dist > d || dist == 0 { continue } if explodable, ok := e.(ExplodableEntity); ok { - impact := (1 - dist/d) * exposure(pos, e) + impact := (1 - dist/d) * exposure(explosionPos, e) explodable.Explode(explosionPos, impact, c) } } diff --git a/server/player/player.go b/server/player/player.go index 9ceb5b150..3262edbcd 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -708,7 +708,7 @@ func (p *Player) FinalDamageFrom(dmg float64, src world.DamageSource) float64 { // Explode ... func (p *Player) Explode(explosionPos mgl64.Vec3, impact float64, c block.ExplosionConfig) { diff := p.Position().Sub(explosionPos) - p.Hurt(math.Floor((impact*impact+impact)*3.5*c.Size+1), entity.ExplosionDamageSource{}) + p.Hurt(math.Floor((impact*impact+impact)/2*7*c.Size*2+1), entity.ExplosionDamageSource{}) p.knockBack(explosionPos, impact, diff[1]/diff.Len()*impact) } From 0637012e5ea28136785feb3f437de2fb7c4d2460 Mon Sep 17 00:00:00 2001 From: Ali <47182802+UnknownOre@users.noreply.github.com> Date: Mon, 18 Nov 2024 01:26:26 +0200 Subject: [PATCH 26/28] entity/item_behaviour: Introduce a CanCollect() method to stop spectators collecting arrows (#879) --- server/entity/item_behaviour.go | 5 +++++ server/entity/projectile.go | 6 ++++++ server/player/player.go | 17 +++++++++++++---- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/server/entity/item_behaviour.go b/server/entity/item_behaviour.go index f1246c17c..05b9829aa 100644 --- a/server/entity/item_behaviour.go +++ b/server/entity/item_behaviour.go @@ -154,6 +154,9 @@ func (i *ItemBehaviour) merge(e *Ent, other *Ent) bool { // collect makes a collector collect the item (or at least part of it). func (i *ItemBehaviour) collect(e *Ent, collector Collector) { + if !collector.CanCollect() { + return + } w, pos := e.World(), e.Position() n := collector.Collect(i.i) if n == 0 { @@ -178,6 +181,8 @@ func (i *ItemBehaviour) collect(e *Ent, collector Collector) { // a player or a zombie. type Collector interface { world.Entity + // CanCollect returns whether the Collector is able to pick up an item or not. + CanCollect() bool // Collect collects the stack passed. It is called if the Collector is standing near an item entity that // may be picked up. // The count of items collected from the stack n is returned. diff --git a/server/entity/projectile.go b/server/entity/projectile.go index a6dfec91c..812400785 100644 --- a/server/entity/projectile.go +++ b/server/entity/projectile.go @@ -221,6 +221,11 @@ func (lt *ProjectileBehaviour) tryPickup(e *Ent) { if !ok { continue } + + if !collector.CanCollect() { + continue + } + // A collector was within range to pick up the entity. lt.close = true for _, viewer := range w.Viewers(e.pos) { @@ -229,6 +234,7 @@ func (lt *ProjectileBehaviour) tryPickup(e *Ent) { if lt.conf.PickupItem.Empty() { return } + _ = collector.Collect(lt.conf.PickupItem) } } diff --git a/server/player/player.go b/server/player/player.go index 3262edbcd..8c3e4b27e 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -2128,13 +2128,22 @@ func (p *Player) ChangingDimension() bool { return p.session().ChangingDimension() } -// Collect makes the player collect the item stack passed, adding it to the inventory. The amount of items that could -// be added is returned. -func (p *Player) Collect(s item.Stack) int { +// CanCollect returns whether the player is able to pick up an item stack or not. +func (p *Player) CanCollect() bool { if p.Dead() { - return 0 + return false } if !p.GameMode().AllowsInteraction() { + return false + } + + return true +} + +// Collect makes the player collect the item stack passed, adding it to the inventory. The amount of items that could +// be added is returned. +func (p *Player) Collect(s item.Stack) int { + if !p.CanCollect() { return 0 } ctx := event.C() From 7abf1b58438f7d1113ea297021be818a7ea49240 Mon Sep 17 00:00:00 2001 From: Sandertv Date: Mon, 18 Nov 2024 11:37:55 +0100 Subject: [PATCH 27/28] Revert "entity/item_behaviour: Introduce a CanCollect() method to stop spectators collecting arrows (#879)" This reverts commit 0637012e5ea28136785feb3f437de2fb7c4d2460. --- server/entity/item_behaviour.go | 5 ----- server/entity/projectile.go | 6 ------ server/player/player.go | 17 ++++------------- 3 files changed, 4 insertions(+), 24 deletions(-) diff --git a/server/entity/item_behaviour.go b/server/entity/item_behaviour.go index 05b9829aa..f1246c17c 100644 --- a/server/entity/item_behaviour.go +++ b/server/entity/item_behaviour.go @@ -154,9 +154,6 @@ func (i *ItemBehaviour) merge(e *Ent, other *Ent) bool { // collect makes a collector collect the item (or at least part of it). func (i *ItemBehaviour) collect(e *Ent, collector Collector) { - if !collector.CanCollect() { - return - } w, pos := e.World(), e.Position() n := collector.Collect(i.i) if n == 0 { @@ -181,8 +178,6 @@ func (i *ItemBehaviour) collect(e *Ent, collector Collector) { // a player or a zombie. type Collector interface { world.Entity - // CanCollect returns whether the Collector is able to pick up an item or not. - CanCollect() bool // Collect collects the stack passed. It is called if the Collector is standing near an item entity that // may be picked up. // The count of items collected from the stack n is returned. diff --git a/server/entity/projectile.go b/server/entity/projectile.go index 812400785..a6dfec91c 100644 --- a/server/entity/projectile.go +++ b/server/entity/projectile.go @@ -221,11 +221,6 @@ func (lt *ProjectileBehaviour) tryPickup(e *Ent) { if !ok { continue } - - if !collector.CanCollect() { - continue - } - // A collector was within range to pick up the entity. lt.close = true for _, viewer := range w.Viewers(e.pos) { @@ -234,7 +229,6 @@ func (lt *ProjectileBehaviour) tryPickup(e *Ent) { if lt.conf.PickupItem.Empty() { return } - _ = collector.Collect(lt.conf.PickupItem) } } diff --git a/server/player/player.go b/server/player/player.go index 8c3e4b27e..3262edbcd 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -2128,22 +2128,13 @@ func (p *Player) ChangingDimension() bool { return p.session().ChangingDimension() } -// CanCollect returns whether the player is able to pick up an item stack or not. -func (p *Player) CanCollect() bool { - if p.Dead() { - return false - } - if !p.GameMode().AllowsInteraction() { - return false - } - - return true -} - // Collect makes the player collect the item stack passed, adding it to the inventory. The amount of items that could // be added is returned. func (p *Player) Collect(s item.Stack) int { - if !p.CanCollect() { + if p.Dead() { + return 0 + } + if !p.GameMode().AllowsInteraction() { return 0 } ctx := event.C() From 92f9c208c12088a398c174a748e8fbc39c791887 Mon Sep 17 00:00:00 2001 From: Sandertv Date: Mon, 18 Nov 2024 11:45:05 +0100 Subject: [PATCH 28/28] Merged CanCollect into Collect again and added a bool return to Collect(). This makes sure that arrows are also not collected if the pickup handler is cancelled. --- server/entity/item_behaviour.go | 8 +++++--- server/entity/projectile.go | 10 +++++----- server/player/player.go | 13 +++++-------- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/server/entity/item_behaviour.go b/server/entity/item_behaviour.go index f1246c17c..138b3b759 100644 --- a/server/entity/item_behaviour.go +++ b/server/entity/item_behaviour.go @@ -155,7 +155,7 @@ func (i *ItemBehaviour) merge(e *Ent, other *Ent) bool { // collect makes a collector collect the item (or at least part of it). func (i *ItemBehaviour) collect(e *Ent, collector Collector) { w, pos := e.World(), e.Position() - n := collector.Collect(i.i) + n, _ := collector.Collect(i.i) if n == 0 { return } @@ -180,6 +180,8 @@ type Collector interface { world.Entity // Collect collects the stack passed. It is called if the Collector is standing near an item entity that // may be picked up. - // The count of items collected from the stack n is returned. - Collect(stack item.Stack) (n int) + // The count of items collected from the stack n is returned, along with a + // bool that indicates if the Collector was in a state where it could + // collect any items in the first place. + Collect(stack item.Stack) (n int, ok bool) } diff --git a/server/entity/projectile.go b/server/entity/projectile.go index a6dfec91c..8a5e520c4 100644 --- a/server/entity/projectile.go +++ b/server/entity/projectile.go @@ -221,15 +221,15 @@ func (lt *ProjectileBehaviour) tryPickup(e *Ent) { if !ok { continue } - // A collector was within range to pick up the entity. + if _, ok := collector.Collect(lt.conf.PickupItem); !ok { + continue + } + + // A collector was within range and able to pick up the entity. lt.close = true for _, viewer := range w.Viewers(e.pos) { viewer.ViewEntityAction(e, PickedUpAction{Collector: collector}) } - if lt.conf.PickupItem.Empty() { - return - } - _ = collector.Collect(lt.conf.PickupItem) } } diff --git a/server/player/player.go b/server/player/player.go index 3262edbcd..24c53bc38 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -2130,19 +2130,16 @@ func (p *Player) ChangingDimension() bool { // Collect makes the player collect the item stack passed, adding it to the inventory. The amount of items that could // be added is returned. -func (p *Player) Collect(s item.Stack) int { - if p.Dead() { - return 0 - } - if !p.GameMode().AllowsInteraction() { - return 0 +func (p *Player) Collect(s item.Stack) (int, bool) { + if p.Dead() || !p.GameMode().AllowsInteraction() { + return 0, false } ctx := event.C() if p.Handler().HandleItemPickup(ctx, &s); ctx.Cancelled() { - return 0 + return 0, false } n, _ := p.Inventory().AddItem(s) - return n + return n, true } // Experience returns the amount of experience the player has.