#include "Platform/stdafx.h" // #include "Minecraft.h" #include #include #include #include "Input/ConsoleInput.h" #include "Level/DerivedServerLevel.h" #include "GameState/DispenserBootstrap.h" #include "Player/EntityTracker.h" #include "MinecraftServer.h" #include "GameState/Options.h" #include "Network/PlayerList.h" #include "Network/ServerChunkCache.h" #include "Network/ServerConnection.h" #include "Level/ServerLevel.h" #include "Level/ServerLevelListener.h" #include "GameState/Settings.h" #include "../Minecraft.World/Commands/Command.h" #include "../Minecraft.World/Util/AABB.h" #include "../Minecraft.World/Util/Vec3.h" #include "../Minecraft.World/Headers/net.minecraft.network.h" #include "../Minecraft.World/Headers/net.minecraft.world.level.dimension.h" #include "../Minecraft.World/Headers/net.minecraft.world.level.storage.h" #include "../Minecraft.World/Headers/net.minecraft.world.h" #include "../Minecraft.World/Headers/net.minecraft.world.level.h" #include "../Minecraft.World/Headers/net.minecraft.world.level.tile.h" #include "../Minecraft.World/Util/Pos.h" #include "../Minecraft.World/Platform/System.h" #include "../Minecraft.World/Util/StringHelpers.h" #if defined(SPLIT_SAVES) #include "../Minecraft.World/IO/Files/ConsoleSaveFileSplit.h" #endif #include "../Minecraft.World/IO/Files/ConsoleSaveFileOriginal.h" #include "../Minecraft.World/Network/Socket.h" #include "../Minecraft.World/Headers/net.minecraft.world.entity.h" #include "Rendering/EntityRenderers/ProgressRenderer.h" #include "Player/ServerPlayer.h" #include "Rendering/GameRenderer.h" #include "../Minecraft.World/Util/ThreadName.h" #include "../Minecraft.World/Level/Storage/CompressedTileStorage.h" #include "../Minecraft.World/Level/Storage/SparseLightStorage.h" #include "../Minecraft.World/Level/Storage/SparseDataStorage.h" #include "../Minecraft.World/IO/Streams/Compression.h" #include "Platform/Common/ShutdownManager.h" #include "Platform/Common/UI/UIStructs.h" #include "Network/ServerCommandDispatcher.h" #include "../Minecraft.World/WorldGen/Biomes/BiomeSource.h" #include "Network/PlayerChunkMap.h" #include "Platform/Common/Telemetry/TelemetryManager.h" #define DEBUG_SERVER_DONT_SPAWN_MOBS 0 // 4J Added MinecraftServer* MinecraftServer::server = nullptr; bool MinecraftServer::setTimeAtEndOfTick = false; int64_t MinecraftServer::setTime = 0; bool MinecraftServer::setTimeOfDayAtEndOfTick = false; int64_t MinecraftServer::setTimeOfDay = 0; bool MinecraftServer::m_bPrimaryPlayerSignedOut = false; bool MinecraftServer::s_bServerHalted = false; bool MinecraftServer::s_bSaveOnExitAnswered = false; #if defined(_ACK_CHUNK_SEND_THROTTLING) bool MinecraftServer::s_hasSentEnoughPackets = false; int64_t MinecraftServer::s_tickStartTime = 0; std::vector MinecraftServer::s_sentTo; #else int MinecraftServer::s_slowQueuePlayerIndex = 0; int MinecraftServer::s_slowQueueLastTime = 0; bool MinecraftServer::s_slowQueuePacketSent = false; #endif std::unordered_map MinecraftServer::ironTimers; MinecraftServer::MinecraftServer() { // 4J - added initialisers connection = nullptr; settings = nullptr; players = nullptr; commands = nullptr; running = true; m_bLoaded = false; stopped = false; tickCount = 0; std::wstring progressStatus; progress = 0; motd = L""; m_isServerPaused = false; m_serverPausedEvent = new C4JThread::Event; m_saveOnExit = false; m_suspending = false; m_ugcPlayersVersion = 0; m_texturePackId = 0; maxBuildHeight = Level::maxBuildHeight; playerIdleTimeout = 0; m_postUpdateThread = nullptr; forceGameType = false; commandDispatcher = new ServerCommandDispatcher(); DispenserBootstrap::bootStrap(); } MinecraftServer::~MinecraftServer() {} bool MinecraftServer::initServer(int64_t seed, NetworkGameInitData* initData, std::uint32_t initSettings, bool findSeed) { // 4J - removed settings = new Settings(new File(L"server.properties")); app.DebugPrintf("\n*** SERVER SETTINGS ***\n"); app.DebugPrintf( "ServerSettings: host-friends-only is %s\n", (app.GetGameHostOption(eGameHostOption_FriendsOfFriends) > 0) ? "on" : "off"); app.DebugPrintf("ServerSettings: game-type is %s\n", (app.GetGameHostOption(eGameHostOption_GameType) == 0) ? "Survival Mode" : "Creative Mode"); app.DebugPrintf( "ServerSettings: pvp is %s\n", (app.GetGameHostOption(eGameHostOption_PvP) > 0) ? "on" : "off"); app.DebugPrintf("ServerSettings: fire spreads is %s\n", (app.GetGameHostOption(eGameHostOption_FireSpreads) > 0) ? "on" : "off"); app.DebugPrintf( "ServerSettings: tnt explodes is %s\n", (app.GetGameHostOption(eGameHostOption_TNT) > 0) ? "on" : "off"); app.DebugPrintf("\n"); // TODO 4J Stu - Init a load of settings based on data passed as params // settings->setBooleanAndSave( L"host-friends-only", // (app.GetGameHostOption(eGameHostOption_FriendsOfFriends)>0) ); // 4J - Unused // localIp = settings->getString(L"server-ip", L""); // onlineMode = settings->getBoolean(L"online-mode", true); // motd = settings->getString(L"motd", L"A Minecraft Server"); // motd.replace('ยง', '$'); setAnimals(settings->getBoolean(L"spawn-animals", true)); setNpcsEnabled(settings->getBoolean(L"spawn-npcs", true)); setPvpAllowed(app.GetGameHostOption(eGameHostOption_PvP) > 0 ? true : false); // settings->getBoolean(L"pvp", true); // 4J Stu - We should never have hacked clients flying when they shouldn't // be like the PC version, so enable flying always Fix for #46612 - TU5: // Code: Multiplayer: A client can be banned for flying when accidentaly // being blown by dynamite setFlightAllowed(true); // settings->getBoolean(L"allow-flight", false); // 4J Stu - Enabling flight to stop it kicking us when we use it #if defined(_DEBUG_MENUS_ENABLED) setFlightAllowed(true); #endif connection = new ServerConnection(this); Socket::Initialise(connection); // 4J - added setPlayers(new PlayerList(this)); // 4J-JEV: Need to wait for levelGenerationOptions to load. while (app.getLevelGenerationOptions() != nullptr && !app.getLevelGenerationOptions()->hasLoadedData()) std::this_thread::sleep_for(std::chrono::milliseconds(1)); if (app.getLevelGenerationOptions() != nullptr && !app.getLevelGenerationOptions()->ready()) { // TODO: Stop loading, add error message. } int64_t levelNanoTime = System::nanoTime(); std::wstring levelName = settings->getString(L"level-name", L"world"); std::wstring levelTypeString; bool gameRuleUseFlatWorld = false; if (app.getLevelGenerationOptions() != nullptr) { gameRuleUseFlatWorld = app.getLevelGenerationOptions()->getuseFlatWorld(); } if (gameRuleUseFlatWorld || app.GetGameHostOption(eGameHostOption_LevelType) > 0) { levelTypeString = settings->getString(L"level-type", L"flat"); } else { levelTypeString = settings->getString(L"level-type", L"default"); } LevelType* pLevelType = LevelType::getLevelType(levelTypeString); if (pLevelType == nullptr) { pLevelType = LevelType::lvl_normal; } ProgressRenderer* mcprogress = Minecraft::GetInstance()->progressRenderer; mcprogress->progressStart(IDS_PROGRESS_INITIALISING_SERVER); if (findSeed) { seed = BiomeSource::findSeed(pLevelType); } setMaxBuildHeight( settings->getInt(L"max-build-height", Level::maxBuildHeight)); setMaxBuildHeight(((getMaxBuildHeight() + 8) / 16) * 16); setMaxBuildHeight( Mth::clamp(getMaxBuildHeight(), 64, Level::maxBuildHeight)); // settings->setProperty(L"max-build-height", maxBuildHeight); // logger.info("Preparing level \"" + levelName + "\""); m_bLoaded = loadLevel(new McRegionLevelStorageSource(File(L".")), levelName, seed, pLevelType, initData); // logger.info("Done (" + (System.nanoTime() - levelNanoTime) + "ns)! // For help, type \"help\" or \"?\""); // 4J delete passed in save data now - this is only required for the // tutorial which is loaded by passing data directly in rather than using // the storage manager if (initData->saveData) { delete[] reinterpret_cast(initData->saveData->data); initData->saveData->data = 0; initData->saveData->fileSize = 0; } g_NetworkManager.ServerReady(); // 4J added return m_bLoaded; } // 4J - added - extra thread to post processing on separate thread during level // creation int MinecraftServer::runPostUpdate(void* lpParam) { ShutdownManager::HasStarted(ShutdownManager::ePostProcessThread); MinecraftServer* server = (MinecraftServer*)lpParam; Entity::useSmallIds(); // This thread can end up spawning entities as // resources Compression::UseDefaultThreadStorage(); Level::enableLightingCache(); Tile::CreateNewThreadStorage(); // Update lights for both levels until we are signalled to terminate do { { std::unique_lock lock(server->m_postProcessCS); if (server->m_postProcessRequests.size()) { MinecraftServer::postProcessRequest request = server->m_postProcessRequests.back(); server->m_postProcessRequests.pop_back(); lock.unlock(); static int count = 0; PIXBeginNamedEvent(0, "Post processing %d ", (count++) % 8); request.chunkSource->postProcess(request.chunkSource, request.x, request.z); PIXEndNamedEvent(); } } std::this_thread::sleep_for(std::chrono::milliseconds(1)); } while (!server->m_postUpdateTerminate && ShutdownManager::ShouldRun(ShutdownManager::ePostProcessThread)); // #ifndef 0 // One final pass through updates to make sure we're done { std::unique_lock lock(server->m_postProcessCS); int maxRequests = server->m_postProcessRequests.size(); while (server->m_postProcessRequests.size() && ShutdownManager::ShouldRun(ShutdownManager::ePostProcessThread)) { MinecraftServer::postProcessRequest request = server->m_postProcessRequests.back(); server->m_postProcessRequests.pop_back(); lock.unlock(); request.chunkSource->postProcess(request.chunkSource, request.x, request.z); lock.lock(); } } // #endif //0 Tile::ReleaseThreadStorage(); Level::destroyLightingCache(); ShutdownManager::HasFinished(ShutdownManager::ePostProcessThread); return 0; } void MinecraftServer::addPostProcessRequest(ChunkSource* chunkSource, int x, int z) { { std::lock_guard lock(m_postProcessCS); m_postProcessRequests.push_back( MinecraftServer::postProcessRequest(x, z, chunkSource)); } } void MinecraftServer::postProcessTerminate(ProgressRenderer* mcprogress) { std::uint32_t status = 0; size_t postProcessItemCount = 0; size_t postProcessItemRemaining = 0; { std::lock_guard lock(server->m_postProcessCS); postProcessItemCount = server->m_postProcessRequests.size(); } do { status = m_postUpdateThread->WaitForCompletion(50); if (status == WAIT_TIMEOUT) { { std::lock_guard lock(server->m_postProcessCS); postProcessItemRemaining = server->m_postProcessRequests.size(); } if (postProcessItemCount) { mcprogress->progressStagePercentage( (postProcessItemCount - postProcessItemRemaining) * 100 / postProcessItemCount); } CompressedTileStorage::tick(); SparseLightStorage::tick(); SparseDataStorage::tick(); } } while (status == WAIT_TIMEOUT); delete m_postUpdateThread; m_postUpdateThread = nullptr; } bool MinecraftServer::loadLevel(LevelStorageSource* storageSource, const std::wstring& name, int64_t levelSeed, LevelType* pLevelType, NetworkGameInitData* initData) { // 4J - TODO - do with new save stuff // if (storageSource->requiresConversion(name)) // { // assert(false); // } ProgressRenderer* mcprogress = Minecraft::GetInstance()->progressRenderer; // 4J TODO - free levels here if there are already some? levels = ServerLevelArray(3); int gameTypeId = settings->getInt( L"gamemode", app.GetGameHostOption( eGameHostOption_GameType)); // LevelSettings::GAMETYPE_SURVIVAL); GameType* gameType = LevelSettings::validateGameType(gameTypeId); app.DebugPrintf("Default game type: %d\n", gameTypeId); LevelSettings* levelSettings = new LevelSettings( levelSeed, gameType, app.GetGameHostOption(eGameHostOption_Structures) > 0 ? true : false, isHardcore(), true, pLevelType, initData->xzSize, initData->hellScale); if (app.GetGameHostOption(eGameHostOption_BonusChest)) levelSettings->enableStartingBonusItems(); // 4J - temp - load existing level std::shared_ptr storage = nullptr; bool levelChunksNeedConverted = false; if (initData->saveData != nullptr) { // We are loading a file from disk with the data passed in #if defined(SPLIT_SAVES) ConsoleSaveFileOriginal oldFormatSave( initData->saveData->saveName, initData->saveData->data, initData->saveData->fileSize, false, initData->savePlatform); ConsoleSaveFile* pSave = new ConsoleSaveFileSplit(&oldFormatSave); // ConsoleSaveFile* pSave = new ConsoleSaveFileSplit( // initData->saveData->saveName, initData->saveData->data, // initData->saveData->fileSize, false, initData->savePlatform ); #else ConsoleSaveFile* pSave = new ConsoleSaveFileOriginal( initData->saveData->saveName, initData->saveData->data, initData->saveData->fileSize, false, initData->savePlatform); #endif if (pSave->isSaveEndianDifferent()) levelChunksNeedConverted = true; pSave->ConvertToLocalPlatform(); // check if we need to convert this // file from PS3->PS4 storage = std::shared_ptr( new McRegionLevelStorage(pSave, File(L"."), name, true)); } else { // We are loading a save from the storage manager #if defined(SPLIT_SAVES) bool bLevelGenBaseSave = false; LevelGenerationOptions* levelGen = app.getLevelGenerationOptions(); if (levelGen != nullptr && levelGen->requiresBaseSave()) { unsigned int fileSize = 0; std::uint8_t* pvSaveData = levelGen->getBaseSaveData(fileSize); if (pvSaveData && fileSize != 0) bLevelGenBaseSave = true; } ConsoleSaveFileSplit* newFormatSave = nullptr; if (bLevelGenBaseSave) { ConsoleSaveFileOriginal oldFormatSave(L""); newFormatSave = new ConsoleSaveFileSplit(&oldFormatSave); } else { newFormatSave = new ConsoleSaveFileSplit(L""); } storage = std::shared_ptr( new McRegionLevelStorage(newFormatSave, File(L"."), name, true)); #else storage = std::shared_ptr(new McRegionLevelStorage( new ConsoleSaveFileOriginal(L""), File(L"."), name, true)); #endif } // McRegionLevelStorage *storage = new McRegionLevelStorage(new // ConsoleSaveFile( L"" ), L"", L"", 0); // original // McRegionLevelStorage *storage = new McRegionLevelStorage(File(L"."), // name, true); // TODO for (unsigned int i = 0; i < levels.length; i++) { if (s_bServerHalted || !g_NetworkManager.IsInSession()) { return false; } // String levelName = name; // if (i == 1) levelName += "_nether"; int dimension = 0; if (i == 1) dimension = -1; if (i == 2) dimension = 1; if (i == 0) { levels[i] = new ServerLevel(this, storage, name, dimension, levelSettings); if (app.getLevelGenerationOptions() != nullptr) { LevelGenerationOptions* mapOptions = app.getLevelGenerationOptions(); Pos* spawnPos = mapOptions->getSpawnPos(); if (spawnPos != nullptr) { levels[i]->setSpawnPos(spawnPos); } levels[i]->getLevelData()->setHasBeenInCreative( mapOptions->isFromDLC()); } } else levels[i] = new DerivedServerLevel(this, storage, name, dimension, levelSettings, levels[0]); // levels[i]->addListener(new ServerLevelListener(this, // levels[i])); // 4J - have moved this to the // ServerLevel ctor so that it is set up in time for the first // chunk to load, which might actually happen there // 4J Stu - We set the levels difficulty based on the minecraft options // levels[i]->difficulty = settings->getBoolean(L"spawn-monsters", true) // ? Difficulty::EASY : Difficulty::PEACEFUL; Minecraft* pMinecraft = Minecraft::GetInstance(); // m_lastSentDifficulty = pMinecraft->options->difficulty; levels[i]->difficulty = app.GetGameHostOption( eGameHostOption_Difficulty); // pMinecraft->options->difficulty; app.DebugPrintf("MinecraftServer::loadLevel - Difficulty = %d\n", levels[i]->difficulty); #if DEBUG_SERVER_DONT_SPAWN_MOBS levels[i]->setSpawnSettings(false, false); #else levels[i]->setSpawnSettings( settings->getBoolean(L"spawn-monsters", true), animals); #endif levels[i]->getLevelData()->setGameType(gameType); if (app.getLevelGenerationOptions() != nullptr) { LevelGenerationOptions* mapOptions = app.getLevelGenerationOptions(); levels[i]->getLevelData()->setHasBeenInCreative( mapOptions->getLevelHasBeenInCreative()); } players->setLevel(levels); } if (levels[0]->isNew) { mcprogress->progressStage(IDS_PROGRESS_GENERATING_SPAWN_AREA); } else { mcprogress->progressStage(IDS_PROGRESS_LOADING_SPAWN_AREA); } app.SetGameHostOption( eGameHostOption_HasBeenInCreative, gameType == GameType::CREATIVE || levels[0]->getHasBeenInCreative()); app.SetGameHostOption(eGameHostOption_Structures, levels[0]->isGenerateMapFeatures()); if (s_bServerHalted || !g_NetworkManager.IsInSession()) return false; // 4J - Make a new thread to do post processing // 4J-PB - fix for 108310 - TCR #001 BAS Game Stability: TU12: Code: // Compliance: Crash after creating world on "journey" seed. Stack gets very // deep with some sand tower falling, so increased the stacj to 256K from // 128k on other platforms (was already set to that on PS3 and Orbis) m_postUpdateThread = new C4JThread(runPostUpdate, this, "Post processing", 256 * 1024); m_postUpdateTerminate = false; m_postUpdateThread->SetProcessor(CPU_CORE_POST_PROCESSING); m_postUpdateThread->SetPriority(THREAD_PRIORITY_ABOVE_NORMAL); m_postUpdateThread->Run(); int64_t startTime = System::currentTimeMillis(); // 4J Stu - Added this to temporarily make starting games on vita faster int r = 196; // 4J JEV: load gameRules. ConsoleSavePath filepath(GAME_RULE_SAVENAME); ConsoleSaveFile* csf = getLevel(0)->getLevelStorage()->getSaveFile(); if (csf->doesFileExist(filepath)) { unsigned int numberOfBytesRead; byteArray ba_gameRules; FileEntry* fe = csf->createFile(filepath); ba_gameRules.length = fe->getFileSize(); ba_gameRules.data = new std::uint8_t[ba_gameRules.length]; csf->setFilePointer(fe, 0, SaveFileSeekOrigin::Begin); csf->readFile(fe, ba_gameRules.data, ba_gameRules.length, &numberOfBytesRead); assert(numberOfBytesRead == ba_gameRules.length); app.m_gameRules.loadGameRules(ba_gameRules.data, ba_gameRules.length); csf->closeHandle(fe); } int64_t lastTime = System::currentTimeMillis(); #if defined(_LARGE_WORLDS) if (app.GetGameNewWorldSize() > levels[0]->getLevelData()->getXZSizeOld()) { if (!app.GetGameNewWorldSizeUseMoat()) // check the moat settings to // see if we should be // overwriting the edge tiles { overwriteBordersForNewWorldSize(levels[0]); } // we're always overwriting hell edges int oldHellSize = levels[0]->getLevelData()->getXZHellSizeOld(); overwriteHellBordersForNewWorldSize(levels[1], oldHellSize); } #endif // 4J Stu - This loop is changed in 1.0.1 to only process the first level // (ie the overworld), but I think we still want to do them all int i = 0; for (int i = 0; i < levels.length; i++) { // logger.info("Preparing start region for level " + i); if (i == 0 || settings->getBoolean(L"allow-nether", true)) { ServerLevel* level = levels[i]; if (levelChunksNeedConverted) { // storage->getSaveFile()->convertLevelChunks(level) } int64_t lastStorageTickTime = System::currentTimeMillis(); Pos* spawnPos = level->getSharedSpawnPos(); int twoRPlusOne = r * 2 + 1; int total = twoRPlusOne * twoRPlusOne; for (int x = -r; x <= r && running; x += 16) { for (int z = -r; z <= r && running; z += 16) { if (s_bServerHalted || !g_NetworkManager.IsInSession()) { delete spawnPos; m_postUpdateTerminate = true; postProcessTerminate(mcprogress); return false; } // printf(">>>%d %d //%d\n",i,x,z); // int64_t now = // System::currentTimeMillis(); if (now < // lastTime) lastTime = now; if (now > // lastTime + 1000) { int pos = (x + r) * twoRPlusOne + (z + 1); // setProgress(L"Preparing spawn // area", (pos) * 100 / total); mcprogress->progressStagePercentage((pos + r) * 100 / total); // lastTime = now; } static int count = 0; PIXBeginNamedEvent(0, "Creating %d ", (count++) % 8); level->cache->create((spawnPos->x + x) >> 4, (spawnPos->z + z) >> 4, true); // 4J - added parameter to // disable postprocessing here PIXEndNamedEvent(); // while (level->updateLights() && // running) // ; if (System::currentTimeMillis() - lastStorageTickTime > 50) { CompressedTileStorage::tick(); SparseLightStorage::tick(); SparseDataStorage::tick(); lastStorageTickTime = System::currentTimeMillis(); } } } // 4J - removed this as now doing the recheckGaps call when each // chunk is post-processed, so can happen on things outside of the // spawn area too delete spawnPos; } } // printf("Main thread complete at %dms\n",System::currentTimeMillis() - // startTime); // Wait for post processing, then lighting threads, to end (post-processing // may make more lighting changes) m_postUpdateTerminate = true; postProcessTerminate(mcprogress); // stronghold position? if (levels[0]->dimension->id == 0) { app.DebugPrintf("===================================\n"); if (!levels[0]->getLevelData()->getHasStronghold()) { int x, z; if (app.GetTerrainFeaturePosition(eTerrainFeature_Stronghold, &x, &z)) { levels[0]->getLevelData()->setXStronghold(x); levels[0]->getLevelData()->setZStronghold(z); levels[0]->getLevelData()->setHasStronghold(); app.DebugPrintf( "=== FOUND stronghold in terrain features list\n"); } else { // can't find the stronghold position in the terrain feature // list. Do we have to run a post-process? app.DebugPrintf( "=== Can't find stronghold in terrain features list\n"); } } else { app.DebugPrintf("=== Leveldata has stronghold position\n"); } app.DebugPrintf("===================================\n"); } // printf("Post processing complete at %dms\n",System::currentTimeMillis() //- startTime); // printf("Lighting complete at %dms\n",System::currentTimeMillis() - // startTime); if (s_bServerHalted || !g_NetworkManager.IsInSession()) return false; if (levels[1]->isNew) { levels[1]->save(true, mcprogress); } if (s_bServerHalted || !g_NetworkManager.IsInSession()) return false; if (levels[2]->isNew) { levels[2]->save(true, mcprogress); } if (s_bServerHalted || !g_NetworkManager.IsInSession()) return false; // 4J - added - immediately save newly created level, like single player // game 4J Stu - We also want to immediately save the tutorial if (levels[0]->isNew) saveGameRules(); if (levels[0]->isNew) { levels[0]->save(true, mcprogress); } if (s_bServerHalted || !g_NetworkManager.IsInSession()) return false; if (levels[0]->isNew || levels[1]->isNew || levels[2]->isNew) { levels[0]->saveToDisc(mcprogress, false); } if (s_bServerHalted || !g_NetworkManager.IsInSession()) return false; /* * int r = 24; for (int x = -r; x <= r; x++) { * setProgress("Preparing spawn area", (x + r) * 100 / (r + r + 1)); for * (int z = -r; z <= r; z++) { if (!running) return; * level.cache.create((level.xSpawn * >> 4) + x, (level.zSpawn >> 4) + z); while (running && * level.updateLights()) ; } } */ endProgress(); return true; } #if defined(_LARGE_WORLDS) void MinecraftServer::overwriteBordersForNewWorldSize(ServerLevel* level) { // recreate the chunks round the border (2 chunks or 32 blocks deep), // deleting any player data from them app.DebugPrintf("Expanding level size\n"); int oldSize = level->getLevelData()->getXZSizeOld(); // top int minVal = -oldSize / 2; int maxVal = (oldSize / 2) - 1; for (int xVal = minVal; xVal <= maxVal; xVal++) { int zVal = minVal; level->cache->overwriteLevelChunkFromSource(xVal, zVal); level->cache->overwriteLevelChunkFromSource(xVal, zVal + 1); } // bottom for (int xVal = minVal; xVal <= maxVal; xVal++) { int zVal = maxVal; level->cache->overwriteLevelChunkFromSource(xVal, zVal); level->cache->overwriteLevelChunkFromSource(xVal, zVal - 1); } // left for (int zVal = minVal; zVal <= maxVal; zVal++) { int xVal = minVal; level->cache->overwriteLevelChunkFromSource(xVal, zVal); level->cache->overwriteLevelChunkFromSource(xVal + 1, zVal); } // right for (int zVal = minVal; zVal <= maxVal; zVal++) { int xVal = maxVal; level->cache->overwriteLevelChunkFromSource(xVal, zVal); level->cache->overwriteLevelChunkFromSource(xVal - 1, zVal); } } void MinecraftServer::overwriteHellBordersForNewWorldSize(ServerLevel* level, int oldHellSize) { // recreate the chunks round the border (1 chunk or 16 blocks deep), // deleting any player data from them app.DebugPrintf("Expanding level size\n"); // top int minVal = -oldHellSize / 2; int maxVal = (oldHellSize / 2) - 1; for (int xVal = minVal; xVal <= maxVal; xVal++) { int zVal = minVal; level->cache->overwriteHellLevelChunkFromSource(xVal, zVal, minVal, maxVal); } // bottom for (int xVal = minVal; xVal <= maxVal; xVal++) { int zVal = maxVal; level->cache->overwriteHellLevelChunkFromSource(xVal, zVal, minVal, maxVal); } // left for (int zVal = minVal; zVal <= maxVal; zVal++) { int xVal = minVal; level->cache->overwriteHellLevelChunkFromSource(xVal, zVal, minVal, maxVal); } // right for (int zVal = minVal; zVal <= maxVal; zVal++) { int xVal = maxVal; level->cache->overwriteHellLevelChunkFromSource(xVal, zVal, minVal, maxVal); } } #endif void MinecraftServer::setProgress(const std::wstring& status, int progress) { progressStatus = status; this->progress = progress; // logger.info(status + ": " + progress + "%"); } void MinecraftServer::endProgress() { progressStatus = L""; this->progress = 0; } void MinecraftServer::saveAllChunks() { // logger.info("Saving chunks"); for (unsigned int i = 0; i < levels.length; i++) { // 4J Stu - Due to the way save mounting is handled on XboxOne, we can // actually save after the player has signed out. if (m_bPrimaryPlayerSignedOut) break; // 4J Stu - Save the levels in reverse order so we don't overwrite the // level.dat with the data from the nethers leveldata. Fix for #7418 - // Functional: Gameplay: Saving after sleeping in a bed will place // player at nighttime when restarting. ServerLevel* level = levels[levels.length - 1 - i]; if (level) // 4J - added check as level can be nullptr if we end up in // stopServer really early on due to network failure { level->save(true, Minecraft::GetInstance()->progressRenderer); // Only close the level storage when we have saved the last level, // otherwise we need to recreate the region files when saving the // next levels if (i == (levels.length - 1)) { level->closeLevelStorage(); } } } } // 4J-JEV: Added void MinecraftServer::saveGameRules() { #if !defined(_CONTENT_PACKAGE) if (app.DebugSettingsOn() && app.GetGameSettingsDebugMask(ProfileManager.GetPrimaryPad()) & (1L << eDebugSetting_DistributableSave)) { // Do nothing } else #endif { byteArray ba; ba.data = nullptr; app.m_gameRules.saveGameRules(&ba.data, &ba.length); if (ba.data != nullptr) { ConsoleSaveFile* csf = getLevel(0)->getLevelStorage()->getSaveFile(); FileEntry* fe = csf->createFile(ConsoleSavePath(GAME_RULE_SAVENAME)); csf->setFilePointer(fe, 0, SaveFileSeekOrigin::Begin); unsigned int length; csf->writeFile(fe, ba.data, ba.length, &length); delete[] ba.data; csf->closeHandle(fe); } } } void MinecraftServer::Suspend() { PIXBeginNamedEvent(0, "Suspending server"); m_suspending = true; // Get the frequency of the timer LARGE_INTEGER qwTicksPerSec, qwTime, qwNewTime, qwDeltaTime; float fElapsedTime = 0.0f; QueryPerformanceFrequency(&qwTicksPerSec); float fSecsPerTick = 1.0f / (float)qwTicksPerSec.QuadPart; // Save the start time QueryPerformanceCounter(&qwTime); if (m_bLoaded && ProfileManager.IsFullVersion() && (!StorageManager.GetSaveDisabled())) { if (players != nullptr) { players->saveAll(nullptr); } for (unsigned int j = 0; j < levels.length; j++) { if (s_bServerHalted) break; // 4J Stu - Save the levels in reverse order so we don't overwrite // the level.dat with the data from the nethers leveldata. Fix for // #7418 - Functional: Gameplay: Saving after sleeping in a bed will // place player at nighttime when restarting. ServerLevel* level = levels[levels.length - 1 - j]; level->Suspend(); } if (!s_bServerHalted) { saveGameRules(); levels[0]->saveToDisc(nullptr, true); } } QueryPerformanceCounter(&qwNewTime); qwDeltaTime.QuadPart = qwNewTime.QuadPart - qwTime.QuadPart; fElapsedTime = fSecsPerTick * static_cast(qwDeltaTime.QuadPart); // 4J-JEV: Flush stats and call PlayerSessionExit. for (int iPad = 0; iPad < XUSER_MAX_COUNT; iPad++) { if (ProfileManager.IsSignedIn(iPad)) { TelemetryManager->RecordPlayerSessionExit( iPad, DisconnectPacket::eDisconnect_Quitting); } } m_suspending = false; app.DebugPrintf("Suspend server: Elapsed time %f\n", fElapsedTime); PIXEndNamedEvent(); } bool MinecraftServer::IsSuspending() { return m_suspending; } void MinecraftServer::stopServer(bool didInit) { // 4J-PB - need to halt the rendering of the data, since we're about to // remove it { Minecraft::GetInstance()->gameRenderer->DisableUpdateThread(); } connection->stop(); app.DebugPrintf("Stopping server\n"); // logger.info("Stopping server"); // 4J-PB - If the primary player has signed out, then don't attempt to save // anything // also need to check for a profile switch here - primary player signs out, // and another player signs in before dismissing the dash if ((m_bPrimaryPlayerSignedOut == false) && ProfileManager.IsSignedIn(ProfileManager.GetPrimaryPad())) { // if trial version or saving is disabled, then don't save anything. // Also don't save anything if we didn't actually get through the server // initialisation. if (m_saveOnExit && ProfileManager.IsFullVersion() && (!StorageManager.GetSaveDisabled()) && didInit) { if (players != nullptr) { players->saveAll(Minecraft::GetInstance()->progressRenderer, true); } // 4J Stu - Save the levels in reverse order so we don't overwrite // the level.dat with the data from the nethers leveldata. Fix for // #7418 - Functional: Gameplay: Saving after sleeping in a bed will // place player at nighttime when restarting. // for (unsigned int i = levels.length - 1; i >= 0; i--) //{ // ServerLevel *level = levels[i]; // if (level != nullptr) // { saveAllChunks(); // } //} saveGameRules(); app.m_gameRules.unloadCurrentGameRules(); if (levels[0] != nullptr) // This can be null if stopServer happens // very quickly due to network error { levels[0]->saveToDisc( Minecraft::GetInstance()->progressRenderer, false); } } } // reset the primary player signout flag m_bPrimaryPlayerSignedOut = false; s_bServerHalted = false; // On Durango/Orbis, we need to wait for all the asynchronous saving // processes to complete before destroying the levels, as that will // ultimately delete the directory level storage & therefore the // ConsoleSaveSplit instance, which needs to be around until all the sub // files have completed saving. // 4J-PB remove the server levels unsigned int iServerLevelC = levels.length; for (unsigned int i = 0; i < iServerLevelC; i++) { if (levels[i] != nullptr) { delete levels[i]; levels[i] = nullptr; } } delete connection; connection = nullptr; delete players; players = nullptr; delete settings; settings = nullptr; g_NetworkManager.ServerStopped(); } void MinecraftServer::halt() { running = false; } void MinecraftServer::setMaxBuildHeight(int maxBuildHeight) { this->maxBuildHeight = maxBuildHeight; } int MinecraftServer::getMaxBuildHeight() { return maxBuildHeight; } PlayerList* MinecraftServer::getPlayers() { return players; } void MinecraftServer::setPlayers(PlayerList* players) { this->players = players; } ServerConnection* MinecraftServer::getConnection() { return connection; } bool MinecraftServer::isAnimals() { return animals; } void MinecraftServer::setAnimals(bool animals) { this->animals = animals; } bool MinecraftServer::isNpcsEnabled() { return npcs; } void MinecraftServer::setNpcsEnabled(bool npcs) { this->npcs = npcs; } bool MinecraftServer::isPvpAllowed() { return pvp; } void MinecraftServer::setPvpAllowed(bool pvp) { this->pvp = pvp; } bool MinecraftServer::isFlightAllowed() { return allowFlight; } void MinecraftServer::setFlightAllowed(bool allowFlight) { this->allowFlight = allowFlight; } bool MinecraftServer::isCommandBlockEnabled() { return false; // settings.getBoolean("enable-command-block", false); } bool MinecraftServer::isNetherEnabled() { return true; // settings.getBoolean("allow-nether", true); } bool MinecraftServer::isHardcore() { return false; } int MinecraftServer::getOperatorUserPermissionLevel() { return Command::LEVEL_OWNERS; // settings.getInt("op-permission-level", // Command.LEVEL_OWNERS); } CommandDispatcher* MinecraftServer::getCommandDispatcher() { return commandDispatcher; } Pos* MinecraftServer::getCommandSenderWorldPosition() { return new Pos(0, 0, 0); } Level* MinecraftServer::getCommandSenderWorld() { return levels[0]; } int MinecraftServer::getSpawnProtectionRadius() { return 16; } bool MinecraftServer::isUnderSpawnProtection(Level* level, int x, int y, int z, std::shared_ptr player) { if (level->dimension->id != 0) return false; // if (getPlayers()->getOps()->empty()) return false; if (getPlayers()->isOp(player->getName())) return false; if (getSpawnProtectionRadius() <= 0) return false; Pos* spawnPos = level->getSharedSpawnPos(); int xd = Mth::abs(x - spawnPos->x); int zd = Mth::abs(z - spawnPos->z); int dist = std::max(xd, zd); return dist <= getSpawnProtectionRadius(); } void MinecraftServer::setForceGameType(bool forceGameType) { this->forceGameType = forceGameType; } bool MinecraftServer::getForceGameType() { return forceGameType; } int64_t MinecraftServer::getCurrentTimeMillis() { return System::currentTimeMillis(); } int MinecraftServer::getPlayerIdleTimeout() { return playerIdleTimeout; } void MinecraftServer::setPlayerIdleTimeout(int playerIdleTimeout) { this->playerIdleTimeout = playerIdleTimeout; } extern int c0a, c0b, c1a, c1b, c1c, c2a, c2b; void MinecraftServer::run(int64_t seed, void* lpParameter) { NetworkGameInitData* initData = nullptr; std::uint32_t initSettings = 0; bool findSeed = false; if (lpParameter != nullptr) { initData = (NetworkGameInitData*)lpParameter; initSettings = app.GetGameHostOption(eGameHostOption_All); findSeed = initData->findSeed; m_texturePackId = initData->texturePackId; } // try { // 4J - removed try/catch/finally bool didInit = false; if (initServer(seed, initData, initSettings, findSeed)) { didInit = true; ServerLevel* levelNormalDimension = levels[0]; // 4J-PB - Set the Stronghold position in the leveldata if there isn't // one in there Minecraft* pMinecraft = Minecraft::GetInstance(); LevelData* pLevelData = levelNormalDimension->getLevelData(); if (pLevelData && pLevelData->getHasStronghold() == false) { int x, z; if (app.GetTerrainFeaturePosition(eTerrainFeature_Stronghold, &x, &z)) { pLevelData->setXStronghold(x); pLevelData->setZStronghold(z); pLevelData->setHasStronghold(); } } int64_t lastTime = getCurrentTimeMillis(); int64_t unprocessedTime = 0; while (running && !s_bServerHalted) { int64_t now = getCurrentTimeMillis(); // 4J Stu - When we pause the server, we don't want to count that as // time passed 4J Stu - TU-1 hotifx - Remove this line. We want to // make sure that we tick connections at the proper rate when paused // Fix for #13191 - The host of a game can get a message informing // them that the connection to the server has been lost // if(m_isServerPaused) lastTime = now; int64_t passedTime = now - lastTime; if (passedTime > MS_PER_TICK * 40) { // logger.warning("Can't keep up! Did the system // time change, or is the server overloaded?"); passedTime = MS_PER_TICK * 40; } if (passedTime < 0) { // logger.warning("Time ran backwards! Did the // system time change?"); passedTime = 0; } unprocessedTime += passedTime; lastTime = now; // 4J Added ability to pause the server if (!m_isServerPaused) { bool didTick = false; if (levels[0]->allPlayersAreSleeping()) { tick(); unprocessedTime = 0; } else { // int tickcount = 0; // int64_t beforeall = // System::currentTimeMillis(); while (unprocessedTime > MS_PER_TICK) { unprocessedTime -= MS_PER_TICK; chunkPacketManagement_PreTick(); // int64_t // before = System::currentTimeMillis(); tick(); // int64_t // after = System::currentTimeMillis(); // PIXReportCounter(L"Server // time",(float)(after-before)); chunkPacketManagement_PostTick(); } // int64_t afterall = // System::currentTimeMillis(); // PIXReportCounter(L"Server time // all",(float)(afterall-beforeall)); // PIXReportCounter(L"Server // ticks",(float)tickcount); } } else { // 4J Stu - TU1-hotfix // Fix for #13191 - The host of a game can get a message // informing them that the connection to the server has been // lost // The connections should tick at the same frequency even when // paused while (unprocessedTime > MS_PER_TICK) { unprocessedTime -= MS_PER_TICK; // Keep ticking the connections to stop them timing out connection->tick(); } } if (MinecraftServer::setTimeAtEndOfTick) { MinecraftServer::setTimeAtEndOfTick = false; for (unsigned int i = 0; i < levels.length; i++) { // if (i == 0 || // settings->getBoolean(L"allow-nether", true)) //// 4J removed - we always have nether { ServerLevel* level = levels[i]; level->setGameTime(MinecraftServer::setTime); } } } if (MinecraftServer::setTimeOfDayAtEndOfTick) { MinecraftServer::setTimeOfDayAtEndOfTick = false; for (unsigned int i = 0; i < levels.length; i++) { if (i == 0 || settings->getBoolean(L"allow-nether", true)) { ServerLevel* level = levels[i]; level->setDayTime(MinecraftServer::setTimeOfDay); } } } // Process delayed actions eXuiServerAction eAction; void* param; for (int i = 0; i < XUSER_MAX_COUNT; i++) { eAction = app.GetXuiServerAction(i); param = app.GetXuiServerActionParam(i); switch (eAction) { case eXuiServerAction_AutoSaveGame: case eXuiServerAction_SaveGame: app.EnterSaveNotificationSection(); if (players != nullptr) { players->saveAll( Minecraft::GetInstance()->progressRenderer); } players->broadcastAll( std::shared_ptr( new UpdateProgressPacket(20))); for (unsigned int j = 0; j < levels.length; j++) { if (s_bServerHalted) break; // 4J Stu - Save the levels in reverse order so we // don't overwrite the level.dat with the data from // the nethers leveldata. Fix for #7418 - // Functional: Gameplay: Saving after sleeping in a // bed will place player at nighttime when // restarting. ServerLevel* level = levels[levels.length - 1 - j]; level->save( true, Minecraft::GetInstance()->progressRenderer, (eAction == eXuiServerAction_AutoSaveGame)); players->broadcastAll( std::shared_ptr( new UpdateProgressPacket(33 + (j * 33)))); } if (!s_bServerHalted) { saveGameRules(); levels[0]->saveToDisc( Minecraft::GetInstance()->progressRenderer, (eAction == eXuiServerAction_AutoSaveGame)); } app.LeaveSaveNotificationSection(); break; case eXuiServerAction_DropItem: // Find the player, and drop the id at their feet { std::shared_ptr player = players->players.at(0); size_t id = (size_t)param; player->drop(std::shared_ptr( new ItemInstance(id, 1, 0))); } break; case eXuiServerAction_SpawnMob: { std::shared_ptr player = players->players.at(0); eINSTANCEOF factory = (eINSTANCEOF)((size_t)param); std::shared_ptr mob = std::dynamic_pointer_cast( EntityIO::newByEnumType(factory, player->level)); mob->moveTo(player->x + 1, player->y, player->z + 1, player->level->random->nextFloat() * 360, 0); mob->setDespawnProtected(); // 4J added, default to // being protected against // despawning (has to be // done after initial // position is set) player->level->addEntity(mob); } break; case eXuiServerAction_PauseServer: m_isServerPaused = ((size_t)param == true); if (m_isServerPaused) { m_serverPausedEvent->Set(); } break; case eXuiServerAction_ToggleRain: { bool isRaining = levels[0]->getLevelData()->isRaining(); levels[0]->getLevelData()->setRaining(!isRaining); levels[0]->getLevelData()->setRainTime( levels[0]->random->nextInt(Level::TICKS_PER_DAY * 7) + Level::TICKS_PER_DAY / 2); } break; case eXuiServerAction_ToggleThunder: { bool isThundering = levels[0]->getLevelData()->isThundering(); levels[0]->getLevelData()->setThundering(!isThundering); levels[0]->getLevelData()->setThunderTime( levels[0]->random->nextInt(Level::TICKS_PER_DAY * 7) + Level::TICKS_PER_DAY / 2); } break; case eXuiServerAction_ServerSettingChanged_Gamertags: players->broadcastAll( std::shared_ptr( new ServerSettingsChangedPacket( ServerSettingsChangedPacket::HOST_OPTIONS, app.GetGameHostOption( eGameHostOption_Gamertags)))); break; case eXuiServerAction_ServerSettingChanged_BedrockFog: players->broadcastAll( std::shared_ptr( new ServerSettingsChangedPacket( ServerSettingsChangedPacket:: HOST_IN_GAME_SETTINGS, app.GetGameHostOption( eGameHostOption_All)))); break; case eXuiServerAction_ServerSettingChanged_Difficulty: players->broadcastAll(std::shared_ptr< ServerSettingsChangedPacket>( new ServerSettingsChangedPacket( ServerSettingsChangedPacket::HOST_DIFFICULTY, Minecraft::GetInstance() ->options->difficulty))); break; case eXuiServerAction_ExportSchematic: #if !defined(_CONTENT_PACKAGE) app.EnterSaveNotificationSection(); // players->broadcastAll( // shared_ptr( new // UpdateProgressPacket(20) ) ); if (!s_bServerHalted) { ConsoleSchematicFile::XboxSchematicInitParam* initData = (ConsoleSchematicFile:: XboxSchematicInitParam*)param; File targetFileDir(L"Schematics"); if (!targetFileDir.exists()) targetFileDir.mkdir(); wchar_t filename[128]; swprintf(filename, 128, L"%ls%dx%dx%d.sch", initData->name, (initData->endX - initData->startX + 1), (initData->endY - initData->startY + 1), (initData->endZ - initData->startZ + 1)); File dataFile = File(targetFileDir, std::wstring(filename)); if (dataFile.exists()) dataFile._delete(); FileOutputStream fos = FileOutputStream(dataFile); DataOutputStream dos = DataOutputStream(&fos); ConsoleSchematicFile::generateSchematicFile( &dos, levels[0], initData->startX, initData->startY, initData->startZ, initData->endX, initData->endY, initData->endZ, initData->bSaveMobs, initData->compressionType); dos.close(); delete initData; } app.LeaveSaveNotificationSection(); #endif break; case eXuiServerAction_SetCameraLocation: #if !defined(_CONTENT_PACKAGE) { DebugSetCameraPosition* pos = (DebugSetCameraPosition*)param; app.DebugPrintf("DEBUG: Player=%i\n", pos->player); app.DebugPrintf( "DEBUG: Teleporting to pos=(%f.2, %f.2, %f.2), " "looking at=(%f.2,%f.2)\n", pos->m_camX, pos->m_camY, pos->m_camZ, pos->m_yRot, pos->m_elev); std::shared_ptr player = players->players.at(pos->player); player->debug_setPosition(pos->m_camX, pos->m_camY, pos->m_camZ, pos->m_yRot, pos->m_elev); // Doesn't work // player->setYHeadRot(pos->m_yRot); // player->absMoveTo(pos->m_camX, pos->m_camY, // pos->m_camZ, pos->m_yRot, pos->m_elev); } #endif break; default: break; } app.SetXuiServerAction(i, eXuiServerAction_Idle); } std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } // else //{ // while (running) // { // handleConsoleInputs(); // std::this_thread::sleep_for(std::chrono::milliseconds(10)); // } // } // 4J Stu - Stop the server when the loops complete, as the finally would do stopServer(didInit); stopped = true; } void MinecraftServer::broadcastStartSavingPacket() { players->broadcastAll(std::shared_ptr( new GameEventPacket(GameEventPacket::START_SAVING, 0))); ; } void MinecraftServer::broadcastStopSavingPacket() { if (!s_bServerHalted) { players->broadcastAll(std::shared_ptr( new GameEventPacket(GameEventPacket::STOP_SAVING, 0))); ; } } void MinecraftServer::tick() { std::vector toRemove; for (auto it = ironTimers.begin(); it != ironTimers.end(); it++) { int t = it->second; if (t > 0) { ironTimers[it->first] = t - 1; } else { toRemove.push_back(it->first); } } for (unsigned int i = 0; i < toRemove.size(); i++) { ironTimers.erase(toRemove[i]); } tickCount++; // 4J We need to update client difficulty levels based on the servers Minecraft* pMinecraft = Minecraft::GetInstance(); // 4J-PB - sending this on the host changing the difficulty in the menus /* if(m_lastSentDifficulty != pMinecraft->options->difficulty) { m_lastSentDifficulty = pMinecraft->options->difficulty; players->broadcastAll( shared_ptr( new ServerSettingsChangedPacket( ServerSettingsChangedPacket::HOST_DIFFICULTY, pMinecraft->options->difficulty) ) ); }*/ for (unsigned int i = 0; i < levels.length; i++) { // if (i == 0 || settings->getBoolean(L"allow-nether", true)) // // 4J removed - we always have nether { ServerLevel* level = levels[i]; // 4J Stu - We set the levels difficulty based on the minecraft // options level->difficulty = app.GetGameHostOption( eGameHostOption_Difficulty); // pMinecraft->options->difficulty; #if DEBUG_SERVER_DONT_SPAWN_MOBS level->setSpawnSettings(false, false); #else level->setSpawnSettings(level->difficulty > 0 && !Minecraft::GetInstance()->isTutorial(), animals); #endif if (tickCount % 20 == 0) { players->broadcastAll( std::shared_ptr(new SetTimePacket( level->getGameTime(), level->getDayTime(), level->getGameRules()->getBoolean( GameRules::RULE_DAYLIGHT))), level->dimension->id); } // #ifndef 0 static int64_t stc = 0; int64_t st0 = System::currentTimeMillis(); PIXBeginNamedEvent(0, "Level tick %d", i); ((Level*)level)->tick(); int64_t st1 = System::currentTimeMillis(); PIXEndNamedEvent(); PIXBeginNamedEvent(0, "Update lights %d", i); int64_t st2 = System::currentTimeMillis(); PIXEndNamedEvent(); PIXBeginNamedEvent(0, "Entity tick %d", i); // 4J added to stop ticking entities in levels when players are not // in those levels. Note: now changed so that we also tick if there // are entities to be removed, as this also happens as a result of // calling tickEntities. If we don't do this, then the entities get // removed at the first point that there is a player count in the // level - this has been causing a problem when going from normal // dimension -> nether -> normal, as the player is getting flagged // as to be removed (from the normal dimension) when going to the // nether, but Actually gets removed only when it returns if ((players->getPlayerCount(level) > 0) || (level->hasEntitiesToRemove())) { level->tickEntities(); } PIXEndNamedEvent(); PIXBeginNamedEvent(0, "Entity tracker tick"); level->getTracker()->tick(); PIXEndNamedEvent(); int64_t st3 = System::currentTimeMillis(); // printf(">>>>>>>>>>>>>>>>>>>>>> Tick %d %d %d : //%d\n", st1 - st0, st2 - st1, st3 - st2, st0 - stc ); stc = st0; // #endif// 0 } } Entity::tickExtraWandering(); // 4J added PIXBeginNamedEvent(0, "Connection tick"); connection->tick(); PIXEndNamedEvent(); PIXBeginNamedEvent(0, "Players tick"); players->tick(); PIXEndNamedEvent(); // 4J - removed // try { // 4J - removed try/catch handleConsoleInputs(); // } catch (Exception e) { // logger.log(Level.WARNING, "Unexpected exception while parsing // console command", e); // } } void MinecraftServer::handleConsoleInput(const std::wstring& msg, ConsoleInputSource* source) { consoleInput.push_back(new ConsoleInput(msg, source)); } void MinecraftServer::handleConsoleInputs() { while (consoleInput.size() > 0) { auto it = consoleInput.begin(); ConsoleInput* input = *it; consoleInput.erase(it); // commands->handleCommand(input); // 4J - removed // - TODO - do we want equivalent of console commands? } } void MinecraftServer::main(int64_t seed, void* lpParameter) { ShutdownManager::HasStarted(ShutdownManager::eServerThread); server = new MinecraftServer(); server->run(seed, lpParameter); delete server; server = nullptr; ShutdownManager::HasFinished(ShutdownManager::eServerThread); } void MinecraftServer::HaltServer(bool bPrimaryPlayerSignedOut) { s_bServerHalted = true; if (server != nullptr) { m_bPrimaryPlayerSignedOut = bPrimaryPlayerSignedOut; server->halt(); } } File* MinecraftServer::getFile(const std::wstring& name) { return new File(name); } void MinecraftServer::info(const std::wstring& string) {} void MinecraftServer::warn(const std::wstring& string) {} std::wstring MinecraftServer::getConsoleName() { return L"CONSOLE"; } ServerLevel* MinecraftServer::getLevel(int dimension) { if (dimension == -1) return levels[1]; else if (dimension == 1) return levels[2]; else return levels[0]; } // 4J added void MinecraftServer::setLevel(int dimension, ServerLevel* level) { if (dimension == -1) levels[1] = level; else if (dimension == 1) levels[2] = level; else levels[0] = level; } #if defined(_ACK_CHUNK_SEND_THROTTLING) bool MinecraftServer::chunkPacketManagement_CanSendTo(INetworkPlayer* player) { if (s_hasSentEnoughPackets) return false; if (player == nullptr) return false; for (int i = 0; i < s_sentTo.size(); i++) { if (s_sentTo[i]->IsSameSystem(player)) { return false; } } return (player->GetOutstandingAckCount() < 2); } void MinecraftServer::chunkPacketManagement_DidSendTo(INetworkPlayer* player) { int64_t currentTime = System::currentTimeMillis(); if ((currentTime - s_tickStartTime) >= MAX_TICK_TIME_FOR_PACKET_SENDS) { s_hasSentEnoughPackets = true; // app.DebugPrintf("Sending, setting enough packet flag: //%dms\n",currentTime - s_tickStartTime); } else { // app.DebugPrintf("Sending, more time: %dms\n",currentTime //- s_tickStartTime); } player->SentChunkPacket(); s_sentTo.push_back(player); } void MinecraftServer::chunkPacketManagement_PreTick() { // app.DebugPrintf("*************************************************************************************************************************************************************************\n"); s_hasSentEnoughPackets = false; s_tickStartTime = System::currentTimeMillis(); s_sentTo.clear(); std::vector >* players = connection->getPlayers(); if (players->size()) { std::vector > playersOrig = *players; players->clear(); do { int longestTime = 0; auto playerConnectionBest = playersOrig.begin(); for (auto it = playersOrig.begin(); it != playersOrig.end(); it++) { int thisTime = 0; INetworkPlayer* np = (*it)->getNetworkPlayer(); if (np) { thisTime = np->GetTimeSinceLastChunkPacket_ms(); } if (thisTime > longestTime) { playerConnectionBest = it; longestTime = thisTime; } } players->push_back(*playerConnectionBest); playersOrig.erase(playerConnectionBest); } while (playersOrig.size() > 0); } } void MinecraftServer::chunkPacketManagement_PostTick() {} #else // 4J Added bool MinecraftServer::chunkPacketManagement_CanSendTo(INetworkPlayer* player) { if (player == nullptr) return false; int time = GetTickCount(); if (player->GetSessionIndex() == s_slowQueuePlayerIndex && (time - s_slowQueueLastTime) > MINECRAFT_SERVER_SLOW_QUEUE_DELAY) { // app.DebugPrintf("Slow queue OK for player #%d\n", // player->GetSessionIndex()); return true; } return false; } void MinecraftServer::chunkPacketManagement_DidSendTo(INetworkPlayer* player) { s_slowQueuePacketSent = true; } void MinecraftServer::chunkPacketManagement_PreTick() {} void MinecraftServer::chunkPacketManagement_PostTick() { // 4J Ensure that the slow queue owner keeps cycling if it's not been used // in a while int time = GetTickCount(); if ((s_slowQueuePacketSent) || ((time - s_slowQueueLastTime) > (2 * MINECRAFT_SERVER_SLOW_QUEUE_DELAY))) { // app.DebugPrintf("Considering cycling: (%d) %d - %d -> %d //> %d\n",s_slowQueuePacketSent, time, s_slowQueueLastTime, (time - // s_slowQueueLastTime), (2*MINECRAFT_SERVER_SLOW_QUEUE_DELAY)); MinecraftServer::cycleSlowQueueIndex(); s_slowQueuePacketSent = false; s_slowQueueLastTime = time; } // else // { // app.DebugPrintf("Not considering cycling: %d - %d -> %d > //%d\n",time, s_slowQueueLastTime, (time - s_slowQueueLastTime), //(2*MINECRAFT_SERVER_SLOW_QUEUE_DELAY)); // } } void MinecraftServer::cycleSlowQueueIndex() { if (!g_NetworkManager.IsInSession()) return; int startingIndex = s_slowQueuePlayerIndex; INetworkPlayer* currentPlayer = nullptr; int currentPlayerCount = 0; do { currentPlayerCount = g_NetworkManager.GetPlayerCount(); if (startingIndex >= currentPlayerCount) startingIndex = 0; ++s_slowQueuePlayerIndex; if (currentPlayerCount > 0) { s_slowQueuePlayerIndex %= currentPlayerCount; // Fix for #9530 - NETWORKING: Attempting to fill a multiplayer game // beyond capacity results in a softlock for the last players to // join. The QNet session might be ending while we do this, so do a // few more checks that the player is real currentPlayer = g_NetworkManager.GetPlayerByIndex(s_slowQueuePlayerIndex); } else { s_slowQueuePlayerIndex = 0; } } while (g_NetworkManager.IsInSession() && currentPlayerCount > 0 && s_slowQueuePlayerIndex != startingIndex && currentPlayer != nullptr && currentPlayer->IsLocal()); // app.DebugPrintf("Cycled slow queue index to %d\n", // s_slowQueuePlayerIndex); } #endif // 4J added - sets up a vector of flags to indicate which entities (with small // Ids) have been removed from the level, but are still haven't constructed a // network packet to tell a remote client about it. These small Ids shouldn't be // re-used. Most of the time this method shouldn't actually do anything, in // which case it will return false and nothing is set up. bool MinecraftServer::flagEntitiesToBeRemoved(unsigned int* flags) { bool removedFound = false; for (unsigned int i = 0; i < levels.length; i++) { ServerLevel* level = levels[i]; if (level) { level->flagEntitiesToBeRemoved(flags, &removedFound); } } return removedFound; }