text: 
  #include "Globals.h"  // NOTE: MSVC stupidness requires this to be the same across all modules
  
  #include "FurnaceRecipe.h"
  #include "Item.h"
  
  #include <fstream>
  
  #define FURNACE_RECIPE_FILE "furnace.txt"
  
  
  
  
  
  typedef std::list<cFurnaceRecipe::cRecipe> RecipeList;
  typedef std::list<cFurnaceRecipe::cFuel> 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++;
  		if (ParsingLine.empty())
  		{
  			continue;
  		}
  
  		// Remove comments from the line:
  		size_t FirstCommentSymbol = ParsingLine.find('#');
  		if ((FirstCommentSymbol != AString::npos) && (FirstCommentSymbol != 0))
  		{
  			ParsingLine.erase(ParsingLine.begin() + (const long)FirstCommentSymbol, ParsingLine.end());
  		}
  
  		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, unsigned int a_LineNum)
  {
  	AString Line(a_Line);
  	Line.erase(Line.begin());  // Remove the beginning "!"
  	Line.erase(std::remove_if(Line.begin(), Line.end(), isspace), Line.end());
  
  	std::auto_ptr<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:
  	cFuel Fuel;
  	Fuel.In = Item.release();
  	Fuel.BurnTime = BurnTime;
  	m_pState->Fuel.push_back(Fuel);
  }
  
  
  
  
  
  void cFurnaceRecipe::AddRecipeFromLine(const AString & a_Line, unsigned int a_LineNum)
  {
  	AString Line(a_Line);
  	Line.erase(std::remove_if(Line.begin(), Line.end(), isspace), Line.end());
  
  	int CookTime = 200;
  	std::auto_ptr<cItem> InputItem(new cItem());
  	std::auto_ptr<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;
  	}
  
  	const AStringVector & InputSplit = StringSplit(Sides[0], "@");
  	if (!ParseItem(InputSplit[0], *InputItem))
  	{
  		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;
  	}
  
  	if (InputSplit.size() > 1)
  	{
  		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.release();
  	Recipe.Out = OutputItem.release();
  	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))
  		{
  			return false;
  		}
  	}
  
  	if (SplitMeta.size() > 1)
  	{
  		if (!StringToInteger<short>(SplitMeta[1].c_str(), a_Item.m_ItemDamage))
  		{
  			return false;
  		}
  	}
  	return true;
  }
  
  
  
  
  
  void cFurnaceRecipe::ClearRecipes(void)
  {
  	for (RecipeList::iterator itr = m_pState->Recipes.begin(); itr != m_pState->Recipes.end(); ++itr)
  	{
  		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)
  	{
  		cFuel Fuel = *itr;
  		delete Fuel.In;
  		Fuel.In = NULL;
  	}
  	m_pState->Fuel.clear();
  }
  
  
  
  
  
  const cFurnaceRecipe::cRecipe * cFurnaceRecipe::GetRecipeFrom(const cItem & a_Ingredient) const
  {
  	const cRecipe * BestRecipe = 0;
  	for (RecipeList::const_iterator itr = m_pState->Recipes.begin(); itr != m_pState->Recipes.end(); ++itr)
  	{
  		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 > Recipe.In->m_ItemCount))
  			{
  				continue;
  			}
  			else
  			{
  				BestRecipe = &Recipe;
  			}
  		}
  	}
  	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 cFuel & Fuel = *itr;
  		if ((Fuel.In->m_ItemType == a_Fuel.m_ItemType) && (Fuel.In->m_ItemCount <= a_Fuel.m_ItemCount))
  		{
  			if (BestFuel > 0 && (BestFuel > Fuel.BurnTime))
  			{
  				continue;
  			}
  			else
  			{
  				BestFuel = Fuel.BurnTime;
  			}
  		}
  	}
  	return BestFuel;
  }
  
  
  
  
  