diff options
-rw-r--r-- | Server/Plugins/APIDump/APIDesc.lua | 1 | ||||
-rwxr-xr-x | compile.sh | 2 | ||||
-rw-r--r-- | src/ClientHandle.cpp | 2 | ||||
-rw-r--r-- | src/Entities/Entity.cpp | 14 | ||||
-rw-r--r-- | src/Entities/Player.cpp | 2 | ||||
-rw-r--r-- | src/IniFile.cpp | 2 | ||||
-rw-r--r-- | src/Root.cpp | 108 | ||||
-rw-r--r-- | src/Root.h | 15 | ||||
-rw-r--r-- | src/World.cpp | 48 |
9 files changed, 141 insertions, 53 deletions
diff --git a/Server/Plugins/APIDump/APIDesc.lua b/Server/Plugins/APIDump/APIDesc.lua index 97e17c7c6..0cbdff479 100644 --- a/Server/Plugins/APIDump/APIDesc.lua +++ b/Server/Plugins/APIDump/APIDesc.lua @@ -2126,7 +2126,6 @@ a_Player:OpenWindow(Window); BroadcastChatLeave = { Params = "MessageText", Return = "", Notes = "Broadcasts the specified message to all players, with its message type set to mtLeave. Use for players leaving the server." }, BroadcastChatSuccess = { Params = "MessageText", Return = "", Notes = "Broadcasts the specified message to all players, with its message type set to mtSuccess. Use for success messages." }, BroadcastChatWarning = { Params = "MessageText", Return = "", Notes = "Broadcasts the specified message to all players, with its message type set to mtWarning. Use for concerning events, such as plugin reload etc." }, - CreateAndInitializeWorld = { Params = "WorldName", Return = "{{cWorld|cWorld}}", Notes = "Creates a new world and initializes it. If there is a world whith the same name it returns nil.<br><br><b>NOTE:</b> This function is currently unsafe, do not use!" }, FindAndDoWithPlayer = { Params = "PlayerName, CallbackFunction", Return = "bool", Notes = "Calls the given callback function for the player with the name best matching the name string provided.<br>This function is case-insensitive and will match partial names.<br>Returns false if player not found or there is ambiguity, true otherwise. The CallbackFunction has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cPlayer|Player}})</pre>" }, DoWithPlayerByUUID = { Params = "PlayerUUID, CallbackFunction", Return = "bool", Notes = "If there is the player with the uuid, calls the CallbackFunction with the {{cPlayer}} parameter representing the player. The CallbackFunction has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cPlayer|Player}})</pre> The function returns false if the player was not found, or whatever bool value the callback returned if the player was found." }, ForEachPlayer = { Params = "CallbackFunction", Return = "", Notes = "Calls the given callback function for each player. The callback function has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cPlayer|cPlayer}})</pre>" }, diff --git a/compile.sh b/compile.sh index 84ed478a2..38aa5f69e 100755 --- a/compile.sh +++ b/compile.sh @@ -215,7 +215,7 @@ cmake .. -DCMAKE_BUILD_TYPE=$BUILDTYPE || error "cmake failed" # Make. echo " --- Compiling..." -make -j`nproc` || error "Compiling failed" +make -j 2 || error "Compiling failed" echo diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index 792ca49b5..df85d9b67 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -1033,6 +1033,7 @@ void cClientHandle::HandleLeftClick(int a_BlockX, int a_BlockY, int a_BlockZ, eB { // A plugin doesn't agree with the action, replace the block on the client and quit: m_Player->GetWorld()->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, m_Player); + SendPlayerPosition(); // Prevents the player from falling through the block that was temporarily broken client side. return; } @@ -1229,6 +1230,7 @@ void cClientHandle::HandleBlockDigFinished(int a_BlockX, int a_BlockY, int a_Blo { // A plugin doesn't agree with the breaking. Bail out. Send the block back to the client, so that it knows: m_Player->GetWorld()->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, m_Player); + SendPlayerPosition(); // Prevents the player from falling through the block that was temporarily broken client side. return; } diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index b207e79c9..d0540b4eb 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -1392,7 +1392,8 @@ bool cEntity::DetectPortal() TargetPos.x *= 8.0; TargetPos.z *= 8.0; - cWorld * TargetWorld = cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedOverworldName(), dimNether, GetWorld()->GetName(), true); + cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName()); + ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start() LOGD("Jumping nether -> overworld"); new cNetherPortalScanner(this, TargetWorld, TargetPos, 256); return true; @@ -1416,7 +1417,8 @@ bool cEntity::DetectPortal() TargetPos.x /= 8.0; TargetPos.z /= 8.0; - cWorld * TargetWorld = cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedNetherWorldName(), dimNether, GetWorld()->GetName(), true); + cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedNetherWorldName()); + ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start() LOGD("Jumping overworld -> nether"); new cNetherPortalScanner(this, TargetWorld, TargetPos, 128); return true; @@ -1446,7 +1448,9 @@ bool cEntity::DetectPortal() Player->GetClientHandle()->SendRespawn(dimOverworld); } - return MoveToWorld(cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedOverworldName()), false); + cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName()); + ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start() + return MoveToWorld(TargetWorld, false); } else { @@ -1463,7 +1467,9 @@ bool cEntity::DetectPortal() reinterpret_cast<cPlayer *>(this)->GetClientHandle()->SendRespawn(dimEnd); } - return MoveToWorld(cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedEndWorldName(), dimEnd, GetWorld()->GetName()), false); + cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedEndWorldName()); + ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start() + return MoveToWorld(TargetWorld, false); } } diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index 5606e9668..b7f6f4d05 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -1871,7 +1871,7 @@ bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World) cEnderChestEntity::LoadFromJson(root["enderchestinventory"], m_EnderChestContents); m_LoadedWorldName = root.get("world", "world").asString(); - a_World = cRoot::Get()->GetWorld(GetLoadedWorldName(), false); + a_World = cRoot::Get()->GetWorld(GetLoadedWorldName()); if (a_World == nullptr) { a_World = cRoot::Get()->GetDefaultWorld(); diff --git a/src/IniFile.cpp b/src/IniFile.cpp index 1e5416813..4a1c8e735 100644 --- a/src/IniFile.cpp +++ b/src/IniFile.cpp @@ -146,7 +146,7 @@ bool cIniFile::ReadFile(const AString & a_FileName, bool a_AllowExampleRedirect) case '=': { valuename = line.substr(0, pLeft); - value = line.substr(pLeft + 1); + value = TrimString(line.substr(pLeft + 1)); AddValue(keyname, valuename, value); break; } diff --git a/src/Root.cpp b/src/Root.cpp index 737d350ff..87c255b9c 100644 --- a/src/Root.cpp +++ b/src/Root.cpp @@ -327,6 +327,17 @@ void cRoot::Start(std::unique_ptr<cSettingsRepositoryInterface> a_OverridesRepo) +void cRoot::StopServer() +{ + m_TerminateEventRaised = true; + m_StopEvent.Set(); + m_InputThreadRunFlag.clear(); +} + + + + + void cRoot::LoadGlobalSettings() { // Nothing needed yet @@ -338,9 +349,9 @@ void cRoot::LoadGlobalSettings() void cRoot::LoadWorlds(cSettingsRepositoryInterface & a_Settings, bool a_IsNewIniFile) { - // First get the default world if (a_IsNewIniFile) { + a_Settings.AddValue("Worlds", "DefaultWorld", "world"); a_Settings.AddValue("Worlds", "World", "world_nether"); a_Settings.AddValue("Worlds", "World", "world_end"); m_pDefaultWorld = new cWorld("world"); @@ -350,6 +361,7 @@ void cRoot::LoadWorlds(cSettingsRepositoryInterface & a_Settings, bool a_IsNewIn return; } + // First get the default world AString DefaultWorldName = a_Settings.GetValueSet("Worlds", "DefaultWorld", "world"); m_pDefaultWorld = new cWorld(DefaultWorldName.c_str()); m_WorldsByName[ DefaultWorldName ] = m_pDefaultWorld; @@ -398,6 +410,7 @@ void cRoot::LoadWorlds(cSettingsRepositoryInterface & a_Settings, bool a_IsNewIn a_Settings.AddValue("Worlds", "World", "world_nether"); a_Settings.AddValue("Worlds", "World", "world_end"); Worlds = a_Settings.GetValues("Worlds"); // Refresh the Worlds list so that the rest of the function works as usual + LOG("The server detected an old default config with bad world linkages. This has been autofixed by adding \"world_nether\" and \"world_end\" to settings.ini"); } } } @@ -409,6 +422,28 @@ void cRoot::LoadWorlds(cSettingsRepositoryInterface & a_Settings, bool a_IsNewIn return; } + /* Here are the world creation rules. Note that these only apply for a world which is in settings.ini but has no world.ini file. + If an ini file is present, it overrides the world linkages and the dimension type in cWorld::start() + The creation rules are as follows: + + - If a world exists in settings.ini but has no world.ini, then: + - If the world name is x_nether, create a world.ini with the dimension type "nether". + - If a world called x exists, set it as x_nether's overworld. + - Otherwise set the default world as x_nether's overworld. + + - If the world name is x_end, create a world.ini with the dimension type "end". + - If a world called x exists, set it as x_end's overworld. + - Otherwise set the default world as x_end's overworld. + + - If the world name is x (and doesn't end with _end or _nether) + - Create a world.ini with a dimension type of "overworld". + - If a world called x_nether exists, set it as x's nether world. + - Otherwise set x's nether world to blank.h + - If a world called x_end exists, set it as x's end world. + - Otherwise set x's nether world to blank. + + */ + bool FoundAdditionalWorlds = false; for (auto WorldNameValue : Worlds) { @@ -423,7 +458,43 @@ void cRoot::LoadWorlds(cSettingsRepositoryInterface & a_Settings, bool a_IsNewIn continue; } FoundAdditionalWorlds = true; - cWorld * NewWorld = new cWorld(WorldName.c_str()); + cWorld * NewWorld; + AString LowercaseName = StrToLower(WorldName); + AString NetherAppend="_nether"; + AString EndAppend="_end"; + + // if the world is called x_nether + if ((LowercaseName.size() > NetherAppend.size()) && (LowercaseName.substr(LowercaseName.size() - NetherAppend.size()) == NetherAppend)) + { + // The world is called x_nether, see if a world called x exists. If yes, choose it as the linked world, + // otherwise, choose the default world as the linked world. + // As before, any ini settings will completely override this if an ini is already present. + + AString LinkTo = WorldName.substr(0, WorldName.size() - NetherAppend.size()); + if (GetWorld(LinkTo) == nullptr) + { + LinkTo = DefaultWorldName; + } + NewWorld = new cWorld(WorldName.c_str(), dimNether, LinkTo); + } + // if the world is called x_end + else if ((LowercaseName.size() > EndAppend.size()) && (LowercaseName.substr(LowercaseName.size() - EndAppend.size()) == EndAppend)) + { + // The world is called x_end, see if a world called x exists. If yes, choose it as the linked world, + // otherwise, choose the default world as the linked world. + // As before, any ini settings will completely override this if an ini is already present. + + AString LinkTo = WorldName.substr(0, WorldName.size() - EndAppend.size()); + if (GetWorld(LinkTo) == nullptr) + { + LinkTo = DefaultWorldName; + } + NewWorld = new cWorld(WorldName.c_str(), dimEnd, LinkTo); + } + else + { + NewWorld = new cWorld(WorldName.c_str()); + } m_WorldsByName[WorldName] = NewWorld; } // for i - Worlds @@ -441,29 +512,6 @@ void cRoot::LoadWorlds(cSettingsRepositoryInterface & a_Settings, bool a_IsNewIn -cWorld * cRoot::CreateAndInitializeWorld(const AString & a_WorldName, eDimension a_Dimension, const AString & a_OverworldName, bool a_InitSpawn) -{ - cWorld * World = m_WorldsByName[a_WorldName]; - if (World != nullptr) - { - return World; - } - - cWorld * NewWorld = new cWorld(a_WorldName.c_str(), a_Dimension, a_OverworldName); - m_WorldsByName[a_WorldName] = NewWorld; - NewWorld->Start(); - if (a_InitSpawn) - { - NewWorld->InitializeSpawn(); - } - m_PluginManager->CallHookWorldStarted(*NewWorld); - return NewWorld; -} - - - - - void cRoot::StartWorlds(void) { for (WorldMap::iterator itr = m_WorldsByName.begin(); itr != m_WorldsByName.end(); ++itr) @@ -513,7 +561,7 @@ cWorld * cRoot::GetDefaultWorld() -cWorld * cRoot::GetWorld(const AString & a_WorldName, bool a_SearchForFolder) +cWorld * cRoot::GetWorld(const AString & a_WorldName) { WorldMap::iterator itr = m_WorldsByName.find(a_WorldName); if (itr != m_WorldsByName.end()) @@ -521,10 +569,6 @@ cWorld * cRoot::GetWorld(const AString & a_WorldName, bool a_SearchForFolder) return itr->second; } - if (a_SearchForFolder && cFile::IsFolder(FILE_IO_PREFIX + a_WorldName)) - { - return CreateAndInitializeWorld(a_WorldName); - } return nullptr; } @@ -597,9 +641,7 @@ void cRoot::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallback // Some commands are built-in: if (a_Cmd == "stop") { - m_TerminateEventRaised = true; - m_StopEvent.Set(); - m_InputThreadRunFlag.clear(); + StopServer(); return; } else if (a_Cmd == "restart") diff --git a/src/Root.h b/src/Root.h index 261dac29f..24c8216d9 100644 --- a/src/Root.h +++ b/src/Root.h @@ -57,20 +57,15 @@ public: void Start(std::unique_ptr<cSettingsRepositoryInterface> a_OverridesRepo); + /** Stops the server, as if "/stop" was typed in the console. */ + void StopServer(); + // tolua_begin cServer * GetServer(void) { return m_Server; } cWorld * GetDefaultWorld(void); - /** 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 = "", bool a_InitSpawn = true); + /** Returns a pointer to the world specified. If no world of that name exists, returns a nullptr. */ + cWorld * GetWorld(const AString & a_WorldName); /** Returns the up time of the server in seconds */ int GetServerUpTime(void) diff --git a/src/World.cpp b/src/World.cpp index 127621069..c704b46bb 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -512,14 +512,58 @@ void cWorld::Start(void) if (GetDimension() == dimOverworld) { - m_LinkedNetherWorldName = IniFile.GetValueSet("LinkedWorlds", "NetherWorldName", GetName() + "_nether"); - m_LinkedEndWorldName = IniFile.GetValueSet("LinkedWorlds", "EndWorldName", GetName() + "_end"); + AString MyNetherName = GetName() + "_nether"; + AString MyEndName = GetName() + "_end"; + if (cRoot::Get()->GetWorld(MyNetherName) == nullptr) + { + MyNetherName = ""; + } + if (cRoot::Get()->GetWorld(MyEndName) == nullptr) + { + MyEndName = ""; + } + m_LinkedNetherWorldName = IniFile.GetValueSet("LinkedWorlds", "NetherWorldName", MyNetherName); + m_LinkedEndWorldName = IniFile.GetValueSet("LinkedWorlds", "EndWorldName", MyEndName); } else { m_LinkedOverworldName = IniFile.GetValueSet("LinkedWorlds", "OverworldName", GetLinkedOverworldName()); } + // If we are linked to one or more worlds that do not exist, ask the server to stop. + AString BadWorlds = ""; + cRoot * Root = cRoot::Get(); + if (GetDimension() == dimOverworld) + { + if ((!m_LinkedNetherWorldName.empty()) && (Root->GetWorld(m_LinkedNetherWorldName) == nullptr)) + { + BadWorlds = m_LinkedNetherWorldName; + } + if ((!m_LinkedEndWorldName.empty()) && (Root->GetWorld(m_LinkedEndWorldName) == nullptr)) + { + if (!(BadWorlds.empty())) + { + BadWorlds += ", "; + } + BadWorlds += m_LinkedEndWorldName; + } + } + else + { + if ((!m_LinkedOverworldName.empty()) && (Root->GetWorld(m_LinkedOverworldName) == nullptr)) + { + BadWorlds = m_LinkedOverworldName; + } + } + if (!BadWorlds.empty()) + { + const char * WorldName = m_WorldName.c_str(); + LOGERROR("\n###### ERROR: \"%s\" is linked to the following nonexisting world/s:\n%s\n\nPlease edit %s/world.ini and fix this.\n\nNote that the server started enforcing proper world linkages recently. And people with older configs may naturally get this error. If you just want a working default config and don't mind losing this world, delete the folder \"%s\" and the server will receate one for you. Otherwise edit the world.ini file and fix the invalid linkages.\n\nMore help and info:\nhttps://forum.cuberite.org/thread-2366.html\n######\n", + WorldName, BadWorlds.c_str(), WorldName, WorldName); + cRoot::Get()->StopServer(); + } + + // Adjust the enum-backed variables into their respective bounds: m_GameMode = static_cast<eGameMode> (Clamp<int>(GameMode, gmSurvival, gmSpectator)); m_TNTShrapnelLevel = static_cast<eShrapnelLevel>(Clamp<int>(TNTShrapnelLevel, slNone, slAll)); |