diff --git a/lib/inifile/iniFile.cpp b/lib/inifile/iniFile.cpp index 66410b901..30f93e5a3 100644 --- a/lib/inifile/iniFile.cpp +++ b/lib/inifile/iniFile.cpp @@ -586,7 +586,11 @@ Int64 cIniFile::GetValueSetI(const AString & keyname, const AString & valuename, 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; } 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 a227245aa..97c1aae86 100644 --- a/src/BlockID.h +++ b/src/BlockID.h @@ -916,9 +916,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/Chunk.cpp b/src/Chunk.cpp index 2850dd93b..02857ba5a 100644 --- a/src/Chunk.cpp +++ b/src/Chunk.cpp @@ -1859,7 +1859,20 @@ 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::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); } diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index 2d8f385cb..4b376a1fe 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" @@ -1047,6 +1048,28 @@ void cEntity::DetectPortal() 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)) { @@ -1061,31 +1084,31 @@ void cEntity::DetectPortal() switch (GetWorld()->GetDimension()) { - case dimNether: - { - cIniFile OwnIni; - OwnIni.ReadFile(GetWorld()->GetIniFileName()); - AString OverworldName = OwnIni.GetValue("General", "OverworldName", cRoot::Get()->GetDefaultWorld()->GetName()); - - cFile::CreateFolder(FILE_IO_PREFIX + OverworldName); - cIniFile File; - File.ReadFile(OverworldName + "/world.ini"); - File.SetValue("General", "Dimension", "Overworld"); - File.WriteFile(OverworldName + "/world.ini"); - - MoveToWorld(OverworldName, cRoot::Get()->CreateAndInitializeWorld(OverworldName)); - break; - } + case dimNether: MoveToWorld(GetWorld()->GetLinkedOverworldName(), cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedOverworldName())); break; case dimOverworld: { - cFile::CreateFolder(FILE_IO_PREFIX + GetWorld()->GetNetherWorldName()); - cIniFile File; - File.ReadFile(GetWorld()->GetNetherWorldName() + "/world.ini"); - File.SetValue("General", "Dimension", "Nether"); - File.SetValue("General", "OverworldName", GetWorld()->GetName()); - File.WriteFile(GetWorld()->GetNetherWorldName() + "/world.ini"); + 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)); - MoveToWorld(GetWorld()->GetNetherWorldName(), cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetNetherWorldName())); + 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; @@ -1103,17 +1126,7 @@ void cEntity::DetectPortal() { case dimEnd: { - cIniFile OwnIni; - OwnIni.ReadFile(GetWorld()->GetIniFileName()); - AString OverworldName = OwnIni.GetValue("General", "OverworldName", cRoot::Get()->GetDefaultWorld()->GetName()); - - cFile::CreateFolder(FILE_IO_PREFIX + OverworldName); - cIniFile File; - File.ReadFile(OverworldName + "/world.ini"); - File.SetValue("General", "Dimension", "Overworld"); - File.WriteFile(OverworldName + "/world.ini"); - - MoveToWorld(OverworldName, cRoot::Get()->CreateAndInitializeWorld(OverworldName)); + MoveToWorld(GetWorld()->GetLinkedOverworldName(), cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedOverworldName())); if (IsPlayer()) { @@ -1124,14 +1137,11 @@ void cEntity::DetectPortal() } case dimOverworld: { - cFile::CreateFolder(FILE_IO_PREFIX + GetWorld()->GetEndWorldName()); - cIniFile File; - File.ReadFile(GetWorld()->GetEndWorldName() + "/world.ini"); - File.SetValue("General", "Dimension", "End"); - File.SetValue("General", "OverworldName", GetWorld()->GetName()); - File.WriteFile(GetWorld()->GetEndWorldName() + "/world.ini"); - - MoveToWorld(GetWorld()->GetEndWorldName(), cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetEndWorldName())); + if (IsPlayer()) + { + ((cPlayer *)this)->AwardAchievement(achEnterTheEnd); + } + MoveToWorld(GetWorld()->GetEndWorldName(), cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetEndWorldName(), dimEnd, GetWorld()->GetName())); break; } default: break; @@ -1146,6 +1156,44 @@ void cEntity::DetectPortal() +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; diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h index da8256606..b2317aaac 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -335,6 +335,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 diff --git a/src/Root.cpp b/src/Root.cpp index 2a80baeb3..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; 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/World.cpp b/src/World.cpp index 1b5582b81..1f4a88fa0 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -232,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"), @@ -253,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()); @@ -511,7 +513,7 @@ void cWorld::InitializeSpawn(void) -void cWorld::Start(void) +void cWorld::Start(bool a_WasDimensionSet) { m_SpawnX = 0; m_SpawnY = cChunkDef::Height; @@ -523,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"); @@ -570,7 +574,7 @@ void cWorld::Start(void) 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); + m_TimeOfDay = IniFile.GetValueSetI("General", "TimeInTicks", m_TimeOfDay); if ((GetDimension() != dimNether) && (GetDimension() != dimEnd)) { @@ -759,6 +763,10 @@ void cWorld::Stop(void) 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); diff --git a/src/World.h b/src/World.h index 80f69f22f..676c5d69a 100644 --- a/src/World.h +++ b/src/World.h @@ -636,6 +636,9 @@ public: 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 @@ -679,7 +682,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); @@ -816,6 +819,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 */ @@ -953,7 +962,7 @@ private: cClientHandleList m_ClientsToAdd; - 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);