diff options
| author | daoge_cmd <3523206925@qq.com> | 2026-03-01 12:16:08 +0800 |
|---|---|---|
| committer | daoge_cmd <3523206925@qq.com> | 2026-03-01 12:16:08 +0800 |
| commit | b691c43c44ff180d10e7d4a9afc83b98551ff586 (patch) | |
| tree | 3e9849222cbc6ba49f2f1fc6e5fe7179632c7390 /Minecraft.Client/MinecraftServer.cpp | |
| parent | def8cb415354ac390b7e89052a50605285f1aca9 (diff) | |
Initial commit
Diffstat (limited to 'Minecraft.Client/MinecraftServer.cpp')
| -rw-r--r-- | Minecraft.Client/MinecraftServer.cpp | 1685 |
1 files changed, 1685 insertions, 0 deletions
diff --git a/Minecraft.Client/MinecraftServer.cpp b/Minecraft.Client/MinecraftServer.cpp new file mode 100644 index 00000000..ceb9554b --- /dev/null +++ b/Minecraft.Client/MinecraftServer.cpp @@ -0,0 +1,1685 @@ +#include "stdafx.h" +//#include "Minecraft.h" + +#include <ctime> + +#include "Options.h" +#include "MinecraftServer.h" +#include "ConsoleInput.h" +#include "PlayerList.h" +#include "ServerLevel.h" +#include "DerivedServerLevel.h" +#include "EntityTracker.h" +#include "ServerConnection.h" +#include "Settings.h" +#include "ServerChunkCache.h" +#include "ServerLevelListener.h" +#include "..\Minecraft.World\AABB.h" +#include "..\Minecraft.World\Vec3.h" +#include "..\Minecraft.World\net.minecraft.network.h" +#include "..\Minecraft.World\net.minecraft.world.level.dimension.h" +#include "..\Minecraft.World\net.minecraft.world.level.storage.h" +#include "..\Minecraft.World\net.minecraft.world.h" +#include "..\Minecraft.World\net.minecraft.world.level.h" +#include "..\Minecraft.World\net.minecraft.world.level.tile.h" +#include "..\Minecraft.World\Pos.h" +#include "..\Minecraft.World\System.h" +#include "..\Minecraft.World\StringHelpers.h" +#ifdef SPLIT_SAVES +#include "..\Minecraft.World\ConsoleSaveFileSplit.h" +#endif +#include "..\Minecraft.World\ConsoleSaveFileOriginal.h" +#include "..\Minecraft.World\Socket.h" +#include "..\Minecraft.World\net.minecraft.world.entity.h" +#include "ProgressRenderer.h" +#include "ServerPlayer.h" +#include "GameRenderer.h" +#include "..\Minecraft.World\ThreadName.h" +#include "..\Minecraft.World\IntCache.h" +#include "..\Minecraft.World\CompressedTileStorage.h" +#include "..\Minecraft.World\SparseLightStorage.h" +#include "..\Minecraft.World\SparseDataStorage.h" +#include "..\Minecraft.World\compression.h" +#ifdef _XBOX +#include "Common\XUI\XUI_DebugSetCamera.h" +#endif +#include "PS3\PS3Extras\ShutdownManager.h" +#include "ServerCommandDispatcher.h" +#include "..\Minecraft.World\BiomeSource.h" +#include "PlayerChunkMap.h" +#include "Common\Telemetry\TelemetryManager.h" + +#define DEBUG_SERVER_DONT_SPAWN_MOBS 0 + +//4J Added +MinecraftServer *MinecraftServer::server = NULL; +bool MinecraftServer::setTimeAtEndOfTick = false; +__int64 MinecraftServer::setTime = 0; +bool MinecraftServer::setTimeOfDayAtEndOfTick = false; +__int64 MinecraftServer::setTimeOfDay = 0; +bool MinecraftServer::m_bPrimaryPlayerSignedOut=false; +bool MinecraftServer::s_bServerHalted=false; +bool MinecraftServer::s_bSaveOnExitAnswered=false; +int MinecraftServer::s_slowQueuePlayerIndex = 0; +int MinecraftServer::s_slowQueueLastTime = 0; +bool MinecraftServer::s_slowQueuePacketSent = false; + +unordered_map<wstring, int> MinecraftServer::ironTimers; + +MinecraftServer::MinecraftServer() +{ + // 4J - added initialisers + connection = NULL; + settings = NULL; + players = NULL; + commands = NULL; + running = true; + m_bLoaded = false; + stopped = false; + tickCount = 0; + 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; + m_postUpdateThread = NULL; + + commandDispatcher = new ServerCommandDispatcher(); +} + +MinecraftServer::~MinecraftServer() +{ +} + +bool MinecraftServer::initServer(__int64 seed, NetworkGameInitData *initData, DWORD initSettings, bool findSeed) +{ + // 4J - removed +#if 0 + commands = new ConsoleCommands(this); + + Thread t = new Thread() { + public void run() { + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + String line = null; + try { + while (!stopped && running && (line = br.readLine()) != null) { + handleConsoleInput(line, MinecraftServer.this); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + }; + t.setDaemon(true); + t.start(); + + + LogConfigurator.initLogger(); + logger.info("Starting minecraft server version " + VERSION); + + if (Runtime.getRuntime().maxMemory() / 1024 / 1024 < 512) { + logger.warning("**** NOT ENOUGH RAM!"); + logger.warning("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\""); + } + + logger.info("Loading properties"); +#endif + 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 +#ifdef _DEBUG_MENUS_ENABLED + setFlightAllowed(true); +#endif + +#if 1 + connection = new ServerConnection(this); + Socket::Initialise(connection); // 4J - added +#else + // 4J - removed + InetAddress localAddress = null; + if (localIp.length() > 0) localAddress = InetAddress.getByName(localIp); + port = settings.getInt("server-port", DEFAULT_MINECRAFT_PORT); + + logger.info("Starting Minecraft server on " + (localIp.length() == 0 ? "*" : localIp) + ":" + port); + try { + connection = new ServerConnection(this, localAddress, port); + } catch (IOException e) { + logger.warning("**** FAILED TO BIND TO PORT!"); + logger.log(Level.WARNING, "The exception was: " + e.toString()); + logger.warning("Perhaps a server is already running on that port?"); + return false; + } + + if (!onlineMode) { + logger.warning("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!"); + logger.warning("The server will make no attempt to authenticate usernames. Beware."); + logger.warning("While this makes the game possible to play without internet access, it also opens up the ability for hackers to connect with any username they choose."); + logger.warning("To change this, set \"online-mode\" to \"true\" in the server.settings file."); + } +#endif + setPlayers(new PlayerList(this)); + + // 4J-JEV: Need to wait for levelGenerationOptions to load. + while ( app.getLevelGenerationOptions() != NULL && !app.getLevelGenerationOptions()->hasLoadedData() ) + Sleep(1); + + if ( app.getLevelGenerationOptions() != NULL && !app.getLevelGenerationOptions()->ready() ) + { + // TODO: Stop loading, add error message. + } + + __int64 levelNanoTime = System::nanoTime(); + + wstring levelName = settings->getString(L"level-name", L"world"); + wstring levelTypeString; + + bool gameRuleUseFlatWorld = false; + if(app.getLevelGenerationOptions() != NULL) + { + 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 == NULL) + { + pLevelType = LevelType::lvl_normal; + } + + ProgressRenderer *mcprogress = Minecraft::GetInstance()->progressRenderer; + mcprogress->progressStart(IDS_PROGRESS_INITIALISING_SERVER); + + if( findSeed ) + { +#ifdef __PSVITA__ + seed = BiomeSource::findSeed(pLevelType, &running); +#else + seed = BiomeSource::findSeed(pLevelType); +#endif + } + + 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); + +#if 0 + wstring levelSeedString = settings->getString(L"level-seed", L""); + __int64 levelSeed = (new Random())->nextLong(); + if (levelSeedString.length() > 0) + { + long newSeed = _fromString<__int64>(levelSeedString); + if (newSeed != 0) { + levelSeed = newSeed; + } + } +#endif +// 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 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 + IntCache::CreateNewThreadStorage(); + AABB::CreateNewThreadStorage(); + Vec3::CreateNewThreadStorage(); + Compression::UseDefaultThreadStorage(); + Level::enableLightingCache(); + Tile::CreateNewThreadStorage(); + + // Update lights for both levels until we are signalled to terminate + do + { + EnterCriticalSection(&server->m_postProcessCS); + if( server->m_postProcessRequests.size() ) + { + MinecraftServer::postProcessRequest request = server->m_postProcessRequests.back(); + server->m_postProcessRequests.pop_back(); + LeaveCriticalSection(&server->m_postProcessCS); + static int count = 0; + PIXBeginNamedEvent(0,"Post processing %d ", (count++)%8); + request.chunkSource->postProcess(request.chunkSource, request.x, request.z ); + PIXEndNamedEvent(); + } + else + { + LeaveCriticalSection(&server->m_postProcessCS); + } + Sleep(1); + } while (!server->m_postUpdateTerminate && ShutdownManager::ShouldRun(ShutdownManager::ePostProcessThread)); +//#ifndef __PS3__ + // One final pass through updates to make sure we're done + EnterCriticalSection(&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(); + LeaveCriticalSection(&server->m_postProcessCS); + request.chunkSource->postProcess(request.chunkSource, request.x, request.z ); +#ifdef __PS3__ +#ifndef _CONTENT_PACKAGE + if((server->m_postProcessRequests.size() % 10) == 0) + printf("processing request %00d\n", server->m_postProcessRequests.size()); +#endif + Sleep(1); +#endif + EnterCriticalSection(&server->m_postProcessCS); + } + LeaveCriticalSection(&server->m_postProcessCS); +//#endif //__PS3__ + Tile::ReleaseThreadStorage(); + IntCache::ReleaseThreadStorage(); + AABB::ReleaseThreadStorage(); + Vec3::ReleaseThreadStorage(); + Level::destroyLightingCache(); + + ShutdownManager::HasFinished(ShutdownManager::ePostProcessThread); + + return 0; +} + +void MinecraftServer::addPostProcessRequest(ChunkSource *chunkSource, int x, int z) +{ + EnterCriticalSection(&m_postProcessCS); + m_postProcessRequests.push_back(MinecraftServer::postProcessRequest(x,z,chunkSource)); + LeaveCriticalSection(&m_postProcessCS); +} + +void MinecraftServer::postProcessTerminate(ProgressRenderer *mcprogress) +{ + DWORD status = 0; + + EnterCriticalSection(&server->m_postProcessCS); + size_t postProcessItemCount = server->m_postProcessRequests.size(); + LeaveCriticalSection(&server->m_postProcessCS); + + do + { + status = m_postUpdateThread->WaitForCompletion(50); + if( status == WAIT_TIMEOUT ) + { + EnterCriticalSection(&server->m_postProcessCS); + size_t postProcessItemRemaining = server->m_postProcessRequests.size(); + LeaveCriticalSection(&server->m_postProcessCS); + + if( postProcessItemCount ) + { + mcprogress->progressStagePercentage((postProcessItemCount - postProcessItemRemaining) * 100 / postProcessItemCount); + } + CompressedTileStorage::tick(); + SparseLightStorage::tick(); + SparseDataStorage::tick(); + } + } while ( status == WAIT_TIMEOUT ); + delete m_postUpdateThread; + m_postUpdateThread = NULL; + DeleteCriticalSection(&m_postProcessCS); +} + +bool MinecraftServer::loadLevel(LevelStorageSource *storageSource, const wstring& name, __int64 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 + shared_ptr<McRegionLevelStorage> storage = nullptr; + bool levelChunksNeedConverted = false; + if( initData->saveData != NULL ) + { + // We are loading a file from disk with the data passed in + +#ifdef 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 = shared_ptr<McRegionLevelStorage>(new McRegionLevelStorage(pSave, File(L"."), name, true)); + } + else + { + // We are loading a save from the storage manager +#ifdef SPLIT_SAVES + bool bLevelGenBaseSave = false; + LevelGenerationOptions *levelGen = app.getLevelGenerationOptions(); + if( levelGen != NULL && levelGen->requiresBaseSave()) + { + DWORD fileSize = 0; + LPVOID pvSaveData = levelGen->getBaseSaveData(fileSize); + if(pvSaveData && fileSize != 0) bLevelGenBaseSave = true; + } + ConsoleSaveFileSplit *newFormatSave = NULL; + if(bLevelGenBaseSave) + { + ConsoleSaveFileOriginal oldFormatSave( L"" ); + newFormatSave = new ConsoleSaveFileSplit( &oldFormatSave ); + } + else + { + newFormatSave = new ConsoleSaveFileSplit( L"" ); + } + + storage = shared_ptr<McRegionLevelStorage>(new McRegionLevelStorage(newFormatSave, File(L"."), name, true)); +#else + storage = shared_ptr<McRegionLevelStorage>(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() != NULL) + { + LevelGenerationOptions *mapOptions = app.getLevelGenerationOptions(); + Pos *spawnPos = mapOptions->getSpawnPos(); + if( spawnPos != NULL ) + { + 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); + 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 + InitializeCriticalSection(&m_postProcessCS); + + // 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 startTime = System::currentTimeMillis(); + + // 4J Stu - Added this to temporarily make starting games on vita faster +#ifdef __PSVITA__ + int r = 48; +#else + int r = 196; +#endif + + // 4J JEV: load gameRules. + ConsoleSavePath filepath(GAME_RULE_SAVENAME); + ConsoleSaveFile *csf = getLevel(0)->getLevelStorage()->getSaveFile(); + if( csf->doesFileExist(filepath) ) + { + DWORD numberOfBytesRead; + byteArray ba_gameRules; + + FileEntry *fe = csf->createFile(filepath); + + ba_gameRules.length = fe->getFileSize(); + ba_gameRules.data = new BYTE[ ba_gameRules.length ]; + + csf->setFilePointer(fe,0,NULL,FILE_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 lastTime = System::currentTimeMillis(); + + // 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) + } + +#if 0 + __int64 lastStorageTickTime = System::currentTimeMillis(); + + // Test code to enable full creation of levels at start up + int halfsidelen = ( i == 0 ) ? 27 : 9; + for( int x = -halfsidelen; x < halfsidelen; x++ ) + { + for( int z = -halfsidelen; z < halfsidelen; z++ ) + { + int total = halfsidelen * halfsidelen * 4; + int pos = z + halfsidelen + ( ( x + halfsidelen ) * 2 * halfsidelen ); + mcprogress->progressStagePercentage((pos) * 100 / total); + level->cache->create(x,z, true); // 4J - added parameter to disable postprocessing here + + if( System::currentTimeMillis() - lastStorageTickTime > 50 ) + { + CompressedTileStorage::tick(); + SparseLightStorage::tick(); + SparseDataStorage::tick(); + lastStorageTickTime = System::currentTimeMillis(); + } + } + } +#else + __int64 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 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 +#if 0 + // 4J - added this code to propagate lighting properly in the spawn area before we go sharing it with the local client or across the network + for (int x = -r; x <= r && running; x += 16) + { + for (int z = -r; z <= r && running; z += 16) + { + PIXBeginNamedEvent(0,"Lighting gaps for %d %d",x,z); + level->getChunkAt(spawnPos->x + x, spawnPos->z + z)->recheckGaps(true); + PIXEndNamedEvent(); + } + } +#endif + + delete spawnPos; +#endif + } + } +// 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; +} + +void MinecraftServer::setProgress(const 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. +#ifndef _XBOX_ONE + if( m_bPrimaryPlayerSignedOut ) break; +#endif + // 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 NULL 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() +{ +#ifndef _CONTENT_PACKAGE + if(app.DebugSettingsOn() && app.GetGameSettingsDebugMask(ProfileManager.GetPrimaryPad())&(1L<<eDebugSetting_DistributableSave)) + { + // Do nothing + } + else +#endif + { + byteArray ba; + ba.data = NULL; + app.m_gameRules.saveGameRules( &ba.data, &ba.length ); + + if (ba.data != NULL) + { + ConsoleSaveFile *csf = getLevel(0)->getLevelStorage()->getSaveFile(); + FileEntry *fe = csf->createFile(ConsoleSavePath(GAME_RULE_SAVENAME)); + csf->setFilePointer(fe, 0, NULL, FILE_BEGIN); + DWORD 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 != NULL) + { + players->saveAll(NULL); + } + 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(NULL, true); + } + } + QueryPerformanceCounter( &qwNewTime ); + + qwDeltaTime.QuadPart = qwNewTime.QuadPart - qwTime.QuadPart; + fElapsedTime = fSecsPerTick * ((FLOAT)(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() +{ + + // 4J-PB - need to halt the rendering of the data, since we're about to remove it +#ifdef __PS3__ + if( ShutdownManager::ShouldRun(ShutdownManager::eServerThread ) ) // This thread will take itself out if we are shutting down +#endif + { + 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 +#ifdef _DURANGO + // On Durango check if the primary user is signed in OR mid-sign-out + if(ProfileManager.GetUser(0, true) != nullptr) +#else + if((m_bPrimaryPlayerSignedOut==false) && ProfileManager.IsSignedIn(ProfileManager.GetPrimaryPad())) +#endif + { +#if defined(_XBOX_ONE) || defined(__ORBIS__) + // Always save on exit! Except if saves are disabled. + if(!saveOnExitAnswered()) m_saveOnExit = true; +#endif + // if trial version or saving is disabled, then don't save anything + if(m_saveOnExit && ProfileManager.IsFullVersion() && (!StorageManager.GetSaveDisabled())) + { + if (players != NULL) + { + 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 != NULL) + // { + saveAllChunks(); + // } + //} + + saveGameRules(); + app.m_gameRules.unloadCurrentGameRules(); + if( levels[0] != NULL ) // 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. +#if defined(_DURANGO) || defined(__ORBIS__) || defined(__PSVITA__) + while(StorageManager.GetSaveState() != C4JStorage::ESaveGame_Idle ) + { + Sleep(10); + } +#endif + + // 4J-PB remove the server levels + unsigned int iServerLevelC=levels.length; + for (unsigned int i = 0; i < iServerLevelC; i++) + { + if(levels[i]!=NULL) + { + delete levels[i]; + levels[i] = NULL; + } + } + +#if defined(__PS3__) || defined(__ORBIS__) + // Clear the update flags as it's possible they could be out of sync, causing a crash when starting a new world after the first new level ticks + // Fix for PS3 #1538 - [IN GAME] If the user 'Exit without saving' from inside the Nether or The End, the title can hang when loading back into the save. +#endif + + delete connection; + connection = NULL; + delete players; + players = NULL; + delete settings; + settings = NULL; + + 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::isNetherEnabled() +{ + return true; //settings.getBoolean("allow-nether", true); +} + +bool MinecraftServer::isHardcore() +{ + return false; +} + +CommandDispatcher *MinecraftServer::getCommandDispatcher() +{ + return commandDispatcher; +} + +extern int c0a, c0b, c1a, c1b, c1c, c2a, c2b; +void MinecraftServer::run(__int64 seed, void *lpParameter) +{ + NetworkGameInitData *initData = NULL; + DWORD initSettings = 0; + bool findSeed = false; + if(lpParameter != NULL) + { + initData = (NetworkGameInitData *)lpParameter; + initSettings = app.GetGameHostOption(eGameHostOption_All); + findSeed = initData->findSeed; + m_texturePackId = initData->texturePackId; + } +// try { // 4J - removed try/catch/finally + if (initServer(seed, initData, initSettings,findSeed)) + { + 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 lastTime = System::currentTimeMillis(); + __int64 unprocessedTime = 0; + while (running && !s_bServerHalted) + { + __int64 now = System::currentTimeMillis(); + + // 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 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 beforeall = System::currentTimeMillis(); + while (unprocessedTime > MS_PER_TICK) + { + unprocessedTime -= MS_PER_TICK; +// __int64 before = System::currentTimeMillis(); + tick(); +// __int64 after = System::currentTimeMillis(); +// PIXReportCounter(L"Server time",(float)(after-before)); + + // 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)); +// } + } +// __int64 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->setTime( MinecraftServer::setTime ); + level->setOverrideTimeOfDay( -1 ); + } + } + } + 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->setTime( MinecraftServer::setTime ); + level->setOverrideTimeOfDay( MinecraftServer::setTimeOfDay ); + } + } + } + + // Process delayed actions + eXuiServerAction eAction; + LPVOID param; + for(int i=0;i<XUSER_MAX_COUNT;i++) + { + eAction = app.GetXuiServerAction(i); + param = app.GetXuiServerActionParam(i); + + switch(eAction) + { + case eXuiServerAction_AutoSaveGame: +#if defined(_XBOX_ONE) || defined(__ORBIS__) + { + PIXBeginNamedEvent(0,"Autosave"); + + // 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 (players != NULL) + { + players->saveAll(NULL); + } + + 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]; + PIXBeginNamedEvent(0, "Saving level %d",levels.length - 1 - j); + level->save(false, NULL, true); + PIXEndNamedEvent(); + } + if( !s_bServerHalted ) + { + PIXBeginNamedEvent(0,"Saving game rules"); + saveGameRules(); + PIXEndNamedEvent(); + + PIXBeginNamedEvent(0,"Save to disc"); + levels[0]->saveToDisc(Minecraft::GetInstance()->progressRenderer, true); + PIXEndNamedEvent(); + } + PIXEndNamedEvent(); + + QueryPerformanceCounter( &qwNewTime ); + qwDeltaTime.QuadPart = qwNewTime.QuadPart - qwTime.QuadPart; + fElapsedTime = fSecsPerTick * ((FLOAT)(qwDeltaTime.QuadPart)); + app.DebugPrintf("Autosave: Elapsed time %f\n", fElapsedTime); + } + break; +#endif + case eXuiServerAction_SaveGame: + app.EnterSaveNotificationSection(); + if (players != NULL) + { + players->saveAll(Minecraft::GetInstance()->progressRenderer); + } + + players->broadcastAll( shared_ptr<UpdateProgressPacket>( 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( shared_ptr<UpdateProgressPacket>( 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 + { + shared_ptr<ServerPlayer> player = players->players.at(0); + size_t id = (size_t) param; + player->drop( shared_ptr<ItemInstance>( new ItemInstance(id, 1, 0 ) ) ); + } + break; + case eXuiServerAction_SpawnMob: + { + shared_ptr<ServerPlayer> player = players->players.at(0); + eINSTANCEOF factory = (eINSTANCEOF)((size_t)param); + shared_ptr<Mob> mob = dynamic_pointer_cast<Mob>(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( shared_ptr<ServerSettingsChangedPacket>( new ServerSettingsChangedPacket( ServerSettingsChangedPacket::HOST_OPTIONS, app.GetGameHostOption(eGameHostOption_Gamertags)) ) ); + break; + case eXuiServerAction_ServerSettingChanged_BedrockFog: + players->broadcastAll( shared_ptr<ServerSettingsChangedPacket>( new ServerSettingsChangedPacket( ServerSettingsChangedPacket::HOST_IN_GAME_SETTINGS, app.GetGameHostOption(eGameHostOption_All)) ) ); + break; + + case eXuiServerAction_ServerSettingChanged_Difficulty: + players->broadcastAll( shared_ptr<ServerSettingsChangedPacket>( new ServerSettingsChangedPacket( ServerSettingsChangedPacket::HOST_DIFFICULTY, Minecraft::GetInstance()->options->difficulty) ) ); + break; + case eXuiServerAction_ExportSchematic: +#ifndef _CONTENT_PACKAGE + app.EnterSaveNotificationSection(); + + //players->broadcastAll( shared_ptr<UpdateProgressPacket>( new UpdateProgressPacket(20) ) ); + + if( !s_bServerHalted ) + { + ConsoleSchematicFile::XboxSchematicInitParam *initData = (ConsoleSchematicFile::XboxSchematicInitParam *)param; +#ifdef _XBOX + File targetFileDir(File::pathRoot + File::pathSeparator + L"Schematics"); +#else + File targetFileDir(L"Schematics"); +#endif + 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, 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: +#ifndef _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 + ); + + shared_ptr<ServerPlayer> 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; + } + + app.SetXuiServerAction(i,eXuiServerAction_Idle); + } + + Sleep(1); + } + } + //else + //{ + // while (running) + // { + // handleConsoleInputs(); + // Sleep(10); + // } + //} +#if 0 + } catch (Throwable t) { + t.printStackTrace(); + logger.log(Level.SEVERE, "Unexpected exception", t); + while (running) { + handleConsoleInputs(); + try { + Thread.sleep(10); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + } + } finally { + try { + stopServer(); + stopped = true; + } catch (Throwable t) { + t.printStackTrace(); + } finally { + System::exit(0); + } + } +#endif + + // 4J Stu - Stop the server when the loops complete, as the finally would do + stopServer(); + stopped = true; +} + +void MinecraftServer::broadcastStartSavingPacket() +{ + players->broadcastAll( shared_ptr<GameEventPacket>( new GameEventPacket(GameEventPacket::START_SAVING, 0) ) );; +} + +void MinecraftServer::broadcastStopSavingPacket() +{ + if( !s_bServerHalted ) + { + players->broadcastAll( shared_ptr<GameEventPacket>( new GameEventPacket(GameEventPacket::STOP_SAVING, 0) ) );; + } +} + +void MinecraftServer::tick() +{ + vector<wstring> toRemove; + for (AUTO_VAR(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]); + } + + AABB::resetPool(); + Vec3::resetPool(); + + 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<ServerSettingsChangedPacket>( 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( shared_ptr<SetTimePacket>( new SetTimePacket(level->getTime() ) ), level->dimension->id); + } +// #ifndef __PS3__ + static __int64 stc = 0; + __int64 st0 = System::currentTimeMillis(); + PIXBeginNamedEvent(0,"Level tick %d",i); + ((Level *)level)->tick(); + __int64 st1 = System::currentTimeMillis(); + PIXEndNamedEvent(); + PIXBeginNamedEvent(0,"Update lights %d",i); + // 4J - used to be in a while loop, but we don't want the server locking up for a big chunk of time (could end up trying to process 1,000,000 lights...) + // Instead call this once, which will try and process up to 2000 lights per tick +// printf("lights: %d\n",level->getLightsToUpdate()); + while(level->updateLights() ) + ; + __int64 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() ) ) + { +#ifdef __PSVITA__ + // AP - the PlayerList->viewDistance initially starts out at 3 to make starting a level speedy + // the problem with this is that spawned monsters are always generated on the edge of the known map + // which means they wont process (unless they are surrounded by 2 visible chunks). This means + // they wont checkDespawn so they are NEVER removed which results in monsters not spawning. + // This bit of hack will modify the view distance once the level is up and running. + int newViewDistance = 5; + level->getServer()->getPlayers()->setViewDistance(newViewDistance); + level->getTracker()->updateMaxRange(); + level->getChunkMap()->setRadius(level->getServer()->getPlayers()->getViewDistance()); +#endif + level->tickEntities(); + } + PIXEndNamedEvent(); + + PIXBeginNamedEvent(0,"Entity tracker tick"); + level->getTracker()->tick(); + PIXEndNamedEvent(); + + __int64 st3 = System::currentTimeMillis(); +// printf(">>>>>>>>>>>>>>>>>>>>>> Tick %d %d %d : %d\n", st1 - st0, st2 - st1, st3 - st2, st0 - stc ); + stc = st0; +// #endif// __PS3__ + } + } + Entity::tickExtraWandering(); // 4J added + + PIXBeginNamedEvent(0,"Connection tick"); + connection->tick(); + PIXEndNamedEvent(); + PIXBeginNamedEvent(0,"Players tick"); + players->tick(); + PIXEndNamedEvent(); + + // 4J - removed +#if 0 + for (int i = 0; i < tickables.size(); i++) { + tickables.get(i)-tick(); + } +#endif + +// try { // 4J - removed try/catch + handleConsoleInputs(); +// } catch (Exception e) { +// logger.log(Level.WARNING, "Unexpected exception while parsing console command", e); +// } +} + +void MinecraftServer::handleConsoleInput(const wstring& msg, ConsoleInputSource *source) +{ + consoleInput.push_back(new ConsoleInput(msg, source)); +} + +void MinecraftServer::handleConsoleInputs() +{ + while (consoleInput.size() > 0) + { + AUTO_VAR(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 seed, void *lpParameter) +{ +#if __PS3__ + ShutdownManager::HasStarted(ShutdownManager::eServerThread ); +#endif + server = new MinecraftServer(); + server->run(seed, lpParameter); + delete server; + server = NULL; + ShutdownManager::HasFinished(ShutdownManager::eServerThread ); +} + +void MinecraftServer::HaltServer(bool bPrimaryPlayerSignedOut) +{ + s_bServerHalted = true; + if( server != NULL ) + { + m_bPrimaryPlayerSignedOut=bPrimaryPlayerSignedOut; + server->halt(); + } +} + +File *MinecraftServer::getFile(const wstring& name) +{ + return new File(name); +} + +void MinecraftServer::info(const wstring& string) +{ +} + +void MinecraftServer::warn(const wstring& string) +{ +} + +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; +} + +// 4J Added +bool MinecraftServer::canSendOnSlowQueue(INetworkPlayer *player) +{ + if( player == NULL ) 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::cycleSlowQueueIndex() +{ + if( !g_NetworkManager.IsInSession() ) return; + + int startingIndex = s_slowQueuePlayerIndex; + INetworkPlayer *currentPlayer = NULL; + DWORD 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 != NULL && + currentPlayer->IsLocal() + ); +// app.DebugPrintf("Cycled slow queue index to %d\n", s_slowQueuePlayerIndex); +} + +// 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; +}
\ No newline at end of file |
