diff options
Diffstat (limited to 'source/CraftingRecipes.cpp')
-rw-r--r-- | source/CraftingRecipes.cpp | 770 |
1 files changed, 0 insertions, 770 deletions
diff --git a/source/CraftingRecipes.cpp b/source/CraftingRecipes.cpp deleted file mode 100644 index 9dc471781..000000000 --- a/source/CraftingRecipes.cpp +++ /dev/null @@ -1,770 +0,0 @@ - -// CraftingRecipes.cpp - -// Interfaces to the cCraftingRecipes class representing the storage of crafting recipes - -#include "Globals.h" -#include "CraftingRecipes.h" -#include "Root.h" -#include "PluginManager.h" - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cCraftingGrid: - -cCraftingGrid::cCraftingGrid(int a_Width, int a_Height) : - m_Width(a_Width), - m_Height(a_Height), - m_Items(new cItem[a_Width * a_Height]) -{ -} - - - - - -cCraftingGrid::cCraftingGrid(const cItem * a_Items, int a_Width, int a_Height) : - m_Width(a_Width), - m_Height(a_Height), - m_Items(new cItem[a_Width * a_Height]) -{ - for (int i = a_Width * a_Height - 1; i >= 0; i--) - { - m_Items[i] = a_Items[i]; - } -} - - - - - -cCraftingGrid::cCraftingGrid(const cCraftingGrid & a_Original) : - m_Width(a_Original.m_Width), - m_Height(a_Original.m_Height), - m_Items(new cItem[a_Original.m_Width * a_Original.m_Height]) -{ - for (int i = m_Width * m_Height - 1; i >= 0; i--) - { - m_Items[i] = a_Original.m_Items[i]; - } -} - - - - - -cCraftingGrid::~cCraftingGrid() -{ - delete[] m_Items; -} - - - - - -cItem & cCraftingGrid::GetItem(int x, int y) const -{ - // Accessible through scripting, must verify parameters: - if ((x < 0) || (x >= m_Width) || (y < 0) || (y >= m_Height)) - { - LOGERROR("Attempted to get an invalid item from a crafting grid: (%d, %d), grid dimensions: (%d, %d).", - x, y, m_Width, m_Height - ); - return m_Items[0]; - } - return m_Items[x + m_Width * y]; -} - - - - - -void cCraftingGrid::SetItem(int x, int y, ENUM_ITEM_ID a_ItemType, int a_ItemCount, short a_ItemHealth) -{ - // Accessible through scripting, must verify parameters: - if ((x < 0) || (x >= m_Width) || (y < 0) || (y >= m_Height)) - { - LOGERROR("Attempted to set an invalid item in a crafting grid: (%d, %d), grid dimensions: (%d, %d).", - x, y, m_Width, m_Height - ); - return; - } - - m_Items[x + m_Width * y] = cItem(a_ItemType, a_ItemCount, a_ItemHealth); -} - - - - - -void cCraftingGrid::SetItem(int x, int y, const cItem & a_Item) -{ - // Accessible through scripting, must verify parameters: - if ((x < 0) || (x >= m_Width) || (y < 0) || (y >= m_Height)) - { - LOGERROR("Attempted to set an invalid item in a crafting grid: (%d, %d), grid dimensions: (%d, %d).", - x, y, m_Width, m_Height - ); - return; - } - - m_Items[x + m_Width * y] = a_Item; -} - - - - - -void cCraftingGrid::Clear(void) -{ - for (int y = 0; y < m_Height; y++) for (int x = 0; x < m_Width; x++) - { - m_Items[x + m_Width * y].Empty(); - } -} - - - - - -void cCraftingGrid::ConsumeGrid(const cCraftingGrid & a_Grid) -{ - if ((a_Grid.m_Width != m_Width) || (a_Grid.m_Height != m_Height)) - { - LOGWARNING("Consuming a grid of different dimensions: (%d, %d) vs (%d, %d)", - a_Grid.m_Width, a_Grid.m_Height, m_Width, m_Height - ); - } - int MinX = std::min(a_Grid.m_Width, m_Width); - int MinY = std::min(a_Grid.m_Height, m_Height); - for (int y = 0; y < MinY; y++) for (int x = 0; x < MinX; x++) - { - int ThatIdx = x + a_Grid.m_Width * y; - if (a_Grid.m_Items[ThatIdx].IsEmpty()) - { - continue; - } - int ThisIdx = x + m_Width * y; - if (a_Grid.m_Items[ThatIdx].m_ItemType != m_Items[ThisIdx].m_ItemType) - { - LOGWARNING("Consuming incompatible grids: item at (%d, %d) is %d in grid and %d in ingredients. Item not consumed.", - x, y, m_Items[ThisIdx].m_ItemType, a_Grid.m_Items[ThatIdx].m_ItemType - ); - continue; - } - char NumWantedItems = a_Grid.m_Items[ThatIdx].m_ItemCount; - if (NumWantedItems > m_Items[ThisIdx].m_ItemCount) - { - LOGWARNING("Consuming more items than there actually are in slot (%d, %d), item %d (want %d, have %d). Item zeroed out.", - x, y, m_Items[ThisIdx].m_ItemType, - NumWantedItems, m_Items[ThisIdx].m_ItemCount - ); - NumWantedItems = m_Items[ThisIdx].m_ItemCount; - } - m_Items[ThisIdx].m_ItemCount -= NumWantedItems; - if (m_Items[ThisIdx].m_ItemCount == 0) - { - m_Items[ThisIdx].Clear(); - } - } // for x, for y -} - - - - - -void cCraftingGrid::CopyToItems(cItem * a_Items) const -{ - for (int i = m_Height * m_Width - 1; i >= 0; i--) - { - a_Items[i] = m_Items[i]; - } // for x, for y -} - - - - - -void cCraftingGrid::Dump(void) -{ - for (int y = 0; y < m_Height; y++) for (int x = 0; x < m_Width; x++) - { - int idx = x + m_Width * y; - LOGD("Slot (%d, %d): Type %d, health %d, count %d", - x, y, m_Items[idx].m_ItemType, m_Items[idx].m_ItemDamage, m_Items[idx].m_ItemCount - ); - } -} - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cCraftingRecipe: - -cCraftingRecipe::cCraftingRecipe(const cCraftingGrid & a_CraftingGrid) : - m_Ingredients(a_CraftingGrid) -{ -} - - - - - -void cCraftingRecipe::Clear(void) -{ - m_Ingredients.Clear(); - m_Result.Clear(); -} - - - - - -void cCraftingRecipe::SetResult(ENUM_ITEM_ID a_ItemType, int a_ItemCount, short a_ItemHealth) -{ - m_Result = cItem(a_ItemType, a_ItemCount, a_ItemHealth); -} - - - - - -void cCraftingRecipe::ConsumeIngredients(cCraftingGrid & a_CraftingGrid) -{ - a_CraftingGrid.ConsumeGrid(m_Ingredients); -} - - - - - -void cCraftingRecipe::Dump(void) -{ - LOGD("Recipe ingredients:"); - m_Ingredients.Dump(); - LOGD("Result: Type %d, health %d, count %d", - m_Result.m_ItemType, m_Result.m_ItemDamage, m_Result.m_ItemCount - ); -} - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cCraftingRecipes: - -cCraftingRecipes::cCraftingRecipes(void) -{ - LoadRecipes(); -} - - - - - -cCraftingRecipes::~cCraftingRecipes() -{ - ClearRecipes(); -} - - - - - -void cCraftingRecipes::GetRecipe(const cPlayer * a_Player, const cCraftingGrid & a_CraftingGrid, cCraftingRecipe & a_Recipe) -{ - // Allow plugins to intercept recipes using a pre-craft hook: - if (cRoot::Get()->GetPluginManager()->CallHookPreCrafting(a_Player, &a_CraftingGrid, &a_Recipe)) - { - return; - } - - // Built-in recipes: - std::auto_ptr<cRecipe> Recipe(FindRecipe(a_CraftingGrid.GetItems(), a_CraftingGrid.GetWidth(), a_CraftingGrid.GetHeight())); - a_Recipe.Clear(); - if (Recipe.get() == NULL) - { - // Allow plugins to intercept a no-recipe-found situation: - cRoot::Get()->GetPluginManager()->CallHookCraftingNoRecipe(a_Player, &a_CraftingGrid, &a_Recipe); - return; - } - for (cRecipeSlots::const_iterator itr = Recipe->m_Ingredients.begin(); itr != Recipe->m_Ingredients.end(); ++itr) - { - a_Recipe.SetIngredient(itr->x, itr->y, itr->m_Item); - } // for itr - a_Recipe.SetResult(Recipe->m_Result); - - // Allow plugins to intercept recipes after they are processed: - cRoot::Get()->GetPluginManager()->CallHookPostCrafting(a_Player, &a_CraftingGrid, &a_Recipe); -} - - - - - -void cCraftingRecipes::LoadRecipes(void) -{ - LOGD("Loading crafting recipes from crafting.txt..."); - ClearRecipes(); - - // Load the crafting.txt file: - cFile f; - if (!f.Open("crafting.txt", cFile::fmRead)) - { - LOGWARNING("Cannot open file \"crafting.txt\", no crafting recipes will be available!"); - return; - } - AString Everything; - f.ReadRestOfFile(Everything); - f.Close(); - - // Split it into lines, then process each line as a single recipe: - AStringVector Split = StringSplit(Everything, "\n"); - int LineNum = 1; - for (AStringVector::const_iterator itr = Split.begin(); itr != Split.end(); ++itr, ++LineNum) - { - // Remove anything after a '#' sign and trim away the whitespace: - AString Recipe = TrimString(itr->substr(0, itr->find('#'))); - if (Recipe.empty()) - { - // Empty recipe - continue; - } - AddRecipeLine(LineNum, Recipe); - } // for itr - Split[] - LOG("Loaded %d crafting recipes", m_Recipes.size()); -} - - - - -void cCraftingRecipes::ClearRecipes(void) -{ - for (cRecipes::iterator itr = m_Recipes.begin(); itr != m_Recipes.end(); ++itr) - { - delete *itr; - } - m_Recipes.clear(); -} - - - - - -void cCraftingRecipes::AddRecipeLine(int a_LineNum, const AString & a_RecipeLine) -{ - AStringVector Sides = StringSplit(a_RecipeLine, "="); - if (Sides.size() != 2) - { - LOGWARNING("crafting.txt: line %d: A single '=' was expected, got %d", a_LineNum, (int)Sides.size() - 1); - LOGINFO("Offending line: \"%s\"", a_RecipeLine.c_str()); - return; - } - - std::auto_ptr<cCraftingRecipes::cRecipe> Recipe(new cCraftingRecipes::cRecipe); - - // Parse the result: - AStringVector ResultSplit = StringSplit(Sides[0], ","); - if (ResultSplit.empty()) - { - LOGWARNING("crafting.txt: line %d: Result is empty, ignoring the recipe.", a_LineNum); - LOGINFO("Offending line: \"%s\"", a_RecipeLine.c_str()); - return; - } - if (!ParseItem(ResultSplit[0], Recipe->m_Result)) - { - LOGWARNING("crafting.txt: line %d: Cannot parse result item, ignoring the recipe.", a_LineNum); - LOGINFO("Offending line: \"%s\"", a_RecipeLine.c_str()); - return; - } - if (ResultSplit.size() > 1) - { - Recipe->m_Result.m_ItemCount = atoi(ResultSplit[1].c_str()); - if (Recipe->m_Result.m_ItemCount == 0) - { - LOGWARNING("crafting.txt: line %d: Cannot parse result count, ignoring the recipe.", a_LineNum); - LOGINFO("Offending line: \"%s\"", a_RecipeLine.c_str()); - return; - } - } - else - { - Recipe->m_Result.m_ItemCount = 1; - } - - // Parse each ingredient: - AStringVector Ingredients = StringSplit(Sides[1], "|"); - int Num = 1; - for (AStringVector::const_iterator itr = Ingredients.begin(); itr != Ingredients.end(); ++itr, ++Num) - { - if (!ParseIngredient(*itr, Recipe.get())) - { - LOGWARNING("crafting.txt: line %d: Cannot parse ingredient #%d, ignoring the recipe.", a_LineNum, Num); - LOGINFO("Offending line: \"%s\"", a_RecipeLine.c_str()); - return; - } - } // for itr - Ingredients[] - - NormalizeIngredients(Recipe.get()); - - m_Recipes.push_back(Recipe.release()); -} - - - - - -bool cCraftingRecipes::ParseItem(const AString & a_String, cItem & a_Item) -{ - // The caller provides error logging - - AStringVector Split = StringSplit(a_String, "^"); - if (Split.empty()) - { - return false; - } - - if (!StringToItem(Split[0], a_Item)) - { - return false; - } - - if (Split.size() > 1) - { - AString Damage = TrimString(Split[1]); - a_Item.m_ItemDamage = atoi(Damage.c_str()); - if ((a_Item.m_ItemDamage == 0) && (Damage.compare("0") != 0)) - { - // Parsing the number failed - return false; - } - } - - // Success - return true; -} - - - - - -bool cCraftingRecipes::ParseIngredient(const AString & a_String, cRecipe * a_Recipe) -{ - // a_String is in this format: "ItemType^damage, X:Y, X:Y, X:Y..." - AStringVector Split = StringSplit(a_String, ","); - if (Split.size() < 2) - { - // Not enough split items - return false; - } - cItem Item; - if (!ParseItem(Split[0], Item)) - { - return false; - } - Item.m_ItemCount = 1; - - cCraftingRecipes::cRecipeSlots TempSlots; - for (AStringVector::const_iterator itr = Split.begin() + 1; itr != Split.end(); ++itr) - { - // Parse the coords in the split item: - AStringVector Coords = StringSplit(*itr, ":"); - if ((Coords.size() == 1) && (TrimString(Coords[0]) == "*")) - { - cCraftingRecipes::cRecipeSlot Slot; - Slot.m_Item = Item; - Slot.x = -1; - Slot.y = -1; - TempSlots.push_back(Slot); - continue; - } - if (Coords.size() != 2) - { - return false; - } - Coords[0] = TrimString(Coords[0]); - Coords[1] = TrimString(Coords[1]); - if (Coords[0].empty() || Coords[1].empty()) - { - return false; - } - cCraftingRecipes::cRecipeSlot Slot; - Slot.m_Item = Item; - switch (Coords[0][0]) - { - case '1': Slot.x = 0; break; - case '2': Slot.x = 1; break; - case '3': Slot.x = 2; break; - case '*': Slot.x = -1; break; - default: - { - return false; - } - } - switch (Coords[1][0]) - { - case '1': Slot.y = 0; break; - case '2': Slot.y = 1; break; - case '3': Slot.y = 2; break; - case '*': Slot.y = -1; break; - default: - { - return false; - } - } - TempSlots.push_back(Slot); - } // for itr - Split[] - - // Append the ingredients: - a_Recipe->m_Ingredients.insert(a_Recipe->m_Ingredients.end(), TempSlots.begin(), TempSlots.end()); - return true; -} - - - - - -void cCraftingRecipes::NormalizeIngredients(cCraftingRecipes::cRecipe * a_Recipe) -{ - // Calculate the minimum coords for ingredients, excluding the "anywhere" items: - int MinX = MAX_GRID_WIDTH, MaxX = 0; - int MinY = MAX_GRID_HEIGHT, MaxY = 0; - for (cRecipeSlots::const_iterator itr = a_Recipe->m_Ingredients.begin(); itr != a_Recipe->m_Ingredients.end(); ++itr) - { - if (itr->x >= 0) - { - MinX = std::min(itr->x, MinX); - MaxX = std::max(itr->x, MaxX); - } - if (itr->y >= 0) - { - MinY = std::min(itr->y, MinY); - MaxY = std::max(itr->y, MaxY); - } - } // for itr - a_Recipe->m_Ingredients[] - - // Move ingredients so that the minimum coords are 0:0 - for (cRecipeSlots::iterator itr = a_Recipe->m_Ingredients.begin(); itr != a_Recipe->m_Ingredients.end(); ++itr) - { - if (itr->x >= 0) - { - itr->x -= MinX; - } - if (itr->y >= 0) - { - itr->y -= MinY; - } - } // for itr - a_Recipe->m_Ingredients[] - a_Recipe->m_Width = std::max(MaxX - MinX + 1, 1); - a_Recipe->m_Height = std::max(MaxY - MinY + 1, 1); - - // TODO: Compress two same ingredients with the same coords into a single ingredient with increased item count -} - - - - - -cCraftingRecipes::cRecipe * cCraftingRecipes::FindRecipe(const cItem * a_CraftingGrid, int a_GridWidth, int a_GridHeight) -{ - ASSERT(a_GridWidth <= MAX_GRID_WIDTH); - ASSERT(a_GridHeight <= MAX_GRID_HEIGHT); - - // Get the real bounds of the crafting grid: - int GridLeft = MAX_GRID_WIDTH, GridTop = MAX_GRID_HEIGHT; - int GridRight = 0, GridBottom = 0; - for (int y = 0; y < a_GridHeight; y++ ) for(int x = 0; x < a_GridWidth; x++) - { - if (!a_CraftingGrid[x + y * a_GridWidth].IsEmpty()) - { - GridRight = std::max(x, GridRight); - GridBottom = std::max(y, GridBottom); - GridLeft = std::min(x, GridLeft); - GridTop = std::min(y, GridTop); - } - } - int GridWidth = GridRight - GridLeft + 1; - int GridHeight = GridBottom - GridTop + 1; - - // Search in the possibly minimized grid, but keep the stride: - const cItem * Grid = a_CraftingGrid + GridLeft + (a_GridWidth * GridTop); - cRecipe * Recipe = FindRecipeCropped(Grid, GridWidth, GridHeight, a_GridWidth); - if (Recipe == NULL) - { - return NULL; - } - - // A recipe has been found, move it to correspond to the original crafting grid: - for (cRecipeSlots::iterator itrS = Recipe->m_Ingredients.begin(); itrS != Recipe->m_Ingredients.end(); ++itrS) - { - itrS->x += GridLeft; - itrS->y += GridTop; - } // for itrS - Recipe->m_Ingredients[] - - return Recipe; -} - - - - - -cCraftingRecipes::cRecipe * cCraftingRecipes::FindRecipeCropped(const cItem * a_CraftingGrid, int a_GridWidth, int a_GridHeight, int a_GridStride) -{ - for (cRecipes::const_iterator itr = m_Recipes.begin(); itr != m_Recipes.end(); ++itr) - { - // Both the crafting grid and the recipes are normalized. The only variable possible is the "anywhere" items. - // 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. - // 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) - // Calculate the maximum offsets for this recipe relative to the grid size, and iterate through all combinations of offsets. - // Also, this calculation automatically filters out recipes that are too large for the current grid - the loop won't be entered at all. - - int MaxOfsX = a_GridWidth - (*itr)->m_Width; - int MaxOfsY = a_GridHeight - (*itr)->m_Height; - for (int x = 0; x <= MaxOfsX; x++) for (int y = 0; y <= MaxOfsY; y++) - { - cRecipe * Recipe = MatchRecipe(a_CraftingGrid, a_GridWidth, a_GridHeight, a_GridStride, *itr, x, y); - if (Recipe != NULL) - { - return Recipe; - } - } // for y, for x - } // for itr - m_Recipes[] - - // No matching recipe found - return NULL; -} - - - - - -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) -{ - // Check the regular items first: - bool HasMatched[MAX_GRID_WIDTH][MAX_GRID_HEIGHT]; - memset(HasMatched, 0, sizeof(HasMatched)); - for (cRecipeSlots::const_iterator itrS = a_Recipe->m_Ingredients.begin(); itrS != a_Recipe->m_Ingredients.end(); ++itrS) - { - if ((itrS->x < 0) || (itrS->y < 0)) - { - // "Anywhere" item, process later - continue; - } - ASSERT(itrS->x + a_OffsetX < a_GridWidth); - ASSERT(itrS->y + a_OffsetY < a_GridHeight); - int GridID = (itrS->x + a_OffsetX) + a_GridStride * (itrS->y + a_OffsetY); - if ( - (itrS->x >= a_GridWidth) || - (itrS->y >= a_GridHeight) || - (itrS->m_Item.m_ItemType != a_CraftingGrid[GridID].m_ItemType) || // same item type? - (itrS->m_Item.m_ItemCount > a_CraftingGrid[GridID].m_ItemCount) || // not enough items - ( - (itrS->m_Item.m_ItemDamage > 0) && // should compare damage values? - (itrS->m_Item.m_ItemDamage != a_CraftingGrid[GridID].m_ItemDamage) - ) - ) - { - // Doesn't match - return NULL; - } - HasMatched[itrS->x + a_OffsetX][itrS->y + a_OffsetY] = true; - } // for itrS - Recipe->m_Ingredients[] - - // Process the "Anywhere" items now, and only in the cells that haven't matched yet - // The "anywhere" items are processed on a first-come-first-served basis. - // Do not use a recipe with one horizontal and one vertical "anywhere" ("*:1, 1:*") as it may not match properly! - cRecipeSlots MatchedSlots; // Stores the slots of "anywhere" items that have matched, with the match coords - for (cRecipeSlots::const_iterator itrS = a_Recipe->m_Ingredients.begin(); itrS != a_Recipe->m_Ingredients.end(); ++itrS) - { - if ((itrS->x >= 0) && (itrS->y >= 0)) - { - // Regular item, already processed - continue; - } - int StartX = 0, EndX = a_GridWidth - 1; - int StartY = 0, EndY = a_GridHeight - 1; - if (itrS->x >= 0) - { - StartX = itrS->x; - EndX = itrS->x; - } - else if (itrS->y >= 0) - { - StartY = itrS->y; - EndY = itrS->y; - } - bool Found = false; - for (int x = StartX; x <= EndX; x++) - { - for (int y = StartY; y <= EndY; y++) - { - if (HasMatched[x][y]) - { - // Already matched some other item - continue; - } - int GridIdx = x + a_GridStride * y; - if ( - (a_CraftingGrid[GridIdx].m_ItemType == itrS->m_Item.m_ItemType) && - ( - (itrS->m_Item.m_ItemDamage < 0) || // doesn't want damage comparison - (itrS->m_Item.m_ItemDamage == a_CraftingGrid[GridIdx].m_ItemDamage) // the damage matches - ) - ) - { - HasMatched[x][y] = true; - Found = true; - MatchedSlots.push_back(*itrS); - MatchedSlots.back().x = x; - MatchedSlots.back().y = y; - break; - } - } // for y - if (Found) - { - break; - } - } // for x - if (!Found) - { - return NULL; - } - } // for itrS - a_Recipe->m_Ingredients[] - - // Check if the whole grid has matched: - for (int x = 0; x < a_GridWidth; x++) for (int y = 0; y < a_GridHeight; y++) - { - if (!HasMatched[x][y] && !a_CraftingGrid[x + a_GridStride * y].IsEmpty()) - { - // There's an unmatched item in the grid - return NULL; - } - } // for y, for x - - // The recipe has matched. Create a copy of the recipe and set its coords to match the crafting grid: - std::auto_ptr<cRecipe> Recipe(new cRecipe); - Recipe->m_Result = a_Recipe->m_Result; - Recipe->m_Width = a_Recipe->m_Width; - Recipe->m_Height = a_Recipe->m_Height; - for (cRecipeSlots::const_iterator itrS = a_Recipe->m_Ingredients.begin(); itrS != a_Recipe->m_Ingredients.end(); ++itrS) - { - if ((itrS->x < 0) || (itrS->y < 0)) - { - // "Anywhere" item, process later - continue; - } - Recipe->m_Ingredients.push_back(*itrS); - } - Recipe->m_Ingredients.insert(Recipe->m_Ingredients.end(), MatchedSlots.begin(), MatchedSlots.end()); - return Recipe.release(); -} - - - - |