2026-03-01 12:16:08 +08:00
# include "stdafx.h"
//#include "Minecraft.h"
# include <ctime>
# include "ConsoleInput.h"
# include "DerivedServerLevel.h"
2026-03-03 03:04:10 +08:00
# include "DispenserBootstrap.h"
2026-03-01 12:16:08 +08:00
# include "EntityTracker.h"
2026-03-03 03:04:10 +08:00
# include "MinecraftServer.h"
# include "Options.h"
# include "PlayerList.h"
2026-03-01 12:16:08 +08:00
# include "ServerChunkCache.h"
2026-03-03 03:04:10 +08:00
# include "ServerConnection.h"
# include "ServerLevel.h"
2026-03-01 12:16:08 +08:00
# include "ServerLevelListener.h"
2026-03-03 03:04:10 +08:00
# include "Settings.h"
# include "..\Minecraft.World\Command.h"
2026-03-01 12:16:08 +08:00
# 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"
2026-03-04 16:18:47 +08:00
# 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"
2026-03-05 01:23:29 +08:00
# ifdef _WINDOWS64
# include "Windows64\Network\WinsockNetLayer.h"
# endif
2026-03-04 16:18:47 +08:00
# include <sstream>
2026-03-01 12:16:08 +08:00
# 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"
2026-03-03 03:04:10 +08:00
# include "PlayerConnection.h"
# ifdef _XBOX_ONE
# include "Durango\Network\NetworkPlayerDurango.h"
# endif
2026-03-01 12:16:08 +08:00
# define DEBUG_SERVER_DONT_SPAWN_MOBS 0
//4J Added
2026-03-07 21:12:22 -06:00
MinecraftServer * MinecraftServer : : server = NULL ;
2026-03-01 12:16:08 +08:00
bool MinecraftServer : : setTimeAtEndOfTick = false ;
2026-03-07 03:31:30 +07:00
int64_t MinecraftServer : : setTime = 0 ;
2026-03-01 12:16:08 +08:00
bool MinecraftServer : : setTimeOfDayAtEndOfTick = false ;
2026-03-07 03:31:30 +07:00
int64_t MinecraftServer : : setTimeOfDay = 0 ;
2026-03-01 12:16:08 +08:00
bool MinecraftServer : : m_bPrimaryPlayerSignedOut = false ;
bool MinecraftServer : : s_bServerHalted = false ;
bool MinecraftServer : : s_bSaveOnExitAnswered = false ;
2026-03-03 03:04:10 +08:00
# ifdef _ACK_CHUNK_SEND_THROTTLING
bool MinecraftServer : : s_hasSentEnoughPackets = false ;
2026-03-07 03:31:30 +07:00
int64_t MinecraftServer : : s_tickStartTime = 0 ;
2026-03-03 03:04:10 +08:00
vector < INetworkPlayer * > MinecraftServer : : s_sentTo ;
# else
2026-03-01 12:16:08 +08:00
int MinecraftServer : : s_slowQueuePlayerIndex = 0 ;
int MinecraftServer : : s_slowQueueLastTime = 0 ;
bool MinecraftServer : : s_slowQueuePacketSent = false ;
2026-03-03 03:04:10 +08:00
# endif
2026-03-01 12:16:08 +08:00
unordered_map < wstring , int > MinecraftServer : : ironTimers ;
2026-03-05 01:23:29 +08:00
static bool ShouldUseDedicatedServerProperties ( )
{
# ifdef _WINDOWS64
return g_Win64DedicatedServer ;
# else
return false ;
# endif
}
static int GetDedicatedServerInt ( Settings * settings , const wchar_t * key , int defaultValue )
{
2026-03-07 21:12:22 -06:00
return ( ShouldUseDedicatedServerProperties ( ) & & settings ! = NULL ) ? settings - > getInt ( key , defaultValue ) : defaultValue ;
2026-03-05 01:23:29 +08:00
}
static bool GetDedicatedServerBool ( Settings * settings , const wchar_t * key , bool defaultValue )
{
2026-03-07 21:12:22 -06:00
return ( ShouldUseDedicatedServerProperties ( ) & & settings ! = NULL ) ? settings - > getBoolean ( key , defaultValue ) : defaultValue ;
2026-03-05 01:23:29 +08:00
}
static wstring GetDedicatedServerString ( Settings * settings , const wchar_t * key , const wstring & defaultValue )
{
2026-03-07 21:12:22 -06:00
return ( ShouldUseDedicatedServerProperties ( ) & & settings ! = NULL ) ? settings - > getString ( key , defaultValue ) : defaultValue ;
2026-03-05 01:23:29 +08:00
}
2026-03-04 16:18:47 +08:00
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 )
{
2026-03-07 21:12:22 -06:00
if ( playerList = = NULL ) return nullptr ;
2026-03-04 16:18:47 +08:00
for ( size_t i = 0 ; i < playerList - > players . size ( ) ; + + i )
{
shared_ptr < ServerPlayer > player = playerList - > players [ i ] ;
2026-03-07 21:12:22 -06:00
if ( player ! = NULL & & equalsIgnoreCase ( player - > getName ( ) , name ) )
2026-03-04 16:18:47 +08:00
{
return player ;
}
}
return nullptr ;
}
static void SetAllLevelTimes ( MinecraftServer * server , int value )
{
for ( unsigned int i = 0 ; i < server - > levels . length ; + + i )
{
2026-03-07 21:12:22 -06:00
if ( server - > levels [ i ] ! = NULL )
2026-03-04 16:18:47 +08:00
{
server - > levels [ i ] - > setDayTime ( value ) ;
}
}
}
static bool ExecuteConsoleCommand ( MinecraftServer * server , const wstring & rawCommand )
{
2026-03-07 21:12:22 -06:00
if ( server = = NULL )
2026-03-04 16:18:47 +08:00
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 " )
{
2026-03-07 21:12:22 -06:00
wstring playerNames = ( playerList ! = NULL ) ? playerList - > getPlayerNames ( ) : L " " ;
2026-03-04 16:18:47 +08:00
if ( playerNames . empty ( ) ) playerNames = L " (none) " ;
2026-03-07 21:12:22 -06:00
server - > info ( L " Players ( " + std : : to_wstring ( ( playerList ! = NULL ) ? playerList - > getPlayerCount ( ) : 0 ) + L " ): " + playerNames ) ;
2026-03-04 16:18:47 +08:00
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 ) ;
2026-03-07 21:12:22 -06:00
if ( playerList ! = NULL )
2026-03-04 16:18:47 +08:00
{
2026-03-07 21:12:22 -06:00
playerList - > broadcastAll ( shared_ptr < ChatPacket > ( new ChatPacket ( message ) ) ) ;
2026-03-04 16:18:47 +08:00
}
server - > info ( message ) ;
return true ;
}
if ( action = = L " save-all " )
{
2026-03-07 21:12:22 -06:00
if ( playerList ! = NULL )
2026-03-04 16:18:47 +08:00
{
2026-03-07 21:12:22 -06:00
playerList - > saveAll ( NULL , false ) ;
2026-03-04 16:18:47 +08:00
}
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 )
{
2026-03-07 21:12:22 -06:00
if ( server - > levels [ i ] ! = NULL )
2026-03-04 16:18:47 +08:00
{
server - > levels [ i ] - > setDayTime ( server - > levels [ i ] - > getDayTime ( ) + delta ) ;
}
}
2026-03-06 02:11:18 +07:00
server - > info ( L " Added " + std : : to_wstring ( delta ) + L " ticks. " ) ;
2026-03-04 16:18:47 +08:00
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 ) ;
2026-03-06 02:11:18 +07:00
server - > info ( L " Time set to " + std : : to_wstring ( targetTime ) + L " . " ) ;
2026-03-04 16:18:47 +08:00
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 ;
}
2026-03-07 21:12:22 -06:00
if ( server - > levels [ 0 ] = = NULL )
2026-03-04 16:18:47 +08:00
{
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 ] ) ;
2026-03-07 21:12:22 -06:00
if ( subject = = NULL )
2026-03-04 16:18:47 +08:00
{
server - > warn ( L " Unknown player: " + tokens [ 1 ] ) ;
return false ;
}
2026-03-07 21:12:22 -06:00
if ( destination = = NULL )
2026-03-04 16:18:47 +08:00
{
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 ] ) ;
2026-03-07 21:12:22 -06:00
if ( player = = NULL )
2026-03-04 16:18:47 +08:00
{
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 ;
}
2026-03-07 21:12:22 -06:00
if ( itemId < = 0 | | Item : : items [ itemId ] = = NULL )
2026-03-04 16:18:47 +08:00
{
2026-03-06 02:11:18 +07:00
server - > warn ( L " Unknown item id: " + std : : to_wstring ( itemId ) ) ;
2026-03-04 16:18:47 +08:00
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 ) ;
2026-03-07 21:12:22 -06:00
if ( drop ! = NULL )
2026-03-04 16:18:47 +08:00
{
drop - > throwTime = 0 ;
}
2026-03-06 02:11:18 +07:00
server - > info ( L " Gave item " + std : : to_wstring ( itemId ) + L " x " + std : : to_wstring ( amount ) + L " to " + player - > getName ( ) + L " . " ) ;
2026-03-04 16:18:47 +08:00
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 ] ) ;
2026-03-07 21:12:22 -06:00
if ( player = = NULL )
2026-03-04 16:18:47 +08:00
{
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 ( ) ;
2026-03-07 21:12:22 -06:00
if ( selectedItem = = NULL )
2026-03-04 16:18:47 +08:00
{
server - > warn ( L " The player is not holding an item. " ) ;
return false ;
}
Enchantment * enchantment = Enchantment : : enchantments [ enchantmentId ] ;
2026-03-07 21:12:22 -06:00
if ( enchantment = = NULL )
2026-03-04 16:18:47 +08:00
{
2026-03-06 02:11:18 +07:00
server - > warn ( L " Unknown enchantment id: " + std : : to_wstring ( enchantmentId ) ) ;
2026-03-04 16:18:47 +08:00
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 ( ) ;
2026-03-07 21:12:22 -06:00
if ( enchantmentTags ! = NULL )
2026-03-04 16:18:47 +08:00
{
for ( int i = 0 ; i < enchantmentTags - > size ( ) ; i + + )
{
int type = enchantmentTags - > get ( i ) - > getShort ( ( wchar_t * ) ItemInstance : : TAG_ENCH_ID ) ;
2026-03-07 21:12:22 -06:00
if ( Enchantment : : enchantments [ type ] ! = NULL & & ! Enchantment : : enchantments [ type ] - > isCompatibleWith ( enchantment ) )
2026-03-04 16:18:47 +08:00
{
server - > warn ( L " That enchantment conflicts with an existing enchantment on the selected item. " ) ;
return false ;
}
}
}
}
selectedItem - > enchant ( enchantment , enchantmentLevel ) ;
2026-03-06 02:11:18 +07:00
server - > info ( L " Enchanted " + player - > getName ( ) + L " 's held item with " + std : : to_wstring ( enchantmentId ) + L " " + std : : to_wstring ( enchantmentLevel ) + L " . " ) ;
2026-03-04 16:18:47 +08:00
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 ] ) ;
2026-03-07 21:12:22 -06:00
if ( player = = NULL )
2026-03-04 16:18:47 +08:00
{
server - > warn ( L " Unknown player: " + tokens [ 1 ] ) ;
return false ;
}
player - > hurt ( DamageSource : : outOfWorld , 3.4e38 f ) ;
server - > info ( L " Killed " + player - > getName ( ) + L " . " ) ;
return true ;
}
server - > warn ( L " Unknown command: " + command ) ;
return false ;
}
2026-03-01 12:16:08 +08:00
MinecraftServer : : MinecraftServer ( )
{
// 4J - added initialisers
2026-03-07 21:12:22 -06:00
connection = NULL ;
settings = NULL ;
players = NULL ;
commands = NULL ;
2026-03-03 03:04:10 +08:00
running = true ;
2026-03-01 12:16:08 +08:00
m_bLoaded = false ;
stopped = false ;
2026-03-03 03:04:10 +08:00
tickCount = 0 ;
2026-03-01 12:16:08 +08:00
wstring progressStatus ;
2026-03-03 03:04:10 +08:00
progress = 0 ;
2026-03-01 12:16:08 +08:00
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 ;
2026-03-03 03:04:10 +08:00
playerIdleTimeout = 0 ;
2026-03-07 21:12:22 -06:00
m_postUpdateThread = NULL ;
2026-03-03 03:04:10 +08:00
forceGameType = false ;
2026-03-01 12:16:08 +08:00
commandDispatcher = new ServerCommandDispatcher ( ) ;
2026-03-04 16:18:47 +08:00
InitializeCriticalSection ( & m_consoleInputCS ) ;
2026-03-03 03:04:10 +08:00
DispenserBootstrap : : bootStrap ( ) ;
2026-03-01 12:16:08 +08:00
}
MinecraftServer : : ~ MinecraftServer ( )
{
2026-03-04 16:18:47 +08:00
DeleteCriticalSection ( & m_consoleInputCS ) ;
2026-03-01 12:16:08 +08:00
}
2026-03-07 03:31:30 +07:00
bool MinecraftServer : : initServer ( int64_t seed , NetworkGameInitData * initData , DWORD initSettings , bool findSeed )
2026-03-01 12:16:08 +08:00
{
// 4J - removed
#if 0
2026-03-03 03:04:10 +08:00
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 " ) ;
2026-03-01 12:16:08 +08:00
# endif
2026-03-03 03:04:10 +08:00
settings = new Settings ( new File ( L " server.properties " ) ) ;
2026-03-05 01:23:29 +08:00
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 ) ;
2026-03-04 16:18:47 +08:00
2026-03-03 03:04:10 +08:00
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");
2026-03-04 03:56:03 -06:00
//motd.replace('<27> ', '$');
2026-03-03 03:04:10 +08:00
2026-03-05 01:23:29 +08:00
setAnimals ( GetDedicatedServerBool ( settings , L " spawn-animals " , true ) ) ;
setNpcsEnabled ( GetDedicatedServerBool ( settings , L " spawn-npcs " , true ) ) ;
2026-03-04 16:18:47 +08:00
setPvpAllowed ( app . GetGameHostOption ( eGameHostOption_PvP ) > 0 ? true : false ) ;
2026-03-03 03:04:10 +08:00
// 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
2026-03-05 01:23:29 +08:00
setFlightAllowed ( GetDedicatedServerBool ( settings , L " allow-flight " , true ) ) ;
2026-03-03 03:04:10 +08:00
// 4J Stu - Enabling flight to stop it kicking us when we use it
2026-03-01 12:16:08 +08:00
# ifdef _DEBUG_MENUS_ENABLED
2026-03-03 03:04:10 +08:00
setFlightAllowed ( true ) ;
2026-03-01 12:16:08 +08:00
# endif
# if 1
2026-03-03 03:04:10 +08:00
connection = new ServerConnection ( this ) ;
Socket : : Initialise ( connection ) ; // 4J - added
2026-03-01 12:16:08 +08:00
# else
2026-03-03 03:04:10 +08:00
// 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. " ) ;
}
2026-03-01 12:16:08 +08:00
# endif
2026-03-03 03:04:10 +08:00
setPlayers ( new PlayerList ( this ) ) ;
2026-03-06 19:23:32 -06:00
# ifdef _WINDOWS64
{
int maxP = getPlayerList ( ) - > getMaxPlayers ( ) ;
WinsockNetLayer : : UpdateAdvertiseMaxPlayers ( ( BYTE ) ( maxP > 255 ? 255 : maxP ) ) ;
}
# endif
2026-03-01 12:16:08 +08:00
2026-03-03 03:04:10 +08:00
// 4J-JEV: Need to wait for levelGenerationOptions to load.
2026-03-07 21:12:22 -06:00
while ( app . getLevelGenerationOptions ( ) ! = NULL & & ! app . getLevelGenerationOptions ( ) - > hasLoadedData ( ) )
2026-03-03 03:04:10 +08:00
Sleep ( 1 ) ;
2026-03-01 12:16:08 +08:00
2026-03-07 21:12:22 -06:00
if ( app . getLevelGenerationOptions ( ) ! = NULL & & ! app . getLevelGenerationOptions ( ) - > ready ( ) )
2026-03-03 03:04:10 +08:00
{
// TODO: Stop loading, add error message.
}
2026-03-01 12:16:08 +08:00
2026-03-07 03:31:30 +07:00
int64_t levelNanoTime = System : : nanoTime ( ) ;
2026-03-01 12:16:08 +08:00
2026-03-05 01:23:29 +08:00
wstring levelName = ( initData & & ! initData - > levelName . empty ( ) ) ? initData - > levelName : GetDedicatedServerString ( settings , L " level-name " , L " world " ) ;
2026-03-01 12:16:08 +08:00
wstring levelTypeString ;
2026-03-03 03:04:10 +08:00
bool gameRuleUseFlatWorld = false ;
2026-03-07 21:12:22 -06:00
if ( app . getLevelGenerationOptions ( ) ! = NULL )
2026-03-03 03:04:10 +08:00
{
gameRuleUseFlatWorld = app . getLevelGenerationOptions ( ) - > getuseFlatWorld ( ) ;
}
if ( gameRuleUseFlatWorld | | app . GetGameHostOption ( eGameHostOption_LevelType ) > 0 )
{
2026-03-05 01:23:29 +08:00
levelTypeString = GetDedicatedServerString ( settings , L " level-type " , L " flat " ) ;
2026-03-03 03:04:10 +08:00
}
else
{
2026-03-05 01:23:29 +08:00
levelTypeString = GetDedicatedServerString ( settings , L " level-type " , L " default " ) ;
2026-03-03 03:04:10 +08:00
}
2026-03-01 12:16:08 +08:00
2026-03-03 03:04:10 +08:00
LevelType * pLevelType = LevelType : : getLevelType ( levelTypeString ) ;
2026-03-07 21:12:22 -06:00
if ( pLevelType = = NULL )
2026-03-03 03:04:10 +08:00
{
pLevelType = LevelType : : lvl_normal ;
}
2026-03-01 12:16:08 +08:00
2026-03-03 03:04:10 +08:00
ProgressRenderer * mcprogress = Minecraft : : GetInstance ( ) - > progressRenderer ;
mcprogress - > progressStart ( IDS_PROGRESS_INITIALISING_SERVER ) ;
2026-03-01 12:16:08 +08:00
2026-03-03 03:04:10 +08:00
if ( findSeed )
{
2026-03-01 12:16:08 +08:00
# ifdef __PSVITA__
2026-03-03 03:04:10 +08:00
seed = BiomeSource : : findSeed ( pLevelType , & running ) ;
2026-03-01 12:16:08 +08:00
# else
2026-03-03 03:04:10 +08:00
seed = BiomeSource : : findSeed ( pLevelType ) ;
2026-03-01 12:16:08 +08:00
# endif
2026-03-03 03:04:10 +08:00
}
2026-03-01 12:16:08 +08:00
2026-03-05 01:23:29 +08:00
setMaxBuildHeight ( GetDedicatedServerInt ( settings , L " max-build-height " , Level : : maxBuildHeight ) ) ;
2026-03-03 03:04:10 +08:00
setMaxBuildHeight ( ( ( getMaxBuildHeight ( ) + 8 ) / 16 ) * 16 ) ;
setMaxBuildHeight ( Mth : : clamp ( getMaxBuildHeight ( ) , 64 , Level : : maxBuildHeight ) ) ;
//settings->setProperty(L"max-build-height", maxBuildHeight);
2026-03-01 12:16:08 +08:00
#if 0
2026-03-03 03:04:10 +08:00
wstring levelSeedString = settings - > getString ( L " level-seed " , L " " ) ;
2026-03-07 03:31:30 +07:00
int64_t levelSeed = ( new Random ( ) ) - > nextLong ( ) ;
2026-03-03 03:04:10 +08:00
if ( levelSeedString . length ( ) > 0 )
{
2026-03-07 03:31:30 +07:00
long newSeed = _fromString < int64_t > ( levelSeedString ) ;
2026-03-03 03:04:10 +08:00
if ( newSeed ! = 0 ) {
levelSeed = newSeed ;
}
}
2026-03-01 12:16:08 +08:00
# endif
2026-03-03 03:04:10 +08:00
// 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 \"?\"");
2026-03-01 12:16:08 +08:00
2026-03-03 03:04:10 +08:00
// 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 ;
}
2026-03-01 12:16:08 +08:00
2026-03-03 03:04:10 +08:00
g_NetworkManager . ServerReady ( ) ; // 4J added
return m_bLoaded ;
2026-03-01 12:16:08 +08:00
}
// 4J - added - extra thread to post processing on separate thread during level creation
int MinecraftServer : : runPostUpdate ( void * lpParam )
{
ShutdownManager : : HasStarted ( ShutdownManager : : ePostProcessThread ) ;
2026-03-07 21:12:22 -06:00
MinecraftServer * server = ( MinecraftServer * ) lpParam ;
2026-03-01 12:16:08 +08:00
Entity : : useSmallIds ( ) ; // This thread can end up spawning entities as resources
IntCache : : CreateNewThreadStorage ( ) ;
AABB : : CreateNewThreadStorage ( ) ;
2026-03-06 02:11:18 +07:00
Vec3 : : CreateNewThreadStorage ( ) ;
2026-03-01 12:16:08 +08:00
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 ) ) ;
2026-03-03 03:04:10 +08:00
//#ifndef __PS3__
2026-03-01 12:16:08 +08:00
// 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 ) ;
2026-03-03 03:04:10 +08:00
//#endif //__PS3__
2026-03-01 12:16:08 +08:00
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 ;
2026-03-07 21:12:22 -06:00
m_postUpdateThread = NULL ;
2026-03-01 12:16:08 +08:00
DeleteCriticalSection ( & m_postProcessCS ) ;
}
2026-03-07 03:31:30 +07:00
bool MinecraftServer : : loadLevel ( LevelStorageSource * storageSource , const wstring & name , int64_t levelSeed , LevelType * pLevelType , NetworkGameInitData * initData )
2026-03-01 12:16:08 +08:00
{
2026-03-03 03:04:10 +08:00
// 4J - TODO - do with new save stuff
// if (storageSource->requiresConversion(name))
// {
// assert(false);
// }
2026-03-01 12:16:08 +08:00
ProgressRenderer * mcprogress = Minecraft : : GetInstance ( ) - > progressRenderer ;
// 4J TODO - free levels here if there are already some?
2026-03-03 03:04:10 +08:00
levels = ServerLevelArray ( 3 ) ;
2026-03-01 12:16:08 +08:00
2026-03-05 01:23:29 +08:00
int gameTypeId = GetDedicatedServerInt ( settings , L " gamemode " , app . GetGameHostOption ( eGameHostOption_GameType ) ) ; //LevelSettings::GAMETYPE_SURVIVAL);
2026-03-01 12:16:08 +08:00
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 ;
2026-03-07 21:12:22 -06:00
if ( initData - > saveData ! = NULL )
2026-03-01 12:16:08 +08:00
{
// We are loading a file from disk with the data passed in
2026-03-06 02:11:18 +07:00
# ifdef SPLIT_SAVES
2026-03-01 12:16:08 +08:00
ConsoleSaveFileOriginal oldFormatSave ( initData - > saveData - > saveName , initData - > saveData - > data , initData - > saveData - > fileSize , false , initData - > savePlatform ) ;
ConsoleSaveFile * pSave = new ConsoleSaveFileSplit ( & oldFormatSave ) ;
2026-03-03 03:04:10 +08:00
2026-03-01 12:16:08 +08:00
//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
2026-03-07 21:12:22 -06:00
storage = shared_ptr < McRegionLevelStorage > ( new McRegionLevelStorage ( pSave , File ( L " . " ) , name , true ) ) ;
2026-03-01 12:16:08 +08:00
}
else
{
// We are loading a save from the storage manager
# ifdef SPLIT_SAVES
bool bLevelGenBaseSave = false ;
LevelGenerationOptions * levelGen = app . getLevelGenerationOptions ( ) ;
2026-03-07 21:12:22 -06:00
if ( levelGen ! = NULL & & levelGen - > requiresBaseSave ( ) )
2026-03-01 12:16:08 +08:00
{
DWORD fileSize = 0 ;
LPVOID pvSaveData = levelGen - > getBaseSaveData ( fileSize ) ;
if ( pvSaveData & & fileSize ! = 0 ) bLevelGenBaseSave = true ;
}
2026-03-07 21:12:22 -06:00
ConsoleSaveFileSplit * newFormatSave = NULL ;
2026-03-01 12:16:08 +08:00
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
2026-03-07 21:12:22 -06:00
storage = shared_ptr < McRegionLevelStorage > ( new McRegionLevelStorage ( new ConsoleSaveFileOriginal ( L " " ) , File ( L " . " ) , name , true ) ) ;
2026-03-01 12:16:08 +08:00
# endif
}
2026-03-03 03:04:10 +08:00
// McRegionLevelStorage *storage = new McRegionLevelStorage(new ConsoleSaveFile( L"" ), L"", L"", 0); // original
2026-03-06 02:11:18 +07:00
// McRegionLevelStorage *storage = new McRegionLevelStorage(File(L"."), name, true); // TODO
2026-03-03 03:04:10 +08:00
for ( unsigned int i = 0 ; i < levels . length ; i + + )
2026-03-01 12:16:08 +08:00
{
if ( s_bServerHalted | | ! g_NetworkManager . IsInSession ( ) )
{
2026-03-03 03:04:10 +08:00
return false ;
2026-03-01 12:16:08 +08:00
}
2026-03-03 03:04:10 +08:00
// String levelName = name;
// if (i == 1) levelName += "_nether";
2026-03-01 12:16:08 +08:00
int dimension = 0 ;
if ( i = = 1 ) dimension = - 1 ;
if ( i = = 2 ) dimension = 1 ;
2026-03-03 03:04:10 +08:00
if ( i = = 0 )
2026-03-01 12:16:08 +08:00
{
levels [ i ] = new ServerLevel ( this , storage , name , dimension , levelSettings ) ;
2026-03-07 21:12:22 -06:00
if ( app . getLevelGenerationOptions ( ) ! = NULL )
2026-03-01 12:16:08 +08:00
{
LevelGenerationOptions * mapOptions = app . getLevelGenerationOptions ( ) ;
Pos * spawnPos = mapOptions - > getSpawnPos ( ) ;
2026-03-07 21:12:22 -06:00
if ( spawnPos ! = NULL )
2026-03-01 12:16:08 +08:00
{
levels [ i ] - > setSpawnPos ( spawnPos ) ;
}
levels [ i ] - > getLevelData ( ) - > setHasBeenInCreative ( mapOptions - > isFromDLC ( ) ) ;
}
}
2026-03-03 03:04:10 +08:00
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
2026-03-01 12:16:08 +08:00
// 4J Stu - We set the levels difficulty based on the minecraft options
2026-03-03 03:04:10 +08:00
//levels[i]->difficulty = settings->getBoolean(L"spawn-monsters", true) ? Difficulty::EASY : Difficulty::PEACEFUL;
2026-03-01 12:16:08 +08:00
Minecraft * pMinecraft = Minecraft : : GetInstance ( ) ;
2026-03-03 03:04:10 +08:00
// m_lastSentDifficulty = pMinecraft->options->difficulty;
2026-03-01 12:16:08 +08:00
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
2026-03-03 03:04:10 +08:00
levels [ i ] - > setSpawnSettings ( false , false ) ;
2026-03-01 12:16:08 +08:00
# else
2026-03-05 01:23:29 +08:00
levels [ i ] - > setSpawnSettings ( GetDedicatedServerBool ( settings , L " spawn-monsters " , true ) , animals ) ;
2026-03-01 12:16:08 +08:00
# endif
levels [ i ] - > getLevelData ( ) - > setGameType ( gameType ) ;
2026-03-03 03:04:10 +08:00
2026-03-07 21:12:22 -06:00
if ( app . getLevelGenerationOptions ( ) ! = NULL )
2026-03-03 03:04:10 +08:00
{
LevelGenerationOptions * mapOptions = app . getLevelGenerationOptions ( ) ;
levels [ i ] - > getLevelData ( ) - > setHasBeenInCreative ( mapOptions - > getLevelHasBeenInCreative ( ) ) ;
}
players - > setLevel ( levels ) ;
}
2026-03-01 12:16:08 +08:00
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 ( ) ;
2026-03-07 03:31:30 +07:00
int64_t startTime = System : : currentTimeMillis ( ) ;
2026-03-01 12:16:08 +08:00
// 4J Stu - Added this to temporarily make starting games on vita faster
# ifdef __PSVITA__
int r = 48 ;
# else
2026-03-06 02:11:18 +07:00
int r = 196 ;
2026-03-01 12:16:08 +08:00
# 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 ] ;
2026-03-07 21:12:22 -06:00
csf - > setFilePointer ( fe , 0 , NULL , FILE_BEGIN ) ;
2026-03-01 12:16:08 +08:00
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 ) ;
}
2026-03-07 03:31:30 +07:00
int64_t lastTime = System : : currentTimeMillis ( ) ;
2026-03-03 03:04:10 +08:00
# 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
2026-03-01 12:16:08 +08:00
// 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 ;
2026-03-03 03:04:10 +08:00
for ( int i = 0 ; i < levels . length ; i + + )
2026-03-01 12:16:08 +08:00
{
2026-03-03 03:04:10 +08:00
// logger.info("Preparing start region for level " + i);
2026-03-05 01:23:29 +08:00
if ( i = = 0 | | GetDedicatedServerBool ( settings , L " allow-nether " , true ) )
2026-03-01 12:16:08 +08:00
{
2026-03-03 03:04:10 +08:00
ServerLevel * level = levels [ i ] ;
2026-03-01 12:16:08 +08:00
if ( levelChunksNeedConverted )
{
2026-03-03 03:04:10 +08:00
// storage->getSaveFile()->convertLevelChunks(level)
2026-03-01 12:16:08 +08:00
}
#if 0
2026-03-07 03:31:30 +07:00
int64_t lastStorageTickTime = System : : currentTimeMillis ( ) ;
2026-03-01 12:16:08 +08:00
// 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
2026-03-07 03:31:30 +07:00
int64_t lastStorageTickTime = System : : currentTimeMillis ( ) ;
2026-03-03 03:04:10 +08:00
Pos * spawnPos = level - > getSharedSpawnPos ( ) ;
2026-03-01 12:16:08 +08:00
int twoRPlusOne = r * 2 + 1 ;
int total = twoRPlusOne * twoRPlusOne ;
2026-03-03 03:04:10 +08:00
for ( int x = - r ; x < = r & & running ; x + = 16 )
2026-03-01 12:16:08 +08:00
{
2026-03-03 03:04:10 +08:00
for ( int z = - r ; z < = r & & running ; z + = 16 )
2026-03-01 12:16:08 +08:00
{
if ( s_bServerHalted | | ! g_NetworkManager . IsInSession ( ) )
{
delete spawnPos ;
m_postUpdateTerminate = true ;
postProcessTerminate ( mcprogress ) ;
return false ;
}
2026-03-03 03:04:10 +08:00
// printf(">>>%d %d %d\n",i,x,z);
2026-03-07 03:31:30 +07:00
// int64_t now = System::currentTimeMillis();
2026-03-03 03:04:10 +08:00
// if (now < lastTime) lastTime = now;
// if (now > lastTime + 1000)
2026-03-01 12:16:08 +08:00
{
2026-03-03 03:04:10 +08:00
int pos = ( x + r ) * twoRPlusOne + ( z + 1 ) ;
// setProgress(L"Preparing spawn area", (pos) * 100 / total);
2026-03-01 12:16:08 +08:00
mcprogress - > progressStagePercentage ( ( pos + r ) * 100 / total ) ;
2026-03-03 03:04:10 +08:00
// lastTime = now;
}
2026-03-01 12:16:08 +08:00
static int count = 0 ;
PIXBeginNamedEvent ( 0 , " Creating %d " , ( count + + ) % 8 ) ;
2026-03-03 03:04:10 +08:00
level - > cache - > create ( ( spawnPos - > x + x ) > > 4 , ( spawnPos - > z + z ) > > 4 , true ) ; // 4J - added parameter to disable postprocessing here
2026-03-01 12:16:08 +08:00
PIXEndNamedEvent ( ) ;
2026-03-03 03:04:10 +08:00
// while (level->updateLights() && running)
// ;
2026-03-01 12:16:08 +08:00
if ( System : : currentTimeMillis ( ) - lastStorageTickTime > 50 )
{
CompressedTileStorage : : tick ( ) ;
SparseLightStorage : : tick ( ) ;
SparseDataStorage : : tick ( ) ;
lastStorageTickTime = System : : currentTimeMillis ( ) ;
}
2026-03-03 03:04:10 +08:00
}
}
2026-03-01 12:16:08 +08:00
// 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
2026-03-03 03:04:10 +08:00
for ( int x = - r ; x < = r & & running ; x + = 16 )
2026-03-01 12:16:08 +08:00
{
2026-03-03 03:04:10 +08:00
for ( int z = - r ; z < = r & & running ; z + = 16 )
2026-03-01 12:16:08 +08:00
{
PIXBeginNamedEvent ( 0 , " Lighting gaps for %d %d " , x , z ) ;
level - > getChunkAt ( spawnPos - > x + x , spawnPos - > z + z ) - > recheckGaps ( true ) ;
PIXEndNamedEvent ( ) ;
}
}
# endif
delete spawnPos ;
# endif
2026-03-03 03:04:10 +08:00
}
}
// printf("Main thread complete at %dms\n",System::currentTimeMillis() - startTime);
2026-03-01 12:16:08 +08:00
// 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 ( ) )
{
2026-03-06 02:11:18 +07:00
int x , z ;
2026-03-01 12:16:08 +08:00
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 " ) ;
}
2026-03-03 03:04:10 +08:00
// printf("Post processing complete at %dms\n",System::currentTimeMillis() - startTime);
2026-03-01 12:16:08 +08:00
2026-03-03 03:04:10 +08:00
// printf("Lighting complete at %dms\n",System::currentTimeMillis() - startTime);
2026-03-01 12:16:08 +08:00
if ( s_bServerHalted | | ! g_NetworkManager . IsInSession ( ) ) return false ;
2026-03-03 03:04:10 +08:00
2026-03-01 12:16:08 +08:00
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 ( ) ;
2026-03-03 03:04:10 +08:00
2026-03-01 12:16:08 +08:00
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 )
{
2026-03-05 20:57:37 +00:00
# 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.
2026-03-01 12:16:08 +08:00
levels [ 0 ] - > saveToDisc ( mcprogress , false ) ;
2026-03-05 20:57:37 +00:00
# endif
2026-03-01 12:16:08 +08:00
}
if ( s_bServerHalted | | ! g_NetworkManager . IsInSession ( ) ) return false ;
2026-03-03 03:04:10 +08:00
/*
* 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 ( ) ;
2026-03-01 12:16:08 +08:00
return true ;
}
2026-03-03 03:04:10 +08:00
# 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
2026-03-01 12:16:08 +08:00
void MinecraftServer : : setProgress ( const wstring & status , int progress )
{
2026-03-03 03:04:10 +08:00
progressStatus = status ;
this - > progress = progress ;
// logger.info(status + ": " + progress + "%");
2026-03-01 12:16:08 +08:00
}
void MinecraftServer : : endProgress ( )
{
2026-03-03 03:04:10 +08:00
progressStatus = L " " ;
this - > progress = 0 ;
2026-03-01 12:16:08 +08:00
}
void MinecraftServer : : saveAllChunks ( )
{
2026-03-03 03:04:10 +08:00
// logger.info("Saving chunks");
for ( unsigned int i = 0 ; i < levels . length ; i + + )
2026-03-01 12:16:08 +08:00
{
// 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.
2026-03-03 03:04:10 +08:00
ServerLevel * level = levels [ levels . length - 1 - i ] ;
2026-03-07 21:12:22 -06:00
if ( level ) // 4J - added check as level can be NULL if we end up in stopServer really early on due to network failure
2026-03-01 12:16:08 +08:00
{
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 ( ) ;
}
}
2026-03-03 03:04:10 +08:00
}
2026-03-01 12:16:08 +08:00
}
// 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 ;
2026-03-07 21:12:22 -06:00
ba . data = NULL ;
2026-03-01 12:16:08 +08:00
app . m_gameRules . saveGameRules ( & ba . data , & ba . length ) ;
2026-03-07 21:12:22 -06:00
if ( ba . data ! = NULL )
2026-03-01 12:16:08 +08:00
{
ConsoleSaveFile * csf = getLevel ( 0 ) - > getLevelStorage ( ) - > getSaveFile ( ) ;
FileEntry * fe = csf - > createFile ( ConsoleSavePath ( GAME_RULE_SAVENAME ) ) ;
2026-03-07 21:12:22 -06:00
csf - > setFilePointer ( fe , 0 , NULL , FILE_BEGIN ) ;
2026-03-01 12:16:08 +08:00
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 ) ;
2026-03-07 21:12:22 -06:00
float fSecsPerTick = 1.0f / ( float ) qwTicksPerSec . QuadPart ;
2026-03-01 12:16:08 +08:00
// Save the start time
QueryPerformanceCounter ( & qwTime ) ;
if ( m_bLoaded & & ProfileManager . IsFullVersion ( ) & & ( ! StorageManager . GetSaveDisabled ( ) ) )
2026-03-06 02:11:18 +07:00
{
2026-03-07 21:12:22 -06:00
if ( players ! = NULL )
2026-03-01 12:16:08 +08:00
{
2026-03-07 21:12:22 -06:00
players - > saveAll ( NULL ) ;
2026-03-01 12:16:08 +08:00
}
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 ( ) ;
2026-03-07 21:12:22 -06:00
levels [ 0 ] - > saveToDisc ( NULL , true ) ;
2026-03-01 12:16:08 +08:00
}
}
QueryPerformanceCounter ( & qwNewTime ) ;
qwDeltaTime . QuadPart = qwNewTime . QuadPart - qwTime . QuadPart ;
2026-03-07 21:12:22 -06:00
fElapsedTime = fSecsPerTick * ( ( FLOAT ) ( qwDeltaTime . QuadPart ) ) ;
2026-03-01 12:16:08 +08:00
// 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 ;
}
2026-03-03 03:04:10 +08:00
void MinecraftServer : : stopServer ( bool didInit )
2026-03-01 12:16:08 +08:00
{
// 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 " ) ;
2026-03-03 03:04:10 +08:00
// logger.info("Stopping server");
2026-03-01 12:16:08 +08:00
// 4J-PB - If the primary player has signed out, then don't attempt to save anything
2026-03-03 03:04:10 +08:00
2026-03-01 12:16:08 +08:00
// 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
2026-03-03 03:04:10 +08:00
// 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 )
2026-03-06 02:11:18 +07:00
{
2026-03-07 21:12:22 -06:00
if ( players ! = NULL )
2026-03-01 12:16:08 +08:00
{
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];
2026-03-07 21:12:22 -06:00
// if (level != NULL)
2026-03-01 12:16:08 +08:00
// {
2026-03-03 03:04:10 +08:00
saveAllChunks ( ) ;
2026-03-01 12:16:08 +08:00
// }
//}
saveGameRules ( ) ;
app . m_gameRules . unloadCurrentGameRules ( ) ;
2026-03-07 21:12:22 -06:00
if ( levels [ 0 ] ! = NULL ) // This can be null if stopServer happens very quickly due to network error
2026-03-01 12:16:08 +08:00
{
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 + + )
{
2026-03-07 21:12:22 -06:00
if ( levels [ i ] ! = NULL )
2026-03-01 12:16:08 +08:00
{
delete levels [ i ] ;
2026-03-07 21:12:22 -06:00
levels [ i ] = NULL ;
2026-03-01 12:16:08 +08:00
}
}
# 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 ;
2026-03-07 21:12:22 -06:00
connection = NULL ;
2026-03-01 12:16:08 +08:00
delete players ;
2026-03-07 21:12:22 -06:00
players = NULL ;
2026-03-01 12:16:08 +08:00
delete settings ;
2026-03-07 21:12:22 -06:00
settings = NULL ;
2026-03-01 12:16:08 +08:00
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 ;
}
2026-03-03 03:04:10 +08:00
bool MinecraftServer : : isCommandBlockEnabled ( )
{
return false ; //settings.getBoolean("enable-command-block", false);
}
2026-03-01 12:16:08 +08:00
bool MinecraftServer : : isNetherEnabled ( )
{
return true ; //settings.getBoolean("allow-nether", true);
}
bool MinecraftServer : : isHardcore ( )
{
return false ;
}
2026-03-03 03:04:10 +08:00
int MinecraftServer : : getOperatorUserPermissionLevel ( )
{
return Command : : LEVEL_OWNERS ; //settings.getInt("op-permission-level", Command.LEVEL_OWNERS);
}
2026-03-01 12:16:08 +08:00
CommandDispatcher * MinecraftServer : : getCommandDispatcher ( )
{
return commandDispatcher ;
}
2026-03-03 03:04:10 +08:00
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 ;
}
2026-03-07 03:31:30 +07:00
int64_t MinecraftServer : : getCurrentTimeMillis ( )
2026-03-03 03:04:10 +08:00
{
return System : : currentTimeMillis ( ) ;
}
int MinecraftServer : : getPlayerIdleTimeout ( )
{
return playerIdleTimeout ;
}
void MinecraftServer : : setPlayerIdleTimeout ( int playerIdleTimeout )
{
this - > playerIdleTimeout = playerIdleTimeout ;
}
2026-03-01 12:16:08 +08:00
extern int c0a , c0b , c1a , c1b , c1c , c2a , c2b ;
2026-03-07 03:31:30 +07:00
void MinecraftServer : : run ( int64_t seed , void * lpParameter )
2026-03-01 12:16:08 +08:00
{
2026-03-07 21:12:22 -06:00
NetworkGameInitData * initData = NULL ;
2026-03-01 12:16:08 +08:00
DWORD initSettings = 0 ;
bool findSeed = false ;
2026-03-07 21:12:22 -06:00
if ( lpParameter ! = NULL )
2026-03-01 12:16:08 +08:00
{
2026-03-07 21:12:22 -06:00
initData = ( NetworkGameInitData * ) lpParameter ;
2026-03-01 12:16:08 +08:00
initSettings = app . GetGameHostOption ( eGameHostOption_All ) ;
findSeed = initData - > findSeed ;
m_texturePackId = initData - > texturePackId ;
}
2026-03-03 03:04:10 +08:00
// try { // 4J - removed try/catch/finally
bool didInit = false ;
if ( initServer ( seed , initData , initSettings , findSeed ) )
2026-03-01 12:16:08 +08:00
{
2026-03-03 03:04:10 +08:00
didInit = true ;
2026-03-01 12:16:08 +08:00
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 )
{
2026-03-06 02:11:18 +07:00
int x , z ;
2026-03-01 12:16:08 +08:00
if ( app . GetTerrainFeaturePosition ( eTerrainFeature_Stronghold , & x , & z ) )
{
pLevelData - > setXStronghold ( x ) ;
pLevelData - > setZStronghold ( z ) ;
pLevelData - > setHasStronghold ( ) ;
}
}
2026-03-07 03:31:30 +07:00
int64_t lastTime = getCurrentTimeMillis ( ) ;
int64_t unprocessedTime = 0 ;
2026-03-03 03:04:10 +08:00
while ( running & & ! s_bServerHalted )
2026-03-01 12:16:08 +08:00
{
2026-03-07 03:31:30 +07:00
int64_t now = getCurrentTimeMillis ( ) ;
2026-03-01 12:16:08 +08:00
// 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;
2026-03-07 03:31:30 +07:00
int64_t passedTime = now - lastTime ;
2026-03-03 03:04:10 +08:00
if ( passedTime > MS_PER_TICK * 40 )
2026-03-01 12:16:08 +08:00
{
2026-03-03 03:04:10 +08:00
// logger.warning("Can't keep up! Did the system time change, or is the server overloaded?");
passedTime = MS_PER_TICK * 40 ;
}
if ( passedTime < 0 )
2026-03-01 12:16:08 +08:00
{
2026-03-03 03:04:10 +08:00
// logger.warning("Time ran backwards! Did the system time change?");
passedTime = 0 ;
}
unprocessedTime + = passedTime ;
lastTime = now ;
2026-03-01 12:16:08 +08:00
// 4J Added ability to pause the server
if ( ! m_isServerPaused )
{
bool didTick = false ;
if ( levels [ 0 ] - > allPlayersAreSleeping ( ) )
{
tick ( ) ;
unprocessedTime = 0 ;
}
else
{
2026-03-03 03:04:10 +08:00
// int tickcount = 0;
2026-03-07 03:31:30 +07:00
// int64_t beforeall = System::currentTimeMillis();
2026-03-01 12:16:08 +08:00
while ( unprocessedTime > MS_PER_TICK )
{
unprocessedTime - = MS_PER_TICK ;
2026-03-03 03:04:10 +08:00
chunkPacketManagement_PreTick ( ) ;
2026-03-07 03:31:30 +07:00
// int64_t before = System::currentTimeMillis();
2026-03-01 12:16:08 +08:00
tick ( ) ;
2026-03-07 03:31:30 +07:00
// int64_t after = System::currentTimeMillis();
2026-03-01 12:16:08 +08:00
// PIXReportCounter(L"Server time",(float)(after-before));
2026-03-03 03:04:10 +08:00
chunkPacketManagement_PostTick ( ) ;
2026-03-01 12:16:08 +08:00
}
2026-03-07 03:31:30 +07:00
// int64_t afterall = System::currentTimeMillis();
2026-03-01 12:16:08 +08:00
// 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 ;
2026-03-03 03:04:10 +08:00
for ( unsigned int i = 0 ; i < levels . length ; i + + )
2026-03-01 12:16:08 +08:00
{
2026-03-03 03:04:10 +08:00
// if (i == 0 || settings->getBoolean(L"allow-nether", true)) // 4J removed - we always have nether
2026-03-01 12:16:08 +08:00
{
ServerLevel * level = levels [ i ] ;
2026-03-03 03:04:10 +08:00
level - > setGameTime ( MinecraftServer : : setTime ) ;
2026-03-01 12:16:08 +08:00
}
2026-03-03 03:04:10 +08:00
}
2026-03-01 12:16:08 +08:00
}
if ( MinecraftServer : : setTimeOfDayAtEndOfTick )
{
MinecraftServer : : setTimeOfDayAtEndOfTick = false ;
2026-03-03 03:04:10 +08:00
for ( unsigned int i = 0 ; i < levels . length ; i + + )
2026-03-01 12:16:08 +08:00
{
2026-03-05 01:23:29 +08:00
if ( i = = 0 | | GetDedicatedServerBool ( settings , L " allow-nether " , true ) )
2026-03-01 12:16:08 +08:00
{
ServerLevel * level = levels [ i ] ;
2026-03-03 03:04:10 +08:00
level - > setDayTime ( MinecraftServer : : setTimeOfDay ) ;
2026-03-01 12:16:08 +08:00
}
2026-03-03 03:04:10 +08:00
}
2026-03-01 12:16:08 +08:00
}
2026-03-06 02:11:18 +07:00
// Process delayed actions
2026-03-01 12:16:08 +08:00
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 ) ;
2026-03-07 21:12:22 -06:00
if ( players ! = NULL )
2026-03-01 12:16:08 +08:00
{
2026-03-07 21:12:22 -06:00
players - > saveAll ( NULL ) ;
2026-03-01 12:16:08 +08:00
}
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 ) ;
2026-03-07 21:12:22 -06:00
level - > save ( false , NULL , true ) ;
2026-03-01 12:16:08 +08:00
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 ( ) ;
2026-03-07 21:12:22 -06:00
if ( players ! = NULL )
2026-03-01 12:16:08 +08:00
{
players - > saveAll ( Minecraft : : GetInstance ( ) - > progressRenderer ) ;
}
2026-03-03 03:04:10 +08:00
2026-03-07 21:12:22 -06:00
players - > broadcastAll ( shared_ptr < UpdateProgressPacket > ( new UpdateProgressPacket ( 20 ) ) ) ;
2026-03-01 12:16:08 +08:00
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 ) ) ;
2026-03-03 03:04:10 +08:00
2026-03-07 21:12:22 -06:00
players - > broadcastAll ( shared_ptr < UpdateProgressPacket > ( new UpdateProgressPacket ( 33 + ( j * 33 ) ) ) ) ;
2026-03-01 12:16:08 +08:00
}
if ( ! s_bServerHalted )
{
saveGameRules ( ) ;
2026-03-06 02:11:18 +07:00
levels [ 0 ] - > saveToDisc ( Minecraft : : GetInstance ( ) - > progressRenderer , ( eAction = = eXuiServerAction_AutoSaveGame ) ) ;
2026-03-01 12:16:08 +08:00
}
app . LeaveSaveNotificationSection ( ) ;
break ;
case eXuiServerAction_DropItem :
// Find the player, and drop the id at their feet
{
2026-03-03 03:04:10 +08:00
shared_ptr < ServerPlayer > player = players - > players . at ( 0 ) ;
size_t id = ( size_t ) param ;
2026-03-07 21:12:22 -06:00
player - > drop ( shared_ptr < ItemInstance > ( new ItemInstance ( id , 1 , 0 ) ) ) ;
2026-03-01 12:16:08 +08:00
}
break ;
case eXuiServerAction_SpawnMob :
{
2026-03-03 03:04:10 +08:00
shared_ptr < ServerPlayer > player = players - > players . at ( 0 ) ;
2026-03-07 21:12:22 -06:00
eINSTANCEOF factory = ( eINSTANCEOF ) ( ( size_t ) param ) ;
2026-03-03 03:04:10 +08:00
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 ) ;
2026-03-01 12:16:08 +08:00
}
break ;
case eXuiServerAction_PauseServer :
m_isServerPaused = ( ( size_t ) param = = TRUE ) ;
if ( m_isServerPaused )
2026-03-06 02:11:18 +07:00
{
2026-03-01 12:16:08 +08:00
m_serverPausedEvent - > Set ( ) ;
}
break ;
case eXuiServerAction_ToggleRain :
2026-03-06 02:11:18 +07:00
{
2026-03-01 12:16:08 +08:00
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 :
2026-03-06 02:11:18 +07:00
{
2026-03-01 12:16:08 +08:00
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 :
2026-03-07 21:12:22 -06:00
players - > broadcastAll ( shared_ptr < ServerSettingsChangedPacket > ( new ServerSettingsChangedPacket ( ServerSettingsChangedPacket : : HOST_OPTIONS , app . GetGameHostOption ( eGameHostOption_Gamertags ) ) ) ) ;
2026-03-01 12:16:08 +08:00
break ;
case eXuiServerAction_ServerSettingChanged_BedrockFog :
2026-03-07 21:12:22 -06:00
players - > broadcastAll ( shared_ptr < ServerSettingsChangedPacket > ( new ServerSettingsChangedPacket ( ServerSettingsChangedPacket : : HOST_IN_GAME_SETTINGS , app . GetGameHostOption ( eGameHostOption_All ) ) ) ) ;
2026-03-01 12:16:08 +08:00
break ;
case eXuiServerAction_ServerSettingChanged_Difficulty :
2026-03-07 21:12:22 -06:00
players - > broadcastAll ( shared_ptr < ServerSettingsChangedPacket > ( new ServerSettingsChangedPacket ( ServerSettingsChangedPacket : : HOST_DIFFICULTY , Minecraft : : GetInstance ( ) - > options - > difficulty ) ) ) ;
2026-03-01 12:16:08 +08:00
break ;
case eXuiServerAction_ExportSchematic :
# ifndef _CONTENT_PACKAGE
app . EnterSaveNotificationSection ( ) ;
2026-03-03 03:04:10 +08:00
2026-03-01 12:16:08 +08:00
//players->broadcastAll( shared_ptr<UpdateProgressPacket>( new UpdateProgressPacket(20) ) );
if ( ! s_bServerHalted )
{
2026-03-07 21:12:22 -06:00
ConsoleSchematicFile : : XboxSchematicInitParam * initData = ( ConsoleSchematicFile : : XboxSchematicInitParam * ) param ;
2026-03-01 12:16:08 +08:00
# 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 ) ;
2026-03-06 02:11:18 +07:00
DataOutputStream dos = DataOutputStream ( & fos ) ;
2026-03-01 12:16:08 +08:00
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
{
2026-03-07 21:12:22 -06:00
DebugSetCameraPosition * pos = ( DebugSetCameraPosition * ) param ;
2026-03-01 12:16:08 +08:00
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 " ,
2026-03-03 03:04:10 +08:00
pos - > m_camX , pos - > m_camY , pos - > m_camZ ,
2026-03-06 02:11:18 +07:00
pos - > m_yRot , pos - > m_elev
2026-03-03 03:04:10 +08:00
) ;
2026-03-01 12:16:08 +08:00
shared_ptr < ServerPlayer > player = players - > players . at ( pos - > player ) ;
player - > debug_setPosition ( pos - > m_camX , pos - > m_camY , pos - > m_camZ ,
2026-03-03 03:04:10 +08:00
pos - > m_yRot , pos - > m_elev ) ;
2026-03-01 12:16:08 +08:00
// 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 ;
}
2026-03-03 03:04:10 +08:00
2026-03-01 12:16:08 +08:00
app . SetXuiServerAction ( i , eXuiServerAction_Idle ) ;
}
Sleep ( 1 ) ;
2026-03-03 03:04:10 +08:00
}
}
2026-03-01 12:16:08 +08:00
//else
//{
2026-03-03 03:04:10 +08:00
// while (running)
2026-03-01 12:16:08 +08:00
// {
2026-03-03 03:04:10 +08:00
// handleConsoleInputs();
2026-03-01 12:16:08 +08:00
// Sleep(10);
2026-03-03 03:04:10 +08:00
// }
//}
2026-03-01 12:16:08 +08:00
#if 0
2026-03-03 03:04:10 +08:00
} 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 ;
2026-03-01 12:16:08 +08:00
} catch ( Throwable t ) {
2026-03-03 03:04:10 +08:00
t . printStackTrace ( ) ;
} finally {
System : : exit ( 0 ) ;
}
}
2026-03-01 12:16:08 +08:00
# endif
// 4J Stu - Stop the server when the loops complete, as the finally would do
2026-03-03 03:04:10 +08:00
stopServer ( didInit ) ;
2026-03-01 12:16:08 +08:00
stopped = true ;
}
void MinecraftServer : : broadcastStartSavingPacket ( )
{
2026-03-07 21:12:22 -06:00
players - > broadcastAll ( shared_ptr < GameEventPacket > ( new GameEventPacket ( GameEventPacket : : START_SAVING , 0 ) ) ) ; ;
2026-03-01 12:16:08 +08:00
}
void MinecraftServer : : broadcastStopSavingPacket ( )
{
if ( ! s_bServerHalted )
{
2026-03-07 21:12:22 -06:00
players - > broadcastAll ( shared_ptr < GameEventPacket > ( new GameEventPacket ( GameEventPacket : : STOP_SAVING , 0 ) ) ) ; ;
2026-03-01 12:16:08 +08:00
}
}
void MinecraftServer : : tick ( )
{
2026-03-03 03:04:10 +08:00
vector < wstring > toRemove ;
2026-03-06 02:11:18 +07:00
for ( auto & it : ironTimers )
{
int t = it . second ;
2026-03-03 03:04:10 +08:00
if ( t > 0 )
2026-03-01 12:16:08 +08:00
{
2026-03-06 02:11:18 +07:00
ironTimers [ it . first ] = t - 1 ;
2026-03-03 03:04:10 +08:00
}
2026-03-01 12:16:08 +08:00
else
{
2026-03-06 02:11:18 +07:00
toRemove . push_back ( it . first ) ;
2026-03-03 03:04:10 +08:00
}
}
2026-03-06 02:11:18 +07:00
for ( const auto & i : toRemove )
2026-03-01 12:16:08 +08:00
{
2026-03-06 02:11:18 +07:00
ironTimers . erase ( i ) ;
2026-03-03 03:04:10 +08:00
}
2026-03-01 12:16:08 +08:00
2026-03-03 03:04:10 +08:00
AABB : : resetPool ( ) ;
Vec3 : : resetPool ( ) ;
2026-03-01 12:16:08 +08:00
2026-03-03 03:04:10 +08:00
tickCount + + ;
2026-03-01 12:16:08 +08:00
// 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
2026-03-03 03:04:10 +08:00
/* if(m_lastSentDifficulty != pMinecraft->options->difficulty)
2026-03-01 12:16:08 +08:00
{
2026-03-03 03:04:10 +08:00
m_lastSentDifficulty = pMinecraft - > options - > difficulty ;
players - > broadcastAll ( shared_ptr < ServerSettingsChangedPacket > ( new ServerSettingsChangedPacket ( ServerSettingsChangedPacket : : HOST_DIFFICULTY , pMinecraft - > options - > difficulty ) ) ) ;
2026-03-01 12:16:08 +08:00
} */
2026-03-03 03:04:10 +08:00
for ( unsigned int i = 0 ; i < levels . length ; i + + )
2026-03-01 12:16:08 +08:00
{
2026-03-03 03:04:10 +08:00
// if (i == 0 || settings->getBoolean(L"allow-nether", true)) // 4J removed - we always have nether
2026-03-01 12:16:08 +08:00
{
2026-03-03 03:04:10 +08:00
ServerLevel * level = levels [ i ] ;
2026-03-01 12:16:08 +08:00
// 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
2026-03-03 03:04:10 +08:00
if ( tickCount % 20 = = 0 )
2026-03-01 12:16:08 +08:00
{
2026-03-07 21:12:22 -06:00
players - > broadcastAll ( shared_ptr < SetTimePacket > ( new SetTimePacket ( level - > getGameTime ( ) , level - > getDayTime ( ) , level - > getGameRules ( ) - > getBoolean ( GameRules : : RULE_DAYLIGHT ) ) ) , level - > dimension - > id ) ;
2026-03-03 03:04:10 +08:00
}
// #ifndef __PS3__
2026-03-07 03:31:30 +07:00
static int64_t stc = 0 ;
int64_t st0 = System : : currentTimeMillis ( ) ;
2026-03-01 12:16:08 +08:00
PIXBeginNamedEvent ( 0 , " Level tick %d " , i ) ;
2026-03-07 21:12:22 -06:00
( ( Level * ) level ) - > tick ( ) ;
2026-03-07 03:31:30 +07:00
int64_t st1 = System : : currentTimeMillis ( ) ;
2026-03-01 12:16:08 +08:00
PIXEndNamedEvent ( ) ;
PIXBeginNamedEvent ( 0 , " Update lights %d " , i ) ;
2026-03-03 03:04:10 +08:00
2026-03-07 03:31:30 +07:00
int64_t st2 = System : : currentTimeMillis ( ) ;
2026-03-01 12:16:08 +08:00
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 ( ) ;
2026-03-07 03:31:30 +07:00
int64_t st3 = System : : currentTimeMillis ( ) ;
2026-03-03 03:04:10 +08:00
// printf(">>>>>>>>>>>>>>>>>>>>>> Tick %d %d %d : %d\n", st1 - st0, st2 - st1, st3 - st2, st0 - stc );
2026-03-01 12:16:08 +08:00
stc = st0 ;
2026-03-03 03:04:10 +08:00
// #endif// __PS3__
}
}
2026-03-01 12:16:08 +08:00
Entity : : tickExtraWandering ( ) ; // 4J added
PIXBeginNamedEvent ( 0 , " Connection tick " ) ;
2026-03-03 03:04:10 +08:00
connection - > tick ( ) ;
2026-03-01 12:16:08 +08:00
PIXEndNamedEvent ( ) ;
PIXBeginNamedEvent ( 0 , " Players tick " ) ;
2026-03-03 03:04:10 +08:00
players - > tick ( ) ;
2026-03-01 12:16:08 +08:00
PIXEndNamedEvent ( ) ;
// 4J - removed
#if 0
2026-03-07 21:12:22 -06:00
for ( int i = 0 ; i < tickables . size ( ) ; i + + ) {
2026-03-03 03:04:10 +08:00
tickables . get ( i ) - tick ( ) ;
}
2026-03-01 12:16:08 +08:00
# endif
2026-03-03 03:04:10 +08:00
// try { // 4J - removed try/catch
handleConsoleInputs ( ) ;
// } catch (Exception e) {
// logger.log(Level.WARNING, "Unexpected exception while parsing console command", e);
// }
2026-03-01 12:16:08 +08:00
}
void MinecraftServer : : handleConsoleInput ( const wstring & msg , ConsoleInputSource * source )
{
2026-03-04 16:18:47 +08:00
EnterCriticalSection ( & m_consoleInputCS ) ;
2026-03-01 12:16:08 +08:00
consoleInput . push_back ( new ConsoleInput ( msg , source ) ) ;
2026-03-04 16:18:47 +08:00
LeaveCriticalSection ( & m_consoleInputCS ) ;
2026-03-01 12:16:08 +08:00
}
void MinecraftServer : : handleConsoleInputs ( )
{
2026-03-04 16:18:47 +08:00
vector < ConsoleInput * > pendingInputs ;
EnterCriticalSection ( & m_consoleInputCS ) ;
pendingInputs . swap ( consoleInput ) ;
LeaveCriticalSection ( & m_consoleInputCS ) ;
for ( size_t i = 0 ; i < pendingInputs . size ( ) ; + + i )
2026-03-01 12:16:08 +08:00
{
2026-03-04 16:18:47 +08:00
ConsoleInput * input = pendingInputs [ i ] ;
ExecuteConsoleCommand ( this , input - > msg ) ;
delete input ;
2026-03-03 03:04:10 +08:00
}
2026-03-01 12:16:08 +08:00
}
2026-03-07 03:31:30 +07:00
void MinecraftServer : : main ( int64_t seed , void * lpParameter )
2026-03-01 12:16:08 +08:00
{
# if __PS3__
ShutdownManager : : HasStarted ( ShutdownManager : : eServerThread ) ;
# endif
server = new MinecraftServer ( ) ;
server - > run ( seed , lpParameter ) ;
delete server ;
2026-03-07 21:12:22 -06:00
server = NULL ;
2026-03-01 12:16:08 +08:00
ShutdownManager : : HasFinished ( ShutdownManager : : eServerThread ) ;
}
void MinecraftServer : : HaltServer ( bool bPrimaryPlayerSignedOut )
{
s_bServerHalted = true ;
2026-03-07 21:12:22 -06:00
if ( server ! = NULL )
2026-03-01 12:16:08 +08:00
{
m_bPrimaryPlayerSignedOut = bPrimaryPlayerSignedOut ;
server - > halt ( ) ;
}
}
File * MinecraftServer : : getFile ( const wstring & name )
{
return new File ( name ) ;
}
void MinecraftServer : : info ( const wstring & string )
{
2026-03-04 16:18:47 +08:00
PrintConsoleLine ( L " [INFO] " , string ) ;
2026-03-01 12:16:08 +08:00
}
void MinecraftServer : : warn ( const wstring & string )
{
2026-03-04 16:18:47 +08:00
PrintConsoleLine ( L " [WARN] " , string ) ;
2026-03-01 12:16:08 +08:00
}
wstring MinecraftServer : : getConsoleName ( )
{
return L " CONSOLE " ;
}
ServerLevel * MinecraftServer : : getLevel ( int dimension )
{
2026-03-03 03:04:10 +08:00
if ( dimension = = - 1 ) return levels [ 1 ] ;
2026-03-01 12:16:08 +08:00
else if ( dimension = = 1 ) return levels [ 2 ] ;
2026-03-03 03:04:10 +08:00
else return levels [ 0 ] ;
2026-03-01 12:16:08 +08:00
}
// 4J added
void MinecraftServer : : setLevel ( int dimension , ServerLevel * level )
{
2026-03-03 03:04:10 +08:00
if ( dimension = = - 1 ) levels [ 1 ] = level ;
2026-03-01 12:16:08 +08:00
else if ( dimension = = 1 ) levels [ 2 ] = level ;
2026-03-03 03:04:10 +08:00
else levels [ 0 ] = level ;
}
# if defined _ACK_CHUNK_SEND_THROTTLING
bool MinecraftServer : : chunkPacketManagement_CanSendTo ( INetworkPlayer * player )
{
if ( s_hasSentEnoughPackets ) return false ;
2026-03-07 21:12:22 -06:00
if ( player = = NULL ) return false ;
2026-03-03 03:04:10 +08:00
2026-03-07 21:12:22 -06:00
for ( int i = 0 ; i < s_sentTo . size ( ) ; i + + )
2026-03-03 03:04:10 +08:00
{
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 )
{
2026-03-07 03:31:30 +07:00
int64_t currentTime = System : : currentTimeMillis ( ) ;
2026-03-03 03:04:10 +08:00
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 ) ;
2026-03-01 12:16:08 +08:00
}
2026-03-03 03:04:10 +08:00
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 ;
2026-03-06 02:11:18 +07:00
auto playerConnectionBest = playersOrig . begin ( ) ;
for ( auto it = playersOrig . begin ( ) ; it ! = playersOrig . end ( ) ; it + + )
2026-03-03 03:04:10 +08:00
{
int thisTime = 0 ;
INetworkPlayer * np = ( * it ) - > getNetworkPlayer ( ) ;
if ( np )
{
thisTime = np - > GetTimeSinceLastChunkPacket_ms ( ) ;
}
2026-03-06 02:11:18 +07:00
if ( thisTime > longestTime )
2026-03-03 03:04:10 +08:00
{
playerConnectionBest = it ;
longestTime = thisTime ;
}
}
players - > push_back ( * playerConnectionBest ) ;
playersOrig . erase ( playerConnectionBest ) ;
} while ( playersOrig . size ( ) > 0 ) ;
}
2026-03-01 12:16:08 +08:00
}
2026-03-03 03:04:10 +08:00
void MinecraftServer : : chunkPacketManagement_PostTick ( )
{
}
# else
2026-03-06 19:23:32 -06:00
// 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.
2026-03-03 03:04:10 +08:00
bool MinecraftServer : : chunkPacketManagement_CanSendTo ( INetworkPlayer * player )
2026-03-01 12:16:08 +08:00
{
2026-03-07 21:12:22 -06:00
if ( player = = NULL ) return false ;
2026-03-01 12:16:08 +08:00
int time = GetTickCount ( ) ;
2026-03-06 19:23:32 -06:00
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 )
2026-03-01 12:16:08 +08:00
{
return true ;
}
return false ;
}
2026-03-03 03:04:10 +08:00
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));
// }
}
2026-03-01 12:16:08 +08:00
void MinecraftServer : : cycleSlowQueueIndex ( )
{
if ( ! g_NetworkManager . IsInSession ( ) ) return ;
int startingIndex = s_slowQueuePlayerIndex ;
2026-03-07 21:12:22 -06:00
INetworkPlayer * currentPlayer = NULL ;
2026-03-01 12:16:08 +08:00
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 & &
2026-03-07 21:12:22 -06:00
currentPlayer ! = NULL & &
2026-03-01 12:16:08 +08:00
currentPlayer - > IsLocal ( )
) ;
// app.DebugPrintf("Cycled slow queue index to %d\n", s_slowQueuePlayerIndex);
}
2026-03-03 03:04:10 +08:00
# endif
2026-03-01 12:16:08 +08:00
// 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 ;
2026-03-03 03:04:10 +08:00
}