package net.minecraft.client.server; import com.google.common.base.MoreObjects; import com.google.common.collect.Lists; import com.mojang.authlib.GameProfile; import com.mojang.logging.LogUtils; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.UUID; import java.util.function.BooleanSupplier; import net.minecraft.CrashReport; import net.minecraft.SharedConstants; import net.minecraft.SystemReport; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.components.Tooltip; import net.minecraft.client.gui.components.toasts.SystemToast; import net.minecraft.core.BlockPos; import net.minecraft.core.GlobalPos; import net.minecraft.core.Position; import net.minecraft.gizmos.Gizmos; import net.minecraft.gizmos.SimpleGizmoCollector; import net.minecraft.gizmos.Gizmos.TemporaryCollection; import net.minecraft.gizmos.SimpleGizmoCollector.GizmoInstance; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceKey; import net.minecraft.server.MinecraftServer; import net.minecraft.server.Services; import net.minecraft.server.WorldStem; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer.SavedPosition; import net.minecraft.server.level.progress.LevelLoadListener; import net.minecraft.server.notifications.NotificationManager; import net.minecraft.server.packs.repository.PackRepository; import net.minecraft.server.permissions.LevelBasedPermissionSet; import net.minecraft.server.permissions.PermissionSet; import net.minecraft.server.players.NameAndId; import net.minecraft.stats.Stats; import net.minecraft.util.ModCheck; import net.minecraft.util.ProblemReporter.ScopedCollector; import net.minecraft.util.debugchart.LocalSampleLogger; import net.minecraft.util.profiling.Profiler; import net.minecraft.util.profiling.ProfilerFiller; import net.minecraft.util.thread.BlockableEventLoop; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.GameType; import net.minecraft.world.level.chunk.storage.RegionStorageInfo; import net.minecraft.world.level.gamerules.GameRules; import net.minecraft.world.level.storage.TagValueInput; import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.LevelStorageSource.LevelStorageAccess; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; public class IntegratedServer extends MinecraftServer { private static final Logger LOGGER = LogUtils.getLogger(); private static final int MIN_SIM_DISTANCE = 2; public static final int MAX_PLAYERS = 8; private final Minecraft minecraft; private boolean paused = true; private int publishedPort = -1; @Nullable private GameType publishedGameType; @Nullable private LanServerPinger lanPinger; @Nullable private UUID uuid; private int previousSimulationDistance = 0; private volatile List latestTicksGizmos = new ArrayList(); private final SimpleGizmoCollector gizmoCollector = new SimpleGizmoCollector(); private IntegratedServer.MultiplayerScope multiplayerScope = IntegratedServer.MultiplayerScope.OFF; public IntegratedServer( final Thread serverThread, final Minecraft minecraft, final LevelStorageAccess levelStorageAccess, final PackRepository packRepository, final WorldStem worldStem, final Optional gameRules, final Services services, final LevelLoadListener levelLoadListener ) { super( serverThread, levelStorageAccess, packRepository, worldStem, gameRules, minecraft.getProxy(), minecraft.getFixerUpper(), services, levelLoadListener, false, new NotificationManager() ); this.setSingleplayerProfile(minecraft.getGameProfile()); this.setDemo(minecraft.isDemo()); this.setPlayerList(new IntegratedPlayerList(this, this.registries(), this.playerDataStorage)); this.minecraft = minecraft; } protected boolean initServer() { LOGGER.info("Starting integrated minecraft server version {}", SharedConstants.getCurrentVersion().name()); this.setUsesAuthentication(true); this.initializeKeyPair(); this.loadLevel(); GameProfile host = this.getSingleplayerProfile(); String levelName = this.getWorldData().getLevelName(); this.setMotd(host != null ? host.name() + " - " + levelName : levelName); this.saveEverything(false, true, true); return true; } public boolean isPaused() { return this.paused; } protected void processPacketsAndTick(final boolean sprinting) { TemporaryCollection ignored = Gizmos.withCollector(this.gizmoCollector); try { super.processPacketsAndTick(sprinting); } catch (Throwable var6) { if (ignored != null) { try { ignored.close(); } catch (Throwable var5) { var6.addSuppressed(var5); } } throw var6; } if (ignored != null) { ignored.close(); } if (this.tickRateManager().runsNormally()) { this.latestTicksGizmos = this.gizmoCollector.drainGizmos(); } } protected void tickServer(final BooleanSupplier haveTime) { boolean wasPaused = this.paused; this.paused = Minecraft.getInstance().isPaused() || this.getPlayerList().getPlayers().isEmpty(); ProfilerFiller profiler = Profiler.get(); if (!wasPaused && this.paused) { profiler.push("autoSave"); LOGGER.info("Saving and pausing game..."); this.saveEverything(false, false, false); profiler.pop(); } if (this.paused) { this.tickPaused(); } else { if (wasPaused) { this.forceGameTimeSynchronization(); } super.tickServer(haveTime); int serverViewDistance = Math.max(2, this.minecraft.options.renderDistance().get()); if (serverViewDistance != this.getPlayerList().getViewDistance()) { LOGGER.info("Changing view distance to {}, from {}", serverViewDistance, this.getPlayerList().getViewDistance()); this.getPlayerList().setViewDistance(serverViewDistance); } int serverSimulationDistance = Math.max(2, this.minecraft.options.simulationDistance().get()); if (serverSimulationDistance != this.previousSimulationDistance) { LOGGER.info("Changing simulation distance to {}, from {}", serverSimulationDistance, this.previousSimulationDistance); this.getPlayerList().setSimulationDistance(serverSimulationDistance); this.previousSimulationDistance = serverSimulationDistance; } } } protected LocalSampleLogger getTickTimeLogger() { return this.minecraft.getDebugOverlay().getTickTimeLogger(); } public boolean isTickTimeLoggingEnabled() { return true; } private void tickPaused() { this.tickConnection(); for (ServerPlayer player : this.getPlayerList().getPlayers()) { player.awardStat(Stats.TOTAL_WORLD_TIME); } } public boolean shouldRconBroadcast() { return true; } public boolean shouldInformAdmins() { return true; } public Path getServerDirectory() { return this.minecraft.gameDirectory.toPath(); } public boolean isDedicatedServer() { return false; } public int getRateLimitPacketsPerSecond() { return 0; } public boolean useNativeTransport() { return this.minecraft.options.useNativeTransport(); } protected void onServerCrash(final CrashReport report) { BlockableEventLoop.relayDelayCrash(report); } public SystemReport fillServerSystemReport(final SystemReport systemReport) { systemReport.setDetail("Type", "Integrated Server"); systemReport.setDetail("Is Modded", () -> this.getModdedStatus().fullDescription()); systemReport.setDetail("Launched Version", this.minecraft::getLaunchedVersion); return systemReport; } public ModCheck getModdedStatus() { return Minecraft.checkModStatus().merge(super.getModdedStatus()); } public boolean publishServer(@Nullable final GameType gameMode, final boolean allowCommands, final int port) { try { this.minecraft.prepareForMultiplayer(); this.minecraft.getConnection().prepareKeyPair(); this.getConnection().startTcpServerListener(null, port); LOGGER.info("Started serving on {}", port); this.publishedPort = port; this.lanPinger = new LanServerPinger(this.getMotd(), port + ""); this.lanPinger.start(); this.publishedGameType = gameMode; this.setCommandsAllowedForAllPlayers(allowCommands); this.minecraft.p2pManager.onHostScopeChanged(this.multiplayerScope); return true; } catch (IOException var5) { return false; } } public void applyDefaultGameMode(final GameType gameMode) { this.setDefaultGameType(gameMode); this.enforceGameTypeForPlayers(this.getForcedGameType()); } public void setCommandsAllowedForAllPlayers(final boolean allowCommands) { this.getPlayerList().setAllowCommandsForAllPlayers(allowCommands); PermissionSet newProfilePermissions = this.getProfilePermissions(this.minecraft.player.nameAndId()); this.minecraft.player.setPermissions(newProfilePermissions); this.minecraft.player.refreshChatAbilities(); for (ServerPlayer player : this.getPlayerList().getPlayers()) { this.getPlayerList().sendPlayerPermissionLevel(player); } } public boolean unpublishServer() { if (!this.isPublished()) { return false; } else { LOGGER.info("Unpublishing integrated server (was on port {})", this.publishedPort); this.getConnection().stopTcpServerListener(); if (this.lanPinger != null) { this.lanPinger.interrupt(); this.lanPinger = null; } Component reason = Component.translatable("multiplayer.disconnect.server_shutdown"); for (ServerPlayer player : Lists.newArrayList(this.getPlayerList().getPlayers())) { if (!player.getUUID().equals(this.uuid)) { player.connection.disconnect(reason); } } this.getPlayerList().setAllowCommandsForAllPlayers(false); this.publishedPort = -1; this.publishedGameType = null; IntegratedServer.MultiplayerScope previousScope = this.multiplayerScope; this.multiplayerScope = IntegratedServer.MultiplayerScope.OFF; this.minecraft.p2pManager.onHostServerStopping(); if (previousScope != IntegratedServer.MultiplayerScope.OFF) { this.minecraft.p2pManager.onHostScopeChanged(IntegratedServer.MultiplayerScope.OFF); } return true; } } public void stopServer() { this.minecraft.p2pManager.onHostServerStopping(); super.stopServer(); if (this.lanPinger != null) { this.lanPinger.interrupt(); this.lanPinger = null; } } public void onPlayerListChanged() { this.minecraft.p2pManager.notifyJoinStateChanged(); } public void halt(final boolean wait) { this.executeBlocking(() -> { for (ServerPlayer player : Lists.newArrayList(this.getPlayerList().getPlayers())) { if (!player.getUUID().equals(this.uuid)) { this.getPlayerList().remove(player); } } }); super.halt(wait); if (this.lanPinger != null) { this.lanPinger.interrupt(); this.lanPinger = null; } } public boolean isPublished() { return this.publishedPort > -1; } public int getPort() { return this.publishedPort; } public void setDefaultGameType(final GameType gameType) { super.setDefaultGameType(gameType); this.publishedGameType = null; } public LevelBasedPermissionSet operatorUserPermissions() { return LevelBasedPermissionSet.GAMEMASTER; } public LevelBasedPermissionSet getFunctionCompilationPermissions() { return LevelBasedPermissionSet.GAMEMASTER; } public void setUUID(final UUID uuid) { this.uuid = uuid; } public boolean isSingleplayerOwner(final NameAndId nameAndId) { return this.getSingleplayerProfile() != null && nameAndId.name().equalsIgnoreCase(this.getSingleplayerProfile().name()); } public int getScaledTrackingDistance(final int baseRange) { return (int)(this.minecraft.options.entityDistanceScaling().get() * baseRange); } public boolean forceSynchronousWrites() { return this.minecraft.options.syncWrites; } @Nullable public GameType getForcedGameType() { return this.isPublished() && !this.isHardcore() ? MoreObjects.firstNonNull(this.publishedGameType, this.worldData.getGameType()) : null; } protected GlobalPos selectLevelLoadFocusPos() { UUID lastSinglePlayerOwnerUUID = this.worldData.getSinglePlayerUUID(); if (lastSinglePlayerOwnerUUID == null) { return super.selectLevelLoadFocusPos(); } else { Optional playerData = this.playerDataStorage.load(new NameAndId(lastSinglePlayerOwnerUUID, "")); if (playerData.isEmpty()) { return super.selectLevelLoadFocusPos(); } else { ScopedCollector reporter = new ScopedCollector(LOGGER); GlobalPos var6; label34: { try { ValueInput input = TagValueInput.create(reporter, this.registryAccess(), (CompoundTag)playerData.get()); SavedPosition loadedPosition = (SavedPosition)input.read(SavedPosition.MAP_CODEC).orElse(SavedPosition.EMPTY); if (loadedPosition.dimension().isPresent() && loadedPosition.position().isPresent()) { var6 = new GlobalPos((ResourceKey)loadedPosition.dimension().get(), BlockPos.containing((Position)loadedPosition.position().get())); break label34; } } catch (Throwable var8) { try { reporter.close(); } catch (Throwable var7) { var8.addSuppressed(var7); } throw var8; } reporter.close(); return super.selectLevelLoadFocusPos(); } reporter.close(); return var6; } } } public void sendLowDiskSpaceWarning() { super.sendLowDiskSpaceWarning(); this.minecraft.sendLowDiskSpaceWarning(); } public void reportChunkLoadFailure(final Throwable throwable, final RegionStorageInfo storageInfo, final ChunkPos pos) { super.reportChunkLoadFailure(throwable, storageInfo, pos); this.warnOnLowDiskSpace(); this.minecraft.execute(() -> SystemToast.onChunkLoadFailure(this.minecraft, pos)); } public void reportChunkSaveFailure(final Throwable throwable, final RegionStorageInfo storageInfo, final ChunkPos pos) { super.reportChunkSaveFailure(throwable, storageInfo, pos); this.warnOnLowDiskSpace(); this.minecraft.execute(() -> SystemToast.onChunkSaveFailure(this.minecraft, pos)); } public int getMaxPlayers() { return 8; } public Collection getPerTickGizmos() { return this.latestTicksGizmos; } public void setMultiplayerScope(final IntegratedServer.MultiplayerScope multiplayerScope) { this.multiplayerScope = multiplayerScope; this.minecraft.p2pManager.onHostScopeChanged(multiplayerScope); } public IntegratedServer.MultiplayerScope getMultiplayerScope() { return this.multiplayerScope; } public boolean enforceSecureProfile() { return this.isPublishedOnline(); } public boolean isPublishedOnline() { return this.isRunning() && this.isPublished() && this.multiplayerScope == IntegratedServer.MultiplayerScope.ONLINE; } public static enum MultiplayerScope { OFF("off"), LAN("lan"), ONLINE("online"); private final Component translatable; private final Tooltip tooltip; private MultiplayerScope(final String key) { this.translatable = Component.translatable("menu.multiplayerOptions.network." + key); this.tooltip = Tooltip.create(Component.translatable("menu.multiplayerOptions.network." + key + ".tooltip")); } public Component getDisplayName() { return this.translatable; } public Tooltip getTooltip() { return this.tooltip; } } }