#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;
}