Merge remote-tracking branch 'origin/furnaceparsing'
This commit is contained in:
commit
3216fbabfd
@ -5,7 +5,8 @@
|
|||||||
#include "Item.h"
|
#include "Item.h"
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
|
||||||
|
#define FURNACE_RECIPE_FILE "furnace.txt"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -54,127 +55,78 @@ void cFurnaceRecipe::ReloadRecipes(void)
|
|||||||
ClearRecipes();
|
ClearRecipes();
|
||||||
LOGD("Loading furnace recipes...");
|
LOGD("Loading furnace recipes...");
|
||||||
|
|
||||||
std::ifstream f;
|
std::ifstream f(FURNACE_RECIPE_FILE, std::ios::in);
|
||||||
char a_File[] = "furnace.txt";
|
|
||||||
f.open(a_File, std::ios::in);
|
|
||||||
|
|
||||||
if (!f.good())
|
if (!f.good())
|
||||||
{
|
{
|
||||||
f.close();
|
LOG("Could not open the furnace recipes file \"%s\"", FURNACE_RECIPE_FILE);
|
||||||
LOG("Could not open the furnace recipes file \"%s\"", a_File);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned int Line = 0;
|
||||||
|
AString ParsingLine;
|
||||||
|
|
||||||
// TODO: Replace this messy parse with a high-level-structured one (ReadLine / ProcessLine)
|
while (std::getline(f, ParsingLine))
|
||||||
bool bSyntaxError = false;
|
|
||||||
while (f.good())
|
|
||||||
{
|
{
|
||||||
char c;
|
Line++;
|
||||||
|
ParsingLine.erase(std::remove_if(ParsingLine.begin(), ParsingLine.end(), isspace), ParsingLine.end()); // Remove whitespace
|
||||||
//////////////////////////////////////////////////////////////////////////
|
if (ParsingLine.empty())
|
||||||
// comments
|
|
||||||
f >> c;
|
|
||||||
f.unget();
|
|
||||||
if( c == '#' )
|
|
||||||
{
|
{
|
||||||
while( f.good() && c != '\n' )
|
|
||||||
{
|
|
||||||
f.get( c );
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Comments
|
||||||
//////////////////////////////////////////////////////////////////////////
|
if (ParsingLine[0] == '#')
|
||||||
// Line breaks
|
|
||||||
f.get( c );
|
|
||||||
while( f.good() && ( c == '\n' || c == '\r' ) ) { f.get( c ); }
|
|
||||||
if (f.eof())
|
|
||||||
{
|
{
|
||||||
break;
|
continue;
|
||||||
}
|
}
|
||||||
f.unget();
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
if (ParsingLine[0] == '!') // Fuels start with a bang :)
|
||||||
// Check for fuel
|
|
||||||
f >> c;
|
|
||||||
if( c == '!' ) // It's fuel :)
|
|
||||||
{
|
{
|
||||||
// Read item
|
int IItemID = 0, IItemCount = 0, IItemHealth = 0, IBurnTime = 0;
|
||||||
int IItemID = 0, IItemCount = 0, IItemHealth = 0;
|
AString::size_type BeginPos = 1; // Begin at one after exclamation mark (bang)
|
||||||
f >> IItemID;
|
|
||||||
f >> c; if( c != ':' ) { bSyntaxError = true; break; }
|
|
||||||
f >> IItemCount;
|
|
||||||
|
|
||||||
// Optional health
|
if (
|
||||||
f >> c;
|
!ReadMandatoryNumber(BeginPos, ":", ParsingLine, Line, IItemID) || // Read item ID
|
||||||
if( c != ':' )
|
!ReadOptionalNumbers(BeginPos, ":", "=", ParsingLine, Line, IItemCount, IItemHealth) || // Read item count (and optionally health)
|
||||||
f.unget();
|
!ReadMandatoryNumber(BeginPos, "0123456789", ParsingLine, Line, IBurnTime, true) // Read item burn time - last value
|
||||||
else
|
)
|
||||||
{
|
{
|
||||||
f >> IItemHealth;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Burn time
|
|
||||||
int BurnTime;
|
|
||||||
f >> c; if( c != '=' ) { bSyntaxError = true; break; }
|
|
||||||
f >> BurnTime;
|
|
||||||
|
|
||||||
// Add to fuel list
|
// Add to fuel list
|
||||||
Fuel F;
|
Fuel F;
|
||||||
F.In = new cItem( (ENUM_ITEM_ID) IItemID, (char)IItemCount, (short)IItemHealth );
|
F.In = new cItem((ENUM_ITEM_ID)IItemID, (char)IItemCount, (short)IItemHealth);
|
||||||
F.BurnTime = BurnTime;
|
F.BurnTime = IBurnTime;
|
||||||
m_pState->Fuel.push_back( F );
|
m_pState->Fuel.push_back(F);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
f.unget();
|
else if (isdigit(ParsingLine[0])) // Recipes start with a numeral :)
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Read items
|
|
||||||
int IItemID = 0, IItemCount = 0, IItemHealth = 0;
|
|
||||||
f >> IItemID;
|
|
||||||
f >> c; if( c != ':' ) { bSyntaxError = true; break; }
|
|
||||||
f >> IItemCount;
|
|
||||||
|
|
||||||
// Optional health
|
|
||||||
f >> c;
|
|
||||||
if( c != ':' )
|
|
||||||
f.unget();
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
f >> IItemHealth;
|
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, ":", ParsingLine, Line, IItemID) || // Read item ID
|
||||||
|
!ReadOptionalNumbers(BeginPos, ":", "@", ParsingLine, Line, IItemCount, IItemHealth) || // Read item count (and optionally health)
|
||||||
|
!ReadMandatoryNumber(BeginPos, "=", ParsingLine, Line, IBurnTime) || // Read item burn time
|
||||||
|
!ReadMandatoryNumber(BeginPos, ":", ParsingLine, Line, OItemID) || // Read result ID
|
||||||
|
!ReadOptionalNumbers(BeginPos, ":", "012456789", ParsingLine, Line, 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
int CookTime;
|
|
||||||
f >> c; if( c != '@' ) { bSyntaxError = true; break; }
|
|
||||||
f >> CookTime;
|
|
||||||
|
|
||||||
int OItemID = 0, OItemCount = 0, OItemHealth = 0;
|
|
||||||
f >> c; if( c != '=' ) { bSyntaxError = true; break; }
|
|
||||||
f >> OItemID;
|
|
||||||
f >> c; if( c != ':' ) { bSyntaxError = true; break; }
|
|
||||||
f >> OItemCount;
|
|
||||||
|
|
||||||
// Optional health
|
|
||||||
f >> c;
|
|
||||||
if( c != ':' )
|
|
||||||
f.unget();
|
|
||||||
else
|
|
||||||
{
|
|
||||||
f >> OItemHealth;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 = CookTime;
|
|
||||||
m_pState->Recipes.push_back( R );
|
|
||||||
}
|
|
||||||
if (bSyntaxError)
|
|
||||||
{
|
|
||||||
LOGERROR("ERROR: FurnaceRecipe, syntax error" );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG("Loaded " SIZE_T_FMT " furnace recipes and " SIZE_T_FMT " fuels", m_pState->Recipes.size(), m_pState->Fuel.size());
|
LOG("Loaded " SIZE_T_FMT " furnace recipes and " SIZE_T_FMT " fuels", m_pState->Recipes.size(), m_pState->Fuel.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,6 +134,110 @@ void cFurnaceRecipe::ReloadRecipes(void)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
void cFurnaceRecipe::ClearRecipes(void)
|
||||||
{
|
{
|
||||||
for (RecipeList::iterator itr = m_pState->Recipes.begin(); itr != m_pState->Recipes.end(); ++itr)
|
for (RecipeList::iterator itr = m_pState->Recipes.begin(); itr != m_pState->Recipes.end(); ++itr)
|
||||||
|
@ -41,6 +41,28 @@ public:
|
|||||||
private:
|
private:
|
||||||
void ClearRecipes(void);
|
void ClearRecipes(void);
|
||||||
|
|
||||||
|
/** Calls LOGWARN with the line, position, and error */
|
||||||
|
static void PrintParseError(unsigned int a_Line, size_t a_Position, const AString & a_CharactersMissing);
|
||||||
|
|
||||||
|
/** Reads a number from a string given, starting at a given position and ending at a delimiter given
|
||||||
|
Updates beginning position to the delimiter found + 1, and updates the value to the one read
|
||||||
|
If it encounters a substring that is not fully numeric, it will call SetParseError() and return false; the caller should abort processing
|
||||||
|
Otherwise, the function will return true
|
||||||
|
*/
|
||||||
|
static bool ReadMandatoryNumber(AString::size_type & a_Begin, const AString & a_Delimiter, const AString & a_Text, unsigned int a_Line, int & a_Value, bool a_IsLastValue = false);
|
||||||
|
|
||||||
|
/** Reads two numbers from a string given, starting at a given position and ending at the first delimiter given, then again (with an updated position) until the second delimiter given
|
||||||
|
Updates beginning position to the second delimiter found + 1, and updates the values to the ones read
|
||||||
|
If it encounters a substring that is not fully numeric whilst reading the second value, it will call SetParseError() and return false; the caller should abort processing
|
||||||
|
If this happens whilst reading the first value, it will call ReadMandatoryNumber() with the appropriate position, as this may legitimately occur with the optional value and AString::find_first_of finding the incorrect delimiter. It will return the result of ReadMandatoryNumber()
|
||||||
|
True will be returned definitively for an optional value that is valid
|
||||||
|
*/
|
||||||
|
static bool 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 = false);
|
||||||
|
|
||||||
|
/** Uses std::all_of to determine if a string contains only digits */
|
||||||
|
static bool DoesStringContainOnlyNumbers(const AString & a_String);
|
||||||
|
|
||||||
|
|
||||||
struct sFurnaceRecipeState;
|
struct sFurnaceRecipeState;
|
||||||
sFurnaceRecipeState * m_pState;
|
sFurnaceRecipeState * m_pState;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user