1
0

Merge pull request #1363 from mc-server/FurnaceRecipes

Rewritten furnace.txt loading
This commit is contained in:
Mattes D 2014-09-01 09:00:51 +02:00
commit 78b7d0006a
6 changed files with 190 additions and 226 deletions

View File

@ -10,25 +10,28 @@
# An Item is defined by an Item Type, an amount (and damage)
# The damage is optional, and if not specified it's assumed to be 0
#
# -Cactus Green:
# 351 : 1 ( : 2 )
# ItemType : Amount ( : Damage )
# Cactus Green example:
# 351 : 2 ( , 1 )
# ItemType : Damage ( , Amount )
# or simple use the item name (marked in items.ini):
# CactusGreen ( , 1 )
#
#
# **** Recipe and result ****
#
# 4:1@200=1:1 -> Produces 1 smooth stone from 1 cobblestone in 200 ticks (10 seconds)
# Cobble @ 200 = Stone -> Produces 1 smooth stone from 1 cobblestone in 200 ticks (10 seconds)
#
# 4 : 1 @ 200 = 1 : 1
# ItemType : Amount @ ticks = ItemID : Amount
# Write in full:
# Cobble : 0 , 1 @ 200 = 1 : 1 , 1
# ItemType : Damage , Amount @ ticks = ItemType : Damage , Amount
#
#
# **** Fuel ****
#
# !17:1 = 300 -> 1 Wood burns for 300 ticks (15 s)
#
# ! 17 : 1 = 300
# Fuel ItemType : Amount = ticks
# ! Wood , 1 = 300
# Fuel ItemType , Amount = ticks
#
#******************************************************#
@ -39,20 +42,20 @@
#--------------------------
# Smelting recipes
4:1 @ 200 = 1:1 # 1 Cobblestone -> 1 Rock
15:1 @ 200 = 265:1 # 1 Iron Ore -> 1 Iron Ingot
14:1 @ 200 = 266:1 # 1 Gold Ore -> 1 Gold Ingot
153:1 @ 200 = 406:1 # 1 Quartz Ore -> 1 Quartz
12:1 @ 200 = 20:1 # 1 Sand -> 1 Glass
319:1 @ 200 = 320:1 # 1 Raw Pork -> 1 Cooked Pork
363:1 @ 200 = 364:1 # 1 Raw Beef -> 1 Cooked Beef (steak)
365:1 @ 200 = 366:1 # 1 Raw Chicken -> 1 Cooked Chicken
337:1 @ 200 = 336:1 # 1 Clay -> 1 Clay Brick
82:1 @ 200 = 172:1 # 1 Clay Block -> 1 Hardened Clay
87:1 @ 200 = 405:1 # 1 NetherRack -> 1 NetherBrick
349:1 @ 200 = 350:1 # 1 Raw Fish -> 1 Cooked Fish
17:1 @ 200 = 263:1:1 # 1 Log -> 1 Charcoal
81:1 @ 200 = 351:1:2 # 1 Cactus -> 1 Green Dye
Cobble = Stone
IronOre = IronIngot
GoldOre = GoldIngot
NetherQuartzOre = NetherQuartz
Sand = Glass
Pork = CookedPork
RawBeef = Steak
RawChicken = CookedChicken
Clay = Brick
ClayBlock = HardenedClay
TallGrass = NetherBrickItem
RawFish = CookedFish
Log = CharCoal
Cactus = GreenDye
@ -61,31 +64,31 @@
#--------------------------
# Fuels
! 263:1 = 1600 # 1 Coal -> 80 sec
! 263:1:1 = 1600 # 1 Charcoal -> 80 sec
! 126:1 = 15 # 1 Halfslab -> 7.5 sec
! 5:1 = 300 # 1 Planks -> 15 sec
! 280:1 = 100 # 1 Stick -> 5 sec
! 85:1 = 300 # 1 Fence -> 15 sec
! 53:1 = 300 # 1 Wooden Stairs -> 15 sec
! 58:1 = 300 # 1 Crafting Table -> 15 sec
! 47:1 = 300 # 1 Bookshelf -> 15 sec
! 54:1 = 300 # 1 Chest -> 15 sec
! 84:1 = 300 # 1 Jukebox -> 15 sec
! 327:1 = 20000 # 1 Lava Bucket -> 1000 sec
! 17:1 = 300 # 1 Wood -> 15 sec
! 6:1 = 100 # 1 Sapling -> 5 sec
! 173:1 = 16000 # 1 Coal Block -> 800 sec
! 369:1 = 2400 # 1 Blaze Rod -> 120 sec
! 25:1 = 300 # 1 Note Block -> 15 sec
! 151:1 = 300 # 1 Daylight Sensor -> 15 sec
! 107:1 = 300 # 1 Fence Gate -> 15 sec
! 167:1 = 300 # 1 Trapdoor -> 15 sec
! 146:1 = 300 # 1 Trapped Chest -> 15 sec
! 72:1 = 300 # 1 Pressure Plate -> 15 sec
! 270:1 = 200 # 1 Wooden Pickaxe -> 10 sec
! 271:1 = 200 # 1 Wooden Axe -> 10 sec
! 269:1 = 200 # 1 Wooden Shovel -> 10 sec
! 290:1 = 200 # 1 Wooden Hoe -> 10 sec
! 268:1 = 200 # 1 Wooden Sword -> 10 sec
! CharCoal = 1600 # -> 80 sec
! Coal = 1600 # -> 80 sec
! WoodenSlab = 15 # -> 7.5 sec
! Planks = 300 # -> 15 sec
! Stick = 100 # -> 5 sec
! Fence = 300 # -> 15 sec
! WoodStairs = 300 # -> 15 sec
! Workbench = 300 # -> 15 sec
! Bookshelf = 300 # -> 15 sec
! Chest = 300 # -> 15 sec
! Jukebox = 300 # -> 15 sec
! Lavabucket = 20000 # -> 1000 sec
! Log = 300 # -> 15 sec
! Sapling = 100 # -> 5 sec
! CoalBlock = 16000 # -> 800 sec
! BlazeRod = 2400 # -> 120 sec
! NoteBlock = 300 # -> 15 sec
! DaylightSensor = 300 # -> 15 sec
! FenceGate = 300 # -> 15 sec
! Trapdoor = 300 # -> 15 sec
! TrappedChest = 300 # -> 15 sec
! WoodPlate = 300 # -> 15 sec
! WoodPickaxe = 200 # -> 10 sec
! WoodAxe = 200 # -> 10 sec
! WoodShovel = 200 # -> 10 sec
! WoodHoe = 200 # -> 10 sec
! WoodSword = 200 # -> 10 sec

