github.com/df-mc/dragonfly@v0.9.13/server/session/handler_grindstone.go (about)

     1  package session
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"math/rand"
     7  
     8  	"github.com/df-mc/dragonfly/server/block"
     9  	"github.com/df-mc/dragonfly/server/entity"
    10  	"github.com/df-mc/dragonfly/server/item"
    11  	"github.com/go-gl/mathgl/mgl64"
    12  	"github.com/sandertv/gophertunnel/minecraft/protocol"
    13  )
    14  
    15  const (
    16  	// grindstoneFirstInputSlot is the slot index of the first input item in the grindstone.
    17  	grindstoneFirstInputSlot = 0x10
    18  	// grindstoneSecondInputSlot is the slot index of the second input item in the grindstone.
    19  	grindstoneSecondInputSlot = 0x11
    20  )
    21  
    22  // handleGrindstoneCraft handles a CraftGrindstoneRecipe stack request action made using a grindstone.
    23  func (h *ItemStackRequestHandler) handleGrindstoneCraft(s *Session) error {
    24  	// First check if there actually is a grindstone opened.
    25  	if !s.containerOpened.Load() {
    26  		return fmt.Errorf("no grindstone container opened")
    27  	}
    28  	if _, ok := s.c.World().Block(s.openedPos.Load()).(block.Grindstone); !ok {
    29  		return fmt.Errorf("no grindstone container opened")
    30  	}
    31  
    32  	// Next, get both input items and ensure they are comparable.
    33  	firstInput, _ := h.itemInSlot(protocol.StackRequestSlotInfo{
    34  		ContainerID: protocol.ContainerGrindstoneInput,
    35  		Slot:        grindstoneFirstInputSlot,
    36  	}, s)
    37  	secondInput, _ := h.itemInSlot(protocol.StackRequestSlotInfo{
    38  		ContainerID: protocol.ContainerGrindstoneAdditional,
    39  		Slot:        grindstoneSecondInputSlot,
    40  	}, s)
    41  	if firstInput.Empty() && secondInput.Empty() {
    42  		return fmt.Errorf("input item(s) are empty")
    43  	}
    44  	if firstInput.Count() > 1 || secondInput.Count() > 1 {
    45  		return fmt.Errorf("input item(s) are not single items")
    46  	}
    47  
    48  	resultStack := nonZeroItem(firstInput, secondInput)
    49  	if !firstInput.Empty() && !secondInput.Empty() {
    50  		// We add the enchantments to the result stack in order to calculate the gained experience. These enchantments
    51  		// are stripped when creating the result.
    52  		resultStack = firstInput.WithEnchantments(secondInput.Enchantments()...)
    53  
    54  		// Merge the durability of the two input items at 5%.
    55  		maxDurability := firstInput.MaxDurability()
    56  		firstDurability, secondDurability := firstInput.Durability(), secondInput.Durability()
    57  
    58  		resultStack = resultStack.WithDurability(firstDurability + secondDurability + maxDurability*5/100)
    59  	}
    60  
    61  	w := s.c.World()
    62  	for _, o := range entity.NewExperienceOrbs(entity.EyePosition(s.c), experienceFromEnchantments(resultStack)) {
    63  		o.SetVelocity(mgl64.Vec3{(rand.Float64()*0.2 - 0.1) * 2, rand.Float64() * 0.4, (rand.Float64()*0.2 - 0.1) * 2})
    64  		w.AddEntity(o)
    65  	}
    66  
    67  	h.setItemInSlot(protocol.StackRequestSlotInfo{
    68  		ContainerID: protocol.ContainerGrindstoneInput,
    69  		Slot:        grindstoneFirstInputSlot,
    70  	}, item.Stack{}, s)
    71  	h.setItemInSlot(protocol.StackRequestSlotInfo{
    72  		ContainerID: protocol.ContainerGrindstoneAdditional,
    73  		Slot:        grindstoneSecondInputSlot,
    74  	}, item.Stack{}, s)
    75  	return h.createResults(s, stripPossibleEnchantments(resultStack))
    76  }
    77  
    78  // curseEnchantment represents an enchantment that may be a curse enchantment.
    79  type curseEnchantment interface {
    80  	Curse() bool
    81  }
    82  
    83  // experienceFromEnchantments returns the amount of experience that is gained from the enchantments on the given stack.
    84  func experienceFromEnchantments(stack item.Stack) int {
    85  	var totalCost int
    86  	for _, enchant := range stack.Enchantments() {
    87  		if _, ok := enchant.Type().(curseEnchantment); ok {
    88  			continue
    89  		}
    90  		cost, _ := enchant.Type().Cost(enchant.Level())
    91  		totalCost += cost
    92  	}
    93  	if totalCost == 0 {
    94  		// No cost, no experience.
    95  		return 0
    96  	}
    97  
    98  	minExperience := int(math.Ceil(float64(totalCost) / 2))
    99  	return minExperience + rand.Intn(minExperience)
   100  }
   101  
   102  // stripPossibleEnchantments strips all enchantments possible, excluding curses.
   103  func stripPossibleEnchantments(stack item.Stack) item.Stack {
   104  	for _, enchant := range stack.Enchantments() {
   105  		if _, ok := enchant.Type().(curseEnchantment); ok {
   106  			continue
   107  		}
   108  		stack = stack.WithoutEnchantments(enchant.Type())
   109  	}
   110  	return stack
   111  }
   112  
   113  // nonZeroItem returns the item.Stack that exists out of two input items. The function expects at least one of the
   114  // items to be non-empty.
   115  func nonZeroItem(first, second item.Stack) item.Stack {
   116  	if first.Empty() {
   117  		return second
   118  	}
   119  	return first
   120  }