Files

128 lines
4.5 KiB
Go
Raw Permalink Normal View History

2026-07-09 08:33:57 +08:00
package session
import (
"fmt"
"github.com/df-mc/dragonfly/server/world"
"math"
"math/rand/v2"
"github.com/df-mc/dragonfly/server/block"
"github.com/df-mc/dragonfly/server/entity"
"github.com/df-mc/dragonfly/server/item"
"github.com/sandertv/gophertunnel/minecraft/protocol"
)
const (
// grindstoneFirstInputSlot is the slot index of the first input item in the grindstone.
grindstoneFirstInputSlot = 0x10
// grindstoneSecondInputSlot is the slot index of the second input item in the grindstone.
grindstoneSecondInputSlot = 0x11
)
// handleGrindstoneCraft handles a CraftGrindstoneRecipe stack request action made using a grindstone.
func (h *ItemStackRequestHandler) handleGrindstoneCraft(s *Session, tx *world.Tx, c Controllable) error {
// First check if there actually is a grindstone opened.
if !s.containerOpened.Load() {
return fmt.Errorf("no grindstone container opened")
}
if _, ok := tx.Block(*s.openedPos.Load()).(block.Grindstone); !ok {
return fmt.Errorf("no grindstone container opened")
}
// Next, get both input items and ensure they are comparable.
firstInput, _ := h.itemInSlot(protocol.StackRequestSlotInfo{
Container: protocol.FullContainerName{ContainerID: protocol.ContainerGrindstoneInput},
Slot: grindstoneFirstInputSlot,
}, s, tx)
secondInput, _ := h.itemInSlot(protocol.StackRequestSlotInfo{
Container: protocol.FullContainerName{ContainerID: protocol.ContainerGrindstoneAdditional},
Slot: grindstoneSecondInputSlot,
}, s, tx)
if firstInput.Empty() && secondInput.Empty() {
return fmt.Errorf("input item(s) are empty")
}
if firstInput.Count() > 1 || secondInput.Count() > 1 {
return fmt.Errorf("input item(s) are not single items")
}
resultStack := nonZeroItem(firstInput, secondInput)
if !firstInput.Empty() && !secondInput.Empty() {
name, meta := firstInput.Item().EncodeItem()
name2, meta2 := secondInput.Item().EncodeItem()
if name != name2 || meta != meta2 {
return fmt.Errorf("input items must be the same type")
}
if _, ok := firstInput.Item().(item.Durable); !ok {
return fmt.Errorf("input items must be durable")
}
// We add the enchantments to the result stack in order to calculate the gained experience. These enchantments
// are stripped when creating the result.
resultStack = firstInput.WithEnchantments(secondInput.Enchantments()...)
// Merge the durability of the two input items at 5%.
maxDurability := firstInput.MaxDurability()
firstDurability, secondDurability := firstInput.Durability(), secondInput.Durability()
resultStack = resultStack.WithDurability(firstDurability + secondDurability + maxDurability*5/100)
}
for _, o := range entity.NewExperienceOrbs(entity.EyePosition(c), experienceFromEnchantments(resultStack)) {
tx.AddEntity(o)
}
h.setItemInSlot(protocol.StackRequestSlotInfo{
Container: protocol.FullContainerName{ContainerID: protocol.ContainerGrindstoneInput},
Slot: grindstoneFirstInputSlot,
}, item.Stack{}, s, tx)
h.setItemInSlot(protocol.StackRequestSlotInfo{
Container: protocol.FullContainerName{ContainerID: protocol.ContainerGrindstoneAdditional},
Slot: grindstoneSecondInputSlot,
}, item.Stack{}, s, tx)
return h.createResults(s, tx, stripPossibleEnchantments(resultStack))
}
// curseEnchantment represents an enchantment that may be a curse enchantment.
type curseEnchantment interface {
Curse() bool
}
// experienceFromEnchantments returns the amount of experience that is gained from the enchantments on the given stack.
func experienceFromEnchantments(stack item.Stack) int {
var totalCost int
for _, enchant := range stack.Enchantments() {
if _, ok := enchant.Type().(curseEnchantment); ok {
continue
}
cost, _ := enchant.Type().Cost(enchant.Level())
totalCost += cost
}
if totalCost == 0 {
// No cost, no experience.
return 0
}
minExperience := int(math.Ceil(float64(totalCost) / 2))
return minExperience + rand.IntN(minExperience)
}
// stripPossibleEnchantments strips all enchantments possible, excluding curses.
func stripPossibleEnchantments(stack item.Stack) item.Stack {
for _, enchant := range stack.Enchantments() {
if _, ok := enchant.Type().(curseEnchantment); ok {
continue
}
stack = stack.WithoutEnchantments(enchant.Type())
}
return stack.WithAnvilCost(0)
}
// nonZeroItem returns the item.Stack that exists out of two input items. The function expects at least one of the
// items to be non-empty.
func nonZeroItem(first, second item.Stack) item.Stack {
if first.Empty() {
return second
}
return first
}