|
|
|
|
@@ -12,8 +12,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
typedef std::list< cFurnaceRecipe::Recipe > RecipeList;
|
|
|
|
|
typedef std::list< cFurnaceRecipe::Fuel > FuelList;
|
|
|
|
|
typedef std::list<cFurnaceRecipe::cRecipe> RecipeList;
|
|
|
|
|
typedef std::list<cFurnaceRecipe::cFuel> FuelList;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -30,7 +30,7 @@ struct cFurnaceRecipe::sFurnaceRecipeState
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cFurnaceRecipe::cFurnaceRecipe()
|
|
|
|
|
: m_pState( new sFurnaceRecipeState)
|
|
|
|
|
: m_pState(new sFurnaceRecipeState)
|
|
|
|
|
{
|
|
|
|
|
ReloadRecipes();
|
|
|
|
|
}
|
|
|
|
|
@@ -68,12 +68,18 @@ void cFurnaceRecipe::ReloadRecipes(void)
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove comments from the line:
|
|
|
|
|
size_t FirstCommentSymbol = ParsingLine.find('#');
|
|
|
|
|
if ((FirstCommentSymbol != AString::npos) && (FirstCommentSymbol != 0))
|
|
|
|
|
{
|
|
|
|
|
ParsingLine.erase(ParsingLine.begin() + FirstCommentSymbol, ParsingLine.end());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (ParsingLine[0])
|
|
|
|
|
{
|
|
|
|
|
case '#':
|
|
|
|
|
@@ -105,24 +111,40 @@ void cFurnaceRecipe::ReloadRecipes(void)
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
AString Line(a_Line);
|
|
|
|
|
Line.erase(Line.begin()); // Remove the beginning "!"
|
|
|
|
|
Line.erase(std::remove_if(Line.begin(), Line.end(), isspace), Line.end());
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
)
|
|
|
|
|
cItem * Item = new cItem();
|
|
|
|
|
int BurnTime;
|
|
|
|
|
|
|
|
|
|
const AStringVector & Sides = StringSplit(Line, "=");
|
|
|
|
|
if (Sides.size() != 2)
|
|
|
|
|
{
|
|
|
|
|
LOGWARNING("furnace.txt: line %d: A single '=' was expected, got %d", a_LineNum, (int)Sides.size() - 1);
|
|
|
|
|
LOGINFO("Offending line: \"%s\"", a_Line.c_str());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ParseItem(Sides[0], *Item))
|
|
|
|
|
{
|
|
|
|
|
LOGWARNING("furnace.txt: line %d: Cannot parse item \"%s\".", a_LineNum, Sides[0].c_str());
|
|
|
|
|
LOGINFO("Offending line: \"%s\"", a_Line.c_str());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!StringToInteger<int>(Sides[1], BurnTime))
|
|
|
|
|
{
|
|
|
|
|
LOGWARNING("furnace.txt: line %d: Cannot parse burn time.", a_LineNum);
|
|
|
|
|
LOGINFO("Offending line: \"%s\"", a_Line.c_str());
|
|
|
|
|
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);
|
|
|
|
|
cFuel Fuel;
|
|
|
|
|
Fuel.In = Item;
|
|
|
|
|
Fuel.BurnTime = BurnTime;
|
|
|
|
|
m_pState->Fuel.push_back(Fuel);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -131,69 +153,87 @@ void cFurnaceRecipe::AddFuelFromLine(const AString & a_Line, int a_LineNum)
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
AString Line(a_Line);
|
|
|
|
|
Line.erase(std::remove_if(Line.begin(), Line.end(), isspace), Line.end());
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
)
|
|
|
|
|
int CookTime = 200;
|
|
|
|
|
cItem * InputItem = new cItem();
|
|
|
|
|
cItem * OutputItem = new cItem();
|
|
|
|
|
|
|
|
|
|
const AStringVector & Sides = StringSplit(Line, "=");
|
|
|
|
|
if (Sides.size() != 2)
|
|
|
|
|
{
|
|
|
|
|
LOGWARNING("furnace.txt: line %d: A single '=' was expected, got %d", a_LineNum, (int)Sides.size() - 1);
|
|
|
|
|
LOGINFO("Offending line: \"%s\"", a_Line.c_str());
|
|
|
|
|
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)
|
|
|
|
|
const AStringVector & InputSplit = StringSplit(Sides[0], "@");
|
|
|
|
|
if (!ParseItem(InputSplit[0], *InputItem))
|
|
|
|
|
{
|
|
|
|
|
End = a_Text.find_first_not_of(a_Delimiter, a_Begin);
|
|
|
|
|
LOGWARNING("furnace.txt: line %d: Cannot parse input item \"%s\".", a_LineNum, InputSplit[0].c_str());
|
|
|
|
|
LOGINFO("Offending line: \"%s\"", a_Line.c_str());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
|
|
|
|
|
if (InputSplit.size() > 1)
|
|
|
|
|
{
|
|
|
|
|
End = a_Text.find_first_of(a_Delimiter, a_Begin);
|
|
|
|
|
if (End == AString::npos)
|
|
|
|
|
if (!StringToInteger<int>(InputSplit[1], CookTime))
|
|
|
|
|
{
|
|
|
|
|
LOGWARNING("furnace.txt: line %d: Cannot parse cook time \"%s\".", a_LineNum, InputSplit[1].c_str());
|
|
|
|
|
LOGINFO("Offending line: \"%s\"", a_Line.c_str());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ParseItem(Sides[1], *OutputItem))
|
|
|
|
|
{
|
|
|
|
|
LOGWARNING("furnace.txt: line %d: Cannot parse output item \"%s\".", a_LineNum, Sides[1].c_str());
|
|
|
|
|
LOGINFO("Offending line: \"%s\"", a_Line.c_str());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cRecipe Recipe;
|
|
|
|
|
Recipe.In = InputItem;
|
|
|
|
|
Recipe.Out = OutputItem;
|
|
|
|
|
Recipe.CookTime = CookTime;
|
|
|
|
|
m_pState->Recipes.push_back(Recipe);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool cFurnaceRecipe::ParseItem(const AString & a_String, cItem & a_Item)
|
|
|
|
|
{
|
|
|
|
|
AString ItemString = a_String;
|
|
|
|
|
|
|
|
|
|
const AStringVector & SplitAmount = StringSplit(ItemString, ",");
|
|
|
|
|
ItemString = SplitAmount[0];
|
|
|
|
|
|
|
|
|
|
const AStringVector & SplitMeta = StringSplit(ItemString, ":");
|
|
|
|
|
ItemString = SplitMeta[0];
|
|
|
|
|
|
|
|
|
|
if (!StringToItem(ItemString, a_Item))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (SplitAmount.size() > 1)
|
|
|
|
|
{
|
|
|
|
|
if (!StringToInteger<char>(SplitAmount[1].c_str(), a_Item.m_ItemCount))
|
|
|
|
|
{
|
|
|
|
|
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
|
|
|
|
|
if (SplitMeta.size() > 1)
|
|
|
|
|
{
|
|
|
|
|
if (!StringToInteger<short>(SplitMeta[1].c_str(), a_Item.m_ItemDamage))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -201,84 +241,23 @@ bool cFurnaceRecipe::ReadMandatoryNumber(AString::size_type & a_Begin, const ASt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
cRecipe Recipe = *itr;
|
|
|
|
|
delete Recipe.In;
|
|
|
|
|
Recipe.In = NULL;
|
|
|
|
|
delete Recipe.Out;
|
|
|
|
|
Recipe.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;
|
|
|
|
|
cFuel Fuel = *itr;
|
|
|
|
|
delete Fuel.In;
|
|
|
|
|
Fuel.In = NULL;
|
|
|
|
|
}
|
|
|
|
|
m_pState->Fuel.clear();
|
|
|
|
|
}
|
|
|
|
|
@@ -287,21 +266,21 @@ void cFurnaceRecipe::ClearRecipes(void)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const cFurnaceRecipe::Recipe * cFurnaceRecipe::GetRecipeFrom(const cItem & a_Ingredient) const
|
|
|
|
|
const cFurnaceRecipe::cRecipe * cFurnaceRecipe::GetRecipeFrom(const cItem & a_Ingredient) const
|
|
|
|
|
{
|
|
|
|
|
const Recipe * BestRecipe = 0;
|
|
|
|
|
const cRecipe * 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))
|
|
|
|
|
const cRecipe & Recipe = *itr;
|
|
|
|
|
if ((Recipe.In->m_ItemType == a_Ingredient.m_ItemType) && (Recipe.In->m_ItemCount <= a_Ingredient.m_ItemCount))
|
|
|
|
|
{
|
|
|
|
|
if (BestRecipe && (BestRecipe->In->m_ItemCount > R.In->m_ItemCount))
|
|
|
|
|
if (BestRecipe && (BestRecipe->In->m_ItemCount > Recipe.In->m_ItemCount))
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
BestRecipe = &R;
|
|
|
|
|
BestRecipe = &Recipe;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -317,16 +296,16 @@ 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))
|
|
|
|
|
const cFuel & Fuel = *itr;
|
|
|
|
|
if ((Fuel.In->m_ItemType == a_Fuel.m_ItemType) && (Fuel.In->m_ItemCount <= a_Fuel.m_ItemCount))
|
|
|
|
|
{
|
|
|
|
|
if (BestFuel > 0 && (BestFuel > F.BurnTime))
|
|
|
|
|
if (BestFuel > 0 && (BestFuel > Fuel.BurnTime))
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
BestFuel = F.BurnTime;
|
|
|
|
|
BestFuel = Fuel.BurnTime;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|