diff options
-rw-r--r-- | src/Entities/ArrowEntity.cpp | 6 | ||||
-rw-r--r-- | src/Entities/Pickup.cpp | 33 | ||||
-rw-r--r-- | src/FurnaceRecipe.cpp | 268 | ||||
-rw-r--r-- | src/FurnaceRecipe.h | 30 | ||||
-rw-r--r-- | src/Item.cpp | 2 | ||||
-rw-r--r-- | src/Mobs/Pig.cpp | 20 | ||||
-rw-r--r-- | src/Mobs/Pig.h | 1 |
7 files changed, 257 insertions, 103 deletions
diff --git a/src/Entities/ArrowEntity.cpp b/src/Entities/ArrowEntity.cpp index 712ae3879..4727f67a9 100644 --- a/src/Entities/ArrowEntity.cpp +++ b/src/Entities/ArrowEntity.cpp @@ -134,7 +134,7 @@ void cArrowEntity::CollectedBy(cPlayer * a_Dest) { if (m_IsInGround && !m_bIsCollected && CanPickup(*a_Dest)) { - // The arrow won't added to the inventory, when the player is creative + // Do not add the arrow to the inventory when the player is in creative: if (!a_Dest->IsGameModeCreative()) { int NumAdded = a_Dest->GetInventory().AddItem(E_ITEM_ARROW); @@ -145,7 +145,9 @@ void cArrowEntity::CollectedBy(cPlayer * a_Dest) } } - m_World->BroadcastCollectPickup((const cPickup &)*this, *a_Dest); + // TODO: BroadcastCollectPickup needs a cPickup, which we don't have + // m_World->BroadcastCollectPickup(*this, *a_Dest); + m_bIsCollected = true; cFastRandom Random; diff --git a/src/Entities/Pickup.cpp b/src/Entities/Pickup.cpp index 0fd006485..10b6bbd5c 100644 --- a/src/Entities/Pickup.cpp +++ b/src/Entities/Pickup.cpp @@ -30,7 +30,7 @@ public: virtual bool Item(cEntity * a_Entity) override { - if (!a_Entity->IsPickup() || (a_Entity->GetUniqueID() == m_Pickup->GetUniqueID()) || a_Entity->IsDestroyed()) + if (!a_Entity->IsPickup() || (a_Entity->GetUniqueID() <= m_Pickup->GetUniqueID()) || a_Entity->IsDestroyed()) { return false; } @@ -38,10 +38,31 @@ public: Vector3d EntityPos = a_Entity->GetPosition(); double Distance = (EntityPos - m_Position).Length(); - if ((Distance < 1.2) && ((cPickup *)a_Entity)->GetItem().IsEqual(m_Pickup->GetItem())) + cItem & Item = ((cPickup *)a_Entity)->GetItem(); + if ((Distance < 1.2) && Item.IsEqual(m_Pickup->GetItem())) { - m_Pickup->GetItem().AddCount(((cPickup *)a_Entity)->GetItem().m_ItemCount); - a_Entity->Destroy(); + short CombineCount = Item.m_ItemCount; + if ((CombineCount + m_Pickup->GetItem().m_ItemCount) > Item.GetMaxStackSize()) + { + CombineCount = Item.GetMaxStackSize() - m_Pickup->GetItem().m_ItemCount; + } + + if (CombineCount <= 0) + { + return false; + } + + m_Pickup->GetItem().AddCount((char)CombineCount); + Item.m_ItemCount -= CombineCount; + + if (Item.m_ItemCount <= 0) + { + a_Entity->Destroy(); + } + else + { + a_Entity->GetWorld()->BroadcastEntityMetadata(*a_Entity); + } m_FoundMatchingPickup = true; } return false; @@ -129,7 +150,7 @@ void cPickup::Tick(float a_Dt, cChunk & a_Chunk) } } - if (!IsDestroyed()) // Don't try to combine if someone has tried to combine me + if (!IsDestroyed() && (m_Item.m_ItemCount < m_Item.GetMaxStackSize())) // Don't combine into an already full pickup { cPickupCombiningCallback PickupCombiningCallback(GetPosition(), this); m_World->ForEachEntity(PickupCombiningCallback); // Not ForEachEntityInChunk, otherwise pickups don't combine across chunk boundaries @@ -206,7 +227,7 @@ bool cPickup::CollectedBy(cPlayer * a_Dest) m_World->BroadcastCollectPickup(*this, *a_Dest); // Also send the "pop" sound effect with a somewhat random pitch (fast-random using EntityID ;) m_World->BroadcastSoundEffect("random.pop",(int)GetPosX() * 8, (int)GetPosY() * 8, (int)GetPosZ() * 8, 0.5, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); - if (m_Item.m_ItemCount == 0) + if (m_Item.m_ItemCount <= 0) { // All of the pickup has been collected, schedule the pickup for destroying m_bCollected = true; diff --git a/src/FurnaceRecipe.cpp b/src/FurnaceRecipe.cpp index 2cb204ccf..8add9610c 100644 --- a/src/FurnaceRecipe.cpp +++ b/src/FurnaceRecipe.cpp @@ -5,7 +5,8 @@ #include "Item.h" #include <fstream> -#include <sstream> + +#define FURNACE_RECIPE_FILE "furnace.txt" @@ -54,128 +55,207 @@ void cFurnaceRecipe::ReloadRecipes(void) ClearRecipes(); LOGD("Loading furnace recipes..."); - std::ifstream f; - char a_File[] = "furnace.txt"; - f.open(a_File, std::ios::in); - + std::ifstream f(FURNACE_RECIPE_FILE, std::ios::in); if (!f.good()) { - f.close(); - LOG("Could not open the furnace recipes file \"%s\"", a_File); + LOG("Could not open the furnace recipes file \"%s\". No furnace recipes are available.", FURNACE_RECIPE_FILE); return; } + + unsigned int LineNum = 0; + AString ParsingLine; - // TODO: Replace this messy parse with a high-level-structured one (ReadLine / ProcessLine) - bool bSyntaxError = false; - while (f.good()) + while (std::getline(f, ParsingLine)) { - char c; + LineNum++; + ParsingLine.erase(std::remove_if(ParsingLine.begin(), ParsingLine.end(), isspace), ParsingLine.end()); // Remove ALL whitespace from the line + if (ParsingLine.empty()) + { + continue; + } - ////////////////////////////////////////////////////////////////////////// - // comments - f >> c; - f.unget(); - if( c == '#' ) + switch (ParsingLine[0]) { - while( f.good() && c != '\n' ) + case '#': { - f.get( c ); + // Comment + break; } - continue; - } + + 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, 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) + + 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 + ) + { + 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); +} + + + + + +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 + + 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 + ) + { + 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); +} + + + - ////////////////////////////////////////////////////////////////////////// - // Line breaks - f.get( c ); - while( f.good() && ( c == '\n' || c == '\r' ) ) { f.get( c ); } - if (f.eof()) +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) + { + End = a_Text.find_first_not_of(a_Delimiter, a_Begin); + } + else + { + End = a_Text.find_first_of(a_Delimiter, a_Begin); + if (End == AString::npos) { - break; + PrintParseError(a_Line, a_Begin, a_Delimiter); + return false; } - f.unget(); + } + + // 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 + return true; +} + + + + - ////////////////////////////////////////////////////////////////////////// - // Check for fuel - f >> c; - if( c == '!' ) // It's fuel :) +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))) { - // Read item - int IItemID = 0, IItemCount = 0, IItemHealth = 0; - f >> IItemID; - f >> c; if( c != ':' ) { bSyntaxError = true; break; } - f >> IItemCount; - - // Optional health - f >> c; - if( c != ':' ) - f.unget(); + 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 { - f >> IItemHealth; + End = a_Text.find_first_of(a_DelimiterTwo, Begin); + if (End == AString::npos) + { + PrintParseError(a_Line, Begin, a_DelimiterTwo); + return false; + } } - // Burn time - int BurnTime; - f >> c; if( c != '=' ) { bSyntaxError = true; break; } - f >> BurnTime; + // 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()); - // Add to fuel list - Fuel F; - F.In = new cItem( (ENUM_ITEM_ID) IItemID, (char)IItemCount, (short)IItemHealth ); - F.BurnTime = BurnTime; - m_pState->Fuel.push_back( F ); - continue; + a_Begin = End + 1; // Jump over delimiter + return true; } - f.unget(); - - ////////////////////////////////////////////////////////////////////////// - // Read items - int IItemID = 0, IItemCount = 0, IItemHealth = 0; - f >> IItemID; - f >> c; if( c != ':' ) { bSyntaxError = true; break; } - f >> IItemCount; - - // Optional health - f >> c; - if( c != ':' ) - f.unget(); else { - f >> IItemHealth; + 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); +} - int CookTime; - f >> c; if( c != '@' ) { bSyntaxError = true; break; } - f >> CookTime; - int OItemID = 0, OItemCount = 0, OItemHealth = 0; - f >> c; if( c != '=' ) { bSyntaxError = true; break; } - f >> OItemID; - f >> c; if( c != ':' ) { bSyntaxError = true; break; } - f >> OItemCount; - // Optional health - f >> c; - if( c != ':' ) - f.unget(); - else - { - f >> OItemHealth; - } - // 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 = CookTime; - m_pState->Recipes.push_back( R ); - } - if (bSyntaxError) - { - LOGERROR("ERROR: FurnaceRecipe, syntax error" ); - } - LOG("Loaded " SIZE_T_FMT " furnace recipes and " SIZE_T_FMT " fuels", m_pState->Recipes.size(), m_pState->Fuel.size()); + +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); } diff --git a/src/FurnaceRecipe.h b/src/FurnaceRecipe.h index 2f91e9bcb..77ed35a57 100644 --- a/src/FurnaceRecipe.h +++ b/src/FurnaceRecipe.h @@ -41,6 +41,36 @@ public: private: void ClearRecipes(void); + /** 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); + + /** 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); + + struct sFurnaceRecipeState; sFurnaceRecipeState * m_pState; }; diff --git a/src/Item.cpp b/src/Item.cpp index d6e8b224a..56ceae0b7 100644 --- a/src/Item.cpp +++ b/src/Item.cpp @@ -1,4 +1,4 @@ - + #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "Item.h" diff --git a/src/Mobs/Pig.cpp b/src/Mobs/Pig.cpp index e862f5aaa..1f77cf613 100644 --- a/src/Mobs/Pig.cpp +++ b/src/Mobs/Pig.cpp @@ -78,3 +78,23 @@ void cPig::OnRightClicked(cPlayer & a_Player) + + +void cPig::Tick(float a_Dt, cChunk & a_Chunk) +{ + super::Tick(a_Dt, a_Chunk); + + // If the attachee player is holding a carrot-on-stick, let them drive this pig: + if (m_bIsSaddled && (m_Attachee != NULL)) + { + if (m_Attachee->IsPlayer() && (m_Attachee->GetEquippedWeapon().m_ItemType == E_ITEM_CARROT_ON_STICK)) + { + MoveToPosition((m_Attachee->GetPosition()) + (m_Attachee->GetLookVector()*10)); + m_bMovingToDestination = true; + } + } +} + + + + diff --git a/src/Mobs/Pig.h b/src/Mobs/Pig.h index d434324c1..313af2f44 100644 --- a/src/Mobs/Pig.h +++ b/src/Mobs/Pig.h @@ -19,6 +19,7 @@ public: virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override; virtual void OnRightClicked(cPlayer & a_Player) override; + virtual void Tick(float a_Dt, cChunk & a_Chunk) override; virtual const cItem GetFollowedItem(void) const override { return cItem(E_ITEM_CARROT); } |