26ed99fda6
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
128 lines
4.5 KiB
Go
128 lines
4.5 KiB
Go
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
|
|
}
|