/* * This file is part of NeptuneVanilla, licensed under the MIT License (MIT). * * Copyright (c) 2015-2017, Jamie Mansfield <https://github.com/jamierocks> * * 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.neptunepowered.vanilla.mixin.minecraft.server; import static net.minecraft.server.MinecraftServer.getCurrentTimeMillis; import co.aikar.timings.NeptuneTimings; import co.aikar.timings.TimingsManager; import co.aikar.timings.WorldTimingsHandler; import com.google.common.collect.Lists; import com.mojang.authlib.GameProfile; import net.canarymod.Canary; import net.canarymod.ToolBox; import net.canarymod.api.CommandBlockLogic; import net.canarymod.api.ConfigurationManager; import net.canarymod.api.OfflinePlayer; import net.canarymod.api.PlayerListData; import net.canarymod.api.PlayerReference; import net.canarymod.api.Server; import net.canarymod.api.chat.ChatComponent; import net.canarymod.api.entity.living.humanoid.Player; import net.canarymod.api.gui.GUIControl; import net.canarymod.api.inventory.recipes.CraftingRecipe; import net.canarymod.api.inventory.recipes.Recipe; import net.canarymod.api.inventory.recipes.SmeltRecipe; import net.canarymod.api.world.World; import net.canarymod.api.world.WorldManager; import net.canarymod.chat.MessageReceiver; import net.canarymod.config.Configuration; import net.canarymod.hook.command.ConsoleCommandHook; import net.canarymod.hook.system.PermissionCheckHook; import net.canarymod.hook.system.ServerTickHook; import net.canarymod.tasks.ServerTask; import net.canarymod.tasks.ServerTaskManager; import net.minecraft.command.ICommandManager; import net.minecraft.command.ICommandSender; import net.minecraft.crash.CrashReport; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.CraftingManager; import net.minecraft.item.crafting.FurnaceRecipes; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.network.NetworkSystem; import net.minecraft.network.ServerStatusResponse; import net.minecraft.network.play.server.S03PacketTimeUpdate; import net.minecraft.profiler.PlayerUsageSnooper; import net.minecraft.profiler.Profiler; import net.minecraft.server.MinecraftServer; import net.minecraft.server.management.PlayerProfileCache; import net.minecraft.server.management.ServerConfigurationManager; import net.minecraft.util.BlockPos; import net.minecraft.util.ITickable; import net.minecraft.util.MathHelper; import net.minecraft.util.ReportedException; import net.minecraft.util.Util; import net.minecraft.world.WorldServer; import net.minecraft.world.WorldType; import net.minecraft.world.chunk.storage.AnvilSaveConverter; import net.minecraft.world.storage.ISaveHandler; import net.minecraft.world.storage.SaveHandler; import org.apache.logging.log4j.Logger; import org.neptunepowered.vanilla.NeptuneOfflinePlayer; import org.neptunepowered.vanilla.interfaces.minecraft.server.IMixinMinecraftServer; import org.neptunepowered.vanilla.interfaces.minecraft.world.IMixinWorld; import org.neptunepowered.vanilla.interfaces.minecraft.world.storage.IMixinSaveHandler; import org.neptunepowered.vanilla.server.ServerTimerManager; import org.neptunepowered.vanilla.world.NeptuneWorldManager; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Implements; import org.spongepowered.asm.mixin.Interface; import org.spongepowered.asm.mixin.Intrinsic; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.awt.GraphicsEnvironment; import java.io.File; import java.net.Proxy; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Random; import java.util.UUID; import java.util.concurrent.FutureTask; @Mixin(MinecraftServer.class) @Implements(@Interface(iface = Server.class, prefix = "server$")) public abstract class MixinMinecraftServer implements Server, IMixinMinecraftServer { @Shadow @Final private static Logger logger; @Shadow @Final public Profiler theProfiler; @Shadow @Final private PlayerUsageSnooper usageSnooper; @Shadow @Final private ServerStatusResponse statusResponse; @Shadow @Final private List<ITickable> playersOnline; @Shadow @Final private Random random; @Shadow @Final public long[] tickTimeArray; @Shadow @Final protected Queue<FutureTask<?>> futureTaskQueue; @Shadow public WorldServer[] worldServers; @Shadow public long[][] timeOfLastDimensionTick; @Shadow private int tickCounter; @Shadow private boolean startProfiling; @Shadow private boolean serverRunning; @Shadow private ServerConfigurationManager serverConfigManager; @Shadow private long nanoTimeSinceStatusRefresh; @Shadow private boolean worldIsBeingDeleted; private NeptuneWorldManager worldManager = new NeptuneWorldManager(); private long previousTick = -1L; @Shadow public abstract void initiateShutdown(); @Shadow public abstract String getMinecraftVersion(); @Shadow public abstract String[] getAllUsernames(); @Shadow public abstract PlayerProfileCache getPlayerProfileCache(); @Shadow public abstract ICommandManager getCommandManager(); @Shadow public abstract String getHostname(); @Shadow protected abstract void setUserMessage(String message); @Shadow public abstract boolean isServerRunning(); @Shadow protected abstract void outputPercentRemaining(String message, int percent); @Shadow protected abstract void clearCurrentTask(); @Shadow public abstract boolean getAllowNether(); @Shadow public abstract NetworkSystem getNetworkSystem(); @Shadow public abstract int getCurrentPlayerCount(); @Shadow protected abstract void saveAllWorlds(boolean dontLog); @Inject(method = "<init>", at = @At("RETURN")) public void onConstruction(File workDir, Proxy proxy, File profileCacheDir, CallbackInfo info) { Canary.setServer(this); } @Redirect(method = "<init>", at = @At(value = "NEW", args = "class=net/minecraft/world/chunk/storage/AnvilSaveConverter")) public AnvilSaveConverter createSaveConverter(File dir) { return new AnvilSaveConverter(NeptuneWorldManager.WORLDS_DIR); } /** * @author jamierocks - 18th May 2015 * @reason Set the server mod name */ @Overwrite public String getServerModName() { return "NeptuneVanilla"; } /** * @author Jamie Mansfield - 26th April 2016 * @reason Use the Canary configuration. */ @Overwrite public int getMaxPlayers() { return Configuration.getServerConfig().getMaxPlayers(); } /** * @author jamierocks - 2nd October 2016 * @reason Add timings calls */ @Overwrite protected void tick() { TimingsManager.FULL_SERVER_TICK.startTiming(); // Neptune - timings NeptuneTimings.canaryTaskManagerTimer.startTiming(); // Neptune - timings ServerTaskManager.runTasks(); // Neptune - Run tasks NeptuneTimings.canaryTaskManagerTimer.stopTiming(); // Neptune - timings long i = System.nanoTime(); ++this.tickCounter; if (this.startProfiling) { this.startProfiling = false; this.theProfiler.profilingEnabled = true; this.theProfiler.clearProfiling(); } this.theProfiler.startSection("root"); this.updateTimeLightAndEntities(); if (i - this.nanoTimeSinceStatusRefresh >= 5000000000L) { this.nanoTimeSinceStatusRefresh = i; this.statusResponse.setPlayerCountData(new ServerStatusResponse.PlayerCountData(this.getMaxPlayers(), this.getCurrentPlayerCount())); GameProfile[] agameprofile = new GameProfile[Math.min(this.getCurrentPlayerCount(), 12)]; int j = MathHelper.getRandomIntegerInRange(this.random, 0, this.getCurrentPlayerCount() - agameprofile.length); for (int k = 0; k < agameprofile.length; ++k) { agameprofile[k] = this.serverConfigManager.getPlayerList().get(j + k).getGameProfile(); } Collections.shuffle(Arrays.asList(agameprofile)); this.statusResponse.getPlayerCountData().setPlayers(agameprofile); } if (this.tickCounter % Configuration.getServerConfig().getWorldAutoSaveInterval() == 0) { // Neptune - use auto-save interval this.theProfiler.startSection("save"); this.serverConfigManager.saveAllPlayerData(); this.saveAllWorlds(true); this.theProfiler.endSection(); } this.theProfiler.startSection("tallying"); this.tickTimeArray[this.tickCounter % 100] = System.nanoTime() - i; this.theProfiler.endSection(); this.theProfiler.startSection("snooper"); if (!this.usageSnooper.isSnooperRunning() && this.tickCounter > 100) { this.usageSnooper.startSnooper(); } if (this.tickCounter % 6000 == 0) { this.usageSnooper.addMemoryStatsToSnooper(); } this.theProfiler.endSection(); this.theProfiler.endSection(); TimingsManager.FULL_SERVER_TICK.stopTiming(); // Neptune - timings } /** * @author jamierocks - 2nd October 2016 * @reason Add timings calls */ @Overwrite public void updateTimeLightAndEntities() { // Neptune - ServerTickHook start new ServerTickHook(this.previousTick).call(); long curTrack = System.nanoTime(); // Neptune - ServerTickHook end NeptuneTimings.minecraftSchedulerTimer.startTiming(); // Neptune - timings this.theProfiler.startSection("jobs"); synchronized (this.futureTaskQueue) { while (!this.futureTaskQueue.isEmpty()) { Util.runTask((FutureTask) this.futureTaskQueue.poll(), logger); } } NeptuneTimings.minecraftSchedulerTimer.stopTiming(); // Neptune - timings this.theProfiler.endStartSection("levels"); for (int j = 0; j < this.worldServers.length; ++j) { long i = System.nanoTime(); if (j == 0 || this.getAllowNether()) { WorldServer worldserver = this.worldServers[j]; final WorldTimingsHandler timings = ((IMixinWorld) worldserver).getTimings(); // Neptune - timings this.theProfiler.startSection(worldserver.getWorldInfo().getWorldName()); NeptuneTimings.timeUpdateTimer.startTiming(); // Neptune - timings if (this.tickCounter % 20 == 0) { this.theProfiler.startSection("timeSync"); this.serverConfigManager.sendPacketToAllPlayersInDimension( new S03PacketTimeUpdate(worldserver.getTotalWorldTime(), worldserver.getWorldTime(), worldserver.getGameRules().getBoolean("doDaylightCycle")), worldserver.provider.getDimensionId()); this.theProfiler.endSection(); } NeptuneTimings.timeUpdateTimer.stopTiming(); // Neptune - timings this.theProfiler.startSection("tick"); try { timings.doTick.startTiming(); // Neptune - timings worldserver.tick(); timings.doTick.stopTiming(); // Neptune - timings } catch (Throwable throwable1) { CrashReport crashreport = CrashReport.makeCrashReport(throwable1, "Exception ticking world"); worldserver.addWorldInfoToCrashReport(crashreport); throw new ReportedException(crashreport); } try { timings.tickEntities.startTiming(); // Neptune - timings worldserver.updateEntities(); timings.tickEntities.stopTiming(); // Neptune - timings } catch (Throwable throwable) { CrashReport crashreport1 = CrashReport.makeCrashReport(throwable, "Exception ticking world entities"); worldserver.addWorldInfoToCrashReport(crashreport1); throw new ReportedException(crashreport1); } this.theProfiler.endSection(); this.theProfiler.startSection("tracker"); worldserver.getEntityTracker().updateTrackedEntities(); this.theProfiler.endSection(); this.theProfiler.endSection(); } this.timeOfLastDimensionTick[j][this.tickCounter % 100] = System.nanoTime() - i; } this.theProfiler.endStartSection("connection"); NeptuneTimings.connectionTimer.startTiming(); // Neptune - timings this.getNetworkSystem().networkTick(); NeptuneTimings.connectionTimer.stopTiming(); // Neptune - timings this.theProfiler.endStartSection("players"); NeptuneTimings.playerListTimer.startTiming(); // Neptune - timings this.serverConfigManager.onTick(); NeptuneTimings.playerListTimer.stopTiming(); // Neptune - timings this.theProfiler.endStartSection("tickables"); NeptuneTimings.tickablesTimer.startTiming(); // Neptune - timings for (int k = 0; k < this.playersOnline.size(); ++k) { this.playersOnline.get(k).update(); } NeptuneTimings.tickablesTimer.stopTiming(); // Neptune - timings this.theProfiler.endSection(); this.previousTick = System.nanoTime() - curTrack; // Neptune - ServerTickHook } @Inject(method = "loadAllWorlds", at = @At("RETURN")) public void onLoadAllWorlds(String saveName, String worldNameIn, long seed, WorldType type, String worldNameIn2, CallbackInfo ci) { // Temporary to populate the world manager for (WorldServer worldServer : this.worldServers) { this.worldManager.addWorld(worldServer); } } @Inject(method = "stopServer", at = @At("HEAD")) public void onServerStop(CallbackInfo ci) { if (!this.worldIsBeingDeleted) { NeptuneTimings.stopServer(); } } @Inject(method = "stopServer", at = @At("RETURN")) public void afterServerStop(CallbackInfo ci) { if (!this.worldIsBeingDeleted) { Canary.log.info("Disabling plugins..."); Canary.pluginManager().disableAllPlugins(Canary.log); } } @Intrinsic public String server$getHostname() { return this.getHostname(); } @Override public int getNumPlayersOnline() { return this.serverConfigManager.getCurrentPlayerCount(); } @Intrinsic public int server$getMaxPlayers() { return this.getMaxPlayers(); } @Override public String[] getPlayerNameList() { return this.serverConfigManager.getAllUsernames(); } @Override public String[] getKnownPlayerNames() { return this.getAllUsernames(); } @Override public List<Player> getPlayerList() { return (List) this.serverConfigManager.getPlayerList(); } @Override public String getDefaultWorldName() { return Configuration.getServerConfig().getDefaultWorldName(); } @Override public WorldManager getWorldManager() { return this.worldManager; } @Override public boolean consoleCommand(String command) { NeptuneTimings.serverCommandTimer.startTiming(); final ConsoleCommandHook commandHook = (ConsoleCommandHook) new ConsoleCommandHook(this, command).call(); if (commandHook.isCanceled()) { NeptuneTimings.serverCommandTimer.stopTiming(); return true; } final String[] args = command.split(" "); String commandName = args[0]; if (commandName.startsWith("/")) { commandName = commandName.substring(1); } if (!Canary.commands().parseCommand(this, commandName, args)) { NeptuneTimings.serverCommandTimer.stopTiming(); return this.getCommandManager().executeCommand((ICommandSender) this, command) > 0; } NeptuneTimings.serverCommandTimer.stopTiming(); return true; } @Override public boolean consoleCommand(String command, Player player) { final ConsoleCommandHook commandHook = (ConsoleCommandHook) new ConsoleCommandHook(player, command).call(); if (commandHook.isCanceled()) { return true; } final String[] args = command.split(" "); String commandName = args[0]; if (commandName.startsWith("/")) { commandName = commandName.substring(1); } if (!Canary.commands().parseCommand(player, commandName, args)) { if (Canary.ops().isOpped(player) || player.hasPermission("canary.vanilla." + commandName)) { return this.getCommandManager().executeCommand((ICommandSender) player, command) > 0; } return false; } return true; } @Override public boolean consoleCommand(String command, CommandBlockLogic cmdBlockLogic) { final ConsoleCommandHook commandHook = (ConsoleCommandHook) new ConsoleCommandHook(cmdBlockLogic, command).call(); if (commandHook.isCanceled()) { return true; } final String[] args = command.split(" "); String commandName = args[0]; if (commandName.startsWith("/")) { commandName = commandName.substring(1); } // Don't pass to vanilla as that is already handled by CommandBlockLogic // This is only called if Minecraft found no command return Canary.commands().parseCommand(cmdBlockLogic, commandName, args); } @Override public void executeVanillaCommand(MessageReceiver caller, String command) { this.getCommandManager().executeCommand((ICommandSender) caller, command); } @Override public void setTimer(String uniqueName, int time) { ServerTimerManager.setTimer(uniqueName, time); } @Override public boolean isTimerExpired(String uniqueName) { return ServerTimerManager.hasTimerFinished(uniqueName); } @Override public Player matchPlayer(String player) { player = player.toLowerCase(); Player lastPlayer = null; for (Player cPlayer : getConfigurationManager().getAllPlayers()) { if (cPlayer.getName().toLowerCase().equals(player)) { // Perfect match found lastPlayer = cPlayer; break; } if (cPlayer.getName().toLowerCase().contains(player)) { // Partial match if (lastPlayer != null) { // Found multiple return null; } lastPlayer = cPlayer; } } return lastPlayer; } @Override public OfflinePlayer getOfflinePlayer(String player) { UUID uuid = ToolBox.uuidFromUsername(player); if (uuid == null) { return null; } return this.getOfflinePlayer(uuid); } @Override public OfflinePlayer getOfflinePlayer(UUID uuid) { final ISaveHandler saveHandler = MinecraftServer.getServer().getEntityWorld().getSaveHandler(); if (saveHandler instanceof SaveHandler) { final NBTTagCompound tagCompound = ((IMixinSaveHandler) saveHandler).readPlayerData(uuid); if (tagCompound != null) { final GameProfile profile = getPlayerProfileCache().getProfileByUUID(uuid); if (profile != null) { return new NeptuneOfflinePlayer(profile.getName(), uuid, tagCompound); } else { return new NeptuneOfflinePlayer("PLAYER_NAME_UNKNOWN", uuid, tagCompound); } } return null; } else { throw new RuntimeException("ISaveHandler is not of type SaveHandler! Failing to load playerdata"); } } @Override public PlayerReference matchKnownPlayer(String player) { PlayerReference reference = this.matchPlayer(player); if (reference == null) { reference = this.getOfflinePlayer(player); } return reference; } @Override public PlayerReference matchKnownPlayer(UUID uuid) { PlayerReference reference = this.getPlayerFromUUID(uuid); if (reference == null) { reference = this.getOfflinePlayer(uuid); } return reference; } @Override public Player getPlayer(String player) { return this.getConfigurationManager().getPlayerByName(player); } @Override public Player getPlayerFromUUID(String uuid) { Player player = null; for (Player p : this.getConfigurationManager().getAllPlayers()) { if (p.getUUIDString().equals(uuid)) { player = p; break; } } return player; } @Override public Player getPlayerFromUUID(UUID uuid) { Player player = null; for (Player p : this.getConfigurationManager().getAllPlayers()) { if (p.getUUID().equals(uuid)) { player = p; break; } } return player; } @Override public void broadcastMessage(String message) { for (Player player : this.getPlayerList()) { player.message(message); } Canary.log.info(message); } @Override public void broadcastMessageToOps(String message) { for (Player player : this.getPlayerList()) { if (player.isOperator()) { player.message(message); } } Canary.log.info(message); } @Override public void broadcastMessageToAdmins(String message) { for (Player player : this.getPlayerList()) { if (player.isAdmin()) { player.message(message); } } Canary.log.info(message); } @Override public boolean loadWorld(String name, long seed) { return false; } @Override public World getWorld(String name) { return null; } @Override public World getDefaultWorld() { return null; } @Override public ConfigurationManager getConfigurationManager() { return (ConfigurationManager) this.serverConfigManager; } @Override public void initiateShutdown(String message) { initiateShutdown(); } @Override public void restart(boolean reloadCanary) { } @Override public boolean isRunning() { return this.serverRunning; } @Override public Recipe addRecipe(CraftingRecipe recipe) { return null; } @Override public List<Recipe> getServerRecipes() { return (List) CraftingManager.getInstance().getRecipeList(); } @Override public boolean removeRecipe(Recipe recipe) { return false; } @Override public void addSmeltingRecipe(SmeltRecipe recipe) { FurnaceRecipes.instance().addSmelting(Item.getItemById(recipe.getItemIDFrom()), (ItemStack) recipe.getResult(), recipe.getXP()); } @Override public List<SmeltRecipe> getServerSmeltRecipes() { final List<SmeltRecipe> smeltRecipes = Lists.newArrayList(); for (Map.Entry<ItemStack, ItemStack> entry : FurnaceRecipes.instance().getSmeltingList().entrySet()) { smeltRecipes.add(new SmeltRecipe((net.canarymod.api.inventory.Item) entry.getKey(), (net.canarymod.api.inventory.Item) entry.getValue())); } return smeltRecipes; } @Override public boolean removeSmeltRecipe(SmeltRecipe recipe) { return false; } @Override public void addGUI(GUIControl gui) { } @Override public long[] getTickTimeArray() { return this.tickTimeArray; } @Override public String getCanaryModVersion() { return Canary.getImplementationVersion(); } @Override public String getServerVersion() { return this.getMinecraftVersion(); } @Override public int getProtocolVersion() { return this.statusResponse.getProtocolVersionInfo().getProtocol(); } @Override public String getServerGUILog() { return null; } @Override public GUIControl getCurrentGUI() { return null; } @Override public boolean isHeadless() { return GraphicsEnvironment.isHeadless(); } @Override public boolean addSynchronousTask(ServerTask task) { return ServerTaskManager.addTask(task); } @Override public boolean removeSynchronousTask(ServerTask task) { return ServerTaskManager.removeTask(task); } @Override public void sendPlayerListData(PlayerListData data) { } @Override public int getCurrentTick() { return this.tickCounter; } @Override public float getTicksPerSecond() { // Thanks to Sponge for this, much nicer than the task that CanaryMod used. double nanoSPerTick = MathHelper.average(this.tickTimeArray); // Cap at 20 TPS return (float) (1000 / Math.max(50, nanoSPerTick / 1000000)); } @Override public void showTitle(ChatComponent title) { this.showTitle(title, null); } @Override public void showTitle(ChatComponent title, ChatComponent subtitle) { for (Player player : this.getPlayerList()) { player.showTitle(title, subtitle); } } @Override public boolean hasPermission(String node) { final PermissionCheckHook hook = (PermissionCheckHook) new PermissionCheckHook(node, this, true).call(); return hook.getResult(); } @Override public boolean safeHasPermission(String permission) { return true; } @Override public String getLocale() { return Configuration.getServerConfig().getServerLocale(); } @Override public void prepareSpawnArea(WorldServer worldServer) { int i1 = 0; this.setUserMessage("menu.generatingTerrain"); logger.info("Preparing start region for level " + ((World) worldServer).getFqName() + " (" + worldServer.provider.getDimensionId() + ")"); BlockPos spawnPoint = worldServer.getSpawnPoint(); long k1 = getCurrentTimeMillis(); for (int l1 = -192; l1 <= 192 && this.isServerRunning(); l1 += 16) { for (int i2 = -192; i2 <= 192 && this.isServerRunning(); i2 += 16) { long j2 = getCurrentTimeMillis(); if (j2 - k1 > 1000L) { this.outputPercentRemaining("Preparing spawn area", i1 * 100 / 625); k1 = j2; } ++i1; worldServer.theChunkProviderServer.loadChunk(spawnPoint.getX() + l1 >> 4, spawnPoint.getZ() + i2 >> 4); } } this.clearCurrentTask(); } }