/*
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.spongepowered.server;
import static com.google.common.base.Preconditions.checkState;
import static net.minecraft.server.MinecraftServer.USER_CACHE_FILE;
import static org.spongepowered.server.launch.VanillaCommandLine.BONUS_CHEST;
import static org.spongepowered.server.launch.VanillaCommandLine.PORT;
import static org.spongepowered.server.launch.VanillaCommandLine.WORLD_DIR;
import static org.spongepowered.server.launch.VanillaCommandLine.WORLD_NAME;
import static org.spongepowered.server.launch.VanillaLaunch.Environment.DEVELOPMENT;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.Stage;
import com.mojang.authlib.GameProfileRepository;
import com.mojang.authlib.minecraft.MinecraftSessionService;
import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;
import joptsimple.OptionSet;
import net.minecraft.init.Bootstrap;
import net.minecraft.network.NetHandlerPlayServer;
import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.server.management.PlayerProfileCache;
import net.minecraft.util.datafix.DataFixesManager;
import org.apache.logging.log4j.Logger;
import org.spongepowered.api.Game;
import org.spongepowered.api.GameState;
import org.spongepowered.api.event.SpongeEventFactory;
import org.spongepowered.api.event.cause.Cause;
import org.spongepowered.api.event.cause.NamedCause;
import org.spongepowered.api.service.permission.PermissionService;
import org.spongepowered.api.service.permission.SubjectData;
import org.spongepowered.api.service.sql.SqlService;
import org.spongepowered.api.util.Tristate;
import org.spongepowered.common.SpongeBootstrap;
import org.spongepowered.common.SpongeImpl;
import org.spongepowered.common.SpongeInternalListeners;
import org.spongepowered.common.entity.ai.SpongeEntityAICommonSuperclass;
import org.spongepowered.common.inject.SpongeGuice;
import org.spongepowered.common.inject.SpongeModule;
import org.spongepowered.common.interfaces.IMixinServerCommandManager;
import org.spongepowered.common.network.message.SpongeMessageHandler;
import org.spongepowered.common.registry.SpongeGameRegistry;
import org.spongepowered.common.service.permission.SpongeContextCalculator;
import org.spongepowered.common.service.permission.SpongePermissionService;
import org.spongepowered.common.service.sql.SqlServiceImpl;
import org.spongepowered.common.util.SpongeHooks;
import org.spongepowered.common.world.storage.SpongePlayerDataHandler;
import org.spongepowered.server.inject.SpongeVanillaModule;
import org.spongepowered.server.launch.VanillaCommandLine;
import org.spongepowered.server.launch.VanillaLaunch;
import org.spongepowered.server.launch.plugin.PluginSource;
import org.spongepowered.server.plugin.MetaPluginContainer;
import org.spongepowered.server.plugin.MetadataContainer;
import org.spongepowered.server.plugin.MinecraftPluginContainer;
import org.spongepowered.server.plugin.VanillaPluginManager;
import java.io.File;
import java.io.IOException;
import java.net.Proxy;
import java.util.Optional;
import java.util.UUID;
@Singleton
public final class SpongeVanilla extends MetaPluginContainer {
private final Logger logger;
private final Game game;
private final Cause gameCause;
private final SpongeGameRegistry registry;
@Inject
public SpongeVanilla(MetadataContainer metadata, Logger logger, Game game, SpongeGameRegistry registry) {
super(metadata.get(SpongeImpl.ECOSYSTEM_ID, "SpongeVanilla"), PluginSource.find(SpongeVanilla.class));
this.logger = logger;
this.game = game;
this.gameCause = Cause.of(NamedCause.source(game));
this.registry = registry;
this.logger.info("This server is running {} version {}", getName(), getVersion().orElse("unknown"));
}
public void preInitialize() throws Exception {
this.logger.info("Loading Sponge...");
// Pre-initialize registry
this.registry.preRegistryInit();
this.game.getEventManager().registerListeners(this, SpongeInternalListeners.getInstance());
SpongeBootstrap.initializeServices();
SpongeBootstrap.initializeCommands();
this.logger.info("Loading plugins...");
((VanillaPluginManager) this.game.getPluginManager()).loadPlugins();
SpongeImpl.postState(GameState.CONSTRUCTION, SpongeEventFactory.createGameConstructionEvent(this.gameCause));
this.logger.info("Initializing plugins...");
SpongeImpl.postState(GameState.PRE_INITIALIZATION, SpongeEventFactory.createGamePreInitializationEvent(this.gameCause));
this.registry.preInit();
checkState(Class.forName("org.spongepowered.api.entity.ai.task.AbstractAITask").getSuperclass()
.equals(SpongeEntityAICommonSuperclass.class));
SpongeInternalListeners.getInstance().registerServiceCallback(PermissionService.class,
input -> input.registerContextCalculator(new SpongeContextCalculator()));
SpongeHooks.enableThreadContentionMonitoring();
SpongeMessageHandler.init();
}
public void initialize() {
this.registry.init();
if (!this.game.getServiceManager().provide(PermissionService.class).isPresent()) {
SpongePermissionService service = new SpongePermissionService(this.game);
// Setup default permissions
service.getGroupForOpLevel(1).getSubjectData().setPermission(SubjectData.GLOBAL_CONTEXT, "minecraft.selector", Tristate.TRUE);
service.getGroupForOpLevel(2).getSubjectData().setPermission(SubjectData.GLOBAL_CONTEXT, "minecraft.commandblock", Tristate.TRUE);
this.game.getServiceManager().setProvider(this, PermissionService.class, service);
}
SpongeImpl.postState(GameState.INITIALIZATION, SpongeEventFactory.createGameInitializationEvent(this.gameCause));
this.registry.postInit();
SpongeImpl.postState(GameState.POST_INITIALIZATION, SpongeEventFactory.createGamePostInitializationEvent(this.gameCause));
this.logger.info("Successfully loaded and initialized plugins.");
SpongeImpl.postState(GameState.LOAD_COMPLETE, SpongeEventFactory.createGameLoadCompleteEvent(this.gameCause));
}
public void onServerAboutToStart() {
((IMixinServerCommandManager) SpongeImpl.getServer().getCommandManager()).registerEarlyCommands(this.game);
SpongeImpl.postState(GameState.SERVER_ABOUT_TO_START, SpongeEventFactory.createGameAboutToStartServerEvent(this.gameCause));
}
public void onServerStarting() {
SpongeImpl.postState(GameState.SERVER_STARTING, SpongeEventFactory.createGameStartingServerEvent(this.gameCause));
SpongeImpl.postState(GameState.SERVER_STARTED, SpongeEventFactory.createGameStartedServerEvent(this.gameCause));
((IMixinServerCommandManager) SpongeImpl.getServer().getCommandManager()).registerLowPriorityCommands(this.game);
SpongePlayerDataHandler.init();
}
public void onServerStopping() {
SpongeImpl.postState(GameState.SERVER_STOPPING, SpongeEventFactory.createGameStoppingServerEvent(this.gameCause));
}
public void onServerStopped() throws IOException {
SpongeImpl.postState(GameState.SERVER_STOPPED, SpongeEventFactory.createGameStoppedServerEvent(this.gameCause));
((SqlServiceImpl) this.game.getServiceManager().provideUnchecked(SqlService.class)).close();
}
@Override
public Optional<?> getInstance() {
return Optional.of(this);
}
private static void start(String[] args) {
// Attempt to load metadata
MetadataContainer metadata = MetadataContainer.load();
// Register Minecraft plugin container
MinecraftPluginContainer.register();
OptionSet options = VanillaCommandLine.parse(args);
// Note: This launches the server instead of MinecraftServer.main
// Keep command line options up-to-date with Vanilla
Bootstrap.register();
File worldDir = options.has(WORLD_DIR) ? options.valueOf(WORLD_DIR) : new File(".");
YggdrasilAuthenticationService authenticationService = new YggdrasilAuthenticationService(Proxy.NO_PROXY, UUID.randomUUID().toString());
MinecraftSessionService sessionService = authenticationService.createMinecraftSessionService();
GameProfileRepository profileRepository = authenticationService.createProfileRepository();
PlayerProfileCache profileCache = new PlayerProfileCache(profileRepository, new File(worldDir, USER_CACHE_FILE.getName()));
DedicatedServer server = new DedicatedServer(worldDir, DataFixesManager.createFixer(),
authenticationService, sessionService, profileRepository, profileCache);
// We force-load NetHandlerPlayServer here.
// Otherwise, VanillaChannelRegistrar causes it to be loaded from
// within the Guice injector (see SpongeVanillaModule), thus swallowing
// any Mixin exception that occurs.
//
// See https://github.com/SpongePowered/SpongeVanilla/issues/235 for a more
// in-depth explanation
NetHandlerPlayServer.class.getName();
final Stage stage = SpongeGuice.getInjectorStage(VanillaLaunch.ENVIRONMENT == DEVELOPMENT ? Stage.DEVELOPMENT : Stage.PRODUCTION);
SpongeImpl.getLogger().debug("Creating injector in stage '{}'", stage);
Guice.createInjector(stage, new SpongeModule(), new SpongeVanillaModule(server, metadata));
if (options.has(WORLD_NAME)) {
server.setFolderName(options.valueOf(WORLD_NAME));
}
if (options.has(PORT)) {
server.setServerPort(options.valueOf(PORT));
}
if (options.has(BONUS_CHEST)) {
server.canCreateBonusChest(true);
}
server.startServerThread();
Runtime.getRuntime().addShutdownHook(new Thread(server::stopServer, "Server Shutdown Thread"));
}
public static void main(String[] args) {
try {
start(args);
} catch (Exception e) {
SpongeImpl.getLogger().fatal("Failed to start the Minecraft server", e);
System.exit(1);
}
}
}