ForgePatch/fml/src/main/java/cpw/mods/fml/client/FMLClientHandler.java

714 lines
24 KiB
Java
Raw Normal View History

2012-05-04 21:02:12 +00:00
/*
* The FML Forge Mod Loader suite. Copyright (C) 2012 cpw
*
* 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; either version 2.1 of the License, or any later version.
*
* 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 cpw.mods.fml.client;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
2012-05-04 21:02:12 +00:00
import java.util.logging.Logger;
import net.minecraft.client.Minecraft;
2013-12-11 23:46:25 +00:00
import net.minecraft.client.entity.EntityClientPlayerMP;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiIngameMenu;
import net.minecraft.client.gui.GuiMainMenu;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiSelectWorld;
import net.minecraft.client.gui.ServerListEntryNormal;
import net.minecraft.client.multiplayer.GuiConnecting;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.client.multiplayer.WorldClient;
2014-01-16 19:58:28 +00:00
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.client.network.OldServerPinger;
2013-12-11 23:46:25 +00:00
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.client.resources.IReloadableResourceManager;
import net.minecraft.client.resources.IResourcePack;
import net.minecraft.crash.CrashReport;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.launchwrapper.Launch;
import net.minecraft.network.INetHandler;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.ServerStatusResponse;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.ResourceLocation;
import org.apache.logging.log4j.Level;
2014-01-02 16:51:16 +00:00
import com.google.common.base.Strings;
2012-08-17 13:24:38 +00:00
import com.google.common.base.Throwables;
2014-01-02 16:51:16 +00:00
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableList;
2012-08-17 13:24:38 +00:00
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.gson.JsonObject;
2013-12-11 23:46:25 +00:00
import cpw.mods.fml.client.registry.RenderingRegistry;
2012-07-23 19:03:17 +00:00
import cpw.mods.fml.common.DummyModContainer;
2012-10-24 13:41:46 +00:00
import cpw.mods.fml.common.DuplicateModsFoundException;
2012-05-25 01:06:27 +00:00
import cpw.mods.fml.common.FMLCommonHandler;
2012-07-23 19:03:17 +00:00
import cpw.mods.fml.common.FMLLog;
2012-05-25 01:06:27 +00:00
import cpw.mods.fml.common.IFMLSidedHandler;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.LoaderException;
2012-07-23 19:03:17 +00:00
import cpw.mods.fml.common.MetadataCollection;
import cpw.mods.fml.common.MissingModsException;
2012-05-25 01:06:27 +00:00
import cpw.mods.fml.common.ModContainer;
import cpw.mods.fml.common.ModMetadata;
2012-07-22 14:26:38 +00:00
import cpw.mods.fml.common.ObfuscationReflectionHelper;
import cpw.mods.fml.common.WrongMinecraftVersionException;
import cpw.mods.fml.common.registry.GameData;
2013-05-27 14:28:00 +00:00
import cpw.mods.fml.common.toposort.ModSortingException;
import cpw.mods.fml.relauncher.Side;
2012-05-04 21:02:12 +00:00
2012-05-04 21:02:12 +00:00
/**
* Handles primary communication from hooked code into the system
*
2013-07-02 23:39:02 +00:00
* The FML entry point is {@link #beginMinecraftLoading(Minecraft, List)} called from
* {@link Minecraft}
*
2012-05-04 21:02:12 +00:00
* Obfuscated code should focus on this class and other members of the "server"
* (or "client") code
*
2012-05-04 21:02:12 +00:00
* The actual mod loading is handled at arms length by {@link Loader}
*
2012-05-04 21:02:12 +00:00
* It is expected that a similar class will exist for each target environment:
* Bukkit and Client side.
*
2012-05-04 21:02:12 +00:00
* It should not be directly modified.
*
2012-05-04 21:02:12 +00:00
* @author cpw
*
2012-05-04 21:02:12 +00:00
*/
public class FMLClientHandler implements IFMLSidedHandler
{
/**
* The singleton
*/
private static final FMLClientHandler INSTANCE = new FMLClientHandler();
/**
* A reference to the server itself
*/
private Minecraft client;
2012-07-23 19:03:17 +00:00
private DummyModContainer optifineContainer;
@SuppressWarnings("unused")
private boolean guiLoaded;
@SuppressWarnings("unused")
2012-07-23 19:03:17 +00:00
private boolean serverIsRunning;
private MissingModsException modsMissing;
2013-05-27 14:28:00 +00:00
private ModSortingException modSorting;
2013-07-28 18:52:12 +00:00
private boolean loading = true;
private WrongMinecraftVersionException wrongMC;
private CustomModLoadingErrorDisplayException customError;
2012-10-24 13:41:46 +00:00
private DuplicateModsFoundException dupesFound;
private boolean serverShouldBeKilledQuietly;
private List<IResourcePack> resourcePackList;
@SuppressWarnings("unused")
private IReloadableResourceManager resourceManager;
private Map<String, IResourcePack> resourcePackMap;
2014-01-02 16:51:16 +00:00
private BiMap<ModContainer, IModGuiFactory> guiFactories;
private Map<ServerStatusResponse,JsonObject> extraServerListData;
private Map<ServerData, ExtendedServerListData> serverDataTag;
/**
2012-10-25 20:18:42 +00:00
* Called to start the whole game off
*
2012-10-25 20:18:42 +00:00
* @param minecraft The minecraft instance being launched
2013-07-02 23:39:02 +00:00
* @param resourcePackList The resource pack list we will populate with mods
* @param resourceManager The resource manager
*/
@SuppressWarnings("unchecked")
public void beginMinecraftLoading(Minecraft minecraft, @SuppressWarnings("rawtypes") List resourcePackList, IReloadableResourceManager resourceManager)
2012-05-04 21:02:12 +00:00
{
2013-03-10 06:15:16 +00:00
client = minecraft;
this.resourcePackList = resourcePackList;
this.resourceManager = resourceManager;
this.resourcePackMap = Maps.newHashMap();
2012-07-31 02:31:07 +00:00
if (minecraft.func_71355_q())
2012-07-24 01:20:37 +00:00
{
FMLLog.severe("DEMO MODE DETECTED, FML will not work. Finishing now.");
haltGame("FML will not run in demo mode", new RuntimeException());
return;
}
2012-07-31 02:31:07 +00:00
FMLCommonHandler.instance().beginLoading(this);
try
{
Class<?> optifineConfig = Class.forName("Config", false, Loader.instance().getModClassLoader());
2012-07-23 19:03:17 +00:00
String optifineVersion = (String) optifineConfig.getField("VERSION").get(null);
Map<String,Object> dummyOptifineMeta = ImmutableMap.<String,Object>builder().put("name", "Optifine").put("version", optifineVersion).build();
2012-08-23 20:43:28 +00:00
ModMetadata optifineMetadata = MetadataCollection.from(getClass().getResourceAsStream("optifinemod.info"),"optifine").getMetadataForId("optifine", dummyOptifineMeta);
2012-07-23 19:03:17 +00:00
optifineContainer = new DummyModContainer(optifineMetadata);
FMLLog.info("Forge Mod Loader has detected optifine %s, enabling compatibility features",optifineContainer.getVersion());
}
catch (Exception e)
{
optifineContainer = null;
}
try
{
Loader.instance().loadMods();
}
catch (WrongMinecraftVersionException wrong)
{
wrongMC = wrong;
}
2012-10-24 13:41:46 +00:00
catch (DuplicateModsFoundException dupes)
{
dupesFound = dupes;
}
catch (MissingModsException missing)
{
modsMissing = missing;
}
2013-05-27 14:28:00 +00:00
catch (ModSortingException sorting)
{
modSorting = sorting;
}
catch (CustomModLoadingErrorDisplayException custom)
{
2013-12-16 16:47:48 +00:00
FMLLog.log(Level.ERROR, custom, "A custom exception was thrown by a mod, the game will now halt");
customError = custom;
}
catch (LoaderException le)
{
haltGame("There was a severe problem during mod loading that has caused the game to fail", le);
return;
}
Map<String,Map<String,String>> sharedModList = (Map<String, Map<String, String>>) Launch.blackboard.get("modList");
if (sharedModList == null)
{
sharedModList = Maps.newHashMap();
Launch.blackboard.put("modList", sharedModList);
}
for (ModContainer mc : Loader.instance().getActiveModList())
{
Map<String,String> sharedModDescriptor = mc.getSharedModDescriptor();
if (sharedModDescriptor != null)
{
String sharedModId = "fml:"+mc.getModId();
sharedModList.put(sharedModId, sharedModDescriptor);
}
}
2012-05-04 21:02:12 +00:00
}
@Override
public void haltGame(String message, Throwable t)
{
2012-07-31 02:31:07 +00:00
client.func_71377_b(new CrashReport(message, t));
2012-07-24 01:20:37 +00:00
throw Throwables.propagate(t);
}
2012-05-04 21:02:12 +00:00
/**
* Called a bit later on during initialization to finish loading mods
* Also initializes key bindings
*
2012-05-04 21:02:12 +00:00
*/
2013-12-11 23:46:25 +00:00
@SuppressWarnings({ "deprecation", "unchecked" })
public void finishMinecraftLoading()
2012-05-04 21:02:12 +00:00
{
2013-05-27 14:28:00 +00:00
if (modsMissing != null || wrongMC != null || customError!=null || dupesFound!=null || modSorting!=null)
{
return;
}
try
{
Loader.instance().initializeMods();
}
catch (CustomModLoadingErrorDisplayException custom)
{
2013-12-16 16:47:48 +00:00
FMLLog.log(Level.ERROR, custom, "A custom exception was thrown by a mod, the game will now halt");
customError = custom;
return;
}
catch (LoaderException le)
{
haltGame("There was a severe problem during mod loading that has caused the game to fail", le);
return;
}
// Reload resources
2013-12-16 16:47:48 +00:00
// client.func_110436_a();
2013-12-11 23:46:25 +00:00
RenderingRegistry.instance().loadEntityRenderers((Map<Class<? extends Entity>, Render>)RenderManager.field_78727_a.field_78729_o);
2014-01-02 16:51:16 +00:00
guiFactories = HashBiMap.create();
for (ModContainer mc : Loader.instance().getActiveModList())
{
String className = mc.getGuiClassName();
if (Strings.isNullOrEmpty(className))
{
continue;
}
try
{
Class<?> clazz = Class.forName(className, true, Loader.instance().getModClassLoader());
Class<? extends IModGuiFactory> guiClassFactory = clazz.asSubclass(IModGuiFactory.class);
IModGuiFactory guiFactory = guiClassFactory.newInstance();
guiFactory.initialize(client);
guiFactories.put(mc, guiFactory);
} catch (Exception e)
{
FMLLog.log(Level.ERROR, e, "A critical error occurred instantiating the gui factory for mod %s", mc.getModId());
}
}
loading = false;
}
@SuppressWarnings("unused")
public void extendModList()
{
@SuppressWarnings("unchecked")
Map<String,Map<String,String>> modList = (Map<String, Map<String, String>>) Launch.blackboard.get("modList");
if (modList != null)
{
for (Entry<String, Map<String, String>> modEntry : modList.entrySet())
{
String sharedModId = modEntry.getKey();
String system = sharedModId.split(":")[0];
if ("fml".equals(system))
{
continue;
}
Map<String, String> mod = modEntry.getValue();
String modSystem = mod.get("modsystem"); // the modsystem (FML uses FML or ModLoader)
String modId = mod.get("id"); // unique ID
String modVersion = mod.get("version"); // version
String modName = mod.get("name"); // a human readable name
String modURL = mod.get("url"); // a URL for the mod (can be empty string)
String modAuthors = mod.get("authors"); // a csv of authors (can be empty string)
String modDescription = mod.get("description"); // a (potentially) multiline description (can be empty string)
}
}
}
public void onInitializationComplete()
{
if (wrongMC != null)
{
showGuiScreen(new GuiWrongMinecraft(wrongMC));
}
else if (modsMissing != null)
{
showGuiScreen(new GuiModsMissing(modsMissing));
}
2012-10-24 13:41:46 +00:00
else if (dupesFound != null)
{
showGuiScreen(new GuiDupesFound(dupesFound));
2012-10-24 13:41:46 +00:00
}
2013-05-27 14:28:00 +00:00
else if (modSorting != null)
{
showGuiScreen(new GuiSortingProblem(modSorting));
2013-05-27 14:28:00 +00:00
}
else if (customError != null)
{
showGuiScreen(new GuiCustomModLoadingErrorScreen(customError));
}
else
{
}
2012-05-04 21:02:12 +00:00
}
/**
* Get the server instance
*/
public Minecraft getClient()
{
return client;
}
/**
* Get a handle to the client's logger instance
* The client actually doesn't have one- so we return null
*/
public Logger getMinecraftLogger()
{
return null;
}
/**
* @return the instance
*/
public static FMLClientHandler instance()
{
return INSTANCE;
}
/**
* @param player
* @param gui
*/
public void displayGuiScreen(EntityPlayer player, GuiScreen gui)
{
2012-07-31 02:31:07 +00:00
if (client.field_71439_g==player && gui != null) {
client.func_147108_a(gui);
}
}
/**
* @param mods
*/
public void addSpecialModEntries(ArrayList<ModContainer> mods)
{
if (optifineContainer!=null) {
mods.add(optifineContainer);
}
}
@Override
public List<String> getAdditionalBrandingInformation()
{
if (optifineContainer!=null)
{
return Arrays.asList(String.format("Optifine %s",optifineContainer.getVersion()));
} else {
return ImmutableList.<String>of();
}
}
@Override
public Side getSide()
{
return Side.CLIENT;
}
2012-07-31 02:31:07 +00:00
public boolean hasOptifine()
{
return optifineContainer!=null;
2012-07-06 14:29:17 +00:00
}
2012-08-08 04:31:24 +00:00
@Override
public void showGuiScreen(Object clientGuiElement)
{
GuiScreen gui = (GuiScreen) clientGuiElement;
client.func_147108_a(gui);
2012-08-08 04:31:24 +00:00
}
2012-08-09 05:58:14 +00:00
2013-12-11 23:46:25 +00:00
public WorldClient getWorldClient()
2012-08-09 05:58:14 +00:00
{
2013-12-11 23:46:25 +00:00
return client.field_71441_e;
}
2012-08-09 05:58:14 +00:00
2013-12-11 23:46:25 +00:00
public EntityClientPlayerMP getClientPlayerEntity()
{
return client.field_71439_g;
2012-08-09 05:58:14 +00:00
}
2012-08-09 12:40:32 +00:00
@Override
public void beginServerLoading(MinecraftServer server)
{
serverShouldBeKilledQuietly = false;
// NOOP
}
@Override
public void finishServerLoading()
{
// NOOP
}
@Override
public MinecraftServer getServer()
{
return client.func_71401_C();
}
2013-12-10 22:29:26 +00:00
public void displayMissingMods(Object modMissingPacket)
{
2013-12-10 22:29:26 +00:00
// showGuiScreen(new GuiModsMissingForServer(modMissingPacket));
}
/**
* If the client is in the midst of loading, we disable saving so that custom settings aren't wiped out
*/
public boolean isLoading()
{
return loading;
}
2012-09-06 14:03:30 +00:00
@Override
public boolean shouldServerShouldBeKilledQuietly()
{
return serverShouldBeKilledQuietly;
}
/**
* Is this GUI type open?
*
* @param gui The type of GUI to test for
* @return if a GUI of this type is open
*/
public boolean isGUIOpen(Class<? extends GuiScreen> gui)
{
return client.field_71462_r != null && client.field_71462_r.getClass().equals(gui);
}
@Override
public void addModAsResource(ModContainer container)
{
Class<?> resourcePackType = container.getCustomResourcePackClass();
if (resourcePackType != null)
{
try
{
IResourcePack pack = (IResourcePack) resourcePackType.getConstructor(ModContainer.class).newInstance(container);
resourcePackList.add(pack);
resourcePackMap.put(container.getModId(), pack);
}
catch (NoSuchMethodException e)
{
2013-12-16 16:47:48 +00:00
FMLLog.log(Level.ERROR, "The container %s (type %s) returned an invalid class for it's resource pack.", container.getName(), container.getClass().getName());
return;
}
catch (Exception e)
{
2013-12-16 16:47:48 +00:00
FMLLog.log(Level.ERROR, e, "An unexpected exception occurred constructing the custom resource pack for %s", container.getName());
throw Throwables.propagate(e);
}
}
}
@Override
public void updateResourcePackList()
{
client.func_110436_a();
}
public IResourcePack getResourcePackFor(String modId)
{
return resourcePackMap.get(modId);
}
@Override
public String getCurrentLanguage()
{
return client.func_135016_M().func_135041_c().func_135034_a();
}
@Override
public void serverStopped()
{
// If the server crashes during startup, it might hang the client- reset the client so it can abend properly.
MinecraftServer server = getServer();
if (server != null && !server.func_71200_ad())
{
ObfuscationReflectionHelper.setPrivateValue(MinecraftServer.class, server, true, "field_71296"+"_Q","serverIs"+"Running");
}
}
@Override
public INetHandler getClientPlayHandler()
{
return this.client.func_147114_u();
}
@Override
public NetworkManager getClientToServerNetworkManager()
{
return this.client.func_147114_u()!=null ? this.client.func_147114_u().func_147298_b() : null;
}
public void handleClientWorldClosing(WorldClient world)
{
NetworkManager client = getClientToServerNetworkManager();
// ONLY revert a non-local connection
if (client != null && !client.func_150731_c())
{
GameData.revertToFrozen();
}
}
public void tryLoadWorld(GuiSelectWorld selectWorldGUI, int selectedIndex)
{
playClientBlock = new CountDownLatch(1);
selectWorldGUI.func_146615_e(selectedIndex);
}
public void showInGameModOptions(GuiIngameMenu guiIngameMenu)
{
showGuiScreen(new GuiIngameModOptions(guiIngameMenu));
}
2014-01-02 16:51:16 +00:00
public IModGuiFactory getGuiFactoryFor(ModContainer selectedMod)
{
return guiFactories.get(selectedMod);
}
public void setupServerList()
{
extraServerListData = Collections.synchronizedMap(Maps.<ServerStatusResponse,JsonObject>newHashMap());
serverDataTag = Collections.synchronizedMap(Maps.<ServerData,ExtendedServerListData>newHashMap());
}
public void captureAdditionalData(ServerStatusResponse serverstatusresponse, JsonObject jsonobject)
{
if (jsonobject.has("modinfo"))
{
JsonObject fmlData = jsonobject.get("modinfo").getAsJsonObject();
extraServerListData.put(serverstatusresponse, fmlData);
}
}
public void bindServerListData(ServerData data, ServerStatusResponse originalResponse)
{
if (extraServerListData.containsKey(originalResponse))
{
JsonObject jsonData = extraServerListData.get(originalResponse);
String type = jsonData.get("type").getAsString();
int modCount = jsonData.get("modList").getAsJsonArray().size();
boolean moddedClientAllowed = jsonData.has("clientModsAllowed") ? jsonData.get("clientModsAllowed").getAsBoolean() : true;
serverDataTag.put(data, new ExtendedServerListData(type, true, modCount, !moddedClientAllowed));
}
else
{
String serverDescription = data.field_78843_d;
boolean moddedClientAllowed = true;
if (!Strings.isNullOrEmpty(serverDescription))
{
moddedClientAllowed = !serverDescription.endsWith(":NOFML§r");
}
serverDataTag.put(data, new ExtendedServerListData("VANILLA", false, -1, !moddedClientAllowed));
}
startupConnectionData.countDown();
}
private static final ResourceLocation iconSheet = new ResourceLocation("fml:textures/gui/icons.png");
private static final CountDownLatch startupConnectionData = new CountDownLatch(1);
public String enhanceServerListEntry(ServerListEntryNormal serverListEntry, ServerData serverEntry, int x, int width, int y, int relativeMouseX, int relativeMouseY)
{
String tooltip;
int idx;
boolean blocked = false;
if (serverDataTag.containsKey(serverEntry))
{
ExtendedServerListData extendedData = serverDataTag.get(serverEntry);
if ("FML".equals(extendedData.type) && extendedData.isCompatible)
{
idx = 0;
tooltip = String.format("Compatible FML modded server\n%d mods present", extendedData.modCount);
}
else if ("FML".equals(extendedData.type) && !extendedData.isCompatible)
{
idx = 16;
tooltip = String.format("Incompatible FML modded server\n%d mods present", extendedData.modCount);
}
else if ("BUKKIT".equals(extendedData.type))
{
idx = 32;
tooltip = String.format("Bukkit modded server");
}
else if ("VANILLA".equals(extendedData.type))
{
idx = 48;
tooltip = String.format("Vanilla server");
}
else
{
idx = 64;
tooltip = String.format("Unknown server data");
}
blocked = extendedData.isBlocked;
}
else
{
return null;
}
this.client.func_110434_K().func_110577_a(iconSheet);
Gui.func_146110_a(x + width - 18, y + 10, 0, (float)idx, 16, 16, 256.0f, 256.0f);
if (blocked)
{
Gui.func_146110_a(x + width - 18, y + 10, 0, 80, 16, 16, 256.0f, 256.0f);
}
return relativeMouseX > width - 15 && relativeMouseX < width && relativeMouseY > 10 && relativeMouseY < 26 ? tooltip : null;
}
public String fixDescription(String description)
{
return description.endsWith(":NOFML§r") ? description.substring(0, description.length() - 8)+"§r" : description;
}
public void connectToServerAtStartup(String host, int port)
{
setupServerList();
OldServerPinger osp = new OldServerPinger();
ServerData serverData = new ServerData("Command Line", host+":"+port);
try
{
osp.func_147224_a(serverData);
startupConnectionData.await(30, TimeUnit.SECONDS);
}
catch (Exception e)
{
showGuiScreen(new GuiConnecting(new GuiMainMenu(), client, host, port));
return;
}
connectToServer(new GuiMainMenu(), serverData);
}
public void connectToServer(GuiScreen guiMultiplayer, ServerData serverEntry)
{
ExtendedServerListData extendedData = serverDataTag.get(serverEntry);
if (extendedData.isBlocked)
{
showGuiScreen(new GuiAccessDenied(guiMultiplayer, serverEntry));
}
else
{
showGuiScreen(new GuiConnecting(guiMultiplayer, client, serverEntry));
}
2014-01-16 19:58:28 +00:00
playClientBlock = new CountDownLatch(1);
}
private CountDownLatch playClientBlock;
public void setPlayClient(NetHandlerPlayClient netHandlerPlayClient)
{
playClientBlock.countDown();
}
@Override
public void waitForPlayClient()
{
boolean gotIt = false;
try
{
gotIt = playClientBlock.await(1,TimeUnit.SECONDS);
} catch (InterruptedException e)
{
}
if (!gotIt)
{
throw new RuntimeException("Timeout waiting for client thread to catch up!");
}
}
2012-05-04 21:02:12 +00:00
}