diff --git a/MCServer/furnace.txt b/MCServer/furnace.txt index 1e98583ba..d6177184b 100644 --- a/MCServer/furnace.txt +++ b/MCServer/furnace.txt @@ -88,3 +88,4 @@ ! 269:1 = 200 # 1 Wooden Shovel -> 10 sec ! 290:1 = 200 # 1 Wooden Hoe -> 10 sec ! 268:1 = 200 # 1 Wooden Sword -> 10 sec + diff --git a/lib/inifile/iniFile.cpp b/lib/inifile/iniFile.cpp index ea03f5d35..2bf6c91ed 100644 --- a/lib/inifile/iniFile.cpp +++ b/lib/inifile/iniFile.cpp @@ -447,6 +447,15 @@ bool cIniFile::SetValueI(const AString & a_KeyName, const AString & a_ValueName, +bool cIniFile::SetValueI(const AString & a_Keyname, const AString & a_ValueName, const Int64 a_Value, const bool a_CreateIfNotExists) +{ + return SetValue(a_Keyname, a_ValueName, Printf("%lld", a_Value), a_CreateIfNotExists); +} + + + + + bool cIniFile::SetValueF(const AString & a_KeyName, const AString & a_ValueName, double const a_Value, const bool a_CreateIfNotExists) { return SetValue(a_KeyName, a_ValueName, Printf("%f", a_Value), a_CreateIfNotExists); @@ -571,6 +580,24 @@ int cIniFile::GetValueSetI(const AString & keyname, const AString & valuename, c +Int64 cIniFile::GetValueSetI(const AString & keyname, const AString & valuename, const Int64 defValue) +{ + AString Data; + Printf(Data, "%lld", defValue); + AString resultstring = GetValueSet(keyname, valuename, Data); + Int64 result = defValue; +#ifdef _WIN32 + sscanf_s(resultstring.c_str(), "%lld", &result); +#else + sscanf(resultstring.c_str(), "%lld", &result); +#endif + return result; +} + + + + + bool cIniFile::DeleteValueByID(const int keyID, const int valueID) { if ((keyID < (int)keys.size()) && (valueID < (int)keys[keyID].names.size())) diff --git a/lib/inifile/iniFile.h b/lib/inifile/iniFile.h index 0bf1d917e..58fecd0cf 100644 --- a/lib/inifile/iniFile.h +++ b/lib/inifile/iniFile.h @@ -119,6 +119,7 @@ public: AString GetValueSet (const AString & keyname, const AString & valuename, const AString & defValue = ""); double GetValueSetF(const AString & keyname, const AString & valuename, const double defValue = 0.0); int GetValueSetI(const AString & keyname, const AString & valuename, const int defValue = 0); + Int64 GetValueSetI(const AString & keyname, const AString & valuename, const Int64 defValue = 0); bool GetValueSetB(const AString & keyname, const AString & valuename, const bool defValue = false) { return (GetValueSetI(keyname, valuename, defValue ? 1 : 0) != 0); @@ -141,6 +142,7 @@ public: bool SetValue (const int keyID, const int valueID, const AString & value); bool SetValue (const AString & a_KeyName, const AString & a_ValueName, const AString & a_Value, const bool a_CreateIfNotExists = true); bool SetValueI(const AString & a_KeyName, const AString & a_ValueName, const int a_Value, const bool a_CreateIfNotExists = true); + bool SetValueI(const AString & a_Keyname, const AString & a_ValueName, const Int64 a_Value, const bool a_CreateIfNotExists = true); bool SetValueB(const AString & a_KeyName, const AString & a_ValueName, const bool a_Value, const bool a_CreateIfNotExists = true) { return SetValueI(a_KeyName, a_ValueName, int(a_Value), a_CreateIfNotExists); diff --git a/src/BlockID.cpp b/src/BlockID.cpp index af96b4414..d145a2e5b 100644 --- a/src/BlockID.cpp +++ b/src/BlockID.cpp @@ -346,6 +346,37 @@ eDimension StringToDimension(const AString & a_DimensionString) +AString DimensionToString(eDimension a_Dimension) +{ + // Decode using a built-in map: + static struct + { + eDimension m_Dimension; + const char * m_String; + } DimensionMap[] = + { + { dimOverworld, "Overworld" }, + { dimNether, "Nether" }, + { dimEnd, "End" }, + }; + + for (size_t i = 0; i < ARRAYCOUNT(DimensionMap); i++) + { + if (DimensionMap[i].m_Dimension == a_Dimension) + { + return DimensionMap[i].m_String; + } + } // for i - DimensionMap[] + + // Not found + LOGWARNING("Unknown dimension: \"%i\". Setting to Overworld", (int)a_Dimension); + return "Overworld"; +} + + + + + /// Translates damage type constant to a string representation (built-in). AString DamageTypeToString(eDamageType a_DamageType) { diff --git a/src/BlockID.h b/src/BlockID.h index 997ee2cf9..08c576886 100644 --- a/src/BlockID.h +++ b/src/BlockID.h @@ -920,9 +920,14 @@ extern AString ItemToFullString(const cItem & a_Item); /// Translates a mob string ("ocelot") to mobtype (E_ENTITY_TYPE_OCELOT) extern int StringToMobType(const AString & a_MobString); -/// Translates a dimension string to dimension enum. Takes either a number or a dimension alias (built-in). Returns -1000 on failure +/// Translates a dimension string to dimension enum. Takes either a number or a dimension alias (built-in). Returns dimOverworld on failure extern eDimension StringToDimension(const AString & a_DimensionString); +/** Translates a dimension enum to dimension string. +Takes an eDimension enum value and returns "Overworld" on failure +*/ +extern AString DimensionToString(eDimension a_Dimension); + /// Translates damage type constant to a string representation (built-in). extern AString DamageTypeToString(eDamageType a_DamageType); diff --git a/src/BlockInfo.cpp b/src/BlockInfo.cpp index 3ec8f100b..602deb26d 100644 --- a/src/BlockInfo.cpp +++ b/src/BlockInfo.cpp @@ -450,6 +450,7 @@ void cBlockInfo::Initialize(cBlockInfoArray & a_Info) a_Info[E_BLOCK_CROPS ].m_IsSolid = false; a_Info[E_BLOCK_DANDELION ].m_IsSolid = false; a_Info[E_BLOCK_DETECTOR_RAIL ].m_IsSolid = false; + a_Info[E_BLOCK_END_PORTAL ].m_IsSolid = false; a_Info[E_BLOCK_FIRE ].m_IsSolid = false; a_Info[E_BLOCK_FLOWER ].m_IsSolid = false; a_Info[E_BLOCK_HEAVY_WEIGHTED_PRESSURE_PLATE].m_IsSolid = false; diff --git a/src/Blocks/BlockBed.cpp b/src/Blocks/BlockBed.cpp index 80ac18560..cd5783f58 100644 --- a/src/Blocks/BlockBed.cpp +++ b/src/Blocks/BlockBed.cpp @@ -108,7 +108,7 @@ void cBlockBedHandler::OnUse(cChunkInterface & a_ChunkInterface, cWorldInterface NIBBLETYPE Meta = a_ChunkInterface.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ); if (Meta & 0x4) { - a_Player->SendMessageFailure("This bed is occupied."); + a_Player->SendMessageFailure("This bed is occupied"); } else { @@ -133,6 +133,8 @@ void cBlockBedHandler::OnUse(cChunkInterface & a_ChunkInterface, cWorldInterface a_ChunkInterface.SetBlockMeta(a_BlockX, a_BlockY, a_BlockZ, Meta | 0x4); // Where 0x4 = occupied bit a_Player->SetIsInBed(true); + a_Player->SetBedPos(Vector3i(a_BlockX, a_BlockY, a_BlockZ)); + a_Player->SendMessageSuccess("Home position set successfully"); cTimeFastForwardTester Tester; if (a_WorldInterface.ForEachPlayer(Tester)) diff --git a/src/Blocks/BlockCauldron.h b/src/Blocks/BlockCauldron.h index 41b79b6c3..e0f86f4cb 100644 --- a/src/Blocks/BlockCauldron.h +++ b/src/Blocks/BlockCauldron.h @@ -58,6 +58,24 @@ public: { return true; } + + virtual void OnUpdate(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cBlockPluginInterface & a_PluginInterface, cChunk & a_Chunk, int a_RelX, int a_RelY, int a_RelZ) override + { + int BlockX = a_RelX + a_Chunk.GetPosX() * cChunkDef::Width; + int BlockZ = a_RelZ + a_Chunk.GetPosZ() * cChunkDef::Width; + if (!a_WorldInterface.IsWeatherWetAt(BlockX, BlockZ) || (a_RelY != a_WorldInterface.GetHeight(BlockX, BlockZ))) + { + // It's not raining at our current location or we do not have a direct view of the sky + // We cannot eat the rain :( + return; + } + + NIBBLETYPE Meta = a_Chunk.GetMeta(a_RelX, a_RelY, a_RelZ); + if (Meta < 3) + { + a_Chunk.SetMeta(a_RelX, a_RelY, a_RelZ, Meta + 1); + } + } } ; diff --git a/src/Blocks/WorldInterface.h b/src/Blocks/WorldInterface.h index a75ee9e26..d1c6f9bfc 100644 --- a/src/Blocks/WorldInterface.h +++ b/src/Blocks/WorldInterface.h @@ -46,6 +46,12 @@ public: virtual void SetTimeOfDay(Int64 a_TimeOfDay) = 0; + /** Returns true if it is raining, stormy or snowing at the specified location. This takes into account biomes. */ + virtual bool IsWeatherWetAt(int a_BlockX, int a_BlockZ) = 0; + + /** Returns the world height at the specified coords; waits for the chunk to get loaded / generated */ + virtual int GetHeight(int a_BlockX, int a_BlockZ) = 0; + /** Wakes up the simulators for the specified block */ virtual void WakeUpSimulators(int a_BlockX, int a_BlockY, int a_BlockZ) = 0; diff --git a/src/Chunk.cpp b/src/Chunk.cpp index 10bc2ff23..b83036b6d 100644 --- a/src/Chunk.cpp +++ b/src/Chunk.cpp @@ -577,39 +577,34 @@ void cChunk::Tick(float a_Dt) m_IsDirty = (*itr)->Tick(a_Dt, *this) | m_IsDirty; } - // Tick all entities in this chunk (except mobs): - for (cEntityList::iterator itr = m_Entities.begin(); itr != m_Entities.end(); ++itr) - { - // Mobs are tickes inside MobTick (as we don't have to tick them if they are far away from players) - if (!((*itr)->IsMob())) + for (cEntityList::iterator itr = m_Entities.begin(); itr != m_Entities.end();) + { + if (!((*itr)->IsMob())) // Mobs are ticked inside cWorld::TickMobs() (as we don't have to tick them if they are far away from players) { + // Tick all entities in this chunk (except mobs): (*itr)->Tick(a_Dt, *this); } - } // for itr - m_Entitites[] - - // Remove all entities that were scheduled for removal: - for (cEntityList::iterator itr = m_Entities.begin(); itr != m_Entities.end();) - { - if ((*itr)->IsDestroyed()) - { - LOGD("Destroying entity #%i (%s)", (*itr)->GetUniqueID(), (*itr)->GetClass()); - cEntity * ToDelete = *itr; - itr = m_Entities.erase(itr); - delete ToDelete; - ToDelete = NULL; - continue; - } - ++itr; - } // for itr - m_Entitites[] - - // If any entity moved out of the chunk, move it to the neighbor: - for (cEntityList::iterator itr = m_Entities.begin(); itr != m_Entities.end();) - { - if ( + + if ((*itr)->IsDestroyed()) // Remove all entities that were scheduled for removal: + { + LOGD("Destroying entity #%i (%s)", (*itr)->GetUniqueID(), (*itr)->GetClass()); + MarkDirty(); + cEntity * ToDelete = *itr; + itr = m_Entities.erase(itr); + delete ToDelete; + } + else if ((*itr)->IsWorldTravellingFrom(m_World)) // Remove all entities that are travelling to another world: + { + MarkDirty(); + (*itr)->SetWorldTravellingFrom(NULL); + itr = m_Entities.erase(itr); + } + else if ( // If any entity moved out of the chunk, move it to the neighbor: ((*itr)->GetChunkX() != m_PosX) || ((*itr)->GetChunkZ() != m_PosZ) ) { + MarkDirty(); MoveEntityToNewChunk(*itr); itr = m_Entities.erase(itr); } @@ -617,7 +612,7 @@ void cChunk::Tick(float a_Dt) { ++itr; } - } + } // for itr - m_Entitites[] ApplyWeatherToTop(); } @@ -902,7 +897,6 @@ void cChunk::ApplyWeatherToTop() } break; } // case (snowy biomes) - // TODO: Rainy biomes should check for farmland and cauldrons default: { break; @@ -1798,7 +1792,7 @@ void cChunk::RemoveBlockEntity( cBlockEntity* a_BlockEntity) -bool cChunk::AddClient(cClientHandle* a_Client) +bool cChunk::AddClient(cClientHandle * a_Client) { for (cClientHandleList::iterator itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) { @@ -1829,7 +1823,7 @@ bool cChunk::AddClient(cClientHandle* a_Client) -void cChunk::RemoveClient( cClientHandle* a_Client) +void cChunk::RemoveClient(cClientHandle * a_Client) { for (cClientHandleList::iterator itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) { @@ -1837,7 +1831,7 @@ void cChunk::RemoveClient( cClientHandle* a_Client) { continue; } - + m_LoadedByClient.erase(itr); if (!a_Client->IsDestroyed()) @@ -1862,7 +1856,7 @@ void cChunk::RemoveClient( cClientHandle* a_Client) -bool cChunk::HasClient( cClientHandle* a_Client) +bool cChunk::HasClient(cClientHandle * a_Client) { for (cClientHandleList::const_iterator itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr) { @@ -1893,9 +1887,9 @@ void cChunk::AddEntity(cEntity * a_Entity) { MarkDirty(); } - + ASSERT(std::find(m_Entities.begin(), m_Entities.end(), a_Entity) == m_Entities.end()); // Not there already - + m_Entities.push_back(a_Entity); } @@ -1905,17 +1899,12 @@ void cChunk::AddEntity(cEntity * a_Entity) void cChunk::RemoveEntity(cEntity * a_Entity) { - size_t SizeBefore = m_Entities.size(); m_Entities.remove(a_Entity); - size_t SizeAfter = m_Entities.size(); - - if (SizeBefore != SizeAfter) + + // Mark as dirty if it was a server-generated entity: + if (!a_Entity->IsPlayer()) { - // Mark as dirty if it was a server-generated entity: - if (!a_Entity->IsPlayer()) - { - MarkDirty(); - } + MarkDirty(); } } diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index e4ad218a2..aaed483e2 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -120,7 +120,8 @@ cClientHandle::~cClientHandle() } if (World != NULL) { - World->RemovePlayer(m_Player); + m_Player->SetWorldTravellingFrom(NULL); // Make sure that the player entity is actually removed + World->RemovePlayer(m_Player); // Must be called before cPlayer::Destroy() as otherwise cChunk tries to delete the player, and then we do it again m_Player->Destroy(); } delete m_Player; @@ -1795,8 +1796,7 @@ void cClientHandle::RemoveFromWorld(void) m_Protocol->SendUnloadChunk(itr->m_ChunkX, itr->m_ChunkZ); } // for itr - Chunks[] - // StreamChunks() called in cPlayer::MoveToWorld() after new world has been set - // Meanwhile here, we set last streamed values to bogus ones so everything is resent + // Here, we set last streamed values to bogus ones so everything is resent m_LastStreamedChunkX = 0x7fffffff; m_LastStreamedChunkZ = 0x7fffffff; m_HasSentPlayerChunk = false; @@ -2377,9 +2377,9 @@ void cClientHandle::SendRemoveEntityEffect(const cEntity & a_Entity, int a_Effec -void cClientHandle::SendRespawn(const cWorld & a_World, bool a_ShouldIgnoreDimensionChecks) +void cClientHandle::SendRespawn(eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) { - m_Protocol->SendRespawn(a_World, a_ShouldIgnoreDimensionChecks); + m_Protocol->SendRespawn(a_Dimension, a_ShouldIgnoreDimensionChecks); } diff --git a/src/ClientHandle.h b/src/ClientHandle.h index 50ed596d5..48eba4de1 100644 --- a/src/ClientHandle.h +++ b/src/ClientHandle.h @@ -161,7 +161,7 @@ public: void SendPlayerSpawn (const cPlayer & a_Player); void SendPluginMessage (const AString & a_Channel, const AString & a_Message); // Exported in ManualBindings.cpp void SendRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID); - void SendRespawn (const cWorld & a_World, bool a_ShouldIgnoreDimensionChecks = false); + void SendRespawn (eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks = false); void SendExperience (void); void SendExperienceOrb (const cExpOrb & a_ExpOrb); void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode); diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index 6c3d7b40c..da578013d 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -38,6 +38,7 @@ cEntity::cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, d , m_Gravity(-9.81f) , m_LastPos(a_X, a_Y, a_Z) , m_IsInitialized(false) + , m_WorldTravellingFrom(NULL) , m_EntityType(a_EntityType) , m_World(NULL) , m_IsFireproof(false) @@ -617,9 +618,12 @@ void cEntity::Tick(float a_Dt, cChunk & a_Chunk) // Handle drowning HandleAir(); } - - // None of the above functions change position, we remain in the chunk of NextChunk - HandlePhysics(a_Dt, *NextChunk); + + if (!DetectPortal()) // Our chunk is invalid if we have moved to another world + { + // None of the above functions changed position, we remain in the chunk of NextChunk + HandlePhysics(a_Dt, *NextChunk); + } } } @@ -856,7 +860,7 @@ void cEntity::TickBurning(cChunk & a_Chunk) // Remember the current burning state: bool HasBeenBurning = (m_TicksLeftBurning > 0); - if (m_World->IsWeatherWet()) + if (GetWorld()->IsWeatherWetAt(POSX_TOINT, POSZ_TOINT)) { if (POSY_TOINT > m_World->GetHeight(POSX_TOINT, POSZ_TOINT)) { @@ -1027,6 +1031,184 @@ void cEntity::DetectCacti(void) +bool cEntity::DetectPortal() +{ + if (GetWorld()->GetDimension() == dimOverworld) + { + if (GetWorld()->GetNetherWorldName().empty() && GetWorld()->GetEndWorldName().empty()) + { + // Teleportation to either dimension not enabled, don't bother proceeding + return false; + } + } + else if (GetWorld()->GetLinkedOverworldName().empty()) + { + // Overworld teleportation disabled, abort + return false; + } + + int X = POSX_TOINT, Y = POSY_TOINT, Z = POSZ_TOINT; + if ((Y > 0) && (Y < cChunkDef::Height)) + { + switch (GetWorld()->GetBlock(X, Y, Z)) + { + case E_BLOCK_NETHER_PORTAL: + { + if (m_PortalCooldownData.m_ShouldPreventTeleportation) + { + // Just exited a portal, don't teleport again + return false; + } + + if (IsPlayer() && !((cPlayer *)this)->IsGameModeCreative() && m_PortalCooldownData.m_TicksDelayed != 80) + { + // Delay teleportation for four seconds if the entity is a non-creative player + m_PortalCooldownData.m_TicksDelayed++; + return false; + } + m_PortalCooldownData.m_TicksDelayed = 0; + + switch (GetWorld()->GetDimension()) + { + case dimNether: + { + if (GetWorld()->GetLinkedOverworldName().empty()) + { + return false; + } + + m_PortalCooldownData.m_ShouldPreventTeleportation = true; // Stop portals from working on respawn + + if (IsPlayer()) + { + ((cPlayer *)this)->GetClientHandle()->SendRespawn(dimOverworld); // Send a respawn packet before world is loaded/generated so the client isn't left in limbo + } + + return MoveToWorld(cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedOverworldName()), false); + } + case dimOverworld: + { + if (GetWorld()->GetNetherWorldName().empty()) + { + return false; + } + + m_PortalCooldownData.m_ShouldPreventTeleportation = true; + + if (IsPlayer()) + { + ((cPlayer *)this)->AwardAchievement(achEnterPortal); + ((cPlayer *)this)->GetClientHandle()->SendRespawn(dimNether); + } + + return MoveToWorld(cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetNetherWorldName(), dimNether, GetWorld()->GetName()), false); + } + default: return false; + } + } + case E_BLOCK_END_PORTAL: + { + if (m_PortalCooldownData.m_ShouldPreventTeleportation) + { + return false; + } + + switch (GetWorld()->GetDimension()) + { + case dimEnd: + { + if (GetWorld()->GetLinkedOverworldName().empty()) + { + return false; + } + + m_PortalCooldownData.m_ShouldPreventTeleportation = true; + + if (IsPlayer()) + { + cPlayer * Player = (cPlayer *)this; + Player->TeleportToCoords(Player->GetLastBedPos().x, Player->GetLastBedPos().y, Player->GetLastBedPos().z); + Player->GetClientHandle()->SendRespawn(dimOverworld); + } + + return MoveToWorld(cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedOverworldName()), false); + } + case dimOverworld: + { + if (GetWorld()->GetEndWorldName().empty()) + { + return false; + } + + m_PortalCooldownData.m_ShouldPreventTeleportation = true; + + if (IsPlayer()) + { + ((cPlayer *)this)->AwardAchievement(achEnterTheEnd); + ((cPlayer *)this)->GetClientHandle()->SendRespawn(dimEnd); + } + + return MoveToWorld(cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetEndWorldName(), dimEnd, GetWorld()->GetName()), false); + } + default: return false; + } + } + default: break; + } + } + + // Allow portals to work again + m_PortalCooldownData.m_ShouldPreventTeleportation = false; + m_PortalCooldownData.m_TicksDelayed = 0; + return false; +} + + + + + +bool cEntity::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn) +{ + UNUSED(a_ShouldSendRespawn); + ASSERT(a_World != NULL); + + if (GetWorld() == a_World) + { + // Don't move to same world + return false; + } + + // Remove all links to the old world + SetWorldTravellingFrom(GetWorld()); // cChunk::Tick() handles entity removal + GetWorld()->BroadcastDestroyEntity(*this); + + // Queue add to new world + a_World->AddEntity(this); + SetWorld(a_World); + + return true; +} + + + + + +bool cEntity::MoveToWorld(const AString & a_WorldName, bool a_ShouldSendRespawn) +{ + cWorld * World = cRoot::Get()->GetWorld(a_WorldName); + if (World == NULL) + { + LOG("%s: Couldn't find world \"%s\".", __FUNCTION__, a_WorldName.c_str()); + return false; + } + + return DoMoveToWorld(World, a_ShouldSendRespawn); +} + + + + + void cEntity::SetSwimState(cChunk & a_Chunk) { int RelY = (int)floor(GetPosY() + 0.1); diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h index efca60a6c..e66194ca2 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -336,6 +336,11 @@ public: /** Detects the time for application of cacti damage */ virtual void DetectCacti(void); + + /** Detects whether we are in a portal block and begins teleportation procedures if so + Returns true if MoveToWorld() was called, false if not + */ + virtual bool DetectPortal(void); /// Handles when the entity is in the void virtual void TickInVoid(cChunk & a_Chunk); @@ -378,8 +383,22 @@ public: /// Teleports to the coordinates specified virtual void TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ); + + /** Moves entity to specified world, taking a world pointer */ + bool MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn = true) { return DoMoveToWorld(a_World, a_ShouldSendRespawn); } + + /** Moves entity to specified world, taking a world name */ + bool MoveToWorld(const AString & a_WorldName, bool a_ShouldSendRespawn = true); // tolua_end + + virtual bool DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn); + + /** Returns if the entity is travelling away from a specified world */ + bool IsWorldTravellingFrom(cWorld * a_World) const { return (m_WorldTravellingFrom == a_World); } + + /** Sets the world the entity will be leaving */ + void SetWorldTravellingFrom(cWorld * a_World) { m_WorldTravellingFrom = a_World; } /// Updates clients of changes in the entity. virtual void BroadcastMovementUpdate(const cClientHandle * a_Exclude = NULL); @@ -482,6 +501,12 @@ protected: /** True when entity is initialised (Initialize()) and false when destroyed pending deletion (Destroy()) */ bool m_IsInitialized; + /** World entity is travelling from + Set to a valid world pointer by MoveToWorld; reset to NULL when the entity is removed from the old world + Can't be a simple boolean as context switches between worlds may leave the new chunk processing (and therefore immediately removing) the entity before the old chunk could remove it + */ + cWorld * m_WorldTravellingFrom; + eEntityType m_EntityType; cWorld * m_World; @@ -503,7 +528,6 @@ protected: /// Time, in ticks, since the last damage dealt by the void. Reset to zero when moving out of the void. int m_TicksSinceLastVoidDamage; - /** Does the actual speed-setting. The default implementation just sets the member variable value; overrides can provide further processing, such as forcing players to move at the given speed. */ @@ -523,6 +547,21 @@ protected: /** Air level of a mobile */ int m_AirLevel; int m_AirTickTimer; + + /** Structure storing the portal delay timer and cooldown boolean */ + struct sPortalCooldownData + { + /** Ticks since entry of portal, used to delay teleportation */ + unsigned short m_TicksDelayed; + + /** Whether the entity has just exited the portal, and should therefore not be teleported again + This prevents teleportation loops, and is reset when the entity has moved out of the portal + */ + bool m_ShouldPreventTeleportation; + }; + + /** Portal delay timer and cooldown boolean data */ + sPortalCooldownData m_PortalCooldownData; /** The number of ticks this entity has been alive for */ long int m_TicksAlive; diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index afe5965aa..ffdcca2ec 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -88,13 +88,14 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) : m_PlayerName = a_PlayerName; - if (!LoadFromDisk()) + cWorld * World = NULL; + if (!LoadFromDisk(World)) { m_Inventory.Clear(); - cWorld * DefaultWorld = cRoot::Get()->GetDefaultWorld(); - SetPosX(DefaultWorld->GetSpawnX()); - SetPosY(DefaultWorld->GetSpawnY()); - SetPosZ(DefaultWorld->GetSpawnZ()); + SetPosX(World->GetSpawnX()); + SetPosY(World->GetSpawnY()); + SetPosZ(World->GetSpawnZ()); + SetBedPos(Vector3i((int)World->GetSpawnX(), (int)World->GetSpawnY(), (int)World->GetSpawnZ())); LOGD("Player \"%s\" is connecting for the first time, spawning at default world spawn {%.2f, %.2f, %.2f}", a_PlayerName.c_str(), GetPosX(), GetPosY(), GetPosZ() @@ -107,11 +108,6 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) : if (m_GameMode == gmNotSet) { - cWorld * World = cRoot::Get()->GetWorld(GetLoadedWorldName()); - if (World == NULL) - { - World = cRoot::Get()->GetDefaultWorld(); - } if (World->IsGameModeCreative()) { m_CanFly = true; @@ -140,8 +136,6 @@ cPlayer::~cPlayer(void) SaveToDisk(); - m_World->RemovePlayer( this); - m_ClientHandle = NULL; delete m_InventoryWindow; @@ -157,8 +151,6 @@ cPlayer::~cPlayer(void) void cPlayer::Destroyed() { CloseWindow(false); - - m_ClientHandle = NULL; } @@ -983,12 +975,12 @@ void cPlayer::Respawn(void) m_LifetimeTotalXp = 0; // ToDo: send score to client? How? - m_ClientHandle->SendRespawn(*m_World, true); + m_ClientHandle->SendRespawn(GetWorld()->GetDimension(), true); // Extinguish the fire: StopBurning(); - TeleportToCoords(GetWorld()->GetSpawnX(), GetWorld()->GetSpawnY(), GetWorld()->GetSpawnZ()); + TeleportToCoords(GetLastBedPos().x, GetLastBedPos().y, GetLastBedPos().z); SetVisible(true); } @@ -1617,29 +1609,29 @@ void cPlayer::TossItems(const cItems & a_Items) -bool cPlayer::MoveToWorld(const char * a_WorldName) +bool cPlayer::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn) { - cWorld * World = cRoot::Get()->GetWorld(a_WorldName); - if (World == NULL) + ASSERT(a_World != NULL); + + if (GetWorld() == a_World) { - LOG("%s: Couldn't find world \"%s\".", __FUNCTION__, a_WorldName); + // Don't move to same world return false; } // Send the respawn packet: - if (m_ClientHandle != NULL) + if (a_ShouldSendRespawn && (m_ClientHandle != NULL)) { - m_ClientHandle->SendRespawn(*World); + m_ClientHandle->SendRespawn(a_World->GetDimension()); } - // Remove all links to the old world - m_World->RemovePlayer(this); - - // If the dimension is different, we can send the respawn packet - // http://wiki.vg/Protocol#0x09 says "don't send if dimension is the same" as of 2013_07_02 + // Remove player from the old world + SetWorldTravellingFrom(GetWorld()); // cChunk handles entity removal + GetWorld()->RemovePlayer(this); // Queue adding player to the new world, including all the necessary adjustments to the object - World->AddPlayer(this); + a_World->AddPlayer(this); + SetWorld(a_World); // Chunks may be streamed before cWorld::AddPlayer() sets the world to the new value return true; } @@ -1687,13 +1679,12 @@ void cPlayer::LoadPermissionsFromDisk() - -bool cPlayer::LoadFromDisk(void) +bool cPlayer::LoadFromDisk(cWorldPtr & a_World) { LoadPermissionsFromDisk(); // Load from the UUID file: - if (LoadFromFile(GetUUIDFileName(m_UUID))) + if (LoadFromFile(GetUUIDFileName(m_UUID), a_World)) { return true; } @@ -1702,7 +1693,7 @@ bool cPlayer::LoadFromDisk(void) AString OfflineUUID = cClientHandle::GenerateOfflineUUID(GetName()); if (cRoot::Get()->GetServer()->ShouldLoadOfflinePlayerData()) { - if (LoadFromFile(GetUUIDFileName(OfflineUUID))) + if (LoadFromFile(GetUUIDFileName(OfflineUUID), a_World)) { return true; } @@ -1712,7 +1703,7 @@ bool cPlayer::LoadFromDisk(void) if (cRoot::Get()->GetServer()->ShouldLoadNamedPlayerData()) { AString OldStyleFileName = Printf("players/%s.json", GetName().c_str()); - if (LoadFromFile(OldStyleFileName)) + if (LoadFromFile(OldStyleFileName, a_World)) { // Save in new format and remove the old file if (SaveToDisk()) @@ -1727,6 +1718,11 @@ bool cPlayer::LoadFromDisk(void) LOG("Player data file not found for %s (%s, offline %s), will be reset to defaults.", GetName().c_str(), m_UUID.c_str(), OfflineUUID.c_str() ); + + if (a_World == NULL) + { + a_World = cRoot::Get()->GetDefaultWorld(); + } return false; } @@ -1734,7 +1730,7 @@ bool cPlayer::LoadFromDisk(void) -bool cPlayer::LoadFromFile(const AString & a_FileName) +bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World) { // Load the data from the file: cFile f; @@ -1799,6 +1795,11 @@ bool cPlayer::LoadFromFile(const AString & a_FileName) cEnderChestEntity::LoadFromJson(root["enderchestinventory"], m_EnderChestContents); m_LoadedWorldName = root.get("world", "world").asString(); + a_World = cRoot::Get()->GetWorld(GetLoadedWorldName(), true); + + m_LastBedPos.x = root.get("SpawnX", a_World->GetSpawnX()).asInt(); + m_LastBedPos.y = root.get("SpawnY", a_World->GetSpawnY()).asInt(); + m_LastBedPos.z = root.get("SpawnZ", a_World->GetSpawnZ()).asInt(); // Load the player stats. // We use the default world name (like bukkit) because stats are shared between dimensions/worlds. @@ -1806,7 +1807,7 @@ bool cPlayer::LoadFromFile(const AString & a_FileName) StatSerializer.Load(); LOGD("Player %s was read from file \"%s\", spawning at {%.2f, %.2f, %.2f} in world \"%s\"", - GetName().c_str(), a_FileName.c_str(), GetPosX(), GetPosY(), GetPosZ(), m_LoadedWorldName.c_str() + GetName().c_str(), a_FileName.c_str(), GetPosX(), GetPosY(), GetPosZ(), a_World->GetName().c_str() ); return true; @@ -1818,7 +1819,6 @@ bool cPlayer::LoadFromFile(const AString & a_FileName) bool cPlayer::SaveToDisk() { - cFile::CreateFolder(FILE_IO_PREFIX + AString("players")); cFile::CreateFolder(FILE_IO_PREFIX + AString("players/") + m_UUID.substr(0, 2)); // create the JSON data @@ -1853,6 +1853,10 @@ bool cPlayer::SaveToDisk() root["foodExhaustion"] = m_FoodExhaustionLevel; root["isflying"] = IsFlying(); root["lastknownname"] = GetName(); + root["SpawnX"] = GetLastBedPos().x; + root["SpawnY"] = GetLastBedPos().y; + root["SpawnZ"] = GetLastBedPos().z; + if (m_World != NULL) { root["world"] = m_World->GetName(); diff --git a/src/Entities/Player.h b/src/Entities/Player.h index 69149c90b..65c1e33a8 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -131,7 +131,7 @@ public: inline const cItem & GetEquippedItem(void) const { return GetInventory().GetEquippedItem(); } // tolua_export - /** Returns whether the player is climbing (ladders, vines e.t.c). */ + /** Returns whether the player is climbing (ladders, vines etc.) */ bool IsClimbing(void) const; virtual void TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ) override; @@ -325,7 +325,7 @@ public: virtual void KilledBy(TakeDamageInfo & a_TDI) override; virtual void Killed(cEntity * a_Victim) override; - + void Respawn(void); // tolua_export void SetVisible( bool a_bVisible); // tolua_export @@ -333,17 +333,24 @@ public: /** Moves the player to the specified world. Returns true if successful, false on failure (world not found). */ - bool MoveToWorld(const char * a_WorldName); // tolua_export + virtual bool DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn) override; + /** Saves all player data, such as inventory, to JSON */ bool SaveToDisk(void); + + typedef cWorld * cWorldPtr; - /** Loads the player data from the disk file. - Returns true on success, false on failure. */ - bool LoadFromDisk(void); + /** Loads the player data from the disk file + Sets a_World to the world where the player will spawn, based on the stored world name or the default world by calling LoadFromFile() + Returns true on success, false on failure + */ + bool LoadFromDisk(cWorldPtr & a_World); - /** Loads the player data from the specified file. - Returns true on success, false on failure. */ - bool LoadFromFile(const AString & a_FileName); + /** Loads the player data from the specified file + Sets a_World to the world where the player will spawn, based on the stored world name or the default world + Returns true on success, false on failure + */ + bool LoadFromFile(const AString & a_FileName, cWorldPtr & a_World); void LoadPermissionsFromDisk(void); // tolua_export @@ -355,8 +362,7 @@ public: void SendExperience(void); - // In UI windows, the item that the player is dragging: - bool IsDraggingItem(void) const { return !m_DraggingItem.IsEmpty(); } + /** In UI windows, get the item that the player is dragging */ cItem & GetDraggingItem(void) {return m_DraggingItem; } // In UI windows, when inventory-painting: @@ -404,11 +410,20 @@ public: /** If true the player can fly even when he's not in creative. */ void SetCanFly(bool a_CanFly); + /** Gets the last position that the player slept in + This is initialised to the world spawn point if the player has not slept in a bed as of yet + */ + Vector3i GetLastBedPos(void) const { return m_LastBedPos; } + + /** Sets the player's bed (home) position */ + void SetBedPos(const Vector3i & a_Pos) { m_LastBedPos = a_Pos; } + /** Update movement-related statistics. */ void UpdateMovementStats(const Vector3d & a_DeltaPos); /** Returns wheter the player can fly or not. */ virtual bool CanFly(void) const { return m_CanFly; } + // tolua_end // cEntity overrides: @@ -466,6 +481,9 @@ protected: cWindow * m_CurrentWindow; cWindow * m_InventoryWindow; + /** The player's last saved bed position */ + Vector3i m_LastBedPos; + char m_Color; eGameMode m_GameMode; @@ -540,7 +558,6 @@ protected: If no ClientHandle is given, the UUID is initialized to empty. */ AString m_UUID; - /** Sets the speed and sends it to the client, so that they are forced to move so. */ virtual void DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ) override; diff --git a/src/Generating/BioGen.cpp b/src/Generating/BioGen.cpp index d314fc13e..8fad9f5c9 100644 --- a/src/Generating/BioGen.cpp +++ b/src/Generating/BioGen.cpp @@ -95,7 +95,7 @@ void cBioGenConstant::GenBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap void cBioGenConstant::InitializeBiomeGen(cIniFile & a_IniFile) { - AString Biome = a_IniFile.GetValueSet("Generator", "ConstantBiome", "Plains"); + AString Biome = a_IniFile.GetValueSet("Generator", "ConstantBiome", ""); m_Biome = StringToBiome(Biome); if (m_Biome == biInvalidBiome) { diff --git a/src/Generating/ChunkDesc.cpp b/src/Generating/ChunkDesc.cpp index e4b305022..c0b646fd0 100644 --- a/src/Generating/ChunkDesc.cpp +++ b/src/Generating/ChunkDesc.cpp @@ -20,7 +20,6 @@ cChunkDesc::cChunkDesc(int a_ChunkX, int a_ChunkZ) : m_bUseDefaultBiomes(true), m_bUseDefaultHeight(true), m_bUseDefaultComposition(true), - m_bUseDefaultStructures(true), m_bUseDefaultFinish(true) { m_BlockArea.Create(cChunkDef::Width, cChunkDef::Height, cChunkDef::Width); @@ -207,26 +206,6 @@ bool cChunkDesc::IsUsingDefaultComposition(void) const -void cChunkDesc::SetUseDefaultStructures(bool a_bUseDefaultStructures) -{ - LOGWARNING("%s: Structures are no longer accounted for, use Finishers instead", __FUNCTION__); - m_bUseDefaultStructures = a_bUseDefaultStructures; -} - - - - - -bool cChunkDesc::IsUsingDefaultStructures(void) const -{ - LOGWARNING("%s: Structures are no longer accounted for, use Finishers instead", __FUNCTION__); - return m_bUseDefaultStructures; -} - - - - - void cChunkDesc::SetUseDefaultFinish(bool a_bUseDefaultFinish) { m_bUseDefaultFinish = a_bUseDefaultFinish; diff --git a/src/Generating/ChunkDesc.h b/src/Generating/ChunkDesc.h index b306b1ee5..eeea0c957 100644 --- a/src/Generating/ChunkDesc.h +++ b/src/Generating/ChunkDesc.h @@ -68,8 +68,6 @@ public: bool IsUsingDefaultHeight(void) const; void SetUseDefaultComposition(bool a_bUseDefaultComposition); bool IsUsingDefaultComposition(void) const; - void SetUseDefaultStructures(bool a_bUseDefaultStructures); - bool IsUsingDefaultStructures(void) const; void SetUseDefaultFinish(bool a_bUseDefaultFinish); bool IsUsingDefaultFinish(void) const; @@ -214,7 +212,6 @@ private: bool m_bUseDefaultBiomes; bool m_bUseDefaultHeight; bool m_bUseDefaultComposition; - bool m_bUseDefaultStructures; bool m_bUseDefaultFinish; } ; // tolua_export diff --git a/src/Generating/ChunkGenerator.cpp b/src/Generating/ChunkGenerator.cpp index 0baf1e991..3d5af152c 100644 --- a/src/Generating/ChunkGenerator.cpp +++ b/src/Generating/ChunkGenerator.cpp @@ -52,7 +52,7 @@ bool cChunkGenerator::Start(cPluginInterface & a_PluginInterface, cChunkSink & a m_ChunkSink = &a_ChunkSink; MTRand rnd; - m_Seed = a_IniFile.GetValueSetI("Seed", "Seed", rnd.randInt()); + m_Seed = a_IniFile.GetValueSetI("Seed", "Seed", (int)rnd.randInt()); AString GeneratorName = a_IniFile.GetValueSet("Generator", "Generator", "Composable"); if (NoCaseCompare(GeneratorName, "Noise3D") == 0) diff --git a/src/Generating/ComposableGenerator.cpp b/src/Generating/ComposableGenerator.cpp index a0355ff8d..a7659149a 100644 --- a/src/Generating/ComposableGenerator.cpp +++ b/src/Generating/ComposableGenerator.cpp @@ -45,7 +45,6 @@ cTerrainCompositionGen * cTerrainCompositionGen::CreateCompositionGen(cIniFile & { LOGWARN("[Generator] CompositionGen value not set in world.ini, using \"Biomal\"."); CompoGenName = "Biomal"; - a_IniFile.SetValue("Generator", "CompositionGen", CompoGenName); } cTerrainCompositionGen * res = NULL; @@ -99,7 +98,6 @@ cTerrainCompositionGen * cTerrainCompositionGen::CreateCompositionGen(cIniFile & else { LOGWARN("Unknown CompositionGen \"%s\", using \"Biomal\" instead.", CompoGenName.c_str()); - a_IniFile.DeleteValue("Generator", "CompositionGen"); a_IniFile.SetValue("Generator", "CompositionGen", "Biomal"); return CreateCompositionGen(a_IniFile, a_BiomeGen, a_HeightGen, a_Seed); } @@ -297,19 +295,7 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile) int Seed = m_ChunkGenerator.GetSeed(); eDimension Dimension = StringToDimension(a_IniFile.GetValue("General", "Dimension", "Overworld")); - // Older configuration used "Structures" in addition to "Finishers"; we don't distinguish between the two anymore (#398) - // Therefore, we load Structures from the ini file for compatibility, but move its contents over to Finishers: - AString Structures = a_IniFile.GetValue("Generator", "Structures", ""); - AString Finishers = a_IniFile.GetValueSet("Generator", "Finishers", "Ravines, WormNestCaves, WaterLakes, LavaLakes, OreNests, Trees, SprinkleFoliage, Ice, Snow, Lilypads, BottomLava, DeadBushes, PreSimulator"); - if (!Structures.empty()) - { - LOGINFO("[Generator].Structures is deprecated, moving the contents to [Generator].Finishers."); - // Structures used to generate before Finishers, so place them first: - Structures.append(", "); - Finishers = Structures + Finishers; - a_IniFile.SetValue("Generator", "Finishers", Finishers); - } - a_IniFile.DeleteValue("Generator", "Structures"); + AString Finishers = a_IniFile.GetValueSet("Generator", "Finishers", ""); // Create all requested finishers: AStringVector Str = StringSplitAndTrim(Finishers, ","); diff --git a/src/Generating/EndGen.cpp b/src/Generating/EndGen.cpp index abbf050f7..c94cd1eff 100644 --- a/src/Generating/EndGen.cpp +++ b/src/Generating/EndGen.cpp @@ -50,7 +50,7 @@ cEndGen::cEndGen(int a_Seed) : -void cEndGen::Initialize(cIniFile & a_IniFile) +void cEndGen::InitializeCompoGen(cIniFile & a_IniFile) { m_IslandSizeX = a_IniFile.GetValueSetI("Generator", "EndGenIslandSizeX", m_IslandSizeX); m_IslandSizeY = a_IniFile.GetValueSetI("Generator", "EndGenIslandSizeY", m_IslandSizeY); diff --git a/src/Generating/EndGen.h b/src/Generating/EndGen.h index 4904a0e3d..322061810 100644 --- a/src/Generating/EndGen.h +++ b/src/Generating/EndGen.h @@ -23,8 +23,6 @@ class cEndGen : public: cEndGen(int a_Seed); - void Initialize(cIniFile & a_IniFile); - protected: /// Seed for the noise @@ -66,4 +64,5 @@ protected: // cTerrainCompositionGen overrides: virtual void ComposeTerrain(cChunkDesc & a_ChunkDesc) override; + virtual void InitializeCompoGen(cIniFile & a_IniFile) override; } ; diff --git a/src/Generating/FinishGen.cpp b/src/Generating/FinishGen.cpp index 842c9ccc5..f53addb68 100644 --- a/src/Generating/FinishGen.cpp +++ b/src/Generating/FinishGen.cpp @@ -135,6 +135,15 @@ void cFinishGenNetherClumpFoliage::TryPlaceClump(cChunkDesc & a_ChunkDesc, int a int zz = a_ChunkDesc.GetChunkZ() * cChunkDef::Width + z; for (int y = MinY; y < MaxY; y++) { + if ( + ((x < 0) || (x >= cChunkDef::Width)) || + ((y < 0) || (y >= cChunkDef::Height)) || + ((z < 0) || (z >= cChunkDef::Width)) + ) + { + continue; + } + if (a_ChunkDesc.GetBlockType(x, y, z) != E_BLOCK_AIR) // Don't replace non air blocks. { continue; diff --git a/src/MobSpawner.cpp b/src/MobSpawner.cpp index 454fabce8..db05b70af 100644 --- a/src/MobSpawner.cpp +++ b/src/MobSpawner.cpp @@ -241,7 +241,8 @@ bool cMobSpawner::CanSpawnHere(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_R (m_Random.NextInt(2, a_Biome) == 0) ); } - + + case cMonster::mtMagmaCube: case cMonster::mtSlime: { return ( diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index c1247c1f8..622a67816 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -1015,7 +1015,7 @@ void cMonster::HandleDaylightBurning(cChunk & a_Chunk) (a_Chunk.GetBlock(RelX, RelY, RelZ) != E_BLOCK_SOULSAND) && // Not on soulsand (GetWorld()->GetTimeOfDay() < (12000 + 1000)) && // It is nighttime !IsOnFire() && // Not already burning - (GetWorld()->GetWeather() != eWeather_Rain) // Not raining + GetWorld()->IsWeatherWetAt(POSX_TOINT, POSZ_TOINT) // Not raining ) { // Burn for 100 ticks, then decide again diff --git a/src/Protocol/Protocol.h b/src/Protocol/Protocol.h index 166e9ab1b..d110f2af9 100644 --- a/src/Protocol/Protocol.h +++ b/src/Protocol/Protocol.h @@ -100,7 +100,7 @@ public: virtual void SendPlayerSpawn (const cPlayer & a_Player) = 0; virtual void SendPluginMessage (const AString & a_Channel, const AString & a_Message) = 0; virtual void SendRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID) = 0; - virtual void SendRespawn (const cWorld & a_World, bool a_ShouldIgnoreDimensionChecks = false) = 0; + virtual void SendRespawn (eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) = 0; virtual void SendExperience (void) = 0; virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) = 0; virtual void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) = 0; diff --git a/src/Protocol/Protocol125.cpp b/src/Protocol/Protocol125.cpp index cad47e6c2..538d31642 100644 --- a/src/Protocol/Protocol125.cpp +++ b/src/Protocol/Protocol125.cpp @@ -833,23 +833,23 @@ void cProtocol125::SendRemoveEntityEffect(const cEntity & a_Entity, int a_Effect -void cProtocol125::SendRespawn(const cWorld & a_World, bool a_ShouldIgnoreDimensionChecks) +void cProtocol125::SendRespawn(eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) { cCSLock Lock(m_CSPacket); - if ((m_LastSentDimension == a_World.GetDimension()) && !a_ShouldIgnoreDimensionChecks) + if ((m_LastSentDimension == a_Dimension) && !a_ShouldIgnoreDimensionChecks) { // Must not send a respawn for the world with the same dimension, the client goes cuckoo if we do (unless we are respawning from death) return; } cPlayer * Player = m_Client->GetPlayer(); WriteByte (PACKET_RESPAWN); - WriteInt (a_World.GetDimension()); + WriteInt ((int)(a_Dimension)); WriteByte (2); // TODO: Difficulty; 2 = Normal WriteChar ((char)Player->GetGameMode()); WriteShort (256); // Current world height WriteString("default"); Flush(); - m_LastSentDimension = a_World.GetDimension(); + m_LastSentDimension = a_Dimension; } diff --git a/src/Protocol/Protocol125.h b/src/Protocol/Protocol125.h index bf08b6c50..18efeb079 100644 --- a/src/Protocol/Protocol125.h +++ b/src/Protocol/Protocol125.h @@ -72,7 +72,7 @@ public: virtual void SendPlayerSpawn (const cPlayer & a_Player) override; virtual void SendPluginMessage (const AString & a_Channel, const AString & a_Message) override; virtual void SendRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID) override; - virtual void SendRespawn (const cWorld & a_World, bool a_ShouldIgnoreDimensionChecks = false) override; + virtual void SendRespawn (eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) override; virtual void SendExperience (void) override; virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) override; virtual void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) override; diff --git a/src/Protocol/Protocol16x.cpp b/src/Protocol/Protocol16x.cpp index a163922c5..0d354a030 100644 --- a/src/Protocol/Protocol16x.cpp +++ b/src/Protocol/Protocol16x.cpp @@ -158,10 +158,10 @@ void cProtocol161::SendPlayerMaxSpeed(void) -void cProtocol161::SendRespawn(const cWorld & a_World, bool a_ShouldIgnoreDimensionChecks) +void cProtocol161::SendRespawn(eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) { // Besides sending the respawn, we need to also send the player max speed, otherwise the client reverts to super-fast - super::SendRespawn(a_World, a_ShouldIgnoreDimensionChecks); + super::SendRespawn(a_Dimension, a_ShouldIgnoreDimensionChecks); SendPlayerMaxSpeed(); } diff --git a/src/Protocol/Protocol16x.h b/src/Protocol/Protocol16x.h index e6e79027e..add761d1e 100644 --- a/src/Protocol/Protocol16x.h +++ b/src/Protocol/Protocol16x.h @@ -42,7 +42,7 @@ protected: virtual void SendGameMode (eGameMode a_GameMode) override; virtual void SendHealth (void) override; virtual void SendPlayerMaxSpeed(void) override; - virtual void SendRespawn (const cWorld & a_World, bool a_ShouldIgnoreDimensionChecks = false) override; + virtual void SendRespawn (eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) override; virtual void SendWindowOpen (const cWindow & a_Window) override; virtual int ParseEntityAction (void) override; diff --git a/src/Protocol/Protocol17x.cpp b/src/Protocol/Protocol17x.cpp index dadf82716..45d39e0e9 100644 --- a/src/Protocol/Protocol17x.cpp +++ b/src/Protocol/Protocol17x.cpp @@ -986,9 +986,9 @@ void cProtocol172::SendRemoveEntityEffect(const cEntity & a_Entity, int a_Effect -void cProtocol172::SendRespawn(const cWorld & a_World, bool a_ShouldIgnoreDimensionChecks) +void cProtocol172::SendRespawn(eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) { - if ((m_LastSentDimension == a_World.GetDimension()) && !a_ShouldIgnoreDimensionChecks) + if ((m_LastSentDimension == a_Dimension) && !a_ShouldIgnoreDimensionChecks) { // Must not send a respawn for the world with the same dimension, the client goes cuckoo if we do (unless we are respawning from death) return; @@ -996,11 +996,11 @@ void cProtocol172::SendRespawn(const cWorld & a_World, bool a_ShouldIgnoreDimens cPacketizer Pkt(*this, 0x07); // Respawn packet cPlayer * Player = m_Client->GetPlayer(); - Pkt.WriteInt(a_World.GetDimension()); + Pkt.WriteInt((int)a_Dimension); Pkt.WriteByte(2); // TODO: Difficulty (set to Normal) Pkt.WriteByte((Byte)Player->GetEffectiveGameMode()); Pkt.WriteString("default"); - m_LastSentDimension = a_World.GetDimension(); + m_LastSentDimension = a_Dimension; } diff --git a/src/Protocol/Protocol17x.h b/src/Protocol/Protocol17x.h index 1a91d5203..9c9f563e0 100644 --- a/src/Protocol/Protocol17x.h +++ b/src/Protocol/Protocol17x.h @@ -104,7 +104,7 @@ public: virtual void SendPlayerSpawn (const cPlayer & a_Player) override; virtual void SendPluginMessage (const AString & a_Channel, const AString & a_Message) override; virtual void SendRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID) override; - virtual void SendRespawn (const cWorld & a_World, bool a_ShouldIgnoreDimensionChecks = false) override; + virtual void SendRespawn (eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) override; virtual void SendSoundEffect (const AString & a_SoundName, double a_X, double a_Y, double a_Z, float a_Volume, float a_Pitch) override; virtual void SendExperience (void) override; virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) override; diff --git a/src/Protocol/ProtocolRecognizer.cpp b/src/Protocol/ProtocolRecognizer.cpp index bf46a15ef..a7fb7bcc2 100644 --- a/src/Protocol/ProtocolRecognizer.cpp +++ b/src/Protocol/ProtocolRecognizer.cpp @@ -556,10 +556,10 @@ void cProtocolRecognizer::SendRemoveEntityEffect(const cEntity & a_Entity, int a -void cProtocolRecognizer::SendRespawn(const cWorld & a_World, bool a_ShouldIgnoreDimensionChecks) +void cProtocolRecognizer::SendRespawn(eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) { ASSERT(m_Protocol != NULL); - m_Protocol->SendRespawn(a_World, a_ShouldIgnoreDimensionChecks); + m_Protocol->SendRespawn(a_Dimension, a_ShouldIgnoreDimensionChecks); } diff --git a/src/Protocol/ProtocolRecognizer.h b/src/Protocol/ProtocolRecognizer.h index 02143f17f..65829ef73 100644 --- a/src/Protocol/ProtocolRecognizer.h +++ b/src/Protocol/ProtocolRecognizer.h @@ -107,7 +107,7 @@ public: virtual void SendPlayerSpawn (const cPlayer & a_Player) override; virtual void SendPluginMessage (const AString & a_Channel, const AString & a_Message) override; virtual void SendRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID) override; - virtual void SendRespawn (const cWorld & a_World, bool a_ShouldIgnoreDimensionChecks = false) override; + virtual void SendRespawn (eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) override; virtual void SendExperience (void) override; virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) override; virtual void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) override; diff --git a/src/Root.cpp b/src/Root.cpp index b03a13382..b65e9b067 100644 --- a/src/Root.cpp +++ b/src/Root.cpp @@ -312,13 +312,15 @@ void cRoot::LoadWorlds(cIniFile & IniFile) -cWorld * cRoot::CreateAndInitializeWorld(const AString & a_WorldName) +cWorld * cRoot::CreateAndInitializeWorld(const AString & a_WorldName, eDimension a_Dimension, const AString & a_OverworldName) { - if (m_WorldsByName[a_WorldName] != NULL) + cWorld * World = m_WorldsByName[a_WorldName]; + if (World != NULL) { - return NULL; + return World; } - cWorld* NewWorld = new cWorld(a_WorldName.c_str()); + + cWorld * NewWorld = new cWorld(a_WorldName.c_str(), a_Dimension, a_OverworldName); m_WorldsByName[a_WorldName] = NewWorld; NewWorld->Start(); NewWorld->InitializeSpawn(); @@ -370,7 +372,7 @@ void cRoot::UnloadWorlds(void) -cWorld* cRoot::GetDefaultWorld() +cWorld * cRoot::GetDefaultWorld() { return m_pDefaultWorld; } @@ -379,12 +381,19 @@ cWorld* cRoot::GetDefaultWorld() -cWorld* cRoot::GetWorld( const AString & a_WorldName) +cWorld * cRoot::GetWorld(const AString & a_WorldName, bool a_SearchForFolder) { - WorldMap::iterator itr = m_WorldsByName.find( a_WorldName); + WorldMap::iterator itr = m_WorldsByName.find(a_WorldName); if (itr != m_WorldsByName.end()) + { return itr->second; - return 0; + } + + if (a_SearchForFolder && cFile::IsFolder(FILE_IO_PREFIX + a_WorldName)) + { + return CreateAndInitializeWorld(a_WorldName); + } + return NULL; } @@ -396,9 +405,12 @@ bool cRoot::ForEachWorld(cWorldListCallback & a_Callback) for (WorldMap::iterator itr = m_WorldsByName.begin(), itr2 = itr; itr != m_WorldsByName.end(); itr = itr2) { ++itr2; - if (a_Callback.Item(itr->second)) + if (itr->second != NULL) { - return false; + if (a_Callback.Item(itr->second)) + { + return false; + } } } return true; diff --git a/src/Root.h b/src/Root.h index 08aafe3c9..93117389e 100644 --- a/src/Root.h +++ b/src/Root.h @@ -53,8 +53,17 @@ public: // tolua_begin cServer * GetServer(void) { return m_Server; } cWorld * GetDefaultWorld(void); - cWorld * GetWorld(const AString & a_WorldName); - cWorld * CreateAndInitializeWorld(const AString & a_WorldName); + + /** Returns a pointer to the world specified + If no world of that name was currently loaded and a_SearchForFolder was true, it will consult cFile::IsFolder() to see if a world folder of that name exists and if so, initialise a world based on that name + */ + cWorld * GetWorld(const AString & a_WorldName, bool a_SearchForFolder = false); + + /** Returns a pointer to a world of specified name - will search loaded worlds first, then create anew if not found + The dimension parameter is used to create a world with a specific dimension + a_OverworldName should be set for non-overworld dimensions if one wishes that world to link back to an overworld via portals + */ + cWorld * CreateAndInitializeWorld(const AString & a_WorldName, eDimension a_Dimension = dimOverworld, const AString & a_OverworldName = ""); // tolua_end /// Calls the callback for each world; returns true if the callback didn't abort (return true) diff --git a/src/UI/Window.cpp b/src/UI/Window.cpp index 2c22b41bd..4731f282b 100644 --- a/src/UI/Window.cpp +++ b/src/UI/Window.cpp @@ -274,7 +274,7 @@ void cWindow::OpenedByPlayer(cPlayer & a_Player) bool cWindow::ClosedByPlayer(cPlayer & a_Player, bool a_CanRefuse) { // Checks whether the player is still holding an item - if (a_Player.IsDraggingItem()) + if (!a_Player.GetDraggingItem().IsEmpty()) { LOGD("Player holds item! Dropping it..."); a_Player.TossHeldItem(a_Player.GetDraggingItem().m_ItemCount); diff --git a/src/World.cpp b/src/World.cpp index 7ad350e24..0b0f7870b 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -230,8 +230,9 @@ void cWorld::cTickThread::Execute(void) //////////////////////////////////////////////////////////////////////////////// // cWorld: -cWorld::cWorld(const AString & a_WorldName) : +cWorld::cWorld(const AString & a_WorldName, eDimension a_Dimension, const AString & a_OverworldName) : m_WorldName(a_WorldName), + m_OverworldName(a_OverworldName), m_IniFileName(m_WorldName + "/world.ini"), m_StorageSchema("Default"), #ifdef __arm__ @@ -239,6 +240,7 @@ cWorld::cWorld(const AString & a_WorldName) : #else m_StorageCompressionFactor(6), #endif + m_Dimension(a_Dimension), m_IsSpawnExplicitlySet(false), m_WorldAgeSecs(0), m_TimeOfDaySecs(0), @@ -518,9 +520,15 @@ void cWorld::Start(void) if (!IniFile.ReadFile(m_IniFileName)) { LOGWARNING("Cannot read world settings from \"%s\", defaults will be used.", m_IniFileName.c_str()); + + // TODO: More descriptions for each key + IniFile.AddHeaderComment(" This is the per-world configuration file, managing settings such as generators, simulators, and spawn points"); + IniFile.AddKeyComment(" LinkedWorlds", "This section governs portal world linkage; leave a value blank to disabled that associated method of teleportation"); } - AString Dimension = IniFile.GetValueSet("General", "Dimension", "Overworld"); - m_Dimension = StringToDimension(Dimension); + + // The presence of a configuration value overrides everything + // If no configuration value is found, GetDimension() is written to file and the variable is written to again to ensure that cosmic rays haven't sneakily changed its value + m_Dimension = StringToDimension(IniFile.GetValueSet("General", "Dimension", DimensionToString(GetDimension()))); // Try to find the "SpawnPosition" key and coord values in the world configuration, set the flag if found int KeyNum = IniFile.FindKey("SpawnPosition"); @@ -528,8 +536,8 @@ void cWorld::Start(void) ( (KeyNum >= 0) && ( - (IniFile.FindValue(KeyNum, "X") >= 0) || - (IniFile.FindValue(KeyNum, "Y") >= 0) || + (IniFile.FindValue(KeyNum, "X") >= 0) && + (IniFile.FindValue(KeyNum, "Y") >= 0) && (IniFile.FindValue(KeyNum, "Z") >= 0) ) ); @@ -565,36 +573,26 @@ void cWorld::Start(void) m_bUseChatPrefixes = IniFile.GetValueSetB("Mechanics", "UseChatPrefixes", true); m_VillagersShouldHarvestCrops = IniFile.GetValueSetB("Monsters", "VillagersShouldHarvestCrops", true); int GameMode = IniFile.GetValueSetI("General", "Gamemode", (int)m_GameMode); + int Weather = IniFile.GetValueSetI("General", "Weather", (int)m_Weather); + m_TimeOfDay = IniFile.GetValueSetI("General", "TimeInTicks", m_TimeOfDay); + + if (GetDimension() == dimOverworld) + { + m_NetherWorldName = IniFile.GetValueSet("LinkedWorlds", "NetherWorldName", GetName() + "_nether"); + m_EndWorldName = IniFile.GetValueSet("LinkedWorlds", "EndWorldName", GetName() + "_end"); + } + else + { + m_OverworldName = IniFile.GetValueSet("LinkedWorlds", "OverworldName", GetLinkedOverworldName()); + } // Adjust the enum-backed variables into their respective bounds: m_GameMode = (eGameMode) Clamp(GameMode, (int)gmSurvival, (int)gmAdventure); m_TNTShrapnelLevel = (eShrapnelLevel)Clamp(TNTShrapnelLevel, (int)slNone, (int)slAll); + m_Weather = (eWeather) Clamp(Weather, (int)wSunny, (int)wStorm); - // Load allowed mobs: - AString DefaultMonsters; - switch (m_Dimension) - { - case dimOverworld: DefaultMonsters = "bat, cavespider, chicken, cow, creeper, enderman, horse, mooshroom, ocelot, pig, sheep, silverfish, skeleton, slime, spider, squid, wolf, zombie"; break; - case dimNether: DefaultMonsters = "blaze, ghast, magmacube, skeleton, zombie, zombiepigman"; break; - case dimEnd: DefaultMonsters = "enderman"; break; - case dimNotSet: break; - } - m_bAnimals = IniFile.GetValueSetB("Monsters", "AnimalsOn", true); - AString AllMonsters = IniFile.GetValueSet("Monsters", "Types", DefaultMonsters); - AStringVector SplitList = StringSplitAndTrim(AllMonsters, ","); - for (AStringVector::const_iterator itr = SplitList.begin(), end = SplitList.end(); itr != end; ++itr) - { - cMonster::eType ToAdd = cMonster::StringToMobType(*itr); - if (ToAdd != cMonster::mtInvalidType) - { - m_AllowedMobs.insert(ToAdd); - LOGD("Allowed mob: %s", itr->c_str()); - } - else - { - LOG("World \"%s\": Unknown mob type: %s", m_WorldName.c_str(), itr->c_str()); - } - } + InitialiseGeneratorDefaults(IniFile); + InitialiseAndLoadMobSpawningValues(IniFile); m_ChunkMap = new cChunkMap(this); @@ -691,6 +689,82 @@ eWeather cWorld::ChooseNewWeather() +void cWorld::InitialiseGeneratorDefaults(cIniFile & a_IniFile) +{ + switch (GetDimension()) + { + case dimEnd: + { + a_IniFile.GetValueSet("Generator", "BiomeGen", "Constant"); + a_IniFile.GetValueSet("Generator", "ConstantBiome", "End"); + a_IniFile.GetValueSet("Generator", "HeightGen", "Biomal"); + a_IniFile.GetValueSet("Generator", "CompositionGen", "End"); + break; + } + case dimOverworld: + { + a_IniFile.GetValueSet("Generator", "BiomeGen", "MultiStepMap"); + a_IniFile.GetValueSet("Generator", "HeightGen", "DistortedHeightmap"); + a_IniFile.GetValueSet("Generator", "CompositionGen", "DistortedHeightmap"); + a_IniFile.GetValueSet("Generator", "Finishers", "Ravines, WormNestCaves, WaterLakes, WaterSprings, LavaLakes, LavaSprings, OreNests, Mineshafts, Trees, SprinkleFoliage, Ice, Snow, Lilypads, BottomLava, DeadBushes, PreSimulator"); + break; + } + case dimNether: + { + a_IniFile.GetValueSet("Generator", "BiomeGen", "Constant"); + a_IniFile.GetValueSet("Generator", "ConstantBiome", "Nether"); + a_IniFile.GetValueSet("Generator", "HeightGen", "Flat"); + a_IniFile.GetValueSet("Generator", "FlatHeight", "128"); + a_IniFile.GetValueSet("Generator", "CompositionGen", "Nether"); + a_IniFile.GetValueSet("Generator", "Finishers", "WormNestCaves, BottomLava, LavaSprings, NetherClumpFoliage, NetherForts, PreSimulator"); + a_IniFile.GetValueSet("Generator", "BottomLavaHeight", "30"); + break; + } + } +} + + + + + +void cWorld::InitialiseAndLoadMobSpawningValues(cIniFile & a_IniFile) +{ + AString DefaultMonsters; + switch (m_Dimension) + { + case dimOverworld: DefaultMonsters = "bat, cavespider, chicken, cow, creeper, enderman, horse, mooshroom, ocelot, pig, sheep, silverfish, skeleton, slime, spider, squid, wolf, zombie"; break; + case dimNether: DefaultMonsters = "blaze, ghast, magmacube, skeleton, zombie, zombiepigman"; break; + case dimEnd: DefaultMonsters = "enderman"; break; + } + + m_bAnimals = a_IniFile.GetValueSetB("Monsters", "AnimalsOn", true); + AString AllMonsters = a_IniFile.GetValueSet("Monsters", "Types", DefaultMonsters); + + if (!m_bAnimals) + { + return; + } + + AStringVector SplitList = StringSplitAndTrim(AllMonsters, ","); + for (AStringVector::const_iterator itr = SplitList.begin(), end = SplitList.end(); itr != end; ++itr) + { + cMonster::eType ToAdd = cMonster::StringToMobType(*itr); + if (ToAdd != cMonster::mtInvalidType) + { + m_AllowedMobs.insert(ToAdd); + LOGD("Allowed mob: %s", itr->c_str()); + } + else + { + LOG("World \"%s\": Unknown mob type: %s", m_WorldName.c_str(), itr->c_str()); + } + } +} + + + + + void cWorld::Stop(void) { // Delete the clients that have been in this world: @@ -703,6 +777,25 @@ void cWorld::Stop(void) } // for itr - m_Clients[] m_Clients.clear(); } + + // Write settings to file; these are all plugin changeable values - keep updated! + cIniFile IniFile; + IniFile.ReadFile(m_IniFileName); + if (GetDimension() == dimOverworld) + { + IniFile.SetValue("LinkedWorlds", "NetherWorldName", m_NetherWorldName); + IniFile.SetValue("LinkedWorlds", "EndWorldName", m_EndWorldName); + } + else + { + IniFile.SetValue("LinkedWorlds", "OverworldName", m_OverworldName); + } + IniFile.SetValueI("Physics", "TNTShrapnelLevel", (int)m_TNTShrapnelLevel); + IniFile.SetValueB("Mechanics", "CommandBlocksEnabled", m_bCommandBlocksEnabled); + IniFile.SetValueB("Mechanics", "UseChatPrefixes", m_bUseChatPrefixes); + IniFile.SetValueI("General", "Weather", (int)m_Weather); + IniFile.SetValueI("General", "TimeInTicks", m_TimeOfDay); + IniFile.WriteFile(m_IniFileName); m_TickThread.Stop(); m_Lighting.Stop(); @@ -955,11 +1048,7 @@ void cWorld::TickClients(float a_Dt) // Add clients scheduled for adding: for (cClientHandleList::iterator itr = m_ClientsToAdd.begin(), end = m_ClientsToAdd.end(); itr != end; ++itr) { - if (std::find(m_Clients.begin(), m_Clients.end(), *itr) != m_Clients.end()) - { - ASSERT(!"Adding a client that is already in the clientlist"); - continue; - } + ASSERT(std::find(m_Clients.begin(), m_Clients.end(), *itr) == m_Clients.end()); m_Clients.push_back(*itr); } // for itr - m_ClientsToRemove[] m_ClientsToAdd.clear(); @@ -2378,8 +2467,10 @@ void cWorld::AddPlayer(cPlayer * a_Player) void cWorld::RemovePlayer(cPlayer * a_Player) { - - m_ChunkMap->RemoveEntity(a_Player); + if (!a_Player->IsWorldTravellingFrom(this)) + { + m_ChunkMap->RemoveEntity(a_Player); + } { cCSLock Lock(m_CSPlayersToAdd); m_PlayersToAdd.remove(a_Player); @@ -2882,15 +2973,6 @@ bool cWorld::HasEntity(int a_UniqueID) -void cWorld::RemoveEntity(cEntity * a_Entity) -{ - m_ChunkMap->RemoveEntity(a_Entity); -} - - - - - /* unsigned int cWorld::GetNumPlayers(void) { @@ -3197,7 +3279,8 @@ void cWorld::AddQueuedPlayers(void) cCSLock Lock(m_CSPlayers); for (cPlayerList::iterator itr = PlayersToAdd.begin(), end = PlayersToAdd.end(); itr != end; ++itr) { - ASSERT(std::find(m_Players.begin(), m_Players.end(), *itr) == m_Players.end()); // Is it already in the list? HOW? + ASSERT(std::find(m_Players.begin(), m_Players.end(), *itr) == m_Players.end()); // Is it already in the list? HOW? + LOGD("Adding player %s to world \"%s\".", (*itr)->GetName().c_str(), m_WorldName.c_str()); m_Players.push_back(*itr); (*itr)->SetWorld(this); @@ -3227,6 +3310,9 @@ void cWorld::AddQueuedPlayers(void) if (Client != NULL) { Client->StreamChunks(); + Client->SendPlayerMoveLook(); + Client->SendHealth(); + Client->SendWholeInventory(*(*itr)->GetWindow()); } } // for itr - PlayersToAdd[] } diff --git a/src/World.h b/src/World.h index 9658178ae..4bf5a9d64 100644 --- a/src/World.h +++ b/src/World.h @@ -185,7 +185,7 @@ public: virtual eDimension GetDimension(void) const { return m_Dimension; } /** Returns the world height at the specified coords; waits for the chunk to get loaded / generated */ - int GetHeight(int a_BlockX, int a_BlockZ); + virtual int GetHeight(int a_BlockX, int a_BlockZ); // tolua_end @@ -304,9 +304,6 @@ public: bool HasEntity(int a_UniqueID); - /** Removes the entity, the entity ptr ownership is assumed taken by the caller */ - void RemoveEntity(cEntity * a_Entity); - /** Calls the callback for each entity in the entire world; returns true if all entities processed, false if the callback aborted by returning true */ bool ForEachEntity(cEntityCallback & a_Callback); // Exported in ManualBindings.cpp @@ -622,6 +619,15 @@ public: bool ShouldUseChatPrefixes(void) const { return m_bUseChatPrefixes; } void SetShouldUseChatPrefixes(bool a_Flag) { m_bUseChatPrefixes = a_Flag; } + + AString GetNetherWorldName(void) const { return m_NetherWorldName; } + void SetNetherWorldName(const AString & a_Name) { m_NetherWorldName = a_Name; } + + AString GetEndWorldName(void) const { return m_EndWorldName; } + void SetEndWorldName(const AString & a_Name) { m_EndWorldName = a_Name; } + + AString GetLinkedOverworldName(void) const { return m_OverworldName; } + void SetLinkedOverworldName(const AString & a_Name) { m_OverworldName = a_Name; } // tolua_end @@ -705,7 +711,7 @@ public: /** Returns true if the current weather is stormy */ bool IsWeatherStorm(void) const { return (m_Weather == wStorm); } - + /** Returns true if the weather is stormy at the specified location. This takes into account biomes. */ bool IsWeatherStormAt(int a_BlockX, int a_BlockZ) { @@ -716,10 +722,11 @@ public: bool IsWeatherWet(void) const { return !IsWeatherSunny(); } /** Returns true if it is raining, stormy or snowing at the specified location. This takes into account biomes. */ - bool IsWeatherWetAt(int a_BlockX, int a_BlockZ) + virtual bool IsWeatherWetAt(int a_BlockX, int a_BlockZ) { return (IsWeatherWet() && !IsBiomeNoDownfall(GetBiomeAt(a_BlockX, a_BlockZ))); } + // tolua_end cChunkGenerator & GetGenerator(void) { return m_Generator; } @@ -824,6 +831,12 @@ private: AString m_WorldName; + + /** The name of the world that a portal in this world should link to + Only has effect if this world is a nether or end world, as it is used by entities to see which world to teleport to when in a portal + */ + AString m_OverworldName; + AString m_IniFileName; /** Name of the storage schema used to load and save chunks */ @@ -908,6 +921,12 @@ private: See the eShrapnelLevel enumeration for details */ eShrapnelLevel m_TNTShrapnelLevel; + + /** Name of the nether world */ + AString m_NetherWorldName; + + /** Name of the end world */ + AString m_EndWorldName; cChunkGenerator m_Generator; @@ -967,7 +986,7 @@ private: cSetChunkDataPtrs m_SetChunkDataQueue; - cWorld(const AString & a_WorldName); + cWorld(const AString & a_WorldName, eDimension a_Dimension = dimOverworld, const AString & a_OverworldName = ""); virtual ~cWorld(); void Tick(float a_Dt, int a_LastTickDurationMSec); @@ -1008,9 +1027,16 @@ private: Assumes it is called from the Tick thread. */ void AddQueuedPlayers(void); + /** Sets generator values to dimension specific defaults, if those values do not exist */ + void InitialiseGeneratorDefaults(cIniFile & a_IniFile); + + /** Sets mob spawning values if nonexistant to their dimension specific defaults */ + void InitialiseAndLoadMobSpawningValues(cIniFile & a_IniFile); + /** Sets the specified chunk data into the chunkmap. Called in the tick thread. Modifies the a_SetChunkData - moves the entities contained in it into the chunk. */ void SetChunkData(cSetChunkData & a_SetChunkData); + }; // tolua_export diff --git a/src/WorldStorage/NBTChunkSerializer.cpp b/src/WorldStorage/NBTChunkSerializer.cpp index 4857da1b6..b7a3d40ce 100644 --- a/src/WorldStorage/NBTChunkSerializer.cpp +++ b/src/WorldStorage/NBTChunkSerializer.cpp @@ -486,6 +486,7 @@ void cNBTChunkSerializer::AddMonsterEntity(cMonster * a_Monster) m_Writer.AddFloat("", a_Monster->GetDropChanceBoots()); m_Writer.EndList(); m_Writer.AddByte("CanPickUpLoot", (char)a_Monster->CanPickUpLoot()); + m_Writer.AddShort("Health", (short)a_Monster->GetHealth()); switch (a_Monster->GetMobType()) { case cMonster::mtBat: diff --git a/src/WorldStorage/WSSAnvil.cpp b/src/WorldStorage/WSSAnvil.cpp index 023813769..6134e2c0a 100644 --- a/src/WorldStorage/WSSAnvil.cpp +++ b/src/WorldStorage/WSSAnvil.cpp @@ -2499,8 +2499,16 @@ bool cWSSAnvil::LoadMonsterBaseFromNBT(cMonster & a_Monster, const cParsedNBT & a_Monster.SetDropChanceChestplate(DropChance[2]); a_Monster.SetDropChanceLeggings(DropChance[3]); a_Monster.SetDropChanceBoots(DropChance[4]); - bool CanPickUpLoot = (a_NBT.GetByte(a_NBT.FindChildByName(a_TagIdx, "CanPickUpLoot")) == 1); - a_Monster.SetCanPickUpLoot(CanPickUpLoot); + + int LootTag = a_NBT.FindChildByName(a_TagIdx, "CanPickUpLoot"); + if (LootTag > 0) + { + bool CanPickUpLoot = (a_NBT.GetByte(LootTag) == 1); + a_Monster.SetCanPickUpLoot(CanPickUpLoot); + } + + int HealthTag = a_NBT.FindChildByName(a_TagIdx, "Health"); + a_Monster.SetHealth(HealthTag > 0 ? a_NBT.GetShort(HealthTag) : a_Monster.GetMaxHealth()); return true; }