Files
MinecraftConsoles/Minecraft.Client/MinecraftServer.cpp
Loki Rautio 087b7e7abf Revert "Project modernization (#630)"
This code was not tested and breaks in Release builds, reverting to restore
functionality of the nightly. All in-game menus do not work and generating
a world crashes.

This reverts commit a9be52c41a.
2026-03-07 21:12:22 -06:00

2451 lines
76 KiB
C++
Raw Permalink Blame History

#include "stdafx.h"
//#include "Minecraft.h"
#include <ctime>
#include "ConsoleInput.h"
#include "DerivedServerLevel.h"
#include "DispenserBootstrap.h"
#include "EntityTracker.h"
#include "MinecraftServer.h"
#include "Options.h"
#include "PlayerList.h"
#include "ServerChunkCache.h"
#include "ServerConnection.h"
#include "ServerLevel.h"
#include "ServerLevelListener.h"
#include "Settings.h"
#include "..\Minecraft.World\Command.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"
#include "..\Minecraft.World\net.minecraft.world.entity.item.h"
#include "..\Minecraft.World\net.minecraft.world.item.h"
#include "..\Minecraft.World\net.minecraft.world.item.enchantment.h"
#include "..\Minecraft.World\net.minecraft.world.damagesource.h"
#ifdef _WINDOWS64
#include "Windows64\Network\WinsockNetLayer.h"
#endif
#include <sstream>
#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"
#include "PlayerConnection.h"
#ifdef _XBOX_ONE
#include "Durango\Network\NetworkPlayerDurango.h"
#endif
#define DEBUG_SERVER_DONT_SPAWN_MOBS 0
//4J Added
MinecraftServer *MinecraftServer::server = NULL;
bool MinecraftServer::setTimeAtEndOfTick = false;
int64_t MinecraftServer::setTime = 0;
bool MinecraftServer::setTimeOfDayAtEndOfTick = false;
int64_t MinecraftServer::setTimeOfDay = 0;
bool MinecraftServer::m_bPrimaryPlayerSignedOut=false;
bool MinecraftServer::s_bServerHalted=false;
bool MinecraftServer::s_bSaveOnExitAnswered=false;
#ifdef _ACK_CHUNK_SEND_THROTTLING
bool MinecraftServer::s_hasSentEnoughPackets = false;
int64_t MinecraftServer::s_tickStartTime = 0;
vector<INetworkPlayer *> MinecraftServer::s_sentTo;
#else
int MinecraftServer::s_slowQueuePlayerIndex = 0;
int MinecraftServer::s_slowQueueLastTime = 0;
bool MinecraftServer::s_slowQueuePacketSent = false;
#endif
unordered_map<wstring, int> MinecraftServer::ironTimers;
static bool ShouldUseDedicatedServerProperties()
{
#ifdef _WINDOWS64
return g_Win64DedicatedServer;
#else
return false;
#endif
}
static int GetDedicatedServerInt(Settings *settings, const wchar_t *key, int defaultValue)
{
return (ShouldUseDedicatedServerProperties() && settings != NULL) ? settings->getInt(key, defaultValue) : defaultValue;
}
static bool GetDedicatedServerBool(Settings *settings, const wchar_t *key, bool defaultValue)
{
return (ShouldUseDedicatedServerProperties() && settings != NULL) ? settings->getBoolean(key, defaultValue) : defaultValue;
}
static wstring GetDedicatedServerString(Settings *settings, const wchar_t *key, const wstring &defaultValue)
{
return (ShouldUseDedicatedServerProperties() && settings != NULL) ? settings->getString(key, defaultValue) : defaultValue;
}
static void PrintConsoleLine(const wchar_t *prefix, const wstring &message)
{
wprintf(L"%ls%ls\n", prefix, message.c_str());
fflush(stdout);
}
static bool TryParseIntValue(const wstring &text, int &value)
{
std::wistringstream stream(text);
stream >> value;
return !stream.fail() && stream.eof();
}
static vector<wstring> SplitConsoleCommand(const wstring &command)
{
vector<wstring> tokens;
std::wistringstream stream(command);
wstring token;
while (stream >> token)
{
tokens.push_back(token);
}
return tokens;
}
static wstring JoinConsoleCommandTokens(const vector<wstring> &tokens, size_t startIndex)
{
wstring joined;
for (size_t i = startIndex; i < tokens.size(); ++i)
{
if (!joined.empty()) joined += L" ";
joined += tokens[i];
}
return joined;
}
static shared_ptr<ServerPlayer> FindPlayerByName(PlayerList *playerList, const wstring &name)
{
if (playerList == NULL) return nullptr;
for (size_t i = 0; i < playerList->players.size(); ++i)
{
shared_ptr<ServerPlayer> player = playerList->players[i];
if (player != NULL && equalsIgnoreCase(player->getName(), name))
{
return player;
}
}
return nullptr;
}
static void SetAllLevelTimes(MinecraftServer *server, int value)
{
for (unsigned int i = 0; i < server->levels.length; ++i)
{
if (server->levels[i] != NULL)
{
server->levels[i]->setDayTime(value);
}
}
}
static bool ExecuteConsoleCommand(MinecraftServer *server, const wstring &rawCommand)
{
if (server == NULL)
return false;
wstring command = trimString(rawCommand);
if (command.empty())
return true;
if (command[0] == L'/')
{
command = trimString(command.substr(1));
}
vector<wstring> tokens = SplitConsoleCommand(command);
if (tokens.empty())
return true;
const wstring action = toLower(tokens[0]);
PlayerList *playerList = server->getPlayers();
if (action == L"help" || action == L"?")
{
server->info(L"Commands: help, stop, list, say <message>, save-all, time <set day|night|ticks|add ticks>, weather <clear|rain|thunder> [seconds], tp <player> <target>, give <player> <itemId> [amount] [aux], enchant <player> <enchantId> [level], kill <player>");
return true;
}
if (action == L"stop")
{
server->info(L"Stopping server...");
MinecraftServer::HaltServer();
return true;
}
if (action == L"list")
{
wstring playerNames = (playerList != NULL) ? playerList->getPlayerNames() : L"";
if (playerNames.empty()) playerNames = L"(none)";
server->info(L"Players (" + std::to_wstring((playerList != NULL) ? playerList->getPlayerCount() : 0) + L"): " + playerNames);
return true;
}
if (action == L"say")
{
if (tokens.size() < 2)
{
server->warn(L"Usage: say <message>");
return false;
}
wstring message = L"[Server] " + JoinConsoleCommandTokens(tokens, 1);
if (playerList != NULL)
{
playerList->broadcastAll(shared_ptr<ChatPacket>(new ChatPacket(message)));
}
server->info(message);
return true;
}
if (action == L"save-all")
{
if (playerList != NULL)
{
playerList->saveAll(NULL, false);
}
server->info(L"World saved.");
return true;
}
if (action == L"time")
{
if (tokens.size() < 2)
{
server->warn(L"Usage: time set <day|night|ticks> | time add <ticks>");
return false;
}
if (toLower(tokens[1]) == L"add")
{
if (tokens.size() < 3)
{
server->warn(L"Usage: time add <ticks>");
return false;
}
int delta = 0;
if (!TryParseIntValue(tokens[2], delta))
{
server->warn(L"Invalid tick value: " + tokens[2]);
return false;
}
for (unsigned int i = 0; i < server->levels.length; ++i)
{
if (server->levels[i] != NULL)
{
server->levels[i]->setDayTime(server->levels[i]->getDayTime() + delta);
}
}
server->info(L"Added " + std::to_wstring(delta) + L" ticks.");
return true;
}
wstring timeValue = toLower(tokens[1]);
if (timeValue == L"set")
{
if (tokens.size() < 3)
{
server->warn(L"Usage: time set <day|night|ticks>");
return false;
}
timeValue = toLower(tokens[2]);
}
int targetTime = 0;
if (timeValue == L"day")
{
targetTime = 0;
}
else if (timeValue == L"night")
{
targetTime = 12500;
}
else if (!TryParseIntValue(timeValue, targetTime))
{
server->warn(L"Invalid time value: " + timeValue);
return false;
}
SetAllLevelTimes(server, targetTime);
server->info(L"Time set to " + std::to_wstring(targetTime) + L".");
return true;
}
if (action == L"weather")
{
if (tokens.size() < 2)
{
server->warn(L"Usage: weather <clear|rain|thunder> [seconds]");
return false;
}
int durationSeconds = 600;
if (tokens.size() >= 3 && !TryParseIntValue(tokens[2], durationSeconds))
{
server->warn(L"Invalid duration: " + tokens[2]);
return false;
}
if (server->levels[0] == NULL)
{
server->warn(L"The overworld is not loaded.");
return false;
}
LevelData *levelData = server->levels[0]->getLevelData();
int duration = durationSeconds * SharedConstants::TICKS_PER_SECOND;
levelData->setRainTime(duration);
levelData->setThunderTime(duration);
wstring weather = toLower(tokens[1]);
if (weather == L"clear")
{
levelData->setRaining(false);
levelData->setThundering(false);
}
else if (weather == L"rain")
{
levelData->setRaining(true);
levelData->setThundering(false);
}
else if (weather == L"thunder")
{
levelData->setRaining(true);
levelData->setThundering(true);
}
else
{
server->warn(L"Usage: weather <clear|rain|thunder> [seconds]");
return false;
}
server->info(L"Weather set to " + weather + L".");
return true;
}
if (action == L"tp" || action == L"teleport")
{
if (tokens.size() < 3)
{
server->warn(L"Usage: tp <player> <target>");
return false;
}
shared_ptr<ServerPlayer> subject = FindPlayerByName(playerList, tokens[1]);
shared_ptr<ServerPlayer> destination = FindPlayerByName(playerList, tokens[2]);
if (subject == NULL)
{
server->warn(L"Unknown player: " + tokens[1]);
return false;
}
if (destination == NULL)
{
server->warn(L"Unknown player: " + tokens[2]);
return false;
}
if (subject->level->dimension->id != destination->level->dimension->id || !subject->isAlive())
{
server->warn(L"Teleport failed because the players are not in the same dimension or the source player is dead.");
return false;
}
subject->ride(nullptr);
subject->connection->teleport(destination->x, destination->y, destination->z, destination->yRot, destination->xRot);
server->info(L"Teleported " + subject->getName() + L" to " + destination->getName() + L".");
return true;
}
if (action == L"give")
{
if (tokens.size() < 3)
{
server->warn(L"Usage: give <player> <itemId> [amount] [aux]");
return false;
}
shared_ptr<ServerPlayer> player = FindPlayerByName(playerList, tokens[1]);
if (player == NULL)
{
server->warn(L"Unknown player: " + tokens[1]);
return false;
}
int itemId = 0;
int amount = 1;
int aux = 0;
if (!TryParseIntValue(tokens[2], itemId))
{
server->warn(L"Invalid item id: " + tokens[2]);
return false;
}
if (tokens.size() >= 4 && !TryParseIntValue(tokens[3], amount))
{
server->warn(L"Invalid amount: " + tokens[3]);
return false;
}
if (tokens.size() >= 5 && !TryParseIntValue(tokens[4], aux))
{
server->warn(L"Invalid aux value: " + tokens[4]);
return false;
}
if (itemId <= 0 || Item::items[itemId] == NULL)
{
server->warn(L"Unknown item id: " + std::to_wstring(itemId));
return false;
}
if (amount <= 0)
{
server->warn(L"Amount must be positive.");
return false;
}
shared_ptr<ItemInstance> itemInstance(new ItemInstance(itemId, amount, aux));
shared_ptr<ItemEntity> drop = player->drop(itemInstance);
if (drop != NULL)
{
drop->throwTime = 0;
}
server->info(L"Gave item " + std::to_wstring(itemId) + L" x" + std::to_wstring(amount) + L" to " + player->getName() + L".");
return true;
}
if (action == L"enchant")
{
if (tokens.size() < 3)
{
server->warn(L"Usage: enchant <player> <enchantId> [level]");
return false;
}
shared_ptr<ServerPlayer> player = FindPlayerByName(playerList, tokens[1]);
if (player == NULL)
{
server->warn(L"Unknown player: " + tokens[1]);
return false;
}
int enchantmentId = 0;
int enchantmentLevel = 1;
if (!TryParseIntValue(tokens[2], enchantmentId))
{
server->warn(L"Invalid enchantment id: " + tokens[2]);
return false;
}
if (tokens.size() >= 4 && !TryParseIntValue(tokens[3], enchantmentLevel))
{
server->warn(L"Invalid enchantment level: " + tokens[3]);
return false;
}
shared_ptr<ItemInstance> selectedItem = player->getSelectedItem();
if (selectedItem == NULL)
{
server->warn(L"The player is not holding an item.");
return false;
}
Enchantment *enchantment = Enchantment::enchantments[enchantmentId];
if (enchantment == NULL)
{
server->warn(L"Unknown enchantment id: " + std::to_wstring(enchantmentId));
return false;
}
if (!enchantment->canEnchant(selectedItem))
{
server->warn(L"That enchantment cannot be applied to the selected item.");
return false;
}
if (enchantmentLevel < enchantment->getMinLevel()) enchantmentLevel = enchantment->getMinLevel();
if (enchantmentLevel > enchantment->getMaxLevel()) enchantmentLevel = enchantment->getMaxLevel();
if (selectedItem->hasTag())
{
ListTag<CompoundTag> *enchantmentTags = selectedItem->getEnchantmentTags();
if (enchantmentTags != NULL)
{
for (int i = 0; i < enchantmentTags->size(); i++)
{
int type = enchantmentTags->get(i)->getShort((wchar_t *)ItemInstance::TAG_ENCH_ID);
if (Enchantment::enchantments[type] != NULL && !Enchantment::enchantments[type]->isCompatibleWith(enchantment))
{
server->warn(L"That enchantment conflicts with an existing enchantment on the selected item.");
return false;
}
}
}
}
selectedItem->enchant(enchantment, enchantmentLevel);
server->info(L"Enchanted " + player->getName() + L"'s held item with " + std::to_wstring(enchantmentId) + L" " + std::to_wstring(enchantmentLevel) + L".");
return true;
}
if (action == L"kill")
{
if (tokens.size() < 2)
{
server->warn(L"Usage: kill <player>");
return false;
}
shared_ptr<ServerPlayer> player = FindPlayerByName(playerList, tokens[1]);
if (player == NULL)
{
server->warn(L"Unknown player: " + tokens[1]);
return false;
}
player->hurt(DamageSource::outOfWorld, 3.4e38f);
server->info(L"Killed " + player->getName() + L".");
return true;
}
server->warn(L"Unknown command: " + command);
return false;
}
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;
playerIdleTimeout = 0;
m_postUpdateThread = NULL;
forceGameType = false;
commandDispatcher = new ServerCommandDispatcher();
InitializeCriticalSection(&m_consoleInputCS);
DispenserBootstrap::bootStrap();
}
MinecraftServer::~MinecraftServer()
{
DeleteCriticalSection(&m_consoleInputCS);
}
bool MinecraftServer::initServer(int64_t 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.SetGameHostOption(eGameHostOption_Difficulty, GetDedicatedServerInt(settings, L"difficulty", app.GetGameHostOption(eGameHostOption_Difficulty)));
app.SetGameHostOption(eGameHostOption_GameType, GetDedicatedServerInt(settings, L"gamemode", app.GetGameHostOption(eGameHostOption_GameType)));
app.SetGameHostOption(eGameHostOption_Structures, GetDedicatedServerBool(settings, L"generate-structures", app.GetGameHostOption(eGameHostOption_Structures) > 0) ? 1 : 0);
app.SetGameHostOption(eGameHostOption_BonusChest, GetDedicatedServerBool(settings, L"bonus-chest", app.GetGameHostOption(eGameHostOption_BonusChest) > 0) ? 1 : 0);
app.SetGameHostOption(eGameHostOption_PvP, GetDedicatedServerBool(settings, L"pvp", app.GetGameHostOption(eGameHostOption_PvP) > 0) ? 1 : 0);
app.SetGameHostOption(eGameHostOption_TrustPlayers, GetDedicatedServerBool(settings, L"trust-players", app.GetGameHostOption(eGameHostOption_TrustPlayers) > 0) ? 1 : 0);
app.SetGameHostOption(eGameHostOption_FireSpreads, GetDedicatedServerBool(settings, L"fire-spreads", app.GetGameHostOption(eGameHostOption_FireSpreads) > 0) ? 1 : 0);
app.SetGameHostOption(eGameHostOption_TNT, GetDedicatedServerBool(settings, L"tnt", app.GetGameHostOption(eGameHostOption_TNT) > 0) ? 1 : 0);
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('<27>', '$');
setAnimals(GetDedicatedServerBool(settings, L"spawn-animals", true));
setNpcsEnabled(GetDedicatedServerBool(settings, L"spawn-npcs", true));
setPvpAllowed(app.GetGameHostOption( eGameHostOption_PvP )>0?true:false);
// 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(GetDedicatedServerBool(settings, L"allow-flight", true));
// 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));
#ifdef _WINDOWS64
{
int maxP = getPlayerList()->getMaxPlayers();
WinsockNetLayer::UpdateAdvertiseMaxPlayers((BYTE)(maxP > 255 ? 255 : maxP));
}
#endif
// 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_t levelNanoTime = System::nanoTime();
wstring levelName = (initData && !initData->levelName.empty()) ? initData->levelName : GetDedicatedServerString(settings, 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 = GetDedicatedServerString(settings, L"level-type", L"flat");
}
else
{
levelTypeString = GetDedicatedServerString(settings, 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(GetDedicatedServerInt(settings, 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_t levelSeed = (new Random())->nextLong();
if (levelSeedString.length() > 0)
{
long newSeed = _fromString<int64_t>(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_t levelSeed, LevelType *pLevelType, NetworkGameInitData *initData)
{
// 4J - TODO - do with new save stuff
// if (storageSource->requiresConversion(name))
// {
// assert(false);
// }
ProgressRenderer *mcprogress = Minecraft::GetInstance()->progressRenderer;
// 4J TODO - free levels here if there are already some?
levels = ServerLevelArray(3);
int gameTypeId = GetDedicatedServerInt(settings, 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(GetDedicatedServerBool(settings, L"spawn-monsters", true), animals);
#endif
levels[i]->getLevelData()->setGameType(gameType);
if(app.getLevelGenerationOptions() != NULL)
{
LevelGenerationOptions *mapOptions = app.getLevelGenerationOptions();
levels[i]->getLevelData()->setHasBeenInCreative(mapOptions->getLevelHasBeenInCreative() );
}
players->setLevel(levels);
}
if( levels[0]->isNew )
{
mcprogress->progressStage(IDS_PROGRESS_GENERATING_SPAWN_AREA);
}
else
{
mcprogress->progressStage(IDS_PROGRESS_LOADING_SPAWN_AREA);
}
app.SetGameHostOption( eGameHostOption_HasBeenInCreative, gameType == GameType::CREATIVE || levels[0]->getHasBeenInCreative() );
app.SetGameHostOption( eGameHostOption_Structures, levels[0]->isGenerateMapFeatures() );
if( s_bServerHalted || !g_NetworkManager.IsInSession() ) return false;
// 4J - Make a new thread to do post processing
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_t 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_t lastTime = System::currentTimeMillis();
#ifdef _LARGE_WORLDS
if(app.GetGameNewWorldSize() > levels[0]->getLevelData()->getXZSizeOld())
{
if(!app.GetGameNewWorldSizeUseMoat()) // check the moat settings to see if we should be overwriting the edge tiles
{
overwriteBordersForNewWorldSize(levels[0]);
}
// we're always overwriting hell edges
int oldHellSize = levels[0]->getLevelData()->getXZHellSizeOld();
overwriteHellBordersForNewWorldSize(levels[1], oldHellSize);
}
#endif
// 4J Stu - This loop is changed in 1.0.1 to only process the first level (ie the overworld), but I think we still want to do them all
int i = 0;
for (int i = 0; i < levels.length ; i++)
{
// logger.info("Preparing start region for level " + i);
if (i == 0 || GetDedicatedServerBool(settings, L"allow-nether", true))
{
ServerLevel *level = levels[i];
if(levelChunksNeedConverted)
{
// storage->getSaveFile()->convertLevelChunks(level)
}
#if 0
int64_t 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_t lastStorageTickTime = System::currentTimeMillis();
Pos *spawnPos = level->getSharedSpawnPos();
int twoRPlusOne = r*2 + 1;
int total = twoRPlusOne * twoRPlusOne;
for (int x = -r; x <= r && running; x += 16)
{
for (int z = -r; z <= r && running; z += 16)
{
if( s_bServerHalted || !g_NetworkManager.IsInSession() )
{
delete spawnPos;
m_postUpdateTerminate = true;
postProcessTerminate(mcprogress);
return false;
}
// printf(">>>%d %d %d\n",i,x,z);
// int64_t now = System::currentTimeMillis();
// if (now < lastTime) lastTime = now;
// if (now > lastTime + 1000)
{
int pos = (x + r) * twoRPlusOne + (z + 1);
// setProgress(L"Preparing spawn area", (pos) * 100 / total);
mcprogress->progressStagePercentage((pos+r) * 100 / total);
// lastTime = now;
}
static int count = 0;
PIXBeginNamedEvent(0,"Creating %d ", (count++)%8);
level->cache->create((spawnPos->x + x) >> 4, (spawnPos->z + z) >> 4, true); // 4J - added parameter to disable postprocessing here
PIXEndNamedEvent();
// while (level->updateLights() && running)
// ;
if( System::currentTimeMillis() - lastStorageTickTime > 50 )
{
CompressedTileStorage::tick();
SparseLightStorage::tick();
SparseDataStorage::tick();
lastStorageTickTime = System::currentTimeMillis();
}
}
}
// 4J - removed this as now doing the recheckGaps call when each chunk is post-processed, so can happen on things outside of the spawn area too
#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 )
{
#ifndef _WINDOWS64
// On Windows64 we skip the automatic initial save so that choosing
// "Exit without saving" on a new world does not leave an orphaned save folder.
levels[0]->saveToDisc(mcprogress, false);
#endif
}
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;
}
#ifdef _LARGE_WORLDS
void MinecraftServer::overwriteBordersForNewWorldSize(ServerLevel* level)
{
// recreate the chunks round the border (2 chunks or 32 blocks deep), deleting any player data from them
app.DebugPrintf("Expanding level size\n");
int oldSize = level->getLevelData()->getXZSizeOld();
// top
int minVal = -oldSize/2;
int maxVal = (oldSize/2)-1;
for(int xVal = minVal; xVal <= maxVal; xVal++)
{
int zVal = minVal;
level->cache->overwriteLevelChunkFromSource(xVal, zVal);
level->cache->overwriteLevelChunkFromSource(xVal, zVal+1);
}
// bottom
for(int xVal = minVal; xVal <= maxVal; xVal++)
{
int zVal = maxVal;
level->cache->overwriteLevelChunkFromSource(xVal, zVal);
level->cache->overwriteLevelChunkFromSource(xVal, zVal-1);
}
// left
for(int zVal = minVal; zVal <= maxVal; zVal++)
{
int xVal = minVal;
level->cache->overwriteLevelChunkFromSource(xVal, zVal);
level->cache->overwriteLevelChunkFromSource(xVal+1, zVal);
}
// right
for(int zVal = minVal; zVal <= maxVal; zVal++)
{
int xVal = maxVal;
level->cache->overwriteLevelChunkFromSource(xVal, zVal);
level->cache->overwriteLevelChunkFromSource(xVal-1, zVal);
}
}
void MinecraftServer::overwriteHellBordersForNewWorldSize(ServerLevel* level, int oldHellSize)
{
// recreate the chunks round the border (1 chunk or 16 blocks deep), deleting any player data from them
app.DebugPrintf("Expanding level size\n");
// top
int minVal = -oldHellSize/2;
int maxVal = (oldHellSize/2)-1;
for(int xVal = minVal; xVal <= maxVal; xVal++)
{
int zVal = minVal;
level->cache->overwriteHellLevelChunkFromSource(xVal, zVal, minVal, maxVal);
}
// bottom
for(int xVal = minVal; xVal <= maxVal; xVal++)
{
int zVal = maxVal;
level->cache->overwriteHellLevelChunkFromSource(xVal, zVal, minVal, maxVal);
}
// left
for(int zVal = minVal; zVal <= maxVal; zVal++)
{
int xVal = minVal;
level->cache->overwriteHellLevelChunkFromSource(xVal, zVal, minVal, maxVal);
}
// right
for(int zVal = minVal; zVal <= maxVal; zVal++)
{
int xVal = maxVal;
level->cache->overwriteHellLevelChunkFromSource(xVal, zVal, minVal, maxVal);
}
}
#endif
void MinecraftServer::setProgress(const 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(bool didInit)
{
// 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. Also don't save anything if we didn't actually get through the server initialisation.
if(m_saveOnExit && ProfileManager.IsFullVersion() && (!StorageManager.GetSaveDisabled()) && didInit)
{
if (players != 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::isCommandBlockEnabled()
{
return false; //settings.getBoolean("enable-command-block", false);
}
bool MinecraftServer::isNetherEnabled()
{
return true; //settings.getBoolean("allow-nether", true);
}
bool MinecraftServer::isHardcore()
{
return false;
}
int MinecraftServer::getOperatorUserPermissionLevel()
{
return Command::LEVEL_OWNERS; //settings.getInt("op-permission-level", Command.LEVEL_OWNERS);
}
CommandDispatcher *MinecraftServer::getCommandDispatcher()
{
return commandDispatcher;
}
Pos *MinecraftServer::getCommandSenderWorldPosition()
{
return new Pos(0, 0, 0);
}
Level *MinecraftServer::getCommandSenderWorld()
{
return levels[0];
}
int MinecraftServer::getSpawnProtectionRadius()
{
return 16;
}
bool MinecraftServer::isUnderSpawnProtection(Level *level, int x, int y, int z, shared_ptr<Player> player)
{
if (level->dimension->id != 0) return false;
//if (getPlayers()->getOps()->empty()) return false;
if (getPlayers()->isOp(player->getName())) return false;
if (getSpawnProtectionRadius() <= 0) return false;
Pos *spawnPos = level->getSharedSpawnPos();
int xd = Mth::abs(x - spawnPos->x);
int zd = Mth::abs(z - spawnPos->z);
int dist = max(xd, zd);
return dist <= getSpawnProtectionRadius();
}
void MinecraftServer::setForceGameType(bool forceGameType)
{
this->forceGameType = forceGameType;
}
bool MinecraftServer::getForceGameType()
{
return forceGameType;
}
int64_t MinecraftServer::getCurrentTimeMillis()
{
return System::currentTimeMillis();
}
int MinecraftServer::getPlayerIdleTimeout()
{
return playerIdleTimeout;
}
void MinecraftServer::setPlayerIdleTimeout(int playerIdleTimeout)
{
this->playerIdleTimeout = playerIdleTimeout;
}
extern int c0a, c0b, c1a, c1b, c1c, c2a, c2b;
void MinecraftServer::run(int64_t seed, void *lpParameter)
{
NetworkGameInitData *initData = 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
bool didInit = false;
if (initServer(seed, initData, initSettings,findSeed))
{
didInit = true;
ServerLevel *levelNormalDimension = levels[0];
// 4J-PB - Set the Stronghold position in the leveldata if there isn't one in there
Minecraft *pMinecraft = Minecraft::GetInstance();
LevelData *pLevelData=levelNormalDimension->getLevelData();
if(pLevelData && pLevelData->getHasStronghold()==false)
{
int x,z;
if(app.GetTerrainFeaturePosition(eTerrainFeature_Stronghold,&x,&z))
{
pLevelData->setXStronghold(x);
pLevelData->setZStronghold(z);
pLevelData->setHasStronghold();
}
}
int64_t lastTime = getCurrentTimeMillis();
int64_t unprocessedTime = 0;
while (running && !s_bServerHalted)
{
int64_t now = getCurrentTimeMillis();
// 4J Stu - When we pause the server, we don't want to count that as time passed
// 4J Stu - TU-1 hotifx - Remove this line. We want to make sure that we tick connections at the proper rate when paused
//Fix for #13191 - The host of a game can get a message informing them that the connection to the server has been lost
//if(m_isServerPaused) lastTime = now;
int64_t passedTime = now - lastTime;
if (passedTime > MS_PER_TICK * 40)
{
// logger.warning("Can't keep up! Did the system time change, or is the server overloaded?");
passedTime = MS_PER_TICK * 40;
}
if (passedTime < 0)
{
// logger.warning("Time ran backwards! Did the system time change?");
passedTime = 0;
}
unprocessedTime += passedTime;
lastTime = now;
// 4J Added ability to pause the server
if( !m_isServerPaused )
{
bool didTick = false;
if (levels[0]->allPlayersAreSleeping())
{
tick();
unprocessedTime = 0;
}
else
{
// int tickcount = 0;
// int64_t beforeall = System::currentTimeMillis();
while (unprocessedTime > MS_PER_TICK)
{
unprocessedTime -= MS_PER_TICK;
chunkPacketManagement_PreTick();
// int64_t before = System::currentTimeMillis();
tick();
// int64_t after = System::currentTimeMillis();
// PIXReportCounter(L"Server time",(float)(after-before));
chunkPacketManagement_PostTick();
}
// int64_t afterall = System::currentTimeMillis();
// PIXReportCounter(L"Server time all",(float)(afterall-beforeall));
// PIXReportCounter(L"Server ticks",(float)tickcount);
}
}
else
{
// 4J Stu - TU1-hotfix
//Fix for #13191 - The host of a game can get a message informing them that the connection to the server has been lost
// The connections should tick at the same frequency even when paused
while (unprocessedTime > MS_PER_TICK)
{
unprocessedTime -= MS_PER_TICK;
// Keep ticking the connections to stop them timing out
connection->tick();
}
}
if(MinecraftServer::setTimeAtEndOfTick)
{
MinecraftServer::setTimeAtEndOfTick = false;
for (unsigned int i = 0; i < levels.length; i++)
{
// if (i == 0 || settings->getBoolean(L"allow-nether", true)) // 4J removed - we always have nether
{
ServerLevel *level = levels[i];
level->setGameTime( MinecraftServer::setTime );
}
}
}
if(MinecraftServer::setTimeOfDayAtEndOfTick)
{
MinecraftServer::setTimeOfDayAtEndOfTick = false;
for (unsigned int i = 0; i < levels.length; i++)
{
if (i == 0 || GetDedicatedServerBool(settings, L"allow-nether", true))
{
ServerLevel *level = levels[i];
level->setDayTime( 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(didInit);
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& it : ironTimers )
{
int t = it.second;
if (t > 0)
{
ironTimers[it.first] = t - 1;
}
else
{
toRemove.push_back(it.first);
}
}
for (const auto& i : toRemove)
{
ironTimers.erase(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->getGameTime(), level->getDayTime(), level->getGameRules()->getBoolean(GameRules::RULE_DAYLIGHT) ) ), level->dimension->id);
}
// #ifndef __PS3__
static int64_t stc = 0;
int64_t st0 = System::currentTimeMillis();
PIXBeginNamedEvent(0,"Level tick %d",i);
((Level *)level)->tick();
int64_t st1 = System::currentTimeMillis();
PIXEndNamedEvent();
PIXBeginNamedEvent(0,"Update lights %d",i);
int64_t st2 = System::currentTimeMillis();
PIXEndNamedEvent();
PIXBeginNamedEvent(0,"Entity tick %d",i);
// 4J added to stop ticking entities in levels when players are not in those levels.
// Note: now changed so that we also tick if there are entities to be removed, as this also happens as a result of calling tickEntities. If we don't do this, then the
// entities get removed at the first point that there is a player count in the level - this has been causing a problem when going from normal dimension -> nether -> normal,
// as the player is getting flagged as to be removed (from the normal dimension) when going to the nether, but Actually gets removed only when it returns
if( ( players->getPlayerCount(level) > 0) || ( level->hasEntitiesToRemove() ) )
{
#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_t 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)
{
EnterCriticalSection(&m_consoleInputCS);
consoleInput.push_back(new ConsoleInput(msg, source));
LeaveCriticalSection(&m_consoleInputCS);
}
void MinecraftServer::handleConsoleInputs()
{
vector<ConsoleInput *> pendingInputs;
EnterCriticalSection(&m_consoleInputCS);
pendingInputs.swap(consoleInput);
LeaveCriticalSection(&m_consoleInputCS);
for (size_t i = 0; i < pendingInputs.size(); ++i)
{
ConsoleInput *input = pendingInputs[i];
ExecuteConsoleCommand(this, input->msg);
delete input;
}
}
void MinecraftServer::main(int64_t 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)
{
PrintConsoleLine(L"[INFO] ", string);
}
void MinecraftServer::warn(const wstring& string)
{
PrintConsoleLine(L"[WARN] ", 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;
}
#if defined _ACK_CHUNK_SEND_THROTTLING
bool MinecraftServer::chunkPacketManagement_CanSendTo(INetworkPlayer *player)
{
if( s_hasSentEnoughPackets ) return false;
if( player == NULL ) return false;
for( int i = 0; i < s_sentTo.size(); i++ )
{
if( s_sentTo[i]->IsSameSystem(player) )
{
return false;
}
}
#if defined(__PS3__) || defined(__ORBIS__) || defined(__PSVITA__)
return ( player->GetOutstandingAckCount() < 3 );
#else
return ( player->GetOutstandingAckCount() < 2 );
#endif
}
void MinecraftServer::chunkPacketManagement_DidSendTo(INetworkPlayer *player)
{
int64_t currentTime = System::currentTimeMillis();
if( ( currentTime - s_tickStartTime ) >= MAX_TICK_TIME_FOR_PACKET_SENDS )
{
s_hasSentEnoughPackets = true;
// app.DebugPrintf("Sending, setting enough packet flag: %dms\n",currentTime - s_tickStartTime);
}
else
{
// app.DebugPrintf("Sending, more time: %dms\n",currentTime - s_tickStartTime);
}
player->SentChunkPacket();
s_sentTo.push_back(player);
}
void MinecraftServer::chunkPacketManagement_PreTick()
{
// app.DebugPrintf("*************************************************************************************************************************************************************************\n");
s_hasSentEnoughPackets = false;
s_tickStartTime = System::currentTimeMillis();
s_sentTo.clear();
vector< shared_ptr<PlayerConnection> > *players = connection->getPlayers();
if( players->size() )
{
vector< shared_ptr<PlayerConnection> > playersOrig = *players;
players->clear();
do
{
int longestTime = 0;
auto playerConnectionBest = playersOrig.begin();
for( auto it = playersOrig.begin(); it != playersOrig.end(); it++)
{
int thisTime = 0;
INetworkPlayer *np = (*it)->getNetworkPlayer();
if( np )
{
thisTime = np->GetTimeSinceLastChunkPacket_ms();
}
if( thisTime > longestTime )
{
playerConnectionBest = it;
longestTime = thisTime;
}
}
players->push_back(*playerConnectionBest);
playersOrig.erase(playerConnectionBest);
} while ( playersOrig.size() > 0 );
}
}
void MinecraftServer::chunkPacketManagement_PostTick()
{
}
#else
// 4J Added - round-robin chunk sends by player index. Compare vs the player at the current queue index,
// not GetSessionIndex() (smallId), so reused smallIds after many connect/disconnects still get chunk sends.
bool MinecraftServer::chunkPacketManagement_CanSendTo(INetworkPlayer *player)
{
if( player == NULL ) return false;
int time = GetTickCount();
DWORD currentPlayerCount = g_NetworkManager.GetPlayerCount();
if( currentPlayerCount == 0 ) return false;
int index = s_slowQueuePlayerIndex % (int)currentPlayerCount;
INetworkPlayer *queuePlayer = g_NetworkManager.GetPlayerByIndex( index );
if( queuePlayer != NULL && (player == queuePlayer || player->IsSameSystem(queuePlayer)) && (time - s_slowQueueLastTime) > MINECRAFT_SERVER_SLOW_QUEUE_DELAY )
{
return true;
}
return false;
}
void MinecraftServer::chunkPacketManagement_DidSendTo(INetworkPlayer *player)
{
s_slowQueuePacketSent = true;
}
void MinecraftServer::chunkPacketManagement_PreTick()
{
}
void MinecraftServer::chunkPacketManagement_PostTick()
{
// 4J Ensure that the slow queue owner keeps cycling if it's not been used in a while
int time = GetTickCount();
if( ( s_slowQueuePacketSent ) || ( (time - s_slowQueueLastTime) > ( 2 * MINECRAFT_SERVER_SLOW_QUEUE_DELAY ) ) )
{
// app.DebugPrintf("Considering cycling: (%d) %d - %d -> %d > %d\n",s_slowQueuePacketSent, time, s_slowQueueLastTime, (time - s_slowQueueLastTime), (2*MINECRAFT_SERVER_SLOW_QUEUE_DELAY));
MinecraftServer::cycleSlowQueueIndex();
s_slowQueuePacketSent = false;
s_slowQueueLastTime = time;
}
// else
// {
// app.DebugPrintf("Not considering cycling: %d - %d -> %d > %d\n",time, s_slowQueueLastTime, (time - s_slowQueueLastTime), (2*MINECRAFT_SERVER_SLOW_QUEUE_DELAY));
// }
}
void MinecraftServer::cycleSlowQueueIndex()
{
if( !g_NetworkManager.IsInSession() ) return;
int startingIndex = s_slowQueuePlayerIndex;
INetworkPlayer *currentPlayer = 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);
}
#endif
// 4J added - sets up a vector of flags to indicate which entities (with small Ids) have been removed from the level, but are still haven't constructed a network packet
// to tell a remote client about it. These small Ids shouldn't be re-used. Most of the time this method shouldn't actually do anything, in which case it will return false
// and nothing is set up.
bool MinecraftServer::flagEntitiesToBeRemoved(unsigned int *flags)
{
bool removedFound = false;
for( unsigned int i = 0; i < levels.length; i++ )
{
ServerLevel *level = levels[i];
if( level )
{
level->flagEntitiesToBeRemoved( flags, &removedFound );
}
}
return removedFound;
}