diff options
33 files changed, 577 insertions, 141 deletions
diff --git a/lib/inifile/iniFile.cpp b/lib/inifile/iniFile.cpp index ea03f5d35..30f93e5a3 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; +#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 bfe826f40..641a6a225 100644 --- a/src/BlockID.cpp +++ b/src/BlockID.cpp @@ -345,6 +345,41 @@ 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" }, + { dimOverworld, "Normal" }, + { dimOverworld, "World" }, + { dimNether, "Nether" }, + { dimNether, "Hell" }, // Alternate name for Nether + { dimEnd, "End" }, + { dimEnd, "Sky" }, // Old name for 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 272fd319d..24b69e41b 100644 --- a/src/BlockID.h +++ b/src/BlockID.h @@ -917,9 +917,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 a string 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/Blocks/BlockBed.cpp b/src/Blocks/BlockBed.cpp index 6a3c6a55b..5525f0d0c 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..5ec776df6 100644 --- a/src/Blocks/BlockCauldron.h +++ b/src/Blocks/BlockCauldron.h @@ -58,6 +58,20 @@ 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 + { + if (IsBiomeNoDownfall(a_Chunk.GetBiomeAt(a_RelX, a_RelZ)) || !a_WorldInterface.IsWeatherWet()) + { + 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/BlockPortal.h b/src/Blocks/BlockPortal.h index 3b8030028..e718d9a70 100644 --- a/src/Blocks/BlockPortal.h +++ b/src/Blocks/BlockPortal.h @@ -55,7 +55,7 @@ public: virtual bool CanBeAt(cChunkInterface & a_ChunkInterface, int a_RelX, int a_RelY, int a_RelZ, const cChunk & a_Chunk) override { - if ((a_RelY - 1 < 0) || (a_RelY + 1 > cChunkDef::Height)) + if ((a_RelY == 0) || (a_RelY == cChunkDef::Height)) // Y can't be < 0 or > Height; (Fast)SetBlock won't allow it { return false; // In case someone places a portal with meta 1 or 2 at boundaries, and server tries to get invalid coords at Y - 1 or Y + 1 } diff --git a/src/Blocks/WorldInterface.h b/src/Blocks/WorldInterface.h index bfbb053d9..7df82197e 100644 --- a/src/Blocks/WorldInterface.h +++ b/src/Blocks/WorldInterface.h @@ -37,4 +37,9 @@ public: virtual void SetTimeOfDay(Int64 a_TimeOfDay) = 0; + /** Returns true if the current weather has any precipitation - rain or storm + Does not check if biome has no downfall, use cChunk::GetBiomeAt(RelX, RelZ) for that + */ + virtual bool IsWeatherWet(void) const = 0; + }; diff --git a/src/Chunk.cpp b/src/Chunk.cpp index 44fcefbe1..399c8b9c7 100644 --- a/src/Chunk.cpp +++ b/src/Chunk.cpp @@ -579,30 +579,25 @@ void cChunk::Tick(float a_Dt) 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())) + // Don't tick things queued to be removed + if ( + !((*itr)->IsMob()) && + (std::find(m_EntitiesToRemove.begin(), m_EntitiesToRemove.end(), (*itr)->GetUniqueID()) == m_EntitiesToRemove.end()) + ) { (*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; - 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 ( + std::vector<int>::iterator itr2 = std::find(m_EntitiesToRemove.begin(), m_EntitiesToRemove.end(), (*itr)->GetUniqueID()); + if (itr2 != m_EntitiesToRemove.end()) + { + itr = m_Entities.erase(itr); + m_EntitiesToRemove.erase(itr2); + } + else if ( // If any entity moved out of the chunk, move it to the neighbor: ((*itr)->GetChunkX() != m_PosX) || ((*itr)->GetChunkZ() != m_PosZ) ) @@ -612,9 +607,19 @@ void cChunk::Tick(float a_Dt) } else { - ++itr; + if ((*itr)->IsDestroyed()) // Remove all entities that were scheduled for removal: + { + LOGD("Destroying entity #%i (%s)", (*itr)->GetUniqueID(), (*itr)->GetClass()); + cEntity * ToDelete = *itr; + itr = m_Entities.erase(itr); + delete ToDelete; + } + else + { + ++itr; + } } - } + } // for itr - m_Entitites[] ApplyWeatherToTop(); } @@ -899,7 +904,6 @@ void cChunk::ApplyWeatherToTop() } break; } // case (snowy biomes) - // TODO: Rainy biomes should check for farmland and cauldrons default: { break; @@ -1760,7 +1764,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) { @@ -1791,7 +1795,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) { @@ -1799,12 +1803,12 @@ void cChunk::RemoveClient( cClientHandle* a_Client ) { continue; } - + m_LoadedByClient.erase(itr); if (!a_Client->IsDestroyed()) { - for (cEntityList::iterator itr = m_Entities.begin(); itr != m_Entities.end(); ++itr ) + for (cEntityList::iterator itr = m_Entities.begin(); itr != m_Entities.end(); ++itr) { /* // DEBUG: @@ -1824,7 +1828,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) { @@ -1855,9 +1859,22 @@ void cChunk::AddEntity(cEntity * a_Entity) { MarkDirty(); } - - ASSERT(std::find(m_Entities.begin(), m_Entities.end(), a_Entity) == m_Entities.end()); // Not there already - + + if (std::find(m_Entities.begin(), m_Entities.end(), a_Entity) != m_Entities.end()) + { + // Not there already + std::vector<int>::iterator itr = std::find(m_EntitiesToRemove.begin(), m_EntitiesToRemove.end(), a_Entity->GetUniqueID()); + if (itr != m_EntitiesToRemove.end()) + { + m_EntitiesToRemove.erase(itr); + return; + } + else + { + ASSERT(!"Entity already present when AddEntity was called!"); + } + } + m_Entities.push_back(a_Entity); } @@ -1867,17 +1884,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) + m_EntitiesToRemove.push_back(a_Entity->GetUniqueID()); + + // 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(); } } @@ -1887,6 +1899,11 @@ void cChunk::RemoveEntity(cEntity * a_Entity) bool cChunk::HasEntity(int a_EntityID) { + if (std::find(m_EntitiesToRemove.begin(), m_EntitiesToRemove.end(), a_EntityID) != m_EntitiesToRemove.end()) + { + return false; // If EntitiesToRemove contains our ID, this chunk doesn't have it, as it should be removed soon + } + for (cEntityList::const_iterator itr = m_Entities.begin(), end = m_Entities.end(); itr != end; ++itr) { if ((*itr)->GetUniqueID() == a_EntityID) diff --git a/src/Chunk.h b/src/Chunk.h index dfdabea04..8123ac062 100644 --- a/src/Chunk.h +++ b/src/Chunk.h @@ -425,6 +425,7 @@ private: // A critical section is not needed, because all chunk access is protected by its parent ChunkMap's csLayers cClientHandleList m_LoadedByClient; cEntityList m_Entities; + std::vector<int> m_EntitiesToRemove; cBlockEntityList m_BlockEntities; /** Number of times the chunk has been requested to stay (by various cChunkStay objects); if zero, the chunk can be unloaded */ diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index e4bb9d8e9..256dad7da 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -124,7 +124,6 @@ cClientHandle::~cClientHandle() } if (World != NULL) { - World->RemovePlayer(m_Player); m_Player->Destroy(); } delete m_Player; diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index 8f736a269..334cf5aa7 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -12,6 +12,7 @@ #include "../Bindings/PluginManager.h" #include "../Tracer.h" #include "Player.h" +#include "BlockArea.h" @@ -629,6 +630,7 @@ void cEntity::Tick(float a_Dt, cChunk & a_Chunk) // Handle drowning HandleAir(); } + DetectPortal(); // None of the above functions change position, we remain in the chunk of NextChunk HandlePhysics(a_Dt, *NextChunk); @@ -868,7 +870,7 @@ void cEntity::TickBurning(cChunk & a_Chunk) // Remember the current burning state: bool HasBeenBurning = (m_TicksLeftBurning > 0); - if (m_World->IsWeatherWet()) + if (IsBiomeNoDownfall(a_Chunk.GetBiomeAt(POSX_TOINT - a_Chunk.GetPosX() * cChunkDef::Width, POSZ_TOINT - a_Chunk.GetPosZ() * cChunkDef::Width)) || GetWorld()->IsWeatherWet()) { if (POSY_TOINT > m_World->GetHeight(POSX_TOINT, POSZ_TOINT)) { @@ -1039,6 +1041,196 @@ void cEntity::DetectCacti(void) +void cEntity::DetectPortal() +{ + if (!GetWorld()->AreNetherPortalsEnabled() && !GetWorld()->AreEndPortalsEnabled()) + { + return; + } + + class cPortalChunkLoader : public cChunkStay + { + public: + cPortalChunkLoader(cEntity * a_Entity, Vector3i & a_PortalPos) : + m_Entity(a_Entity), + m_PortalPos(a_PortalPos) + {} + + private: + virtual bool OnAllChunksAvailable(void) override + { + m_Entity->CreateExitPortal(m_PortalPos.x, m_PortalPos.y, m_PortalPos.z); + return true; + } + + virtual void OnChunkAvailable(int a_ChunkX, int a_ChunkZ) override {}; + virtual void OnDisabled(void) override {}; + + cEntity * m_Entity; + Vector3i m_PortalPos; + }; + + 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 (!GetWorld()->AreNetherPortalsEnabled()) + { + return; + } + + switch (GetWorld()->GetDimension()) + { + case dimNether: MoveToWorld(GetWorld()->GetLinkedOverworldName(), cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedOverworldName())); break; + case dimOverworld: + { + if (IsPlayer()) + { + ((cPlayer *)this)->AwardAchievement(achEnterPortal); + } + MoveToWorld(GetWorld()->GetNetherWorldName(), cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetNetherWorldName(), dimNether, GetWorld()->GetName())); + + cChunkStay * Stay = new cPortalChunkLoader(this, Vector3i(X, Y, Z)); + + int MinChunkX, MaxChunkX; + int MinChunkZ, MaxChunkZ; + cChunkDef::BlockToChunk(X - 128, Z - 128, MinChunkX, MinChunkZ); + cChunkDef::BlockToChunk(X + 128, Z + 128, MaxChunkX, MaxChunkZ); + + for (int OtherMinChunkX = MinChunkX; OtherMinChunkX <= MaxChunkX; ++OtherMinChunkX) + { + for (int OtherMinChunkZ = MinChunkZ; OtherMinChunkZ <= MaxChunkZ; ++OtherMinChunkZ) + { + Stay->Add(OtherMinChunkX, OtherMinChunkZ); + } + } + + Stay->Enable(*GetWorld()->GetChunkMap()); + break; + } + default: break; + } + break; + } + case E_BLOCK_END_PORTAL: + { + if (!GetWorld()->AreEndPortalsEnabled()) + { + return; + } + + switch (GetWorld()->GetDimension()) + { + case dimEnd: + { + MoveToWorld(GetWorld()->GetLinkedOverworldName(), cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedOverworldName())); + + if (IsPlayer()) + { + cPlayer * Player = (cPlayer *)this; + Player->TeleportToCoords(Player->GetLastBedPos().x, Player->GetLastBedPos().y, Player->GetLastBedPos().z); + } + break; + } + case dimOverworld: + { + if (IsPlayer()) + { + ((cPlayer *)this)->AwardAchievement(achEnterTheEnd); + } + MoveToWorld(GetWorld()->GetEndWorldName(), cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetEndWorldName(), dimEnd, GetWorld()->GetName())); + break; + } + default: break; + } + } + default: break; + } + } +} + + + + + +void cEntity::CreateExitPortal(int a_BlockX, int a_BlockY, int a_BlockZ) +{ + cBlockArea Area; + Area.Read(GetWorld(), a_BlockX - 128, a_BlockX + 128, 0, 128, a_BlockZ - 128, a_BlockZ + 128); + for (int x = a_BlockX - 128; x <= a_BlockX + 128; ++x) for (int y = 0; y <= 128; ++y) for (int z = a_BlockZ - 128; z <= a_BlockZ + 128; ++z) + { + if ( + (Area.GetBlockType(x, y, z) == E_BLOCK_NETHER_PORTAL) && + ( + (Area.GetBlockType(x, (int)floor(y + GetHeight()), z) == E_BLOCK_NETHER_PORTAL) || + (Area.GetBlockType(x, (int)floor(y - GetHeight()), z) == E_BLOCK_NETHER_PORTAL) + ) + ) + { + TeleportToCoords(x, y, z); + return; + } + } + + int MinX = std::max(a_BlockX - (int)ceil(GetWidth()), a_BlockX - 2), MaxX = std::max(a_BlockX + (int)ceil(GetWidth()), a_BlockX + 1); + int MinY = std::max(a_BlockY - (int)ceil(GetHeight()), a_BlockY - 2), MaxY = std::max(a_BlockY + (int)ceil(GetHeight()), a_BlockY + 1); + + for (int y = MinY; y < MaxY + 1; y += MaxY - MinY) for (int x = MinX; x < MaxX + 1; ++x) + { + Area.SetBlockType(x, y, a_BlockZ, E_BLOCK_OBSIDIAN); + } + for (int y = MinY; y < MaxY + 1; ++y) for (int x = MinX; x < MaxX + 1; x += MaxX - MinX) + { + Area.SetBlockType(x, y, a_BlockZ, E_BLOCK_OBSIDIAN); + } + + Area.Write(GetWorld(), MinX, MinY, a_BlockZ); +} + + + + + +bool cEntity::MoveToWorld(const AString & a_WorldName, cWorld * a_World) +{ + cWorld * World; + if (a_World == NULL) + { + World = cRoot::Get()->GetWorld(a_WorldName); + if (World == NULL) + { + LOG("%s: Couldn't find world \"%s\".", __FUNCTION__, a_WorldName.c_str()); + return false; + } + } + else + { + World = a_World; + } + + if (GetWorld() == World) + { + return false; + } + + // Remove all links to the old world + GetWorld()->RemoveEntity(this); + GetWorld()->BroadcastDestroyEntity(*this); + + // Add to all the necessary parts of the new world + SetWorld(World); + World->AddEntity(this); + + return true; +} + + + + + 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 85ad42d54..934e0302b 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -324,6 +324,9 @@ 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 */ + virtual void DetectPortal(void); /// Handles when the entity is in the void virtual void TickInVoid(cChunk & a_Chunk); @@ -333,6 +336,9 @@ public: /// Called when the entity finishes burning virtual void OnFinishedBurning(void); + + /** Creates exit portal at given coordinates */ + void CreateExitPortal(int a_BlockX, int a_BlockY, int a_BlockZ); // tolua_begin @@ -366,6 +372,9 @@ public: /// Teleports to the coordinates specified virtual void TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ); + + /** Moves entity to specified world */ + virtual bool MoveToWorld(const AString & a_WorldName, cWorld * a_World = NULL); // tolua_end diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index feb09b5d2..4d6688694 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -82,13 +82,14 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) m_PlayerName = a_PlayerName; - if (!LoadFromDisk()) + cWorld * World; + 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(World->GetSpawnX(), World->GetSpawnY(), 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() @@ -101,11 +102,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; @@ -134,7 +130,7 @@ cPlayer::~cPlayer(void) SaveToDisk(); - m_World->RemovePlayer( this ); + m_World->RemovePlayer(this); m_ClientHandle = NULL; @@ -150,8 +146,6 @@ cPlayer::~cPlayer(void) void cPlayer::Destroyed() { CloseWindow(false); - - m_ClientHandle = NULL; } @@ -954,12 +948,12 @@ void cPlayer::Respawn(void) m_LifetimeTotalXp = 0; // ToDo: send score to client? How? - m_ClientHandle->SendRespawn(*m_World); + m_ClientHandle->SendRespawn(*GetWorld()); // Extinguish the fire: StopBurning(); - TeleportToCoords(GetWorld()->GetSpawnX(), GetWorld()->GetSpawnY(), GetWorld()->GetSpawnZ()); + TeleportToCoords(GetLastBedPos().x, GetLastBedPos().y, GetLastBedPos().z); SetVisible(true); } @@ -1576,12 +1570,25 @@ void cPlayer::TossItems(const cItems & a_Items) -bool cPlayer::MoveToWorld(const char * a_WorldName) +bool cPlayer::MoveToWorld(const AString & a_WorldName, cWorld * a_World) { - cWorld * World = cRoot::Get()->GetWorld(a_WorldName); - if (World == NULL) + cWorld * World; + if (a_World == NULL) + { + World = cRoot::Get()->GetWorld(a_WorldName); + if (World == NULL) + { + LOG("%s: Couldn't find world \"%s\".", __FUNCTION__, a_WorldName.c_str()); + return false; + } + } + else + { + World = a_World; + } + + if (GetWorld() == World) { - LOG("%s: Couldn't find world \"%s\".", __FUNCTION__, a_WorldName); return false; } @@ -1600,6 +1607,13 @@ bool cPlayer::MoveToWorld(const char * a_WorldName) // Queue adding player to the new world, including all the necessary adjustments to the object World->AddPlayer(this); + if (GetWorld()->GetDimension() != World->GetDimension()) + { + GetClientHandle()->SendPlayerMoveLook(); + GetClientHandle()->SendHealth(); + GetClientHandle()->SendWholeInventory(*GetWindow()); + } + return true; } @@ -1646,8 +1660,14 @@ void cPlayer::LoadPermissionsFromDisk() -bool cPlayer::LoadFromDisk() +bool cPlayer::LoadFromDisk(cWorld *& a_World) { + a_World = cRoot::Get()->GetWorld(GetLoadedWorldName()); + if (a_World == NULL) + { + a_World = cRoot::Get()->GetDefaultWorld(); + } + LoadPermissionsFromDisk(); AString SourceFile; @@ -1701,6 +1721,9 @@ bool cPlayer::LoadFromDisk() m_LifetimeTotalXp = (short) root.get("xpTotal", 0).asInt(); m_CurrentXp = (short) root.get("xpCurrent", 0).asInt(); m_IsFlying = root.get("isflying", 0).asBool(); + 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(); m_GameMode = (eGameMode) root.get("gamemode", eGameMode_NotSet).asInt(); @@ -1719,7 +1742,7 @@ bool cPlayer::LoadFromDisk() StatSerializer.Load(); LOGD("Player \"%s\" was read from file, spawning at {%.2f, %.2f, %.2f} in world \"%s\"", - GetName().c_str(), GetPosX(), GetPosY(), GetPosZ(), m_LoadedWorldName.c_str() + GetName().c_str(), GetPosX(), GetPosY(), GetPosZ(), GetLoadedWorldName().c_str() ); return true; @@ -1761,6 +1784,9 @@ bool cPlayer::SaveToDisk() root["foodExhaustion"] = m_FoodExhaustionLevel; root["world"] = GetWorld()->GetName(); root["isflying"] = IsFlying(); + root["SpawnX"] = GetLastBedPos().x; + root["SpawnY"] = GetLastBedPos().y; + root["SpawnZ"] = GetLastBedPos().z; if (m_GameMode == GetWorld()->GetGameMode()) { diff --git a/src/Entities/Player.h b/src/Entities/Player.h index 83b9ad593..99a0e601c 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -127,7 +127,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; @@ -330,10 +330,15 @@ 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 MoveToWorld(const AString & a_WorldName, cWorld * a_World = NULL) override; // tolua_export + /** Saves all player data, such as inventory, to JSON */ bool SaveToDisk(void); - bool LoadFromDisk(void); + + /** Loads player data from JSON to the object + Takes a (NULL) cWorld pointer which it will assign a value to based on either the loaded world or default world + */ + bool LoadFromDisk(cWorld *& a_World); void LoadPermissionsFromDisk(void); // tolua_export const AString & GetLoadedWorldName() { return m_LoadedWorldName; } @@ -344,8 +349,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, the item that the player is dragging */ cItem & GetDraggingItem(void) {return m_DraggingItem; } // In UI windows, when inventory-painting: @@ -393,11 +397,19 @@ 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 */ + 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: @@ -452,6 +464,9 @@ protected: cWindow * m_CurrentWindow; cWindow * m_InventoryWindow; + /** The player's last saved bed position */ + Vector3i m_LastBedPos; + char m_Color; eGameMode m_GameMode; @@ -510,8 +525,6 @@ protected: cStatManager m_Stats; - - void ResolvePermissions(void); void ResolveGroups(void); diff --git a/src/Generating/BioGen.cpp b/src/Generating/BioGen.cpp index 47ba080c6..36a195a3d 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 7711723fc..759d57b83 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 8edc2800b..2680fe3fa 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 73f0223e8..1f2958901 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 1801b7375..7f57343ad 100644 --- a/src/Generating/ComposableGenerator.cpp +++ b/src/Generating/ComposableGenerator.cpp @@ -43,7 +43,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; @@ -97,7 +96,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); } @@ -295,19 +293,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 f466039b9..5be73bfbe 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 f2d66af70..c77145bc6 100644 --- a/src/Generating/FinishGen.cpp +++ b/src/Generating/FinishGen.cpp @@ -125,6 +125,15 @@ void cFinishGenNetherClumpFoliage::TryPlaceClump(cChunkDesc & a_ChunkDesc, int a float zz = (float) a_ChunkDesc.GetChunkZ() * cChunkDef::Width + z; for (int y = a_RelY - 2; y < a_RelY + 2; 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/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index 5843ca5a6..0f030da2d 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -1034,7 +1034,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 + (IsBiomeNoDownfall(a_Chunk.GetBiomeAt(RelX, RelZ)) || !GetWorld()->IsWeatherWet()) // Not raining ) { // Burn for 100 ticks, then decide again diff --git a/src/Protocol/Protocol125.cpp b/src/Protocol/Protocol125.cpp index 491058919..5796ba271 100644 --- a/src/Protocol/Protocol125.cpp +++ b/src/Protocol/Protocol125.cpp @@ -843,7 +843,7 @@ void cProtocol125::SendRespawn(const cWorld & a_World) } cPlayer * Player = m_Client->GetPlayer(); WriteByte (PACKET_RESPAWN); - WriteInt (a_World.GetDimension()); + WriteInt ((int)(a_World.GetDimension())); WriteByte (2); // TODO: Difficulty; 2 = Normal WriteChar ((char)Player->GetGameMode()); WriteShort (256); // Current world height diff --git a/src/Protocol/Protocol17x.cpp b/src/Protocol/Protocol17x.cpp index 02c577dc8..f5b176e54 100644 --- a/src/Protocol/Protocol17x.cpp +++ b/src/Protocol/Protocol17x.cpp @@ -996,7 +996,7 @@ void cProtocol172::SendRespawn(const cWorld & a_World) cPacketizer Pkt(*this, 0x07); // Respawn packet cPlayer * Player = m_Client->GetPlayer(); - Pkt.WriteInt(a_World.GetDimension()); + Pkt.WriteInt((int)a_World.GetDimension()); Pkt.WriteByte(2); // TODO: Difficulty (set to Normal) Pkt.WriteByte((Byte)Player->GetEffectiveGameMode()); Pkt.WriteString("default"); diff --git a/src/Root.cpp b/src/Root.cpp index c82b05a66..572cbf1fc 100644 --- a/src/Root.cpp +++ b/src/Root.cpp @@ -314,15 +314,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) { return NULL; } - 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->Start(!a_OverworldName.empty()); NewWorld->InitializeSpawn(); m_PluginManager->CallHookWorldStarted(*NewWorld); return NewWorld; @@ -372,7 +372,7 @@ void cRoot::UnloadWorlds(void) -cWorld* cRoot::GetDefaultWorld() +cWorld * cRoot::GetDefaultWorld() { return m_pDefaultWorld; } @@ -381,12 +381,14 @@ cWorld* cRoot::GetDefaultWorld() -cWorld* cRoot::GetWorld( const AString & a_WorldName ) +cWorld * cRoot::GetWorld(const AString & a_WorldName) { - WorldMap::iterator itr = m_WorldsByName.find( a_WorldName ); - if( itr != m_WorldsByName.end() ) + WorldMap::iterator itr = m_WorldsByName.find(a_WorldName); + if (itr != m_WorldsByName.end()) + { return itr->second; - return 0; + } + return NULL; } @@ -398,9 +400,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 d2a4d1eed..1b56b4528 100644 --- a/src/Root.h +++ b/src/Root.h @@ -44,7 +44,7 @@ public: cServer * GetServer(void) { return m_Server; } // tolua_export cWorld * GetDefaultWorld(void); // tolua_export cWorld * GetWorld(const AString & a_WorldName); // tolua_export - cWorld * CreateAndInitializeWorld(const AString & a_WorldName); // tolua_export + cWorld * CreateAndInitializeWorld(const AString & a_WorldName, eDimension a_Dimension = dimOverworld, const AString & a_OverworldName = ""); // tolua_export /// Calls the callback for each world; returns true if the callback didn't abort (return true) bool ForEachWorld(cWorldListCallback & a_Callback); // >> Exported in ManualBindings << diff --git a/src/UI/Window.cpp b/src/UI/Window.cpp index 98a9a0cec..3922ac489 100644 --- a/src/UI/Window.cpp +++ b/src/UI/Window.cpp @@ -283,7 +283,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 6bcd1391a..6904000ea 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -66,6 +66,9 @@ const int TIME_NIGHT_END = 22812; const int TIME_SUNRISE = 23999; const int TIME_SPAWN_DIVISOR = 148; +#define DEFAULT_NETHER_NAME GetName() + "_nether" +#define DEFAULT_END_NAME GetName() + "_end" + @@ -229,7 +232,7 @@ 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_IniFileName(m_WorldName + "/world.ini"), m_StorageSchema("Default"), @@ -250,7 +253,9 @@ cWorld::cWorld(const AString & a_WorldName) : m_Scoreboard(this), m_MapManager(this), m_GeneratorCallbacks(*this), - m_TickThread(*this) + m_TickThread(*this), + m_Dimension(a_Dimension), + m_OverworldName(a_OverworldName) { LOGD("cWorld::cWorld(\"%s\")", a_WorldName.c_str()); @@ -508,7 +513,7 @@ void cWorld::InitializeSpawn(void) -void cWorld::Start(void) +void cWorld::Start(bool a_WasDimensionSet) { m_SpawnX = 0; m_SpawnY = cChunkDef::Height; @@ -520,8 +525,10 @@ void cWorld::Start(void) { LOGWARNING("Cannot read world settings from \"%s\", defaults will be used.", m_IniFileName.c_str()); } - AString Dimension = IniFile.GetValueSet("General", "Dimension", "Overworld"); + + AString Dimension = IniFile.GetValueSet("General", "Dimension", a_WasDimensionSet ? DimensionToString(GetDimension()) : "Overworld"); m_Dimension = StringToDimension(Dimension); + m_OverworldName = IniFile.GetValue("General", "OverworldName", a_WasDimensionSet ? m_OverworldName : ""); // Try to find the "SpawnPosition" key and coord values in the world configuration, set the flag if found int KeyNum = IniFile.FindKey("SpawnPosition"); @@ -566,10 +573,52 @@ 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() != dimNether) && (GetDimension() != dimEnd)) + { + m_bNetherPortalsEnabled = IniFile.GetValueSetB("General", "NetherPortalsEnabled", true); + m_NetherWorldName = IniFile.GetValueSet("General", "NetherWorldName", DEFAULT_NETHER_NAME); + m_bEndPortalsEnabled = IniFile.GetValueSetB("General", "EndPortalsEnabled", true); + m_EndWorldName = IniFile.GetValueSet("General", "EndWorldName", DEFAULT_END_NAME); + } // 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); + + switch (GetDimension()) + { + case dimEnd: + { + IniFile.GetValueSet("Generator", "BiomeGen", "Constant"); + IniFile.GetValueSet("Generator", "ConstantBiome", "End"); + IniFile.GetValueSet("Generator", "HeightGen", "Biomal"); + IniFile.GetValueSet("Generator", "CompositionGen", "End"); + break; + } + case dimOverworld: + { + IniFile.GetValueSet("Generator", "BiomeGen", "MultiStepMap"); + IniFile.GetValueSet("Generator", "HeightGen", "DistortedHeightmap"); + IniFile.GetValueSet("Generator", "CompositionGen", "DistortedHeightmap"); + IniFile.GetValueSet("Generator", "Finishers", "Ravines, WormNestCaves, WaterLakes, WaterSprings, LavaLakes, LavaSprings, OreNests, Mineshafts, Trees, SprinkleFoliage, Ice, Snow, Lilypads, BottomLava, DeadBushes, PreSimulator"); + break; + } + case dimNether: + { + IniFile.GetValueSet("Generator", "BiomeGen", "Constant"); + IniFile.GetValueSet("Generator", "ConstantBiome", "Nether"); + IniFile.GetValueSet("Generator", "HeightGen", "Flat"); + IniFile.GetValueSet("Generator", "FlatHeight", "128"); + IniFile.GetValueSet("Generator", "CompositionGen", "Nether"); + IniFile.GetValueSet("Generator", "Finishers", "WormNestCaves, BottomLava, LavaSprings, NetherClumpFoliage, NetherForts, PreSimulator"); + IniFile.GetValueSet("Generator", "BottomLavaHeight", "30"); + break; + } + } // Load allowed mobs: const char * DefaultMonsters = ""; @@ -703,6 +752,27 @@ 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() != dimNether) && (GetDimension() != dimEnd)) + { + IniFile.SetValueB("General", "NetherPortalsEnabled", m_bNetherPortalsEnabled); + IniFile.SetValue("General", "NetherWorldName", m_NetherWorldName); + IniFile.SetValueB("General", "EndPortalsEnabled", m_bEndPortalsEnabled); + IniFile.SetValue("General", "EndWorldName", m_EndWorldName); + } + else + { + IniFile.SetValue("General", "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(); diff --git a/src/World.h b/src/World.h index abdc3120c..6ac58b09e 100644 --- a/src/World.h +++ b/src/World.h @@ -632,6 +632,21 @@ public: bool ShouldUseChatPrefixes(void) const { return m_bUseChatPrefixes; } void SetShouldUseChatPrefixes(bool a_Flag) { m_bUseChatPrefixes = a_Flag; } + + bool AreNetherPortalsEnabled(void) const { return m_bNetherPortalsEnabled; } + void SetNetherPortalsEnabled(bool a_Flag) { m_bNetherPortalsEnabled = a_Flag; } + + bool AreEndPortalsEnabled(void) const { return m_bEndPortalsEnabled; } + void SetEndPortalsEnabled(bool a_Flag) { m_bEndPortalsEnabled = 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 @@ -675,7 +690,7 @@ public: void InitializeSpawn(void); /** Starts threads that belong to this world */ - void Start(void); + void Start(bool a_WasDimensionSet = true); /** Stops threads that belong to this world (part of deinit) */ void Stop(void); @@ -711,9 +726,11 @@ public: bool IsWeatherRain (void) const { return (m_Weather == wRain); } bool IsWeatherStorm(void) const { return (m_Weather == wStorm); } - /** Returns true if the current weather has any precipitation - rain or storm */ - bool IsWeatherWet (void) const { return (m_Weather != wSunny); } - + /** Returns true if the current weather has any precipitation - rain or storm + Does not check if biome has no downfall, use cChunk::GetBiomeAt(RelX, RelZ) for that + */ + virtual bool IsWeatherWet(void) const override { return (m_Weather != wSunny); } + // tolua_end cChunkGenerator & GetGenerator(void) { return m_Generator; } @@ -810,6 +827,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 */ @@ -894,6 +917,18 @@ private: See the eShrapnelLevel enumeration for details */ eShrapnelLevel m_TNTShrapnelLevel; + + /** Whether nether portals teleport entities */ + bool m_bNetherPortalsEnabled; + + /** Whether end portals teleport entities */ + bool m_bEndPortalsEnabled; + + /** Name of the nether world */ + AString m_NetherWorldName; + + /** Name of the end world */ + AString m_EndWorldName; cChunkGenerator m_Generator; @@ -947,7 +982,7 @@ private: cPlayerList m_PlayersToAdd; - 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); diff --git a/src/WorldStorage/NBTChunkSerializer.cpp b/src/WorldStorage/NBTChunkSerializer.cpp index a3b0d57be..70c07af50 100644 --- a/src/WorldStorage/NBTChunkSerializer.cpp +++ b/src/WorldStorage/NBTChunkSerializer.cpp @@ -484,6 +484,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 1891762fd..c84763ac2 100644 --- a/src/WorldStorage/WSSAnvil.cpp +++ b/src/WorldStorage/WSSAnvil.cpp @@ -2454,8 +2454,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; } |