Cuberite
A lightweight, fast and extensible game server for Minecraft
CraftingRecipes.cpp
Go to the documentation of this file.
1 
2 // CraftingRecipes.cpp
3 
4 // Interfaces to the cCraftingRecipes class representing the storage of crafting recipes
5 
6 #include "Globals.h"
7 #include "CraftingRecipes.h"
8 #include "Root.h"
10 
11 
12 
13 
14 
16 // cCraftingGrid:
17 
18 cCraftingGrid::cCraftingGrid(int a_Width, int a_Height) :
19  m_Width(a_Width),
20  m_Height(a_Height),
21  m_Items(new cItem[ToUnsigned(a_Width * a_Height)])
22 {
23 }
24 
25 
26 
27 
28 
29 cCraftingGrid::cCraftingGrid(const cItem * a_Items, int a_Width, int a_Height) :
30  cCraftingGrid(a_Width, a_Height)
31 {
32  for (int i = a_Width * a_Height - 1; i >= 0; i--)
33  {
34  m_Items[i] = a_Items[i];
35  }
36 }
37 
38 
39 
40 
41 
43  cCraftingGrid(a_Original.m_Width, a_Original.m_Height)
44 {
45  for (int i = m_Width * m_Height - 1; i >= 0; i--)
46  {
47  m_Items[i] = a_Original.m_Items[i];
48  }
49 }
50 
51 
52 
53 
54 
56 {
57  delete[] m_Items;
58  m_Items = nullptr;
59 }
60 
61 
62 
63 
64 
65 cItem & cCraftingGrid::GetItem(int x, int y) const
66 {
67  // Accessible through scripting, must verify parameters:
68  if ((x < 0) || (x >= m_Width) || (y < 0) || (y >= m_Height))
69  {
70  LOGERROR("Attempted to get an invalid item from a crafting grid: (%d, %d), grid dimensions: (%d, %d).",
71  x, y, m_Width, m_Height
72  );
73  return m_Items[0];
74  }
75  return m_Items[x + m_Width * y];
76 }
77 
78 
79 
80 
81 
82 void cCraftingGrid::SetItem(int x, int y, ENUM_ITEM_TYPE a_ItemType, char a_ItemCount, short a_ItemHealth)
83 {
84  // Accessible through scripting, must verify parameters:
85  if ((x < 0) || (x >= m_Width) || (y < 0) || (y >= m_Height))
86  {
87  LOGERROR("Attempted to set an invalid item in a crafting grid: (%d, %d), grid dimensions: (%d, %d).",
88  x, y, m_Width, m_Height
89  );
90  return;
91  }
92 
93  m_Items[x + m_Width * y] = cItem(a_ItemType, a_ItemCount, a_ItemHealth);
94 }
95 
96 
97 
98 
99 
100 void cCraftingGrid::SetItem(int x, int y, const cItem & a_Item)
101 {
102  // Accessible through scripting, must verify parameters:
103  if ((x < 0) || (x >= m_Width) || (y < 0) || (y >= m_Height))
104  {
105  LOGERROR("Attempted to set an invalid item in a crafting grid: (%d, %d), grid dimensions: (%d, %d).",
106  x, y, m_Width, m_Height
107  );
108  return;
109  }
110 
111  m_Items[x + m_Width * y] = a_Item;
112 }
113 
114 
115 
116 
117 
119 {
120  for (int y = 0; y < m_Height; y++) for (int x = 0; x < m_Width; x++)
121  {
122  m_Items[x + m_Width * y].Empty();
123  }
124 }
125 
126 
127 
128 
129 
131 {
132  if ((a_Grid.m_Width != m_Width) || (a_Grid.m_Height != m_Height))
133  {
134  LOGWARNING("Consuming a grid of different dimensions: (%d, %d) vs (%d, %d)",
135  a_Grid.m_Width, a_Grid.m_Height, m_Width, m_Height
136  );
137  }
138  int MinX = std::min(a_Grid.m_Width, m_Width);
139  int MinY = std::min(a_Grid.m_Height, m_Height);
140  for (int y = 0; y < MinY; y++) for (int x = 0; x < MinX; x++)
141  {
142  int ThatIdx = x + a_Grid.m_Width * y;
143  if (a_Grid.m_Items[ThatIdx].IsEmpty())
144  {
145  continue;
146  }
147  int ThisIdx = x + m_Width * y;
148  if (a_Grid.m_Items[ThatIdx].m_ItemType != m_Items[ThisIdx].m_ItemType)
149  {
150  LOGWARNING("Consuming incompatible grids: item at (%d, %d) is %d in grid and %d in ingredients. Item not consumed.",
151  x, y, m_Items[ThisIdx].m_ItemType, a_Grid.m_Items[ThatIdx].m_ItemType
152  );
153  continue;
154  }
155  char NumWantedItems = a_Grid.m_Items[ThatIdx].m_ItemCount;
156  if (NumWantedItems > m_Items[ThisIdx].m_ItemCount)
157  {
158  LOGWARNING("Consuming more items than there actually are in slot (%d, %d), item %d (want %d, have %d). Item zeroed out.",
159  x, y, m_Items[ThisIdx].m_ItemType,
160  NumWantedItems, m_Items[ThisIdx].m_ItemCount
161  );
162  NumWantedItems = m_Items[ThisIdx].m_ItemCount;
163  }
164  m_Items[ThisIdx].m_ItemCount -= NumWantedItems;
165  if (m_Items[ThisIdx].m_ItemCount == 0)
166  {
167  if ((m_Items[ThisIdx].m_ItemType == E_ITEM_MILK) || (m_Items[ThisIdx].m_ItemType == E_ITEM_WATER_BUCKET) || (m_Items[ThisIdx].m_ItemType == E_ITEM_LAVA_BUCKET))
168  {
169  m_Items[ThisIdx] = cItem(E_ITEM_BUCKET);
170  }
171  else
172  {
173  m_Items[ThisIdx].Clear();
174  }
175  }
176  } // for x, for y
177 }
178 
179 
180 
181 
182 
183 void cCraftingGrid::CopyToItems(cItem * a_Items) const
184 {
185  for (int i = m_Height * m_Width - 1; i >= 0; i--)
186  {
187  a_Items[i] = m_Items[i];
188  } // for x, for y
189 }
190 
191 
192 
193 
194 
196 {
197  for (int y = 0; y < m_Height; y++) for (int x = 0; x < m_Width; x++)
198  {
199  [[maybe_unused]] const int idx = x + m_Width * y;
200  LOGD("Slot (%d, %d): Type %d, health %d, count %d",
201  x, y, m_Items[idx].m_ItemType, m_Items[idx].m_ItemDamage, m_Items[idx].m_ItemCount
202  );
203  }
204 }
205 
206 
207 
208 
209 
211 // cCraftingRecipe:
212 
214  m_Ingredients(a_CraftingGrid)
215 {
216 }
217 
218 
219 
220 
221 
223 {
225  m_Result.Clear();
226 }
227 
228 
229 
230 
231 
232 void cCraftingRecipe::SetResult(ENUM_ITEM_TYPE a_ItemType, char a_ItemCount, short a_ItemHealth)
233 {
234  m_Result = cItem(a_ItemType, a_ItemCount, a_ItemHealth);
235 }
236 
237 
238 
239 
240 
242 {
243  a_CraftingGrid.ConsumeGrid(m_Ingredients);
244 }
245 
246 
247 
248 
249 
251 {
252  LOGD("Recipe ingredients:");
254  LOGD("Result: Type %d, health %d, count %d",
256  );
257 }
258 
259 
260 
261 
262 
264 // cCraftingRecipes:
265 
267 {
268  LoadRecipes();
270 }
271 
272 
273 
274 
275 
277 {
278  ClearRecipes();
279 }
280 
281 
282 
283 
284 
285 bool cCraftingRecipes::IsNewCraftableRecipe(const cRecipe * a_Recipe, const cItem & a_Item, const std::set<cItem, cItem::sItemCompare> & a_KnownItems)
286 {
287  bool ContainsNewItem = false;
288  for (const auto & Ingredient : a_Recipe->m_Ingredients)
289  {
290  if (
291  (Ingredient.m_Item.m_ItemType == a_Item.m_ItemType) &&
292  (
293  (Ingredient.m_Item.m_ItemDamage == a_Item.m_ItemDamage) ||
294  (Ingredient.m_Item.m_ItemDamage == -1)
295  )
296  )
297  {
298  ContainsNewItem = true;
299  }
300  if (a_KnownItems.find(Ingredient.m_Item) == a_KnownItems.end())
301  {
302  return false;
303  }
304  }
305  return ContainsNewItem;
306 }
307 
308 
309 
310 
311 
312 std::vector<UInt32> cCraftingRecipes::FindNewRecipesForItem(const cItem & a_Item, const std::set<cItem, cItem::sItemCompare> & a_KnownItems)
313 {
314  std::vector<UInt32> Recipes;
315  for (UInt32 i = 0; i < m_Recipes.size(); i++)
316  {
317  if (m_Recipes[i]->m_RecipeName.empty())
318  {
319  continue;
320  }
321  if (IsNewCraftableRecipe(m_Recipes[i], a_Item, a_KnownItems))
322  {
323  Recipes.push_back(i);
324  }
325  }
326  return Recipes;
327 }
328 
329 
330 
331 
332 
333 const std::map<AString, UInt32> & cCraftingRecipes::GetRecipeNameMap()
334 {
335  return m_RecipeNameMap;
336 }
337 
338 
339 
340 
341 
343 {
344  return m_Recipes[a_RecipeId];
345 }
346 
347 
348 
349 
350 
351 void cCraftingRecipes::GetRecipe(cPlayer & a_Player, cCraftingGrid & a_CraftingGrid, cCraftingRecipe & a_Recipe)
352 {
353  // Allow plugins to intercept recipes using a pre-craft hook:
354  if (cRoot::Get()->GetPluginManager()->CallHookPreCrafting(a_Player, a_CraftingGrid, a_Recipe))
355  {
356  return;
357  }
358 
359  // Built-in recipes:
360  std::unique_ptr<cRecipe> Recipe(FindRecipe(a_CraftingGrid.GetItems(), a_CraftingGrid.GetWidth(), a_CraftingGrid.GetHeight()));
361  a_Recipe.Clear();
362  if (Recipe.get() == nullptr)
363  {
364  // Allow plugins to intercept a no-recipe-found situation:
365  cRoot::Get()->GetPluginManager()->CallHookCraftingNoRecipe(a_Player, a_CraftingGrid, a_Recipe);
366  return;
367  }
368  for (cRecipeSlots::const_iterator itr = Recipe->m_Ingredients.begin(); itr != Recipe->m_Ingredients.end(); ++itr)
369  {
370  a_Recipe.SetIngredient(itr->x, itr->y, itr->m_Item);
371  } // for itr
372  a_Recipe.SetResult(Recipe->m_Result);
373 
374  // Allow plugins to intercept recipes after they are processed:
375  cRoot::Get()->GetPluginManager()->CallHookPostCrafting(a_Player, a_CraftingGrid, a_Recipe);
376 }
377 
378 
379 
380 
381 
383 {
384  LOGD("Loading crafting recipes from crafting.txt...");
385  ClearRecipes();
386 
387  // Load the crafting.txt file:
388  cFile f;
389  if (!f.Open("crafting.txt", cFile::fmRead))
390  {
391  LOGWARNING("Cannot open file \"crafting.txt\", no crafting recipes will be available!");
392  return;
393  }
394  AString Everything;
395  if (!f.ReadRestOfFile(Everything))
396  {
397  LOGWARNING("Cannot read file \"crafting.txt\", no crafting recipes will be available!");
398  return;
399  }
400  f.Close();
401 
402  // Split it into lines, then process each line as a single recipe:
403  AStringVector Split = StringSplit(Everything, "\n");
404  int LineNum = 1;
405  for (AStringVector::const_iterator itr = Split.begin(); itr != Split.end(); ++itr, ++LineNum)
406  {
407  // Remove anything after a '#' sign and trim away the whitespace:
408  AString Recipe = TrimString(itr->substr(0, itr->find('#')));
409  if (Recipe.empty())
410  {
411  // Empty recipe
412  continue;
413  }
414  AddRecipeLine(LineNum, Recipe);
415  } // for itr - Split[]
416  LOG("Loaded %zu crafting recipes", m_Recipes.size());
417 }
418 
419 
420 
421 
422 
424 {
425  for (UInt32 i = 0; i < m_Recipes.size(); i++)
426  {
427  if (!m_Recipes[i]->m_RecipeName.empty())
428  {
429  m_RecipeNameMap.emplace(m_Recipes[i]->m_RecipeName, i);
430  }
431  }
432 }
433 
434 
435 
436 
437 
439 {
440  for (cRecipes::iterator itr = m_Recipes.begin(); itr != m_Recipes.end(); ++itr)
441  {
442  delete *itr;
443  }
444  m_Recipes.clear();
445 }
446 
447 
448 
449 
450 
451 void cCraftingRecipes::AddRecipeLine(int a_LineNum, const AString & a_RecipeLine)
452 {
453  // Remove any spaces within the line:
454  AString RecipeLine(a_RecipeLine);
455  RecipeLine.erase(std::remove_if(RecipeLine.begin(), RecipeLine.end(), isspace), RecipeLine.end());
456 
457  AStringVector Sides = StringSplit(RecipeLine, "=");
458  if (Sides.size() != 2)
459  {
460  LOGWARNING("crafting.txt: line %d: A single '=' was expected, got %zu", a_LineNum, Sides.size() - 1);
461  LOGINFO("Offending line: \"%s\"", a_RecipeLine.c_str());
462  return;
463  }
464 
465  std::unique_ptr<cCraftingRecipes::cRecipe> Recipe = std::make_unique<cCraftingRecipes::cRecipe>();
466 
467  AStringVector RecipeSplit = StringSplit(Sides[0], ":");
468  const auto * resultPart = &RecipeSplit[0];
469  if (RecipeSplit.size() > 1)
470  {
471  resultPart = &RecipeSplit[1];
472  Recipe->m_RecipeName = RecipeSplit[0];
473  }
474  // Parse the result:
475  AStringVector ResultSplit = StringSplit(*resultPart, ",");
476  if (ResultSplit.empty())
477  {
478  LOGWARNING("crafting.txt: line %d: Result is empty, ignoring the recipe.", a_LineNum);
479  LOGINFO("Offending line: \"%s\"", a_RecipeLine.c_str());
480  return;
481  }
482  if (!ParseItem(ResultSplit[0], Recipe->m_Result))
483  {
484  LOGWARNING("crafting.txt: line %d: Cannot parse result item, ignoring the recipe.", a_LineNum);
485  LOGINFO("Offending line: \"%s\"", a_RecipeLine.c_str());
486  return;
487  }
488  if (ResultSplit.size() > 1)
489  {
490  if (!StringToInteger<char>(ResultSplit[1], Recipe->m_Result.m_ItemCount))
491  {
492  LOGWARNING("crafting.txt: line %d: Cannot parse result count, ignoring the recipe.", a_LineNum);
493  LOGINFO("Offending line: \"%s\"", a_RecipeLine.c_str());
494  return;
495  }
496  }
497  else
498  {
499  Recipe->m_Result.m_ItemCount = 1;
500  }
501 
502  // Parse each ingredient:
503  AStringVector Ingredients = StringSplit(Sides[1], "|");
504  int Num = 1;
505  for (AStringVector::const_iterator itr = Ingredients.begin(); itr != Ingredients.end(); ++itr, ++Num)
506  {
507  if (!ParseIngredient(*itr, Recipe.get()))
508  {
509  LOGWARNING("crafting.txt: line %d: Cannot parse ingredient #%d, ignoring the recipe.", a_LineNum, Num);
510  LOGINFO("Offending line: \"%s\"", a_RecipeLine.c_str());
511  return;
512  }
513  } // for itr - Ingredients[]
514 
515  NormalizeIngredients(Recipe.get());
516 
517  m_Recipes.push_back(Recipe.release());
518 }
519 
520 
521 
522 
523 
524 bool cCraftingRecipes::ParseItem(const AString & a_String, cItem & a_Item)
525 {
526  // The caller provides error logging
527 
528  AStringVector Split = StringSplit(a_String, "^");
529  if (Split.empty())
530  {
531  return false;
532  }
533 
534  if (!StringToItem(Split[0], a_Item))
535  {
536  return false;
537  }
538 
539  if (Split.size() > 1)
540  {
541  AString Damage = TrimString(Split[1]);
542  if (!StringToInteger<short>(Damage, a_Item.m_ItemDamage))
543  {
544  // Parsing the number failed
545  return false;
546  }
547  }
548 
549  // Success
550  return true;
551 }
552 
553 
554 
555 
556 
557 bool cCraftingRecipes::ParseIngredient(const AString & a_String, cRecipe * a_Recipe)
558 {
559  // a_String is in this format: "ItemType^damage, X:Y, X:Y, X:Y..."
560  AStringVector Split = StringSplit(a_String, ",");
561  if (Split.size() < 2)
562  {
563  // Not enough split items
564  return false;
565  }
566  cItem Item;
567  if (!ParseItem(Split[0], Item))
568  {
569  return false;
570  }
571  Item.m_ItemCount = 1;
572 
574  for (AStringVector::const_iterator itr = Split.begin() + 1; itr != Split.end(); ++itr)
575  {
576  // Parse the coords in the split item:
577  AStringVector Coords = StringSplit(*itr, ":");
578  if ((Coords.size() == 1) && (TrimString(Coords[0]) == "*"))
579  {
581  Slot.m_Item = Item;
582  Slot.x = -1;
583  Slot.y = -1;
584  TempSlots.push_back(Slot);
585  continue;
586  }
587  if (Coords.size() != 2)
588  {
589  return false;
590  }
591  Coords[0] = TrimString(Coords[0]);
592  Coords[1] = TrimString(Coords[1]);
593  if (Coords[0].empty() || Coords[1].empty())
594  {
595  return false;
596  }
598  Slot.m_Item = Item;
599  switch (Coords[0][0])
600  {
601  case '1': Slot.x = 0; break;
602  case '2': Slot.x = 1; break;
603  case '3': Slot.x = 2; break;
604  case '*': Slot.x = -1; break;
605  default:
606  {
607  return false;
608  }
609  }
610  switch (Coords[1][0])
611  {
612  case '1': Slot.y = 0; break;
613  case '2': Slot.y = 1; break;
614  case '3': Slot.y = 2; break;
615  case '*': Slot.y = -1; break;
616  default:
617  {
618  return false;
619  }
620  }
621  TempSlots.push_back(Slot);
622  } // for itr - Split[]
623 
624  // Append the ingredients:
625  a_Recipe->m_Ingredients.insert(a_Recipe->m_Ingredients.end(), TempSlots.begin(), TempSlots.end());
626  return true;
627 }
628 
629 
630 
631 
632 
634 {
635  // Calculate the minimum coords for ingredients, excluding the "anywhere" items:
636  int MinX = MAX_GRID_WIDTH, MaxX = 0;
637  int MinY = MAX_GRID_HEIGHT, MaxY = 0;
638  for (cRecipeSlots::const_iterator itr = a_Recipe->m_Ingredients.begin(); itr != a_Recipe->m_Ingredients.end(); ++itr)
639  {
640  if (itr->x >= 0)
641  {
642  MinX = std::min(itr->x, MinX);
643  MaxX = std::max(itr->x, MaxX);
644  }
645  if (itr->y >= 0)
646  {
647  MinY = std::min(itr->y, MinY);
648  MaxY = std::max(itr->y, MaxY);
649  }
650  } // for itr - a_Recipe->m_Ingredients[]
651 
652  // Move ingredients so that the minimum coords are 0:0
653  for (cRecipeSlots::iterator itr = a_Recipe->m_Ingredients.begin(); itr != a_Recipe->m_Ingredients.end(); ++itr)
654  {
655  if (itr->x >= 0)
656  {
657  itr->x -= MinX;
658  }
659  if (itr->y >= 0)
660  {
661  itr->y -= MinY;
662  }
663  } // for itr - a_Recipe->m_Ingredients[]
664  a_Recipe->m_Width = std::max(MaxX - MinX + 1, 1);
665  a_Recipe->m_Height = std::max(MaxY - MinY + 1, 1);
666 
667  // TODO: Compress two same ingredients with the same coords into a single ingredient with increased item count
668 }
669 
670 
671 
672 
673 
674 cCraftingRecipes::cRecipe * cCraftingRecipes::FindRecipe(const cItem * a_CraftingGrid, int a_GridWidth, int a_GridHeight)
675 {
676  ASSERT(a_GridWidth <= MAX_GRID_WIDTH);
677  ASSERT(a_GridHeight <= MAX_GRID_HEIGHT);
678 
679  // Get the real bounds of the crafting grid:
680  int GridLeft = MAX_GRID_WIDTH, GridTop = MAX_GRID_HEIGHT;
681  int GridRight = 0, GridBottom = 0;
682  for (int y = 0; y < a_GridHeight; y++) for (int x = 0; x < a_GridWidth; x++)
683  {
684  if (!a_CraftingGrid[x + y * a_GridWidth].IsEmpty())
685  {
686  GridRight = std::max(x, GridRight);
687  GridBottom = std::max(y, GridBottom);
688  GridLeft = std::min(x, GridLeft);
689  GridTop = std::min(y, GridTop);
690  }
691  }
692  int GridWidth = GridRight - GridLeft + 1;
693  int GridHeight = GridBottom - GridTop + 1;
694 
695  // Search in the possibly minimized grid, but keep the stride:
696  const cItem * Grid = a_CraftingGrid + GridLeft + (a_GridWidth * GridTop);
697  cRecipe * Recipe = FindRecipeCropped(Grid, GridWidth, GridHeight, a_GridWidth);
698  if (Recipe == nullptr)
699  {
700  return nullptr;
701  }
702 
703  // A recipe has been found, move it to correspond to the original crafting grid:
704  for (cRecipeSlots::iterator itrS = Recipe->m_Ingredients.begin(); itrS != Recipe->m_Ingredients.end(); ++itrS)
705  {
706  itrS->x += GridLeft;
707  itrS->y += GridTop;
708  } // for itrS - Recipe->m_Ingredients[]
709 
710  return Recipe;
711 }
712 
713 
714 
715 
716 
717 cCraftingRecipes::cRecipe * cCraftingRecipes::FindRecipeCropped(const cItem * a_CraftingGrid, int a_GridWidth, int a_GridHeight, int a_GridStride)
718 {
719  for (cRecipes::const_iterator itr = m_Recipes.begin(); itr != m_Recipes.end(); ++itr)
720  {
721  // Both the crafting grid and the recipes are normalized. The only variable possible is the "anywhere" items.
722  // This still means that the "anywhere" item may be the one that is offsetting the grid contents to the right or downwards, so we need to check all possible positions.
723  // E. g. recipe "A, * | B, 1:1 | ..." still needs to check grid for B at 2:2 (in case A was in grid's 1:1)
724  // Calculate the maximum offsets for this recipe relative to the grid size, and iterate through all combinations of offsets.
725  // Also, this calculation automatically filters out recipes that are too large for the current grid - the loop won't be entered at all.
726 
727  int MaxOfsX = a_GridWidth - (*itr)->m_Width;
728  int MaxOfsY = a_GridHeight - (*itr)->m_Height;
729  for (int x = 0; x <= MaxOfsX; x++) for (int y = 0; y <= MaxOfsY; y++)
730  {
731  cRecipe * Recipe = MatchRecipe(a_CraftingGrid, a_GridWidth, a_GridHeight, a_GridStride, *itr, x, y);
732  if (Recipe != nullptr)
733  {
734  return Recipe;
735  }
736  } // for y, for x
737  } // for itr - m_Recipes[]
738 
739  // No matching recipe found
740  return nullptr;
741 }
742 
743 
744 
745 
746 
747 cCraftingRecipes::cRecipe * cCraftingRecipes::MatchRecipe(const cItem * a_CraftingGrid, int a_GridWidth, int a_GridHeight, int a_GridStride, const cRecipe * a_Recipe, int a_OffsetX, int a_OffsetY)
748 {
749  // Check the regular items first:
750  bool HasMatched[MAX_GRID_WIDTH][MAX_GRID_HEIGHT];
751  memset(HasMatched, 0, sizeof(HasMatched));
752  for (cRecipeSlots::const_iterator itrS = a_Recipe->m_Ingredients.begin(); itrS != a_Recipe->m_Ingredients.end(); ++itrS)
753  {
754  if ((itrS->x < 0) || (itrS->y < 0))
755  {
756  // "Anywhere" item, process later
757  continue;
758  }
759  ASSERT(itrS->x + a_OffsetX < a_GridWidth);
760  ASSERT(itrS->y + a_OffsetY < a_GridHeight);
761  int GridID = (itrS->x + a_OffsetX) + a_GridStride * (itrS->y + a_OffsetY);
762 
763  const cItem & Item = itrS->m_Item;
764  if (
765  (itrS->x >= a_GridWidth) ||
766  (itrS->y >= a_GridHeight) ||
767  (Item.m_ItemType != a_CraftingGrid[GridID].m_ItemType) || // same item type?
768  (Item.m_ItemCount > a_CraftingGrid[GridID].m_ItemCount) || // not enough items
769  (
770  (Item.m_ItemDamage >= 0) && // should compare damage values?
771  (Item.m_ItemDamage != a_CraftingGrid[GridID].m_ItemDamage)
772  )
773  )
774  {
775  // Doesn't match
776  return nullptr;
777  }
778  HasMatched[itrS->x + a_OffsetX][itrS->y + a_OffsetY] = true;
779  } // for itrS - Recipe->m_Ingredients[]
780 
781  // Process the "Anywhere" items now, and only in the cells that haven't matched yet
782  // The "anywhere" items are processed on a first-come-first-served basis.
783  // Do not use a recipe with one horizontal and one vertical "anywhere" ("*:1, 1:*") as it may not match properly!
784  cRecipeSlots MatchedSlots; // Stores the slots of "anywhere" items that have matched, with the match coords
785  for (cRecipeSlots::const_iterator itrS = a_Recipe->m_Ingredients.begin(); itrS != a_Recipe->m_Ingredients.end(); ++itrS)
786  {
787  if ((itrS->x >= 0) && (itrS->y >= 0))
788  {
789  // Regular item, already processed
790  continue;
791  }
792  int StartX = 0, EndX = a_GridWidth - 1;
793  int StartY = 0, EndY = a_GridHeight - 1;
794  if (itrS->x >= 0)
795  {
796  StartX = itrS->x;
797  EndX = itrS->x;
798  }
799  else if (itrS->y >= 0)
800  {
801  StartY = itrS->y;
802  EndY = itrS->y;
803  }
804  bool Found = false;
805  for (int x = StartX; x <= EndX; x++)
806  {
807  for (int y = StartY; y <= EndY; y++)
808  {
809  if (HasMatched[x][y])
810  {
811  // Already matched some other item
812  continue;
813  }
814  int GridIdx = x + a_GridStride * y;
815  if (
816  (a_CraftingGrid[GridIdx].m_ItemType == itrS->m_Item.m_ItemType) &&
817  (
818  (itrS->m_Item.m_ItemDamage < 0) || // doesn't want damage comparison
819  (itrS->m_Item.m_ItemDamage == a_CraftingGrid[GridIdx].m_ItemDamage) // the damage matches
820  )
821  )
822  {
823  HasMatched[x][y] = true;
824  Found = true;
825  MatchedSlots.push_back(*itrS);
826  MatchedSlots.back().x = x;
827  MatchedSlots.back().y = y;
828  break;
829  }
830  } // for y
831  if (Found)
832  {
833  break;
834  }
835  } // for x
836  if (!Found)
837  {
838  return nullptr;
839  }
840  } // for itrS - a_Recipe->m_Ingredients[]
841 
842  // Check if the whole grid has matched:
843  for (int x = 0; x < a_GridWidth; x++) for (int y = 0; y < a_GridHeight; y++)
844  {
845  if (!HasMatched[x][y] && !a_CraftingGrid[x + a_GridStride * y].IsEmpty())
846  {
847  // There's an unmatched item in the grid
848  return nullptr;
849  }
850  } // for y, for x
851 
852  // The recipe has matched. Create a copy of the recipe and set its coords to match the crafting grid:
853  std::unique_ptr<cRecipe> Recipe = std::make_unique<cRecipe>();
854  Recipe->m_Result = a_Recipe->m_Result;
855  Recipe->m_Width = a_Recipe->m_Width;
856  Recipe->m_Height = a_Recipe->m_Height;
857  for (cRecipeSlots::const_iterator itrS = a_Recipe->m_Ingredients.begin(); itrS != a_Recipe->m_Ingredients.end(); ++itrS)
858  {
859  if ((itrS->x < 0) || (itrS->y < 0))
860  {
861  // "Anywhere" item, process later
862  continue;
863  }
864  Recipe->m_Ingredients.push_back(*itrS);
865  Recipe->m_Ingredients.back().x += a_OffsetX;
866  Recipe->m_Ingredients.back().y += a_OffsetY;
867  }
868  Recipe->m_Ingredients.insert(Recipe->m_Ingredients.end(), MatchedSlots.begin(), MatchedSlots.end());
869 
870  // Handle the fireworks-related effects:
871  // We use Recipe instead of a_Recipe because we want the wildcard ingredients' slot numbers as well, which was just added previously
872  HandleFireworks(a_CraftingGrid, Recipe.get(), a_GridStride, a_OffsetX, a_OffsetY);
873 
874  // Handle Dyed Leather
875  HandleDyedLeather(a_CraftingGrid, Recipe.get(), a_GridStride, a_GridWidth, a_GridHeight);
876 
877  return Recipe.release();
878 }
879 
880 
881 
882 
883 
884 void cCraftingRecipes::HandleFireworks(const cItem * a_CraftingGrid, cCraftingRecipes::cRecipe * a_Recipe, int a_GridStride, int a_OffsetX, int a_OffsetY)
885 {
886  // TODO: add support for more than one dye in the recipe
887  // A manual and temporary solution (listing everything) is in crafting.txt for fade colours, but a programmatic solutions needs to be done for everything else
888 
889  if (a_Recipe->m_Result.m_ItemType == E_ITEM_FIREWORK_ROCKET)
890  {
891  for (cRecipeSlots::const_iterator itr = a_Recipe->m_Ingredients.begin(); itr != a_Recipe->m_Ingredients.end(); ++itr)
892  {
893  switch (itr->m_Item.m_ItemType)
894  {
896  {
897  // Result was a rocket, found a star - copy star data to rocket data
898  int GridID = (itr->x + a_OffsetX) + a_GridStride * (itr->y + a_OffsetY);
899  a_Recipe->m_Result.m_FireworkItem.CopyFrom(a_CraftingGrid[GridID].m_FireworkItem);
900  break;
901  }
902  case E_ITEM_GUNPOWDER:
903  {
904  // Gunpowder - increase flight time
906  break;
907  }
908  case E_ITEM_PAPER: break;
909  default: LOG("Unexpected item in firework rocket recipe, was the crafting file's fireworks section changed?"); break;
910  }
911  }
912  }
913  else if (a_Recipe->m_Result.m_ItemType == E_ITEM_FIREWORK_STAR)
914  {
915  std::vector<int> DyeColours;
916  bool FoundStar = false;
917 
918  for (cRecipeSlots::const_iterator itr = a_Recipe->m_Ingredients.begin(); itr != a_Recipe->m_Ingredients.end(); ++itr)
919  {
920  switch (itr->m_Item.m_ItemType)
921  {
923  {
924  // Result was star, found another star - probably adding fade colours, but copy data over anyhow
925  FoundStar = true;
926  int GridID = (itr->x + a_OffsetX) + a_GridStride * (itr->y + a_OffsetY);
927  a_Recipe->m_Result.m_FireworkItem.CopyFrom(a_CraftingGrid[GridID].m_FireworkItem);
928  break;
929  }
930  case E_ITEM_DYE:
931  {
932  int GridID = (itr->x + a_OffsetX) + a_GridStride * (itr->y + a_OffsetY);
933  DyeColours.push_back(cFireworkItem::GetVanillaColourCodeFromDye(static_cast<NIBBLETYPE>(a_CraftingGrid[GridID].m_ItemDamage & 0x0f)));
934  break;
935  }
936  case E_ITEM_GUNPOWDER: break;
937  case E_ITEM_DIAMOND: a_Recipe->m_Result.m_FireworkItem.m_HasTrail = true; break;
938  case E_ITEM_GLOWSTONE_DUST: a_Recipe->m_Result.m_FireworkItem.m_HasFlicker = true; break;
939 
940  case E_ITEM_FIRE_CHARGE: a_Recipe->m_Result.m_FireworkItem.m_Type = 1; break;
941  case E_ITEM_GOLD_NUGGET: a_Recipe->m_Result.m_FireworkItem.m_Type = 2; break;
942  case E_ITEM_FEATHER: a_Recipe->m_Result.m_FireworkItem.m_Type = 4; break;
943  case E_ITEM_HEAD: a_Recipe->m_Result.m_FireworkItem.m_Type = 3; break;
944  default: LOG("Unexpected item in firework star recipe, was the crafting file's fireworks section changed?"); break; // ermahgerd BARD ardmins
945  }
946  }
947 
948  if (FoundStar && (!DyeColours.empty()))
949  {
950  // Found a star and a dye? Fade colours.
951  a_Recipe->m_Result.m_FireworkItem.m_FadeColours = DyeColours;
952  }
953  else if (!DyeColours.empty())
954  {
955  // Only dye? Normal colours.
956  a_Recipe->m_Result.m_FireworkItem.m_Colours = DyeColours;
957  }
958  }
959 }
960 
961 
962 
963 
964 
965 void cCraftingRecipes::HandleDyedLeather(const cItem * a_CraftingGrid, cCraftingRecipes::cRecipe * a_Recipe, int a_GridStride, int a_GridWidth, int a_GridHeight)
966 {
967  short result_type = a_Recipe->m_Result.m_ItemType;
968  if ((result_type == E_ITEM_LEATHER_CAP) || (result_type == E_ITEM_LEATHER_TUNIC) || (result_type == E_ITEM_LEATHER_PANTS) || (result_type == E_ITEM_LEATHER_BOOTS))
969  {
970  bool found = false;
971  cItem temp;
972 
973  float red = 0;
974  float green = 0;
975  float blue = 0;
976  float dye_count = 0;
977 
978  for (int x = 0; x < a_GridWidth; ++x)
979  {
980  for (int y = 0; y < a_GridHeight; ++y)
981  {
982  int GridIdx = x + a_GridStride * y;
983  if ((a_CraftingGrid[GridIdx].m_ItemType == result_type) && !found)
984  {
985  found = true;
986  temp = a_CraftingGrid[GridIdx].CopyOne();
987  // The original color of the item affects the result
988  if (temp.m_ItemColor.IsValid())
989  {
990  red += temp.m_ItemColor.GetRed();
991  green += temp.m_ItemColor.GetGreen();
992  blue += temp.m_ItemColor.GetBlue();
993  ++dye_count;
994  }
995  }
996  else if (a_CraftingGrid[GridIdx].m_ItemType == E_ITEM_DYE)
997  {
998  switch (a_CraftingGrid[GridIdx].m_ItemDamage)
999  {
1000  case E_META_DYE_BLACK:
1001  {
1002  red += 23;
1003  green += 23;
1004  blue += 23;
1005  break;
1006  }
1007  case E_META_DYE_RED:
1008  {
1009  red += 142;
1010  green += 47;
1011  blue += 47;
1012  break;
1013  }
1014  case E_META_DYE_GREEN:
1015  {
1016  red += 95;
1017  green += 118;
1018  blue += 47;
1019  break;
1020  }
1021  case E_META_DYE_BROWN:
1022  {
1023  red += 95;
1024  green += 71;
1025  blue += 47;
1026  break;
1027  }
1028  case E_META_DYE_BLUE:
1029  {
1030  red += 47;
1031  green += 71;
1032  blue += 165;
1033  break;
1034  }
1035  case E_META_DYE_PURPLE:
1036  {
1037  red += 118;
1038  green += 59;
1039  blue += 165;
1040  break;
1041  }
1042  case E_META_DYE_CYAN:
1043  {
1044  red += 71;
1045  green += 118;
1046  blue += 142;
1047  break;
1048  }
1049  case E_META_DYE_LIGHTGRAY:
1050  {
1051  red += 142;
1052  green += 142;
1053  blue += 142;
1054  break;
1055  }
1056  case E_META_DYE_GRAY:
1057  {
1058  red += 71;
1059  green += 71;
1060  blue += 71;
1061  break;
1062  }
1063  case E_META_DYE_PINK:
1064  {
1065  red += 225;
1066  green += 118;
1067  blue += 153;
1068  break;
1069  }
1070  case E_META_DYE_LIGHTGREEN:
1071  {
1072  red += 118;
1073  green += 190;
1074  blue += 23;
1075  break;
1076  }
1077  case E_META_DYE_YELLOW:
1078  {
1079  red += 213;
1080  green += 213;
1081  blue += 47;
1082  break;
1083  }
1084  case E_META_DYE_LIGHTBLUE:
1085  {
1086  red += 95;
1087  green += 142;
1088  blue += 201;
1089  break;
1090  }
1091  case E_META_DYE_MAGENTA:
1092  {
1093  red += 165;
1094  green += 71;
1095  blue += 201;
1096  break;
1097  }
1098  case E_META_DYE_ORANGE:
1099  {
1100  red += 201;
1101  green += 118;
1102  blue += 47;
1103  break;
1104  }
1105  case E_META_DYE_WHITE:
1106  {
1107  red += 237;
1108  green += 237;
1109  blue += 237;
1110  break;
1111  }
1112  }
1113  ++dye_count;
1114  }
1115  else if (a_CraftingGrid[GridIdx].m_ItemType != E_ITEM_EMPTY)
1116  {
1117  return;
1118  }
1119  }
1120  }
1121 
1122  if (!found)
1123  {
1124  return;
1125  }
1126 
1127  // Calculate the rgb values
1128  double maximum = static_cast<double>(std::max({red, green, blue}));
1129 
1130  double average_red = red / dye_count;
1131  double average_green = green / dye_count;
1132  double average_blue = blue / dye_count;
1133  double average_max = maximum / dye_count;
1134 
1135  double max_average = std::max({average_red, average_green, average_blue});
1136 
1137  double gain_factor = average_max / max_average;
1138 
1139 
1140  unsigned char result_red = static_cast<unsigned char>(average_red * gain_factor);
1141  unsigned char result_green = static_cast<unsigned char>(average_green * gain_factor);
1142  unsigned char result_blue = static_cast<unsigned char>(average_blue * gain_factor);
1143 
1144  // Set the results values
1145  a_Recipe->m_Result = temp;
1146  a_Recipe->m_Result.m_ItemColor.SetColor(result_red, result_green, result_blue);
1147  }
1148 }
bool StringToItem(const AString &a_ItemTypeString, cItem &a_Item)
Translates an itemtype string into an item.
Definition: BlockType.cpp:228
ENUM_ITEM_TYPE
Definition: BlockType.h:295
@ E_ITEM_LEATHER_PANTS
Definition: BlockType.h:344
@ E_ITEM_FIREWORK_STAR
Definition: BlockType.h:448
@ E_ITEM_EMPTY
Definition: BlockType.h:296
@ E_ITEM_FIRE_CHARGE
Definition: BlockType.h:431
@ E_ITEM_GOLD_NUGGET
Definition: BlockType.h:416
@ E_ITEM_DYE
Definition: BlockType.h:396
@ E_ITEM_LAVA_BUCKET
Definition: BlockType.h:371
@ E_ITEM_FEATHER
Definition: BlockType.h:332
@ E_ITEM_WATER_BUCKET
Definition: BlockType.h:370
@ E_ITEM_HEAD
Definition: BlockType.h:443
@ E_ITEM_MILK
Definition: BlockType.h:379
@ E_ITEM_FIREWORK_ROCKET
Definition: BlockType.h:447
@ E_ITEM_DIAMOND
Definition: BlockType.h:308
@ E_ITEM_LEATHER_TUNIC
Definition: BlockType.h:343
@ E_ITEM_GUNPOWDER
Definition: BlockType.h:333
@ E_ITEM_LEATHER_CAP
Definition: BlockType.h:342
@ E_ITEM_BUCKET
Definition: BlockType.h:369
@ E_ITEM_GLOWSTONE_DUST
Definition: BlockType.h:393
@ E_ITEM_LEATHER_BOOTS
Definition: BlockType.h:345
@ E_ITEM_PAPER
Definition: BlockType.h:384
@ E_META_DYE_GRAY
Definition: BlockType.h:1053
@ E_META_DYE_PURPLE
Definition: BlockType.h:1050
@ E_META_DYE_WHITE
Definition: BlockType.h:1060
@ E_META_DYE_RED
Definition: BlockType.h:1046
@ E_META_DYE_BLACK
Definition: BlockType.h:1045
@ E_META_DYE_BLUE
Definition: BlockType.h:1049
@ E_META_DYE_LIGHTBLUE
Definition: BlockType.h:1057
@ E_META_DYE_BROWN
Definition: BlockType.h:1048
@ E_META_DYE_LIGHTGRAY
Definition: BlockType.h:1052
@ E_META_DYE_GREEN
Definition: BlockType.h:1047
@ E_META_DYE_YELLOW
Definition: BlockType.h:1056
@ E_META_DYE_MAGENTA
Definition: BlockType.h:1058
@ E_META_DYE_CYAN
Definition: BlockType.h:1051
@ E_META_DYE_LIGHTGREEN
Definition: BlockType.h:1055
@ E_META_DYE_ORANGE
Definition: BlockType.h:1059
@ E_META_DYE_PINK
Definition: BlockType.h:1054
unsigned char NIBBLETYPE
The datatype used by nibbledata (meta, light, skylight)
Definition: ChunkDef.h:44
auto ToUnsigned(T a_Val)
Definition: Globals.h:387
unsigned int UInt32
Definition: Globals.h:157
#define ASSERT(x)
Definition: Globals.h:276
void LOGERROR(std::string_view a_Format, const Args &... args)
Definition: LoggerSimple.h:73
void LOGWARNING(std::string_view a_Format, const Args &... args)
Definition: LoggerSimple.h:67
void LOG(std::string_view a_Format, const Args &... args)
Definition: LoggerSimple.h:55
#define LOGD
Definition: LoggerSimple.h:83
void LOGINFO(std::string_view a_Format, const Args &... args)
Definition: LoggerSimple.h:61
Item
Definition: Items.h:4
AString TrimString(const AString &str)
Trims whitespace at both ends of the string.
AStringVector StringSplit(const AString &str, const AString &delim)
Split the string at any of the listed delimiters.
Definition: StringUtils.cpp:55
std::vector< AString > AStringVector
Definition: StringUtils.h:12
std::string AString
Definition: StringUtils.h:11
bool CallHookPostCrafting(cPlayer &a_Player, cCraftingGrid &a_Grid, cCraftingRecipe &a_Recipe)
bool CallHookCraftingNoRecipe(cPlayer &a_Player, cCraftingGrid &a_Grid, cCraftingRecipe &a_Recipe)
void SetColor(unsigned char a_Red, unsigned char a_Green, unsigned char a_Blue)
Changes the color.
Definition: Color.cpp:19
unsigned char GetRed() const
Returns the red value of the color.
Definition: Color.cpp:55
unsigned char GetGreen() const
Returns the green value of the color.
Definition: Color.cpp:64
unsigned char GetBlue() const
Returns the blue value of the color.
Definition: Color.cpp:73
bool IsValid() const
Returns whether the color is a valid color.
Definition: Color.h:28
cCraftingGrid(const cCraftingGrid &a_Original)
void SetItem(int x, int y, ENUM_ITEM_TYPE a_ItemType, char a_ItemCount, short a_ItemHealth)
cItem * GetItems(void) const
void Dump(void)
Dumps the entire crafting grid using LOGD()
int GetWidth(void) const
void CopyToItems(cItem *a_Items) const
Copies internal contents into the item array specified.
void ConsumeGrid(const cCraftingGrid &a_Grid)
Removes items in a_Grid from m_Items[] (used by cCraftingRecipe::ConsumeIngredients())
int GetHeight(void) const
cItem & GetItem(int x, int y) const
void Dump(void)
Dumps the entire recipe using LOGD()
void SetIngredient(int x, int y, ENUM_ITEM_TYPE a_ItemType, char a_ItemCount, short a_ItemHealth)
void ConsumeIngredients(cCraftingGrid &a_CraftingGrid)
Consumes ingredients from the crafting grid specified.
void SetResult(ENUM_ITEM_TYPE a_ItemType, char a_ItemCount, short a_ItemHealth)
cCraftingRecipe(const cCraftingGrid &a_CraftingGrid)
cCraftingGrid m_Ingredients
static const int MAX_GRID_HEIGHT
std::vector< cRecipeSlot > cRecipeSlots
void HandleFireworks(const cItem *a_CraftingGrid, cCraftingRecipes::cRecipe *a_Recipe, int a_GridStride, int a_OffsetX, int a_OffsetY)
Searches for anything firework related, and does the data setting if appropriate.
static const int MAX_GRID_WIDTH
void GetRecipe(cPlayer &a_Player, cCraftingGrid &a_CraftingGrid, cCraftingRecipe &a_Recipe)
Returns the recipe for current crafting grid.
void NormalizeIngredients(cRecipe *a_Recipe)
Moves the recipe to top-left corner, sets its MinWidth / MinHeight.
const std::map< AString, UInt32 > & GetRecipeNameMap()
Gets a map of all recipes with name and recipe id.
std::map< AString, UInt32 > m_RecipeNameMap
Mapping the minecraft recipe names to the internal cuberite recipe Ids.
bool IsNewCraftableRecipe(const cRecipe *a_Recipe, const cItem &a_NewItem, const std::set< cItem, cItem::sItemCompare > &a_KnownItems)
Checks if all ingredients of the a_Recipe are within the a_KnownItems list and if the a_NewItem is pa...
cRecipe * MatchRecipe(const cItem *a_CraftingGrid, int a_GridWidth, int a_GridHeight, int a_GridStride, const cRecipe *a_Recipe, int a_OffsetX, int a_OffsetY)
Checks if the grid matches the specified recipe, offset by the specified offsets.
cRecipe * GetRecipeById(UInt32 a_RecipeId)
Returns the recipe by id.
cRecipe * FindRecipeCropped(const cItem *a_CraftingGrid, int a_GridWidth, int a_GridHeight, int a_GridStride)
Same as FindRecipe, but the grid is guaranteed to be of minimal dimensions needed.
void HandleDyedLeather(const cItem *a_CraftingGrid, cCraftingRecipes::cRecipe *a_Recipe, int a_GridStride, int a_GridWidth, int a_GridHeight)
Searches for anything dye related for leather, calculates the appropriate color value,...
cRecipe * FindRecipe(const cItem *a_CraftingGrid, int a_GridWidth, int a_GridHeight)
Finds a recipe matching the crafting grid.
bool ParseItem(const AString &a_String, cItem &a_Item)
Parses an item string in the format "<ItemType>[^<Damage>]", returns true if successful.
bool ParseIngredient(const AString &a_String, cRecipe *a_Recipe)
Parses one ingredient and adds it to the specified recipe.
std::vector< UInt32 > FindNewRecipesForItem(const cItem &a_Item, const std::set< cItem, cItem::sItemCompare > &a_KnownItems)
Find recipes and returns the RecipeIds which contain the new item and all ingredients are in the know...
void PopulateRecipeNameMap(void)
Populates the RecipeNameMap.
void AddRecipeLine(int a_LineNum, const AString &a_RecipeLine)
Parses the recipe line and adds it into m_Recipes.
A single recipe, stored.
Definition: Player.h:29
Definition: Item.h:37
char m_ItemCount
Definition: Item.h:164
bool IsEmpty(void) const
Returns true if the item represents an empty stack - either the type is invalid, or count is zero.
Definition: Item.h:69
cColor m_ItemColor
Definition: Item.h:202
short m_ItemType
Definition: Item.h:163
void Clear(void)
Empties the item and frees up any dynamic storage used by the internals.
Definition: Item.cpp:80
void Empty(void)
Empties the item and frees up any dynamic storage used by the internals.
Definition: Item.cpp:63
cFireworkItem m_FireworkItem
Definition: Item.h:201
cItem CopyOne(void) const
Returns a copy of this item with m_ItemCount set to 1.
Definition: Item.cpp:93
short m_ItemDamage
Definition: Item.h:165
Definition: File.h:38
@ fmRead
Definition: File.h:54
bool Open(const AString &iFileName, eMode iMode)
Definition: File.cpp:52
int ReadRestOfFile(AString &a_Contents)
Reads the file from current position till EOF into an AString; returns the number of bytes read or -1...
Definition: File.cpp:263
void Close(void)
Definition: File.cpp:102
static cRoot * Get()
Definition: Root.h:52
cPluginManager * GetPluginManager(void)
Definition: Root.h:111
std::vector< int > m_FadeColours
std::vector< int > m_Colours
void CopyFrom(const cFireworkItem &a_Item)
static int GetVanillaColourCodeFromDye(NIBBLETYPE a_DyeMeta)
Returns a colour code for fireworks used by the network code.