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