2014-08-28 18:42:33 -04:00
2012-06-14 09:06:06 -04:00
// CraftingRecipes.cpp
// Interfaces to the cCraftingRecipes class representing the storage of crafting recipes
# include "Globals.h"
# include "CraftingRecipes.h"
2012-09-23 18:09:57 -04:00
# include "Root.h"
2013-12-08 06:17:54 -05:00
# include "Bindings/PluginManager.h"
2012-06-14 09:06:06 -04:00
2014-07-17 16:15:34 -04:00
////////////////////////////////////////////////////////////////////////////////
2012-06-14 09:06:06 -04:00
// cCraftingGrid:
cCraftingGrid : : cCraftingGrid ( int a_Width , int a_Height ) :
m_Width ( a_Width ) ,
m_Height ( a_Height ) ,
m_Items ( new cItem [ a_Width * a_Height ] )
{
}
2012-09-20 09:25:54 -04:00
cCraftingGrid : : cCraftingGrid ( const cItem * a_Items , int a_Width , int a_Height ) :
2012-06-14 09:06:06 -04:00
m_Width ( a_Width ) ,
m_Height ( a_Height ) ,
m_Items ( new cItem [ a_Width * a_Height ] )
{
for ( int i = a_Width * a_Height - 1 ; i > = 0 ; i - - )
{
m_Items [ i ] = a_Items [ i ] ;
}
}
cCraftingGrid : : cCraftingGrid ( const cCraftingGrid & a_Original ) :
m_Width ( a_Original . m_Width ) ,
m_Height ( a_Original . m_Height ) ,
m_Items ( new cItem [ a_Original . m_Width * a_Original . m_Height ] )
{
for ( int i = m_Width * m_Height - 1 ; i > = 0 ; i - - )
{
m_Items [ i ] = a_Original . m_Items [ i ] ;
}
}
cCraftingGrid : : ~ cCraftingGrid ( )
{
delete [ ] m_Items ;
2014-10-20 16:55:07 -04:00
m_Items = nullptr ;
2012-06-14 09:06:06 -04:00
}
cItem & cCraftingGrid : : GetItem ( int x , int y ) const
{
// Accessible through scripting, must verify parameters:
if ( ( x < 0 ) | | ( x > = m_Width ) | | ( y < 0 ) | | ( y > = m_Height ) )
{
LOGERROR ( " Attempted to get an invalid item from a crafting grid: (%d, %d), grid dimensions: (%d, %d). " ,
x , y , m_Width , m_Height
) ;
return m_Items [ 0 ] ;
}
return m_Items [ x + m_Width * y ] ;
}
2014-08-13 07:14:55 -04:00
void cCraftingGrid : : SetItem ( int x , int y , ENUM_ITEM_ID a_ItemType , char a_ItemCount , short a_ItemHealth )
2012-06-14 09:06:06 -04:00
{
// Accessible through scripting, must verify parameters:
if ( ( x < 0 ) | | ( x > = m_Width ) | | ( y < 0 ) | | ( y > = m_Height ) )
{
LOGERROR ( " Attempted to set an invalid item in a crafting grid: (%d, %d), grid dimensions: (%d, %d). " ,
x , y , m_Width , m_Height
) ;
return ;
}
m_Items [ x + m_Width * y ] = cItem ( a_ItemType , a_ItemCount , a_ItemHealth ) ;
}
void cCraftingGrid : : SetItem ( int x , int y , const cItem & a_Item )
{
// Accessible through scripting, must verify parameters:
if ( ( x < 0 ) | | ( x > = m_Width ) | | ( y < 0 ) | | ( y > = m_Height ) )
{
LOGERROR ( " Attempted to set an invalid item in a crafting grid: (%d, %d), grid dimensions: (%d, %d). " ,
x , y , m_Width , m_Height
) ;
return ;
}
m_Items [ x + m_Width * y ] = a_Item ;
}
void cCraftingGrid : : Clear ( void )
{
for ( int y = 0 ; y < m_Height ; y + + ) for ( int x = 0 ; x < m_Width ; x + + )
{
m_Items [ x + m_Width * y ] . Empty ( ) ;
}
}
void cCraftingGrid : : ConsumeGrid ( const cCraftingGrid & a_Grid )
{
if ( ( a_Grid . m_Width ! = m_Width ) | | ( a_Grid . m_Height ! = m_Height ) )
{
2014-07-17 10:33:09 -04:00
LOGWARNING ( " Consuming a grid of different dimensions: (%d, %d) vs (%d, %d) " ,
2012-06-14 09:06:06 -04:00
a_Grid . m_Width , a_Grid . m_Height , m_Width , m_Height
) ;
}
int MinX = std : : min ( a_Grid . m_Width , m_Width ) ;
int MinY = std : : min ( a_Grid . m_Height , m_Height ) ;
for ( int y = 0 ; y < MinY ; y + + ) for ( int x = 0 ; x < MinX ; x + + )
{
int ThatIdx = x + a_Grid . m_Width * y ;
if ( a_Grid . m_Items [ ThatIdx ] . IsEmpty ( ) )
{
continue ;
}
int ThisIdx = x + m_Width * y ;
2013-01-11 23:46:01 -05:00
if ( a_Grid . m_Items [ ThatIdx ] . m_ItemType ! = m_Items [ ThisIdx ] . m_ItemType )
2012-06-14 09:06:06 -04:00
{
LOGWARNING ( " Consuming incompatible grids: item at (%d, %d) is %d in grid and %d in ingredients. Item not consumed. " ,
2013-01-11 23:46:01 -05:00
x , y , m_Items [ ThisIdx ] . m_ItemType , a_Grid . m_Items [ ThatIdx ] . m_ItemType
2012-06-14 09:06:06 -04:00
) ;
continue ;
}
char NumWantedItems = a_Grid . m_Items [ ThatIdx ] . m_ItemCount ;
if ( NumWantedItems > m_Items [ ThisIdx ] . m_ItemCount )
{
LOGWARNING ( " Consuming more items than there actually are in slot (%d, %d), item %d (want %d, have %d). Item zeroed out. " ,
2013-01-11 23:46:01 -05:00
x , y , m_Items [ ThisIdx ] . m_ItemType ,
2012-06-14 09:06:06 -04:00
NumWantedItems , m_Items [ ThisIdx ] . m_ItemCount
) ;
NumWantedItems = m_Items [ ThisIdx ] . m_ItemCount ;
}
m_Items [ ThisIdx ] . m_ItemCount - = NumWantedItems ;
if ( m_Items [ ThisIdx ] . m_ItemCount = = 0 )
{
2015-06-07 09:21:41 -04:00
if ( ( m_Items [ ThisIdx ] . m_ItemType = = E_ITEM_MILK ) | | ( m_Items [ ThisIdx ] . m_ItemType = = E_ITEM_WATER_BUCKET ) | | ( m_Items [ ThisIdx ] . m_ItemType = = E_ITEM_LAVA_BUCKET ) )
2015-06-07 09:02:38 -04:00
{
m_Items [ ThisIdx ] = cItem ( E_ITEM_BUCKET , m_Items [ ThisIdx ] . m_ItemCount ) ;
}
else
{
m_Items [ ThisIdx ] . Clear ( ) ;
}
2012-06-14 09:06:06 -04:00
}
} // for x, for y
}
void cCraftingGrid : : CopyToItems ( cItem * a_Items ) const
{
for ( int i = m_Height * m_Width - 1 ; i > = 0 ; i - - )
{
a_Items [ i ] = m_Items [ i ] ;
} // for x, for y
}
void cCraftingGrid : : Dump ( void )
{
for ( int y = 0 ; y < m_Height ; y + + ) for ( int x = 0 ; x < m_Width ; x + + )
{
2014-03-10 16:12:43 -04:00
# ifdef _DEBUG
2012-06-14 09:06:06 -04:00
int idx = x + m_Width * y ;
2014-03-10 16:12:43 -04:00
# endif
2014-07-17 10:33:09 -04:00
LOGD ( " Slot (%d, %d): Type %d, health %d, count %d " ,
2013-01-11 23:46:01 -05:00
x , y , m_Items [ idx ] . m_ItemType , m_Items [ idx ] . m_ItemDamage , m_Items [ idx ] . m_ItemCount
2012-06-14 09:06:06 -04:00
) ;
}
}
2014-07-17 16:15:34 -04:00
////////////////////////////////////////////////////////////////////////////////
2012-06-14 09:06:06 -04:00
// cCraftingRecipe:
cCraftingRecipe : : cCraftingRecipe ( const cCraftingGrid & a_CraftingGrid ) :
m_Ingredients ( a_CraftingGrid )
{
}
void cCraftingRecipe : : Clear ( void )
{
m_Ingredients . Clear ( ) ;
m_Result . Clear ( ) ;
}
2014-08-13 07:14:55 -04:00
void cCraftingRecipe : : SetResult ( ENUM_ITEM_ID a_ItemType , char a_ItemCount , short a_ItemHealth )
2012-06-14 09:06:06 -04:00
{
m_Result = cItem ( a_ItemType , a_ItemCount , a_ItemHealth ) ;
}
void cCraftingRecipe : : ConsumeIngredients ( cCraftingGrid & a_CraftingGrid )
{
a_CraftingGrid . ConsumeGrid ( m_Ingredients ) ;
}
void cCraftingRecipe : : Dump ( void )
{
LOGD ( " Recipe ingredients: " ) ;
m_Ingredients . Dump ( ) ;
2014-07-17 10:33:09 -04:00
LOGD ( " Result: Type %d, health %d, count %d " ,
2013-01-11 23:46:01 -05:00
m_Result . m_ItemType , m_Result . m_ItemDamage , m_Result . m_ItemCount
2012-06-14 09:06:06 -04:00
) ;
}
2014-07-17 16:15:34 -04:00
////////////////////////////////////////////////////////////////////////////////
2012-06-14 09:06:06 -04:00
// cCraftingRecipes:
cCraftingRecipes : : cCraftingRecipes ( void )
{
LoadRecipes ( ) ;
}
cCraftingRecipes : : ~ cCraftingRecipes ( )
{
ClearRecipes ( ) ;
}
2014-10-15 13:01:55 -04:00
void cCraftingRecipes : : GetRecipe ( cPlayer & a_Player , cCraftingGrid & a_CraftingGrid , cCraftingRecipe & a_Recipe )
2012-06-14 09:06:06 -04:00
{
// Allow plugins to intercept recipes using a pre-craft hook:
2014-10-15 13:01:55 -04:00
if ( cRoot : : Get ( ) - > GetPluginManager ( ) - > CallHookPreCrafting ( a_Player , a_CraftingGrid , a_Recipe ) )
2012-06-14 09:06:06 -04:00
{
return ;
}
// Built-in recipes:
2014-11-27 15:27:03 -05:00
std : : unique_ptr < cRecipe > Recipe ( FindRecipe ( a_CraftingGrid . GetItems ( ) , a_CraftingGrid . GetWidth ( ) , a_CraftingGrid . GetHeight ( ) ) ) ;
2012-06-14 09:06:06 -04:00
a_Recipe . Clear ( ) ;
2014-10-20 16:55:07 -04:00
if ( Recipe . get ( ) = = nullptr )
2012-06-14 09:06:06 -04:00
{
// Allow plugins to intercept a no-recipe-found situation:
2014-12-04 03:21:57 -05:00
cRoot : : Get ( ) - > GetPluginManager ( ) - > CallHookCraftingNoRecipe ( a_Player , a_CraftingGrid , a_Recipe ) ;
2012-06-14 09:06:06 -04:00
return ;
}
for ( cRecipeSlots : : const_iterator itr = Recipe - > m_Ingredients . begin ( ) ; itr ! = Recipe - > m_Ingredients . end ( ) ; + + itr )
{
a_Recipe . SetIngredient ( itr - > x , itr - > y , itr - > m_Item ) ;
} // for itr
a_Recipe . SetResult ( Recipe - > m_Result ) ;
// Allow plugins to intercept recipes after they are processed:
2014-10-15 13:01:55 -04:00
cRoot : : Get ( ) - > GetPluginManager ( ) - > CallHookPostCrafting ( a_Player , a_CraftingGrid , a_Recipe ) ;
2012-06-14 09:06:06 -04:00
}
void cCraftingRecipes : : LoadRecipes ( void )
{
2013-09-28 15:36:01 -04:00
LOGD ( " Loading crafting recipes from crafting.txt... " ) ;
2012-06-14 09:06:06 -04:00
ClearRecipes ( ) ;
// Load the crafting.txt file:
cFile f ;
if ( ! f . Open ( " crafting.txt " , cFile : : fmRead ) )
{
LOGWARNING ( " Cannot open file \" crafting.txt \" , no crafting recipes will be available! " ) ;
return ;
}
AString Everything ;
2014-08-13 07:10:21 -04:00
if ( ! f . ReadRestOfFile ( Everything ) )
{
LOGWARNING ( " Cannot read file \" crafting.txt \" , no crafting recipes will be available! " ) ;
return ;
}
2012-06-14 09:06:06 -04:00
f . Close ( ) ;
// Split it into lines, then process each line as a single recipe:
AStringVector Split = StringSplit ( Everything , " \n " ) ;
int LineNum = 1 ;
for ( AStringVector : : const_iterator itr = Split . begin ( ) ; itr ! = Split . end ( ) ; + + itr , + + LineNum )
{
// Remove anything after a '#' sign and trim away the whitespace:
AString Recipe = TrimString ( itr - > substr ( 0 , itr - > find ( ' # ' ) ) ) ;
if ( Recipe . empty ( ) )
{
// Empty recipe
continue ;
}
AddRecipeLine ( LineNum , Recipe ) ;
} // for itr - Split[]
2014-03-12 13:34:50 -04:00
LOG ( " Loaded " SIZE_T_FMT " crafting recipes " , m_Recipes . size ( ) ) ;
2012-06-14 09:06:06 -04:00
}
void cCraftingRecipes : : ClearRecipes ( void )
{
for ( cRecipes : : iterator itr = m_Recipes . begin ( ) ; itr ! = m_Recipes . end ( ) ; + + itr )
{
delete * itr ;
}
m_Recipes . clear ( ) ;
}
void cCraftingRecipes : : AddRecipeLine ( int a_LineNum , const AString & a_RecipeLine )
{
2015-04-16 14:59:57 -04:00
// Remove any spaces within the line:
2014-08-28 18:42:33 -04:00
AString RecipeLine ( a_RecipeLine ) ;
2014-08-29 10:12:45 -04:00
RecipeLine . erase ( std : : remove_if ( RecipeLine . begin ( ) , RecipeLine . end ( ) , isspace ) , RecipeLine . end ( ) ) ;
2014-08-28 18:42:33 -04:00
AStringVector Sides = StringSplit ( RecipeLine , " = " ) ;
2012-06-14 09:06:06 -04:00
if ( Sides . size ( ) ! = 2 )
{
2015-05-24 07:56:56 -04:00
LOGWARNING ( " crafting.txt: line %d: A single '=' was expected, got " SIZE_T_FMT , a_LineNum , Sides . size ( ) - 1 ) ;
2012-11-18 08:09:13 -05:00
LOGINFO ( " Offending line: \" %s \" " , a_RecipeLine . c_str ( ) ) ;
2012-06-14 09:06:06 -04:00
return ;
}
2015-07-12 14:58:19 -04:00
std : : unique_ptr < cCraftingRecipes : : cRecipe > Recipe = cpp14 : : make_unique < cCraftingRecipes : : cRecipe > ( ) ;
2012-06-14 09:06:06 -04:00
// Parse the result:
AStringVector ResultSplit = StringSplit ( Sides [ 0 ] , " , " ) ;
if ( ResultSplit . empty ( ) )
{
LOGWARNING ( " crafting.txt: line %d: Result is empty, ignoring the recipe. " , a_LineNum ) ;
2012-11-18 08:09:13 -05:00
LOGINFO ( " Offending line: \" %s \" " , a_RecipeLine . c_str ( ) ) ;
2012-06-14 09:06:06 -04:00
return ;
}
if ( ! ParseItem ( ResultSplit [ 0 ] , Recipe - > m_Result ) )
{
LOGWARNING ( " crafting.txt: line %d: Cannot parse result item, ignoring the recipe. " , a_LineNum ) ;
2012-11-18 08:09:13 -05:00
LOGINFO ( " Offending line: \" %s \" " , a_RecipeLine . c_str ( ) ) ;
2012-06-14 09:06:06 -04:00
return ;
}
if ( ResultSplit . size ( ) > 1 )
{
2014-08-13 08:03:56 -04:00
if ( ! StringToInteger < char > ( ResultSplit [ 1 ] . c_str ( ) , Recipe - > m_Result . m_ItemCount ) )
2012-06-14 09:06:06 -04:00
{
LOGWARNING ( " crafting.txt: line %d: Cannot parse result count, ignoring the recipe. " , a_LineNum ) ;
2012-11-18 08:09:13 -05:00
LOGINFO ( " Offending line: \" %s \" " , a_RecipeLine . c_str ( ) ) ;
2012-06-14 09:06:06 -04:00
return ;
}
}
else
{
Recipe - > m_Result . m_ItemCount = 1 ;
}
// Parse each ingredient:
AStringVector Ingredients = StringSplit ( Sides [ 1 ] , " | " ) ;
int Num = 1 ;
for ( AStringVector : : const_iterator itr = Ingredients . begin ( ) ; itr ! = Ingredients . end ( ) ; + + itr , + + Num )
{
if ( ! ParseIngredient ( * itr , Recipe . get ( ) ) )
{
LOGWARNING ( " crafting.txt: line %d: Cannot parse ingredient #%d, ignoring the recipe. " , a_LineNum , Num ) ;
2012-11-18 08:09:13 -05:00
LOGINFO ( " Offending line: \" %s \" " , a_RecipeLine . c_str ( ) ) ;
2012-06-14 09:06:06 -04:00
return ;
}
} // for itr - Ingredients[]
NormalizeIngredients ( Recipe . get ( ) ) ;
m_Recipes . push_back ( Recipe . release ( ) ) ;
}
bool cCraftingRecipes : : ParseItem ( const AString & a_String , cItem & a_Item )
{
// The caller provides error logging
AStringVector Split = StringSplit ( a_String , " ^ " ) ;
if ( Split . empty ( ) )
{
return false ;
}
if ( ! StringToItem ( Split [ 0 ] , a_Item ) )
{
return false ;
}
if ( Split . size ( ) > 1 )
{
AString Damage = TrimString ( Split [ 1 ] ) ;
2014-08-13 08:03:56 -04:00
if ( ! StringToInteger < short > ( Damage . c_str ( ) , a_Item . m_ItemDamage ) )
2012-06-14 09:06:06 -04:00
{
// Parsing the number failed
return false ;
}
}
// Success
return true ;
}
bool cCraftingRecipes : : ParseIngredient ( const AString & a_String , cRecipe * a_Recipe )
{
// a_String is in this format: "ItemType^damage, X:Y, X:Y, X:Y..."
AStringVector Split = StringSplit ( a_String , " , " ) ;
if ( Split . size ( ) < 2 )
{
// Not enough split items
return false ;
}
cItem Item ;
if ( ! ParseItem ( Split [ 0 ] , Item ) )
{
return false ;
}
Item . m_ItemCount = 1 ;
cCraftingRecipes : : cRecipeSlots TempSlots ;
for ( AStringVector : : const_iterator itr = Split . begin ( ) + 1 ; itr ! = Split . end ( ) ; + + itr )
{
// Parse the coords in the split item:
AStringVector Coords = StringSplit ( * itr , " : " ) ;
if ( ( Coords . size ( ) = = 1 ) & & ( TrimString ( Coords [ 0 ] ) = = " * " ) )
{
cCraftingRecipes : : cRecipeSlot Slot ;
Slot . m_Item = Item ;
Slot . x = - 1 ;
Slot . y = - 1 ;
TempSlots . push_back ( Slot ) ;
continue ;
}
if ( Coords . size ( ) ! = 2 )
{
return false ;
}
Coords [ 0 ] = TrimString ( Coords [ 0 ] ) ;
Coords [ 1 ] = TrimString ( Coords [ 1 ] ) ;
if ( Coords [ 0 ] . empty ( ) | | Coords [ 1 ] . empty ( ) )
{
return false ;
}
cCraftingRecipes : : cRecipeSlot Slot ;
Slot . m_Item = Item ;
switch ( Coords [ 0 ] [ 0 ] )
{
case ' 1 ' : Slot . x = 0 ; break ;
case ' 2 ' : Slot . x = 1 ; break ;
case ' 3 ' : Slot . x = 2 ; break ;
case ' * ' : Slot . x = - 1 ; break ;
default :
{
return false ;
}
}
switch ( Coords [ 1 ] [ 0 ] )
{
case ' 1 ' : Slot . y = 0 ; break ;
case ' 2 ' : Slot . y = 1 ; break ;
case ' 3 ' : Slot . y = 2 ; break ;
case ' * ' : Slot . y = - 1 ; break ;
default :
{
return false ;
}
}
TempSlots . push_back ( Slot ) ;
} // for itr - Split[]
// Append the ingredients:
a_Recipe - > m_Ingredients . insert ( a_Recipe - > m_Ingredients . end ( ) , TempSlots . begin ( ) , TempSlots . end ( ) ) ;
return true ;
}
void cCraftingRecipes : : NormalizeIngredients ( cCraftingRecipes : : cRecipe * a_Recipe )
{
// Calculate the minimum coords for ingredients, excluding the "anywhere" items:
int MinX = MAX_GRID_WIDTH , MaxX = 0 ;
int MinY = MAX_GRID_HEIGHT , MaxY = 0 ;
for ( cRecipeSlots : : const_iterator itr = a_Recipe - > m_Ingredients . begin ( ) ; itr ! = a_Recipe - > m_Ingredients . end ( ) ; + + itr )
{
if ( itr - > x > = 0 )
{
MinX = std : : min ( itr - > x , MinX ) ;
MaxX = std : : max ( itr - > x , MaxX ) ;
}
if ( itr - > y > = 0 )
{
MinY = std : : min ( itr - > y , MinY ) ;
MaxY = std : : max ( itr - > y , MaxY ) ;
}
} // for itr - a_Recipe->m_Ingredients[]
// Move ingredients so that the minimum coords are 0:0
for ( cRecipeSlots : : iterator itr = a_Recipe - > m_Ingredients . begin ( ) ; itr ! = a_Recipe - > m_Ingredients . end ( ) ; + + itr )
{
if ( itr - > x > = 0 )
{
itr - > x - = MinX ;
}
if ( itr - > y > = 0 )
{
itr - > y - = MinY ;
}
} // for itr - a_Recipe->m_Ingredients[]
a_Recipe - > m_Width = std : : max ( MaxX - MinX + 1 , 1 ) ;
a_Recipe - > m_Height = std : : max ( MaxY - MinY + 1 , 1 ) ;
// TODO: Compress two same ingredients with the same coords into a single ingredient with increased item count
}
cCraftingRecipes : : cRecipe * cCraftingRecipes : : FindRecipe ( const cItem * a_CraftingGrid , int a_GridWidth , int a_GridHeight )
{
ASSERT ( a_GridWidth < = MAX_GRID_WIDTH ) ;
ASSERT ( a_GridHeight < = MAX_GRID_HEIGHT ) ;
// Get the real bounds of the crafting grid:
int GridLeft = MAX_GRID_WIDTH , GridTop = MAX_GRID_HEIGHT ;
int GridRight = 0 , GridBottom = 0 ;
2014-07-21 09:19:48 -04:00
for ( int y = 0 ; y < a_GridHeight ; y + + ) for ( int x = 0 ; x < a_GridWidth ; x + + )
2012-06-14 09:06:06 -04:00
{
if ( ! a_CraftingGrid [ x + y * a_GridWidth ] . IsEmpty ( ) )
{
2013-05-07 15:59:17 -04:00
GridRight = std : : max ( x , GridRight ) ;
GridBottom = std : : max ( y , GridBottom ) ;
GridLeft = std : : min ( x , GridLeft ) ;
GridTop = std : : min ( y , GridTop ) ;
2012-06-14 09:06:06 -04:00
}
}
int GridWidth = GridRight - GridLeft + 1 ;
int GridHeight = GridBottom - GridTop + 1 ;
// Search in the possibly minimized grid, but keep the stride:
const cItem * Grid = a_CraftingGrid + GridLeft + ( a_GridWidth * GridTop ) ;
cRecipe * Recipe = FindRecipeCropped ( Grid , GridWidth , GridHeight , a_GridWidth ) ;
2014-10-20 16:55:07 -04:00
if ( Recipe = = nullptr )
2012-06-14 09:06:06 -04:00
{
2014-10-20 16:55:07 -04:00
return nullptr ;
2012-06-14 09:06:06 -04:00
}
// A recipe has been found, move it to correspond to the original crafting grid:
for ( cRecipeSlots : : iterator itrS = Recipe - > m_Ingredients . begin ( ) ; itrS ! = Recipe - > m_Ingredients . end ( ) ; + + itrS )
{
itrS - > x + = GridLeft ;
itrS - > y + = GridTop ;
} // for itrS - Recipe->m_Ingredients[]
return Recipe ;
}
cCraftingRecipes : : cRecipe * cCraftingRecipes : : FindRecipeCropped ( const cItem * a_CraftingGrid , int a_GridWidth , int a_GridHeight , int a_GridStride )
{
for ( cRecipes : : const_iterator itr = m_Recipes . begin ( ) ; itr ! = m_Recipes . end ( ) ; + + itr )
{
// Both the crafting grid and the recipes are normalized. The only variable possible is the "anywhere" items.
// This still means that the "anywhere" item may be the one that is offsetting the grid contents to the right or downwards, so we need to check all possible positions.
// E. g. recipe "A, * | B, 1:1 | ..." still needs to check grid for B at 2:2 (in case A was in grid's 1:1)
// Calculate the maximum offsets for this recipe relative to the grid size, and iterate through all combinations of offsets.
// Also, this calculation automatically filters out recipes that are too large for the current grid - the loop won't be entered at all.
int MaxOfsX = a_GridWidth - ( * itr ) - > m_Width ;
int MaxOfsY = a_GridHeight - ( * itr ) - > m_Height ;
for ( int x = 0 ; x < = MaxOfsX ; x + + ) for ( int y = 0 ; y < = MaxOfsY ; y + + )
{
cRecipe * Recipe = MatchRecipe ( a_CraftingGrid , a_GridWidth , a_GridHeight , a_GridStride , * itr , x , y ) ;
2014-10-20 16:55:07 -04:00
if ( Recipe ! = nullptr )
2012-06-14 09:06:06 -04:00
{
return Recipe ;
}
} // for y, for x
} // for itr - m_Recipes[]
// No matching recipe found
2014-10-20 16:55:07 -04:00
return nullptr ;
2012-06-14 09:06:06 -04:00
}
cCraftingRecipes : : cRecipe * cCraftingRecipes : : MatchRecipe ( const cItem * a_CraftingGrid , int a_GridWidth , int a_GridHeight , int a_GridStride , const cRecipe * a_Recipe , int a_OffsetX , int a_OffsetY )
{
// Check the regular items first:
bool HasMatched [ MAX_GRID_WIDTH ] [ MAX_GRID_HEIGHT ] ;
memset ( HasMatched , 0 , sizeof ( HasMatched ) ) ;
for ( cRecipeSlots : : const_iterator itrS = a_Recipe - > m_Ingredients . begin ( ) ; itrS ! = a_Recipe - > m_Ingredients . end ( ) ; + + itrS )
{
if ( ( itrS - > x < 0 ) | | ( itrS - > y < 0 ) )
{
// "Anywhere" item, process later
continue ;
}
ASSERT ( itrS - > x + a_OffsetX < a_GridWidth ) ;
ASSERT ( itrS - > y + a_OffsetY < a_GridHeight ) ;
int GridID = ( itrS - > x + a_OffsetX ) + a_GridStride * ( itrS - > y + a_OffsetY ) ;
2014-04-18 15:09:44 -04:00
const cItem & Item = itrS - > m_Item ;
2012-06-14 09:06:06 -04:00
if (
2014-07-17 10:33:09 -04:00
( itrS - > x > = a_GridWidth ) | |
2012-06-14 09:06:06 -04:00
( itrS - > y > = a_GridHeight ) | |
2015-04-16 15:33:42 -04:00
( Item . m_ItemType ! = a_CraftingGrid [ GridID ] . m_ItemType ) | | // same item type?
2014-04-18 15:09:44 -04:00
( Item . m_ItemCount > a_CraftingGrid [ GridID ] . m_ItemCount ) | | // not enough items
2012-06-14 09:06:06 -04:00
(
2015-04-16 15:33:42 -04:00
( Item . m_ItemDamage > = 0 ) & & // should compare damage values?
2014-04-18 15:09:44 -04:00
( Item . m_ItemDamage ! = a_CraftingGrid [ GridID ] . m_ItemDamage )
2012-06-14 09:06:06 -04:00
)
)
{
// Doesn't match
2014-10-20 16:55:07 -04:00
return nullptr ;
2012-06-14 09:06:06 -04:00
}
HasMatched [ itrS - > x + a_OffsetX ] [ itrS - > y + a_OffsetY ] = true ;
} // for itrS - Recipe->m_Ingredients[]
// Process the "Anywhere" items now, and only in the cells that haven't matched yet
// The "anywhere" items are processed on a first-come-first-served basis.
// Do not use a recipe with one horizontal and one vertical "anywhere" ("*:1, 1:*") as it may not match properly!
cRecipeSlots MatchedSlots ; // Stores the slots of "anywhere" items that have matched, with the match coords
for ( cRecipeSlots : : const_iterator itrS = a_Recipe - > m_Ingredients . begin ( ) ; itrS ! = a_Recipe - > m_Ingredients . end ( ) ; + + itrS )
{
if ( ( itrS - > x > = 0 ) & & ( itrS - > y > = 0 ) )
{
// Regular item, already processed
continue ;
}
int StartX = 0 , EndX = a_GridWidth - 1 ;
int StartY = 0 , EndY = a_GridHeight - 1 ;
if ( itrS - > x > = 0 )
{
StartX = itrS - > x ;
EndX = itrS - > x ;
}
else if ( itrS - > y > = 0 )
{
StartY = itrS - > y ;
EndY = itrS - > y ;
}
bool Found = false ;
for ( int x = StartX ; x < = EndX ; x + + )
{
for ( int y = StartY ; y < = EndY ; y + + )
{
if ( HasMatched [ x ] [ y ] )
{
// Already matched some other item
continue ;
}
int GridIdx = x + a_GridStride * y ;
if (
2013-01-11 23:46:01 -05:00
( a_CraftingGrid [ GridIdx ] . m_ItemType = = itrS - > m_Item . m_ItemType ) & &
2012-06-14 09:06:06 -04:00
(
2013-01-11 23:46:01 -05:00
( itrS - > m_Item . m_ItemDamage < 0 ) | | // doesn't want damage comparison
( itrS - > m_Item . m_ItemDamage = = a_CraftingGrid [ GridIdx ] . m_ItemDamage ) // the damage matches
2012-06-14 09:06:06 -04:00
)
)
{
HasMatched [ x ] [ y ] = true ;
Found = true ;
MatchedSlots . push_back ( * itrS ) ;
MatchedSlots . back ( ) . x = x ;
MatchedSlots . back ( ) . y = y ;
break ;
}
} // for y
if ( Found )
{
break ;
}
} // for x
if ( ! Found )
{
2014-10-20 16:55:07 -04:00
return nullptr ;
2012-06-14 09:06:06 -04:00
}
} // for itrS - a_Recipe->m_Ingredients[]
// Check if the whole grid has matched:
for ( int x = 0 ; x < a_GridWidth ; x + + ) for ( int y = 0 ; y < a_GridHeight ; y + + )
{
if ( ! HasMatched [ x ] [ y ] & & ! a_CraftingGrid [ x + a_GridStride * y ] . IsEmpty ( ) )
{
// There's an unmatched item in the grid
2014-10-20 16:55:07 -04:00
return nullptr ;
2012-06-14 09:06:06 -04:00
}
} // for y, for x
// The recipe has matched. Create a copy of the recipe and set its coords to match the crafting grid:
2015-07-12 14:58:19 -04:00
std : : unique_ptr < cRecipe > Recipe = cpp14 : : make_unique < cRecipe > ( ) ;
2012-06-14 09:06:06 -04:00
Recipe - > m_Result = a_Recipe - > m_Result ;
Recipe - > m_Width = a_Recipe - > m_Width ;
Recipe - > m_Height = a_Recipe - > m_Height ;
for ( cRecipeSlots : : const_iterator itrS = a_Recipe - > m_Ingredients . begin ( ) ; itrS ! = a_Recipe - > m_Ingredients . end ( ) ; + + itrS )
{
if ( ( itrS - > x < 0 ) | | ( itrS - > y < 0 ) )
{
// "Anywhere" item, process later
continue ;
}
Recipe - > m_Ingredients . push_back ( * itrS ) ;
2015-05-31 15:21:57 -04:00
Recipe - > m_Ingredients . back ( ) . x + = a_OffsetX ;
Recipe - > m_Ingredients . back ( ) . y + = a_OffsetY ;
2012-06-14 09:06:06 -04:00
}
Recipe - > m_Ingredients . insert ( Recipe - > m_Ingredients . end ( ) , MatchedSlots . begin ( ) , MatchedSlots . end ( ) ) ;
2014-02-26 18:29:14 -05:00
2015-05-31 15:21:57 -04:00
// Handle the fireworks-related effects:
2014-02-26 18:29:14 -05:00
// We use Recipe instead of a_Recipe because we want the wildcard ingredients' slot numbers as well, which was just added previously
2014-03-01 16:25:01 -05:00
HandleFireworks ( a_CraftingGrid , Recipe . get ( ) , a_GridStride , a_OffsetX , a_OffsetY ) ;
2015-07-13 20:15:37 -04:00
// Handle Dyed Leather
HandleDyedLeather ( a_CraftingGrid , Recipe . get ( ) , a_GridStride , a_GridWidth , a_GridHeight ) ;
2014-03-01 16:25:01 -05:00
return Recipe . release ( ) ;
}
void cCraftingRecipes : : HandleFireworks ( const cItem * a_CraftingGrid , cCraftingRecipes : : cRecipe * a_Recipe , int a_GridStride , int a_OffsetX , int a_OffsetY )
{
// TODO: add support for more than one dye in the recipe
// A manual and temporary solution (listing everything) is in crafting.txt for fade colours, but a programmatic solutions needs to be done for everything else
2014-02-26 18:29:14 -05:00
2014-03-01 16:25:01 -05:00
if ( a_Recipe - > m_Result . m_ItemType = = E_ITEM_FIREWORK_ROCKET )
2014-02-26 18:29:14 -05:00
{
2014-03-01 16:25:01 -05:00
for ( cRecipeSlots : : const_iterator itr = a_Recipe - > m_Ingredients . begin ( ) ; itr ! = a_Recipe - > m_Ingredients . end ( ) ; + + itr )
2014-02-26 18:29:14 -05:00
{
switch ( itr - > m_Item . m_ItemType )
{
case E_ITEM_FIREWORK_STAR :
{
// Result was a rocket, found a star - copy star data to rocket data
int GridID = ( itr - > x + a_OffsetX ) + a_GridStride * ( itr - > y + a_OffsetY ) ;
2014-03-01 16:25:01 -05:00
a_Recipe - > m_Result . m_FireworkItem . CopyFrom ( a_CraftingGrid [ GridID ] . m_FireworkItem ) ;
2014-02-26 18:29:14 -05:00
break ;
}
case E_ITEM_GUNPOWDER :
{
// Gunpowder - increase flight time
2014-03-01 16:25:01 -05:00
a_Recipe - > m_Result . m_FireworkItem . m_FlightTimeInTicks + = 20 ;
2014-02-26 18:29:14 -05:00
break ;
}
case E_ITEM_PAPER : break ;
2014-04-23 16:06:07 -04:00
default : LOG ( " Unexpected item in firework rocket recipe, was the crafting file's fireworks section changed? " ) ; break ;
2014-02-26 18:29:14 -05:00
}
}
}
2014-03-01 16:25:01 -05:00
else if ( a_Recipe - > m_Result . m_ItemType = = E_ITEM_FIREWORK_STAR )
2014-02-26 18:29:14 -05:00
{
2014-03-01 16:25:01 -05:00
std : : vector < int > DyeColours ;
bool FoundStar = false ;
for ( cRecipeSlots : : const_iterator itr = a_Recipe - > m_Ingredients . begin ( ) ; itr ! = a_Recipe - > m_Ingredients . end ( ) ; + + itr )
2014-02-26 18:29:14 -05:00
{
switch ( itr - > m_Item . m_ItemType )
{
case E_ITEM_FIREWORK_STAR :
{
// Result was star, found another star - probably adding fade colours, but copy data over anyhow
2014-03-01 16:25:01 -05:00
FoundStar = true ;
2014-02-26 18:29:14 -05:00
int GridID = ( itr - > x + a_OffsetX ) + a_GridStride * ( itr - > y + a_OffsetY ) ;
2014-03-01 16:25:01 -05:00
a_Recipe - > m_Result . m_FireworkItem . CopyFrom ( a_CraftingGrid [ GridID ] . m_FireworkItem ) ;
2014-02-26 18:29:14 -05:00
break ;
}
case E_ITEM_DYE :
{
2014-02-27 18:33:46 -05:00
int GridID = ( itr - > x + a_OffsetX ) + a_GridStride * ( itr - > y + a_OffsetY ) ;
2015-05-24 07:56:56 -04:00
DyeColours . push_back ( cFireworkItem : : GetVanillaColourCodeFromDye ( static_cast < NIBBLETYPE > ( a_CraftingGrid [ GridID ] . m_ItemDamage & 0x0f ) ) ) ;
2014-02-26 18:29:14 -05:00
break ;
}
case E_ITEM_GUNPOWDER : break ;
2014-03-01 16:25:01 -05:00
case E_ITEM_DIAMOND : a_Recipe - > m_Result . m_FireworkItem . m_HasTrail = true ; break ;
case E_ITEM_GLOWSTONE_DUST : a_Recipe - > m_Result . m_FireworkItem . m_HasFlicker = true ; break ;
case E_ITEM_FIRE_CHARGE : a_Recipe - > m_Result . m_FireworkItem . m_Type = 1 ; break ;
case E_ITEM_GOLD_NUGGET : a_Recipe - > m_Result . m_FireworkItem . m_Type = 2 ; break ;
case E_ITEM_FEATHER : a_Recipe - > m_Result . m_FireworkItem . m_Type = 4 ; break ;
case E_ITEM_HEAD : a_Recipe - > m_Result . m_FireworkItem . m_Type = 3 ; break ;
2014-07-17 13:13:23 -04:00
default : LOG ( " Unexpected item in firework star recipe, was the crafting file's fireworks section changed? " ) ; break ; // ermahgerd BARD ardmins
2014-02-26 18:29:14 -05:00
}
}
2014-03-01 16:25:01 -05:00
if ( FoundStar & & ( ! DyeColours . empty ( ) ) )
{
// Found a star and a dye? Fade colours.
a_Recipe - > m_Result . m_FireworkItem . m_FadeColours = DyeColours ;
}
else if ( ! DyeColours . empty ( ) )
{
// Only dye? Normal colours.
a_Recipe - > m_Result . m_FireworkItem . m_Colours = DyeColours ;
}
}
2012-06-14 09:06:06 -04:00
}
2015-07-13 20:15:37 -04:00
void cCraftingRecipes : : HandleDyedLeather ( const cItem * a_CraftingGrid , cCraftingRecipes : : cRecipe * a_Recipe , int a_GridStride , int a_GridWidth , int a_GridHeight )
{
short result_type = a_Recipe - > m_Result . m_ItemType ;
if ( ( result_type = = E_ITEM_LEATHER_CAP ) | | ( result_type = = E_ITEM_LEATHER_TUNIC ) | | ( result_type = = E_ITEM_LEATHER_PANTS ) | | ( result_type = = E_ITEM_LEATHER_BOOTS ) )
{
bool found = false ;
cItem temp ;
float red = 0 ;
float green = 0 ;
float blue = 0 ;
float dye_count = 0 ;
for ( int x = 0 ; x < a_GridWidth ; + + x )
{
for ( int y = 0 ; y < a_GridHeight ; + + y )
{
int GridIdx = x + a_GridStride * y ;
if ( ( a_CraftingGrid [ GridIdx ] . m_ItemType = = result_type ) & & ( found = = false ) )
{
found = true ;
temp = a_CraftingGrid [ GridIdx ] . CopyOne ( ) ;
// The original color of the item affects the result
if ( temp . m_ItemColor . IsValid ( ) )
{
red + = temp . m_ItemColor . GetRed ( ) ;
green + = temp . m_ItemColor . GetGreen ( ) ;
blue + = temp . m_ItemColor . GetBlue ( ) ;
+ + dye_count ;
}
}
else if ( a_CraftingGrid [ GridIdx ] . m_ItemType = = E_ITEM_DYE )
{
switch ( a_CraftingGrid [ GridIdx ] . m_ItemDamage )
{
case E_META_DYE_BLACK :
{
red + = 23 ;
green + = 23 ;
blue + = 23 ;
break ;
}
case E_META_DYE_RED :
{
red + = 142 ;
green + = 47 ;
blue + = 47 ;
break ;
}
case E_META_DYE_GREEN :
{
red + = 95 ;
green + = 118 ;
blue + = 47 ;
break ;
}
case E_META_DYE_BROWN :
{
red + = 95 ;
green + = 71 ;
blue + = 47 ;
break ;
}
case E_META_DYE_BLUE :
{
red + = 47 ;
green + = 71 ;
blue + = 165 ;
break ;
}
case E_META_DYE_PURPLE :
{
red + = 118 ;
green + = 59 ;
blue + = 165 ;
break ;
}
case E_META_DYE_CYAN :
{
red + = 71 ;
green + = 118 ;
blue + = 142 ;
break ;
}
case E_META_DYE_LIGHTGRAY :
{
red + = 142 ;
green + = 142 ;
blue + = 142 ;
break ;
}
case E_META_DYE_GRAY :
{
red + = 71 ;
green + = 71 ;
blue + = 71 ;
break ;
}
case E_META_DYE_PINK :
{
red + = 225 ;
green + = 118 ;
blue + = 153 ;
break ;
}
case E_META_DYE_LIGHTGREEN :
{
red + = 118 ;
green + = 190 ;
blue + = 23 ;
break ;
}
case E_META_DYE_YELLOW :
{
red + = 213 ;
green + = 213 ;
blue + = 47 ;
break ;
}
case E_META_DYE_LIGHTBLUE :
{
red + = 95 ;
green + = 142 ;
blue + = 201 ;
break ;
}
case E_META_DYE_MAGENTA :
{
red + = 165 ;
green + = 71 ;
blue + = 201 ;
break ;
}
case E_META_DYE_ORANGE :
{
red + = 201 ;
green + = 118 ;
blue + = 47 ;
break ;
}
case E_META_DYE_WHITE :
{
red + = 237 ;
green + = 237 ;
blue + = 237 ;
break ;
}
}
+ + dye_count ;
}
else if ( a_CraftingGrid [ GridIdx ] . m_ItemType ! = E_ITEM_EMPTY )
{
return ;
}
}
}
if ( ! found )
{
return ;
}
// Calculate the rgb values
double maximum = static_cast < double > ( std : : max ( { red , green , blue } ) ) ;
double average_red = red / dye_count ;
double average_green = green / dye_count ;
double average_blue = blue / dye_count ;
double average_max = maximum / dye_count ;
double max_average = std : : max ( { average_red , average_green , average_blue } ) ;
double gain_factor = average_max / max_average ;
unsigned char result_red = static_cast < unsigned char > ( average_red * gain_factor ) ;
unsigned char result_green = static_cast < unsigned char > ( average_green * gain_factor ) ;
unsigned char result_blue = static_cast < unsigned char > ( average_blue * gain_factor ) ;
// Set the results values
a_Recipe - > m_Result = temp ;
a_Recipe - > m_Result . m_ItemColor . SetColor ( result_red , result_green , result_blue ) ;
}
}