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

     1  package session
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/df-mc/dragonfly/server/item"
     6  	"github.com/df-mc/dragonfly/server/item/creative"
     7  	"github.com/df-mc/dragonfly/server/item/inventory"
     8  	"github.com/df-mc/dragonfly/server/item/recipe"
     9  	"github.com/df-mc/dragonfly/server/world"
    10  	"github.com/sandertv/gophertunnel/minecraft/protocol"
    11  	"math"
    12  	"slices"
    13  )
    14  
    15  // handleCraft handles the CraftRecipe request action.
    16  func (h *ItemStackRequestHandler) handleCraft(a *protocol.CraftRecipeStackRequestAction, s *Session) error {
    17  	craft, ok := s.recipes[a.RecipeNetworkID]
    18  	if !ok {
    19  		return fmt.Errorf("recipe with network id %v does not exist", a.RecipeNetworkID)
    20  	}
    21  	_, shaped := craft.(recipe.Shaped)
    22  	_, shapeless := craft.(recipe.Shapeless)
    23  	if !shaped && !shapeless {
    24  		return fmt.Errorf("recipe with network id %v is not a shaped or shapeless recipe", a.RecipeNetworkID)
    25  	}
    26  	if craft.Block() != "crafting_table" {
    27  		return fmt.Errorf("recipe with network id %v is not a crafting table recipe", a.RecipeNetworkID)
    28  	}
    29  
    30  	size := s.craftingSize()
    31  	offset := s.craftingOffset()
    32  	consumed := make([]bool, size)
    33  	for _, expected := range craft.Input() {
    34  		var processed bool
    35  		for slot := offset; slot < offset+size; slot++ {
    36  			if consumed[slot-offset] {
    37  				// We've already consumed this slot, skip it.
    38  				continue
    39  			}
    40  			has, _ := s.ui.Item(int(slot))
    41  			if has.Empty() != expected.Empty() || has.Count() < expected.Count() {
    42  				// We can't process this item, as it's not a part of the recipe.
    43  				continue
    44  			}
    45  			if !matchingStacks(has, expected) {
    46  				// Not the same item.
    47  				continue
    48  			}
    49  			processed, consumed[slot-offset] = true, true
    50  			st := has.Grow(-expected.Count())
    51  			h.setItemInSlot(protocol.StackRequestSlotInfo{
    52  				ContainerID: protocol.ContainerCraftingInput,
    53  				Slot:        byte(slot),
    54  			}, st, s)
    55  			break
    56  		}
    57  		if !processed {
    58  			return fmt.Errorf("recipe %v: could not consume expected item: %v", a.RecipeNetworkID, expected)
    59  		}
    60  	}
    61  	return h.createResults(s, craft.Output()...)
    62  }
    63  
    64  // handleAutoCraft handles the AutoCraftRecipe request action.
    65  func (h *ItemStackRequestHandler) handleAutoCraft(a *protocol.AutoCraftRecipeStackRequestAction, s *Session) error {
    66  	craft, ok := s.recipes[a.RecipeNetworkID]
    67  	if !ok {
    68  		return fmt.Errorf("recipe with network id %v does not exist", a.RecipeNetworkID)
    69  	}
    70  	_, shaped := craft.(recipe.Shaped)
    71  	_, shapeless := craft.(recipe.Shapeless)
    72  	if !shaped && !shapeless {
    73  		return fmt.Errorf("recipe with network id %v is not a shaped or shapeless recipe", a.RecipeNetworkID)
    74  	}
    75  	if craft.Block() != "crafting_table" {
    76  		return fmt.Errorf("recipe with network id %v is not a crafting table recipe", a.RecipeNetworkID)
    77  	}
    78  
    79  	repetitions := int(a.TimesCrafted)
    80  	input := make([]item.Stack, 0, len(craft.Input()))
    81  	for _, i := range craft.Input() {
    82  		input = append(input, i.Grow(i.Count()*(repetitions-1)))
    83  	}
    84  
    85  	flattenedInputs := make([]item.Stack, 0, len(input))
    86  	for _, i := range input {
    87  		if i.Empty() {
    88  			// We don't actually need this item - it's empty, so avoid putting it in our flattened inputs.
    89  			continue
    90  		}
    91  
    92  		if ind := slices.IndexFunc(flattenedInputs, func(st item.Stack) bool {
    93  			return matchingStacks(st, i)
    94  		}); ind >= 0 {
    95  			i = i.Grow(flattenedInputs[ind].Count())
    96  			flattenedInputs = slices.Delete(flattenedInputs, ind, ind+1)
    97  		}
    98  		flattenedInputs = append(flattenedInputs, i)
    99  	}
   100  
   101  	for _, expected := range flattenedInputs {
   102  		for id, inv := range map[byte]*inventory.Inventory{
   103  			protocol.ContainerCraftingInput:              s.ui,
   104  			protocol.ContainerCombinedHotBarAndInventory: s.inv,
   105  		} {
   106  			for slot, has := range inv.Slots() {
   107  				if has.Empty() {
   108  					// We don't have this item, skip it.
   109  					continue
   110  				}
   111  				if !matchingStacks(has, expected) {
   112  					// Not the same item.
   113  					continue
   114  				}
   115  
   116  				remaining, removal := expected.Count(), has.Count()
   117  				if remaining < removal {
   118  					removal = remaining
   119  				}
   120  
   121  				expected, has = expected.Grow(-removal), has.Grow(-removal)
   122  				h.setItemInSlot(protocol.StackRequestSlotInfo{
   123  					ContainerID: id,
   124  					Slot:        byte(slot),
   125  				}, has, s)
   126  				if expected.Empty() {
   127  					// Consumed this item, so go to the next one.
   128  					break
   129  				}
   130  			}
   131  			if expected.Empty() {
   132  				// Consumed this item, so go to the next one.
   133  				break
   134  			}
   135  		}
   136  		if !expected.Empty() {
   137  			return fmt.Errorf("recipe %v: could not consume expected item: %v", a.RecipeNetworkID, expected)
   138  		}
   139  	}
   140  
   141  	output := make([]item.Stack, 0, len(craft.Output()))
   142  	for _, o := range craft.Output() {
   143  		count, maxCount := o.Count(), o.MaxCount()
   144  		total := count * repetitions
   145  
   146  		stacks := int(math.Ceil(float64(total) / float64(maxCount)))
   147  		for i := 0; i < stacks; i++ {
   148  			inc := min(total, maxCount)
   149  			total -= inc
   150  
   151  			output = append(output, o.Grow(inc-count))
   152  		}
   153  	}
   154  	return h.createResults(s, output...)
   155  }
   156  
   157  // handleCreativeCraft handles the CreativeCraft request action.
   158  func (h *ItemStackRequestHandler) handleCreativeCraft(a *protocol.CraftCreativeStackRequestAction, s *Session) error {
   159  	if !s.c.GameMode().CreativeInventory() {
   160  		return fmt.Errorf("can only craft creative items in gamemode creative/spectator")
   161  	}
   162  	index := a.CreativeItemNetworkID - 1
   163  	if int(index) >= len(creative.Items()) {
   164  		return fmt.Errorf("creative item with network ID %v does not exist", index)
   165  	}
   166  	it := creative.Items()[index]
   167  	it = it.Grow(it.MaxCount() - 1)
   168  	return h.createResults(s, it)
   169  }
   170  
   171  // craftingSize gets the crafting size based on the opened container ID.
   172  func (s *Session) craftingSize() uint32 {
   173  	if s.openedContainerID.Load() == 1 {
   174  		return craftingGridSizeLarge
   175  	}
   176  	return craftingGridSizeSmall
   177  }
   178  
   179  // craftingOffset gets the crafting offset based on the opened container ID.
   180  func (s *Session) craftingOffset() uint32 {
   181  	if s.openedContainerID.Load() == 1 {
   182  		return craftingGridLargeOffset
   183  	}
   184  	return craftingGridSmallOffset
   185  }
   186  
   187  // duplicateStack duplicates an item.Stack with the new item type given.
   188  func duplicateStack(input item.Stack, newType world.Item) item.Stack {
   189  	outputStack := item.NewStack(newType, input.Count()).
   190  		Damage(input.MaxDurability() - input.Durability()).
   191  		WithCustomName(input.CustomName()).
   192  		WithLore(input.Lore()...).
   193  		WithEnchantments(input.Enchantments()...).
   194  		WithAnvilCost(input.AnvilCost())
   195  	for k, v := range input.Values() {
   196  		outputStack = outputStack.WithValue(k, v)
   197  	}
   198  	return outputStack
   199  }
   200  
   201  // matchingStacks returns true if the two stacks are the same in a crafting scenario.
   202  func matchingStacks(has, expected item.Stack) bool {
   203  	_, variants := expected.Value("variants")
   204  	if !variants {
   205  		return has.Comparable(expected)
   206  	}
   207  	nameOne, _ := has.Item().EncodeItem()
   208  	nameTwo, _ := expected.Item().EncodeItem()
   209  	return nameOne == nameTwo
   210  }