#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "FurnaceRecipe.h" #include "Item.h" #include #define FURNACE_RECIPE_FILE "furnace.txt" typedef std::list< cFurnaceRecipe::Recipe > RecipeList; typedef std::list< cFurnaceRecipe::Fuel > FuelList; struct cFurnaceRecipe::sFurnaceRecipeState { RecipeList Recipes; FuelList Fuel; }; cFurnaceRecipe::cFurnaceRecipe() : m_pState( new sFurnaceRecipeState) { ReloadRecipes(); } cFurnaceRecipe::~cFurnaceRecipe() { ClearRecipes(); delete m_pState; m_pState = NULL; } void cFurnaceRecipe::ReloadRecipes(void) { ClearRecipes(); LOGD("Loading furnace recipes..."); std::ifstream f(FURNACE_RECIPE_FILE, std::ios::in); if (!f.good()) { LOG("Could not open the furnace recipes file \"%s\". No furnace recipes are available.", FURNACE_RECIPE_FILE); return; } unsigned int LineNum = 0; AString ParsingLine; while (std::getline(f, ParsingLine)) { LineNum++; ParsingLine.erase(std::remove_if(ParsingLine.begin(), ParsingLine.end(), isspace), ParsingLine.end()); // Remove ALL whitespace from the line if (ParsingLine.empty()) { continue; } switch (ParsingLine[0]) { case '#': { // Comment break; } case '!': { AddFuelFromLine(ParsingLine, LineNum); break; } default: { AddRecipeFromLine(ParsingLine, LineNum); break; } } // switch (ParsingLine[0]) } // while (getline(ParsingLine)) LOG("Loaded " SIZE_T_FMT " furnace recipes and " SIZE_T_FMT " fuels", m_pState->Recipes.size(), m_pState->Fuel.size()); } void cFurnaceRecipe::AddFuelFromLine(const AString & a_Line, int a_LineNum) { // Fuel int IItemID = 0, IItemCount = 0, IItemHealth = 0, IBurnTime = 0; AString::size_type BeginPos = 1; // Begin at one after exclamation mark (bang) if ( !ReadMandatoryNumber(BeginPos, ":", a_Line, a_LineNum, IItemID) || // Read item ID !ReadOptionalNumbers(BeginPos, ":", "=", a_Line, a_LineNum, IItemCount, IItemHealth) || // Read item count (and optionally health) !ReadMandatoryNumber(BeginPos, "0123456789", a_Line, a_LineNum, IBurnTime, true) // Read item burn time - last value ) { return; } // Add to fuel list: Fuel F; F.In = new cItem((ENUM_ITEM_ID)IItemID, (char)IItemCount, (short)IItemHealth); F.BurnTime = IBurnTime; m_pState->Fuel.push_back(F); } void cFurnaceRecipe::AddRecipeFromLine(const AString & a_Line, int a_LineNum) { int IItemID = 0, IItemCount = 0, IItemHealth = 0, IBurnTime = 0; int OItemID = 0, OItemCount = 0, OItemHealth = 0; AString::size_type BeginPos = 0; // Begin at start of line if ( !ReadMandatoryNumber(BeginPos, ":", a_Line, a_LineNum, IItemID) || // Read item ID !ReadOptionalNumbers(BeginPos, ":", "@", a_Line, a_LineNum, IItemCount, IItemHealth) || // Read item count (and optionally health) !ReadMandatoryNumber(BeginPos, "=", a_Line, a_LineNum, IBurnTime) || // Read item burn time !ReadMandatoryNumber(BeginPos, ":", a_Line, a_LineNum, OItemID) || // Read result ID !ReadOptionalNumbers(BeginPos, ":", "012456789", a_Line, a_LineNum, OItemCount, OItemHealth, true) // Read result count (and optionally health) - last value ) { return; } // Add to recipe list Recipe R; R.In = new cItem((ENUM_ITEM_ID)IItemID, (char)IItemCount, (short)IItemHealth); R.Out = new cItem((ENUM_ITEM_ID)OItemID, (char)OItemCount, (short)OItemHealth); R.CookTime = IBurnTime; m_pState->Recipes.push_back(R); } void cFurnaceRecipe::PrintParseError(unsigned int a_Line, size_t a_Position, const AString & a_CharactersMissing) { LOGWARN("Error parsing furnace recipes at line %i pos " SIZE_T_FMT ": missing '%s'", a_Line, a_Position, a_CharactersMissing.c_str()); } bool cFurnaceRecipe::ReadMandatoryNumber(AString::size_type & a_Begin, const AString & a_Delimiter, const AString & a_Text, unsigned int a_Line, int & a_Value, bool a_IsLastValue) { // TODO: replace atoi with std::stoi AString::size_type End; if (a_IsLastValue) { End = a_Text.find_first_not_of(a_Delimiter, a_Begin); } else { End = a_Text.find_first_of(a_Delimiter, a_Begin); if (End == AString::npos) { PrintParseError(a_Line, a_Begin, a_Delimiter); return false; } } // stoi won't throw an exception if the string is alphanumeric, we should check for this if (!DoesStringContainOnlyNumbers(a_Text.substr(a_Begin, End - a_Begin))) { PrintParseError(a_Line, a_Begin, "number"); return false; } a_Value = atoi(a_Text.substr(a_Begin, End - a_Begin).c_str()); a_Begin = End + 1; // Jump over delimiter return true; } bool cFurnaceRecipe::ReadOptionalNumbers(AString::size_type & a_Begin, const AString & a_DelimiterOne, const AString & a_DelimiterTwo, const AString & a_Text, unsigned int a_Line, int & a_ValueOne, int & a_ValueTwo, bool a_IsLastValue) { // TODO: replace atoi with std::stoi AString::size_type End, Begin = a_Begin; End = a_Text.find_first_of(a_DelimiterOne, Begin); if (End != AString::npos) { if (DoesStringContainOnlyNumbers(a_Text.substr(Begin, End - Begin))) { a_ValueOne = std::atoi(a_Text.substr(Begin, End - Begin).c_str()); Begin = End + 1; if (a_IsLastValue) { End = a_Text.find_first_not_of(a_DelimiterTwo, Begin); } else { End = a_Text.find_first_of(a_DelimiterTwo, Begin); if (End == AString::npos) { PrintParseError(a_Line, Begin, a_DelimiterTwo); return false; } } // stoi won't throw an exception if the string is alphanumeric, we should check for this if (!DoesStringContainOnlyNumbers(a_Text.substr(Begin, End - Begin))) { PrintParseError(a_Line, Begin, "number"); return false; } a_ValueTwo = atoi(a_Text.substr(Begin, End - Begin).c_str()); a_Begin = End + 1; // Jump over delimiter return true; } else { return ReadMandatoryNumber(a_Begin, a_DelimiterTwo, a_Text, a_Line, a_ValueOne, a_IsLastValue); } } return ReadMandatoryNumber(a_Begin, a_DelimiterTwo, a_Text, a_Line, a_ValueOne, a_IsLastValue); } bool cFurnaceRecipe::DoesStringContainOnlyNumbers(const AString & a_String) { // TODO: replace this with std::all_of(a_String.begin(), a_String.end(), isdigit) return (a_String.find_first_not_of("0123456789") == AString::npos); } void cFurnaceRecipe::ClearRecipes(void) { for (RecipeList::iterator itr = m_pState->Recipes.begin(); itr != m_pState->Recipes.end(); ++itr) { Recipe R = *itr; delete R.In; R.In = NULL; delete R.Out; R.Out = NULL; } m_pState->Recipes.clear(); for (FuelList::iterator itr = m_pState->Fuel.begin(); itr != m_pState->Fuel.end(); ++itr) { Fuel F = *itr; delete F.In; F.In = NULL; } m_pState->Fuel.clear(); } const cFurnaceRecipe::Recipe * cFurnaceRecipe::GetRecipeFrom(const cItem & a_Ingredient) const { const Recipe * BestRecipe = 0; for (RecipeList::const_iterator itr = m_pState->Recipes.begin(); itr != m_pState->Recipes.end(); ++itr) { const Recipe & R = *itr; if ((R.In->m_ItemType == a_Ingredient.m_ItemType) && (R.In->m_ItemCount <= a_Ingredient.m_ItemCount)) { if (BestRecipe && (BestRecipe->In->m_ItemCount > R.In->m_ItemCount)) { continue; } else { BestRecipe = &R; } } } return BestRecipe; } int cFurnaceRecipe::GetBurnTime(const cItem & a_Fuel) const { int BestFuel = 0; for (FuelList::const_iterator itr = m_pState->Fuel.begin(); itr != m_pState->Fuel.end(); ++itr) { const Fuel & F = *itr; if ((F.In->m_ItemType == a_Fuel.m_ItemType) && (F.In->m_ItemCount <= a_Fuel.m_ItemCount)) { if (BestFuel > 0 && (BestFuel > F.BurnTime)) { continue; } else { BestFuel = F.BurnTime; } } } return BestFuel; }