View File

@ -397,6 +397,7 @@ darkoakwoodstairs=164
roofedoakwoodstairs=164
haybale=170
carpet=171
hardenedclay=172
ironshovel=256
ironspade=256
ironpickaxe=257

View File

@ -2663,7 +2663,7 @@ static int tolua_cRoot_GetFurnaceRecipe(lua_State * tolua_S)
// Get the recipe for the input
cFurnaceRecipe * FR = cRoot::Get()->GetFurnaceRecipe();
const cFurnaceRecipe::Recipe * Recipe = FR->GetRecipeFrom(*Input);
const cFurnaceRecipe::cRecipe * Recipe = FR->GetRecipeFrom(*Input);
if (Recipe == NULL)
{
// There is no such furnace recipe for this input, return no value

View File

@ -105,7 +105,7 @@ protected:
NIBBLETYPE m_BlockMeta;
/// The recipe for the current input slot
const cFurnaceRecipe::Recipe * m_CurrentRecipe;
const cFurnaceRecipe::cRecipe * m_CurrentRecipe;
/// The item that is being smelted
cItem m_LastInput;

View File

@ -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() + (const long)FirstCommentSymbol, ParsingLine.end());
}
switch (ParsingLine[0])
{
case '#':
@ -103,97 +109,131 @@ void cFurnaceRecipe::ReloadRecipes(void)
void cFurnaceRecipe::AddFuelFromLine(const AString & a_Line, int a_LineNum)
void cFurnaceRecipe::AddFuelFromLine(const AString & a_Line, unsigned 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
)
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:
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.release();
Fuel.BurnTime = BurnTime;
m_pState->Fuel.push_back(Fuel);
}
void cFurnaceRecipe::AddRecipeFromLine(const AString & a_Line, int a_LineNum)
void cFurnaceRecipe::AddRecipeFromLine(const AString & a_Line, unsigned 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;
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;
}
// 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.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))
{
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;
}
}
}

View File

@ -19,23 +19,23 @@ public:
void ReloadRecipes(void);
struct Fuel
struct cFuel
{
cItem * In;
int BurnTime; ///< How long this fuel burns, in ticks
};
struct Recipe
struct cRecipe
{
cItem * In;
cItem * Out;
int CookTime; ///< How long this recipe takes to smelt, in ticks
};
/// Returns a recipe for the specified input, NULL if no recipe found
const Recipe * GetRecipeFrom(const cItem & a_Ingredient) const;
/** Returns a recipe for the specified input, NULL if no recipe found */
const cRecipe * GetRecipeFrom(const cItem & a_Ingredient) const;
/// Returns the amount of time that the specified fuel burns, in ticks
/** Returns the amount of time that the specified fuel burns, in ticks */
int GetBurnTime(const cItem & a_Fuel) const;
private:
@ -43,33 +43,14 @@ private:
/** Parses the fuel contained in the line, adds it to m_pState's fuels.
Logs a warning to the console on input error. */
void AddFuelFromLine(const AString & a_Line, int a_LineNum);
void AddFuelFromLine(const AString & a_Line, unsigned int a_LineNum);
/** Parses the recipe contained in the line, adds it to m_pState's recipes.
Logs a warning to the console on input error. */
void AddRecipeFromLine(const AString & a_Line, int a_LineNum);
/** 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);
void AddRecipeFromLine(const AString & a_Line, unsigned int a_LineNum);
/** Parses an item string in the format "<ItemType>[:<Damage>][,<Amount>]", returns true if successful. */
bool ParseItem(const AString & a_String, cItem & a_Item);
struct sFurnaceRecipeState;
sFurnaceRecipeState * m_pState;