From 92c59963f82f81aa3202657e7fdbb2592924ede3 Mon Sep 17 00:00:00 2001 From: "cedeel@gmail.com" Date: Thu, 14 Jun 2012 13:06:06 +0000 Subject: Attempt to bring sanity to newlines across systems. git-svn-id: http://mc-server.googlecode.com/svn/trunk@606 0a769ca7-a7f5-676a-18bf-c427514a06d6 --- source/CraftingRecipes.cpp | 1530 ++++++++++++++++++++++---------------------- 1 file changed, 765 insertions(+), 765 deletions(-) (limited to 'source/CraftingRecipes.cpp') diff --git a/source/CraftingRecipes.cpp b/source/CraftingRecipes.cpp index d26f553e9..38daa1fe8 100644 --- a/source/CraftingRecipes.cpp +++ b/source/CraftingRecipes.cpp @@ -1,765 +1,765 @@ - -// CraftingRecipes.cpp - -// Interfaces to the cCraftingRecipes class representing the storage of crafting recipes - -#include "Globals.h" -#include "CraftingRecipes.h" -#include "cRoot.h" -#include "cPluginManager.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(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_ItemID != m_Items[ThisIdx].m_ItemID) - { - 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_ItemID, a_Grid.m_Items[ThatIdx].m_ItemID - ); - 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_ItemID, - 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_ItemID, m_Items[idx].m_ItemHealth, 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_ItemID, m_Result.m_ItemHealth, 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 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) -{ - LOG("-- 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("-- %d crafting recipes loaded from crafting.txt --", 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); - return; - } - - std::auto_ptr 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); - return; - } - if (!ParseItem(ResultSplit[0], Recipe->m_Result)) - { - LOGWARNING("crafting.txt: line %d: Cannot parse result item, ignoring the recipe.", a_LineNum); - 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); - 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); - 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_ItemHealth = atoi(Damage.c_str()); - if ((a_Item.m_ItemHealth == 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 = MAX(x, GridRight); - GridBottom = MAX(y, GridBottom); - GridLeft = MIN(x, GridLeft); - GridTop = 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_ItemID != a_CraftingGrid[GridID].m_ItemID) || // same item type? - (itrS->m_Item.m_ItemCount > a_CraftingGrid[GridID].m_ItemCount) || // not enough items - ( - (itrS->m_Item.m_ItemHealth > 0) && // should compare damage values? - (itrS->m_Item.m_ItemHealth != a_CraftingGrid[GridID].m_ItemHealth) - ) - ) - { - // 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_ItemID == itrS->m_Item.m_ItemID) && - ( - (itrS->m_Item.m_ItemHealth < 0) || // doesn't want damage comparison - (itrS->m_Item.m_ItemHealth == a_CraftingGrid[GridIdx].m_ItemHealth) // 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 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(); -} - - - - + +// CraftingRecipes.cpp + +// Interfaces to the cCraftingRecipes class representing the storage of crafting recipes + +#include "Globals.h" +#include "CraftingRecipes.h" +#include "cRoot.h" +#include "cPluginManager.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(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_ItemID != m_Items[ThisIdx].m_ItemID) + { + 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_ItemID, a_Grid.m_Items[ThatIdx].m_ItemID + ); + 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_ItemID, + 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_ItemID, m_Items[idx].m_ItemHealth, 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_ItemID, m_Result.m_ItemHealth, 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 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) +{ + LOG("-- 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("-- %d crafting recipes loaded from crafting.txt --", 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); + return; + } + + std::auto_ptr 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); + return; + } + if (!ParseItem(ResultSplit[0], Recipe->m_Result)) + { + LOGWARNING("crafting.txt: line %d: Cannot parse result item, ignoring the recipe.", a_LineNum); + 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); + 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); + 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_ItemHealth = atoi(Damage.c_str()); + if ((a_Item.m_ItemHealth == 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 = MAX(x, GridRight); + GridBottom = MAX(y, GridBottom); + GridLeft = MIN(x, GridLeft); + GridTop = 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_ItemID != a_CraftingGrid[GridID].m_ItemID) || // same item type? + (itrS->m_Item.m_ItemCount > a_CraftingGrid[GridID].m_ItemCount) || // not enough items + ( + (itrS->m_Item.m_ItemHealth > 0) && // should compare damage values? + (itrS->m_Item.m_ItemHealth != a_CraftingGrid[GridID].m_ItemHealth) + ) + ) + { + // 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_ItemID == itrS->m_Item.m_ItemID) && + ( + (itrS->m_Item.m_ItemHealth < 0) || // doesn't want damage comparison + (itrS->m_Item.m_ItemHealth == a_CraftingGrid[GridIdx].m_ItemHealth) // 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 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(); +} + + + + -- cgit v1.2.3