Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement curse of binding #716

Closed
wants to merge 16 commits into from
51 changes: 51 additions & 0 deletions server/item/enchantment/curse_of_binding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package enchantment

import (
"github.com/df-mc/dragonfly/server/item"
"github.com/df-mc/dragonfly/server/world"
)

// CurseOfBinding is an enchantment that prevents the removal of an equipped item enchanted with the curse of binding from its armour slot.
type CurseOfBinding struct{}
This conversation was marked as resolved.
Show resolved Hide resolved

// Name ...
func (CurseOfBinding) Name() string {
return "Curse of Binding"
}

// MaxLevel ...
func (CurseOfBinding) MaxLevel() int {
return 1
}

// Cost ...
func (CurseOfBinding) Cost(level int) (int, int) {
return 25, 50
}

// Rarity ...
func (CurseOfBinding) Rarity() item.EnchantmentRarity {
return item.EnchantmentRarityVeryRare
}

// CompatibleWithEnchantment ...
func (CurseOfBinding) CompatibleWithEnchantment(item.EnchantmentType) bool {
return true
}

// CompatibleWithItem ...
func (CurseOfBinding) CompatibleWithItem(i world.Item) bool {
_, isArmour := i.(item.Armour)

return isArmour
}

// Treasure ...
func (CurseOfBinding) Treasure() bool {
return true
}

// Curse ...
func (CurseOfBinding) Curse() bool {
return true
}
2 changes: 1 addition & 1 deletion server/item/enchantment/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func init() {
// TODO: (24) Lure.
// TODO: (25) Frost Walker.
item.RegisterEnchantment(26, Mending{})
// TODO: (27) Curse of Binding.
item.RegisterEnchantment(27, CurseOfBinding{})
item.RegisterEnchantment(28, CurseOfVanishing{})
// TODO: (29) Impaling.
// TODO: (30) Riptide.
Expand Down
5 changes: 5 additions & 0 deletions server/player/player.go
Original file line number Diff line number Diff line change
Expand Up @@ -2930,6 +2930,11 @@ func (p *Player) useContext() *item.UseContext {
srcIt, _ := srcInv.Item(src)
dstIt, _ := dstInv.Item(dst)

// If dstIt is enchanted with curse of binding, do not swap.
if _, isCursed := dstIt.Enchantment(enchantment.CurseOfBinding{}); isCursed && !p.GameMode().CreativeInventory() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels out of place. How could this ever happen? I assume any attempt to move the item out will be cancelled at the session level?

Copy link
Author

@ghost ghost Jan 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not get cancelled at the session level.

Alternatively, I could modify relevant items' Use() function and check if the carrier (player) is wearing armour enchanted with curse of binding.

return
}

ctx := event.C()
_ = call(ctx, src, srcIt, srcInv.Handler().HandleTake)
_ = call(ctx, src, dstIt, srcInv.Handler().HandlePlace)
Expand Down
29 changes: 29 additions & 0 deletions server/session/handler_item_stack_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/df-mc/dragonfly/server/entity"
"github.com/df-mc/dragonfly/server/event"
"github.com/df-mc/dragonfly/server/item"
"github.com/df-mc/dragonfly/server/item/enchantment"
"github.com/df-mc/dragonfly/server/item/inventory"
"github.com/go-gl/mathgl/mgl64"
"github.com/sandertv/gophertunnel/minecraft/protocol"
Expand Down Expand Up @@ -157,6 +158,12 @@ func (h *ItemStackRequestHandler) handleTransfer(from, to protocol.StackRequestS
if (dest.Count()+int(count) > dest.MaxCount()) && !dest.Empty() {
return fmt.Errorf("client tried adding %v to item count %v, but max is %v", count, dest.Count(), dest.MaxCount())
}

// Do not allow an equipped item cursed with binding to be transferred.
if curseOfBinding(from.ContainerID, i, s) || curseOfBinding(to.ContainerID, dest, s) {
return fmt.Errorf("client attempted to transfer an equipped armour item enchanted with the curse of binding")
}

if dest.Empty() {
dest = i.Grow(-math.MaxInt32)
}
Expand Down Expand Up @@ -197,6 +204,11 @@ func (h *ItemStackRequestHandler) handleSwap(a *protocol.SwapStackRequestAction,
return err
}

// Do not allow an equipped item cursed with binding to be swapped out by cursor or touch screen.
if curseOfBinding(a.Destination.ContainerID, dest, s) || curseOfBinding(a.Source.ContainerID, i, s) {
return fmt.Errorf("client attempted to swap an equipped armour item enchanted with the curse of binding")
}

h.setItemInSlot(a.Source, dest, s)
h.setItemInSlot(a.Destination, i, s)
h.collectRewards(s, invA, int(a.Source.Slot))
Expand Down Expand Up @@ -254,6 +266,11 @@ func (h *ItemStackRequestHandler) handleDrop(a *protocol.DropStackRequestAction,
return err
}

// Do not allow an equipped item cursed with binding to be dropped.
if curseOfBinding(a.Source.ContainerID, i, s) {
return fmt.Errorf("client attempted to drop an equipped armour item enchanted with the curse of binding")
}

n := s.c.Drop(i.Grow(int(a.Count) - i.Count()))
h.setItemInSlot(a.Source, i.Grow(-n), s)
return nil
Expand Down Expand Up @@ -512,3 +529,15 @@ func call(ctx *event.Context, slot int, it item.Stack, f func(ctx *event.Context
}
return nil
}

// curseOfBinding returns true if an ItemStackRequestHandler should be cancelled as a result of the given combination of
// containerID, item and session. containerID should be the ID of the container where i is currently.
func curseOfBinding(containerID byte, i item.Stack, s *Session) bool {
if containerID != protocol.ContainerArmor || s.c.GameMode().CreativeInventory() {
return false
}

_, isCursed := i.Enchantment(enchantment.CurseOfBinding{})

return isCursed
}