ForgePatch/src/main/java/net/minecraftforge/fml/common/FMLCommonHandler.java

678 lines
21 KiB
Java

/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fml.common;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.EnumConnectionState;
import net.minecraft.network.INetHandler;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.handshake.client.C00Handshake;
import net.minecraft.network.login.server.SPacketDisconnect;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.IThreadListener;
import net.minecraft.util.text.TextComponentString;
import net.minecraft.world.World;
import net.minecraft.world.storage.SaveHandler;
import net.minecraft.world.storage.WorldInfo;
import net.minecraftforge.client.model.animation.Animation;
import net.minecraftforge.common.ForgeVersion;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.util.CompoundDataFixer;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.StartupQuery;
import net.minecraftforge.fml.WorldPersistenceHooks;
import net.minecraftforge.fml.BrandingControl;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.common.gameevent.InputEvent;
import net.minecraftforge.fml.common.gameevent.PlayerEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent.Phase;
import net.minecraftforge.fml.common.network.FMLNetworkEvent;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import net.minecraftforge.fml.common.thread.SidedThreadGroup;
import net.minecraftforge.fml.ModContainer;
import net.minecraftforge.fml.server.FMLServerHandler;
import org.apache.logging.log4j.Logger;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Maps;
/**
* The main class for non-obfuscated hook handling code
*
* Anything that doesn't require obfuscated or client/server specific code should
* go in this handler
*
* It also contains a reference to the sided handler instance that is valid
* allowing for common code to access specific properties from the obfuscated world
* without a direct dependency
*
* @author cpw
*
*/
public class FMLCommonHandler
{
/**
* The singleton
*/
private static final FMLCommonHandler INSTANCE = new FMLCommonHandler();
/**
* The delegate for side specific data and functions
*/
private IFMLSidedHandler sidedDelegate;
private boolean noForge;
private List<String> brandings;
private List<String> brandingsNoMC;
private Set<SaveHandler> handlerSet = Collections.newSetFromMap(new MapMaker().weakKeys().<SaveHandler,Boolean>makeMap());
private WeakReference<SaveHandler> handlerToCheck;
private IEventBus eventBus = MinecraftForge.EVENT_BUS;
private volatile CountDownLatch exitLatch = null;
private FMLCommonHandler()
{
}
/**
* The FML event bus. Subscribe here for FML related events
*
* @Deprecated Use {@link MinecraftForge#EVENT_BUS} they're the same thing now
* @return the event bus
*/
@Deprecated
public IEventBus bus()
{
return eventBus;
}
public List<String> beginLoading(IFMLSidedHandler handler)
{
sidedDelegate = handler;
MinecraftForge.initialize();
// MinecraftForge.registerCrashCallable();
return ImmutableList.<String>of();
}
/**
* @return the instance
*/
public static FMLCommonHandler instance()
{
return INSTANCE;
}
/**
* Find the container that associates with the supplied mod object
* @param mod
*/
public ModContainer findContainerFor(Object mod)
{
if (mod instanceof String)
{
return Loader.instance().getIndexedModList().get(mod);
}
else
{
return Loader.instance().getReversedModObjectList().get(mod);
}
}
/**
* Get the forge mod loader logging instance (goes to the forgemodloader log file)
* @return The log instance for the FML log file
*
* @deprecated Not used in FML, Mods use your own logger, see {@link FMLPreInitializationEvent#getModLog()}
*/
@Deprecated
public Logger getFMLLogger()
{
return FMLLog.log;
}
public Side getSide()
{
return sidedDelegate.getSide();
}
/**
* Return the effective side for the context in the game. This is dependent
* on thread analysis to try and determine whether the code is running in the
* server or not. Use at your own risk
*/
public Side getEffectiveSide()
{
final ThreadGroup group = Thread.currentThread().getThreadGroup();
return group instanceof SidedThreadGroup ? ((SidedThreadGroup) group).getSide() : Side.CLIENT;
}
/**
* Raise an exception
*/
public void raiseException(Throwable exception, String message, boolean stopGame)
{
FMLLog.log.error("Something raised an exception. The message was '{}'. 'stopGame' is {}", stopGame, exception);
if (stopGame)
{
getSidedDelegate().haltGame(message,exception);
}
}
public List<String> getBrandings(boolean includeMC)
{
if (brandings == null)
{
BrandingControl.computeBranding();
}
return includeMC ? ImmutableList.copyOf(brandings) : ImmutableList.copyOf(brandingsNoMC);
}
public IFMLSidedHandler getSidedDelegate()
{
return sidedDelegate;
}
public void onPostServerTick()
{
bus().post(new TickEvent.ServerTickEvent(Phase.END));
}
/**
* Every tick just after world and other ticks occur
*/
public void onPostWorldTick(World world)
{
bus().post(new TickEvent.WorldTickEvent(Side.SERVER, Phase.END, world));
}
public void onPreServerTick()
{
bus().post(new TickEvent.ServerTickEvent(Phase.START));
}
/**
* Every tick just before world and other ticks occur
*/
public void onPreWorldTick(World world)
{
bus().post(new TickEvent.WorldTickEvent(Side.SERVER, Phase.START, world));
}
public boolean handleServerAboutToStart(MinecraftServer server)
{
return Loader.instance().serverAboutToStart(server);
}
public boolean handleServerStarting(MinecraftServer server)
{
return Loader.instance().serverStarting(server);
}
public void handleServerStarted()
{
Loader.instance().serverStarted();
sidedDelegate.allowLogins();
}
public void handleServerStopping()
{
Loader.instance().serverStopping();
}
public File getSavesDirectory() {
return sidedDelegate.getSavesDirectory();
}
public MinecraftServer getMinecraftServerInstance()
{
return sidedDelegate.getServer();
}
public void showGuiScreen(Object clientGuiElement)
{
sidedDelegate.showGuiScreen(clientGuiElement);
}
public void queryUser(StartupQuery query) throws InterruptedException
{
sidedDelegate.queryUser(query);
}
public void onServerStart(MinecraftServer dedicatedServer)
{
FMLServerHandler.instance();
sidedDelegate.beginServerLoading(dedicatedServer);
}
public void onServerStarted()
{
sidedDelegate.finishServerLoading();
}
public void onPreClientTick()
{
bus().post(new TickEvent.ClientTickEvent(Phase.START));
}
public void onPostClientTick()
{
bus().post(new TickEvent.ClientTickEvent(Phase.END));
}
public void onRenderTickStart(float timer)
{
Animation.setClientPartialTickTime(timer);
bus().post(new TickEvent.RenderTickEvent(Phase.START, timer));
}
public void onRenderTickEnd(float timer)
{
bus().post(new TickEvent.RenderTickEvent(Phase.END, timer));
}
public void onPlayerPreTick(EntityPlayer player)
{
bus().post(new TickEvent.PlayerTickEvent(Phase.START, player));
}
public void onPlayerPostTick(EntityPlayer player)
{
bus().post(new TickEvent.PlayerTickEvent(Phase.END, player));
}
public void handleWorldDataSave(SaveHandler handler, WorldInfo worldInfo, NBTTagCompound tagCompound)
{
for (ModContainer mc : Loader.instance().getModList())
{
if (mc instanceof InjectedModContainer)
{
WorldPersistenceHooks.WorldPersistenceHook wac = ((InjectedModContainer)mc).getWrappedWorldAccessContainer();
if (wac != null)
{
NBTTagCompound dataForWriting = wac.getDataForWriting(handler, worldInfo);
tagCompound.setTag(mc.getModId(), dataForWriting);
}
}
}
}
public void handleWorldDataLoad(SaveHandler handler, WorldInfo worldInfo, NBTTagCompound tagCompound)
{
if (getEffectiveSide()!=LogicalSide.SERVER)
{
return;
}
if (handlerSet.contains(handler))
{
return;
}
handlerSet.add(handler);
handlerToCheck = new WeakReference<SaveHandler>(handler); // for confirmBackupLevelDatUse
Map<String,NBTBase> additionalProperties = Maps.newHashMap();
worldInfo.setAdditionalProperties(additionalProperties);
for (ModContainer mc : Loader.instance().getModList())
{
if (mc instanceof InjectedModContainer)
{
WorldPersistenceHooks.WorldPersistenceHook wac = ((InjectedModContainer)mc).getWrappedWorldAccessContainer();
if (wac != null)
{
wac.readData(handler, worldInfo, additionalProperties, tagCompound.getCompoundTag(mc.getModId()));
}
}
}
}
public void confirmBackupLevelDatUse(SaveHandler handler)
{
if (handlerToCheck == null || handlerToCheck.get() != handler) {
// only run if the save has been initially loaded
handlerToCheck = null;
return;
}
String text = "Forge Mod Loader detected that the backup level.dat is being used.\n\n" +
"This may happen due to a bug or corruption, continuing can damage\n" +
"your world beyond repair or lose data / progress.\n\n" +
"It's recommended to create a world backup before continuing.";
boolean confirmed = StartupQuery.confirm(text);
if (!confirmed) StartupQuery.abort();
}
public boolean isDisplayCloseRequested()
{
return sidedDelegate != null && sidedDelegate.isDisplayCloseRequested();
}
public boolean shouldServerBeKilledQuietly()
{
if (sidedDelegate == null)
{
return false;
}
return sidedDelegate.shouldServerShouldBeKilledQuietly();
}
/**
* Make handleExit() wait for handleServerStopped().
*
* For internal use only!
*/
public void expectServerStopped()
{
exitLatch = new CountDownLatch(1);
}
/**
* Delayed System.exit() until the server is actually stopped/done saving.
*
* For internal use only!
*
* @param retVal Exit code for System.exit()
*/
public void handleExit(int retVal)
{
CountDownLatch latch = exitLatch;
if (latch != null)
{
try
{
FMLLog.log.info("Waiting for the server to terminate/save.");
if (!latch.await(10, TimeUnit.SECONDS))
{
FMLLog.log.warn("The server didn't stop within 10 seconds, exiting anyway.");
}
else
{
FMLLog.log.info("Server terminated.");
}
}
catch (InterruptedException e)
{
FMLLog.log.warn("Interrupted wait, exiting.");
}
}
System.exit(retVal);
}
public void handleServerStopped()
{
sidedDelegate.serverStopped();
MinecraftServer server = getMinecraftServerInstance();
Loader.instance().serverStopped();
// FORCE the internal server to stop: hello optifine workaround!
if (server!=null) ObfuscationReflectionHelper.setPrivateValue(MinecraftServer.class, server, false, "field_71316"+"_v", "u", "serverStopped");
// allow any pending exit to continue, clear exitLatch
CountDownLatch latch = exitLatch;
if (latch != null)
{
latch.countDown();
exitLatch = null;
}
}
public String getModName()
{
List<String> modNames = Lists.newArrayListWithExpectedSize(3);
modNames.add("fml");
if (!noForge)
{
modNames.add(ForgeVersion.MOD_ID);
}
if (Loader.instance().getFMLBrandingProperties().containsKey("snooperbranding"))
{
modNames.add(Loader.instance().getFMLBrandingProperties().get("snooperbranding"));
}
return Joiner.on(',').join(modNames);
}
public void addModToResourcePack(ModContainer container)
{
sidedDelegate.addModAsResource(container);
}
public String getCurrentLanguage()
{
return sidedDelegate.getCurrentLanguage();
}
public void bootstrap()
{
}
public NetworkManager getClientToServerNetworkManager()
{
return sidedDelegate.getClientToServerNetworkManager();
}
public void fireMouseInput()
{
bus().post(new InputEvent.MouseInputEvent());
}
public void fireKeyInput()
{
bus().post(new InputEvent.KeyInputEvent());
}
public void firePlayerChangedDimensionEvent(EntityPlayer player, int fromDim, int toDim)
{
bus().post(new PlayerEvent.PlayerChangedDimensionEvent(player, fromDim, toDim));
}
public void firePlayerLoggedIn(EntityPlayer player)
{
bus().post(new PlayerEvent.PlayerLoggedInEvent(player));
}
public void firePlayerLoggedOut(EntityPlayer player)
{
bus().post(new PlayerEvent.PlayerLoggedOutEvent(player));
}
public void firePlayerRespawnEvent(EntityPlayer player, boolean endConquered)
{
bus().post(new PlayerEvent.PlayerRespawnEvent(player, endConquered));
}
public void firePlayerItemPickupEvent(EntityPlayer player, EntityItem item, ItemStack clone)
{
bus().post(new PlayerEvent.ItemPickupEvent(player, item, clone));
}
public void firePlayerCraftingEvent(EntityPlayer player, ItemStack crafted, IInventory craftMatrix)
{
bus().post(new PlayerEvent.ItemCraftedEvent(player, crafted, craftMatrix));
}
public void firePlayerSmeltedEvent(EntityPlayer player, ItemStack smelted)
{
bus().post(new PlayerEvent.ItemSmeltedEvent(player, smelted));
}
public INetHandler getClientPlayHandler()
{
return sidedDelegate.getClientPlayHandler();
}
public void fireNetRegistrationEvent(NetworkManager manager, Set<String> channelSet, String channel, Side side)
{
sidedDelegate.fireNetRegistrationEvent(bus(), manager, channelSet, channel, side);
}
public boolean shouldAllowPlayerLogins()
{
return sidedDelegate.shouldAllowPlayerLogins();
}
public void fireServerConnectionEvent(NetworkManager manager)
{
bus().post(new FMLNetworkEvent.ServerConnectionFromClientEvent(manager));
}
/**
* Process initial Handshake packet, kicks players from the server if they are connecting while we are starting up.
* Also verifies the client has the FML marker.
*
* @param packet Handshake Packet
* @param manager NetworkDirection connection
* @return True to allow connection, otherwise False.
*/
public boolean handleServerHandshake(C00Handshake packet, NetworkManager manager)
{
if (!shouldAllowPlayerLogins())
{
TextComponentString text = new TextComponentString("Server is still starting! Please wait before reconnecting.");
LOGGER.info("Disconnecting Player: {}", text.getUnformattedText());
manager.sendPacket(new SPacketDisconnect(text));
manager.closeChannel(text);
return false;
}
if (packet.getRequestedState() == EnumConnectionState.LOGIN && (!NetworkRegistry.INSTANCE.isVanillaAccepted(Side.CLIENT) && !packet.hasFMLMarker()))
{
manager.setConnectionState(EnumConnectionState.LOGIN);
TextComponentString text = new TextComponentString("This server has mods that require FML/Forge to be installed on the client. Contact your server admin for more details.");
Collection<String> modNames = NetworkRegistry.INSTANCE.getRequiredMods(Side.CLIENT);
FMLLog.log.info("Disconnecting Player: This server has mods that require FML/Forge to be installed on the client: {}", modNames);
manager.sendPacket(new SPacketDisconnect(text));
manager.closeChannel(text);
return false;
}
manager.channel().attr(net.minecraftforge.fml.network.NetworkRegistry.FML_MARKER).set(packet.hasFMLMarker());
return true;
}
public void processWindowMessages()
{
if (sidedDelegate == null) return;
sidedDelegate.processWindowMessages();
}
/**
* Used to exit from java, with system exit preventions in place. Will be tidy about it and just log a message,
* unless debugging is enabled
*
* @param exitCode The exit code
* @param hardExit Perform a halt instead of an exit (only use when the world is unsavable) - read the warnings at {@link Runtime#halt(int)}
*/
public void exitJava(int exitCode, boolean hardExit)
{
FMLLog.log.warn("Java has been asked to exit (code {})", exitCode);
if (hardExit)
{
FMLLog.log.warn("This is an abortive exit and could cause world corruption or other things");
}
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
FMLLog.log.warn("Exit trace:");
//The first 2 elements are Thread#getStackTrace and FMLCommonHandler#exitJava and aren't relevant
for (int i = 2; i < stack.length; i++)
{
FMLLog.log.warn("\t{}", stack[i]);
}
if (hardExit)
{
Runtime.getRuntime().halt(exitCode);
}
else
{
Runtime.getRuntime().exit(exitCode);
}
}
public IThreadListener getWorldThread(INetHandler net)
{
return sidedDelegate.getWorldThread(net);
}
public static void callFuture(FutureTask<?> task)
{
try
{
task.run();
task.get(); // Forces the exception to be thrown if any
}
catch (InterruptedException | ExecutionException e)
{
FMLLog.log.fatal("Exception caught executing FutureTask: {}", e.toString(), e);
}
}
public String stripSpecialChars(String message)
{
return sidedDelegate != null ? sidedDelegate.stripSpecialChars(message) : message;
}
public void reloadRenderers() {
sidedDelegate.reloadRenderers();
}
public void fireSidedRegistryEvents()
{
sidedDelegate.fireSidedRegistryEvents();
}
public CompoundDataFixer getDataFixer()
{
return (CompoundDataFixer)sidedDelegate.getDataFixer();
}
public boolean isDisplayVSyncForced() { return sidedDelegate.isDisplayVSyncForced(); }
public void resetClientRecipeBook() {
this.sidedDelegate.resetClientRecipeBook();
}
public void reloadSearchTrees() {
this.sidedDelegate.reloadSearchTrees();
}
public void reloadCreativeSettings()
{
this.sidedDelegate.reloadCreativeSettings();
}
}