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 }