459 lines
11 KiB
C++
459 lines
11 KiB
C++
|
#include "cRecipeChecker.h"
|
||
|
|
||
|
#include <fstream>
|
||
|
#include <sstream>
|
||
|
#include <vector>
|
||
|
#include <string>
|
||
|
#include <list>
|
||
|
|
||
|
#ifndef _WIN32
|
||
|
#include <cstring>
|
||
|
#include <cstdlib>
|
||
|
#endif
|
||
|
|
||
|
#include "Defines.h"
|
||
|
#include "cMCLogger.h"
|
||
|
#include "cRoot.h"
|
||
|
|
||
|
typedef std::list< cRecipeChecker::Recipe* > RecipeList;
|
||
|
struct cRecipeChecker::sRecipeCheckerState
|
||
|
{
|
||
|
RecipeList Recipes;
|
||
|
};
|
||
|
|
||
|
cRecipeChecker* cRecipeChecker::GetRecipeChecker()
|
||
|
{
|
||
|
LOGWARN("WARNING: Using deprecated function cRecipeChecker::GetRecipeChecker() use cRoot::Get()->GetRecipeChecker() instead!");
|
||
|
return cRoot::Get()->GetRecipeChecker();
|
||
|
}
|
||
|
|
||
|
cRecipeChecker::Recipe::~Recipe()
|
||
|
{
|
||
|
delete [] Slots;
|
||
|
Slots = 0;
|
||
|
}
|
||
|
|
||
|
cRecipeChecker::~cRecipeChecker()
|
||
|
{
|
||
|
ClearRecipes();
|
||
|
delete m_pState;
|
||
|
}
|
||
|
|
||
|
cRecipeChecker::cRecipeChecker()
|
||
|
: m_pState( new sRecipeCheckerState )
|
||
|
{
|
||
|
ReloadRecipes();
|
||
|
}
|
||
|
|
||
|
void cRecipeChecker::ClearRecipes()
|
||
|
{
|
||
|
while( m_pState->Recipes.size() > 0 )
|
||
|
{
|
||
|
delete *m_pState->Recipes.begin();
|
||
|
m_pState->Recipes.remove( *m_pState->Recipes.begin() );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void PrintRecipe( std::vector< cRecipeChecker::RecipeSlot > & RecipeSlots )
|
||
|
{
|
||
|
LOG("Recipe:");
|
||
|
for(unsigned int i = 0; i < RecipeSlots.size(); i++)
|
||
|
{
|
||
|
cRecipeChecker::RecipeSlot Slot = RecipeSlots[i];
|
||
|
LOG("x:%i y:%i id:%i #%i", Slot.x, Slot.y, Slot.Item.m_ItemID, Slot.Item.m_ItemCount );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void PrintNear( std::ifstream & f, int a_History = 64 )
|
||
|
{
|
||
|
f.clear();
|
||
|
|
||
|
// Calculate how far we can move pointer back
|
||
|
std::streamoff Position = f.tellg();
|
||
|
f.seekg( 0, std::ios_base::beg );
|
||
|
std::streamoff Difference = Position - f.tellg();
|
||
|
if( Difference > a_History ) Difference = a_History;
|
||
|
f.seekg( Position - Difference, std::ios_base::beg );
|
||
|
|
||
|
std::string Near;
|
||
|
if( f.good() )
|
||
|
{
|
||
|
while( f.good() && Near.size() < (unsigned int)Difference )
|
||
|
{
|
||
|
char c;
|
||
|
f.get(c);
|
||
|
Near.push_back( c );
|
||
|
}
|
||
|
}
|
||
|
LOGERROR("Error near: \"%s\"", Near.c_str() );
|
||
|
}
|
||
|
|
||
|
void cRecipeChecker::ReloadRecipes()
|
||
|
{
|
||
|
LOG("--Loading recipes--");
|
||
|
ClearRecipes();
|
||
|
|
||
|
/*
|
||
|
char a_File[] = "recipes.txt";
|
||
|
|
||
|
FILE* f = 0;
|
||
|
#ifdef _WIN32
|
||
|
if( fopen_s(&f, a_File, "rb" ) == 0 ) // no error
|
||
|
#else
|
||
|
if( (f = fopen(a_File, "rb" )) != 0 ) // no error
|
||
|
#endif
|
||
|
{
|
||
|
char c;
|
||
|
while( fread( &c, sizeof(char), 1, f) == 1 )
|
||
|
{
|
||
|
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
LOG("Could not open file for recipes: %s", a_File);
|
||
|
return;
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
|
||
|
std::ifstream f;
|
||
|
|
||
|
char a_File[] = "recipes.txt";
|
||
|
f.open(a_File, std::ios::in);
|
||
|
std::string input;
|
||
|
|
||
|
if( !f.good() )
|
||
|
{
|
||
|
f.close();
|
||
|
LOG("Could not open file for recipes: %s", a_File);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
std::vector< RecipeSlot > RecipeSlots;
|
||
|
|
||
|
bool bError = false;
|
||
|
while( f.good() )
|
||
|
{
|
||
|
bool bLoadSlot = false;
|
||
|
bool bLoadResult = false;
|
||
|
|
||
|
char c;
|
||
|
f >> c;
|
||
|
f.unget();
|
||
|
if( c == '#' )
|
||
|
{
|
||
|
//LOG("Ignoring comment");
|
||
|
while( f.good() && c != '\n' )
|
||
|
{
|
||
|
f.get( c );
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
f.get( c );
|
||
|
while( f.good() && ( c == '\n' || c == '\r' ) ) { f.get( c ); }
|
||
|
if( f.eof() ) break;
|
||
|
f.unget();
|
||
|
|
||
|
int width, height;
|
||
|
f >> width;
|
||
|
f >> c; if( c != 'x' ) { bError=true; break; }
|
||
|
f >> height;
|
||
|
f >> c;
|
||
|
if( c == ',' ) bLoadSlot = true;
|
||
|
|
||
|
while( f.good() && bLoadSlot )
|
||
|
{
|
||
|
bool bDontAddRecipe = false;
|
||
|
int x, y, ItemID, Amount;
|
||
|
if( f.peek() == '*' )
|
||
|
{
|
||
|
f >> c;
|
||
|
x = -1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
f >> x;
|
||
|
}
|
||
|
f >> c; if( c != ':' ) { bError=true; break; }
|
||
|
if( f.peek() == '*' )
|
||
|
{
|
||
|
f >> c;
|
||
|
y = -1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
f >> y;
|
||
|
}
|
||
|
f >> c; if( c != ':' ) { bError=true; break; }
|
||
|
f >> ItemID;
|
||
|
f >> c; if( c != ':' ) { bError=true; break; }
|
||
|
f >> Amount;
|
||
|
|
||
|
f >> c;
|
||
|
if( c == '@' ) bLoadResult = true;
|
||
|
if( c != ',' ) bLoadSlot = false;
|
||
|
|
||
|
if( !isValidItem( ItemID ) )
|
||
|
{
|
||
|
LOGERROR("Error in recipes file (%s): Invalid Item ID %i", a_File, ItemID );
|
||
|
bDontAddRecipe = true;
|
||
|
}
|
||
|
if( x < 0 && y < 0 )
|
||
|
{
|
||
|
if( Amount < 0 )
|
||
|
{
|
||
|
LOGERROR("Error in recipes file (%s): Invalid use of negative amount on wildcard coordinates", a_File );
|
||
|
bDontAddRecipe = true;
|
||
|
}
|
||
|
for(int x = 0; x < width; ++x) for(int y = 0; y < height; ++y )
|
||
|
{
|
||
|
cItem Item( (ENUM_ITEM_ID)ItemID, (char)Amount );
|
||
|
RecipeSlot Slot;
|
||
|
Slot.Item = Item;
|
||
|
Slot.x = x;
|
||
|
Slot.y = y;
|
||
|
RecipeSlots.push_back( Slot );
|
||
|
}
|
||
|
}
|
||
|
else if( x < 0 )
|
||
|
{
|
||
|
if( Amount < 0 )
|
||
|
{
|
||
|
for(int x = 0; x < width; ++x) for(int yy = 0; yy < height; ++yy )
|
||
|
{
|
||
|
if( yy == y-1 ) continue;
|
||
|
cItem Item( (ENUM_ITEM_ID)ItemID, (char)abs(Amount) );
|
||
|
RecipeSlot Slot;
|
||
|
Slot.Item = Item;
|
||
|
Slot.x = x;
|
||
|
Slot.y = yy;
|
||
|
RecipeSlots.push_back( Slot );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
for(int x = 0; x < width; ++x)
|
||
|
{
|
||
|
cItem Item( (ENUM_ITEM_ID)ItemID, (char)Amount );
|
||
|
RecipeSlot Slot;
|
||
|
Slot.Item = Item;
|
||
|
Slot.x = x;
|
||
|
Slot.y = y-1;
|
||
|
RecipeSlots.push_back( Slot );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if( y < 0 )
|
||
|
{
|
||
|
if( Amount < 0 )
|
||
|
{
|
||
|
for(int xx = 0; xx < width; ++xx) for(int y = 0; y < height; ++y )
|
||
|
{
|
||
|
if( xx == x-1 ) continue;
|
||
|
cItem Item( (ENUM_ITEM_ID)ItemID, (char)abs(Amount) );
|
||
|
RecipeSlot Slot;
|
||
|
Slot.Item = Item;
|
||
|
Slot.x = xx;
|
||
|
Slot.y = y;
|
||
|
RecipeSlots.push_back( Slot );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
for(int y = 0; y < height; ++y)
|
||
|
{
|
||
|
cItem Item( (ENUM_ITEM_ID)ItemID, (char)Amount );
|
||
|
RecipeSlot Slot;
|
||
|
Slot.Item = Item;
|
||
|
Slot.x = x-1;
|
||
|
Slot.y = y;
|
||
|
RecipeSlots.push_back( Slot );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if( Amount < 0 )
|
||
|
{
|
||
|
for(int xx = 0; xx < width; ++xx) for(int yy = 0; yy < height; ++yy )
|
||
|
{
|
||
|
if( xx == x-1 && yy == y-1 ) continue;
|
||
|
cItem Item( (ENUM_ITEM_ID)ItemID, (char)abs(Amount) );
|
||
|
RecipeSlot Slot;
|
||
|
Slot.Item = Item;
|
||
|
Slot.x = xx;
|
||
|
Slot.y = yy;
|
||
|
RecipeSlots.push_back( Slot );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
cItem Item( (ENUM_ITEM_ID)ItemID, (char)Amount );
|
||
|
RecipeSlot Slot;
|
||
|
Slot.Item = Item;
|
||
|
Slot.x = x-1;
|
||
|
Slot.y = y-1;
|
||
|
RecipeSlots.push_back( Slot );
|
||
|
}
|
||
|
}
|
||
|
//LOG("%i %i %i %i", x, y, ItemID, Amount );
|
||
|
|
||
|
if( bLoadResult )
|
||
|
{
|
||
|
bLoadResult = false;
|
||
|
f >> ItemID;
|
||
|
f >> c; if( c != ':' ) { bError=true; break; }
|
||
|
f >> Amount;
|
||
|
//LOG("%i %i", ItemID, Amount );
|
||
|
if( !isValidItem( ItemID ) )
|
||
|
{
|
||
|
LOGERROR("Error in recipes file (%s): Invalid Item ID %i", a_File, ItemID );
|
||
|
bDontAddRecipe = true;
|
||
|
}
|
||
|
|
||
|
// Do a sanity check - Handshake algorithm :)
|
||
|
bool bDuplicateEntries = false;
|
||
|
for(unsigned int i = 0; i < RecipeSlots.size(); i++)
|
||
|
{
|
||
|
for(unsigned int j = i+1; j < RecipeSlots.size(); j++)
|
||
|
{
|
||
|
if( RecipeSlots[i].x == RecipeSlots[j].x && RecipeSlots[i].y == RecipeSlots[j].y )
|
||
|
{
|
||
|
LOGERROR("Error in recipes file (%s): Duplicate item on coordinates %i:%i", a_File, RecipeSlots[i].x+1, RecipeSlots[i].y+1 );
|
||
|
bDontAddRecipe = true;
|
||
|
bDuplicateEntries = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if( bDuplicateEntries )
|
||
|
{
|
||
|
PrintNear( f, 64 );
|
||
|
PrintRecipe( RecipeSlots );
|
||
|
}
|
||
|
|
||
|
if( bDontAddRecipe == false )
|
||
|
{
|
||
|
cItem Item( (ENUM_ITEM_ID)ItemID, (char)Amount );
|
||
|
Recipe* recipe = new Recipe;
|
||
|
recipe->Result = Item;
|
||
|
recipe->NumItems = RecipeSlots.size();
|
||
|
recipe->Slots = new RecipeSlot[ recipe->NumItems ];
|
||
|
memcpy( recipe->Slots, &RecipeSlots[0], sizeof(RecipeSlot)*recipe->NumItems );
|
||
|
m_pState->Recipes.push_back( recipe );
|
||
|
//LOG("Loaded recipe for %i times %i", Amount, ItemID );
|
||
|
}
|
||
|
|
||
|
RecipeSlots.clear();
|
||
|
}
|
||
|
}
|
||
|
if( bError ) break;
|
||
|
}
|
||
|
if( bError || ( !f.eof() && !f.good() ) )
|
||
|
{
|
||
|
LOGERROR("Error: Wrong format");
|
||
|
PrintNear( f, 128 );
|
||
|
}
|
||
|
f.close();
|
||
|
|
||
|
LOG("Found %i recipes", m_pState->Recipes.size() );
|
||
|
// for(RecipeList::iterator itr = m_pState->Recipes.begin(); itr != m_pState->Recipes.end(); ++itr )
|
||
|
// {
|
||
|
// LOG("Recipe for %i times %i", (*itr)->Result.m_ItemCount, (*itr)->Result.m_ItemID );
|
||
|
// for(unsigned int j = 0; j < (*itr)->NumItems; j++)
|
||
|
// {
|
||
|
// RecipeSlot Slot = (*itr)->Slots[j];
|
||
|
// LOG("%i %i %i %i", Slot.x, Slot.y, Slot.Item.m_ItemID, Slot.Item.m_ItemCount );
|
||
|
// }
|
||
|
// }
|
||
|
LOG("--Done loading recipes--");
|
||
|
}
|
||
|
|
||
|
cItem cRecipeChecker::CookIngredients( cItem* a_Items, int a_Width, int a_Height, bool a_bConsumeIngredients /* = false */ )
|
||
|
{
|
||
|
int iLeft = 999, iTop = 999;
|
||
|
int iRight = 0, iBottom = 0;
|
||
|
for(int y = 0; y < a_Height; y++ ) for(int x = 0; x < a_Width; x++)
|
||
|
{
|
||
|
cItem Item = a_Items[x + y*a_Width];
|
||
|
if( Item.m_ItemID != E_ITEM_EMPTY && Item.m_ItemCount > 0 )
|
||
|
{
|
||
|
iRight = MAX(x, iRight);
|
||
|
iBottom = MAX(y, iBottom);
|
||
|
iLeft = MIN(x, iLeft);
|
||
|
iTop = MIN(y, iTop);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for(RecipeList::iterator itr = m_pState->Recipes.begin(); itr != m_pState->Recipes.end(); ++itr )
|
||
|
{
|
||
|
Recipe* recipe = (*itr);
|
||
|
int Left = 999, Top = 999;
|
||
|
int Right = 0, Bottom = 0;
|
||
|
for(unsigned int i = 0; i < recipe->NumItems; i++)
|
||
|
{
|
||
|
Right = MAX(recipe->Slots[i].x, Right);
|
||
|
Bottom = MAX(recipe->Slots[i].y, Bottom);
|
||
|
Left = MIN(recipe->Slots[i].x, Left);
|
||
|
Top = MIN(recipe->Slots[i].y, Top);
|
||
|
}
|
||
|
if( Right-Left != iRight-iLeft || Bottom-Top != iBottom-iTop ) continue;
|
||
|
// it has the right dimensions
|
||
|
|
||
|
// Check for empty spaces
|
||
|
unsigned int Hash = 0;
|
||
|
for(unsigned int i = 0; i < recipe->NumItems; i++)
|
||
|
{
|
||
|
int x = recipe->Slots[i].x - Left + iLeft +1;
|
||
|
int y = recipe->Slots[i].y - Top + iTop +1;
|
||
|
Hash += x + y * a_Width;
|
||
|
}
|
||
|
for(int y = 0; y < a_Height; y++ ) for(int x = 0; x < a_Width; x++)
|
||
|
{
|
||
|
cItem & Item = a_Items[x + y*a_Width];
|
||
|
if( Item.m_ItemID != E_ITEM_EMPTY && Item.m_ItemCount > 0 )
|
||
|
{
|
||
|
Hash -= (x+1) + (y+1)*a_Width;
|
||
|
}
|
||
|
}
|
||
|
if( Hash != 0 ) continue; // Empty spaces not in right place
|
||
|
|
||
|
bool bWrong = false;
|
||
|
for(unsigned int i = 0; i < recipe->NumItems; i++)
|
||
|
{
|
||
|
int x = recipe->Slots[i].x - Left + iLeft;
|
||
|
int y = recipe->Slots[i].y - Top + iTop;
|
||
|
cItem Item = a_Items[x + y*a_Width];
|
||
|
if( Item.m_ItemID != recipe->Slots[i].Item.m_ItemID
|
||
|
|| Item.m_ItemCount < recipe->Slots[i].Item.m_ItemCount )
|
||
|
{
|
||
|
bWrong = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if( bWrong ) continue;
|
||
|
|
||
|
cItem Dish = recipe->Result;
|
||
|
|
||
|
// else
|
||
|
if( a_bConsumeIngredients )
|
||
|
{
|
||
|
// Consume! nomnom~
|
||
|
for(unsigned int i = 0; i < recipe->NumItems; i++)
|
||
|
{
|
||
|
int x = recipe->Slots[i].x - Left + iLeft;
|
||
|
int y = recipe->Slots[i].y - Top + iTop;
|
||
|
a_Items[x + y*a_Width].m_ItemCount -= recipe->Slots[i].Item.m_ItemCount;
|
||
|
if( a_Items[x + y*a_Width].m_ItemCount <= 0 ) a_Items[x + y*a_Width].Empty();
|
||
|
}
|
||
|
Dish = CookIngredients( a_Items, a_Width, a_Height, false );
|
||
|
}
|
||
|
|
||
|
// Return the resulting dish!
|
||
|
return Dish;
|
||
|
}
|
||
|
return cItem();
|
||
|
}
|