base mod created

This commit is contained in:
Mohammad-Ali Minaie
2018-10-08 09:07:47 -04:00
parent 0a7700c356
commit b86dedad2f
7848 changed files with 584664 additions and 1 deletions

View File

@@ -0,0 +1,82 @@
/*
* 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.client;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiErrorScreen;
import net.minecraft.client.gui.GuiScreen;
import net.minecraftforge.fml.common.EnhancedRuntimeException;
import net.minecraftforge.fml.common.IFMLHandledException;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
/**
* If a mod throws this exception during loading, it will be called back to render
* the error screen through the methods below. This error will not be cleared, and will
* not allow the game to carry on, but might be useful if your mod wishes to report
* a fatal configuration error in a pretty way.
*
* Throw this through a proxy. It won't work on the dedicated server environment.
* @author cpw
*
*/
@SideOnly(Side.CLIENT)
public abstract class CustomModLoadingErrorDisplayException extends EnhancedRuntimeException implements IFMLHandledException, IDisplayableError
{
public CustomModLoadingErrorDisplayException() {
}
public CustomModLoadingErrorDisplayException(String message, Throwable cause)
{
super(message, cause);
}
private static final long serialVersionUID = 1L;
/**
* Called after the GUI is initialized by the parent code. You can do extra stuff here, maybe?
*
* @param errorScreen The error screen we're painting
* @param fontRenderer A font renderer for you
*/
public abstract void initGui(GuiErrorScreen errorScreen, FontRenderer fontRenderer);
/**
* Draw your error to the screen.
*
* <br/><em>Warning: Minecraft is in a deep error state.</em> <strong>All</strong> it can do is stop.
* Do not try and do anything involving complex user interaction here.
*
* @param errorScreen The error screen to draw to
* @param fontRenderer A font renderer for you
* @param mouseRelX Mouse X
* @param mouseRelY Mouse Y
* @param tickTime tick time
*/
public abstract void drawScreen(GuiErrorScreen errorScreen, FontRenderer fontRenderer, int mouseRelX, int mouseRelY, float tickTime);
@Override public void printStackTrace(EnhancedRuntimeException.WrappedPrintStream s){}; // Do Nothing unless the modder wants to.
@Override
public final GuiScreen createGui()
{
return new GuiCustomModLoadingErrorScreen(this);
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.client;
import java.util.Set;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraftforge.fml.client.config.GuiConfig;
import net.minecraftforge.fml.common.ModContainer;
import net.minecraftforge.fml.client.IModGuiFactory.RuntimeOptionCategoryElement;
public class DefaultGuiFactory implements IModGuiFactory
{
protected String modid, title;
protected Minecraft minecraft;
protected DefaultGuiFactory(String modid, String title)
{
this.modid = modid;
this.title = title;
}
@Override
public boolean hasConfigGui()
{
return true;
}
@Override
public void initialize(Minecraft minecraftInstance)
{
this.minecraft = minecraftInstance;
}
@Override
public GuiScreen createConfigGui(GuiScreen parentScreen)
{
return new GuiConfig(parentScreen, modid, title);
}
@Override
public Set<RuntimeOptionCategoryElement> runtimeGuiCategories()
{
return null;
}
public static IModGuiFactory forMod(ModContainer mod)
{
return new DefaultGuiFactory(mod.getModId(), mod.getName());
}
}

View File

@@ -0,0 +1,37 @@
/*
* 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.client;
import java.util.Map;
public class ExtendedServerListData {
public final String type;
public final boolean isCompatible;
public final Map<String,String> modData;
public final boolean isBlocked;
public ExtendedServerListData(String type, boolean isCompatible, Map<String,String> modData, boolean isBlocked)
{
this.type = type;
this.isCompatible = isCompatible;
this.modData = modData;
this.isBlocked = isBlocked;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,151 @@
/*
* 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.client;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import com.google.common.collect.ImmutableSet;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.fml.client.config.ConfigGuiType;
import net.minecraftforge.fml.client.config.DummyConfigElement;
import net.minecraftforge.fml.client.config.GuiConfig;
import net.minecraftforge.fml.client.config.GuiEditArray;
import net.minecraftforge.fml.client.config.GuiEditArrayEntries;
import net.minecraftforge.fml.client.config.IConfigElement;
import net.minecraftforge.fml.client.config.DummyConfigElement.DummyCategoryElement;
import net.minecraftforge.fml.client.config.DummyConfigElement.DummyListElement;
import net.minecraftforge.fml.client.config.GuiConfigEntries.NumberSliderEntry;
import net.minecraftforge.fml.client.IModGuiFactory.RuntimeOptionCategoryElement;
public class FMLConfigGuiFactory implements IModGuiFactory
{
public static class FMLConfigGuiScreen extends GuiConfig
{
public FMLConfigGuiScreen(GuiScreen parent)
{
super(parent, getConfigElements(), "FML", false, false, I18n.format("fml.config.sample.title"));
}
private static List<IConfigElement> getConfigElements()
{
List<IConfigElement> list = new ArrayList<IConfigElement>();
List<IConfigElement> listsList = new ArrayList<IConfigElement>();
List<IConfigElement> stringsList = new ArrayList<IConfigElement>();
List<IConfigElement> numbersList = new ArrayList<IConfigElement>();
Pattern commaDelimitedPattern = Pattern.compile("([A-Za-z]+((,){1}( )*|$))+?");
// Top Level Settings
list.add(new DummyConfigElement("imABoolean", true, ConfigGuiType.BOOLEAN, "fml.config.sample.imABoolean").setRequiresMcRestart(true));
list.add(new DummyConfigElement("imAnInteger", 42, ConfigGuiType.INTEGER, "fml.config.sample.imAnInteger", -1, 256).setRequiresMcRestart(true));
list.add(new DummyConfigElement("imADouble", 42.4242D, ConfigGuiType.DOUBLE, "fml.config.sample.imADouble", -1.0D, 256.256D).setRequiresMcRestart(true));
list.add(new DummyConfigElement("imAString", "http://www.montypython.net/scripts/string.php", ConfigGuiType.STRING, "fml.config.sample.imAString").setRequiresMcRestart(true));
// Lists category
listsList.add(new DummyListElement("booleanList", new Boolean[] {true, false, true, false, true, false, true, false}, ConfigGuiType.BOOLEAN, "fml.config.sample.booleanList"));
listsList.add(new DummyListElement("booleanListFixed", new Boolean[] {true, false, true, false, true, false, true, false}, ConfigGuiType.BOOLEAN, "fml.config.sample.booleanListFixed", true));
listsList.add(new DummyListElement("booleanListMax", new Boolean[] {true, false, true, false, true, false, true, false}, ConfigGuiType.BOOLEAN, "fml.config.sample.booleanListMax", 10));
listsList.add(new DummyListElement("doubleList", new Double[] {0.0D, 1.1D, 2.2D, 3.3D, 4.4D, 5.5D, 6.6D, 7.7D, 8.8D, 9.9D}, ConfigGuiType.DOUBLE, "fml.config.sample.doubleList"));
listsList.add(new DummyListElement("doubleListFixed", new Double[] {0.0D, 1.1D, 2.2D, 3.3D, 4.4D, 5.5D, 6.6D, 7.7D, 8.8D, 9.9D}, ConfigGuiType.DOUBLE, "fml.config.sample.doubleListFixed", true));
listsList.add(new DummyListElement("doubleListMax", new Double[] {0.0D, 1.1D, 2.2D, 3.3D, 4.4D, 5.5D, 6.6D, 7.7D, 8.8D, 9.9D}, ConfigGuiType.DOUBLE, "fml.config.sample.doubleListMax", 15));
listsList.add(new DummyListElement("doubleListBounded", new Double[] {0.0D, 1.1D, 2.2D, 3.3D, 4.4D, 5.5D, 6.6D, 7.7D, 8.8D, 9.9D}, ConfigGuiType.DOUBLE, "fml.config.sample.doubleListBounded", -1.0D, 10.0D));
listsList.add(new DummyListElement("integerList", new Integer[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, ConfigGuiType.INTEGER, "fml.config.sample.integerList"));
listsList.add(new DummyListElement("integerListFixed", new Integer[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, ConfigGuiType.INTEGER, "fml.config.sample.integerListFixed", true));
listsList.add(new DummyListElement("integerListMax", new Integer[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, ConfigGuiType.INTEGER, "fml.config.sample.integerListMax", 15));
listsList.add(new DummyListElement("integerListBounded", new Integer[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, ConfigGuiType.INTEGER, "fml.config.sample.integerListBounded", -1, 10));
listsList.add(new DummyListElement("stringList", new String[] {"An", "array", "of", "string", "values"}, ConfigGuiType.STRING, "fml.config.sample.stringList"));
listsList.add(new DummyListElement("stringListFixed", new String[] {"A", "fixed", "length", "array", "of", "string", "values"}, ConfigGuiType.STRING, "fml.config.sample.stringListFixed", true));
listsList.add(new DummyListElement("stringListMax", new String[] {"An", "array", "of", "string", "values", "with", "a", "max", "length", "of", "15"}, ConfigGuiType.STRING, "fml.config.sample.stringListMax", 15));
listsList.add(new DummyListElement("stringListPattern", new String[] {"Valid", "Not Valid", "Is, Valid", "Comma, Separated, Value"}, ConfigGuiType.STRING, "fml.config.sample.stringListPattern", commaDelimitedPattern));
listsList.add(new DummyListElement("stringListCustom", new Object[0], ConfigGuiType.STRING, "fml.config.sample.stringListCustom").setArrayEntryClass(CustomArrayEntry.class));
list.add(new DummyCategoryElement("lists", "fml.config.sample.ctgy.lists", listsList));
// Strings category
stringsList.add(new DummyConfigElement("basicString", "Just a regular String value, anything goes.", ConfigGuiType.STRING, "fml.config.sample.basicString"));
stringsList.add(new DummyConfigElement("cycleString", "this", ConfigGuiType.STRING, "fml.config.sample.cycleString", new String[] {"this", "property", "cycles", "through", "a", "list", "of", "valid", "choices"}));
stringsList.add(new DummyConfigElement("patternString", "only, comma, separated, words, can, be, entered, in, this, box", ConfigGuiType.STRING, "fml.config.sample.patternString", commaDelimitedPattern));
stringsList.add(new DummyConfigElement("chatColorPicker", "c", ConfigGuiType.COLOR, "fml.config.sample.chatColorPicker", new String[] {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}));
stringsList.add(new DummyConfigElement("modIDSelector", "FML", ConfigGuiType.MOD_ID, "fml.config.sample.modIDSelector"));
list.add(new DummyCategoryElement("strings", "fml.config.sample.ctgy.strings", stringsList));
// Numbers category
numbersList.add((new DummyConfigElement("basicInteger", 42, ConfigGuiType.INTEGER, "fml.config.sample.basicInteger")));
numbersList.add((new DummyConfigElement("boundedInteger", 42, ConfigGuiType.INTEGER, "fml.config.sample.boundedInteger", -1, 256)));
numbersList.add((new DummyConfigElement("sliderInteger", 2000, ConfigGuiType.INTEGER, "fml.config.sample.sliderInteger", 100, 10000)).setCustomListEntryClass(NumberSliderEntry.class));
numbersList.add(new DummyConfigElement("basicDouble", 42.4242D, ConfigGuiType.DOUBLE, "fml.config.sample.basicDouble"));
numbersList.add(new DummyConfigElement("boundedDouble", 42.4242D, ConfigGuiType.DOUBLE, "fml.config.sample.boundedDouble", -1.0D, 256.256D));
numbersList.add(new DummyConfigElement("sliderDouble", 42.4242D, ConfigGuiType.DOUBLE, "fml.config.sample.sliderDouble", -1.0D, 256.256D).setCustomListEntryClass(NumberSliderEntry.class));
list.add(new DummyCategoryElement("numbers", "fml.config.sample.ctgy.numbers", numbersList));
return list;
}
}
@Override
public boolean hasConfigGui()
{
return true;
}
public static class CustomArrayEntry extends GuiEditArrayEntries.StringEntry
{
public CustomArrayEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement, Object value)
{
super(owningScreen, owningEntryList, configElement, value);
}
@Override
public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, int mouseX, int mouseY, boolean isSelected, float partial)
{
textFieldValue.setTextColor((int) (Math.random() * 0xFFFFFF));
super.drawEntry(slotIndex, x, y, listWidth, slotHeight, mouseX, mouseY, isSelected, partial);
}
}
@Override
public void initialize(Minecraft minecraftInstance)
{
}
@Override
public GuiScreen createConfigGui(GuiScreen parentScreen)
{
return new FMLConfigGuiScreen(parentScreen);
}
private static final Set<RuntimeOptionCategoryElement> fmlCategories = ImmutableSet.of(new RuntimeOptionCategoryElement("HELP", "FML"));
@Override
public Set<RuntimeOptionCategoryElement> runtimeGuiCategories()
{
return fmlCategories;
}
}

View File

@@ -0,0 +1,86 @@
/*
* 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.client;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import net.minecraftforge.fml.common.FMLLog;
import org.apache.logging.log4j.LogManager;
import javax.imageio.ImageIO;
import net.minecraft.client.resources.FileResourcePack;
import net.minecraftforge.fml.common.FMLContainerHolder;
import net.minecraftforge.fml.common.ModContainer;
public class FMLFileResourcePack extends FileResourcePack implements FMLContainerHolder {
private ModContainer container;
public FMLFileResourcePack(ModContainer container)
{
super(container.getSource());
this.container = container;
}
@Override
public String getPackName()
{
return "FMLFileResourcePack:"+container.getName();
}
@Override
protected InputStream getInputStreamByName(String resourceName) throws IOException
{
try
{
return super.getInputStreamByName(resourceName);
}
catch (IOException ioe)
{
if ("pack.mcmeta".equals(resourceName))
{
FMLLog.log.debug("Mod {} is missing a pack.mcmeta file, substituting a dummy one", container.getName());
return new ByteArrayInputStream(("{\n" +
" \"pack\": {\n"+
" \"description\": \"dummy FML pack for "+container.getName()+"\",\n"+
" \"pack_format\": 2\n"+
"}\n" +
"}").getBytes(StandardCharsets.UTF_8));
}
else throw ioe;
}
}
@Override
public BufferedImage getPackImage() throws IOException
{
return ImageIO.read(getInputStreamByName(container.getMetadata().logoFile));
}
@Override
public ModContainer getFMLContainer()
{
return container;
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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.client;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import net.minecraftforge.fml.common.FMLLog;
import org.apache.logging.log4j.LogManager;
import javax.imageio.ImageIO;
import net.minecraft.client.resources.FolderResourcePack;
import net.minecraftforge.fml.common.FMLContainerHolder;
import net.minecraftforge.fml.common.ModContainer;
public class FMLFolderResourcePack extends FolderResourcePack implements FMLContainerHolder {
private ModContainer container;
public FMLFolderResourcePack(ModContainer container)
{
super(container.getSource());
this.container = container;
}
@Override
protected boolean hasResourceName(String name)
{
return super.hasResourceName(name);
}
@Override
public String getPackName()
{
return "FMLFileResourcePack:"+container.getName();
}
@Override
protected InputStream getInputStreamByName(String resourceName) throws IOException
{
try
{
return super.getInputStreamByName(resourceName);
}
catch (IOException ioe)
{
if ("pack.mcmeta".equals(resourceName))
{
FMLLog.log.debug("Mod {} is missing a pack.mcmeta file, substituting a dummy one", container.getName());
return new ByteArrayInputStream(("{\n" +
" \"pack\": {\n"+
" \"description\": \"dummy FML pack for "+container.getName()+"\",\n"+
" \"pack_format\": 2\n"+
"}\n" +
"}").getBytes(StandardCharsets.UTF_8));
}
else throw ioe;
}
}
@Override
public BufferedImage getPackImage() throws IOException
{
return ImageIO.read(getInputStreamByName(container.getMetadata().logoFile));
}
@Override
public ModContainer getFMLContainer()
{
return container;
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.client;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.client.resources.I18n;
public class GuiAccessDenied extends GuiScreen
{
private GuiScreen parent;
private ServerData data;
public GuiAccessDenied(GuiScreen parent, ServerData data)
{
this.parent = parent;
this.data = data;
}
/**
* Adds the buttons (and other controls) to the screen in question. Called when the GUI is displayed and when the
* window resizes, the buttonList is cleared beforehand.
*/
@Override
public void initGui()
{
this.buttonList.add(new GuiButton(1, this.width / 2 - 75, this.height - 38, I18n.format("gui.done")));
}
/**
* Called by the controls from the buttonList when activated. (Mouse pressed for buttons)
*/
@Override
protected void actionPerformed(GuiButton p_73875_1_)
{
if (p_73875_1_.enabled && p_73875_1_.id == 1)
{
FMLClientHandler.instance().showGuiScreen(parent);
}
}
/**
* Draws the screen and all the components in it.
*/
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.drawDefaultBackground();
int offset = Math.max(85 - 2 * 10, 10);
this.drawCenteredString(this.fontRenderer, "Forge Mod Loader could not connect to this server", this.width / 2, offset, 0xFFFFFF);
offset += 10;
this.drawCenteredString(this.fontRenderer, String.format("The server %s has forbidden modded access", data.serverName), this.width / 2, offset, 0xFFFFFF);
super.drawScreen(mouseX, mouseY, partialTicks);
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.client;
import java.io.File;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
public class GuiBackupFailed extends GuiScreen
{
private GuiScreen parent;
private File zipName;
public GuiBackupFailed(GuiScreen parent, File zipName)
{
this.parent = parent;
this.zipName = zipName;
}
/**
* Adds the buttons (and other controls) to the screen in question. Called when the GUI is displayed and when the
* window resizes, the buttonList is cleared beforehand.
*/
@Override
public void initGui()
{
this.buttonList.add(new GuiButton(1, this.width / 2 - 75, this.height - 38, I18n.format("gui.done")));
}
/**
* Called by the controls from the buttonList when activated. (Mouse pressed for buttons)
*/
@Override
protected void actionPerformed(GuiButton p_73875_1_)
{
if (p_73875_1_.enabled && p_73875_1_.id == 1)
{
FMLClientHandler.instance().showGuiScreen(parent);
}
}
/**
* Draws the screen and all the components in it.
*/
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.drawDefaultBackground();
int offset = Math.max(85 - 2 * 10, 10);
this.drawCenteredString(this.fontRenderer, String.format("There was an error saving the archive %s", zipName.getName()), this.width / 2, offset, 0xFFFFFF);
offset += 10;
this.drawCenteredString(this.fontRenderer, String.format("Please fix the problem and try again"), this.width / 2, offset, 0xFFFFFF);
super.drawScreen(mouseX, mouseY, partialTicks);
}
}

View File

@@ -0,0 +1,58 @@
/*
* 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.client;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiOptionButton;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.fml.common.StartupQuery;
public class GuiConfirmation extends GuiNotification
{
public GuiConfirmation(StartupQuery query)
{
super(query);
}
/**
* Adds the buttons (and other controls) to the screen in question. Called when the GUI is displayed and when the
* window resizes, the buttonList is cleared beforehand.
*/
@Override
public void initGui()
{
this.buttonList.add(new GuiOptionButton(0, this.width / 2 - 155, this.height - 38, I18n.format("gui.yes")));
this.buttonList.add(new GuiOptionButton(1, this.width / 2 - 155 + 160, this.height - 38, I18n.format("gui.no")));
}
/**
* Called by the controls from the buttonList when activated. (Mouse pressed for buttons)
*/
@Override
protected void actionPerformed(GuiButton button)
{
if (button.enabled && (button.id == 0 || button.id == 1))
{
FMLClientHandler.instance().showGuiScreen(null);
query.setResult(button.id == 0);
query.finish();
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.client;
public class GuiCustomModLoadingErrorScreen extends GuiErrorBase
{
private CustomModLoadingErrorDisplayException customException;
public GuiCustomModLoadingErrorScreen(CustomModLoadingErrorDisplayException customException)
{
this.customException = customException;
}
/**
* Adds the buttons (and other controls) to the screen in question. Called when the GUI is displayed and when the
* window resizes, the buttonList is cleared beforehand.
*/
@Override
public void initGui()
{
super.initGui();
this.customException.initGui(this, fontRenderer);
}
/**
* Draws the screen and all the components in it.
*/
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.drawDefaultBackground();
super.drawScreen(mouseX, mouseY, partialTicks);
this.customException.drawScreen(this, fontRenderer, mouseX, mouseY, partialTicks);
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.client;
import net.minecraftforge.fml.common.DuplicateModsFoundException;
import net.minecraftforge.fml.common.ModContainer;
import java.io.File;
import java.util.Map.Entry;
public class GuiDupesFound extends GuiErrorBase
{
private DuplicateModsFoundException dupes;
public GuiDupesFound(DuplicateModsFoundException dupes)
{
this.dupes = dupes;
}
/**
* Draws the screen and all the components in it.
*/
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.drawDefaultBackground();
int offset = Math.max(85 - dupes.dupes.size() * 10, 10);
this.drawCenteredString(this.fontRenderer, "Forge Mod Loader has found a problem with your minecraft installation", this.width / 2, offset, 0xFFFFFF);
offset+=10;
this.drawCenteredString(this.fontRenderer, "You have mod sources that are duplicate within your system", this.width / 2, offset, 0xFFFFFF);
offset+=10;
this.drawCenteredString(this.fontRenderer, "Mod Id : File name", this.width / 2, offset, 0xFFFFFF);
offset+=5;
for (Entry<ModContainer, File> mc : dupes.dupes.entries())
{
offset+=10;
this.drawCenteredString(this.fontRenderer, String.format("%s : %s", mc.getKey().getModId(), mc.getValue().getName()), this.width / 2, offset, 0xEEEEEE);
}
super.drawScreen(mouseX, mouseY, partialTicks);
}
}

View File

@@ -0,0 +1,101 @@
/*
* 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.client;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiErrorScreen;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.Loader;
import java.awt.*;
import java.io.File;
public class GuiErrorBase extends GuiErrorScreen
{
static final File minecraftDir = new File(Loader.instance().getConfigDir().getParent());
static final File logFile = new File(minecraftDir, "logs/latest.log");
public GuiErrorBase()
{
super(null, null);
}
private String translateOrDefault(String translateKey, String alternative, Object... format)
{
return I18n.hasKey(translateKey) ? I18n.format(translateKey, format) : String.format(alternative, format); //When throwing a DuplicateModsException, the translation system does not work...
}
/**
* Adds the buttons (and other controls) to the screen in question. Called when the GUI is displayed and when the
* window resizes, the buttonList is cleared beforehand.
*/
@Override
public void initGui()
{
super.initGui();
this.buttonList.clear();
this.buttonList.add(new GuiButton(10, 50, this.height - 38, this.width / 2 - 55, 20, translateOrDefault("fml.button.open.mods.folder", "Open Mods Folder")));
String openFileText = translateOrDefault("fml.button.open.file", "Open %s", logFile.getName());
this.buttonList.add(new GuiButton(11, this.width / 2 + 5, this.height - 38, this.width / 2 - 55, 20, openFileText));
}
/**
* Called by the controls from the buttonList when activated. (Mouse pressed for buttons)
*/
@Override
protected void actionPerformed(GuiButton button)
{
if (button.id == 10)
{
try
{
File modsDir = new File(minecraftDir, "mods");
Desktop.getDesktop().open(modsDir);
}
catch (Exception e)
{
FMLLog.log.error("Problem opening mods folder", e);
}
}
else if (button.id == 11)
{
try
{
Desktop.getDesktop().open(logFile);
}
catch (Exception e)
{
FMLLog.log.error("Problem opening log file {}", logFile, e);
}
}
}
/**
* Draws the screen and all the components in it.
*/
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
for(GuiButton button : buttonList)
{
button.drawButton(this.mc, mouseX, mouseY, partialTicks);
}
}
}

View File

@@ -0,0 +1,636 @@
/*
* 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.client;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map.Entry;
import javax.annotation.Nullable;
import javax.imageio.ImageIO;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiTextField;
import net.minecraft.client.gui.GuiUtilRenderComponents;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.resources.I18n;
import net.minecraft.client.resources.IResourcePack;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentString;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.StringUtils;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.common.ForgeVersion;
import net.minecraftforge.common.ForgeVersion.CheckResult;
import net.minecraftforge.common.ForgeVersion.Status;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.ModContainer;
import net.minecraftforge.fml.common.ModContainer.Disableable;
import net.minecraftforge.fml.common.versioning.ComparableVersion;
import static net.minecraft.util.text.TextFormatting.*;
import org.lwjgl.input.Mouse;
import com.google.common.base.Strings;
import org.lwjgl.opengl.GL11;
/**
* @author cpw
*
*/
public class GuiModList extends GuiScreen
{
private enum SortType implements Comparator<ModContainer>
{
NORMAL(24),
A_TO_Z(25){ @Override protected int compare(String name1, String name2){ return name1.compareTo(name2); }},
Z_TO_A(26){ @Override protected int compare(String name1, String name2){ return name2.compareTo(name1); }};
private int buttonID;
private SortType(int buttonID)
{
this.buttonID = buttonID;
}
@Nullable
public static SortType getTypeForButton(GuiButton button)
{
for (SortType t : values())
{
if (t.buttonID == button.id)
{
return t;
}
}
return null;
}
protected int compare(String name1, String name2){ return 0; }
@Override
public int compare(ModContainer o1, ModContainer o2)
{
String name1 = StringUtils.stripControlCodes(o1.getName()).toLowerCase();
String name2 = StringUtils.stripControlCodes(o2.getName()).toLowerCase();
return compare(name1, name2);
}
}
private GuiScreen mainMenu;
private GuiSlotModList modList;
private GuiScrollingList modInfo;
private int selected = -1;
private ModContainer selectedMod;
private int listWidth;
private ArrayList<ModContainer> mods;
private GuiButton configModButton;
private GuiButton disableModButton;
private int buttonMargin = 1;
private int numButtons = SortType.values().length;
private String lastFilterText = "";
private GuiTextField search;
private boolean sorted = false;
private SortType sortType = SortType.NORMAL;
/**
* @param mainMenu
*/
public GuiModList(GuiScreen mainMenu)
{
this.mainMenu = mainMenu;
this.mods = new ArrayList<ModContainer>();
FMLClientHandler.instance().addSpecialModEntries(mods);
// Add child mods to their parent's list
for (ModContainer mod : Loader.instance().getModList())
{
if (mod.getMetadata() != null && mod.getMetadata().parentMod == null && !Strings.isNullOrEmpty(mod.getMetadata().parent))
{
String parentMod = mod.getMetadata().parent;
ModContainer parentContainer = Loader.instance().getIndexedModList().get(parentMod);
if (parentContainer != null)
{
mod.getMetadata().parentMod = parentContainer;
parentContainer.getMetadata().childMods.add(mod);
continue;
}
}
else if (mod.getMetadata() != null && mod.getMetadata().parentMod != null)
{
continue;
}
mods.add(mod);
}
}
/**
* Adds the buttons (and other controls) to the screen in question. Called when the GUI is displayed and when the
* window resizes, the buttonList is cleared beforehand.
*/
@Override
public void initGui()
{
int slotHeight = 35;
for (ModContainer mod : mods)
{
listWidth = Math.max(listWidth,getFontRenderer().getStringWidth(mod.getName()) + 10);
listWidth = Math.max(listWidth,getFontRenderer().getStringWidth(mod.getVersion()) + 5 + slotHeight);
}
listWidth = Math.min(listWidth, 150);
this.modList = new GuiSlotModList(this, mods, listWidth, slotHeight);
this.buttonList.add(new GuiButton(6, ((modList.right + this.width) / 2) - 100, this.height - 38, I18n.format("gui.done")));
configModButton = new GuiButton(20, 10, this.height - 49, this.listWidth, 20, "Config");
disableModButton = new GuiButton(21, 10, this.height - 27, this.listWidth, 20, "Disable");
this.buttonList.add(configModButton);
this.buttonList.add(disableModButton);
search = new GuiTextField(0, getFontRenderer(), 12, modList.bottom + 17, modList.listWidth - 4, 14);
search.setFocused(true);
search.setCanLoseFocus(true);
int width = (modList.listWidth / numButtons);
int x = 10, y = 10;
GuiButton normalSort = new GuiButton(SortType.NORMAL.buttonID, x, y, width - buttonMargin, 20, I18n.format("fml.menu.mods.normal"));
normalSort.enabled = false;
buttonList.add(normalSort);
x += width + buttonMargin;
buttonList.add(new GuiButton(SortType.A_TO_Z.buttonID, x, y, width - buttonMargin, 20, "A-Z"));
x += width + buttonMargin;
buttonList.add(new GuiButton(SortType.Z_TO_A.buttonID, x, y, width - buttonMargin, 20, "Z-A"));
updateCache();
}
/**
* Called when the mouse is clicked. Args : mouseX, mouseY, clickedButton
*/
@Override
protected void mouseClicked(int x, int y, int button) throws IOException
{
super.mouseClicked(x, y, button);
search.mouseClicked(x, y, button);
if (button == 1 && x >= search.x && x < search.x + search.width && y >= search.y && y < search.y + search.height) {
search.setText("");
}
}
/**
* Fired when a key is typed (except F11 which toggles full screen). This is the equivalent of
* KeyListener.keyTyped(KeyEvent e). Args : character (character on the key), keyCode (lwjgl Keyboard key code)
*/
@Override
protected void keyTyped(char c, int keyCode) throws IOException
{
super.keyTyped(c, keyCode);
search.textboxKeyTyped(c, keyCode);
}
/**
* Called from the main game loop to update the screen.
*/
@Override
public void updateScreen()
{
super.updateScreen();
search.updateCursorCounter();
if (!search.getText().equals(lastFilterText))
{
reloadMods();
sorted = false;
}
if (!sorted)
{
reloadMods();
Collections.sort(mods, sortType);
selected = modList.selectedIndex = mods.indexOf(selectedMod);
sorted = true;
}
}
private void reloadMods()
{
ArrayList<ModContainer> mods = modList.getMods();
mods.clear();
for (ModContainer m : Loader.instance().getActiveModList())
{
// If it passes the filter, and is not a child mod
if (m.getName().toLowerCase().contains(search.getText().toLowerCase()) && m.getMetadata().parentMod == null)
{
mods.add(m);
}
}
this.mods = mods;
lastFilterText = search.getText();
}
/**
* Called by the controls from the buttonList when activated. (Mouse pressed for buttons)
*/
@Override
protected void actionPerformed(GuiButton button) throws IOException
{
if (button.enabled)
{
SortType type = SortType.getTypeForButton(button);
if (type != null)
{
for (GuiButton b : buttonList)
{
if (SortType.getTypeForButton(b) != null)
{
b.enabled = true;
}
}
button.enabled = false;
sorted = false;
sortType = type;
this.mods = modList.getMods();
}
else
{
switch (button.id)
{
case 6:
{
this.mc.displayGuiScreen(this.mainMenu);
return;
}
case 20:
{
try
{
IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(selectedMod);
GuiScreen newScreen = guiFactory.createConfigGui(this);
this.mc.displayGuiScreen(newScreen);
}
catch (Exception e)
{
FMLLog.log.error("There was a critical issue trying to build the config GUI for {}", selectedMod.getModId(), e);
}
return;
}
}
}
}
super.actionPerformed(button);
}
public int drawLine(String line, int offset, int shifty)
{
this.fontRenderer.drawString(line, offset, shifty, 0xd7edea);
return shifty + 10;
}
/**
* Draws the screen and all the components in it.
*/
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.modList.drawScreen(mouseX, mouseY, partialTicks);
if (this.modInfo != null)
this.modInfo.drawScreen(mouseX, mouseY, partialTicks);
int left = ((this.width - this.listWidth - 38) / 2) + this.listWidth + 30;
this.drawCenteredString(this.fontRenderer, "Mod List", left, 16, 0xFFFFFF);
super.drawScreen(mouseX, mouseY, partialTicks);
String text = I18n.format("fml.menu.mods.search");
int x = ((10 + modList.right) / 2) - (getFontRenderer().getStringWidth(text) / 2);
getFontRenderer().drawString(text, x, modList.bottom + 5, 0xFFFFFF);
search.drawTextBox();
}
/**
* Handles mouse input.
*/
@Override
public void handleMouseInput() throws IOException
{
int mouseX = Mouse.getEventX() * this.width / this.mc.displayWidth;
int mouseY = this.height - Mouse.getEventY() * this.height / this.mc.displayHeight - 1;
super.handleMouseInput();
if (this.modInfo != null)
this.modInfo.handleMouseInput(mouseX, mouseY);
this.modList.handleMouseInput(mouseX, mouseY);
}
Minecraft getMinecraftInstance()
{
return mc;
}
FontRenderer getFontRenderer()
{
return fontRenderer;
}
public void selectModIndex(int index)
{
if (index == this.selected)
return;
this.selected = index;
this.selectedMod = (index >= 0 && index <= mods.size()) ? mods.get(selected) : null;
updateCache();
}
public boolean modIndexSelected(int index)
{
return index == selected;
}
private void updateCache()
{
configModButton.visible = false;
disableModButton.visible = false;
modInfo = null;
if (selectedMod == null)
return;
ResourceLocation logoPath = null;
Dimension logoDims = new Dimension(0, 0);
List<String> lines = new ArrayList<String>();
CheckResult vercheck = ForgeVersion.getResult(selectedMod);
String logoFile = selectedMod.getMetadata().logoFile;
if (!logoFile.isEmpty())
{
TextureManager tm = mc.getTextureManager();
IResourcePack pack = FMLClientHandler.instance().getResourcePackFor(selectedMod.getModId());
try
{
BufferedImage logo = null;
if (pack != null)
{
logo = pack.getPackImage();
}
else
{
InputStream logoResource = getClass().getResourceAsStream(logoFile);
if (logoResource != null)
logo = ImageIO.read(logoResource);
}
if (logo != null)
{
logoPath = tm.getDynamicTextureLocation("modlogo", new DynamicTexture(logo));
logoDims = new Dimension(logo.getWidth(), logo.getHeight());
}
}
catch (IOException e) { }
}
if (!selectedMod.getMetadata().autogenerated)
{
disableModButton.visible = true;
disableModButton.enabled = true;
disableModButton.packedFGColour = 0;
Disableable disableable = selectedMod.canBeDisabled();
if (disableable == Disableable.RESTART)
{
disableModButton.packedFGColour = 0xFF3377;
}
else if (disableable != Disableable.YES)
{
disableModButton.enabled = false;
}
IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(selectedMod);
configModButton.visible = true;
configModButton.enabled = false;
if (guiFactory != null)
{
configModButton.enabled = guiFactory.hasConfigGui();
}
lines.add(selectedMod.getMetadata().name);
lines.add(String.format("Version: %s (%s)", selectedMod.getDisplayVersion(), selectedMod.getVersion()));
lines.add(String.format("Mod ID: '%s' Mod State: %s", selectedMod.getModId(), Loader.instance().getModState(selectedMod)));
if (!selectedMod.getMetadata().credits.isEmpty())
{
lines.add("Credits: " + selectedMod.getMetadata().credits);
}
lines.add("Authors: " + selectedMod.getMetadata().getAuthorList());
lines.add("URL: " + selectedMod.getMetadata().url);
if (selectedMod.getMetadata().childMods.isEmpty())
lines.add("No child mods for this mod");
else
lines.add("Child mods: " + selectedMod.getMetadata().getChildModList());
if (vercheck.status == Status.OUTDATED || vercheck.status == Status.BETA_OUTDATED)
lines.add("Update Available: " + (vercheck.url == null ? "" : vercheck.url));
lines.add(null);
lines.add(selectedMod.getMetadata().description);
}
else
{
lines.add(WHITE + selectedMod.getName());
lines.add(WHITE + "Version: " + selectedMod.getVersion());
lines.add(WHITE + "Mod State: " + Loader.instance().getModState(selectedMod));
if (vercheck.status == Status.OUTDATED || vercheck.status == Status.BETA_OUTDATED)
lines.add("Update Available: " + (vercheck.url == null ? "" : vercheck.url));
lines.add(null);
lines.add(RED + "No mod information found");
lines.add(RED + "Ask your mod author to provide a mod mcmod.info file");
}
if ((vercheck.status == Status.OUTDATED || vercheck.status == Status.BETA_OUTDATED) && vercheck.changes.size() > 0)
{
lines.add(null);
lines.add("Changes:");
for (Entry<ComparableVersion, String> entry : vercheck.changes.entrySet())
{
lines.add(" " + entry.getKey() + ":");
lines.add(entry.getValue());
lines.add(null);
}
}
modInfo = new Info(this.width - this.listWidth - 30, lines, logoPath, logoDims);
}
private class Info extends GuiScrollingList
{
@Nullable
private ResourceLocation logoPath;
private Dimension logoDims;
private List<ITextComponent> lines = null;
public Info(int width, List<String> lines, @Nullable ResourceLocation logoPath, Dimension logoDims)
{
super(GuiModList.this.getMinecraftInstance(),
width,
GuiModList.this.height,
32, GuiModList.this.height - 88 + 4,
GuiModList.this.listWidth + 20, 60,
GuiModList.this.width,
GuiModList.this.height);
this.lines = resizeContent(lines);
this.logoPath = logoPath;
this.logoDims = logoDims;
this.setHeaderInfo(true, getHeaderHeight());
}
@Override protected int getSize() { return 0; }
@Override protected void elementClicked(int index, boolean doubleClick) { }
@Override protected boolean isSelected(int index) { return false; }
@Override protected void drawBackground() {}
@Override protected void drawSlot(int slotIdx, int entryRight, int slotTop, int slotBuffer, Tessellator tess) { }
private List<ITextComponent> resizeContent(List<String> lines)
{
List<ITextComponent> ret = new ArrayList<ITextComponent>();
for (String line : lines)
{
if (line == null)
{
ret.add(null);
continue;
}
ITextComponent chat = ForgeHooks.newChatWithLinks(line, false);
int maxTextLength = this.listWidth - 8;
if (maxTextLength >= 0)
{
ret.addAll(GuiUtilRenderComponents.splitText(chat, maxTextLength, GuiModList.this.fontRenderer, false, true));
}
}
return ret;
}
private int getHeaderHeight()
{
int height = 0;
if (logoPath != null)
{
double scaleX = logoDims.width / 200.0;
double scaleY = logoDims.height / 65.0;
double scale = 1.0;
if (scaleX > 1 || scaleY > 1)
{
scale = 1.0 / Math.max(scaleX, scaleY);
}
logoDims.width *= scale;
logoDims.height *= scale;
height += logoDims.height;
height += 10;
}
height += (lines.size() * 10);
if (height < this.bottom - this.top - 8) height = this.bottom - this.top - 8;
return height;
}
@Override
protected void drawHeader(int entryRight, int relativeY, Tessellator tess)
{
int top = relativeY;
if (logoPath != null)
{
GlStateManager.enableBlend();
GuiModList.this.mc.renderEngine.bindTexture(logoPath);
BufferBuilder wr = tess.getBuffer();
int offset = (this.left + this.listWidth/2) - (logoDims.width / 2);
wr.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
wr.pos(offset, top + logoDims.height, zLevel).tex(0, 1).endVertex();
wr.pos(offset + logoDims.width, top + logoDims.height, zLevel).tex(1, 1).endVertex();
wr.pos(offset + logoDims.width, top, zLevel).tex(1, 0).endVertex();
wr.pos(offset, top, zLevel).tex(0, 0).endVertex();
tess.draw();
GlStateManager.disableBlend();
top += logoDims.height + 10;
}
for (ITextComponent line : lines)
{
if (line != null)
{
GlStateManager.enableBlend();
GuiModList.this.fontRenderer.drawStringWithShadow(line.getFormattedText(), this.left + 4, top, 0xFFFFFF);
GlStateManager.disableAlpha();
GlStateManager.disableBlend();
}
top += 10;
}
}
@Override
protected void clickHeader(int x, int y)
{
int offset = y;
if (logoPath != null) {
offset -= logoDims.height + 10;
}
if (offset <= 0)
return;
int lineIdx = offset / 10;
if (lineIdx >= lines.size())
return;
ITextComponent line = lines.get(lineIdx);
if (line != null)
{
int k = -4;
for (ITextComponent part : line) {
if (!(part instanceof TextComponentString))
continue;
k += GuiModList.this.fontRenderer.getStringWidth(((TextComponentString)part).getText());
if (k >= x)
{
GuiModList.this.handleComponentClick(part);
break;
}
}
}
}
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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.client;
import java.util.List;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.text.TextFormatting;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.MissingModsException;
import net.minecraftforge.fml.common.ModContainer;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion;
public class GuiModsMissing extends GuiErrorBase
{
private MissingModsException modsMissing;
public GuiModsMissing(MissingModsException modsMissing)
{
this.modsMissing = modsMissing;
}
/**
* Draws the screen and all the components in it.
*/
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.drawDefaultBackground();
List<MissingModsException.MissingModInfo> missingModsVersions = modsMissing.getMissingModInfos();
int offset = Math.max(85 - missingModsVersions.size() * 10, 10);
String modMissingDependenciesText = I18n.format("fml.messages.mod.missing.dependencies.compatibility", TextFormatting.BOLD + modsMissing.getModName() + TextFormatting.RESET);
this.drawCenteredString(this.fontRenderer, modMissingDependenciesText, this.width / 2, offset, 0xFFFFFF);
offset+=5;
for (MissingModsException.MissingModInfo versionInfo : missingModsVersions)
{
ArtifactVersion acceptedVersion = versionInfo.getAcceptedVersion();
String acceptedModId = acceptedVersion.getLabel();
ArtifactVersion currentVersion = versionInfo.getCurrentVersion();
String missingReason;
if (currentVersion == null)
{
missingReason = I18n.format("fml.messages.mod.missing.dependencies.missing");
}
else
{
missingReason = I18n.format("fml.messages.mod.missing.dependencies.you.have", currentVersion.getVersionString());
}
String acceptedModVersionString = acceptedVersion.getRangeString();
if (acceptedVersion instanceof DefaultArtifactVersion)
{
DefaultArtifactVersion dav = (DefaultArtifactVersion)acceptedVersion;
if (dav.getRange() != null)
{
acceptedModVersionString = dav.getRange().toStringFriendly();
}
}
ModContainer acceptedMod = Loader.instance().getIndexedModList().get(acceptedModId);
String acceptedModName = acceptedMod != null ? acceptedMod.getName() : acceptedModId;
String versionInfoText = String.format(TextFormatting.BOLD + "%s " + TextFormatting.RESET + "%s (%s)", acceptedModName, acceptedModVersionString, missingReason);
String message;
if (versionInfo.isRequired())
{
message = I18n.format("fml.messages.mod.missing.dependencies.requires", versionInfoText);
}
else
{
message = I18n.format("fml.messages.mod.missing.dependencies.compatible.with", versionInfoText);
}
offset += 10;
this.drawCenteredString(this.fontRenderer, message, this.width / 2, offset, 0xEEEEEE);
}
super.drawScreen(mouseX, mouseY, partialTicks);
}
}

View File

@@ -0,0 +1,83 @@
/*
* 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.client;
import java.util.List;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.fml.common.MissingModsException;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
public class GuiModsMissingForServer extends GuiScreen
{
private MissingModsException modsMissing;
public GuiModsMissingForServer(MissingModsException modsMissing)
{
this.modsMissing = modsMissing;
}
/**
* Adds the buttons (and other controls) to the screen in question. Called when the GUI is displayed and when the
* window resizes, the buttonList is cleared beforehand.
*/
@Override
public void initGui()
{
this.buttonList.add(new GuiButton(1, this.width / 2 - 75, this.height - 38, I18n.format("gui.done")));
}
/**
* Called by the controls from the buttonList when activated. (Mouse pressed for buttons)
*/
@Override
protected void actionPerformed(GuiButton p_73875_1_)
{
if (p_73875_1_.enabled && p_73875_1_.id == 1)
{
FMLClientHandler.instance().showGuiScreen(null);
}
}
/**
* Draws the screen and all the components in it.
*/
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.drawDefaultBackground();
List<MissingModsException.MissingModInfo> missingModsVersions = modsMissing.getMissingModInfos();
int offset = Math.max(85 - missingModsVersions.size() * 10, 10);
this.drawCenteredString(this.fontRenderer, "Forge Mod Loader could not connect to this server", this.width / 2, offset, 0xFFFFFF);
offset += 10;
this.drawCenteredString(this.fontRenderer, "The mods and versions listed below could not be found", this.width / 2, offset, 0xFFFFFF);
offset += 10;
this.drawCenteredString(this.fontRenderer, "They are required to play on this server", this.width / 2, offset, 0xFFFFFF);
offset += 5;
for (MissingModsException.MissingModInfo info : missingModsVersions)
{
ArtifactVersion v = info.getAcceptedVersion();
offset += 10;
this.drawCenteredString(this.fontRenderer, String.format("%s : %s", v.getLabel(), v.getRangeString()), this.width / 2, offset, 0xEEEEEE);
}
super.drawScreen(mouseX, mouseY, partialTicks);
}
}

View File

@@ -0,0 +1,205 @@
/*
* 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.client;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.text.TextFormatting;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.MissingModsException;
import net.minecraftforge.fml.common.ModContainer;
import net.minecraftforge.fml.common.MultipleModsErrored;
import net.minecraftforge.fml.common.WrongMinecraftVersionException;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion;
import org.lwjgl.input.Mouse;
import java.io.IOException;
import java.util.List;
public class GuiMultipleModsErrored extends GuiErrorBase
{
private final List<WrongMinecraftVersionException> wrongMinecraftExceptions;
private final List<MissingModsException> missingModsExceptions;
private GuiList list;
public GuiMultipleModsErrored(MultipleModsErrored exception)
{
wrongMinecraftExceptions = exception.wrongMinecraftExceptions;
missingModsExceptions = exception.missingModsExceptions;
}
/**
* Adds the buttons (and other controls) to the screen in question. Called when the GUI is displayed and when the
* window resizes, the buttonList is cleared beforehand.
*/
@Override
public void initGui()
{
super.initGui();
int additionalSize = missingModsExceptions.isEmpty() || wrongMinecraftExceptions.isEmpty() ? 20 : 55;
for (MissingModsException exception : missingModsExceptions)
{
additionalSize += exception.getMissingModInfos().size() * 10;
}
list = new GuiList(wrongMinecraftExceptions.size() * 10 + missingModsExceptions.size() * 15 + additionalSize);
}
/**
* Draws the screen and all the components in it.
*/
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.drawDefaultBackground();
this.list.drawScreen(mouseX, mouseY, partialTicks);
this.drawCenteredString(this.fontRenderer, I18n.format("fml.messages.mod.missing.multiple", missingModsExceptions.size() + wrongMinecraftExceptions.size()), this.width/2, 10, 0xFFFFFF);
super.drawScreen(mouseX, mouseY, partialTicks);
}
/**
* Called by the controls from the buttonList when activated. (Mouse pressed for buttons)
*/
@Override
public void actionPerformed(GuiButton button)
{
this.list.actionPerformed(button);
super.actionPerformed(button);
}
/**
* Handles mouse input.
*/
@Override
public void handleMouseInput() throws IOException
{
super.handleMouseInput();
int mouseX = Mouse.getEventX() * this.width / this.mc.displayWidth;
int mouseY = this.height - Mouse.getEventY() * this.height / this.mc.displayHeight - 1;
this.list.handleMouseInput(mouseX, mouseY);
}
private class GuiList extends GuiScrollingList
{
public GuiList(int entryHeight)
{
super(GuiMultipleModsErrored.this.mc,
GuiMultipleModsErrored.this.width-20,
GuiMultipleModsErrored.this.height -30,
30, GuiMultipleModsErrored.this.height-50,
10,
entryHeight,
GuiMultipleModsErrored.this.width,
GuiMultipleModsErrored.this.height);
}
@Override
protected int getSize()
{
return 1;
}
@Override
protected void elementClicked(int index, boolean doubleClick) {}
@Override
protected boolean isSelected(int index)
{
return false;
}
@Override
protected void drawBackground()
{
drawDefaultBackground();
}
@Override
protected void drawSlot(int slotIdx, int entryRight, int slotTop, int slotBuffer, Tessellator tess)
{
int offset = slotTop;
FontRenderer renderer = GuiMultipleModsErrored.this.fontRenderer;
if (!wrongMinecraftExceptions.isEmpty())
{
renderer.drawString(TextFormatting.UNDERLINE + I18n.format("fml.messages.mod.wrongminecraft", Loader.instance().getMinecraftModContainer().getVersion()), this.left, offset, 0xFFFFFF);
offset+=15;
for(WrongMinecraftVersionException exception : wrongMinecraftExceptions)
{
renderer.drawString(I18n.format("fml.messages.mod.wrongminecraft.requirement", TextFormatting.BOLD + exception.mod.getName() + TextFormatting.RESET, exception.mod.getModId(), exception.mod.acceptableMinecraftVersionRange().toStringFriendly()), this.left, offset, 0xFFFFFF);
offset += 10;
}
offset+=5;
renderer.drawString(I18n.format("fml.messages.mod.wrongminecraft.fix.multiple"), this.left, offset, 0xFFFFFF);
offset+=20;
}
if (!missingModsExceptions.isEmpty())
{
renderer.drawString(I18n.format("fml.messages.mod.missing.dependencies.multiple.issues"), this.left, offset, 0xFFFFFF);
offset+=15;
for (MissingModsException exception : missingModsExceptions)
{
renderer.drawString(exception.getModName() + ":", this.left, offset, 0xFFFFFF);
for (MissingModsException.MissingModInfo versionInfo : exception.getMissingModInfos())
{
ArtifactVersion acceptedVersion = versionInfo.getAcceptedVersion();
String acceptedModId = acceptedVersion.getLabel();
ArtifactVersion currentVersion = versionInfo.getCurrentVersion();
String missingReason;
if (currentVersion == null)
{
missingReason = I18n.format("fml.messages.mod.missing.dependencies.missing");
}
else
{
missingReason = I18n.format("fml.messages.mod.missing.dependencies.you.have", currentVersion.getVersionString());
}
String acceptedModVersionString = acceptedVersion.getRangeString();
if (acceptedVersion instanceof DefaultArtifactVersion)
{
DefaultArtifactVersion dav = (DefaultArtifactVersion)acceptedVersion;
if (dav.getRange() != null)
{
acceptedModVersionString = dav.getRange().toStringFriendly();
}
}
ModContainer acceptedMod = Loader.instance().getIndexedModList().get(acceptedModId);
String acceptedModName = acceptedMod != null ? acceptedMod.getName() : acceptedModId;
String versionInfoText = String.format(TextFormatting.BOLD + "%s " + TextFormatting.RESET + "%s (%s)", acceptedModName, acceptedModVersionString, missingReason);
String message;
if (versionInfo.isRequired())
{
message = I18n.format("fml.messages.mod.missing.dependencies.requires", versionInfoText);
}
else
{
message = I18n.format("fml.messages.mod.missing.dependencies.compatible.with", versionInfoText);
}
offset += 10;
renderer.drawString(message, this.left, offset, 0xEEEEEE);
}
offset += 15;
}
}
}
}
}

View File

@@ -0,0 +1,90 @@
/*
* 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.client;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.fml.common.StartupQuery;
public class GuiNotification extends GuiScreen
{
public GuiNotification(StartupQuery query)
{
this.query = query;
}
/**
* Adds the buttons (and other controls) to the screen in question. Called when the GUI is displayed and when the
* window resizes, the buttonList is cleared beforehand.
*/
@Override
public void initGui()
{
this.buttonList.add(new GuiButton(0, this.width / 2 - 100, this.height - 38, I18n.format("gui.done")));
}
/**
* Called by the controls from the buttonList when activated. (Mouse pressed for buttons)
*/
@Override
protected void actionPerformed(GuiButton button)
{
if (button.enabled && button.id == 0)
{
FMLClientHandler.instance().showGuiScreen(null);
query.finish();
}
}
/**
* Draws the screen and all the components in it.
*/
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.drawDefaultBackground();
String[] lines = query.getText().split("\n");
int spaceAvailable = this.height - 38 - 20;
int spaceRequired = Math.min(spaceAvailable, 10 + 10 * lines.length);
int offset = 10 + (spaceAvailable - spaceRequired) / 2; // vertically centered
for (String line : lines)
{
if (offset >= spaceAvailable)
{
this.drawCenteredString(this.fontRenderer, "...", this.width / 2, offset, 0xFFFFFF);
break;
}
else
{
if (!line.isEmpty()) this.drawCenteredString(this.fontRenderer, line, this.width / 2, offset, 0xFFFFFF);
offset += 10;
}
}
super.drawScreen(mouseX, mouseY, partialTicks);
}
protected final StartupQuery query;
}

View File

@@ -0,0 +1,114 @@
/*
* 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.client;
import java.io.File;
import java.io.IOException;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiYesNo;
import net.minecraft.client.gui.GuiYesNoCallback;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.StartupQuery;
import net.minecraftforge.fml.common.ZipperUtil;
public class GuiOldSaveLoadConfirm extends GuiYesNo implements GuiYesNoCallback {
private String dirName;
private String saveName;
private File zip;
private GuiScreen parent;
public GuiOldSaveLoadConfirm(String dirName, String saveName, GuiScreen parent)
{
super(null, "", "", 0);
this.parent = parent;
this.dirName = dirName;
this.saveName = saveName;
this.zip = new File(FMLClientHandler.instance().getClient().mcDataDir,String.format("%s-%2$td%2$tm%2$ty%2$tH%2$tM%2$tS.zip", dirName, System.currentTimeMillis()));
}
/**
* Draws the screen and all the components in it.
*/
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.drawDefaultBackground();
this.drawCenteredString(this.fontRenderer, String.format("The world %s contains pre-update modding data", saveName), this.width / 2, 50, 16777215);
this.drawCenteredString(this.fontRenderer, String.format("There may be problems updating it to this version"), this.width / 2, 70, 16777215);
this.drawCenteredString(this.fontRenderer, String.format("FML will save a zip to %s", zip.getName()), this.width / 2, 90, 16777215);
this.drawCenteredString(this.fontRenderer, String.format("Do you wish to continue loading?"), this.width / 2, 110, 16777215);
int k;
for (k = 0; k < this.buttonList.size(); ++k)
{
this.buttonList.get(k).drawButton(this.mc, mouseX, mouseY, partialTicks);
}
for (k = 0; k < this.labelList.size(); ++k)
{
this.labelList.get(k).drawLabel(this.mc, mouseX, mouseY);
}
}
/**
* Called by the controls from the buttonList when activated. (Mouse pressed for buttons)
*/
@Override
protected void actionPerformed(GuiButton button)
{
if (button.id == 1)
{
FMLClientHandler.instance().showGuiScreen(parent);
}
else
{
FMLLog.log.info("Capturing current state of world {} into file {}", saveName, zip.getAbsolutePath());
try
{
String skip = System.getProperty("fml.doNotBackup");
if (skip == null || !"true".equals(skip))
{
ZipperUtil.zip(new File(FMLClientHandler.instance().getSavesDir(), dirName), zip);
}
else
{
for (int x = 0; x < 10; x++)
FMLLog.log.fatal("!!!!!!!!!! UPDATING WORLD WITHOUT DOING BACKUP !!!!!!!!!!!!!!!!");
}
} catch (IOException e)
{
FMLLog.log.warn("There was a problem saving the backup {}. Please fix and try again", zip.getName(), e);
FMLClientHandler.instance().showGuiScreen(new GuiBackupFailed(parent, zip));
return;
}
FMLClientHandler.instance().showGuiScreen(null);
try
{
mc.launchIntegratedServer(dirName, saveName, null);
}
catch (StartupQuery.AbortedException e)
{
// ignore
}
}
}
}

View File

@@ -0,0 +1,395 @@
/*
* 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.client;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraftforge.fml.client.config.GuiUtils;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11;
import java.io.IOException;
import java.util.List;
public abstract class GuiScrollingList
{
private final Minecraft client;
protected final int listWidth;
protected final int listHeight;
protected final int screenWidth;
protected final int screenHeight;
protected final int top;
protected final int bottom;
protected final int right;
protected final int left;
protected final int slotHeight;
private int scrollUpActionId;
private int scrollDownActionId;
protected int mouseX;
protected int mouseY;
private float initialMouseClickY = -2.0F;
private float scrollFactor;
private float scrollDistance;
protected int selectedIndex = -1;
private long lastClickTime = 0L;
private boolean highlightSelected = true;
private boolean hasHeader;
private int headerHeight;
protected boolean captureMouse = true;
@Deprecated // We need to know screen size.
public GuiScrollingList(Minecraft client, int width, int height, int top, int bottom, int left, int entryHeight)
{
this(client, width, height, top, bottom, left, entryHeight, width, height);
}
public GuiScrollingList(Minecraft client, int width, int height, int top, int bottom, int left, int entryHeight, int screenWidth, int screenHeight)
{
this.client = client;
this.listWidth = width;
this.listHeight = height;
this.top = top;
this.bottom = bottom;
this.slotHeight = entryHeight;
this.left = left;
this.right = width + this.left;
this.screenWidth = screenWidth;
this.screenHeight = screenHeight;
}
@Deprecated // Unused, remove in 1.9.3?
public void func_27258_a(boolean p_27258_1_)
{
this.highlightSelected = p_27258_1_;
}
@Deprecated protected void func_27259_a(boolean hasFooter, int footerHeight){ setHeaderInfo(hasFooter, footerHeight); }
protected void setHeaderInfo(boolean hasHeader, int headerHeight)
{
this.hasHeader = hasHeader;
this.headerHeight = headerHeight;
if (!hasHeader) this.headerHeight = 0;
}
protected abstract int getSize();
protected abstract void elementClicked(int index, boolean doubleClick);
protected abstract boolean isSelected(int index);
protected int getContentHeight()
{
return this.getSize() * this.slotHeight + this.headerHeight;
}
protected abstract void drawBackground();
/**
* Draw anything special on the screen. GL_SCISSOR is enabled for anything that
* is rendered outside of the view box. Do not mess with SCISSOR unless you support this.
*/
protected abstract void drawSlot(int slotIdx, int entryRight, int slotTop, int slotBuffer, Tessellator tess);
@Deprecated protected void func_27260_a(int entryRight, int relativeY, Tessellator tess) {}
/**
* Draw anything special on the screen. GL_SCISSOR is enabled for anything that
* is rendered outside of the view box. Do not mess with SCISSOR unless you support this.
*/
protected void drawHeader(int entryRight, int relativeY, Tessellator tess) { func_27260_a(entryRight, relativeY, tess); }
@Deprecated protected void func_27255_a(int x, int y) {}
protected void clickHeader(int x, int y) { func_27255_a(x, y); }
@Deprecated protected void func_27257_b(int mouseX, int mouseY) {}
/**
* Draw anything special on the screen. GL_SCISSOR is enabled for anything that
* is rendered outside of the view box. Do not mess with SCISSOR unless you support this.
*/
protected void drawScreen(int mouseX, int mouseY) { func_27257_b(mouseX, mouseY); }
@Deprecated // Unused, Remove in 1.9.3?
public int func_27256_c(int x, int y)
{
int left = this.left + 1;
int right = this.left + this.listWidth - 7;
int relativeY = y - this.top - this.headerHeight + (int)this.scrollDistance - 4;
int entryIndex = relativeY / this.slotHeight;
return x >= left && x <= right && entryIndex >= 0 && relativeY >= 0 && entryIndex < this.getSize() ? entryIndex : -1;
}
// FIXME: is this correct/still needed?
public void registerScrollButtons(List<GuiButton> buttons, int upActionID, int downActionID)
{
this.scrollUpActionId = upActionID;
this.scrollDownActionId = downActionID;
}
private void applyScrollLimits()
{
int listHeight = this.getContentHeight() - (this.bottom - this.top - 4);
if (listHeight < 0)
{
listHeight /= 2;
}
if (this.scrollDistance < 0.0F)
{
this.scrollDistance = 0.0F;
}
if (this.scrollDistance > (float)listHeight)
{
this.scrollDistance = (float)listHeight;
}
}
public void actionPerformed(GuiButton button)
{
if (button.enabled)
{
if (button.id == this.scrollUpActionId)
{
this.scrollDistance -= (float)(this.slotHeight * 2 / 3);
this.initialMouseClickY = -2.0F;
this.applyScrollLimits();
}
else if (button.id == this.scrollDownActionId)
{
this.scrollDistance += (float)(this.slotHeight * 2 / 3);
this.initialMouseClickY = -2.0F;
this.applyScrollLimits();
}
}
}
public void handleMouseInput(int mouseX, int mouseY) throws IOException
{
boolean isHovering = mouseX >= this.left && mouseX <= this.left + this.listWidth &&
mouseY >= this.top && mouseY <= this.bottom;
if (!isHovering)
return;
int scroll = Mouse.getEventDWheel();
if (scroll != 0)
{
this.scrollDistance += (float)((-1 * scroll / 120.0F) * this.slotHeight / 2);
}
}
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.mouseX = mouseX;
this.mouseY = mouseY;
this.drawBackground();
boolean isHovering = mouseX >= this.left && mouseX <= this.left + this.listWidth &&
mouseY >= this.top && mouseY <= this.bottom;
int listLength = this.getSize();
int scrollBarWidth = 6;
int scrollBarRight = this.left + this.listWidth;
int scrollBarLeft = scrollBarRight - scrollBarWidth;
int entryLeft = this.left;
int entryRight = scrollBarLeft - 1;
int viewHeight = this.bottom - this.top;
int border = 4;
if (Mouse.isButtonDown(0))
{
if (this.initialMouseClickY == -1.0F)
{
if (isHovering)
{
int mouseListY = mouseY - this.top - this.headerHeight + (int)this.scrollDistance - border;
int slotIndex = mouseListY / this.slotHeight;
if (mouseX >= entryLeft && mouseX <= entryRight && slotIndex >= 0 && mouseListY >= 0 && slotIndex < listLength)
{
this.elementClicked(slotIndex, slotIndex == this.selectedIndex && System.currentTimeMillis() - this.lastClickTime < 250L);
this.selectedIndex = slotIndex;
this.lastClickTime = System.currentTimeMillis();
}
else if (mouseX >= entryLeft && mouseX <= entryRight && mouseListY < 0)
{
this.clickHeader(mouseX - entryLeft, mouseY - this.top + (int)this.scrollDistance - border);
}
if (mouseX >= scrollBarLeft && mouseX <= scrollBarRight)
{
this.scrollFactor = -1.0F;
int scrollHeight = this.getContentHeight() - viewHeight - border;
if (scrollHeight < 1) scrollHeight = 1;
int var13 = (int)((float)(viewHeight * viewHeight) / (float)this.getContentHeight());
if (var13 < 32) var13 = 32;
if (var13 > viewHeight - border*2)
var13 = viewHeight - border*2;
this.scrollFactor /= (float)(viewHeight - var13) / (float)scrollHeight;
}
else
{
this.scrollFactor = 1.0F;
}
this.initialMouseClickY = mouseY;
}
else
{
this.initialMouseClickY = -2.0F;
}
}
else if (this.initialMouseClickY >= 0.0F)
{
this.scrollDistance -= ((float)mouseY - this.initialMouseClickY) * this.scrollFactor;
this.initialMouseClickY = (float)mouseY;
}
}
else
{
this.initialMouseClickY = -1.0F;
}
this.applyScrollLimits();
Tessellator tess = Tessellator.getInstance();
BufferBuilder worldr = tess.getBuffer();
ScaledResolution res = new ScaledResolution(client);
double scaleW = client.displayWidth / res.getScaledWidth_double();
double scaleH = client.displayHeight / res.getScaledHeight_double();
GL11.glEnable(GL11.GL_SCISSOR_TEST);
GL11.glScissor((int)(left * scaleW), (int)(client.displayHeight - (bottom * scaleH)),
(int)(listWidth * scaleW), (int)(viewHeight * scaleH));
if (this.client.world != null)
{
this.drawGradientRect(this.left, this.top, this.right, this.bottom, 0xC0101010, 0xD0101010);
}
else // Draw dark dirt background
{
GlStateManager.disableLighting();
GlStateManager.disableFog();
this.client.renderEngine.bindTexture(Gui.OPTIONS_BACKGROUND);
GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
final float scale = 32.0F;
worldr.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR);
worldr.pos(this.left, this.bottom, 0.0D).tex(this.left / scale, (this.bottom + (int)this.scrollDistance) / scale).color(0x20, 0x20, 0x20, 0xFF).endVertex();
worldr.pos(this.right, this.bottom, 0.0D).tex(this.right / scale, (this.bottom + (int)this.scrollDistance) / scale).color(0x20, 0x20, 0x20, 0xFF).endVertex();
worldr.pos(this.right, this.top, 0.0D).tex(this.right / scale, (this.top + (int)this.scrollDistance) / scale).color(0x20, 0x20, 0x20, 0xFF).endVertex();
worldr.pos(this.left, this.top, 0.0D).tex(this.left / scale, (this.top + (int)this.scrollDistance) / scale).color(0x20, 0x20, 0x20, 0xFF).endVertex();
tess.draw();
}
int baseY = this.top + border - (int)this.scrollDistance;
if (this.hasHeader) {
this.drawHeader(entryRight, baseY, tess);
}
for (int slotIdx = 0; slotIdx < listLength; ++slotIdx)
{
int slotTop = baseY + slotIdx * this.slotHeight + this.headerHeight;
int slotBuffer = this.slotHeight - border;
if (slotTop <= this.bottom && slotTop + slotBuffer >= this.top)
{
if (this.highlightSelected && this.isSelected(slotIdx))
{
int min = this.left;
int max = entryRight;
GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
GlStateManager.disableTexture2D();
worldr.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR);
worldr.pos(min, slotTop + slotBuffer + 2, 0).tex(0, 1).color(0x80, 0x80, 0x80, 0xFF).endVertex();
worldr.pos(max, slotTop + slotBuffer + 2, 0).tex(1, 1).color(0x80, 0x80, 0x80, 0xFF).endVertex();
worldr.pos(max, slotTop - 2, 0).tex(1, 0).color(0x80, 0x80, 0x80, 0xFF).endVertex();
worldr.pos(min, slotTop - 2, 0).tex(0, 0).color(0x80, 0x80, 0x80, 0xFF).endVertex();
worldr.pos(min + 1, slotTop + slotBuffer + 1, 0).tex(0, 1).color(0x00, 0x00, 0x00, 0xFF).endVertex();
worldr.pos(max - 1, slotTop + slotBuffer + 1, 0).tex(1, 1).color(0x00, 0x00, 0x00, 0xFF).endVertex();
worldr.pos(max - 1, slotTop - 1, 0).tex(1, 0).color(0x00, 0x00, 0x00, 0xFF).endVertex();
worldr.pos(min + 1, slotTop - 1, 0).tex(0, 0).color(0x00, 0x00, 0x00, 0xFF).endVertex();
tess.draw();
GlStateManager.enableTexture2D();
}
this.drawSlot(slotIdx, entryRight, slotTop, slotBuffer, tess);
}
}
GlStateManager.disableDepth();
int extraHeight = (this.getContentHeight() + border) - viewHeight;
if (extraHeight > 0)
{
int height = (viewHeight * viewHeight) / this.getContentHeight();
if (height < 32) height = 32;
if (height > viewHeight - border*2)
height = viewHeight - border*2;
int barTop = (int)this.scrollDistance * (viewHeight - height) / extraHeight + this.top;
if (barTop < this.top)
{
barTop = this.top;
}
GlStateManager.disableTexture2D();
worldr.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR);
worldr.pos(scrollBarLeft, this.bottom, 0.0D).tex(0.0D, 1.0D).color(0x00, 0x00, 0x00, 0xFF).endVertex();
worldr.pos(scrollBarRight, this.bottom, 0.0D).tex(1.0D, 1.0D).color(0x00, 0x00, 0x00, 0xFF).endVertex();
worldr.pos(scrollBarRight, this.top, 0.0D).tex(1.0D, 0.0D).color(0x00, 0x00, 0x00, 0xFF).endVertex();
worldr.pos(scrollBarLeft, this.top, 0.0D).tex(0.0D, 0.0D).color(0x00, 0x00, 0x00, 0xFF).endVertex();
tess.draw();
worldr.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR);
worldr.pos(scrollBarLeft, barTop + height, 0.0D).tex(0.0D, 1.0D).color(0x80, 0x80, 0x80, 0xFF).endVertex();
worldr.pos(scrollBarRight, barTop + height, 0.0D).tex(1.0D, 1.0D).color(0x80, 0x80, 0x80, 0xFF).endVertex();
worldr.pos(scrollBarRight, barTop, 0.0D).tex(1.0D, 0.0D).color(0x80, 0x80, 0x80, 0xFF).endVertex();
worldr.pos(scrollBarLeft, barTop, 0.0D).tex(0.0D, 0.0D).color(0x80, 0x80, 0x80, 0xFF).endVertex();
tess.draw();
worldr.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR);
worldr.pos(scrollBarLeft, barTop + height - 1, 0.0D).tex(0.0D, 1.0D).color(0xC0, 0xC0, 0xC0, 0xFF).endVertex();
worldr.pos(scrollBarRight - 1, barTop + height - 1, 0.0D).tex(1.0D, 1.0D).color(0xC0, 0xC0, 0xC0, 0xFF).endVertex();
worldr.pos(scrollBarRight - 1, barTop, 0.0D).tex(1.0D, 0.0D).color(0xC0, 0xC0, 0xC0, 0xFF).endVertex();
worldr.pos(scrollBarLeft, barTop, 0.0D).tex(0.0D, 0.0D).color(0xC0, 0xC0, 0xC0, 0xFF).endVertex();
tess.draw();
}
this.drawScreen(mouseX, mouseY);
GlStateManager.enableTexture2D();
GlStateManager.shadeModel(GL11.GL_FLAT);
GlStateManager.enableAlpha();
GlStateManager.disableBlend();
GL11.glDisable(GL11.GL_SCISSOR_TEST);
}
protected void drawGradientRect(int left, int top, int right, int bottom, int color1, int color2)
{
GuiUtils.drawGradientRect(0, left, top, right, bottom, color1, color2);
}
}

View File

@@ -0,0 +1,123 @@
/*
* 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.client;
import java.util.ArrayList;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.StringUtils;
import net.minecraftforge.common.ForgeVersion;
import net.minecraftforge.common.ForgeVersion.CheckResult;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.LoaderState.ModState;
import net.minecraftforge.fml.common.ModContainer;
/**
* @author cpw
*
*/
public class GuiSlotModList extends GuiScrollingList
{
private static final ResourceLocation VERSION_CHECK_ICONS = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/version_check_icons.png");
private GuiModList parent;
private ArrayList<ModContainer> mods;
public GuiSlotModList(GuiModList parent, ArrayList<ModContainer> mods, int listWidth, int slotHeight)
{
super(parent.getMinecraftInstance(), listWidth, parent.height, 32, parent.height - 88 + 4, 10, slotHeight, parent.width, parent.height);
this.parent = parent;
this.mods = mods;
}
@Override
protected int getSize()
{
return mods.size();
}
@Override
protected void elementClicked(int index, boolean doubleClick)
{
this.parent.selectModIndex(index);
}
@Override
protected boolean isSelected(int index)
{
return this.parent.modIndexSelected(index);
}
@Override
protected void drawBackground()
{
this.parent.drawDefaultBackground();
}
@Override
protected int getContentHeight()
{
return (this.getSize()) * 35 + 1;
}
ArrayList<ModContainer> getMods()
{
return mods;
}
@Override
protected void drawSlot(int idx, int right, int top, int height, Tessellator tess)
{
ModContainer mc = mods.get(idx);
String name = StringUtils.stripControlCodes(mc.getName());
String version = StringUtils.stripControlCodes(mc.getDisplayVersion());
FontRenderer font = this.parent.getFontRenderer();
CheckResult vercheck = ForgeVersion.getResult(mc);
if (Loader.instance().getModState(mc) == ModState.DISABLED)
{
font.drawString(font.trimStringToWidth(name, listWidth - 10), this.left + 3 , top + 2, 0xFF2222);
font.drawString(font.trimStringToWidth(version, listWidth - (5 + height)), this.left + 3 , top + 12, 0xFF2222);
font.drawString(font.trimStringToWidth("DISABLED", listWidth - 10), this.left + 3 , top + 22, 0xFF2222);
}
else
{
font.drawString(font.trimStringToWidth(name, listWidth - 10), this.left + 3 , top + 2, 0xFFFFFF);
font.drawString(font.trimStringToWidth(version, listWidth - (5 + height)), this.left + 3 , top + 12, 0xCCCCCC);
font.drawString(font.trimStringToWidth(mc.getMetadata() != null ? mc.getMetadata().getChildModCountString() : "Metadata not found", listWidth - 10), this.left + 3 , top + 22, 0xCCCCCC);
if (vercheck.status.shouldDraw())
{
//TODO: Consider adding more icons for visualization
Minecraft.getMinecraft().getTextureManager().bindTexture(VERSION_CHECK_ICONS);
GlStateManager.color(1, 1, 1, 1);
GlStateManager.pushMatrix();
Gui.drawModalRectWithCustomSizedTexture(right - (height / 2 + 4), top + (height / 2 - 4), vercheck.status.getSheetOffset() * 8, (vercheck.status.isAnimated() && ((System.currentTimeMillis() / 800 & 1)) == 1) ? 8 : 0, 8, 8, 64, 16);
GlStateManager.popMatrix();
}
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* 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.client;
import net.minecraft.client.gui.GuiScreen;
import net.minecraftforge.fml.common.ModContainer;
import net.minecraftforge.fml.common.toposort.ModSortingException;
import net.minecraftforge.fml.common.toposort.ModSortingException.SortingExceptionData;
public class GuiSortingProblem extends GuiScreen {
private SortingExceptionData<ModContainer> failedList;
public GuiSortingProblem(ModSortingException modSorting)
{
this.failedList = modSorting.getExceptionData();
}
/**
* Adds the buttons (and other controls) to the screen in question. Called when the GUI is displayed and when the
* window resizes, the buttonList is cleared beforehand.
*/
@Override
public void initGui()
{
super.initGui();
}
/**
* Draws the screen and all the components in it.
*/
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.drawDefaultBackground();
int offset = Math.max(85 - (failedList.getVisitedNodes().size() + 3) * 10, 10);
this.drawCenteredString(this.fontRenderer, "Forge Mod Loader has found a problem with your minecraft installation", this.width / 2, offset, 0xFFFFFF);
offset+=10;
this.drawCenteredString(this.fontRenderer, "A mod sorting cycle was detected and loading cannot continue", this.width / 2, offset, 0xFFFFFF);
offset+=10;
this.drawCenteredString(this.fontRenderer, String.format("The first mod in the cycle is %s", failedList.getFirstBadNode()), this.width / 2, offset, 0xFFFFFF);
offset+=10;
this.drawCenteredString(this.fontRenderer, "The remainder of the cycle involves these mods", this.width / 2, offset, 0xFFFFFF);
offset+=5;
for (ModContainer mc : failedList.getVisitedNodes())
{
offset+=10;
this.drawCenteredString(this.fontRenderer, String.format("%s : before: %s, after: %s", mc.toString(), mc.getDependants(), mc.getDependencies()), this.width / 2, offset, 0xEEEEEE);
}
offset+=20;
this.drawCenteredString(this.fontRenderer, "The file 'ForgeModLoader-client-0.log' contains more information", this.width / 2, offset, 0xFFFFFF);
}
}

View File

@@ -0,0 +1,53 @@
/*
* 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.client;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.text.TextFormatting;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.WrongMinecraftVersionException;
public class GuiWrongMinecraft extends GuiErrorBase
{
private WrongMinecraftVersionException wrongMC;
public GuiWrongMinecraft(WrongMinecraftVersionException wrongMC)
{
this.wrongMC = wrongMC;
}
/**
* Draws the screen and all the components in it.
*/
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.drawDefaultBackground();
int offset = 75;
this.drawCenteredString(this.fontRenderer, I18n.format("fml.messages.mod.wrongminecraft", Loader.instance().getMinecraftModContainer().getVersion()), this.width / 2, offset, 0xFFFFFF);
offset+=15;
this.drawCenteredString(this.fontRenderer, I18n.format("fml.messages.mod.wrongminecraft.requirement", TextFormatting.BOLD + wrongMC.mod.getName() + TextFormatting.RESET, wrongMC.mod.getModId(), wrongMC.mod.acceptableMinecraftVersionRange().toStringFriendly()), this.width / 2, offset, 0xEEEEEE);
offset+=15;
this.drawCenteredString(this.fontRenderer, I18n.format("fml.messages.mod.wrongminecraft.fix", wrongMC.mod.getName()),this.width/2, offset, 0xFFFFFF);
offset+=20;
this.drawCenteredString(this.fontRenderer, I18n.format("fml.messages.mod.missing.dependencies.see.log", GuiErrorBase.logFile.getName()), this.width / 2, offset, 0xFFFFFF);
super.drawScreen(mouseX, mouseY, partialTicks);
}
}

View File

@@ -0,0 +1,30 @@
/*
* 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.client;
import net.minecraft.client.gui.GuiScreen;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
public interface IDisplayableError
{
@SideOnly(Side.CLIENT)
GuiScreen createGui();
}

View File

@@ -0,0 +1,105 @@
/*
* 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.client;
import java.util.Set;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
/**
* This is the interface you need to implement if you want to provide a customized config screen.
* {@link DefaultGuiFactory} provides a default implementation of this interface and will be used
* if the mod does not specify anything else.
*/
public interface IModGuiFactory {
/**
* Called when instantiated to initialize with the active minecraft instance.
*
* @param minecraftInstance the instance
*/
void initialize(Minecraft minecraftInstance);
/**
* If this method returns false, the config button in the mod list will be disabled
* @return true if this object provides a config gui screen, false otherwise
*/
boolean hasConfigGui();
/**
* Return an initialized {@link GuiScreen}. This screen will be displayed
* when the "config" button is pressed in the mod list. It will
* have a single argument constructor - the "parent" screen, the same as all
* Minecraft GUIs. The expected behaviour is that this screen will replace the
* "mod list" screen completely, and will return to the mod list screen through
* the parent link, once the appropriate action is taken from the config screen.
*
* This config GUI is anticipated to provide configuration to the mod in a friendly
* visual way. It should not be abused to set internals such as IDs (they're gonna
* keep disappearing anyway), but rather, interesting behaviours. This config GUI
* is never run when a server game is running, and should be used to configure
* desired behaviours that affect server state. Costs, mod game modes, stuff like that
* can be changed here.
*
* @param parentScreen The screen to which must be returned when closing the
* returned screen.
* @return A class that will be instantiated on clicks on the config button
* or null if no GUI is desired.
*/
GuiScreen createConfigGui(GuiScreen parentScreen);
/**
* Return a list of the "runtime" categories this mod wishes to populate with
* GUI elements.
*
* Runtime categories are created on demand and organized in a 'lite' tree format.
* The parent represents the parent node in the tree. There is one special parent
* 'Help' that will always list first, and is generally meant to provide Help type
* content for mods. The remaining parents will sort alphabetically, though
* this may change if there is a lot of alphabetic abuse. "AAA" is probably never a valid
* category parent.
*
* Runtime configuration itself falls into two flavours: in-game help, which is
* generally non interactive except for the text it wishes to show, and client-only
* affecting behaviours. This would include things like toggling minimaps, or cheat modes
* or anything NOT affecting the behaviour of the server. Please don't abuse this to
* change the state of the server in any way, this is intended to behave identically
* when the server is local or remote.
*
* @return the set of options this mod wishes to have available, or empty if none
*/
Set<RuntimeOptionCategoryElement> runtimeGuiCategories();
/**
* Represents an option category and entry in the runtime gui options list.
*
* @author cpw
*
*/
public static class RuntimeOptionCategoryElement {
public final String parent;
public final String child;
public RuntimeOptionCategoryElement(String parent, String child)
{
this.parent = parent;
this.child = child;
}
}
}

View File

@@ -0,0 +1,967 @@
/*
* 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.client;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL12.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.nio.IntBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Properties;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.resources.DefaultResourcePack;
import net.minecraft.client.resources.FileResourcePack;
import net.minecraft.client.resources.FolderResourcePack;
import net.minecraft.client.resources.IResource;
import net.minecraft.client.resources.IResourcePack;
import net.minecraft.client.resources.SimpleResource;
import net.minecraft.crash.CrashReport;
import net.minecraft.launchwrapper.Launch;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.common.EnhancedRuntimeException;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.ICrashCallable;
import net.minecraftforge.fml.common.ProgressManager;
import net.minecraftforge.fml.common.ProgressManager.ProgressBar;
import net.minecraftforge.fml.common.asm.FMLSanityChecker;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.Drawable;
import org.lwjgl.opengl.SharedDrawable;
import org.lwjgl.util.glu.GLU;
import net.minecraftforge.fml.common.EnhancedRuntimeException.WrappedPrintStream;
/**
* Not a fully fleshed out API, may change in future MC versions.
* However feel free to use and suggest additions.
*/
@SuppressWarnings("serial")
public class SplashProgress
{
private static Drawable d;
private static volatile boolean pause = false;
private static volatile boolean done = false;
private static Thread thread;
private static volatile Throwable threadError;
private static int angle = 0;
private static final Lock lock = new ReentrantLock(true);
private static SplashFontRenderer fontRenderer;
private static final IResourcePack mcPack = Minecraft.getMinecraft().mcDefaultResourcePack;
private static final IResourcePack fmlPack = createResourcePack(FMLSanityChecker.fmlLocation);
private static IResourcePack miscPack;
private static Texture fontTexture;
private static Texture logoTexture;
private static Texture forgeTexture;
private static Properties config;
private static boolean enabled;
private static boolean rotate;
private static int logoOffset;
private static int backgroundColor;
private static int fontColor;
private static int barBorderColor;
private static int barColor;
private static int barBackgroundColor;
private static boolean showMemory;
private static int memoryGoodColor;
private static int memoryWarnColor;
private static int memoryLowColor;
private static float memoryColorPercent;
private static long memoryColorChangeTime;
static boolean isDisplayVSyncForced = false;
private static final int TIMING_FRAME_COUNT = 200;
private static final int TIMING_FRAME_THRESHOLD = TIMING_FRAME_COUNT * 5 * 1000000; // 5 ms per frame, scaled to nanos
static final Semaphore mutex = new Semaphore(1);
private static String getString(String name, String def)
{
String value = config.getProperty(name, def);
config.setProperty(name, value);
return value;
}
private static boolean getBool(String name, boolean def)
{
return Boolean.parseBoolean(getString(name, Boolean.toString(def)));
}
private static int getInt(String name, int def)
{
return Integer.decode(getString(name, Integer.toString(def)));
}
private static int getHex(String name, int def)
{
return Integer.decode(getString(name, "0x" + Integer.toString(def, 16).toUpperCase()));
}
public static void start()
{
File configFile = new File(Minecraft.getMinecraft().mcDataDir, "config/splash.properties");
File parent = configFile.getParentFile();
if (!parent.exists())
parent.mkdirs();
config = new Properties();
try (Reader r = new InputStreamReader(new FileInputStream(configFile), StandardCharsets.UTF_8))
{
config.load(r);
}
catch(IOException e)
{
FMLLog.log.info("Could not load splash.properties, will create a default one");
}
//Some systems do not support this and have weird effects, so we need to detect and disable them by default.
//The user can always force enable it if they want to take the responsibility for bugs.
boolean defaultEnabled = true;
// Enable if we have the flag, and there's either no optifine, or optifine has added a key to the blackboard ("optifine.ForgeSplashCompatible")
// Optifine authors - add this key to the blackboard if you feel your modifications are now compatible with this code.
enabled = getBool("enabled", defaultEnabled) && ( (!FMLClientHandler.instance().hasOptifine()) || Launch.blackboard.containsKey("optifine.ForgeSplashCompatible"));
rotate = getBool("rotate", false);
showMemory = getBool("showMemory", true);
logoOffset = getInt("logoOffset", 0);
backgroundColor = getHex("background", 0xFFFFFF);
fontColor = getHex("font", 0x000000);
barBorderColor = getHex("barBorder", 0xC0C0C0);
barColor = getHex("bar", 0xCB3D35);
barBackgroundColor = getHex("barBackground", 0xFFFFFF);
memoryGoodColor = getHex("memoryGood", 0x78CB34);
memoryWarnColor = getHex("memoryWarn", 0xE6E84A);
memoryLowColor = getHex("memoryLow", 0xE42F2F);
final ResourceLocation fontLoc = new ResourceLocation(getString("fontTexture", "textures/font/ascii.png"));
final ResourceLocation logoLoc = new ResourceLocation("textures/gui/title/mojang.png");
final ResourceLocation forgeLoc = new ResourceLocation(getString("forgeTexture", "fml:textures/gui/forge.png"));
final ResourceLocation forgeFallbackLoc = new ResourceLocation("fml:textures/gui/forge.png");
File miscPackFile = new File(Minecraft.getMinecraft().mcDataDir, getString("resourcePackPath", "resources"));
try (Writer w = new OutputStreamWriter(new FileOutputStream(configFile), StandardCharsets.UTF_8))
{
config.store(w, "Splash screen properties");
}
catch(IOException e)
{
FMLLog.log.error("Could not save the splash.properties file", e);
}
miscPack = createResourcePack(miscPackFile);
if(!enabled) return;
// getting debug info out of the way, while we still can
FMLCommonHandler.instance().registerCrashCallable(new ICrashCallable()
{
@Override
public String call() throws Exception
{
return "' Vendor: '" + glGetString(GL_VENDOR) +
"' Version: '" + glGetString(GL_VERSION) +
"' Renderer: '" + glGetString(GL_RENDERER) +
"'";
}
@Override
public String getLabel()
{
return "GL info";
}
});
CrashReport report = CrashReport.makeCrashReport(new Throwable(), "Loading screen debug info");
StringBuilder systemDetailsBuilder = new StringBuilder();
report.getCategory().appendToStringBuilder(systemDetailsBuilder);
FMLLog.log.info(systemDetailsBuilder.toString());
try
{
d = new SharedDrawable(Display.getDrawable());
Display.getDrawable().releaseContext();
d.makeCurrent();
}
catch (LWJGLException e)
{
FMLLog.log.error("Error starting SplashProgress:", e);
disableSplash(e);
}
//Call this ASAP if splash is enabled so that threading doesn't cause issues later
getMaxTextureSize();
//Thread mainThread = Thread.currentThread();
thread = new Thread(new Runnable()
{
private final int barWidth = 400;
private final int barHeight = 20;
private final int textHeight2 = 20;
private final int barOffset = 55;
private long updateTiming;
private long framecount;
@Override
public void run()
{
setGL();
fontTexture = new Texture(fontLoc, null);
logoTexture = new Texture(logoLoc, null, false);
forgeTexture = new Texture(forgeLoc, forgeFallbackLoc);
glEnable(GL_TEXTURE_2D);
fontRenderer = new SplashFontRenderer();
glDisable(GL_TEXTURE_2D);
while(!done)
{
framecount++;
ProgressBar first = null, penult = null, last = null;
Iterator<ProgressBar> i = ProgressManager.barIterator();
while(i.hasNext())
{
if(first == null) first = i.next();
else
{
penult = last;
last = i.next();
}
}
glClear(GL_COLOR_BUFFER_BIT);
// matrix setup
int w = Display.getWidth();
int h = Display.getHeight();
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(320 - w/2, 320 + w/2, 240 + h/2, 240 - h/2, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// mojang logo
setColor(backgroundColor);
glEnable(GL_TEXTURE_2D);
logoTexture.bind();
glBegin(GL_QUADS);
logoTexture.texCoord(0, 0, 0);
glVertex2f(320 - 256, 240 - 256);
logoTexture.texCoord(0, 0, 1);
glVertex2f(320 - 256, 240 + 256);
logoTexture.texCoord(0, 1, 1);
glVertex2f(320 + 256, 240 + 256);
logoTexture.texCoord(0, 1, 0);
glVertex2f(320 + 256, 240 - 256);
glEnd();
glDisable(GL_TEXTURE_2D);
// memory usage
if (showMemory)
{
glPushMatrix();
glTranslatef(320 - (float) barWidth / 2, 20, 0);
drawMemoryBar();
glPopMatrix();
}
// bars
if(first != null)
{
glPushMatrix();
glTranslatef(320 - (float)barWidth / 2, 310, 0);
drawBar(first);
if(penult != null)
{
glTranslatef(0, barOffset, 0);
drawBar(penult);
}
if(last != null)
{
glTranslatef(0, barOffset, 0);
drawBar(last);
}
glPopMatrix();
}
angle += 1;
// forge logo
glColor4f(1, 1, 1, 1);
float fw = (float)forgeTexture.getWidth() / 2;
float fh = (float)forgeTexture.getHeight() / 2;
if(rotate)
{
float sh = Math.max(fw, fh);
glTranslatef(320 + w/2 - sh - logoOffset, 240 + h/2 - sh - logoOffset, 0);
glRotatef(angle, 0, 0, 1);
}
else
{
glTranslatef(320 + w/2 - fw - logoOffset, 240 + h/2 - fh - logoOffset, 0);
}
int f = (angle / 5) % forgeTexture.getFrames();
glEnable(GL_TEXTURE_2D);
forgeTexture.bind();
glBegin(GL_QUADS);
forgeTexture.texCoord(f, 0, 0);
glVertex2f(-fw, -fh);
forgeTexture.texCoord(f, 0, 1);
glVertex2f(-fw, fh);
forgeTexture.texCoord(f, 1, 1);
glVertex2f(fw, fh);
forgeTexture.texCoord(f, 1, 0);
glVertex2f(fw, -fh);
glEnd();
glDisable(GL_TEXTURE_2D);
// We use mutex to indicate safely to the main thread that we're taking the display global lock
// So the main thread can skip processing messages while we're updating.
// There are system setups where this call can pause for a while, because the GL implementation
// is trying to impose a framerate or other thing is occurring. Without the mutex, the main
// thread would delay waiting for the same global display lock
mutex.acquireUninterruptibly();
long updateStart = System.nanoTime();
Display.update();
// As soon as we're done, we release the mutex. The other thread can now ping the processmessages
// call as often as it wants until we get get back here again
long dur = System.nanoTime() - updateStart;
if (framecount < TIMING_FRAME_COUNT) {
updateTiming += dur;
}
mutex.release();
if(pause)
{
clearGL();
setGL();
}
// Such a hack - if the time taken is greater than 10 milliseconds, we're gonna guess that we're on a
// system where vsync is forced through the swapBuffers call - so we have to force a sleep and let the
// loading thread have a turn - some badly designed mods access Keyboard and therefore GlobalLock.lock
// during splash screen, and mutex against the above Display.update call as a result.
// 4 milliseconds is a guess - but it should be enough to trigger in most circumstances. (Maybe if
// 240FPS is possible, this won't fire?)
if (framecount >= TIMING_FRAME_COUNT && updateTiming > TIMING_FRAME_THRESHOLD) {
if (!isDisplayVSyncForced)
{
isDisplayVSyncForced = true;
FMLLog.log.info("Using alternative sync timing : {} frames of Display.update took {} nanos", TIMING_FRAME_COUNT, updateTiming);
}
try { Thread.sleep(16); } catch (InterruptedException ie) {}
} else
{
if (framecount ==TIMING_FRAME_COUNT) {
FMLLog.log.info("Using sync timing. {} frames of Display.update took {} nanos", TIMING_FRAME_COUNT, updateTiming);
}
Display.sync(100);
}
}
clearGL();
}
private void setColor(int color)
{
glColor3ub((byte)((color >> 16) & 0xFF), (byte)((color >> 8) & 0xFF), (byte)(color & 0xFF));
}
private void drawBox(int w, int h)
{
glBegin(GL_QUADS);
glVertex2f(0, 0);
glVertex2f(0, h);
glVertex2f(w, h);
glVertex2f(w, 0);
glEnd();
}
private void drawBar(ProgressBar b)
{
glPushMatrix();
// title - message
setColor(fontColor);
glScalef(2, 2, 1);
glEnable(GL_TEXTURE_2D);
fontRenderer.drawString(b.getTitle() + " - " + b.getMessage(), 0, 0, 0x000000);
glDisable(GL_TEXTURE_2D);
glPopMatrix();
// border
glPushMatrix();
glTranslatef(0, textHeight2, 0);
setColor(barBorderColor);
drawBox(barWidth, barHeight);
// interior
setColor(barBackgroundColor);
glTranslatef(1, 1, 0);
drawBox(barWidth - 2, barHeight - 2);
// slidy part
setColor(barColor);
drawBox((barWidth - 2) * (b.getStep() + 1) / (b.getSteps() + 1), barHeight - 2); // Step can sometimes be 0.
// progress text
String progress = "" + b.getStep() + "/" + b.getSteps();
glTranslatef(((float)barWidth - 2) / 2 - fontRenderer.getStringWidth(progress), 2, 0);
setColor(fontColor);
glScalef(2, 2, 1);
glEnable(GL_TEXTURE_2D);
fontRenderer.drawString(progress, 0, 0, 0x000000);
glPopMatrix();
}
private void drawMemoryBar() {
int maxMemory = bytesToMb(Runtime.getRuntime().maxMemory());
int totalMemory = bytesToMb(Runtime.getRuntime().totalMemory());
int freeMemory = bytesToMb(Runtime.getRuntime().freeMemory());
int usedMemory = totalMemory - freeMemory;
float usedMemoryPercent = usedMemory / (float) maxMemory;
glPushMatrix();
// title - message
setColor(fontColor);
glScalef(2, 2, 1);
glEnable(GL_TEXTURE_2D);
fontRenderer.drawString("Memory Used / Total", 0, 0, 0x000000);
glDisable(GL_TEXTURE_2D);
glPopMatrix();
// border
glPushMatrix();
glTranslatef(0, textHeight2, 0);
setColor(barBorderColor);
drawBox(barWidth, barHeight);
// interior
setColor(barBackgroundColor);
glTranslatef(1, 1, 0);
drawBox(barWidth - 2, barHeight - 2);
// slidy part
long time = System.currentTimeMillis();
if (usedMemoryPercent > memoryColorPercent || (time - memoryColorChangeTime > 1000))
{
memoryColorChangeTime = time;
memoryColorPercent = usedMemoryPercent;
}
int memoryBarColor;
if (memoryColorPercent < 0.75f)
{
memoryBarColor = memoryGoodColor;
}
else if (memoryColorPercent < 0.85f)
{
memoryBarColor = memoryWarnColor;
}
else
{
memoryBarColor = memoryLowColor;
}
setColor(memoryLowColor);
glPushMatrix();
glTranslatef((barWidth - 2) * (totalMemory) / (maxMemory) - 2, 0, 0);
drawBox(2, barHeight - 2);
glPopMatrix();
setColor(memoryBarColor);
drawBox((barWidth - 2) * (usedMemory) / (maxMemory), barHeight - 2);
// progress text
String progress = getMemoryString(usedMemory) + " / " + getMemoryString(maxMemory);
glTranslatef(((float)barWidth - 2) / 2 - fontRenderer.getStringWidth(progress), 2, 0);
setColor(fontColor);
glScalef(2, 2, 1);
glEnable(GL_TEXTURE_2D);
fontRenderer.drawString(progress, 0, 0, 0x000000);
glPopMatrix();
}
private String getMemoryString(int memory)
{
return StringUtils.leftPad(Integer.toString(memory), 4, ' ') + " MB";
}
private void setGL()
{
lock.lock();
try
{
Display.getDrawable().makeCurrent();
}
catch (LWJGLException e)
{
FMLLog.log.error("Error setting GL context:", e);
throw new RuntimeException(e);
}
glClearColor((float)((backgroundColor >> 16) & 0xFF) / 0xFF, (float)((backgroundColor >> 8) & 0xFF) / 0xFF, (float)(backgroundColor & 0xFF) / 0xFF, 1);
glDisable(GL_LIGHTING);
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
private void clearGL()
{
Minecraft mc = Minecraft.getMinecraft();
mc.displayWidth = Display.getWidth();
mc.displayHeight = Display.getHeight();
mc.resize(mc.displayWidth, mc.displayHeight);
glClearColor(1, 1, 1, 1);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, .1f);
try
{
Display.getDrawable().releaseContext();
}
catch (LWJGLException e)
{
FMLLog.log.error("Error releasing GL context:", e);
throw new RuntimeException(e);
}
finally
{
lock.unlock();
}
}
});
thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler()
{
@Override
public void uncaughtException(Thread t, Throwable e)
{
FMLLog.log.error("Splash thread Exception", e);
threadError = e;
}
});
thread.start();
checkThreadState();
}
private static int max_texture_size = -1;
public static int getMaxTextureSize()
{
if (max_texture_size != -1) return max_texture_size;
for (int i = 0x4000; i > 0; i >>= 1)
{
GlStateManager.glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, i, i, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);
if (GlStateManager.glGetTexLevelParameteri(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) != 0)
{
max_texture_size = i;
return i;
}
}
return -1;
}
private static void checkThreadState()
{
if(thread.getState() == Thread.State.TERMINATED || threadError != null)
{
throw new IllegalStateException("Splash thread", threadError);
}
}
/**
* Call before you need to explicitly modify GL context state during loading.
* Resource loading doesn't usually require this call.
* Call {@link #resume()} when you're done.
* @deprecated not a stable API, will break, don't use this yet
*/
@Deprecated
public static void pause()
{
if(!enabled) return;
checkThreadState();
pause = true;
lock.lock();
try
{
d.releaseContext();
Display.getDrawable().makeCurrent();
}
catch (LWJGLException e)
{
FMLLog.log.error("Error setting GL context:", e);
throw new RuntimeException(e);
}
}
/**
* @deprecated not a stable API, will break, don't use this yet
*/
@Deprecated
public static void resume()
{
if(!enabled) return;
checkThreadState();
pause = false;
try
{
Display.getDrawable().releaseContext();
d.makeCurrent();
}
catch (LWJGLException e)
{
FMLLog.log.error("Error releasing GL context:", e);
throw new RuntimeException(e);
}
lock.unlock();
}
public static void finish()
{
if(!enabled) return;
try
{
checkThreadState();
done = true;
thread.join();
glFlush(); // process any remaining GL calls before releaseContext (prevents missing textures on mac)
d.releaseContext();
Display.getDrawable().makeCurrent();
fontTexture.delete();
logoTexture.delete();
forgeTexture.delete();
}
catch (Exception e)
{
FMLLog.log.error("Error finishing SplashProgress:", e);
disableSplash(e);
}
}
private static boolean disableSplash(Exception e)
{
if (disableSplash())
{
throw new EnhancedRuntimeException(e)
{
@Override
protected void printStackTrace(WrappedPrintStream stream)
{
stream.println("SplashProgress has detected a error loading Minecraft.");
stream.println("This can sometimes be caused by bad video drivers.");
stream.println("We have automatically disabled the new Splash Screen in config/splash.properties.");
stream.println("Try reloading minecraft before reporting any errors.");
}
};
}
else
{
throw new EnhancedRuntimeException(e)
{
@Override
protected void printStackTrace(WrappedPrintStream stream)
{
stream.println("SplashProgress has detected a error loading Minecraft.");
stream.println("This can sometimes be caused by bad video drivers.");
stream.println("Please try disabling the new Splash Screen in config/splash.properties.");
stream.println("After doing so, try reloading minecraft before reporting any errors.");
}
};
}
}
private static boolean disableSplash()
{
File configFile = new File(Minecraft.getMinecraft().mcDataDir, "config/splash.properties");
File parent = configFile.getParentFile();
if (!parent.exists())
parent.mkdirs();
enabled = false;
config.setProperty("enabled", "false");
try (Writer w = new OutputStreamWriter(new FileOutputStream(configFile), StandardCharsets.UTF_8))
{
config.store(w, "Splash screen properties");
}
catch(IOException e)
{
FMLLog.log.error("Could not save the splash.properties file", e);
return false;
}
return true;
}
private static IResourcePack createResourcePack(File file)
{
if(file.isDirectory())
{
return new FolderResourcePack(file);
}
else
{
return new FileResourcePack(file);
}
}
private static final IntBuffer buf = BufferUtils.createIntBuffer(4 * 1024 * 1024);
@SuppressWarnings("unused")
private static class Texture
{
private final ResourceLocation location;
private final int name;
private final int width;
private final int height;
private final int frames;
private final int size;
public Texture(ResourceLocation location, @Nullable ResourceLocation fallback)
{
this(location, fallback, true);
}
public Texture(ResourceLocation location, @Nullable ResourceLocation fallback, boolean allowRP)
{
InputStream s = null;
try
{
this.location = location;
s = open(location, fallback, allowRP);
ImageInputStream stream = ImageIO.createImageInputStream(s);
Iterator<ImageReader> readers = ImageIO.getImageReaders(stream);
if(!readers.hasNext()) throw new IOException("No suitable reader found for image" + location);
ImageReader reader = readers.next();
reader.setInput(stream);
int frames = reader.getNumImages(true);
BufferedImage[] images = new BufferedImage[frames];
for(int i = 0; i < frames; i++)
{
images[i] = reader.read(i);
}
reader.dispose();
width = images[0].getWidth();
int height = images[0].getHeight();
// Animation strip
if (height > width && height % width == 0)
{
frames = height / width;
BufferedImage original = images[0];
height = width;
images = new BufferedImage[frames];
for (int i = 0; i < frames; i++)
{
images[i] = original.getSubimage(0, i * height, width, height);
}
}
this.frames = frames;
this.height = height;
int size = 1;
while((size / width) * (size / height) < frames) size *= 2;
this.size = size;
glEnable(GL_TEXTURE_2D);
synchronized(SplashProgress.class)
{
name = glGenTextures();
glBindTexture(GL_TEXTURE_2D, name);
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, (IntBuffer)null);
checkGLError("Texture creation");
for(int i = 0; i * (size / width) < frames; i++)
{
for(int j = 0; i * (size / width) + j < frames && j < size / width; j++)
{
buf.clear();
BufferedImage image = images[i * (size / width) + j];
for(int k = 0; k < height; k++)
{
for(int l = 0; l < width; l++)
{
buf.put(image.getRGB(l, k));
}
}
buf.position(0).limit(width * height);
glTexSubImage2D(GL_TEXTURE_2D, 0, j * width, i * height, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, buf);
checkGLError("Texture uploading");
}
}
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
}
catch(IOException e)
{
FMLLog.log.error("Error reading texture from file: {}", location, e);
throw new RuntimeException(e);
}
finally
{
IOUtils.closeQuietly(s);
}
}
public ResourceLocation getLocation()
{
return location;
}
public int getName()
{
return name;
}
public int getWidth()
{
return width;
}
public int getHeight()
{
return height;
}
public int getFrames()
{
return frames;
}
public int getSize()
{
return size;
}
public void bind()
{
glBindTexture(GL_TEXTURE_2D, name);
}
public void delete()
{
glDeleteTextures(name);
}
public float getU(int frame, float u)
{
return width * (frame % (size / width) + u) / size;
}
public float getV(int frame, float v)
{
return height * (frame / (size / width) + v) / size;
}
public void texCoord(int frame, float u, float v)
{
glTexCoord2f(getU(frame, u), getV(frame, v));
}
}
private static class SplashFontRenderer extends FontRenderer
{
public SplashFontRenderer()
{
super(Minecraft.getMinecraft().gameSettings, fontTexture.getLocation(), null, false);
super.onResourceManagerReload(null);
}
@Override
protected void bindTexture(@Nonnull ResourceLocation location)
{
if(location != locationFontTexture) throw new IllegalArgumentException();
fontTexture.bind();
}
@Nonnull
@Override
protected IResource getResource(@Nonnull ResourceLocation location) throws IOException
{
DefaultResourcePack pack = Minecraft.getMinecraft().mcDefaultResourcePack;
return new SimpleResource(pack.getPackName(), location, pack.getInputStream(location), null, null);
}
}
public static void drawVanillaScreen(TextureManager renderEngine) throws LWJGLException
{
if(!enabled)
{
Minecraft.getMinecraft().drawSplashScreen(renderEngine);
}
}
public static void clearVanillaResources(TextureManager renderEngine, ResourceLocation mojangLogo)
{
if(!enabled)
{
renderEngine.deleteTexture(mojangLogo);
}
}
public static void checkGLError(String where)
{
int err = glGetError();
if (err != 0)
{
throw new IllegalStateException(where + ": " + GLU.gluErrorString(err));
}
}
private static InputStream open(ResourceLocation loc, @Nullable ResourceLocation fallback, boolean allowResourcePack) throws IOException
{
if (!allowResourcePack)
return mcPack.getInputStream(loc);
if(miscPack.resourceExists(loc))
{
return miscPack.getInputStream(loc);
}
else if(fmlPack.resourceExists(loc))
{
return fmlPack.getInputStream(loc);
}
else if(!mcPack.resourceExists(loc) && fallback != null)
{
return open(fallback, null, true);
}
return mcPack.getInputStream(loc);
}
private static int bytesToMb(long bytes)
{
return (int) (bytes / 1024L / 1024L);
}
}

View File

@@ -0,0 +1,31 @@
/*
* 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.client.config;
public enum ConfigGuiType
{
STRING,
INTEGER,
BOOLEAN,
DOUBLE,
COLOR,
MOD_ID,
CONFIG_CATEGORY;
}

View File

@@ -0,0 +1,427 @@
/*
* 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.client.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.fml.client.config.GuiConfigEntries.IConfigEntry;
import net.minecraftforge.fml.client.config.GuiEditArrayEntries.IArrayEntry;
import javax.annotation.Nullable;
/**
* This class's main purpose is to provide the necessary objects for a sample Config GUI for FML, although
* there may be practical uses for the objects defined here such as using the DummyCategoryElement object as a
* wrapper for a custom IGuiConfigListEntry object that opens a special screen.
*
* @author bspkrs
*/
public class DummyConfigElement implements IConfigElement
{
protected boolean isProperty = true;
protected boolean isList = false;
protected ConfigGuiType type;
protected String name;
protected String langKey;
protected Object value;
protected Object defaultValue;
protected Object[] values;
protected Object[] defaultValues;
protected String[] validValues;
protected Pattern validStringPattern;
protected Object minValue;
protected Object maxValue;
protected boolean requiresWorldRestart = false;
protected boolean requiresMcRestart = false;
protected boolean isListFixedLength = false;
protected int maxListLength = -1;
protected List<IConfigElement> childElements;
@Nullable
protected Class<? extends IConfigEntry> configEntryClass;
protected Class<? extends IArrayEntry> arrayEntryClass;
/**
* This class provides a Dummy Category IConfigElement. It can be used to define a custom list of GUI entries that will
* appear on the child screen or to specify a custom IGuiConfigListEntry for a special category.
*/
public static class DummyCategoryElement extends DummyConfigElement
{
public DummyCategoryElement(String name, String langKey, List<IConfigElement> childElements)
{
this(name, langKey, childElements, null);
}
public DummyCategoryElement(String name, String langKey, Class<? extends IConfigEntry> customListEntryClass)
{
this(name, langKey, new ArrayList<IConfigElement>(), customListEntryClass);
}
public DummyCategoryElement(String name, String langKey, List<IConfigElement> childElements, @Nullable Class<? extends IConfigEntry> customListEntryClass)
{
super(name, null, ConfigGuiType.CONFIG_CATEGORY, langKey);
this.childElements = childElements;
this.configEntryClass = customListEntryClass;
isProperty = false;
}
}
/**
* This class provides a dummy array-type IConfigElement.
*/
public static class DummyListElement extends DummyConfigElement
{
public DummyListElement(String name, Object[] defaultValues, ConfigGuiType type, String langKey, boolean isListFixedLength, int maxListLength, @Nullable Pattern validStringPattern, @Nullable Object minValue, @Nullable Object maxValue)
{
super(name, null, type, langKey, minValue, maxValue);
this.defaultValues = defaultValues;
this.values = defaultValues;
this.isListFixedLength = isListFixedLength;
this.maxListLength = maxListLength;
this.validStringPattern = validStringPattern;
isList = true;
}
public DummyListElement(String name, Object[] defaultValues, ConfigGuiType type, String langKey)
{
this(name, defaultValues, type, langKey, false, -1, null, null, null);
}
public DummyListElement(String name, Object[] defaultValues, ConfigGuiType type, String langKey, boolean isListFixedLength)
{
this(name, defaultValues, type, langKey, isListFixedLength, -1, null, null, null);
}
public DummyListElement(String name, Object[] defaultValues, ConfigGuiType type, String langKey, int maxListLength)
{
this(name, defaultValues, type, langKey, false, maxListLength, null, null, null);
}
public DummyListElement(String name, Object[] defaultValues, ConfigGuiType type, String langKey, Object minValue, Object maxValue)
{
this(name, defaultValues, type, langKey, false, -1, null, minValue, maxValue);
}
public DummyListElement(String name, Object[] defaultValues, ConfigGuiType type, String langKey, boolean isListFixedLength, Object minValue, Object maxValue)
{
this(name, defaultValues, type, langKey, isListFixedLength, -1, null, minValue, maxValue);
}
public DummyListElement(String name, Object[] defaultValues, ConfigGuiType type, String langKey, int maxListLength, Object minValue, Object maxValue)
{
this(name, defaultValues, type, langKey, false, maxListLength, null, minValue, maxValue);
}
public DummyListElement(String name, Object[] defaultValues, ConfigGuiType type, String langKey, boolean isListFixedLength, int maxListLength, Object minValue, Object maxValue)
{
this(name, defaultValues, type, langKey, isListFixedLength, maxListLength, null, minValue, maxValue);
}
public DummyListElement(String name, Object[] defaultValues, ConfigGuiType type, String langKey, Pattern validStringPattern)
{
this(name, defaultValues, type, langKey, false, -1, validStringPattern, null, null);
}
public DummyListElement(String name, Object[] defaultValues, ConfigGuiType type, String langKey, boolean isListFixedLength, Pattern validStringPattern)
{
this(name, defaultValues, type, langKey, isListFixedLength, -1, validStringPattern, null, null);
}
public DummyListElement(String name, Object[] defaultValues, ConfigGuiType type, String langKey, int maxListLength, Pattern validStringPattern)
{
this(name, defaultValues, type, langKey, false, maxListLength, validStringPattern, null, null);
}
public DummyListElement setCustomEditListEntryClass(Class<? extends IArrayEntry> clazz)
{
this.arrayEntryClass = clazz;
return this;
}
@Override
public Object getDefault()
{
return Arrays.toString(this.defaultValues);
}
}
public DummyConfigElement(String name, Object defaultValue, ConfigGuiType type, String langKey, String[] validValues, Pattern validStringPattern, Object minValue, Object maxValue)
{
this.name = name;
this.defaultValue = defaultValue;
this.value = defaultValue;
this.type = type;
this.langKey = langKey;
this.validValues = validValues;
this.validStringPattern = validStringPattern;
if (minValue == null)
{
if (type == ConfigGuiType.INTEGER)
this.minValue = Integer.MIN_VALUE;
else if (type == ConfigGuiType.DOUBLE)
this.minValue = -Double.MAX_VALUE;
}
else
this.minValue = minValue;
if (maxValue == null)
{
if (type == ConfigGuiType.INTEGER)
this.maxValue = Integer.MAX_VALUE;
else if (type == ConfigGuiType.DOUBLE)
this.maxValue = Double.MAX_VALUE;
}
else
this.maxValue = maxValue;
}
public DummyConfigElement(String name, Object defaultValue, ConfigGuiType type, String langKey, Pattern validStringPattern)
{
this(name, defaultValue, type, langKey, null, validStringPattern, null, null);
}
public DummyConfigElement(String name, Object defaultValue, ConfigGuiType type, String langKey, String[] validValues)
{
this(name, defaultValue, type, langKey, validValues, null, null, null);
}
public DummyConfigElement(String name, Object defaultValue, ConfigGuiType type, String langKey)
{
this(name, defaultValue, type, langKey, null, null, null, null);
}
public DummyConfigElement(String name, Object defaultValue, ConfigGuiType type, String langKey, Object minValue, Object maxValue)
{
this(name, defaultValue, type, langKey, null, null, minValue, maxValue);
}
public DummyConfigElement setCustomListEntryClass(Class<? extends IConfigEntry> clazz)
{
this.configEntryClass = clazz;
return this;
}
@Override
public boolean isProperty()
{
return isProperty;
}
public IConfigElement setConfigEntryClass(Class<? extends IConfigEntry> clazz)
{
this.configEntryClass = clazz;
return this;
}
@Override
public Class<? extends IConfigEntry> getConfigEntryClass()
{
return configEntryClass;
}
public IConfigElement setArrayEntryClass(Class<? extends IArrayEntry> clazz)
{
this.arrayEntryClass = clazz;
return this;
}
@Override
public Class<? extends IArrayEntry> getArrayEntryClass()
{
return arrayEntryClass;
}
@Override
public String getName()
{
return name;
}
@Override
public String getQualifiedName()
{
return name;
}
@Override
public String getLanguageKey()
{
return langKey;
}
@Override
public String getComment()
{
return I18n.format(langKey + ".tooltip");
}
@Override
public List<IConfigElement> getChildElements()
{
return childElements;
}
@Override
public ConfigGuiType getType()
{
return type;
}
@Override
public boolean isList()
{
return isList;
}
@Override
public boolean isListLengthFixed()
{
return this.isListFixedLength;
}
@Override
public int getMaxListLength()
{
return this.maxListLength;
}
@Override
public boolean isDefault()
{
if (isProperty)
{
if (!isList)
{
if (value != null)
return value.equals(defaultValue);
else
return defaultValue == null;
}
else
{
return Arrays.deepEquals(values, defaultValues);
}
}
return true;
}
@Override
public Object getDefault()
{
return defaultValue;
}
@Override
public Object[] getDefaults()
{
return defaultValues;
}
@Override
public void setToDefault()
{
if (isList)
this.values = Arrays.copyOf(this.defaultValues, this.defaultValues.length);
else
this.value = defaultValue;
}
public IConfigElement setRequiresWorldRestart(boolean requiresWorldRestart)
{
this.requiresWorldRestart = requiresWorldRestart;
return this;
}
@Override
public boolean requiresWorldRestart()
{
return requiresWorldRestart;
}
@Override
public boolean showInGui()
{
return true;
}
public IConfigElement setRequiresMcRestart(boolean requiresMcRestart)
{
this.requiresMcRestart = this.requiresWorldRestart = requiresMcRestart;
return this;
}
@Override
public boolean requiresMcRestart()
{
return requiresMcRestart;
}
@Override
public String[] getValidValues()
{
return validValues;
}
@Override
public Pattern getValidationPattern()
{
return validStringPattern;
}
@Override
public Object get()
{
return value;
}
@Override
public Object[] getList()
{
return values;
}
@Override
public void set(Object value)
{
defaultValue = value;
}
@Override
public void set(Object[] aVal)
{
defaultValues = aVal;
}
@Override
public Object getMinValue()
{
return minValue;
}
@Override
public Object getMaxValue()
{
return maxValue;
}
}

View File

@@ -0,0 +1,87 @@
/*
* 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.client.config;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
/**
* This class provides a button that fixes several bugs present in the vanilla GuiButton drawing code.
* The gist of it is that it allows buttons of any size without gaps in the graphics and with the
* borders drawn properly. It also prevents button text from extending out of the sides of the button by
* trimming the end of the string and adding an ellipsis.<br/><br/>
*
* The code that handles drawing the button is in GuiUtils.
*
* @author bspkrs
*/
public class GuiButtonExt extends GuiButton
{
public GuiButtonExt(int id, int xPos, int yPos, String displayString)
{
super(id, xPos, yPos, displayString);
}
public GuiButtonExt(int id, int xPos, int yPos, int width, int height, String displayString)
{
super(id, xPos, yPos, width, height, displayString);
}
/**
* Draws this button to the screen.
*/
/**
* Draws this button to the screen.
*/
@Override
public void drawButton(Minecraft mc, int mouseX, int mouseY, float partial)
{
if (this.visible)
{
this.hovered = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height;
int k = this.getHoverState(this.hovered);
GuiUtils.drawContinuousTexturedBox(BUTTON_TEXTURES, this.x, this.y, 0, 46 + k * 20, this.width, this.height, 200, 20, 2, 3, 2, 2, this.zLevel);
this.mouseDragged(mc, mouseX, mouseY);
int color = 14737632;
if (packedFGColour != 0)
{
color = packedFGColour;
}
else if (!this.enabled)
{
color = 10526880;
}
else if (this.hovered)
{
color = 16777120;
}
String buttonText = this.displayString;
int strWidth = mc.fontRenderer.getStringWidth(buttonText);
int ellipsisWidth = mc.fontRenderer.getStringWidth("...");
if (strWidth > width - 6 && strWidth > ellipsisWidth)
buttonText = mc.fontRenderer.trimStringToWidth(buttonText, width - 6 - ellipsisWidth).trim() + "...";
this.drawCenteredString(mc.fontRenderer, buttonText, this.x + this.width / 2, this.y + (this.height - 8) / 2, color);
}
}
}

View File

@@ -0,0 +1,98 @@
/*
* 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.client.config;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
/**
* This class provides a checkbox style control.
*
* @author bspkrs
*/
public class GuiCheckBox extends GuiButton
{
private boolean isChecked;
private int boxWidth;
public GuiCheckBox(int id, int xPos, int yPos, String displayString, boolean isChecked)
{
super(id, xPos, yPos, displayString);
this.isChecked = isChecked;
this.boxWidth = 11;
this.height = 11;
this.width = this.boxWidth + 2 + Minecraft.getMinecraft().fontRenderer.getStringWidth(displayString);
}
/**
* Draws this button to the screen.
*/
@Override
public void drawButton(Minecraft mc, int mouseX, int mouseY, float partial)
{
if (this.visible)
{
this.hovered = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.boxWidth && mouseY < this.y + this.height;
GuiUtils.drawContinuousTexturedBox(BUTTON_TEXTURES, this.x, this.y, 0, 46, this.boxWidth, this.height, 200, 20, 2, 3, 2, 2, this.zLevel);
this.mouseDragged(mc, mouseX, mouseY);
int color = 14737632;
if (packedFGColour != 0)
{
color = packedFGColour;
}
else if (!this.enabled)
{
color = 10526880;
}
if (this.isChecked)
this.drawCenteredString(mc.fontRenderer, "x", this.x + this.boxWidth / 2 + 1, this.y + 1, 14737632);
this.drawString(mc.fontRenderer, displayString, this.x + this.boxWidth + 2, this.y + 2, color);
}
}
/**
* Returns true if the mouse has been pressed on this control. Equivalent of MouseListener.mousePressed(MouseEvent
* e).
*/
@Override
public boolean mousePressed(Minecraft mc, int mouseX, int mouseY)
{
if (this.enabled && this.visible && mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height)
{
this.isChecked = !this.isChecked;
return true;
}
return false;
}
public boolean isChecked()
{
return this.isChecked;
}
public void setIsChecked(boolean isChecked)
{
this.isChecked = isChecked;
}
}

View File

@@ -0,0 +1,460 @@
/*
* 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.client.config;
import static net.minecraftforge.fml.client.config.GuiUtils.RESET_CHAR;
import static net.minecraftforge.fml.client.config.GuiUtils.UNDO_CHAR;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.text.TextComponentString;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.config.ConfigElement;
import net.minecraftforge.common.config.ConfigManager;
import net.minecraftforge.fml.client.config.GuiConfigEntries.IConfigEntry;
import net.minecraftforge.fml.client.event.ConfigChangedEvent;
import net.minecraftforge.fml.client.event.ConfigChangedEvent.OnConfigChangedEvent;
import net.minecraftforge.fml.client.event.ConfigChangedEvent.PostConfigChangedEvent;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.eventhandler.Event.Result;
import org.lwjgl.input.Keyboard;
import javax.annotation.Nullable;
/**
* This class is the base GuiScreen for all config GUI screens. It can be extended by mods to provide the top-level config screen
* that will be called when the Config button is clicked from the Main Menu Mods list.
*
* @author bspkrs
*/
public class GuiConfig extends GuiScreen
{
/**
* A reference to the screen object that created this. Used for navigating between screens.
*/
public final GuiScreen parentScreen;
public String title = "Config GUI";
@Nullable
public String titleLine2;
public final List<IConfigElement> configElements;
public final List<IConfigEntry> initEntries;
public GuiConfigEntries entryList;
protected GuiButtonExt btnDefaultAll;
protected GuiButtonExt btnUndoAll;
protected GuiCheckBox chkApplyGlobally;
public final String modID;
/**
* When set to a non-null value the OnConfigChanged and PostConfigChanged events will be posted when the Done button is pressed
* if any configElements were changed (includes child screens). If not defined, the events will be posted if the parent gui is null
* or if the parent gui is not an instance of GuiConfig.
*/
@Nullable
public final String configID;
public final boolean isWorldRunning;
public final boolean allRequireWorldRestart;
public final boolean allRequireMcRestart;
public boolean needsRefresh = true;
protected HoverChecker undoHoverChecker;
protected HoverChecker resetHoverChecker;
protected HoverChecker checkBoxHoverChecker;
/**
* This constructor handles the {@code @Config} configuration classes
* @param parentScreen the parent GuiScreen object
* @param mod the mod for which to create a screen
*/
public GuiConfig(GuiScreen parentScreen, String modid, String title)
{
this(parentScreen, modid, false, false, title, ConfigManager.getModConfigClasses(modid));
}
/**
*
* @param parentScreen the parrent GuiScreen object
* @param modID the mod ID for the mod whose config settings will be editted
* @param allRequireWorldRestart whether all config elements on this screen require a world restart
* @param allRequireMcRestart whether all config elements on this screen require a game restart
* @param title the desired title for this screen. For consistency it is recommended that you pass the path of the config file being
* edited.
* @param configClasses an array of classes annotated with {@code @Config} providing the configuration
*/
public GuiConfig(GuiScreen parentScreen, String modID, boolean allRequireWorldRestart, boolean allRequireMcRestart, String title,
Class<?>... configClasses)
{
this(parentScreen, collectConfigElements(configClasses), modID, null, allRequireWorldRestart, allRequireMcRestart, title, null);
}
private static List<IConfigElement> collectConfigElements(Class<?>[] configClasses)
{
List<IConfigElement> toReturn;
if(configClasses.length == 1)
{
toReturn = ConfigElement.from(configClasses[0]).getChildElements();
}
else
{
toReturn = new ArrayList<IConfigElement>();
for(Class<?> clazz : configClasses)
{
toReturn.add(ConfigElement.from(clazz));
}
}
toReturn.sort(Comparator.comparing(e -> I18n.format(e.getLanguageKey())));
return toReturn;
}
/**
* GuiConfig constructor that will use ConfigChangedEvent when editing is concluded. If a non-null value is passed for configID,
* the OnConfigChanged and PostConfigChanged events will be posted when the Done button is pressed if any configElements were changed
* (includes child screens). If configID is not defined, the events will be posted if the parent gui is null or if the parent gui
* is not an instance of GuiConfig.
*
* @param parentScreen the parent GuiScreen object
* @param configElements a List of IConfigElement objects
* @param modID the mod ID for the mod whose config settings will be edited
* @param configID an identifier that will be passed to the OnConfigChanged and PostConfigChanged events. Setting this value will force
* the save action to be called when the Done button is pressed on this screen if any configElements were changed.
* @param allRequireWorldRestart send true if all configElements on this screen require a world restart
* @param allRequireMcRestart send true if all configElements on this screen require MC to be restarted
* @param title the desired title for this screen. For consistency it is recommended that you pass the path of the config file being
* edited.
*/
public GuiConfig(GuiScreen parentScreen, List<IConfigElement> configElements, String modID, String configID,
boolean allRequireWorldRestart, boolean allRequireMcRestart, String title)
{
this(parentScreen, configElements, modID, configID, allRequireWorldRestart, allRequireMcRestart, title, null);
}
/**
* GuiConfig constructor that will use ConfigChangedEvent when editing is concluded. This constructor passes null for configID.
* If configID is not defined, the events will be posted if the parent gui is null or if the parent gui is not an instance of GuiConfig.
*
* @param parentScreen the parent GuiScreen object
* @param configElements a List of IConfigElement objects
* @param modID the mod ID for the mod whose config settings will be edited
* @param allRequireWorldRestart send true if all configElements on this screen require a world restart
* @param allRequireMcRestart send true if all configElements on this screen require MC to be restarted
* @param title the desired title for this screen. For consistency it is recommended that you pass the path of the config file being
* edited.
*/
public GuiConfig(GuiScreen parentScreen, List<IConfigElement> configElements, String modID,
boolean allRequireWorldRestart, boolean allRequireMcRestart, String title)
{
this(parentScreen, configElements, modID, null, allRequireWorldRestart, allRequireMcRestart, title, null);
}
/**
* GuiConfig constructor that will use ConfigChangedEvent when editing is concluded. This constructor passes null for configID.
* If configID is not defined, the events will be posted if the parent gui is null or if the parent gui is not an instance of GuiConfig.
*
* @param parentScreen the parent GuiScreen object
* @param configElements a List of IConfigElement objects
* @param modID the mod ID for the mod whose config settings will be edited
* @param allRequireWorldRestart send true if all configElements on this screen require a world restart
* @param allRequireMcRestart send true if all configElements on this screen require MC to be restarted
* @param title the desired title for this screen. For consistency it is recommended that you pass the path of the config file being
* edited.
* @param titleLine2 the desired title second line for this screen. Typically this is used to send the category name of the category
* currently being edited.
*/
public GuiConfig(GuiScreen parentScreen, List<IConfigElement> configElements, String modID,
boolean allRequireWorldRestart, boolean allRequireMcRestart, String title, String titleLine2)
{
this(parentScreen, configElements, modID, null, allRequireWorldRestart, allRequireMcRestart, title, titleLine2);
}
/**
* GuiConfig constructor that will use ConfigChangedEvent when editing is concluded. titleLine2 is specified in this constructor.
* If a non-null value is passed for configID, the OnConfigChanged and PostConfigChanged events will be posted when the Done button is
* pressed if any configElements were changed (includes child screens). If configID is not defined, the events will be posted if the parent
* gui is null or if the parent gui is not an instance of GuiConfig.
*
* @param parentScreen the parent GuiScreen object
* @param configElements a List of IConfigElement objects
* @param modID the mod ID for the mod whose config settings will be edited
* @param configID an identifier that will be passed to the OnConfigChanged and PostConfigChanged events
* @param allRequireWorldRestart send true if all configElements on this screen require a world restart
* @param allRequireMcRestart send true if all configElements on this screen require MC to be restarted
* @param title the desired title for this screen. For consistency it is recommended that you pass the path of the config file being
* edited.
* @param titleLine2 the desired title second line for this screen. Typically this is used to send the category name of the category
* currently being edited.
*/
public GuiConfig(GuiScreen parentScreen, List<IConfigElement> configElements, String modID, @Nullable String configID,
boolean allRequireWorldRestart, boolean allRequireMcRestart, String title, @Nullable String titleLine2)
{
this.mc = Minecraft.getMinecraft();
this.parentScreen = parentScreen;
this.configElements = configElements;
this.entryList = new GuiConfigEntries(this, mc);
this.initEntries = new ArrayList<IConfigEntry>(entryList.listEntries);
this.allRequireWorldRestart = allRequireWorldRestart;
IF:if (!allRequireWorldRestart)
{
for (IConfigElement element : configElements)
{
if (!element.requiresWorldRestart());
break IF;
}
allRequireWorldRestart = true;
}
this.allRequireMcRestart = allRequireMcRestart;
IF:if (!allRequireMcRestart)
{
for (IConfigElement element : configElements)
{
if (!element.requiresMcRestart());
break IF;
}
allRequireMcRestart = true;
}
this.modID = modID;
this.configID = configID;
this.isWorldRunning = mc.world != null;
if (title != null)
this.title = title;
this.titleLine2 = titleLine2;
if (this.titleLine2 != null && this.titleLine2.startsWith(" > "))
this.titleLine2 = this.titleLine2.replaceFirst(" > ", "");
}
public static String getAbridgedConfigPath(String path)
{
Minecraft mc = Minecraft.getMinecraft();
if (mc.mcDataDir.getAbsolutePath().endsWith("."))
return path.replace("\\", "/").replace(mc.mcDataDir.getAbsolutePath().replace("\\", "/").substring(0, mc.mcDataDir.getAbsolutePath().length() - 1), "/.minecraft/");
else
return path.replace("\\", "/").replace(mc.mcDataDir.getAbsolutePath().replace("\\", "/"), "/.minecraft");
}
/**
* Adds the buttons (and other controls) to the screen in question. Called when the GUI is displayed and when the
* window resizes, the buttonList is cleared beforehand.
*/
@Override
public void initGui()
{
Keyboard.enableRepeatEvents(true);
if (this.entryList == null || this.needsRefresh)
{
this.entryList = new GuiConfigEntries(this, mc);
this.needsRefresh = false;
}
int undoGlyphWidth = mc.fontRenderer.getStringWidth(UNDO_CHAR) * 2;
int resetGlyphWidth = mc.fontRenderer.getStringWidth(RESET_CHAR) * 2;
int doneWidth = Math.max(mc.fontRenderer.getStringWidth(I18n.format("gui.done")) + 20, 100);
int undoWidth = mc.fontRenderer.getStringWidth(" " + I18n.format("fml.configgui.tooltip.undoChanges")) + undoGlyphWidth + 20;
int resetWidth = mc.fontRenderer.getStringWidth(" " + I18n.format("fml.configgui.tooltip.resetToDefault")) + resetGlyphWidth + 20;
int checkWidth = mc.fontRenderer.getStringWidth(I18n.format("fml.configgui.applyGlobally")) + 13;
int buttonWidthHalf = (doneWidth + 5 + undoWidth + 5 + resetWidth + 5 + checkWidth) / 2;
this.buttonList.add(new GuiButtonExt(2000, this.width / 2 - buttonWidthHalf, this.height - 29, doneWidth, 20, I18n.format("gui.done")));
this.buttonList.add(this.btnDefaultAll = new GuiUnicodeGlyphButton(2001, this.width / 2 - buttonWidthHalf + doneWidth + 5 + undoWidth + 5,
this.height - 29, resetWidth, 20, " " + I18n.format("fml.configgui.tooltip.resetToDefault"), RESET_CHAR, 2.0F));
this.buttonList.add(btnUndoAll = new GuiUnicodeGlyphButton(2002, this.width / 2 - buttonWidthHalf + doneWidth + 5,
this.height - 29, undoWidth, 20, " " + I18n.format("fml.configgui.tooltip.undoChanges"), UNDO_CHAR, 2.0F));
this.buttonList.add(chkApplyGlobally = new GuiCheckBox(2003, this.width / 2 - buttonWidthHalf + doneWidth + 5 + undoWidth + 5 + resetWidth + 5,
this.height - 24, I18n.format("fml.configgui.applyGlobally"), false));
this.undoHoverChecker = new HoverChecker(this.btnUndoAll, 800);
this.resetHoverChecker = new HoverChecker(this.btnDefaultAll, 800);
this.checkBoxHoverChecker = new HoverChecker(chkApplyGlobally, 800);
this.entryList.initGui();
}
/**
* Called when the screen is unloaded. Used to disable keyboard repeat events
*/
@Override
public void onGuiClosed()
{
this.entryList.onGuiClosed();
if (this.configID != null && this.parentScreen instanceof GuiConfig)
{
GuiConfig parentGuiConfig = (GuiConfig) this.parentScreen;
parentGuiConfig.needsRefresh = true;
parentGuiConfig.initGui();
}
if (!(this.parentScreen instanceof GuiConfig))
Keyboard.enableRepeatEvents(false);
}
/**
* Called by the controls from the buttonList when activated. (Mouse pressed for buttons)
*/
@Override
protected void actionPerformed(GuiButton button)
{
if (button.id == 2000)
{
boolean flag = true;
try
{
if ((configID != null || this.parentScreen == null || !(this.parentScreen instanceof GuiConfig))
&& (this.entryList.hasChangedEntry(true)))
{
boolean requiresMcRestart = this.entryList.saveConfigElements();
if (Loader.isModLoaded(modID))
{
ConfigChangedEvent event = new OnConfigChangedEvent(modID, configID, isWorldRunning, requiresMcRestart);
MinecraftForge.EVENT_BUS.post(event);
if (!event.getResult().equals(Result.DENY))
MinecraftForge.EVENT_BUS.post(new PostConfigChangedEvent(modID, configID, isWorldRunning, requiresMcRestart));
if (requiresMcRestart)
{
flag = false;
mc.displayGuiScreen(new GuiMessageDialog(parentScreen, "fml.configgui.gameRestartTitle",
new TextComponentString(I18n.format("fml.configgui.gameRestartRequired")), "fml.configgui.confirmRestartMessage"));
}
if (this.parentScreen instanceof GuiConfig)
((GuiConfig) this.parentScreen).needsRefresh = true;
}
}
}
catch (Throwable e)
{
FMLLog.log.error("Error performing GuiConfig action:", e);
}
if (flag)
this.mc.displayGuiScreen(this.parentScreen);
}
else if (button.id == 2001)
{
this.entryList.setAllToDefault(this.chkApplyGlobally.isChecked());
}
else if (button.id == 2002)
{
this.entryList.undoAllChanges(this.chkApplyGlobally.isChecked());
}
}
/**
* Handles mouse input.
*/
@Override
public void handleMouseInput() throws IOException
{
super.handleMouseInput();
this.entryList.handleMouseInput();
}
/**
* Called when the mouse is clicked. Args : mouseX, mouseY, clickedButton
*/
@Override
protected void mouseClicked(int x, int y, int mouseEvent) throws IOException
{
if (mouseEvent != 0 || !this.entryList.mouseClicked(x, y, mouseEvent))
{
this.entryList.mouseClickedPassThru(x, y, mouseEvent);
super.mouseClicked(x, y, mouseEvent);
}
}
/**
* Called when a mouse button is released.
*/
@Override
protected void mouseReleased(int x, int y, int mouseEvent)
{
if (mouseEvent != 0 || !this.entryList.mouseReleased(x, y, mouseEvent))
{
super.mouseReleased(x, y, mouseEvent);
}
}
/**
* Fired when a key is typed (except F11 which toggles full screen). This is the equivalent of
* KeyListener.keyTyped(KeyEvent e). Args : character (character on the key), keyCode (lwjgl Keyboard key code)
*/
@Override
protected void keyTyped(char eventChar, int eventKey)
{
if (eventKey == Keyboard.KEY_ESCAPE)
this.mc.displayGuiScreen(parentScreen);
else
this.entryList.keyTyped(eventChar, eventKey);
}
/**
* Called from the main game loop to update the screen.
*/
@Override
public void updateScreen()
{
super.updateScreen();
this.entryList.updateScreen();
}
/**
* Draws the screen and all the components in it.
*/
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.drawDefaultBackground();
this.entryList.drawScreen(mouseX, mouseY, partialTicks);
this.drawCenteredString(this.fontRenderer, this.title, this.width / 2, 8, 16777215);
String title2 = this.titleLine2;
if (title2 != null)
{
int strWidth = mc.fontRenderer.getStringWidth(title2);
int ellipsisWidth = mc.fontRenderer.getStringWidth("...");
if (strWidth > width - 6 && strWidth > ellipsisWidth)
title2 = mc.fontRenderer.trimStringToWidth(title2, width - 6 - ellipsisWidth).trim() + "...";
this.drawCenteredString(this.fontRenderer, title2, this.width / 2, 18, 16777215);
}
this.btnUndoAll.enabled = this.entryList.areAnyEntriesEnabled(this.chkApplyGlobally.isChecked()) && this.entryList.hasChangedEntry(this.chkApplyGlobally.isChecked());
this.btnDefaultAll.enabled = this.entryList.areAnyEntriesEnabled(this.chkApplyGlobally.isChecked()) && !this.entryList.areAllEntriesDefault(this.chkApplyGlobally.isChecked());
super.drawScreen(mouseX, mouseY, partialTicks);
this.entryList.drawScreenPost(mouseX, mouseY, partialTicks);
if (this.undoHoverChecker.checkHover(mouseX, mouseY))
this.drawToolTip(Arrays.asList(I18n.format("fml.configgui.tooltip.undoAll").split("\n")), mouseX, mouseY);
if (this.resetHoverChecker.checkHover(mouseX, mouseY))
this.drawToolTip(Arrays.asList(I18n.format("fml.configgui.tooltip.resetAll").split("\n")), mouseX, mouseY);
if (this.checkBoxHoverChecker.checkHover(mouseX, mouseY))
this.drawToolTip(Arrays.asList(I18n.format("fml.configgui.tooltip.applyGlobally").split("\n")), mouseX, mouseY);
}
public void drawToolTip(List<String> stringList, int x, int y)
{
GuiUtils.drawHoveringText(stringList, x, y, width, height, 300, fontRenderer);
}
}

View File

@@ -0,0 +1,248 @@
/*
* 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.client.config;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.text.TextFormatting;
import static net.minecraftforge.fml.client.config.GuiUtils.RESET_CHAR;
import static net.minecraftforge.fml.client.config.GuiUtils.UNDO_CHAR;
import net.minecraftforge.fml.common.FMLLog;
import org.lwjgl.input.Keyboard;
/**
* This class is the base screen used for editing an array-type property. It provides a list of array entries for the user to edit.
* This screen is invoked from a GuiConfig screen by controls that use the EditListPropEntry IGuiConfigListEntry object.
*
* @author bspkrs
*/
public class GuiEditArray extends GuiScreen
{
protected GuiScreen parentScreen;
protected IConfigElement configElement;
protected GuiEditArrayEntries entryList;
protected GuiButtonExt btnUndoChanges, btnDefault, btnDone;
protected String title;
protected String titleLine2;
protected String titleLine3;
protected int slotIndex;
protected final Object[] beforeValues;
protected Object[] currentValues;
protected HoverChecker tooltipHoverChecker;
protected List<String> toolTip;
protected boolean enabled;
public GuiEditArray(GuiScreen parentScreen, IConfigElement configElement, int slotIndex, Object[] currentValues, boolean enabled)
{
this.mc = Minecraft.getMinecraft();
this.parentScreen = parentScreen;
this.configElement = configElement;
this.slotIndex = slotIndex;
this.beforeValues = currentValues;
this.currentValues = currentValues;
this.toolTip = new ArrayList<String>();
this.enabled = enabled;
String propName = I18n.format(configElement.getLanguageKey());
String comment;
comment = I18n.format(configElement.getLanguageKey() + ".tooltip",
"\n" + TextFormatting.AQUA, configElement.getDefault(), configElement.getMinValue(), configElement.getMaxValue());
if (!comment.equals(configElement.getLanguageKey() + ".tooltip"))
Collections.addAll(toolTip, (TextFormatting.GREEN + propName + "\n" + TextFormatting.YELLOW + comment).split("\n"));
else if (configElement.getComment() != null && !configElement.getComment().trim().isEmpty())
Collections.addAll(toolTip, (TextFormatting.GREEN + propName + "\n" + TextFormatting.YELLOW + configElement.getComment()).split("\n"));
else
Collections.addAll(toolTip, (TextFormatting.GREEN + propName + "\n" + TextFormatting.RED + "No tooltip defined.").split("\n"));
if (parentScreen instanceof GuiConfig)
{
this.title = ((GuiConfig) parentScreen).title;
if (((GuiConfig) parentScreen).titleLine2 != null)
{
this.titleLine2 = ((GuiConfig) parentScreen).titleLine2;
this.titleLine3 = I18n.format(configElement.getLanguageKey());
}
else
this.titleLine2 = I18n.format(configElement.getLanguageKey());
this.tooltipHoverChecker = new HoverChecker(28, 37, 0, parentScreen.width, 800);
}
else
{
this.title = I18n.format(configElement.getLanguageKey());
this.tooltipHoverChecker = new HoverChecker(8, 17, 0, parentScreen.width, 800);
}
}
/**
* Adds the buttons (and other controls) to the screen in question. Called when the GUI is displayed and when the
* window resizes, the buttonList is cleared beforehand.
*/
@Override
public void initGui()
{
this.entryList = createEditArrayEntries();
int undoGlyphWidth = mc.fontRenderer.getStringWidth(UNDO_CHAR) * 2;
int resetGlyphWidth = mc.fontRenderer.getStringWidth(RESET_CHAR) * 2;
int doneWidth = Math.max(mc.fontRenderer.getStringWidth(I18n.format("gui.done")) + 20, 100);
int undoWidth = mc.fontRenderer.getStringWidth(" " + I18n.format("fml.configgui.tooltip.undoChanges")) + undoGlyphWidth + 20;
int resetWidth = mc.fontRenderer.getStringWidth(" " + I18n.format("fml.configgui.tooltip.resetToDefault")) + resetGlyphWidth + 20;
int buttonWidthHalf = (doneWidth + 5 + undoWidth + 5 + resetWidth) / 2;
this.buttonList.add(btnDone = new GuiButtonExt(2000, this.width / 2 - buttonWidthHalf, this.height - 29, doneWidth, 20, I18n.format("gui.done")));
this.buttonList.add(btnDefault = new GuiUnicodeGlyphButton(2001, this.width / 2 - buttonWidthHalf + doneWidth + 5 + undoWidth + 5,
this.height - 29, resetWidth, 20, " " + I18n.format("fml.configgui.tooltip.resetToDefault"), RESET_CHAR, 2.0F));
this.buttonList.add(btnUndoChanges = new GuiUnicodeGlyphButton(2002, this.width / 2 - buttonWidthHalf + doneWidth + 5,
this.height - 29, undoWidth, 20, " " + I18n.format("fml.configgui.tooltip.undoChanges"), UNDO_CHAR, 2.0F));
}
/**
* Called by the controls from the buttonList when activated. (Mouse pressed for buttons)
*/
@Override
protected void actionPerformed(GuiButton button)
{
if (button.id == 2000)
{
try
{
this.entryList.saveListChanges();
}
catch (Throwable e)
{
FMLLog.log.error("Error performing GuiEditArray action:", e);
}
this.mc.displayGuiScreen(this.parentScreen);
}
else if (button.id == 2001)
{
this.currentValues = configElement.getDefaults();
this.entryList = createEditArrayEntries();
}
else if (button.id == 2002)
{
this.currentValues = Arrays.copyOf(beforeValues, beforeValues.length);
this.entryList = createEditArrayEntries();
}
}
protected GuiEditArrayEntries createEditArrayEntries()
{
return new GuiEditArrayEntries(this, this.mc, this.configElement, this.beforeValues, this.currentValues);
}
/**
* Handles mouse input.
*/
@Override
public void handleMouseInput() throws IOException
{
super.handleMouseInput();
this.entryList.handleMouseInput();
}
/**
* Called when the mouse is clicked. Args : mouseX, mouseY, clickedButton
*/
@Override
protected void mouseClicked(int x, int y, int mouseEvent) throws IOException
{
if (mouseEvent != 0 || !this.entryList.mouseClicked(x, y, mouseEvent))
{
this.entryList.mouseClickedPassThru(x, y, mouseEvent);
super.mouseClicked(x, y, mouseEvent);
}
}
/**
* Called when a mouse button is released.
*/
@Override
protected void mouseReleased(int x, int y, int mouseEvent)
{
if (mouseEvent != 0 || !this.entryList.mouseReleased(x, y, mouseEvent))
{
super.mouseReleased(x, y, mouseEvent);
}
}
/**
* Fired when a key is typed (except F11 which toggles full screen). This is the equivalent of
* KeyListener.keyTyped(KeyEvent e). Args : character (character on the key), keyCode (lwjgl Keyboard key code)
*/
@Override
protected void keyTyped(char eventChar, int eventKey)
{
if (eventKey == Keyboard.KEY_ESCAPE)
this.mc.displayGuiScreen(parentScreen);
else
this.entryList.keyTyped(eventChar, eventKey);
}
/**
* Called from the main game loop to update the screen.
*/
@Override
public void updateScreen()
{
super.updateScreen();
this.entryList.updateScreen();
}
/**
* Draws the screen and all the components in it.
*/
@Override
public void drawScreen(int par1, int par2, float par3)
{
this.drawDefaultBackground();
this.entryList.drawScreen(par1, par2, par3);
this.drawCenteredString(this.fontRenderer, this.title, this.width / 2, 8, 16777215);
if (this.titleLine2 != null)
this.drawCenteredString(this.fontRenderer, this.titleLine2, this.width / 2, 18, 16777215);
if (this.titleLine3 != null)
this.drawCenteredString(this.fontRenderer, this.titleLine3, this.width / 2, 28, 16777215);
this.btnDone.enabled = this.entryList.isListSavable();
this.btnDefault.enabled = enabled && !this.entryList.isDefault();
this.btnUndoChanges.enabled = enabled && this.entryList.isChanged();
super.drawScreen(par1, par2, par3);
this.entryList.drawScreenPost(par1, par2, par3);
if (this.tooltipHoverChecker != null && this.tooltipHoverChecker.checkHover(par1, par2))
drawToolTip(this.toolTip, par1, par2);
}
public void drawToolTip(List<String> stringList, int x, int y)
{
GuiUtils.drawHoveringText(stringList, x, y, width, height, 300, fontRenderer);
}
}

View File

@@ -0,0 +1,726 @@
/*
* 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.client.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiListExtended;
import net.minecraft.client.gui.GuiTextField;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.text.TextFormatting;
import net.minecraftforge.fml.client.config.GuiConfigEntries.ArrayEntry;
import net.minecraftforge.fml.common.FMLLog;
import org.lwjgl.input.Keyboard;
import static net.minecraftforge.fml.client.config.GuiUtils.INVALID;
import static net.minecraftforge.fml.client.config.GuiUtils.VALID;
/**
* This class implements the scrolling list functionality of the GuiEditList screen. It also provides all the default controls
* for editing array-type properties.
*/
public class GuiEditArrayEntries extends GuiListExtended
{
protected GuiEditArray owningGui;
public IConfigElement configElement;
public List<IArrayEntry> listEntries;
public boolean isDefault;
public boolean isChanged;
public boolean canAddMoreEntries;
public final int controlWidth;
public final Object[] beforeValues;
public Object[] currentValues;
public GuiEditArrayEntries(GuiEditArray parent, Minecraft mc, IConfigElement configElement, Object[] beforeValues, Object[] currentValues)
{
super(mc, parent.width, parent.height, parent.titleLine2 != null ? (parent.titleLine3 != null ? 43 : 33) : 23, parent.height - 32, 20);
this.owningGui = parent;
this.configElement = configElement;
this.beforeValues = beforeValues;
this.currentValues = currentValues;
this.setShowSelectionBox(false);
this.isChanged = !Arrays.deepEquals(beforeValues, currentValues);
this.isDefault = Arrays.deepEquals(currentValues, configElement.getDefaults());
this.canAddMoreEntries = !configElement.isListLengthFixed() && (configElement.getMaxListLength() == -1 || currentValues.length < configElement.getMaxListLength());
listEntries = new ArrayList<IArrayEntry>();
controlWidth = (parent.width / 2) - (configElement.isListLengthFixed() ? 0 : 48);
if (configElement.isList() && configElement.getArrayEntryClass() != null)
{
Class<? extends IArrayEntry> clazz = configElement.getArrayEntryClass();
for (Object value : currentValues)
{
try
{
listEntries.add(clazz.getConstructor(GuiEditArray.class, GuiEditArrayEntries.class, IConfigElement.class, Object.class)
.newInstance(this.owningGui, this, configElement, value));
}
catch (Throwable e)
{
FMLLog.log.error("There was a critical error instantiating the custom IGuiEditListEntry for property {}.", configElement.getName(), e);
}
}
}
else if (configElement.isList() && configElement.getType().equals(ConfigGuiType.BOOLEAN))
for (Object value : currentValues)
listEntries.add(new BooleanEntry(this.owningGui, this, configElement, Boolean.valueOf(value.toString())));
else if (configElement.isList() && configElement.getType().equals(ConfigGuiType.INTEGER))
for (Object value : currentValues)
listEntries.add(new IntegerEntry(this.owningGui, this, configElement, Integer.parseInt(value.toString())));
else if (configElement.isList() && configElement.getType().equals(ConfigGuiType.DOUBLE))
for (Object value : currentValues)
listEntries.add(new DoubleEntry(this.owningGui, this, configElement, Double.parseDouble(value.toString())));
else if (configElement.isList())
for (Object value : currentValues)
listEntries.add(new StringEntry(this.owningGui, this, configElement, value.toString()));
if (!configElement.isListLengthFixed())
listEntries.add(new BaseEntry(this.owningGui, this, configElement));
}
@Override
protected int getScrollBarX()
{
return width - (width / 4);
}
/**
* Gets the width of the list
*/
/**
* Gets the width of the list
*/
@Override
public int getListWidth()
{
return owningGui.width;
}
/**
* Gets the IGuiListEntry object for the given index
*/
@Override
public IArrayEntry getListEntry(int index)
{
return listEntries.get(index);
}
@Override
protected int getSize()
{
return listEntries.size();
}
public void addNewEntry(int index)
{
if (configElement.isList() && configElement.getArrayEntryClass() != null)
{
Class<? extends IArrayEntry> clazz = configElement.getArrayEntryClass();
try
{
Object value;
switch (configElement.getType())
{
case BOOLEAN:
value = true;
break;
case INTEGER:
value = 0;
break;
case DOUBLE:
value = 0.0D;
break;
default:
value = "";
}
listEntries.add(index, clazz.getConstructor(GuiEditArray.class, GuiEditArrayEntries.class, IConfigElement.class, Object.class).newInstance(this.owningGui, this, configElement, value));
}
catch (Throwable e)
{
FMLLog.log.error("There was a critical error instantiating the custom IGuiEditListEntry for property {}.", configElement.getName(), e);
}
}
else if (configElement.isList() && configElement.getType() == ConfigGuiType.BOOLEAN)
listEntries.add(index, new BooleanEntry(this.owningGui, this, this.configElement, true));
else if (configElement.isList() && configElement.getType() == ConfigGuiType.INTEGER)
listEntries.add(index, new IntegerEntry(this.owningGui, this, this.configElement, 0));
else if (configElement.isList() && configElement.getType() == ConfigGuiType.DOUBLE)
listEntries.add(index, new DoubleEntry(this.owningGui, this, this.configElement, 0.0D));
else if (configElement.isList())
listEntries.add(index, new StringEntry(this.owningGui, this, this.configElement, ""));
this.canAddMoreEntries = !configElement.isListLengthFixed()
&& (configElement.getMaxListLength() == -1 || this.listEntries.size() - 1 < configElement.getMaxListLength());
keyTyped((char) Keyboard.CHAR_NONE, Keyboard.KEY_END);
}
public void removeEntry(int index)
{
this.listEntries.remove(index);
this.canAddMoreEntries = !configElement.isListLengthFixed()
&& (configElement.getMaxListLength() == -1 || this.listEntries.size() - 1 < configElement.getMaxListLength());
keyTyped((char) Keyboard.CHAR_NONE, Keyboard.KEY_END);
}
public boolean isChanged()
{
return isChanged;
}
public boolean isDefault()
{
return isDefault;
}
public void recalculateState()
{
isDefault = true;
isChanged = false;
int listLength = configElement.isListLengthFixed() ? listEntries.size() : listEntries.size() - 1;
if (listLength != configElement.getDefaults().length)
{
isDefault = false;
}
if (listLength != beforeValues.length)
{
isChanged = true;
}
if (isDefault)
for (int i = 0; i < listLength; i++)
if (!configElement.getDefaults()[i].equals(listEntries.get(i).getValue()))
isDefault = false;
if (!isChanged)
for (int i = 0; i < listLength; i++)
if (!beforeValues[i].equals(listEntries.get(i).getValue()))
isChanged = true;
}
protected void keyTyped(char eventChar, int eventKey)
{
for (IArrayEntry entry : this.listEntries)
entry.keyTyped(eventChar, eventKey);
recalculateState();
}
protected void updateScreen()
{
for (IArrayEntry entry : this.listEntries)
entry.updateCursorCounter();
}
protected void mouseClickedPassThru(int x, int y, int mouseEvent)
{
for (IArrayEntry entry : this.listEntries)
entry.mouseClicked(x, y, mouseEvent);
}
protected boolean isListSavable()
{
for (IArrayEntry entry : this.listEntries)
if (!entry.isValueSavable())
return false;
return true;
}
protected void saveListChanges()
{
int listLength = configElement.isListLengthFixed() ? listEntries.size() : listEntries.size() - 1;
if (owningGui.slotIndex != -1 && owningGui.parentScreen != null
&& owningGui.parentScreen instanceof GuiConfig
&& ((GuiConfig) owningGui.parentScreen).entryList.getListEntry(owningGui.slotIndex) instanceof ArrayEntry)
{
ArrayEntry entry = (ArrayEntry) ((GuiConfig) owningGui.parentScreen).entryList.getListEntry(owningGui.slotIndex);
Object[] ao = new Object[listLength];
for (int i = 0; i < listLength; i++)
ao[i] = listEntries.get(i).getValue();
entry.setListFromChildScreen(ao);
}
else
{
if (configElement.isList() && configElement.getType() == ConfigGuiType.BOOLEAN)
{
Boolean[] abol = new Boolean[listLength];
for (int i = 0; i < listLength; i++)
abol[i] = Boolean.valueOf(listEntries.get(i).getValue().toString());
configElement.set(abol);
}
else if (configElement.isList() && configElement.getType() == ConfigGuiType.INTEGER)
{
Integer[] ai = new Integer[listLength];
for (int i = 0; i < listLength; i++)
ai[i] = Integer.valueOf(listEntries.get(i).getValue().toString());
configElement.set(ai);
}
else if (configElement.isList() && configElement.getType() == ConfigGuiType.DOUBLE)
{
Double[] ad = new Double[listLength];
for (int i = 0; i < listLength; i++)
ad[i] = Double.valueOf(listEntries.get(i).getValue().toString());
configElement.set(ad);
}
else if (configElement.isList())
{
String[] as = new String[listLength];
for (int i = 0; i < listLength; i++)
as[i] = listEntries.get(i).getValue().toString();
configElement.set(as);
}
}
}
protected void drawScreenPost(int mouseX, int mouseY, float f)
{
for (IArrayEntry entry : this.listEntries)
entry.drawToolTip(mouseX, mouseY);
}
public Minecraft getMC()
{
return this.mc;
}
/**
* IGuiListEntry Inner Classes
*/
public static class DoubleEntry extends StringEntry
{
public DoubleEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement, Double value)
{
super(owningScreen, owningEntryList, configElement, value);
this.isValidated = true;
}
@Override
public void keyTyped(char eventChar, int eventKey)
{
if (owningScreen.enabled || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT
|| eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END)
{
String validChars = "0123456789";
String before = this.textFieldValue.getText();
if (validChars.contains(String.valueOf(eventChar)) ||
(!before.startsWith("-") && this.textFieldValue.getCursorPosition() == 0 && eventChar == '-')
|| (!before.contains(".") && eventChar == '.')
|| eventKey == Keyboard.KEY_BACK || eventKey == Keyboard.KEY_DELETE || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT
|| eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END)
this.textFieldValue.textboxKeyTyped((owningScreen.enabled ? eventChar : Keyboard.CHAR_NONE), eventKey);
if (!textFieldValue.getText().trim().isEmpty() && !textFieldValue.getText().trim().equals("-"))
{
try
{
double value = Double.parseDouble(textFieldValue.getText().trim());
if (value < Double.valueOf(configElement.getMinValue().toString()) || value > Double.valueOf(configElement.getMaxValue().toString()))
this.isValidValue = false;
else
this.isValidValue = true;
}
catch (Throwable e)
{
this.isValidValue = false;
}
}
else
this.isValidValue = false;
}
}
@Override
public Double getValue()
{
try
{
return Double.valueOf(this.textFieldValue.getText().trim());
}
catch (Throwable e)
{
return Double.MAX_VALUE;
}
}
}
public static class IntegerEntry extends StringEntry
{
public IntegerEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement, Integer value)
{
super(owningScreen, owningEntryList, configElement, value);
this.isValidated = true;
}
@Override
public void keyTyped(char eventChar, int eventKey)
{
if (owningScreen.enabled || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT
|| eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END)
{
String validChars = "0123456789";
String before = this.textFieldValue.getText();
if (validChars.contains(String.valueOf(eventChar))
|| (!before.startsWith("-") && this.textFieldValue.getCursorPosition() == 0 && eventChar == '-')
|| eventKey == Keyboard.KEY_BACK || eventKey == Keyboard.KEY_DELETE
|| eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT || eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END)
this.textFieldValue.textboxKeyTyped((owningScreen.enabled ? eventChar : Keyboard.CHAR_NONE), eventKey);
if (!textFieldValue.getText().trim().isEmpty() && !textFieldValue.getText().trim().equals("-"))
{
try
{
long value = Long.parseLong(textFieldValue.getText().trim());
if (value < Integer.valueOf(configElement.getMinValue().toString()) || value > Integer.valueOf(configElement.getMaxValue().toString()))
this.isValidValue = false;
else
this.isValidValue = true;
}
catch (Throwable e)
{
this.isValidValue = false;
}
}
else
this.isValidValue = false;
}
}
@Override
public Integer getValue()
{
try
{
return Integer.valueOf(this.textFieldValue.getText().trim());
}
catch (Throwable e)
{
return Integer.MAX_VALUE;
}
}
}
public static class StringEntry extends BaseEntry
{
protected final GuiTextField textFieldValue;
public StringEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement, Object value)
{
super(owningScreen, owningEntryList, configElement);
this.textFieldValue = new GuiTextField(0, owningEntryList.getMC().fontRenderer, owningEntryList.width / 4 + 1, 0, owningEntryList.controlWidth - 3, 16);
this.textFieldValue.setMaxStringLength(10000);
this.textFieldValue.setText(value.toString());
this.isValidated = configElement.getValidationPattern() != null;
if (configElement.getValidationPattern() != null)
{
if (configElement.getValidationPattern().matcher(this.textFieldValue.getText().trim()).matches())
isValidValue = true;
else
isValidValue = false;
}
}
@Override
public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, int mouseX, int mouseY, boolean isSelected, float partial)
{
super.drawEntry(slotIndex, x, y, listWidth, slotHeight, mouseX, mouseY, isSelected, partial);
if (configElement.isListLengthFixed() || slotIndex != owningEntryList.listEntries.size() - 1)
{
this.textFieldValue.setVisible(true);
this.textFieldValue.y = y + 1;
this.textFieldValue.drawTextBox();
}
else
this.textFieldValue.setVisible(false);
}
@Override
public void keyTyped(char eventChar, int eventKey)
{
if (owningScreen.enabled || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT
|| eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END)
{
this.textFieldValue.textboxKeyTyped((owningScreen.enabled ? eventChar : Keyboard.CHAR_NONE), eventKey);
if (configElement.getValidationPattern() != null)
{
if (configElement.getValidationPattern().matcher(this.textFieldValue.getText().trim()).matches())
isValidValue = true;
else
isValidValue = false;
}
}
}
@Override
public void updateCursorCounter()
{
this.textFieldValue.updateCursorCounter();
}
@Override
public void mouseClicked(int x, int y, int mouseEvent)
{
this.textFieldValue.mouseClicked(x, y, mouseEvent);
}
@Override
public Object getValue()
{
return this.textFieldValue.getText().trim();
}
}
public static class BooleanEntry extends BaseEntry
{
protected final GuiButtonExt btnValue;
private boolean value;
public BooleanEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement, boolean value)
{
super(owningScreen, owningEntryList, configElement);
this.value = value;
this.btnValue = new GuiButtonExt(0, 0, 0, owningEntryList.controlWidth, 18, I18n.format(String.valueOf(value)));
this.btnValue.enabled = owningScreen.enabled;
this.isValidated = false;
}
@Override
public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, int mouseX, int mouseY, boolean isSelected, float partial)
{
super.drawEntry(slotIndex, x, y, listWidth, slotHeight, mouseX, mouseY, isSelected, partial);
this.btnValue.x = listWidth / 4;
this.btnValue.y = y;
String trans = I18n.format(String.valueOf(value));
if (!trans.equals(String.valueOf(value)))
this.btnValue.displayString = trans;
else
this.btnValue.displayString = String.valueOf(value);
btnValue.packedFGColour = value ? GuiUtils.getColorCode('2', true) : GuiUtils.getColorCode('4', true);
this.btnValue.drawButton(owningEntryList.getMC(), mouseX, mouseY, partial);
}
/**
* Called when the mouse is clicked within this entry. Returning true means that something within this entry was
* clicked and the list should not be dragged.
*/
@Override
public boolean mousePressed(int index, int x, int y, int mouseEvent, int relativeX, int relativeY)
{
if (this.btnValue.mousePressed(owningEntryList.getMC(), x, y))
{
btnValue.playPressSound(owningEntryList.getMC().getSoundHandler());
value = !value;
owningEntryList.recalculateState();
return true;
}
return super.mousePressed(index, x, y, mouseEvent, relativeX, relativeY);
}
/**
* Fired when the mouse button is released. Arguments: index, x, y, mouseEvent, relativeX, relativeY
*/
@Override
public void mouseReleased(int index, int x, int y, int mouseEvent, int relativeX, int relativeY)
{
this.btnValue.mouseReleased(x, y);
super.mouseReleased(index, x, y, mouseEvent, relativeX, relativeY);
}
@Override
public Object getValue()
{
return value;
}
}
public static class BaseEntry implements IArrayEntry
{
protected final GuiEditArray owningScreen;
protected final GuiEditArrayEntries owningEntryList;
protected final IConfigElement configElement;
protected final GuiButtonExt btnAddNewEntryAbove;
private final HoverChecker addNewEntryAboveHoverChecker;
protected final GuiButtonExt btnRemoveEntry;
private final HoverChecker removeEntryHoverChecker;
private List<String> addNewToolTip, removeToolTip;
protected boolean isValidValue = true;
protected boolean isValidated = false;
public BaseEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement)
{
this.owningScreen = owningScreen;
this.owningEntryList = owningEntryList;
this.configElement = configElement;
this.btnAddNewEntryAbove = new GuiButtonExt(0, 0, 0, 18, 18, "+");
this.btnAddNewEntryAbove.packedFGColour = GuiUtils.getColorCode('2', true);
this.btnAddNewEntryAbove.enabled = owningScreen.enabled;
this.btnRemoveEntry = new GuiButtonExt(0, 0, 0, 18, 18, "x");
this.btnRemoveEntry.packedFGColour = GuiUtils.getColorCode('c', true);
this.btnRemoveEntry.enabled = owningScreen.enabled;
this.addNewEntryAboveHoverChecker = new HoverChecker(this.btnAddNewEntryAbove, 800);
this.removeEntryHoverChecker = new HoverChecker(this.btnRemoveEntry, 800);
this.addNewToolTip = new ArrayList<String>();
this.removeToolTip = new ArrayList<String>();
addNewToolTip.add(I18n.format("fml.configgui.tooltip.addNewEntryAbove"));
removeToolTip.add(I18n.format("fml.configgui.tooltip.removeEntry"));
}
@Override
public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, int mouseX, int mouseY, boolean isSelected, float partial)
{
if (this.getValue() != null && this.isValidated)
owningEntryList.getMC().fontRenderer.drawString(
isValidValue ? TextFormatting.GREEN + VALID : TextFormatting.RED + INVALID,
listWidth / 4 - owningEntryList.getMC().fontRenderer.getStringWidth(VALID) - 2,
y + slotHeight / 2 - owningEntryList.getMC().fontRenderer.FONT_HEIGHT / 2,
16777215);
int half = listWidth / 2;
if (owningEntryList.canAddMoreEntries)
{
this.btnAddNewEntryAbove.visible = true;
this.btnAddNewEntryAbove.x = half + ((half / 2) - 44);
this.btnAddNewEntryAbove.y = y;
this.btnAddNewEntryAbove.drawButton(owningEntryList.getMC(), mouseX, mouseY, partial);
}
else
this.btnAddNewEntryAbove.visible = false;
if (!configElement.isListLengthFixed() && slotIndex != owningEntryList.listEntries.size() - 1)
{
this.btnRemoveEntry.visible = true;
this.btnRemoveEntry.x = half + ((half / 2) - 22);
this.btnRemoveEntry.y = y;
this.btnRemoveEntry.drawButton(owningEntryList.getMC(), mouseX, mouseY, partial);
}
else
this.btnRemoveEntry.visible = false;
}
@Override
public void drawToolTip(int mouseX, int mouseY)
{
boolean canHover = mouseY < owningEntryList.bottom && mouseY > owningEntryList.top;
if (this.btnAddNewEntryAbove.visible && this.addNewEntryAboveHoverChecker.checkHover(mouseX, mouseY, canHover))
owningScreen.drawToolTip(this.addNewToolTip, mouseX, mouseY);
if (this.btnRemoveEntry.visible && this.removeEntryHoverChecker.checkHover(mouseX, mouseY, canHover))
owningScreen.drawToolTip(this.removeToolTip, mouseX, mouseY);
}
/**
* Called when the mouse is clicked within this entry. Returning true means that something within this entry was
* clicked and the list should not be dragged.
*/
@Override
public boolean mousePressed(int index, int x, int y, int mouseEvent, int relativeX, int relativeY)
{
if (this.btnAddNewEntryAbove.mousePressed(owningEntryList.getMC(), x, y))
{
btnAddNewEntryAbove.playPressSound(owningEntryList.getMC().getSoundHandler());
owningEntryList.addNewEntry(index);
owningEntryList.recalculateState();
return true;
}
else if (this.btnRemoveEntry.mousePressed(owningEntryList.getMC(), x, y))
{
btnRemoveEntry.playPressSound(owningEntryList.getMC().getSoundHandler());
owningEntryList.removeEntry(index);
owningEntryList.recalculateState();
return true;
}
return false;
}
/**
* Fired when the mouse button is released. Arguments: index, x, y, mouseEvent, relativeX, relativeY
*/
@Override
public void mouseReleased(int index, int x, int y, int mouseEvent, int relativeX, int relativeY)
{
this.btnAddNewEntryAbove.mouseReleased(x, y);
this.btnRemoveEntry.mouseReleased(x, y);
}
@Override
public void keyTyped(char eventChar, int eventKey)
{}
@Override
public void updateCursorCounter()
{}
@Override
public void mouseClicked(int x, int y, int mouseEvent)
{}
@Override
public boolean isValueSavable()
{
return isValidValue;
}
@Override
public Object getValue()
{
return null;
}
@Override
public void updatePosition(int p_178011_1_, int p_178011_2_, int p_178011_3_, float partial){}
}
public static interface IArrayEntry extends GuiListExtended.IGuiListEntry
{
void keyTyped(char eventChar, int eventKey);
void updateCursorCounter();
void mouseClicked(int x, int y, int mouseEvent);
void drawToolTip(int mouseX, int mouseY);
boolean isValueSavable();
Object getValue();
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.client.config;
import net.minecraft.client.gui.GuiDisconnected;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.text.ITextComponent;
import javax.annotation.Nullable;
public class GuiMessageDialog extends GuiDisconnected
{
protected String buttonText;
public GuiMessageDialog(@Nullable GuiScreen nextScreen, String title, ITextComponent message, String buttonText)
{
super(nextScreen, title, message);
this.buttonText = buttonText;
}
/**
* Adds the buttons (and other controls) to the screen in question. Called when the GUI is displayed and when the
* window resizes, the buttonList is cleared beforehand.
*/
@Override
public void initGui()
{
super.initGui();
buttonList.get(0).displayString = I18n.format(buttonText);
}
}

View File

@@ -0,0 +1,206 @@
/*
* 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.client.config;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.text.TextFormatting;
import net.minecraftforge.fml.common.FMLLog;
import static net.minecraftforge.fml.client.config.GuiUtils.RESET_CHAR;
import static net.minecraftforge.fml.client.config.GuiUtils.UNDO_CHAR;
/**
* This class provides a screen that allows the user to select a value from a list.
*
* @author bspkrs
*/
public class GuiSelectString extends GuiScreen
{
protected GuiScreen parentScreen;
protected IConfigElement configElement;
protected GuiSelectStringEntries entryList;
protected GuiButtonExt btnUndoChanges, btnDefault, btnDone;
protected String title;
protected String titleLine2;
protected String titleLine3;
protected int slotIndex;
protected final Map<Object, String> selectableValues;
public final Object beforeValue;
public Object currentValue;
protected HoverChecker tooltipHoverChecker;
protected List<String> toolTip;
protected boolean enabled;
public GuiSelectString(GuiScreen parentScreen, IConfigElement configElement, int slotIndex, Map<Object, String> selectableValues, Object currentValue, boolean enabled)
{
this.mc = Minecraft.getMinecraft();
this.parentScreen = parentScreen;
this.configElement = configElement;
this.slotIndex = slotIndex;
this.selectableValues = selectableValues;
this.beforeValue = currentValue;
this.currentValue = currentValue;
this.toolTip = new ArrayList<String>();
this.enabled = enabled;
String propName = I18n.format(configElement.getLanguageKey());
String comment;
comment = I18n.format(configElement.getLanguageKey() + ".tooltip",
"\n" + TextFormatting.AQUA, configElement.getDefault(), configElement.getMinValue(), configElement.getMaxValue());
if (!comment.equals(configElement.getLanguageKey() + ".tooltip"))
Collections.addAll(toolTip, (TextFormatting.GREEN + propName + "\n" + TextFormatting.YELLOW + comment).split("\n"));
else if (configElement.getComment() != null && !configElement.getComment().trim().isEmpty())
Collections.addAll(toolTip, (TextFormatting.GREEN + propName + "\n" + TextFormatting.YELLOW + configElement.getComment()).split("\n"));
else
Collections.addAll(toolTip, (TextFormatting.GREEN + propName + "\n" + TextFormatting.RED + "No tooltip defined.").split("\n"));
if (parentScreen instanceof GuiConfig)
{
this.title = ((GuiConfig) parentScreen).title;
this.titleLine2 = ((GuiConfig) parentScreen).titleLine2;
this.titleLine3 = I18n.format(configElement.getLanguageKey());
this.tooltipHoverChecker = new HoverChecker(28, 37, 0, parentScreen.width, 800);
if(titleLine3 != null && titleLine2 == null)
{
((GuiConfig) parentScreen).titleLine2 = "";
this.titleLine2 = "";
}
}
else
{
this.title = I18n.format(configElement.getLanguageKey());
this.tooltipHoverChecker = new HoverChecker(8, 17, 0, parentScreen.width, 800);
}
}
/**
* Adds the buttons (and other controls) to the screen in question. Called when the GUI is displayed and when the
* window resizes, the buttonList is cleared beforehand.
*/
@Override
public void initGui()
{
this.entryList = new GuiSelectStringEntries(this, this.mc, this.configElement, this.selectableValues);
int undoGlyphWidth = mc.fontRenderer.getStringWidth(UNDO_CHAR) * 2;
int resetGlyphWidth = mc.fontRenderer.getStringWidth(RESET_CHAR) * 2;
int doneWidth = Math.max(mc.fontRenderer.getStringWidth(I18n.format("gui.done")) + 20, 100);
int undoWidth = mc.fontRenderer.getStringWidth(" " + I18n.format("fml.configgui.tooltip.undoChanges")) + undoGlyphWidth + 20;
int resetWidth = mc.fontRenderer.getStringWidth(" " + I18n.format("fml.configgui.tooltip.resetToDefault")) + resetGlyphWidth + 20;
int buttonWidthHalf = (doneWidth + 5 + undoWidth + 5 + resetWidth) / 2;
this.buttonList.add(btnDone = new GuiButtonExt(2000, this.width / 2 - buttonWidthHalf, this.height - 29, doneWidth, 20, I18n.format("gui.done")));
this.buttonList.add(btnDefault = new GuiUnicodeGlyphButton(2001, this.width / 2 - buttonWidthHalf + doneWidth + 5 + undoWidth + 5,
this.height - 29, resetWidth, 20, " " + I18n.format("fml.configgui.tooltip.resetToDefault"), RESET_CHAR, 2.0F));
this.buttonList.add(btnUndoChanges = new GuiUnicodeGlyphButton(2002, this.width / 2 - buttonWidthHalf + doneWidth + 5,
this.height - 29, undoWidth, 20, " " + I18n.format("fml.configgui.tooltip.undoChanges"), UNDO_CHAR, 2.0F));
}
/**
* Called by the controls from the buttonList when activated. (Mouse pressed for buttons)
*/
@Override
protected void actionPerformed(GuiButton button)
{
if (button.id == 2000)
{
try
{
this.entryList.saveChanges();
}
catch (Throwable e)
{
FMLLog.log.error("Error performing GuiSelectString action:", e);
}
this.mc.displayGuiScreen(this.parentScreen);
}
else if (button.id == 2001)
{
this.currentValue = configElement.getDefault();
this.entryList = new GuiSelectStringEntries(this, this.mc, this.configElement, this.selectableValues);
}
else if (button.id == 2002)
{
this.currentValue = beforeValue;
this.entryList = new GuiSelectStringEntries(this, this.mc, this.configElement, this.selectableValues);
}
}
/**
* Handles mouse input.
*/
@Override
public void handleMouseInput() throws IOException
{
super.handleMouseInput();
this.entryList.handleMouseInput();
}
/**
* Called when a mouse button is released.
*/
@Override
protected void mouseReleased(int x, int y, int mouseEvent)
{
if (mouseEvent != 0 || !this.entryList.mouseReleased(x, y, mouseEvent))
{
super.mouseReleased(x, y, mouseEvent);
}
}
/**
* Draws the screen and all the components in it.
*/
@Override
public void drawScreen(int par1, int par2, float par3)
{
this.drawDefaultBackground();
this.entryList.drawScreen(par1, par2, par3);
this.drawCenteredString(this.fontRenderer, this.title, this.width / 2, 8, 16777215);
if (this.titleLine2 != null)
this.drawCenteredString(this.fontRenderer, this.titleLine2, this.width / 2, 18, 16777215);
if (this.titleLine3 != null)
this.drawCenteredString(this.fontRenderer, this.titleLine3, this.width / 2, 28, 16777215);
this.btnDone.enabled = currentValue != null;
this.btnDefault.enabled = enabled && !this.entryList.isDefault();
this.btnUndoChanges.enabled = enabled && this.entryList.isChanged();
super.drawScreen(par1, par2, par3);
if (this.tooltipHoverChecker != null && this.tooltipHoverChecker.checkHover(par1, par2))
drawToolTip(this.toolTip, par1, par2);
}
public void drawToolTip(List<String> stringList, int x, int y)
{
GuiUtils.drawHoveringText(stringList, x, y, width, height, 300, fontRenderer);
}
}

View File

@@ -0,0 +1,224 @@
/*
* 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.client.config;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiListExtended;
import net.minecraftforge.fml.client.config.GuiConfigEntries.SelectValueEntry;
/**
* This class implements the scrolling list functionality of the GuiSelectString screen.
*
* @author bspkrs
*/
public class GuiSelectStringEntries extends GuiListExtended
{
public GuiSelectString owningScreen;
public Minecraft mc;
public IConfigElement configElement;
public List<IGuiSelectStringListEntry> listEntries;
public final Map<Object, String> selectableValues;
public int selectedIndex = -1;
public int maxEntryWidth = 0;
public GuiSelectStringEntries(GuiSelectString owningScreen, Minecraft mc, IConfigElement configElement, Map<Object, String> selectableValues)
{
super(mc, owningScreen.width, owningScreen.height, owningScreen.titleLine2 != null ? (owningScreen.titleLine3 != null ? 43 : 33) : 23,
owningScreen.height - 32, 11);
this.owningScreen = owningScreen;
this.mc = mc;
this.configElement = configElement;
this.selectableValues = selectableValues;
this.setShowSelectionBox(true);
listEntries = new ArrayList<IGuiSelectStringListEntry>();
int index = 0;
List<Entry<Object, String>> sortedList = new ArrayList<Entry<Object, String>>(selectableValues.entrySet());
Collections.sort(sortedList, new EntryComparator());
for (Entry<Object, String> entry : sortedList)
{
listEntries.add(new ListEntry(this, entry));
if (mc.fontRenderer.getStringWidth(entry.getValue()) > maxEntryWidth)
maxEntryWidth = mc.fontRenderer.getStringWidth(entry.getValue());
if (owningScreen.currentValue.equals(entry.getKey()))
{
this.selectedIndex = index;
}
index++;
}
}
public static class EntryComparator implements Comparator<Entry<Object, String>>
{
@Override
public int compare(Entry<Object, String> o1, Entry<Object, String> o2)
{
int compare = o1.getValue().toLowerCase(Locale.US).compareTo(o2.getValue().toLowerCase(Locale.US));
if (compare == 0)
compare = o1.getKey().toString().toLowerCase(Locale.US).compareTo(o2.getKey().toString().toLowerCase(Locale.US));
return compare;
}
}
/**
* The element in the slot that was clicked, boolean for whether it was double clicked or not
*/
/**
* The element in the slot that was clicked, boolean for whether it was double clicked or not
*/
@Override
protected void elementClicked(int index, boolean doubleClick, int mouseX, int mouseY)
{
selectedIndex = index;
owningScreen.currentValue = listEntries.get(index).getValue();
}
/**
* Returns true if the element passed in is currently selected
*/
/**
* Returns true if the element passed in is currently selected
*/
@Override
protected boolean isSelected(int index)
{
return index == selectedIndex;
}
@Override
protected int getScrollBarX()
{
return width / 2 + this.maxEntryWidth / 2 + 5;
}
/**
* Gets the width of the list
*/
/**
* Gets the width of the list
*/
@Override
public int getListWidth()
{
return maxEntryWidth + 5;
}
/**
* Gets the IGuiListEntry object for the given index
*/
@Override
public IGuiSelectStringListEntry getListEntry(int index)
{
return listEntries.get(index);
}
@Override
protected int getSize()
{
return listEntries.size();
}
public boolean isChanged()
{
return owningScreen.beforeValue != null ? !owningScreen.beforeValue.equals(owningScreen.currentValue) : owningScreen.currentValue != null;
}
public boolean isDefault()
{
return owningScreen.currentValue != null ? owningScreen.currentValue.equals(configElement.getDefault()) : configElement.getDefault() == null;
}
public void saveChanges()
{
if (owningScreen.slotIndex != -1 && owningScreen.parentScreen != null
&& owningScreen.parentScreen instanceof GuiConfig
&& ((GuiConfig) owningScreen.parentScreen).entryList.getListEntry(owningScreen.slotIndex) instanceof SelectValueEntry)
{
SelectValueEntry entry = (SelectValueEntry) ((GuiConfig) owningScreen.parentScreen).entryList.getListEntry(owningScreen.slotIndex);
entry.setValueFromChildScreen(owningScreen.currentValue);
}
else
configElement.set(owningScreen.currentValue);
}
public static class ListEntry implements IGuiSelectStringListEntry
{
protected final GuiSelectStringEntries owningList;
protected final Entry<Object, String> value;
public ListEntry(GuiSelectStringEntries owningList, Entry<Object, String> value)
{
this.owningList = owningList;
this.value = value;
}
@Override
public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, int mouseX, int mouseY, boolean isSelected, float partial)
{
owningList.mc.fontRenderer.drawString(value.getValue(), x + 1, y, slotIndex == owningList.selectedIndex ? 16777215 : 14737632);
}
/**
* Called when the mouse is clicked within this entry. Returning true means that something within this entry was
* clicked and the list should not be dragged.
*/
@Override
public boolean mousePressed(int index, int x, int y, int mouseEvent, int relativeX, int relativeY)
{
return false;
}
/**
* Fired when the mouse button is released. Arguments: index, x, y, mouseEvent, relativeX, relativeY
*/
@Override
public void mouseReleased(int index, int x, int y, int mouseEvent, int relativeX, int relativeY)
{}
@Override
public Object getValue()
{
return value.getKey();
}
@Override
public void updatePosition(int slotIndex, int x, int y, float partialTicks){}
}
public static interface IGuiSelectStringListEntry extends GuiListExtended.IGuiListEntry
{
Object getValue();
}
}

View File

@@ -0,0 +1,239 @@
/*
* 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.client.config;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GlStateManager;
import javax.annotation.Nullable;
/**
* This class is blatantly stolen from iChunUtils with permission.
*
* @author iChun
*/
public class GuiSlider extends GuiButtonExt
{
/** The value of this slider control. */
public double sliderValue = 1.0F;
public String dispString = "";
/** Is this slider control being dragged. */
public boolean dragging = false;
public boolean showDecimal = true;
public double minValue = 0.0D;
public double maxValue = 5.0D;
public int precision = 1;
@Nullable
public ISlider parent = null;
public String suffix = "";
public boolean drawString = true;
public GuiSlider(int id, int xPos, int yPos, int width, int height, String prefix, String suf, double minVal, double maxVal, double currentVal, boolean showDec, boolean drawStr)
{
this(id, xPos, yPos, width, height, prefix, suf, minVal, maxVal, currentVal, showDec, drawStr, null);
}
public GuiSlider(int id, int xPos, int yPos, int width, int height, String prefix, String suf, double minVal, double maxVal, double currentVal, boolean showDec, boolean drawStr, @Nullable ISlider par)
{
super(id, xPos, yPos, width, height, prefix);
minValue = minVal;
maxValue = maxVal;
sliderValue = (currentVal - minValue) / (maxValue - minValue);
dispString = prefix;
parent = par;
suffix = suf;
showDecimal = showDec;
String val;
if (showDecimal)
{
val = Double.toString(sliderValue * (maxValue - minValue) + minValue);
precision = Math.min(val.substring(val.indexOf(".") + 1).length(), 4);
}
else
{
val = Integer.toString((int)Math.round(sliderValue * (maxValue - minValue) + minValue));
precision = 0;
}
displayString = dispString + val + suffix;
drawString = drawStr;
if(!drawString)
{
displayString = "";
}
}
public GuiSlider(int id, int xPos, int yPos, String displayStr, double minVal, double maxVal, double currentVal, ISlider par)
{
this(id, xPos, yPos, 150, 20, displayStr, "", minVal, maxVal, currentVal, true, true, par);
}
/**
* Returns 0 if the button is disabled, 1 if the mouse is NOT hovering over this button and 2 if it IS hovering over
* this button.
*/
/**
* Returns 0 if the button is disabled, 1 if the mouse is NOT hovering over this button and 2 if it IS hovering over
* this button.
*/
@Override
public int getHoverState(boolean par1)
{
return 0;
}
/**
* Fired when the mouse button is dragged. Equivalent of MouseListener.mouseDragged(MouseEvent e).
*/
/**
* Fired when the mouse button is dragged. Equivalent of MouseListener.mouseDragged(MouseEvent e).
*/
@Override
protected void mouseDragged(Minecraft par1Minecraft, int par2, int par3)
{
if (this.visible)
{
if (this.dragging)
{
this.sliderValue = (par2 - (this.x + 4)) / (float)(this.width - 8);
updateSlider();
}
GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
this.drawTexturedModalRect(this.x + (int)(this.sliderValue * (float)(this.width - 8)), this.y, 0, 66, 4, 20);
this.drawTexturedModalRect(this.x + (int)(this.sliderValue * (float)(this.width - 8)) + 4, this.y, 196, 66, 4, 20);
}
}
/**
* Returns true if the mouse has been pressed on this control. Equivalent of MouseListener.mousePressed(MouseEvent
* e).
*/
/**
* Returns true if the mouse has been pressed on this control. Equivalent of MouseListener.mousePressed(MouseEvent
* e).
*/
@Override
public boolean mousePressed(Minecraft par1Minecraft, int par2, int par3)
{
if (super.mousePressed(par1Minecraft, par2, par3))
{
this.sliderValue = (float)(par2 - (this.x + 4)) / (float)(this.width - 8);
updateSlider();
this.dragging = true;
return true;
}
else
{
return false;
}
}
public void updateSlider()
{
if (this.sliderValue < 0.0F)
{
this.sliderValue = 0.0F;
}
if (this.sliderValue > 1.0F)
{
this.sliderValue = 1.0F;
}
String val;
if (showDecimal)
{
val = Double.toString(sliderValue * (maxValue - minValue) + minValue);
if (val.substring(val.indexOf(".") + 1).length() > precision)
{
val = val.substring(0, val.indexOf(".") + precision + 1);
if (val.endsWith("."))
{
val = val.substring(0, val.indexOf(".") + precision);
}
}
else
{
while (val.substring(val.indexOf(".") + 1).length() < precision)
{
val = val + "0";
}
}
}
else
{
val = Integer.toString((int)Math.round(sliderValue * (maxValue - minValue) + minValue));
}
if(drawString)
{
displayString = dispString + val + suffix;
}
if (parent != null)
{
parent.onChangeSliderValue(this);
}
}
/**
* Fired when the mouse button is released. Equivalent of MouseListener.mouseReleased(MouseEvent e).
*/
/**
* Fired when the mouse button is released. Equivalent of MouseListener.mouseReleased(MouseEvent e).
*/
@Override
public void mouseReleased(int par1, int par2)
{
this.dragging = false;
}
public int getValueInt()
{
return (int)Math.round(sliderValue * (maxValue - minValue) + minValue);
}
public double getValue()
{
return sliderValue * (maxValue - minValue) + minValue;
}
public void setValue(double d)
{
this.sliderValue = (d - minValue) / (maxValue - minValue);
}
public static interface ISlider
{
void onChangeSliderValue(GuiSlider slider);
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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.client.config;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.renderer.GlStateManager;
/**
* This class provides a button that shows a string glyph at the beginning. The glyph can be scaled using the glyphScale parameter.
*
* @author bspkrs
*/
public class GuiUnicodeGlyphButton extends GuiButtonExt
{
public String glyph;
public float glyphScale;
public GuiUnicodeGlyphButton(int id, int xPos, int yPos, int width, int height, String displayString, String glyph, float glyphScale)
{
super(id, xPos, yPos, width, height, displayString);
this.glyph = glyph;
this.glyphScale = glyphScale;
}
/**
* Draws this button to the screen.
*/
@Override
public void drawButton(Minecraft mc, int mouseX, int mouseY, float partial)
{
if (this.visible)
{
this.hovered = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height;
int k = this.getHoverState(this.hovered);
GuiUtils.drawContinuousTexturedBox(GuiButton.BUTTON_TEXTURES, this.x, this.y, 0, 46 + k * 20, this.width, this.height, 200, 20, 2, 3, 2, 2, this.zLevel);
this.mouseDragged(mc, mouseX, mouseY);
int color = 14737632;
if (packedFGColour != 0)
{
color = packedFGColour;
}
else if (!this.enabled)
{
color = 10526880;
}
else if (this.hovered)
{
color = 16777120;
}
String buttonText = this.displayString;
int glyphWidth = (int) (mc.fontRenderer.getStringWidth(glyph) * glyphScale);
int strWidth = mc.fontRenderer.getStringWidth(buttonText);
int ellipsisWidth = mc.fontRenderer.getStringWidth("...");
int totalWidth = strWidth + glyphWidth;
if (totalWidth > width - 6 && totalWidth > ellipsisWidth)
buttonText = mc.fontRenderer.trimStringToWidth(buttonText, width - 6 - ellipsisWidth).trim() + "...";
strWidth = mc.fontRenderer.getStringWidth(buttonText);
totalWidth = glyphWidth + strWidth;
GlStateManager.pushMatrix();
GlStateManager.scale(glyphScale, glyphScale, 1.0F);
this.drawCenteredString(mc.fontRenderer, glyph,
(int) (((this.x + (this.width / 2) - (strWidth / 2)) / glyphScale) - (glyphWidth / (2 * glyphScale)) + 2),
(int) (((this.y + ((this.height - 8) / glyphScale) / 2) - 1) / glyphScale), color);
GlStateManager.popMatrix();
this.drawCenteredString(mc.fontRenderer, buttonText, (int) (this.x + (this.width / 2) + (glyphWidth / glyphScale)),
this.y + (this.height - 8) / 2, color);
}
}
}

View File

@@ -0,0 +1,449 @@
/*
* 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.client.config;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.event.RenderTooltipEvent;
import net.minecraftforge.common.MinecraftForge;
import org.lwjgl.opengl.GL11;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nonnull;
/**
* This class provides several methods and constants used by the Config GUI classes.
*
* @author bspkrs
*/
public class GuiUtils
{
public static final String UNDO_CHAR = "\u21B6";
public static final String RESET_CHAR = "\u2604";
public static final String VALID = "\u2714";
public static final String INVALID = "\u2715";
public static int[] colorCodes = new int[] { 0, 170, 43520, 43690, 11141120, 11141290, 16755200, 11184810, 5592405, 5592575, 5635925, 5636095, 16733525, 16733695, 16777045, 16777215,
0, 42, 10752, 10794, 2752512, 2752554, 2763264, 2763306, 1381653, 1381695, 1392405, 1392447, 4134165, 4134207, 4144917, 4144959 };
public static int getColorCode(char c, boolean isLighter)
{
return colorCodes[isLighter ? "0123456789abcdef".indexOf(c) : "0123456789abcdef".indexOf(c) + 16];
}
/**
* Draws a textured box of any size (smallest size is borderSize * 2 square) based on a fixed size textured box with continuous borders
* and filler. It is assumed that the desired texture ResourceLocation object has been bound using
* Minecraft.getMinecraft().getTextureManager().bindTexture(resourceLocation).
*
* @param x x axis offset
* @param y y axis offset
* @param u bound resource location image x offset
* @param v bound resource location image y offset
* @param width the desired box width
* @param height the desired box height
* @param textureWidth the width of the box texture in the resource location image
* @param textureHeight the height of the box texture in the resource location image
* @param borderSize the size of the box's borders
* @param zLevel the zLevel to draw at
*/
public static void drawContinuousTexturedBox(int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight,
int borderSize, float zLevel)
{
drawContinuousTexturedBox(x, y, u, v, width, height, textureWidth, textureHeight, borderSize, borderSize, borderSize, borderSize, zLevel);
}
/**
* Draws a textured box of any size (smallest size is borderSize * 2 square) based on a fixed size textured box with continuous borders
* and filler. The provided ResourceLocation object will be bound using
* Minecraft.getMinecraft().getTextureManager().bindTexture(resourceLocation).
*
* @param res the ResourceLocation object that contains the desired image
* @param x x axis offset
* @param y y axis offset
* @param u bound resource location image x offset
* @param v bound resource location image y offset
* @param width the desired box width
* @param height the desired box height
* @param textureWidth the width of the box texture in the resource location image
* @param textureHeight the height of the box texture in the resource location image
* @param borderSize the size of the box's borders
* @param zLevel the zLevel to draw at
*/
public static void drawContinuousTexturedBox(ResourceLocation res, int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight,
int borderSize, float zLevel)
{
drawContinuousTexturedBox(res, x, y, u, v, width, height, textureWidth, textureHeight, borderSize, borderSize, borderSize, borderSize, zLevel);
}
/**
* Draws a textured box of any size (smallest size is borderSize * 2 square) based on a fixed size textured box with continuous borders
* and filler. The provided ResourceLocation object will be bound using
* Minecraft.getMinecraft().getTextureManager().bindTexture(resourceLocation).
*
* @param res the ResourceLocation object that contains the desired image
* @param x x axis offset
* @param y y axis offset
* @param u bound resource location image x offset
* @param v bound resource location image y offset
* @param width the desired box width
* @param height the desired box height
* @param textureWidth the width of the box texture in the resource location image
* @param textureHeight the height of the box texture in the resource location image
* @param topBorder the size of the box's top border
* @param bottomBorder the size of the box's bottom border
* @param leftBorder the size of the box's left border
* @param rightBorder the size of the box's right border
* @param zLevel the zLevel to draw at
*/
public static void drawContinuousTexturedBox(ResourceLocation res, int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight,
int topBorder, int bottomBorder, int leftBorder, int rightBorder, float zLevel)
{
Minecraft.getMinecraft().getTextureManager().bindTexture(res);
drawContinuousTexturedBox(x, y, u, v, width, height, textureWidth, textureHeight, topBorder, bottomBorder, leftBorder, rightBorder, zLevel);
}
/**
* Draws a textured box of any size (smallest size is borderSize * 2 square) based on a fixed size textured box with continuous borders
* and filler. It is assumed that the desired texture ResourceLocation object has been bound using
* Minecraft.getMinecraft().getTextureManager().bindTexture(resourceLocation).
*
* @param x x axis offset
* @param y y axis offset
* @param u bound resource location image x offset
* @param v bound resource location image y offset
* @param width the desired box width
* @param height the desired box height
* @param textureWidth the width of the box texture in the resource location image
* @param textureHeight the height of the box texture in the resource location image
* @param topBorder the size of the box's top border
* @param bottomBorder the size of the box's bottom border
* @param leftBorder the size of the box's left border
* @param rightBorder the size of the box's right border
* @param zLevel the zLevel to draw at
*/
public static void drawContinuousTexturedBox(int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight,
int topBorder, int bottomBorder, int leftBorder, int rightBorder, float zLevel)
{
GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
GlStateManager.enableBlend();
GlStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, 1, 0);
int fillerWidth = textureWidth - leftBorder - rightBorder;
int fillerHeight = textureHeight - topBorder - bottomBorder;
int canvasWidth = width - leftBorder - rightBorder;
int canvasHeight = height - topBorder - bottomBorder;
int xPasses = canvasWidth / fillerWidth;
int remainderWidth = canvasWidth % fillerWidth;
int yPasses = canvasHeight / fillerHeight;
int remainderHeight = canvasHeight % fillerHeight;
// Draw Border
// Top Left
drawTexturedModalRect(x, y, u, v, leftBorder, topBorder, zLevel);
// Top Right
drawTexturedModalRect(x + leftBorder + canvasWidth, y, u + leftBorder + fillerWidth, v, rightBorder, topBorder, zLevel);
// Bottom Left
drawTexturedModalRect(x, y + topBorder + canvasHeight, u, v + topBorder + fillerHeight, leftBorder, bottomBorder, zLevel);
// Bottom Right
drawTexturedModalRect(x + leftBorder + canvasWidth, y + topBorder + canvasHeight, u + leftBorder + fillerWidth, v + topBorder + fillerHeight, rightBorder, bottomBorder, zLevel);
for (int i = 0; i < xPasses + (remainderWidth > 0 ? 1 : 0); i++)
{
// Top Border
drawTexturedModalRect(x + leftBorder + (i * fillerWidth), y, u + leftBorder, v, (i == xPasses ? remainderWidth : fillerWidth), topBorder, zLevel);
// Bottom Border
drawTexturedModalRect(x + leftBorder + (i * fillerWidth), y + topBorder + canvasHeight, u + leftBorder, v + topBorder + fillerHeight, (i == xPasses ? remainderWidth : fillerWidth), bottomBorder, zLevel);
// Throw in some filler for good measure
for (int j = 0; j < yPasses + (remainderHeight > 0 ? 1 : 0); j++)
drawTexturedModalRect(x + leftBorder + (i * fillerWidth), y + topBorder + (j * fillerHeight), u + leftBorder, v + topBorder, (i == xPasses ? remainderWidth : fillerWidth), (j == yPasses ? remainderHeight : fillerHeight), zLevel);
}
// Side Borders
for (int j = 0; j < yPasses + (remainderHeight > 0 ? 1 : 0); j++)
{
// Left Border
drawTexturedModalRect(x, y + topBorder + (j * fillerHeight), u, v + topBorder, leftBorder, (j == yPasses ? remainderHeight : fillerHeight), zLevel);
// Right Border
drawTexturedModalRect(x + leftBorder + canvasWidth, y + topBorder + (j * fillerHeight), u + leftBorder + fillerWidth, v + topBorder, rightBorder, (j == yPasses ? remainderHeight : fillerHeight), zLevel);
}
}
public static void drawTexturedModalRect(int x, int y, int u, int v, int width, int height, float zLevel)
{
final float uScale = 1f / 0x100;
final float vScale = 1f / 0x100;
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder wr = tessellator.getBuffer();
wr.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
wr.pos(x , y + height, zLevel).tex( u * uScale, ((v + height) * vScale)).endVertex();
wr.pos(x + width, y + height, zLevel).tex((u + width) * uScale, ((v + height) * vScale)).endVertex();
wr.pos(x + width, y , zLevel).tex((u + width) * uScale, ( v * vScale)).endVertex();
wr.pos(x , y , zLevel).tex( u * uScale, ( v * vScale)).endVertex();
tessellator.draw();
}
@Nonnull
private static ItemStack cachedTooltipStack = ItemStack.EMPTY;
/**
* Must be called from {@code GuiScreen.renderToolTip} before {@code GuiScreen.drawHoveringText} is called.
*
* @param stack The stack for which a tooltip is about to be drawn.
*/
public static void preItemToolTip(@Nonnull ItemStack stack)
{
cachedTooltipStack = stack;
}
/**
* Must be called from {@code GuiScreen.renderToolTip} after {@code GuiScreen.drawHoveringText} is called.
*/
public static void postItemToolTip()
{
cachedTooltipStack = ItemStack.EMPTY;
}
/**
* Draws a tooltip box on the screen with text in it.
* Automatically positions the box relative to the mouse to match Mojang's implementation.
* Automatically wraps text when there is not enough space on the screen to display the text without wrapping.
* Can have a maximum width set to avoid creating very wide tooltips.
*
* @param textLines the lines of text to be drawn in a hovering tooltip box.
* @param mouseX the mouse X position
* @param mouseY the mouse Y position
* @param screenWidth the available screen width for the tooltip to drawn in
* @param screenHeight the available screen height for the tooltip to drawn in
* @param maxTextWidth the maximum width of the text in the tooltip box.
* Set to a negative number to have no max width.
* @param font the font for drawing the text in the tooltip box
*/
public static void drawHoveringText(List<String> textLines, int mouseX, int mouseY, int screenWidth, int screenHeight, int maxTextWidth, FontRenderer font)
{
drawHoveringText(cachedTooltipStack, textLines, mouseX, mouseY, screenWidth, screenHeight, maxTextWidth, font);
}
/**
* Use this version if calling from somewhere where ItemStack context is available.
*
* @see #drawHoveringText(List, int, int, int, int, int, FontRenderer)
*/
public static void drawHoveringText(@Nonnull final ItemStack stack, List<String> textLines, int mouseX, int mouseY, int screenWidth, int screenHeight, int maxTextWidth, FontRenderer font)
{
if (!textLines.isEmpty())
{
RenderTooltipEvent.Pre event = new RenderTooltipEvent.Pre(stack, textLines, mouseX, mouseY, screenWidth, screenHeight, maxTextWidth, font);
if (MinecraftForge.EVENT_BUS.post(event)) {
return;
}
mouseX = event.getX();
mouseY = event.getY();
screenWidth = event.getScreenWidth();
screenHeight = event.getScreenHeight();
maxTextWidth = event.getMaxWidth();
font = event.getFontRenderer();
GlStateManager.disableRescaleNormal();
RenderHelper.disableStandardItemLighting();
GlStateManager.disableLighting();
GlStateManager.disableDepth();
int tooltipTextWidth = 0;
for (String textLine : textLines)
{
int textLineWidth = font.getStringWidth(textLine);
if (textLineWidth > tooltipTextWidth)
{
tooltipTextWidth = textLineWidth;
}
}
boolean needsWrap = false;
int titleLinesCount = 1;
int tooltipX = mouseX + 12;
if (tooltipX + tooltipTextWidth + 4 > screenWidth)
{
tooltipX = mouseX - 16 - tooltipTextWidth;
if (tooltipX < 4) // if the tooltip doesn't fit on the screen
{
if (mouseX > screenWidth / 2)
{
tooltipTextWidth = mouseX - 12 - 8;
}
else
{
tooltipTextWidth = screenWidth - 16 - mouseX;
}
needsWrap = true;
}
}
if (maxTextWidth > 0 && tooltipTextWidth > maxTextWidth)
{
tooltipTextWidth = maxTextWidth;
needsWrap = true;
}
if (needsWrap)
{
int wrappedTooltipWidth = 0;
List<String> wrappedTextLines = new ArrayList<String>();
for (int i = 0; i < textLines.size(); i++)
{
String textLine = textLines.get(i);
List<String> wrappedLine = font.listFormattedStringToWidth(textLine, tooltipTextWidth);
if (i == 0)
{
titleLinesCount = wrappedLine.size();
}
for (String line : wrappedLine)
{
int lineWidth = font.getStringWidth(line);
if (lineWidth > wrappedTooltipWidth)
{
wrappedTooltipWidth = lineWidth;
}
wrappedTextLines.add(line);
}
}
tooltipTextWidth = wrappedTooltipWidth;
textLines = wrappedTextLines;
if (mouseX > screenWidth / 2)
{
tooltipX = mouseX - 16 - tooltipTextWidth;
}
else
{
tooltipX = mouseX + 12;
}
}
int tooltipY = mouseY - 12;
int tooltipHeight = 8;
if (textLines.size() > 1)
{
tooltipHeight += (textLines.size() - 1) * 10;
if (textLines.size() > titleLinesCount) {
tooltipHeight += 2; // gap between title lines and next lines
}
}
if (tooltipY < 4)
{
tooltipY = 4;
}
else if (tooltipY + tooltipHeight + 4 > screenHeight)
{
tooltipY = screenHeight - tooltipHeight - 4;
}
final int zLevel = 300;
int backgroundColor = 0xF0100010;
int borderColorStart = 0x505000FF;
int borderColorEnd = (borderColorStart & 0xFEFEFE) >> 1 | borderColorStart & 0xFF000000;
RenderTooltipEvent.Color colorEvent = new RenderTooltipEvent.Color(stack, textLines, tooltipX, tooltipY, font, backgroundColor, borderColorStart, borderColorEnd);
MinecraftForge.EVENT_BUS.post(colorEvent);
backgroundColor = colorEvent.getBackground();
borderColorStart = colorEvent.getBorderStart();
borderColorEnd = colorEvent.getBorderEnd();
drawGradientRect(zLevel, tooltipX - 3, tooltipY - 4, tooltipX + tooltipTextWidth + 3, tooltipY - 3, backgroundColor, backgroundColor);
drawGradientRect(zLevel, tooltipX - 3, tooltipY + tooltipHeight + 3, tooltipX + tooltipTextWidth + 3, tooltipY + tooltipHeight + 4, backgroundColor, backgroundColor);
drawGradientRect(zLevel, tooltipX - 3, tooltipY - 3, tooltipX + tooltipTextWidth + 3, tooltipY + tooltipHeight + 3, backgroundColor, backgroundColor);
drawGradientRect(zLevel, tooltipX - 4, tooltipY - 3, tooltipX - 3, tooltipY + tooltipHeight + 3, backgroundColor, backgroundColor);
drawGradientRect(zLevel, tooltipX + tooltipTextWidth + 3, tooltipY - 3, tooltipX + tooltipTextWidth + 4, tooltipY + tooltipHeight + 3, backgroundColor, backgroundColor);
drawGradientRect(zLevel, tooltipX - 3, tooltipY - 3 + 1, tooltipX - 3 + 1, tooltipY + tooltipHeight + 3 - 1, borderColorStart, borderColorEnd);
drawGradientRect(zLevel, tooltipX + tooltipTextWidth + 2, tooltipY - 3 + 1, tooltipX + tooltipTextWidth + 3, tooltipY + tooltipHeight + 3 - 1, borderColorStart, borderColorEnd);
drawGradientRect(zLevel, tooltipX - 3, tooltipY - 3, tooltipX + tooltipTextWidth + 3, tooltipY - 3 + 1, borderColorStart, borderColorStart);
drawGradientRect(zLevel, tooltipX - 3, tooltipY + tooltipHeight + 2, tooltipX + tooltipTextWidth + 3, tooltipY + tooltipHeight + 3, borderColorEnd, borderColorEnd);
MinecraftForge.EVENT_BUS.post(new RenderTooltipEvent.PostBackground(stack, textLines, tooltipX, tooltipY, font, tooltipTextWidth, tooltipHeight));
int tooltipTop = tooltipY;
for (int lineNumber = 0; lineNumber < textLines.size(); ++lineNumber)
{
String line = textLines.get(lineNumber);
font.drawStringWithShadow(line, (float)tooltipX, (float)tooltipY, -1);
if (lineNumber + 1 == titleLinesCount)
{
tooltipY += 2;
}
tooltipY += 10;
}
MinecraftForge.EVENT_BUS.post(new RenderTooltipEvent.PostText(stack, textLines, tooltipX, tooltipTop, font, tooltipTextWidth, tooltipHeight));
GlStateManager.enableLighting();
GlStateManager.enableDepth();
RenderHelper.enableStandardItemLighting();
GlStateManager.enableRescaleNormal();
}
}
public static void drawGradientRect(int zLevel, int left, int top, int right, int bottom, int startColor, int endColor)
{
float startAlpha = (float)(startColor >> 24 & 255) / 255.0F;
float startRed = (float)(startColor >> 16 & 255) / 255.0F;
float startGreen = (float)(startColor >> 8 & 255) / 255.0F;
float startBlue = (float)(startColor & 255) / 255.0F;
float endAlpha = (float)(endColor >> 24 & 255) / 255.0F;
float endRed = (float)(endColor >> 16 & 255) / 255.0F;
float endGreen = (float)(endColor >> 8 & 255) / 255.0F;
float endBlue = (float)(endColor & 255) / 255.0F;
GlStateManager.disableTexture2D();
GlStateManager.enableBlend();
GlStateManager.disableAlpha();
GlStateManager.tryBlendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO);
GlStateManager.shadeModel(GL11.GL_SMOOTH);
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR);
buffer.pos(right, top, zLevel).color(startRed, startGreen, startBlue, startAlpha).endVertex();
buffer.pos( left, top, zLevel).color(startRed, startGreen, startBlue, startAlpha).endVertex();
buffer.pos( left, bottom, zLevel).color( endRed, endGreen, endBlue, endAlpha).endVertex();
buffer.pos(right, bottom, zLevel).color( endRed, endGreen, endBlue, endAlpha).endVertex();
tessellator.draw();
GlStateManager.shadeModel(GL11.GL_FLAT);
GlStateManager.disableBlend();
GlStateManager.enableAlpha();
GlStateManager.enableTexture2D();
}
}

View File

@@ -0,0 +1,103 @@
/*
* 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.client.config;
import net.minecraft.client.gui.GuiButton;
/**
* This class implements an easy way to check if the mouse has hovered within a certain region of the screen for a given
* period of time. The region can be defined manually or by supplying a GuiButton object.
*
* @author bspkrs
*/
public class HoverChecker
{
private int top, bottom, left, right, threshold;
private GuiButton button;
private long hoverStart;
public HoverChecker(int top, int bottom, int left, int right, int threshold)
{
this.top = top;
this.bottom = bottom;
this.left = left;
this.right = right;
this.threshold = threshold;
this.hoverStart = -1;
}
public HoverChecker(GuiButton button, int threshold)
{
this.button = button;
this.threshold = threshold;
}
/**
* Call this method if the intended region has changed such as if the region must follow a scrolling list.
* It is not necessary to call this method if a GuiButton defines the hover region.
*/
public void updateBounds(int top, int bottom, int left, int right)
{
this.top = top;
this.bottom = bottom;
this.left = left;
this.right = right;
}
/**
* Checks if the mouse is in the hover region. If the specified time period has elapsed the method returns true.
* The hover timer is reset if the mouse is not within the region.
*/
public boolean checkHover(int mouseX, int mouseY)
{
return checkHover(mouseX, mouseY, true);
}
/**
* Checks if the mouse is in the hover region. If the specified time period has elapsed the method returns true.
* The hover timer is reset if the mouse is not within the region.
*/
public boolean checkHover(int mouseX, int mouseY, boolean canHover)
{
if (this.button != null)
{
this.top = button.y;
this.bottom = button.y + button.height;
this.left = button.x;
this.right = button.x + button.width;
canHover = canHover && button.visible;
}
if (canHover && hoverStart == -1 && mouseY >= top && mouseY <= bottom && mouseX >= left && mouseX <= right)
hoverStart = System.currentTimeMillis();
else if (!canHover || mouseY < top || mouseY > bottom || mouseX < left || mouseX > right)
resetHoverTimer();
return canHover && hoverStart != -1 && System.currentTimeMillis() - hoverStart >= threshold;
}
/**
* Manually resets the hover timer.
*/
public void resetHoverTimer()
{
hoverStart = -1;
}
}

View File

@@ -0,0 +1,189 @@
/*
* 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.client.config;
import java.util.List;
import java.util.regex.Pattern;
import net.minecraftforge.fml.client.config.GuiConfigEntries.IConfigEntry;
import net.minecraftforge.fml.client.config.GuiEditArrayEntries.IArrayEntry;
/**
* This interface provides the information needed by GuiConfig and GuiConfigEntries to display config elements for editing.
*
* @author bspkrs
*/
public interface IConfigElement
{
/**
* [Property, Category] Is this object a property object?
*/
boolean isProperty();
/**
* This method returns a class that implements {@link IConfigEntry} or null. This class MUST
* provide a constructor with the following parameter types: {@link GuiConfig}, {@link GuiConfigEntries}, {@link IConfigElement}
*
* @see GuiConfigEntries.ListEntryBase
* @see GuiConfigEntries.StringEntry
* @see GuiConfigEntries.BooleanEntry
* @see GuiConfigEntries.DoubleEntry
* @see GuiConfigEntries.IntegerEntry
*/
Class<? extends IConfigEntry> getConfigEntryClass();
/**
* This method returns a class that implements {@link IArrayEntry}. This class MUST provide a constructor with the
* following parameter types: {@link GuiEditArray}, {@link GuiEditArrayEntries}, {@link IConfigElement}, {@link Object}
*
* @see GuiEditArrayEntries.BaseEntry
* @see GuiEditArrayEntries.StringEntry
* @see GuiEditArrayEntries.BooleanEntry
* @see GuiEditArrayEntries.DoubleEntry
* @see GuiEditArrayEntries.IntegerEntry
*/
Class<? extends IArrayEntry> getArrayEntryClass();
/**
* [Property, Category] Gets the name of this object.
*/
String getName();
/**
* [Category] Gets the qualified name of this object. This is typically only used for category objects.
*/
String getQualifiedName();
/**
* [Property, Category] Gets a language key for localization of config GUI entry names. If the same key is specified with .tooltip
* appended to the end, that key will return a localized tooltip when the mouse hovers over the property label/category button.
*/
String getLanguageKey();
/**
* [Property, Category] Gets the comment for this object. Used for the tooltip if getLanguageKey() + ".tooltip" is not defined in the
* .lang file.
*/
String getComment();
/**
* [Category] Gets this category's child categories/properties.
*/
List<IConfigElement> getChildElements();
/**
* [Property, Category] Gets the ConfigGuiType value corresponding to the type of this property object, or CONFIG_CATEGORY if this is a
* category object.
*/
ConfigGuiType getType();
/**
* [Property] Is this property object a list?
*/
boolean isList();
/**
* [Property] Does this list property have to remain a fixed length?
*/
boolean isListLengthFixed();
/**
* [Property] Gets the max length of this list property, or -1 if the length is unlimited.
*/
int getMaxListLength();
/**
* [Property] Is this property value equal to the default value?
*/
boolean isDefault();
/**
* [Property] Gets this property's default value. If this element is an array, this method should return a String
* representation of that array using Arrays.toString()
*/
Object getDefault();
/**
* [Property] Gets this property's default values.
*/
Object[] getDefaults();
/**
* [Property] Sets this property's value to the default value.
*/
void setToDefault();
/**
* [Property, Category] Whether or not this element is safe to modify while a world is running. For Categories return false if ANY properties
* in the category are modifiable while a world is running, true if all are not.
*/
boolean requiresWorldRestart();
/**
* [Property, Category] Whether or not this element should be allowed to show on config GUIs.
*/
boolean showInGui();
/**
* [Property, Category] Whether or not this element requires Minecraft to be restarted when changed.
*/
boolean requiresMcRestart();
/**
* [Property] Gets this property value.
*/
Object get();
/**
* [Property] Gets this property value as a list. Generally you should be sure of whether the property is a list before calling this.
*/
Object[] getList();
/**
* [Property] Sets this property's value.
*/
void set(Object value);
/**
* [Property] Sets this property's value to the specified array.
*/
void set(Object[] aVal);
/**
* [Property] Gets a String array of valid values for this property. This is generally used for String properties to allow the user to
* select a value from a list of valid values.
*/
String[] getValidValues();
/**
* [Property] Gets this property's minimum value.
*/
Object getMinValue();
/**
* [Property] Gets this property's maximum value.
*/
Object getMaxValue();
/**
* [Property] Gets a Pattern object used in String property input validation.
*/
Pattern getValidationPattern();
}

View File

@@ -0,0 +1,116 @@
/*
* 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.client.event;
import net.minecraftforge.fml.common.eventhandler.Event;
import net.minecraftforge.fml.common.eventhandler.Event.HasResult;
import javax.annotation.Nullable;
/**
* These events are posted from the GuiConfig screen when the done button is pressed. The events are only posted
* if the parent screen is not an instance of GuiConfig or if the configID field has been set for
* the GuiConfig screen.
*
* Listeners for this event should use OnConfigChanged or PostConfigChanged and check for a specific mod ID.
* For best results the listener should refresh any objects/fields that are set based on the mod's config
* and should serialize the modified config.
*
* @author bspkrs
*/
@HasResult
public class ConfigChangedEvent extends Event
{
private final String modID;
private final boolean isWorldRunning;
private final boolean requiresMcRestart;
@Nullable
private final String configID;
public ConfigChangedEvent(String modID, @Nullable String configID, boolean isWorldRunning, boolean requiresMcRestart)
{
this.modID = modID;
this.configID = configID;
this.isWorldRunning = isWorldRunning;
this.requiresMcRestart = requiresMcRestart;
}
/**
* The Mod ID of the mod whose configuration just changed.
*/
public String getModID()
{
return modID;
}
/**
* Whether or not a world is currently running.
*/
public boolean isWorldRunning()
{
return isWorldRunning;
}
/**
* Will be set to true if any elements were changed that require a restart of Minecraft.
*/
public boolean isRequiresMcRestart()
{
return requiresMcRestart;
}
/**
* A String identifier for this ConfigChangedEvent.
*/
@Nullable
public String getConfigID()
{
return configID;
}
/**
* This event is intended to be consumed by the mod whose config has been changed. It fires when the Done button
* has been clicked on a GuiConfig screen and the following conditions are met:<br/>
* - at least one config element has been changed<br/>
* - one of these 2 conditions are met:<br/>
* 1) the parent screen is null or is not an instance of GuiConfig<br/>
* 2) the configID field has been set to a non-null value for the GuiConfig screen<br/><br/>
* Modders should check the modID field of the event to ensure they are only acting on their own config screen's event!
*/
public static class OnConfigChangedEvent extends ConfigChangedEvent
{
public OnConfigChangedEvent(String modID, @Nullable String configID, boolean isWorldRunning, boolean requiresMcRestart)
{
super(modID, configID, isWorldRunning, requiresMcRestart);
}
}
/**
* This event is provided for mods to consume if they want to be able to check if other mods' configs have been changed.
* This event only fires if the OnConfigChangedEvent result is not DENY.
*/
public static class PostConfigChangedEvent extends ConfigChangedEvent
{
public PostConfigChangedEvent(String modID, @Nullable String configID, boolean isWorldRunning, boolean requiresMcRestart)
{
super(modID, configID, isWorldRunning, requiresMcRestart);
}
}
}

View File

@@ -0,0 +1,81 @@
/*
* 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.client.registry;
import com.google.common.collect.Maps;
import net.minecraft.entity.Entity;
import net.minecraft.util.ResourceLocation;
import org.apache.commons.lang3.ArrayUtils;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer;
import net.minecraft.client.settings.KeyBinding;
import net.minecraft.tileentity.TileEntity;
import net.minecraftforge.fml.common.registry.GameRegistry;
import java.util.Map;
public class ClientRegistry
{
private static Map<Class<? extends Entity>, ResourceLocation> entityShaderMap = Maps.newHashMap();
/**
*
* Utility method for registering a tile entity and it's renderer at once - generally you should register them separately
*
* @param tileEntityClass
* @param id
* @param specialRenderer
*/
public static <T extends TileEntity> void registerTileEntity(Class<T> tileEntityClass, String id, TileEntitySpecialRenderer<? super T> specialRenderer)
{
GameRegistry.registerTileEntity(tileEntityClass, id);
bindTileEntitySpecialRenderer(tileEntityClass, specialRenderer);
}
public static <T extends TileEntity> void bindTileEntitySpecialRenderer(Class<T> tileEntityClass, TileEntitySpecialRenderer<? super T> specialRenderer)
{
TileEntityRendererDispatcher.instance.renderers.put(tileEntityClass, specialRenderer);
specialRenderer.setRendererDispatcher(TileEntityRendererDispatcher.instance);
}
public static void registerKeyBinding(KeyBinding key)
{
Minecraft.getMinecraft().gameSettings.keyBindings = ArrayUtils.add(Minecraft.getMinecraft().gameSettings.keyBindings, key);
}
/**
* Register a shader for an entity. This shader gets activated when a spectator begins spectating an entity.
* Vanilla examples of this are the green effect for creepers and the invert effect for endermen.
*
* @param entityClass
* @param shader
*/
public static void registerEntityShader(Class<? extends Entity> entityClass, ResourceLocation shader)
{
entityShaderMap.put(entityClass, shader);
}
public static ResourceLocation getEntityShader(Class<? extends Entity> entityClass)
{
return entityShaderMap.get(entityClass);
}
}

View File

@@ -0,0 +1,29 @@
/*
* 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.client.registry;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.entity.Entity;
public interface IRenderFactory<T extends Entity>
{
Render<? super T> createRenderFor(RenderManager manager);
}

View File

@@ -0,0 +1,79 @@
/*
* 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.client.registry;
import java.util.Map;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.entity.Entity;
import com.google.common.collect.Maps;
public class RenderingRegistry
{
private static final RenderingRegistry INSTANCE = new RenderingRegistry();
private Map<Class<? extends Entity>, IRenderFactory<? extends Entity>> entityRenderers = Maps.newHashMap();
private Map<Class<? extends Entity>, Render<? extends Entity>> entityRenderersOld = Maps.newHashMap();
/**
* Register an entity rendering handler. This will, after mod initialization, be inserted into the main
* render map for entities.
* Call this during Initialization phase.
*
* @deprecated use the factory version during Preinitialization.
* TODO Will be removed in 1.11.
*/
@Deprecated
public static void registerEntityRenderingHandler(Class<? extends Entity> entityClass, Render<? extends Entity> renderer)
{
INSTANCE.entityRenderersOld.put(entityClass, renderer);
}
public static void loadEntityRenderers(Map<Class<? extends Entity>, Render<? extends Entity>> entityRenderMap)
{
entityRenderMap.putAll(INSTANCE.entityRenderersOld);
}
/**
* Register an entity rendering handler. This will, after mod initialization, be inserted into the main
* render map for entities.
* Call this during Preinitialization phase.
*/
public static <T extends Entity> void registerEntityRenderingHandler(Class<T> entityClass, IRenderFactory<? super T> renderFactory)
{
INSTANCE.entityRenderers.put(entityClass, renderFactory);
}
public static void loadEntityRenderers(RenderManager manager, Map<Class<? extends Entity> , Render<? extends Entity>> renderMap)
{
for (Map.Entry<Class<? extends Entity>, IRenderFactory<? extends Entity>> entry : INSTANCE.entityRenderers.entrySet())
{
register(manager, renderMap, entry.getKey(), entry.getValue());
}
}
@SuppressWarnings("unchecked")
private static <T extends Entity> void register(RenderManager manager, Map<Class<? extends Entity> , Render<? extends Entity>> renderMap, Class<T> entityClass, IRenderFactory<?> renderFactory)
{
renderMap.put(entityClass, ((IRenderFactory<T>)renderFactory).createRenderFor(manager));
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PACKAGE)
public @interface API {
String owner();
String provides();
String apiVersion();
}

View File

@@ -0,0 +1,94 @@
/*
* 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 com.google.common.base.Strings;
import com.google.common.collect.SetMultimap;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.discovery.ASMDataTable;
import net.minecraftforge.fml.common.discovery.ASMDataTable.ASMData;
import net.minecraftforge.fml.common.discovery.asm.ModAnnotation;
import net.minecraftforge.fml.relauncher.Side;
import org.apache.logging.log4j.Level;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
/**
* Automatic eventbus subscriber - reads {@link net.minecraftforge.fml.common.Mod.EventBusSubscriber}
* annotations and passes the class instances to the {@link net.minecraftforge.common.MinecraftForge.EVENT_BUS}
*/
public class AutomaticEventSubscriber
{
private static final EnumSet<Side> DEFAULT = EnumSet.allOf(Side.class);
public static void inject(ModContainer mod, ASMDataTable data, Side side)
{
FMLLog.log.debug("Attempting to inject @EventBusSubscriber classes into the eventbus for {}", mod.getModId());
SetMultimap<String, ASMData> modData = data.getAnnotationsFor(mod);
Set<ASMDataTable.ASMData> mods = modData.get(Mod.class.getName());
Set<ASMDataTable.ASMData> targets = modData.get(Mod.EventBusSubscriber.class.getName());
ClassLoader mcl = Loader.instance().getModClassLoader();
for (ASMDataTable.ASMData targ : targets)
{
try
{
//noinspection unchecked
@SuppressWarnings("unchecked")
List<ModAnnotation.EnumHolder> sidesEnum = (List<ModAnnotation.EnumHolder>)targ.getAnnotationInfo().get("value");
EnumSet<Side> sides = DEFAULT;
if (sidesEnum != null) {
sides = EnumSet.noneOf(Side.class);
for (ModAnnotation.EnumHolder h: sidesEnum) {
sides.add(Side.valueOf(h.getValue()));
}
}
if (sides == DEFAULT || sides.contains(side)) {
//FMLLog.log.debug("Found @EventBusSubscriber class {}", targ.getClassName());
String amodid = (String)targ.getAnnotationInfo().get("modid");
if (Strings.isNullOrEmpty(amodid)) {
amodid = ASMDataTable.getOwnerModID(mods, targ);
if (Strings.isNullOrEmpty(amodid)) {
FMLLog.bigWarning("Could not determine owning mod for @EventBusSubscriber on {} for mod {}", targ.getClassName(), mod.getModId());
continue;
}
}
if (!mod.getModId().equals(amodid))
{
FMLLog.log.debug("Skipping @EventBusSubscriber injection for {} since it is not for mod {}", targ.getClassName(), mod.getModId());
continue; //We're not injecting this guy
}
FMLLog.log.debug("Registering @EventBusSubscriber for {} for mod {}", targ.getClassName(), mod.getModId());
Class<?> subscriptionTarget = Class.forName(targ.getClassName(), false, mcl);
MinecraftForge.EVENT_BUS.register(subscriptionTarget);
FMLLog.log.debug("Injected @EventBusSubscriber class {}", targ.getClassName());
}
}
catch (Throwable e)
{
FMLLog.log.error("An error occurred trying to load an EventBusSubscriber {} for modid {}", targ.getClassName(), mod.getModId(), e);
throw new LoaderException(e);
}
}
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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 com.google.common.collect.ImmutableList;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.cert.Certificate;
public class CertificateHelper {
private static final String HEXES = "0123456789abcdef";
public static ImmutableList<String> getFingerprints(Certificate[] certificates)
{
int len = 0;
if (certificates != null)
{
len = certificates.length;
}
ImmutableList.Builder<String> certBuilder = ImmutableList.builder();
for (int i = 0; i < len; i++)
{
certBuilder.add(CertificateHelper.getFingerprint(certificates[i]));
}
return certBuilder.build();
}
public static String getFingerprint(Certificate certificate)
{
if (certificate == null)
{
return "NO VALID CERTIFICATE FOUND";
}
try
{
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] der = certificate.getEncoded();
md.update(der);
byte[] digest = md.digest();
return hexify(digest);
}
catch (Exception e)
{
return "CERTIFICATE FINGERPRINT EXCEPTION";
}
}
public static String getFingerprint(ByteBuffer buffer)
{
try
{
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update(buffer);
byte[] chksum = digest.digest();
return hexify(chksum);
}
catch (Exception e)
{
return "CERTIFICATE FINGERPRINT EXCEPTION";
}
}
private static String hexify(byte[] chksum)
{
final StringBuilder hex = new StringBuilder( 2 * chksum.length );
for ( final byte b : chksum ) {
hex.append(HEXES.charAt((b & 0xF0) >> 4))
.append(HEXES.charAt((b & 0x0F)));
}
return hex.toString();
}
}

View File

@@ -0,0 +1,29 @@
/*
* 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;
public class ClassNameUtils
{
public static String shortName(Class<?> clz)
{
String nm = clz.getName();
return nm.indexOf('.') > -1 ? nm.substring(nm.lastIndexOf('.')+1) : nm;
}
}

View File

@@ -0,0 +1,241 @@
/*
* 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.net.URL;
import java.security.cert.Certificate;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion;
import net.minecraftforge.fml.common.versioning.VersionRange;
import com.google.common.collect.ImmutableList;
import com.google.common.eventbus.EventBus;
import javax.annotation.Nullable;
import net.minecraftforge.fml.common.ModContainer.Disableable;
public class DummyModContainer implements ModContainer
{
private ModMetadata md;
private ArtifactVersion processedVersion;
private String label;
private int classVersion;
public DummyModContainer(ModMetadata md)
{
this.md = md;
}
public DummyModContainer(String label)
{
this.label = label;
}
public DummyModContainer()
{
}
@Override
public void bindMetadata(MetadataCollection mc)
{
}
@Override
public List<ArtifactVersion> getDependants()
{
return Collections.emptyList();
}
@Override
public List<ArtifactVersion> getDependencies()
{
return Collections.emptyList();
}
@Override
public Set<ArtifactVersion> getRequirements()
{
return Collections.emptySet();
}
@Override
public ModMetadata getMetadata()
{
return md;
}
@Override
public Object getMod()
{
return null;
}
@Override
public String getModId()
{
return md.modId;
}
@Override
public String getName()
{
return md.name;
}
@Override
public String getSortingRules()
{
return "";
}
@Override
public File getSource()
{
return null;
}
@Override
public String getVersion()
{
return md.version;
}
@Override
public boolean matches(Object mod)
{
return false;
}
@Override
public void setEnabledState(boolean enabled)
{
}
@Override
public boolean registerBus(EventBus bus, LoadController controller)
{
return false;
}
@Override
public ArtifactVersion getProcessedVersion()
{
if (processedVersion == null)
{
processedVersion = new DefaultArtifactVersion(getModId(), getVersion());
}
return processedVersion;
}
@Override
public boolean isImmutable()
{
return false;
}
@Override
public String getDisplayVersion()
{
return md.version;
}
@Override
public VersionRange acceptableMinecraftVersionRange()
{
return Loader.instance().getMinecraftModContainer().getStaticVersionRange();
}
@Override
@Nullable
public Certificate getSigningCertificate()
{
return null;
}
@Override
public String toString()
{
return md != null ? getModId() : "Dummy Container ("+label+") @" + System.identityHashCode(this);
}
@Override
public Map<String, String> getCustomModProperties()
{
return EMPTY_PROPERTIES;
}
@Override
public Class<?> getCustomResourcePackClass()
{
return null;
}
@Override
public Map<String, String> getSharedModDescriptor()
{
return null;
}
@Override
public Disableable canBeDisabled()
{
return Disableable.NEVER;
}
@Override
public String getGuiClassName()
{
return null;
}
@Override
public List<String> getOwnedPackages()
{
return ImmutableList.of();
}
@Override
public boolean shouldLoadInEnvironment()
{
return true;
}
@Override
public URL getUpdateUrl()
{
return null;
}
@Override
public void setClassVersion(int classVersion)
{
this.classVersion = classVersion;
}
@Override
public int getClassVersion()
{
return this.classVersion;
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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.util.Map.Entry;
import com.google.common.collect.SetMultimap;
import net.minecraft.client.gui.GuiScreen;
import net.minecraftforge.fml.client.GuiDupesFound;
import net.minecraftforge.fml.client.IDisplayableError;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import net.minecraftforge.fml.common.EnhancedRuntimeException.WrappedPrintStream;
public class DuplicateModsFoundException extends LoaderException implements IDisplayableError
{
private static final long serialVersionUID = 1L;
public SetMultimap<ModContainer,File> dupes;
public DuplicateModsFoundException(SetMultimap<ModContainer, File> dupes) {
this.dupes = dupes;
}
@Override
protected void printStackTrace(WrappedPrintStream stream)
{
stream.println("Duplicate Mods:");
for (Entry<ModContainer, File> e : dupes.entries())
{
stream.println(String.format("\t%s : %s", e.getKey().getModId(), e.getValue().getAbsolutePath()));
}
stream.println("");
}
@Override
@SideOnly(Side.CLIENT)
public GuiScreen createGui()
{
return new GuiDupesFound(this);
}
}

View File

@@ -0,0 +1,101 @@
/*
* 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.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* RuntimeException that gives subclasses the simple opportunity to write extra data when printing the stack trace.
* Mainly a helper class as printsStackTrace has multiple signatures.
*/
public abstract class EnhancedRuntimeException extends RuntimeException
{
private static final long serialVersionUID = 1L;
public EnhancedRuntimeException() { super(); }
public EnhancedRuntimeException(String message) { super(message); }
public EnhancedRuntimeException(String message, Throwable cause) { super(message, cause); }
public EnhancedRuntimeException(Throwable cause) { super(cause); }
@Override
public String getMessage()
{
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
if (stack.length > 2 && stack[2].getClassName().startsWith("org.apache.logging.log4j."))
{
// This is a bit of a hack to force ourselves to be able to give a extended description when log4j prints this out.
// Sadly this text is displayed AFTER the initial exception line, and before the stack trace. But as the intention
// is to print this to the end user this is what we need to do.
final StringWriter buf = new StringWriter();
String msg = super.getMessage();
if (msg != null)
buf.append(msg);
buf.append('\n');
this.printStackTrace(new WrappedPrintStream()
{
@Override
public void println(String line)
{
buf.append(line).append('\n');
}
});
return buf.toString();
}
return super.getMessage();
}
@Override
public void printStackTrace(final PrintWriter s)
{
printStackTrace(new WrappedPrintStream()
{
@Override
public void println(String line)
{
s.println(line);
}
});
super.printStackTrace(s);
}
@Override
public void printStackTrace(final PrintStream s)
{
printStackTrace(new WrappedPrintStream()
{
@Override
public void println(String line)
{
s.println(line);
}
});
super.printStackTrace(s);
}
protected abstract void printStackTrace(WrappedPrintStream stream);
public static abstract class WrappedPrintStream
{
public abstract void println(String line);
}
}

View File

@@ -0,0 +1,774 @@
/*
* 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.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
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.crash.CrashReport;
import net.minecraft.crash.CrashReportCategory;
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.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.EventBus;
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.NetworkRegistry;
import net.minecraftforge.fml.common.thread.SidedThreadGroup;
import net.minecraftforge.fml.relauncher.CoreModManager;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.server.FMLServerHandler;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.Logger;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Maps;
import javax.annotation.Nullable;
/**
* 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 List<ICrashCallable> crashCallables = Lists.newArrayList(Loader.instance().getCallableCrashInformation());
private Set<SaveHandler> handlerSet = Collections.newSetFromMap(new MapMaker().weakKeys().<SaveHandler,Boolean>makeMap());
private WeakReference<SaveHandler> handlerToCheck;
private EventBus eventBus = MinecraftForge.EVENT_BUS;
private volatile CountDownLatch exitLatch = null;
private FMLCommonHandler()
{
registerCrashCallable(new ICrashCallable()
{
@Override
public String call() throws Exception
{
StringBuilder builder = new StringBuilder();
Joiner joiner = Joiner.on("\n ");
for(String coreMod : CoreModManager.getTransformers().keySet())
{
builder.append("\n" + coreMod + "\n ").append(joiner.join(CoreModManager.getTransformers().get(coreMod)));
}
return builder.toString();
}
@Override
public String getLabel()
{
return "Loaded coremods (and transformers)";
}
});
}
/**
* 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 EventBus 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 void computeBranding()
{
if (brandings == null)
{
Builder<String> brd = ImmutableList.builder();
brd.add(Loader.instance().getMCVersionString());
brd.add(Loader.instance().getMCPVersionString());
brd.add("Powered by Forge " + ForgeVersion.getVersion());
if (sidedDelegate!=null)
{
brd.addAll(sidedDelegate.getAdditionalBrandingInformation());
}
if (Loader.instance().getFMLBrandingProperties().containsKey("fmlbranding"))
{
brd.add(Loader.instance().getFMLBrandingProperties().get("fmlbranding"));
}
int tModCount = Loader.instance().getModList().size();
int aModCount = Loader.instance().getActiveModList().size();
brd.add(String.format("%d mod%s loaded, %d mod%s active", tModCount, tModCount!=1 ? "s" :"", aModCount, aModCount!=1 ? "s" :"" ));
brandings = brd.build();
brandingsNoMC = brandings.subList(1, brandings.size());
}
}
public List<String> getBrandings(boolean includeMC)
{
if (brandings == null)
{
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 registerCrashCallable(ICrashCallable callable)
{
crashCallables.add(callable);
}
public void enhanceCrashReport(CrashReport crashReport, CrashReportCategory category)
{
for (ICrashCallable call: crashCallables)
{
category.addDetail(call.getLabel(), call);
}
}
public void handleWorldDataSave(SaveHandler handler, WorldInfo worldInfo, NBTTagCompound tagCompound)
{
for (ModContainer mc : Loader.instance().getModList())
{
if (mc instanceof InjectedModContainer)
{
WorldAccessContainer 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()!=Side.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)
{
WorldAccessContainer 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();
}
/**
* 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 Network 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.");
FMLLog.log.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(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);
}
}
/**
* Loads a lang file, first searching for a marker to enable the 'extended' format {escape characters}
* If the marker is not found it simply returns and let the vanilla code load things.
* The Marker is 'PARSE_ESCAPES' by itself on a line starting with '#' as such:
* #PARSE_ESCAPES
*
* @param table The Map to load each key/value pair into.
* @param inputstream Input stream containing the lang file.
* @return A new InputStream that vanilla uses to load normal Lang files, Null if this is a 'enhanced' file and loading is done.
*/
@Nullable
public InputStream loadLanguage(Map<String, String> table, InputStream inputstream) throws IOException
{
byte[] data = IOUtils.toByteArray(inputstream);
boolean isEnhanced = false;
for (String line : IOUtils.readLines(new ByteArrayInputStream(data), StandardCharsets.UTF_8))
{
if (!line.isEmpty() && line.charAt(0) == '#')
{
line = line.substring(1).trim();
if (line.equals("PARSE_ESCAPES"))
{
isEnhanced = true;
break;
}
}
}
if (!isEnhanced)
return new ByteArrayInputStream(data);
Properties props = new Properties();
props.load(new InputStreamReader(new ByteArrayInputStream(data), StandardCharsets.UTF_8));
for (Entry<Object, Object> e : props.entrySet())
{
table.put((String)e.getKey(), (String)e.getValue());
}
props.clear();
return null;
}
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();
}
}

View File

@@ -0,0 +1,225 @@
/*
* 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.security.cert.Certificate;
import java.util.Arrays;
import java.util.Map;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.storage.SaveHandler;
import net.minecraft.world.storage.WorldInfo;
import net.minecraftforge.fml.client.FMLFileResourcePack;
import net.minecraftforge.fml.client.FMLFolderResourcePack;
import net.minecraftforge.fml.common.asm.FMLSanityChecker;
import net.minecraftforge.fml.common.event.FMLConstructionEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.network.NetworkCheckHandler;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import net.minecraftforge.fml.common.network.internal.FMLNetworkHandler;
import net.minecraftforge.fml.common.registry.ForgeRegistries;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.registries.ForgeRegistry;
import net.minecraftforge.registries.GameData;
import net.minecraftforge.registries.RegistryManager;
import org.apache.logging.log4j.LogManager;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable;
/**
* @author cpw
*
*/
public final class FMLContainer extends DummyModContainer implements WorldAccessContainer
{
private static final Logger modTrackerLogger = LogManager.getLogger("FML.ModTracker");
public FMLContainer()
{
super(new ModMetadata());
ModMetadata meta = getMetadata();
meta.modId="FML";
meta.name="Forge Mod Loader";
meta.version=Loader.instance().getFMLVersionString();
meta.credits="Made possible with help from many people";
meta.authorList=Arrays.asList("cpw", "LexManos", "Player");
meta.description="The Forge Mod Loader provides the ability for systems to load mods " +
"from the file system. It also provides key capabilities for mods to be able " +
"to cooperate and provide a good modding environment. ";
meta.url="https://github.com/MinecraftForge/FML/wiki";
meta.screenshots=new String[0];
meta.logoFile="";
}
@Override
public boolean registerBus(EventBus bus, LoadController controller)
{
bus.register(this);
return true;
}
@Subscribe
public void modConstruction(FMLConstructionEvent evt)
{
NetworkRegistry.INSTANCE.register(this, this.getClass(), null, evt.getASMHarvestedData());
FMLNetworkHandler.registerChannel(this, evt.getSide());
}
@Subscribe
public void modPreinitialization(FMLPreInitializationEvent evt)
{
// Initialize all Forge/Vanilla registries {invoke the static init)
if (ForgeRegistries.ITEMS == null)
throw new RuntimeException("Something horrible went wrong in init, ForgeRegistres didn't create...");
}
@NetworkCheckHandler
public boolean checkModLists(Map<String,String> modList, Side side)
{
return Loader.instance().checkRemoteModList(modList,side);
}
@Override
public NBTTagCompound getDataForWriting(SaveHandler handler, WorldInfo info)
{
NBTTagCompound fmlData = new NBTTagCompound();
NBTTagList modList = new NBTTagList();
for (ModContainer mc : Loader.instance().getActiveModList())
{
NBTTagCompound mod = new NBTTagCompound();
mod.setString("ModId", mc.getModId());
mod.setString("ModVersion", mc.getVersion());
modList.appendTag(mod);
}
fmlData.setTag("ModList", modList);
NBTTagCompound registries = new NBTTagCompound();
fmlData.setTag("Registries", registries);
FMLLog.log.debug("Gathering id map for writing to world save {}", info.getWorldName());
for (Map.Entry<ResourceLocation, ForgeRegistry.Snapshot> e : RegistryManager.ACTIVE.takeSnapshot(true).entrySet())
{
registries.setTag(e.getKey().toString(), e.getValue().write());
}
return fmlData;
}
@Override
public void readData(SaveHandler handler, WorldInfo info, Map<String, NBTBase> propertyMap, NBTTagCompound tag)
{
if (tag.hasKey("ModList"))
{
NBTTagList modList = tag.getTagList("ModList", (byte)10);
for (int i = 0; i < modList.tagCount(); i++)
{
NBTTagCompound mod = modList.getCompoundTagAt(i);
String modId = mod.getString("ModId");
String modVersion = mod.getString("ModVersion");
ModContainer container = Loader.instance().getIndexedModList().get(modId);
if (container == null)
{
modTrackerLogger.error("This world was saved with mod {} which appears to be missing, things may not work well", modId);
continue;
}
if (!modVersion.equals(container.getVersion()))
{
modTrackerLogger.info("This world was saved with mod {} version {} and it is now at version {}, things may not work well", modId, modVersion, container.getVersion());
}
}
}
Multimap<ResourceLocation, ResourceLocation> failedElements = null;
if (tag.hasKey("ModItemData") || tag.hasKey("ItemData")) // Pre 1.7
{
StartupQuery.notify("This save predates 1.7.10, it can no longer be loaded here. Please load in 1.7.10 or 1.8 first");
StartupQuery.abort();
}
else if (tag.hasKey("Registries")) // 1.8, genericed out the 'registries' list
{
Map<ResourceLocation, ForgeRegistry.Snapshot> snapshot = Maps.newHashMap();
NBTTagCompound regs = tag.getCompoundTag("Registries");
for (String key : regs.getKeySet())
{
snapshot.put(new ResourceLocation(key), ForgeRegistry.Snapshot.read(regs.getCompoundTag(key)));
}
failedElements = GameData.injectSnapshot(snapshot, true, true);
}
if (failedElements != null && !failedElements.isEmpty())
{
StringBuilder buf = new StringBuilder();
buf.append("Forge Mod Loader could not load this save.\n\n")
.append("There are ").append(failedElements.size()).append(" unassigned registry entries in this save.\n")
.append("You will not be able to load until they are present again.\n\n");
failedElements.asMap().forEach((name, entries) ->
{
buf.append("Missing ").append(name).append(":\n");
entries.forEach(rl -> buf.append(" ").append(rl).append("\n"));
});
StartupQuery.notify(buf.toString());
StartupQuery.abort();
}
}
@Override
@Nullable
public Certificate getSigningCertificate()
{
Certificate[] certificates = getClass().getProtectionDomain().getCodeSource().getCertificates();
return certificates != null ? certificates[0] : null;
}
@Override
public File getSource()
{
return FMLSanityChecker.fmlLocation;
}
@Override
public Class<?> getCustomResourcePackClass()
{
return getSource().isDirectory() ? FMLFolderResourcePack.class : FMLFileResourcePack.class;
}
@Override
public String getGuiClassName()
{
return "net.minecraftforge.fml.client.FMLConfigGuiFactory";
}
@Override
public Object getMod()
{
return this;
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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;
public interface FMLContainerHolder
{
ModContainer getFMLContainer();
}

View File

@@ -0,0 +1,109 @@
/*
* 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 net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.relauncher.FMLRelaunchLog;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* FMLs logging class. <b>Internal use only, NOT FOR MOD LOGGING!</b> Mods use your own log, see {@link FMLPreInitializationEvent#getModLog()}.
* TODO 1.13 remove all the deprecated methods
*/
public class FMLLog
{
public static final Logger log = LogManager.getLogger("FML");
public static void bigWarning(String format, Object... data)
{
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
log.warn("****************************************");
log.warn("* "+format, data);
for (int i = 2; i < 8 && i < trace.length; i++)
{
log.warn("* at {}{}", trace[i].toString(), i == 7 ? "..." : "");
}
log.warn("****************************************");
}
@Deprecated
public static void log(String targetLog, Level level, String format, Object... data)
{
FMLRelaunchLog.log(targetLog, level, format, data);
}
@Deprecated
public static void log(Level level, String format, Object... data)
{
FMLRelaunchLog.log(level, format, data);
}
@Deprecated
public static void log(String targetLog, Level level, Throwable ex, String format, Object... data)
{
FMLRelaunchLog.log(targetLog, level, ex, format, data);
}
@Deprecated
public static void log(Level level, Throwable ex, String format, Object... data)
{
FMLRelaunchLog.log(level, ex, format, data);
}
@Deprecated
public static void severe(String format, Object... data)
{
log(Level.ERROR, format, data);
}
@Deprecated
public static void warning(String format, Object... data)
{
log(Level.WARN, format, data);
}
@Deprecated
public static void info(String format, Object... data)
{
log(Level.INFO, format, data);
}
@Deprecated
public static void fine(String format, Object... data)
{
log(Level.DEBUG, format, data);
}
@Deprecated
public static void finer(String format, Object... data)
{
log(Level.TRACE, format, data);
}
@Deprecated
public static Logger getLogger()
{
return log;
}
}

View File

@@ -0,0 +1,783 @@
/*
* 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.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.cert.Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import com.google.common.base.Throwables;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.config.Config;
import net.minecraftforge.common.config.ConfigManager;
import net.minecraftforge.fml.common.Mod.Instance;
import net.minecraftforge.fml.common.Mod.Metadata;
import net.minecraftforge.fml.common.asm.transformers.BlamingTransformer;
import net.minecraftforge.fml.common.discovery.ASMDataTable;
import net.minecraftforge.fml.common.discovery.ModCandidate;
import net.minecraftforge.fml.common.discovery.ASMDataTable.ASMData;
import net.minecraftforge.fml.common.event.FMLConstructionEvent;
import net.minecraftforge.fml.common.event.FMLEvent;
import net.minecraftforge.fml.common.event.FMLFingerprintViolationEvent;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion;
import net.minecraftforge.fml.common.versioning.DependencyParser;
import net.minecraftforge.fml.common.versioning.VersionParser;
import net.minecraftforge.fml.common.versioning.VersionRange;
import net.minecraftforge.fml.relauncher.Side;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.Level;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.function.Function;
import com.google.common.base.Strings;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.SetMultimap;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import org.apache.logging.log4j.message.FormattedMessage;
import javax.annotation.Nullable;
import net.minecraftforge.fml.common.ModContainer.Disableable;
public class FMLModContainer implements ModContainer
{
private Object modInstance;
private File source;
private ModMetadata modMetadata;
private String className;
private Map<String, Object> descriptor;
private boolean enabled = true;
private String internalVersion;
private boolean overridesMetadata;
private EventBus eventBus;
private LoadController controller;
private DefaultArtifactVersion processedVersion;
private String annotationDependencies;
private VersionRange minecraftAccepted;
private boolean fingerprintNotPresent;
private Set<String> sourceFingerprints;
private Certificate certificate;
private String modLanguage;
private ILanguageAdapter languageAdapter;
private Disableable disableability;
private ListMultimap<Class<? extends FMLEvent>, Method> eventMethods;
private Map<String, String> customModProperties;
private ModCandidate candidate;
private URL updateJSONUrl;
private int classVersion;
public FMLModContainer(String className, ModCandidate container, Map<String, Object> modDescriptor)
{
this.className = className;
this.source = container.getModContainer();
this.candidate = container;
this.descriptor = modDescriptor;
this.eventMethods = ArrayListMultimap.create();
this.modLanguage = (String)modDescriptor.get("modLanguage");
String languageAdapterType = (String)modDescriptor.get("modLanguageAdapter");
if (Strings.isNullOrEmpty(languageAdapterType))
{
this.languageAdapter = "scala".equals(modLanguage) ? new ILanguageAdapter.ScalaAdapter() : new ILanguageAdapter.JavaAdapter();
}
else
{
// Delay loading of the adapter until the mod is on the classpath, in case the mod itself contains it.
this.languageAdapter = null;
FMLLog.log.trace("Using custom language adapter {} for {} (modid: {})", languageAdapterType, this.className, getModId());
}
sanityCheckModId();
}
private void sanityCheckModId()
{
String modid = (String)this.descriptor.get("modid");
if (Strings.isNullOrEmpty(modid))
{
throw new IllegalArgumentException("The modId is null or empty");
}
if (modid.length() > 64)
{
throw new IllegalArgumentException(String.format("The modId %s is longer than the maximum of 64 characters.", modid));
}
if (!modid.equals(modid.toLowerCase(Locale.ENGLISH)))
{
throw new IllegalArgumentException(String.format("The modId %s must be all lowercase.", modid));
}
}
private ILanguageAdapter getLanguageAdapter()
{
if (languageAdapter == null)
{
try
{
languageAdapter = (ILanguageAdapter)Class.forName((String)descriptor.get("modLanguageAdapter"), true, Loader.instance().getModClassLoader()).newInstance();
}
catch (Exception ex)
{
FMLLog.log.error("Error constructing custom mod language adapter referenced by {} (modid: {})", getModId(), ex);
throw new RuntimeException(ex);
}
}
return languageAdapter;
}
@Override
public String getModId()
{
return (String)descriptor.get("modid");
}
@Override
public String getName()
{
return modMetadata.name;
}
@Override
public String getVersion()
{
return internalVersion;
}
@Override
public File getSource()
{
return source;
}
@Override
public ModMetadata getMetadata()
{
return modMetadata;
}
@Override
public void bindMetadata(MetadataCollection mc)
{
modMetadata = mc.getMetadataForId(getModId(), descriptor);
if (descriptor.containsKey("useMetadata"))
{
overridesMetadata = !((Boolean)descriptor.get("useMetadata"));
}
if (overridesMetadata || !modMetadata.useDependencyInformation)
{
annotationDependencies = (String)descriptor.get("dependencies");
DependencyParser dependencyParser = new DependencyParser(getModId(), FMLCommonHandler.instance().getSide());
DependencyParser.DependencyInfo info = dependencyParser.parseDependencies(annotationDependencies);
info.dependants.addAll(Loader.instance().getInjectedBefore(getModId()));
info.dependencies.addAll(Loader.instance().getInjectedAfter(getModId()));
modMetadata.requiredMods = info.requirements;
modMetadata.dependencies = info.dependencies;
modMetadata.dependants = info.dependants;
FMLLog.log.trace("Parsed dependency info for {}: Requirements: {} After:{} Before:{}", getModId(), info.requirements, info.dependencies, info.dependants);
}
else
{
FMLLog.log.trace("Using mcmod dependency info for {}: {} {} {}", getModId(), modMetadata.requiredMods, modMetadata.dependencies, modMetadata.dependants);
}
if (Strings.isNullOrEmpty(modMetadata.name))
{
FMLLog.log.info("Mod {} is missing the required element 'name'. Substituting {}", getModId(), getModId());
modMetadata.name = getModId();
}
internalVersion = (String)descriptor.get("version");
if (Strings.isNullOrEmpty(internalVersion))
{
Properties versionProps = searchForVersionProperties();
if (versionProps != null)
{
internalVersion = versionProps.getProperty(getModId() + ".version");
FMLLog.log.debug("Found version {} for mod {} in version.properties, using", internalVersion, getModId());
}
}
if (Strings.isNullOrEmpty(internalVersion) && !Strings.isNullOrEmpty(modMetadata.version))
{
FMLLog.log.warn("Mod {} is missing the required element 'version' and a version.properties file could not be found. Falling back to metadata version {}", getModId(), modMetadata.version);
internalVersion = modMetadata.version;
}
if (Strings.isNullOrEmpty(internalVersion))
{
FMLLog.log.warn("Mod {} is missing the required element 'version' and no fallback can be found. Substituting '1.0'.", getModId());
modMetadata.version = internalVersion = "1.0";
}
String mcVersionString = (String)descriptor.get("acceptedMinecraftVersions");
if ("[1.12]".equals(mcVersionString))
mcVersionString = "[1.12,1.12.2]";
if ("[1.12.1]".equals(mcVersionString) || "[1.12,1.12.1]".equals(mcVersionString))
mcVersionString = "[1.12,1.12.2]";
if (!Strings.isNullOrEmpty(mcVersionString))
{
minecraftAccepted = VersionParser.parseRange(mcVersionString);
}
else
{
minecraftAccepted = Loader.instance().getMinecraftModContainer().getStaticVersionRange();
}
String jsonURL = (String)descriptor.get("updateJSON");
if (!Strings.isNullOrEmpty(jsonURL))
{
try
{
this.updateJSONUrl = new URL(jsonURL);
}
catch (MalformedURLException e)
{
FMLLog.log.debug("Specified json URL for mod '{}' is invalid: {}", getModId(), jsonURL);
}
}
}
@Nullable
public Properties searchForVersionProperties()
{
try
{
FMLLog.log.debug("Attempting to load the file version.properties from {} to locate a version number for mod {}", getSource().getName(), getModId());
Properties version = null;
if (getSource().isFile())
{
ZipFile source = new ZipFile(getSource());
ZipEntry versionFile = source.getEntry("version.properties");
if (versionFile != null)
{
version = new Properties();
InputStream sourceInputStream = source.getInputStream(versionFile);
try
{
version.load(sourceInputStream);
}
finally
{
IOUtils.closeQuietly(sourceInputStream);
}
}
source.close();
}
else if (getSource().isDirectory())
{
File propsFile = new File(getSource(), "version.properties");
if (propsFile.exists() && propsFile.isFile())
{
version = new Properties();
try (FileInputStream fis = new FileInputStream(propsFile))
{
version.load(fis);
}
}
}
return version;
}
catch (IOException e)
{
FMLLog.log.trace("Failed to find a usable version.properties file for mod {}", getModId());
return null;
}
}
@Override
public void setEnabledState(boolean enabled)
{
this.enabled = enabled;
}
@Override
public Set<ArtifactVersion> getRequirements()
{
return modMetadata.requiredMods;
}
@Override
public List<ArtifactVersion> getDependencies()
{
return modMetadata.dependencies;
}
@Override
public List<ArtifactVersion> getDependants()
{
return modMetadata.dependants;
}
@Override
public String getSortingRules()
{
return ((overridesMetadata || !modMetadata.useDependencyInformation) ? Strings.nullToEmpty(annotationDependencies) : modMetadata.printableSortingRules());
}
@Override
public boolean matches(Object mod)
{
return mod == modInstance;
}
@Override
public Object getMod()
{
return modInstance;
}
@Override
public boolean registerBus(EventBus bus, LoadController controller)
{
if (this.enabled)
{
FMLLog.log.debug("Enabling mod {}", getModId());
this.eventBus = bus;
this.controller = controller;
eventBus.register(this);
return true;
}
else
{
return false;
}
}
@Nullable
private Method gatherAnnotations(Class<?> clazz)
{
Method factoryMethod = null;
for (Method m : clazz.getDeclaredMethods())
{
for (Annotation a : m.getAnnotations())
{
if (a.annotationType().equals(Mod.EventHandler.class))
{
if (m.getParameterTypes().length == 1 && FMLEvent.class.isAssignableFrom(m.getParameterTypes()[0]))
{
m.setAccessible(true);
@SuppressWarnings("unchecked")
Class<? extends FMLEvent> parameterType = (Class<? extends FMLEvent>) m.getParameterTypes()[0];
eventMethods.put(parameterType, m);
}
else
{
FMLLog.log.error("The mod {} appears to have an invalid event annotation {}. This annotation can only apply to methods with recognized event arguments - it will not be called", getModId(), a.annotationType().getSimpleName());
}
}
else if (a.annotationType().equals(Mod.InstanceFactory.class))
{
if (Modifier.isStatic(m.getModifiers()) && m.getParameterTypes().length == 0 && factoryMethod == null)
{
m.setAccessible(true);
factoryMethod = m;
}
else if (!(Modifier.isStatic(m.getModifiers()) && m.getParameterTypes().length == 0))
{
FMLLog.log.error("The InstanceFactory annotation can only apply to a static method, taking zero arguments - it will be ignored on {}({}) for mod {}", m.getName(), Arrays.asList(m.getParameterTypes()), getModId());
}
else if (factoryMethod != null)
{
FMLLog.log.error("The InstanceFactory annotation can only be used once, the application to {}({}) will be ignored for mod {}", m.getName(), Arrays.asList(m.getParameterTypes()), getModId());
}
}
}
}
return factoryMethod;
}
private void processFieldAnnotations(ASMDataTable asmDataTable) throws IllegalAccessException
{
SetMultimap<String, ASMData> annotations = asmDataTable.getAnnotationsFor(this);
parseSimpleFieldAnnotation(annotations, Instance.class.getName(), ModContainer::getMod);
parseSimpleFieldAnnotation(annotations, Metadata.class.getName(), ModContainer::getMetadata);
}
private void parseSimpleFieldAnnotation(SetMultimap<String, ASMData> annotations, String annotationClassName, Function<ModContainer, Object> retriever) throws IllegalAccessException
{
Set<ASMDataTable.ASMData> mods = annotations.get(Mod.class.getName());
String[] annName = annotationClassName.split("\\.");
String annotationName = annName[annName.length - 1];
for (ASMData targets : annotations.get(annotationClassName))
{
String targetMod = (String)targets.getAnnotationInfo().get("value");
String owner = (String)targets.getAnnotationInfo().get("owner");
if (Strings.isNullOrEmpty(owner))
{
owner = ASMDataTable.getOwnerModID(mods, targets);
if (Strings.isNullOrEmpty(owner))
{
FMLLog.bigWarning("Could not determine owning mod for @{} on {} for mod {}", annotationClassName, targets.getClassName(), this.getModId());
continue;
}
}
if (!this.getModId().equals(owner))
{
FMLLog.log.debug("Skipping @{} injection for {}.{} since it is not for mod {}", annotationClassName, targets.getClassName(), targets.getObjectName(), this.getModId());
continue;
}
Field f = null;
Object injectedMod = null;
ModContainer mc = this;
boolean isStatic = false;
Class<?> clz = modInstance.getClass();
if (!Strings.isNullOrEmpty(targetMod))
{
if (Loader.isModLoaded(targetMod))
{
mc = Loader.instance().getIndexedModList().get(targetMod);
}
else
{
mc = null;
}
}
if (mc != null)
{
try
{
clz = Class.forName(targets.getClassName(), true, Loader.instance().getModClassLoader());
f = clz.getDeclaredField(targets.getObjectName());
f.setAccessible(true);
isStatic = Modifier.isStatic(f.getModifiers());
injectedMod = retriever.apply(mc);
}
catch (ReflectiveOperationException e)
{
FMLLog.log.warn("Attempting to load @{} in class {} for {} and failing", annotationName, targets.getClassName(), mc.getModId(), e);
}
}
if (f != null)
{
Object target = null;
if (!isStatic)
{
target = modInstance;
if (!modInstance.getClass().equals(clz))
{
FMLLog.log.warn("Unable to inject @{} in non-static field {}.{} for {} as it is NOT the primary mod instance", annotationName, targets.getClassName(), targets.getObjectName(), mc.getModId());
continue;
}
}
f.set(target, injectedMod);
}
}
}
@Subscribe
public void constructMod(FMLConstructionEvent event)
{
BlamingTransformer.addClasses(getModId(), candidate.getClassList());
ModClassLoader modClassLoader = event.getModClassLoader();
try
{
modClassLoader.addFile(source);
}
catch (MalformedURLException e)
{
FormattedMessage message = new FormattedMessage("{} Failed to add file to classloader: {}", getModId(), source);
throw new LoaderException(message.getFormattedMessage(), e);
}
modClassLoader.clearNegativeCacheFor(candidate.getClassList());
//Only place I could think to add this...
MinecraftForge.preloadCrashClasses(event.getASMHarvestedData(), getModId(), candidate.getClassList());
Class<?> clazz;
try
{
clazz = Class.forName(className, true, modClassLoader);
}
catch (ClassNotFoundException e)
{
FormattedMessage message = new FormattedMessage("{} Failed load class: {}", getModId(), className);
throw new LoaderException(message.getFormattedMessage(), e);
}
Certificate[] certificates = clazz.getProtectionDomain().getCodeSource().getCertificates();
ImmutableList<String> certList = CertificateHelper.getFingerprints(certificates);
sourceFingerprints = ImmutableSet.copyOf(certList);
String expectedFingerprint = (String)descriptor.get("certificateFingerprint");
fingerprintNotPresent = true;
if (expectedFingerprint != null && !expectedFingerprint.isEmpty())
{
if (!sourceFingerprints.contains(expectedFingerprint))
{
Level warnLevel = source.isDirectory() ? Level.TRACE : Level.ERROR;
FMLLog.log.log(warnLevel, "The mod {} is expecting signature {} for source {}, however there is no signature matching that description", getModId(), expectedFingerprint, source.getName());
}
else
{
certificate = certificates[certList.indexOf(expectedFingerprint)];
fingerprintNotPresent = false;
}
}
@SuppressWarnings("unchecked")
List<Map<String, String>> props = (List<Map<String, String>>)descriptor.get("customProperties");
if (props != null)
{
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
for (Map<String, String> p : props)
{
builder.put(p.get("k"), p.get("v"));
}
customModProperties = builder.build();
}
else
{
customModProperties = EMPTY_PROPERTIES;
}
Boolean hasDisableableFlag = (Boolean)descriptor.get("canBeDeactivated");
boolean hasReverseDepends = !event.getReverseDependencies().get(getModId()).isEmpty();
if (hasDisableableFlag != null && hasDisableableFlag)
{
disableability = hasReverseDepends ? Disableable.DEPENDENCIES : Disableable.YES;
}
else
{
disableability = hasReverseDepends ? Disableable.DEPENDENCIES : Disableable.RESTART;
}
Method factoryMethod = gatherAnnotations(clazz);
ILanguageAdapter languageAdapter = getLanguageAdapter();
try
{
modInstance = languageAdapter.getNewInstance(this, clazz, modClassLoader, factoryMethod);
}
catch (Exception e)
{
FormattedMessage message = new FormattedMessage("{} Failed to load new mod instance.", getModId());
throw new LoaderException(message.getFormattedMessage(), e);
}
NetworkRegistry.INSTANCE.register(this, clazz, (String)(descriptor.getOrDefault("acceptableRemoteVersions", null)), event.getASMHarvestedData());
if (fingerprintNotPresent)
{
eventBus.post(new FMLFingerprintViolationEvent(source.isDirectory(), source, ImmutableSet.copyOf(this.sourceFingerprints), expectedFingerprint));
}
ProxyInjector.inject(this, event.getASMHarvestedData(), FMLCommonHandler.instance().getSide(), languageAdapter);
AutomaticEventSubscriber.inject(this, event.getASMHarvestedData(), FMLCommonHandler.instance().getSide());
ConfigManager.sync(this.getModId(), Config.Type.INSTANCE);
try
{
processFieldAnnotations(event.getASMHarvestedData());
}
catch (IllegalAccessException e)
{
FormattedMessage message = new FormattedMessage("{} Failed to process field annotations.", getModId());
throw new LoaderException(message.getFormattedMessage(), e);
}
}
@Subscribe
public void handleModStateEvent(FMLEvent event)
{
if (!eventMethods.containsKey(event.getClass()))
{
return;
}
try
{
for (Method m : eventMethods.get(event.getClass()))
{
m.invoke(modInstance, event);
}
}
catch (Throwable t)
{
controller.errorOccurred(this, t);
}
}
@Override
public ArtifactVersion getProcessedVersion()
{
if (processedVersion == null)
{
processedVersion = new DefaultArtifactVersion(getModId(), getVersion());
}
return processedVersion;
}
@Override
public boolean isImmutable()
{
return false;
}
@Override
public String getDisplayVersion()
{
return modMetadata.version;
}
@Override
public VersionRange acceptableMinecraftVersionRange()
{
return minecraftAccepted;
}
@Override
public Certificate getSigningCertificate()
{
return certificate;
}
@Override
public String toString()
{
return "FMLMod:" + getModId() + "{" + getVersion() + "}";
}
@Override
public Map<String, String> getCustomModProperties()
{
return customModProperties;
}
@Override
public Class<?> getCustomResourcePackClass()
{
try
{
return getSource().isDirectory() ? Class.forName("net.minecraftforge.fml.client.FMLFolderResourcePack", true, getClass().getClassLoader()) : Class.forName("net.minecraftforge.fml.client.FMLFileResourcePack", true, getClass().getClassLoader());
}
catch (ClassNotFoundException e)
{
return null;
}
}
@Override
public Map<String, String> getSharedModDescriptor()
{
Map<String, String> descriptor = Maps.newHashMap();
descriptor.put("modsystem", "FML");
descriptor.put("id", getModId());
descriptor.put("version", getDisplayVersion());
descriptor.put("name", getName());
descriptor.put("url", modMetadata.url);
descriptor.put("authors", modMetadata.getAuthorList());
descriptor.put("description", modMetadata.description);
return descriptor;
}
@Override
public Disableable canBeDisabled()
{
return disableability;
}
@Override
public String getGuiClassName()
{
return (String)descriptor.get("guiFactory");
}
@Override
public List<String> getOwnedPackages()
{
return candidate.getContainedPackages();
}
private boolean isTrue(Boolean value)
{
if (value == null)
{
return false;
}
return value;
}
@Override
public boolean shouldLoadInEnvironment()
{
boolean clientSideOnly = isTrue((Boolean)descriptor.get("clientSideOnly"));
boolean serverSideOnly = isTrue((Boolean)descriptor.get("serverSideOnly"));
if (clientSideOnly && serverSideOnly)
{
throw new RuntimeException("Mod annotation claims to be both client and server side only!");
}
Side side = FMLCommonHandler.instance().getSide();
if (clientSideOnly && side != Side.CLIENT)
{
FMLLog.log.info("Disabling mod {} it is client side only.", getModId());
return false;
}
if (serverSideOnly && side != Side.SERVER)
{
FMLLog.log.info("Disabling mod {} it is server side only.", getModId());
return false;
}
return true;
}
@Override
public URL getUpdateUrl()
{
return updateJSONUrl;
}
@Override
public void setClassVersion(int classVersion)
{
this.classVersion = classVersion;
}
@Override
public int getClassVersion()
{
return this.classVersion;
}
}

View File

@@ -0,0 +1,27 @@
/*
* 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 net.minecraft.crash.ICrashReportDetail;
public interface ICrashCallable extends ICrashReportDetail<String>
{
String getLabel();
}

View File

@@ -0,0 +1,50 @@
/*
* 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 com.google.common.base.Predicate;
import net.minecraft.command.EntitySelector;
import net.minecraft.command.ICommandSender;
import net.minecraft.entity.Entity;
import net.minecraft.util.math.Vec3d;
import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Allows mods to create custom selectors in commands.
* Registered in {@link net.minecraftforge.fml.common.registry.GameRegistry#registerEntitySelector(IEntitySelectorFactory, String...)}
* For an example implementation, see CustomEntitySelectorTest
*/
public interface IEntitySelectorFactory
{
/**
* Called every time a command that contains entity selectors is executed
*
* @param arguments A map with all arguments and their values
* @param mainSelector The main selector string (e.g. 'a' for all players or 'e' for all entities)
* @param sender The sender of the command
* @param position A position either specified in the selector arguments or by the players position. See {@link EntitySelector#getPosFromArguments(Map, Vec3d)}
* @return A list of new predicates, can be empty ({@link Collections#emptyList()} but not null.
*/
@Nonnull List<Predicate<Entity>> createPredicates(Map<String, String> arguments, String mainSelector, ICommandSender sender, Vec3d position);
}

View File

@@ -0,0 +1,25 @@
/*
* 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;
public interface IFMLHandledException
{
}

View File

@@ -0,0 +1,91 @@
/*
* 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.util.List;
import java.util.Set;
import net.minecraft.network.INetHandler;
import net.minecraft.network.NetworkManager;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.IThreadListener;
import net.minecraftforge.common.util.CompoundDataFixer;
import net.minecraftforge.fml.common.eventhandler.EventBus;
import net.minecraftforge.fml.relauncher.Side;
public interface IFMLSidedHandler
{
List<String> getAdditionalBrandingInformation();
Side getSide();
void haltGame(String message, Throwable exception);
void showGuiScreen(Object clientGuiElement);
void queryUser(StartupQuery query) throws InterruptedException;
void beginServerLoading(MinecraftServer server);
void finishServerLoading();
File getSavesDirectory();
MinecraftServer getServer();
boolean isDisplayCloseRequested();
boolean shouldServerShouldBeKilledQuietly();
void addModAsResource(ModContainer container);
String getCurrentLanguage();
void serverStopped();
NetworkManager getClientToServerNetworkManager();
INetHandler getClientPlayHandler();
void fireNetRegistrationEvent(EventBus bus, NetworkManager manager, Set<String> channelSet, String channel, Side side);
boolean shouldAllowPlayerLogins();
void allowLogins();
IThreadListener getWorldThread(INetHandler net);
void processWindowMessages();
String stripSpecialChars(String message);
void reloadRenderers();
void fireSidedRegistryEvents();
CompoundDataFixer getDataFixer();
boolean isDisplayVSyncForced();
default void resetClientRecipeBook(){}
default void reloadSearchTrees(){}
}

View File

@@ -0,0 +1,33 @@
/*
* 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 net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraftforge.event.furnace.FurnaceFuelBurnTimeEvent;
/**
* @deprecated set your item's {@link Item#getItemBurnTime(ItemStack)} or subscribe to {@link FurnaceFuelBurnTimeEvent} instead.
*/
@Deprecated
public interface IFuelHandler
{
int getBurnTime(ItemStack fuel);
}

View File

@@ -0,0 +1,216 @@
/*
* 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.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import net.minecraftforge.fml.relauncher.Side;
import org.apache.logging.log4j.Level;
public interface ILanguageAdapter {
Object getNewInstance(FMLModContainer container, Class<?> objectClass, ClassLoader classLoader, Method factoryMarkedAnnotation) throws Exception;
boolean supportsStatics();
void setProxy(Field target, Class<?> proxyTarget, Object proxy) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException;
void setInternalProxies(ModContainer mod, Side side, ClassLoader loader);
public static class ScalaAdapter implements ILanguageAdapter {
@Override
public Object getNewInstance(FMLModContainer container, Class<?> scalaObjectClass, ClassLoader classLoader, Method factoryMarkedAnnotation) throws Exception
{
Class<?> sObjectClass = Class.forName(scalaObjectClass.getName()+"$",true,classLoader);
return sObjectClass.getField("MODULE$").get(null);
}
@Override
public boolean supportsStatics()
{
return false;
}
@Override
public void setProxy(Field target, Class<?> proxyTarget, Object proxy) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException
{
try
{
// Get the actual singleton class. The two variants are from
// whether the @SidedProxy is declared in a the class block
// of the object directly, or in the object block, i.e.
// whether it's:
// class ModName {
// @SidedProxy ...
// }
// object ModName extends ModName {}
// which leads to us getting the outer class, or
// object ModName {
// @SidedProxy ...
// }
// which leads to us getting the inner class.
if (!proxyTarget.getName().endsWith("$"))
{
// Get internal class generated by Scala.
proxyTarget = Class.forName(proxyTarget.getName() + "$", true, proxyTarget.getClassLoader());
}
}
catch (ClassNotFoundException e)
{
// Not a singleton, look for @Instance field as a fallback.
FMLLog.log.info("An error occurred trying to load a proxy into {}.{}. Did you declare your mod as 'class' instead of 'object'?", proxyTarget.getSimpleName(), target.getName(), e);
return;
}
// Get the instance via the MODULE$ field which is
// automatically generated by the Scala compiler for
// singletons.
Object targetInstance = proxyTarget.getField("MODULE$").get(null);
try
{
// Find setter function. We do it this way because we can't
// necessarily use proxyTarget.getMethod(proxy.getClass()), as
// it might be a subclass and not the exact parameter type.
// All fields are private in Scala, wrapped by a getter and
// setter named <fieldname> and <fieldname>_$eq. To those
// familiar with scala.reflect.BeanProperty: these will always
// be there, set<Fieldname> and get<Fieldname> will always
// only be generated *additionally*.
final String setterName = target.getName() + "_$eq";
for (Method setter : proxyTarget.getMethods())
{
Class<?>[] setterParameters = setter.getParameterTypes();
if (setterName.equals(setter.getName()) &&
// Some more validation.
setterParameters.length == 1 &&
setterParameters[0].isAssignableFrom(proxy.getClass()))
{
// Here goes nothing...
setter.invoke(targetInstance, proxy);
return;
}
}
}
catch (InvocationTargetException e)
{
FMLLog.log.error("An error occurred trying to load a proxy into {}.{}", target.getName(), e);
throw new LoaderException(e);
}
// If we come here we could not find a setter for this proxy.
FMLLog.log.fatal("Failed loading proxy into {}.{}, could not find setter function. Did you declare the field with 'val' instead of 'var'?", proxyTarget.getSimpleName(), target.getName());
throw new LoaderException(String.format("Failed loading proxy into %s.%s, could not find setter function. Did you declare the field with 'val' instead of 'var'?", proxyTarget.getSimpleName(), target.getName()));
}
@Override
public void setInternalProxies(ModContainer mod, Side side, ClassLoader loader)
{
// For Scala mods, we want to enable authors to write them like so:
// object ModName {
// @SidedProxy(...)
// var proxy: ModProxy = null
// }
// For this to work, we have to search inside the inner class Scala
// generates for singletons, which is in called ModName$. These are
// not automatically handled, because the mod discovery code ignores
// internal classes.
// Note that it is alternatively possible to write this like so:
// class ModName {
// @SidedProxy(...)
// var proxy: ModProxy = null
// }
// object ModName extends ModName { ... }
// which will fall back to the normal injection code which calls
// setProxy in turn.
// Get the actual mod implementation, which will be the inner class
// if we have a singleton.
Class<?> proxyTarget = mod.getMod().getClass();
if (proxyTarget.getName().endsWith("$"))
{
// So we have a singleton class, check if there are targets.
for (Field target : proxyTarget.getDeclaredFields())
{
// This will not turn up anything if the alternative
// approach was taken (manually declaring the class).
// So we don't initialize the field twice.
if (target.getAnnotation(SidedProxy.class) != null)
{
String targetType = side.isClient() ? target.getAnnotation(SidedProxy.class).clientSide() : target.getAnnotation(SidedProxy.class).serverSide();
try
{
Object proxy = Class.forName(targetType, true, loader).newInstance();
if (!target.getType().isAssignableFrom(proxy.getClass()))
{
FMLLog.log.fatal("Attempted to load a proxy type {} into {}.{}, but the types don't match", targetType, proxyTarget.getSimpleName(), target.getName());
throw new LoaderException(String.format("Attempted to load a proxy type %s into %s.%s, but the types don't match", targetType, proxyTarget.getSimpleName(), target.getName()));
}
setProxy(target, proxyTarget, proxy);
}
catch (Exception e) {
FMLLog.log.error("An error occurred trying to load a proxy into {}.{}", target.getName(), e);
throw new LoaderException(e);
}
}
}
}
else
{
FMLLog.log.trace("Mod does not appear to be a singleton.");
}
}
}
public static class JavaAdapter implements ILanguageAdapter {
@Override
public Object getNewInstance(FMLModContainer container, Class<?> objectClass, ClassLoader classLoader, Method factoryMarkedMethod) throws Exception
{
if (factoryMarkedMethod != null)
{
return factoryMarkedMethod.invoke(null);
}
else
{
return objectClass.newInstance();
}
}
@Override
public boolean supportsStatics()
{
return true;
}
@Override
public void setProxy(Field target, Class<?> proxyTarget, Object proxy) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException,
SecurityException
{
target.set(null, proxy);
}
@Override
public void setInternalProxies(ModContainer mod, Side side, ClassLoader loader)
{
// Nothing to do here.
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.util.Random;
import net.minecraft.world.World;
import net.minecraft.world.chunk.IChunkProvider;
import net.minecraft.world.gen.IChunkGenerator;
/**
* This is called back during world generation.
*
* @author cpw
*
*/
public interface IWorldGenerator
{
/**
* Generate some world
*
* @param random the chunk specific {@link Random}.
* @param chunkX the chunk X coordinate of this chunk.
* @param chunkZ the chunk Z coordinate of this chunk.
* @param world : additionalData[0] The minecraft {@link World} we're generating for.
* @param chunkGenerator : additionalData[1] The {@link IChunkProvider} that is generating.
* @param chunkProvider : additionalData[2] {@link IChunkProvider} that is requesting the world generation.
*
*/
void generate(Random random, int chunkX, int chunkZ, World world, IChunkGenerator chunkGenerator, IChunkProvider chunkProvider);
}

View File

@@ -0,0 +1,242 @@
/*
* 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.net.URL;
import java.security.cert.Certificate;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import net.minecraftforge.fml.common.versioning.VersionRange;
import com.google.common.eventbus.EventBus;
import javax.annotation.Nullable;
import net.minecraftforge.fml.common.ModContainer.Disableable;
public class InjectedModContainer implements ModContainer
{
private File source;
public final ModContainer wrappedContainer;
public InjectedModContainer(ModContainer mc, File source)
{
this.source = source != null ? source : new File("minecraft.jar");
this.wrappedContainer = mc;
}
@Override
public String getModId()
{
return wrappedContainer.getModId();
}
@Override
public String getName()
{
return wrappedContainer.getName();
}
@Override
public String getVersion()
{
return wrappedContainer.getVersion();
}
@Override
public File getSource()
{
return source;
}
@Override
public ModMetadata getMetadata()
{
return wrappedContainer.getMetadata();
}
@Override
public void bindMetadata(MetadataCollection mc)
{
wrappedContainer.bindMetadata(mc);
}
@Override
public void setEnabledState(boolean enabled)
{
wrappedContainer.setEnabledState(enabled);
}
@Override
public Set<ArtifactVersion> getRequirements()
{
return wrappedContainer.getRequirements();
}
@Override
public List<ArtifactVersion> getDependencies()
{
return wrappedContainer.getDependencies();
}
@Override
public List<ArtifactVersion> getDependants()
{
return wrappedContainer.getDependants();
}
@Override
public String getSortingRules()
{
return wrappedContainer.getSortingRules();
}
@Override
public boolean registerBus(EventBus bus, LoadController controller)
{
return wrappedContainer.registerBus(bus, controller);
}
@Override
public boolean matches(Object mod)
{
return wrappedContainer.matches(mod);
}
@Override
public Object getMod()
{
return wrappedContainer.getMod();
}
@Override
public ArtifactVersion getProcessedVersion()
{
return wrappedContainer.getProcessedVersion();
}
@Override
public boolean isImmutable()
{
return true;
}
@Override
public String getDisplayVersion()
{
return wrappedContainer.getDisplayVersion();
}
@Override
public VersionRange acceptableMinecraftVersionRange()
{
return wrappedContainer.acceptableMinecraftVersionRange();
}
@Nullable
public WorldAccessContainer getWrappedWorldAccessContainer()
{
if (wrappedContainer instanceof WorldAccessContainer)
{
return (WorldAccessContainer) wrappedContainer;
}
else
{
return null;
}
}
@Override
@Nullable
public Certificate getSigningCertificate()
{
return wrappedContainer.getSigningCertificate();
}
@Override
public String toString()
{
return "Wrapped{"+wrappedContainer.toString()+"}";
}
@Override
public Map<String, String> getCustomModProperties()
{
return wrappedContainer.getCustomModProperties();
}
@Override
public Class<?> getCustomResourcePackClass()
{
return wrappedContainer.getCustomResourcePackClass();
}
@Override
public Map<String, String> getSharedModDescriptor()
{
return wrappedContainer.getSharedModDescriptor();
}
@Override
public Disableable canBeDisabled()
{
return wrappedContainer.canBeDisabled();
}
@Override
public String getGuiClassName()
{
return wrappedContainer.getGuiClassName();
}
@Override
public List<String> getOwnedPackages()
{
return wrappedContainer.getOwnedPackages();
}
@Override
public boolean shouldLoadInEnvironment()
{
return true;
}
@Override
public URL getUpdateUrl()
{
return wrappedContainer.getUpdateUrl();
}
@Override
public void setClassVersion(int classVersion)
{
wrappedContainer.setClassVersion(classVersion);
}
@Override
public int getClassVersion()
{
return wrappedContainer.getClassVersion();
}
}

View File

@@ -0,0 +1,386 @@
/*
* 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.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import net.minecraftforge.common.util.TextTable;
import net.minecraftforge.fml.common.LoaderState.ModState;
import net.minecraftforge.fml.common.ProgressManager.ProgressBar;
import net.minecraftforge.fml.common.event.FMLEvent;
import net.minecraftforge.fml.common.event.FMLLoadEvent;
import net.minecraftforge.fml.common.event.FMLModDisabledEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.event.FMLStateEvent;
import net.minecraftforge.fml.common.eventhandler.FMLThrowingEventBus;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.message.FormattedMessage;
import com.google.common.base.Throwables;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import javax.annotation.Nullable;
public class LoadController
{
private Loader loader;
private EventBus masterChannel;
private ImmutableMap<String, EventBus> eventChannels;
private LoaderState state;
private Multimap<String, ModState> modStates = ArrayListMultimap.create();
private List<ModContainer> activeModList = Lists.newArrayList();
private ModContainer activeContainer;
private BiMap<ModContainer, Object> modObjectList;
private ListMultimap<String, ModContainer> packageOwners;
public LoadController(Loader loader)
{
this.loader = loader;
this.masterChannel = new FMLThrowingEventBus((exception, context) -> {
Throwables.throwIfUnchecked(exception);
// should not happen, but add some extra context for checked exceptions
Method method = context.getSubscriberMethod();
String parameterNames = Stream.of(method.getParameterTypes()).map(Class::getName).collect(Collectors.joining(", "));
String message = "Exception thrown during LoadController." + method.getName() + '(' + parameterNames + ')';
throw new LoaderExceptionModCrash(message, exception);
});
this.masterChannel.register(this);
state = LoaderState.NOINIT;
packageOwners = ArrayListMultimap.create();
}
void disableMod(ModContainer mod)
{
HashMap<String, EventBus> temporary = Maps.newHashMap(eventChannels);
String modId = mod.getModId();
EventBus bus = temporary.remove(modId);
bus.post(new FMLModDisabledEvent());
eventChannels = ImmutableMap.copyOf(temporary);
modStates.put(modId, ModState.DISABLED);
modObjectList.remove(mod);
activeModList.remove(mod);
}
@Subscribe
public void buildModList(FMLLoadEvent event)
{
Builder<String, EventBus> eventBus = ImmutableMap.builder();
for (final ModContainer mod : loader.getModList())
{
EventBus bus = new FMLThrowingEventBus((exception, context) -> this.errorOccurred(mod, exception));
boolean isActive = mod.registerBus(bus, this);
if (isActive)
{
activeModList.add(mod);
modStates.put(mod.getModId(), ModState.UNLOADED);
eventBus.put(mod.getModId(), bus);
FMLCommonHandler.instance().addModToResourcePack(mod);
}
else
{
FMLLog.log.warn("Mod {} has been disabled through configuration", mod.getModId());
modStates.put(mod.getModId(), ModState.UNLOADED);
modStates.put(mod.getModId(), ModState.DISABLED);
}
}
eventChannels = eventBus.build();
}
public void distributeStateMessage(LoaderState state, Object... eventData)
{
if (state.hasEvent())
{
masterChannel.post(state.getEvent(eventData));
}
}
public void transition(LoaderState desiredState, boolean forceState)
{
if (FMLCommonHandler.instance().isDisplayCloseRequested())
{
FMLLog.log.info("The game window is being closed by the player, exiting.");
FMLCommonHandler.instance().exitJava(0, false);
}
LoaderState oldState = state;
state = state.transition(false);
if (state != desiredState)
{
if (!forceState)
{
FormattedMessage message = new FormattedMessage("A fatal error occurred during the state transition from {} to {}. State became {} instead. Loading cannot continue.", oldState, desiredState, state);
throw new LoaderException(message.getFormattedMessage());
}
else
{
FMLLog.log.info("The state engine was in incorrect state {} and forced into state {}. Errors may have been discarded.", state, desiredState);
forceState(desiredState);
}
}
}
@Deprecated // TODO remove in 1.13
public void checkErrorsAfterAvailable()
{
}
@Deprecated // TODO remove in 1.13
public void checkErrors()
{
}
@Nullable
public ModContainer activeContainer()
{
return activeContainer != null ? activeContainer : findActiveContainerFromStack();
}
void forceActiveContainer(@Nullable ModContainer container)
{
activeContainer = container;
}
@Subscribe
public void propogateStateMessage(FMLEvent stateEvent)
{
if (stateEvent instanceof FMLPreInitializationEvent)
{
modObjectList = buildModObjectList();
}
ProgressBar bar = ProgressManager.push(stateEvent.description(), activeModList.size(), true);
for (ModContainer mc : activeModList)
{
bar.step(mc.getName());
sendEventToModContainer(stateEvent, mc);
}
ProgressManager.pop(bar);
}
private void sendEventToModContainer(FMLEvent stateEvent, ModContainer mc)
{
String modId = mc.getModId();
Collection<String> requirements = mc.getRequirements().stream().map(ArtifactVersion::getLabel).collect(Collectors.toCollection(HashSet::new));
for (ArtifactVersion av : mc.getDependencies())
{
if (av.getLabel() != null && requirements.contains(av.getLabel()) && modStates.containsEntry(av.getLabel(), ModState.ERRORED))
{
FMLLog.log.error("Skipping event {} and marking errored mod {} since required dependency {} has errored", stateEvent.getEventType(), modId, av.getLabel());
modStates.put(modId, ModState.ERRORED);
return;
}
}
activeContainer = mc;
stateEvent.applyModContainer(mc);
ThreadContext.put("mod", modId);
FMLLog.log.trace("Sending event {} to mod {}", stateEvent.getEventType(), modId);
eventChannels.get(modId).post(stateEvent);
FMLLog.log.trace("Sent event {} to mod {}", stateEvent.getEventType(), modId);
ThreadContext.remove("mod");
activeContainer = null;
if (stateEvent instanceof FMLStateEvent)
{
modStates.put(modId, ((FMLStateEvent) stateEvent).getModState());
}
}
public ImmutableBiMap<ModContainer, Object> buildModObjectList()
{
ImmutableBiMap.Builder<ModContainer, Object> builder = ImmutableBiMap.builder();
for (ModContainer mc : activeModList)
{
if (!mc.isImmutable() && mc.getMod() != null)
{
builder.put(mc, mc.getMod());
List<String> packages = mc.getOwnedPackages();
for (String pkg : packages)
{
packageOwners.put(pkg, mc);
}
}
if (mc.getMod() == null && !mc.isImmutable() && state != LoaderState.CONSTRUCTING)
{
FormattedMessage message = new FormattedMessage("There is a severe problem with {} ({}) - it appears not to have constructed correctly", mc.getName(), mc.getModId());
this.errorOccurred(mc, new RuntimeException(message.getFormattedMessage()));
}
}
return builder.build();
}
public void errorOccurred(ModContainer modContainer, Throwable exception)
{
String modId = modContainer.getModId();
String modName = modContainer.getName();
modStates.put(modId, ModState.ERRORED);
if (exception instanceof InvocationTargetException)
{
exception = exception.getCause();
}
if (exception instanceof LoaderException) // avoid wrapping loader exceptions multiple times
{
throw (LoaderException) exception;
}
FormattedMessage message = new FormattedMessage("Caught exception from {} ({})", modName, modId);
throw new LoaderExceptionModCrash(message.getFormattedMessage(), exception);
}
public void printModStates(StringBuilder ret)
{
ret.append("\n\tStates:");
for (ModState state : ModState.values())
ret.append(" '").append(state.getMarker()).append("' = ").append(state.toString());
TextTable table = new TextTable(Lists.newArrayList(
TextTable.column("State"),
TextTable.column("ID"),
TextTable.column("Version"),
TextTable.column("Source"),
TextTable.column("Signature"))
);
for (ModContainer mc : loader.getModList())
{
table.add(
modStates.get(mc.getModId()).stream().map(ModState::getMarker).reduce("", (a, b) -> a + b),
mc.getModId(),
mc.getVersion(),
mc.getSource().getName(),
mc.getSigningCertificate() != null ? CertificateHelper.getFingerprint(mc.getSigningCertificate()) : "None"
);
}
ret.append("\n");
ret.append("\n\t");
table.append(ret, "\n\t");
ret.append("\n");
}
public List<ModContainer> getActiveModList()
{
return activeModList;
}
public ModState getModState(ModContainer selectedMod)
{
return Iterables.getLast(modStates.get(selectedMod.getModId()), ModState.AVAILABLE);
}
public void distributeStateMessage(Class<?> customEvent)
{
Object eventInstance;
try
{
eventInstance = customEvent.newInstance();
}
catch (InstantiationException | IllegalAccessException e)
{
throw new LoaderException("Failed to create new event instance for " + customEvent.getName(), e);
}
masterChannel.post(eventInstance);
}
public BiMap<ModContainer, Object> getModObjectList()
{
if (modObjectList == null)
{
FMLLog.log.fatal("Detected an attempt by a mod {} to perform game activity during mod construction. This is a serious programming error.", activeContainer);
return buildModObjectList();
}
return ImmutableBiMap.copyOf(modObjectList);
}
public boolean isInState(LoaderState state)
{
return this.state == state;
}
boolean hasReachedState(LoaderState state)
{
return this.state.ordinal() >= state.ordinal() && this.state != LoaderState.ERRORED;
}
void forceState(LoaderState newState)
{
this.state = newState;
}
@Nullable
private ModContainer findActiveContainerFromStack()
{
for (Class<?> c : getCallingStack())
{
int idx = c.getName().lastIndexOf('.');
if (idx == -1)
{
continue;
}
String pkg = c.getName().substring(0, idx);
if (packageOwners.containsKey(pkg))
{
return packageOwners.get(pkg).get(0);
}
}
return null;
}
private FMLSecurityManager accessibleManager = new FMLSecurityManager();
class FMLSecurityManager extends SecurityManager
{
Class<?>[] getStackClasses()
{
return getClassContext();
}
}
Class<?>[] getCallingStack()
{
return accessibleManager.getStackClasses();
}
LoaderState getState()
{
return state;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,49 @@
/*
* 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 net.minecraftforge.fml.common.EnhancedRuntimeException.WrappedPrintStream;
public class LoaderException extends EnhancedRuntimeException
{
/**
*
*/
private static final long serialVersionUID = -5675297950958861378L;
public LoaderException(Throwable wrapped)
{
super(wrapped);
}
public LoaderException()
{
}
public LoaderException(String message)
{
super(message);
}
public LoaderException(String message, Throwable cause)
{
super(message, cause);
}
@Override protected void printStackTrace(WrappedPrintStream stream){}
}

View File

@@ -0,0 +1,39 @@
/*
* 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;
/**
* Prevent LoaderException from adding its own stack trace to the wrapped throwable's stack trace.
*/
public class LoaderExceptionModCrash extends LoaderException
{
private static final long serialVersionUID = 1L;
public LoaderExceptionModCrash(String message, Throwable cause)
{
super(message, cause);
}
@Override
public synchronized Throwable fillInStackTrace()
{
return this;
}
}

View File

@@ -0,0 +1,138 @@
/*
* 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 net.minecraftforge.fml.common.event.FMLConstructionEvent;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLLoadCompleteEvent;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.event.FMLServerAboutToStartEvent;
import net.minecraftforge.fml.common.event.FMLServerStartedEvent;
import net.minecraftforge.fml.common.event.FMLServerStartingEvent;
import net.minecraftforge.fml.common.event.FMLServerStoppedEvent;
import net.minecraftforge.fml.common.event.FMLServerStoppingEvent;
import net.minecraftforge.fml.common.event.FMLStateEvent;
/**
* The state enum used to help track state progression for the loader
* @author cpw
*
*/
public enum LoaderState
{
NOINIT("Uninitialized",null),
LOADING("Loading",null),
CONSTRUCTING("Constructing mods",FMLConstructionEvent.class),
PREINITIALIZATION("Pre-initializing mods", FMLPreInitializationEvent.class),
INITIALIZATION("Initializing mods", FMLInitializationEvent.class),
POSTINITIALIZATION("Post-initializing mods", FMLPostInitializationEvent.class),
AVAILABLE("Mod loading complete", FMLLoadCompleteEvent.class),
SERVER_ABOUT_TO_START("Server about to start", FMLServerAboutToStartEvent.class),
SERVER_STARTING("Server starting", FMLServerStartingEvent.class),
SERVER_STARTED("Server started", FMLServerStartedEvent.class),
SERVER_STOPPING("Server stopping", FMLServerStoppingEvent.class),
SERVER_STOPPED("Server stopped", FMLServerStoppedEvent.class),
ERRORED("Mod Loading errored",null);
private Class<? extends FMLStateEvent> eventClass;
private String name;
private LoaderState(String name, Class<? extends FMLStateEvent> event)
{
this.name = name;
this.eventClass = event;
}
public LoaderState transition(boolean errored)
{
if (errored)
{
return ERRORED;
}
// stopping -> available
if (this == SERVER_STOPPED)
{
return AVAILABLE;
}
return values()[ordinal() < values().length ? ordinal()+1 : ordinal()];
}
public boolean hasEvent()
{
return eventClass != null;
}
public FMLStateEvent getEvent(Object... eventData)
{
try
{
return eventClass.getConstructor(Object[].class).newInstance((Object)eventData);
}
catch (ReflectiveOperationException e)
{
throw new RuntimeException(e);
}
}
public LoaderState requiredState()
{
if (this == NOINIT) return NOINIT;
return LoaderState.values()[this.ordinal()-1];
}
public String getPrettyName()
{
return name;
}
public enum ModState
{
UNLOADED ("Unloaded", "U"),
LOADED ("Loaded", "L"),
CONSTRUCTED ("Constructed", "C"),
PREINITIALIZED ("Pre-initialized", "H"),
INITIALIZED ("Initialized", "I"),
POSTINITIALIZED("Post-initialized", "J"),
AVAILABLE ("Available", "A"),
DISABLED ("Disabled", "D"),
ERRORED ("Errored", "E");
private String label;
private String marker;
private ModState(String label, String marker)
{
this.label = label;
this.marker = marker;
}
@Override
public String toString()
{
return this.label;
}
public String getMarker()
{
return this.marker;
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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 com.google.common.eventbus.EventBus;
import net.minecraftforge.fml.common.ModContainer.Disableable;
public class MCPDummyContainer extends DummyModContainer {
public MCPDummyContainer(ModMetadata metadata) {
super(metadata);
}
@Override
public boolean registerBus(EventBus bus, LoadController controller) {
return true;
}
@Override
public Disableable canBeDisabled()
{
return Disableable.YES;
}
}

View File

@@ -0,0 +1,127 @@
/*
* 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.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import net.minecraftforge.fml.common.versioning.VersionParser;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import javax.annotation.Nullable;
public class MetadataCollection
{
private String modListVersion;
private ModMetadata[] modList;
private Map<String, ModMetadata> metadatas = Maps.newHashMap();
public static MetadataCollection from(@Nullable InputStream inputStream, String sourceName)
{
if (inputStream == null)
{
return new MetadataCollection();
}
InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
try
{
MetadataCollection collection;
Gson gson = new GsonBuilder().registerTypeAdapter(ArtifactVersion.class, new ArtifactVersionAdapter()).create();
JsonParser parser = new JsonParser();
JsonElement rootElement = parser.parse(reader);
if (rootElement.isJsonArray())
{
collection = new MetadataCollection();
JsonArray jsonList = rootElement.getAsJsonArray();
collection.modList = new ModMetadata[jsonList.size()];
int i = 0;
for (JsonElement mod : jsonList)
{
collection.modList[i++]=gson.fromJson(mod, ModMetadata.class);
}
}
else
{
collection = gson.fromJson(rootElement, MetadataCollection.class);
}
collection.parseModMetadataList();
return collection;
}
catch (JsonParseException e)
{
FMLLog.log.error("The mcmod.info file in {} cannot be parsed as valid JSON. It will be ignored", sourceName, e);
return new MetadataCollection();
}
}
private void parseModMetadataList()
{
for (ModMetadata modMetadata : modList)
{
metadatas.put(modMetadata.modId, modMetadata);
}
}
public ModMetadata getMetadataForId(String modId, Map<String, Object> extraData)
{
if (!metadatas.containsKey(modId))
{
ModMetadata dummy = new ModMetadata();
dummy.modId = modId;
dummy.name = (String) extraData.get("name");
dummy.version = (String) extraData.get("version");
dummy.autogenerated = true;
metadatas.put(modId, dummy);
}
return metadatas.get(modId);
}
public static class ArtifactVersionAdapter extends TypeAdapter<ArtifactVersion>
{
@Override
public void write(JsonWriter out, ArtifactVersion value) throws IOException
{
// no op - we never write these out
}
@Override
public ArtifactVersion read(JsonReader in) throws IOException
{
return VersionParser.parseVersionReference(in.nextString());
}
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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.security.cert.Certificate;
import com.google.common.eventbus.EventBus;
import net.minecraftforge.fml.common.versioning.VersionParser;
import net.minecraftforge.fml.common.versioning.VersionRange;
import net.minecraftforge.fml.relauncher.FMLLaunchHandler;
import net.minecraftforge.fml.relauncher.Side;
import javax.annotation.Nullable;
public class MinecraftDummyContainer extends DummyModContainer
{
private VersionRange staticRange;
public MinecraftDummyContainer(String actualMCVersion)
{
super(new ModMetadata());
getMetadata().modId = "minecraft";
getMetadata().name = "Minecraft";
getMetadata().version = actualMCVersion;
staticRange = VersionParser.parseRange("["+actualMCVersion+"]");
}
@Override
public boolean isImmutable()
{
return true;
}
@Override
public File getSource()
{
return new File("minecraft.jar");
}
@Override
public boolean registerBus(EventBus bus, LoadController controller)
{
return true;
}
public VersionRange getStaticVersionRange()
{
return staticRange;
}
@Override
@Nullable
public Certificate getSigningCertificate()
{
if (FMLLaunchHandler.side() != Side.CLIENT)
return null;
try
{
Class<?> cbr = Class.forName("net.minecraft.client.ClientBrandRetriever", false, getClass().getClassLoader());
Certificate[] certificates = cbr.getProtectionDomain().getCodeSource().getCertificates();
return certificates != null ? certificates[0] : null;
}
catch (Exception ignored){} // Errors don't matter just return null.
return null;
}
}

View File

@@ -0,0 +1,151 @@
/*
* 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 javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import com.google.common.base.Preconditions;
import net.minecraft.client.gui.GuiScreen;
import net.minecraftforge.fml.client.GuiModsMissing;
import net.minecraftforge.fml.client.IDisplayableError;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import net.minecraftforge.fml.common.EnhancedRuntimeException.WrappedPrintStream;
public class MissingModsException extends EnhancedRuntimeException implements IDisplayableError
{
private static final long serialVersionUID = 1L;
private final String id;
private final String name;
/** @deprecated use {@link #getMissingModInfos()} */
@Deprecated // TODO remove in 1.13
public final Set<ArtifactVersion> missingMods;
private final List<MissingModInfo> missingModsInfos;
private final String modName;
public MissingModsException(String id, String name)
{
this(new HashSet<>(), id, name);
}
/**
* @deprecated use {@link #MissingModsException(String, String)}
*/
@Deprecated // TODO remove in 1.13
public MissingModsException(Set<ArtifactVersion> missingMods, String id, String name)
{
this.id = id;
this.name = name;
this.missingMods = missingMods;
this.missingModsInfos = new ArrayList<>();
for (ArtifactVersion artifactVersion : missingMods)
{
missingModsInfos.add(new MissingModInfo(artifactVersion, null, true));
}
this.modName = name;
}
@Override
public String getMessage()
{
Set<ArtifactVersion> missingMods = missingModsInfos.stream().map(MissingModInfo::getAcceptedVersion).collect(Collectors.toSet());
return String.format("Mod %s (%s) requires %s", id, name, missingMods);
}
public void addMissingMod(ArtifactVersion acceptedVersion, @Nullable ArtifactVersion currentVersion, boolean required)
{
MissingModInfo missingModInfo = new MissingModInfo(acceptedVersion, currentVersion, required);
this.missingModsInfos.add(missingModInfo);
this.missingMods.add(acceptedVersion);
}
public String getModName()
{
return modName;
}
public List<MissingModInfo> getMissingModInfos()
{
return Collections.unmodifiableList(this.missingModsInfos);
}
@Override
protected void printStackTrace(WrappedPrintStream stream)
{
stream.println("Missing Mods:");
for (MissingModInfo info : this.missingModsInfos)
{
ArtifactVersion acceptedVersion = info.getAcceptedVersion();
ArtifactVersion currentVersion = info.getCurrentVersion();
String currentString = currentVersion != null ? currentVersion.getVersionString() : "missing";
stream.println(String.format("\t%s : need %s: have %s", acceptedVersion.getVersionString(), acceptedVersion.getRangeString(), currentString));
}
stream.println("");
}
@Override
@SideOnly(Side.CLIENT)
public GuiScreen createGui()
{
return new GuiModsMissing(this);
}
public static class MissingModInfo
{
private final ArtifactVersion acceptedVersion;
@Nullable
private final ArtifactVersion currentVersion;
private final boolean required;
private MissingModInfo(ArtifactVersion acceptedVersion, @Nullable ArtifactVersion currentVersion, boolean required)
{
Preconditions.checkNotNull(acceptedVersion, "acceptedVersion");
this.acceptedVersion = acceptedVersion;
this.currentVersion = currentVersion;
this.required = required;
}
@Nullable
public ArtifactVersion getCurrentVersion()
{
return currentVersion;
}
public ArtifactVersion getAcceptedVersion()
{
return acceptedVersion;
}
public boolean isRequired()
{
return required;
}
}
}

View File

@@ -0,0 +1,384 @@
/*
* 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.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import net.minecraftforge.fml.client.IModGuiFactory;
import net.minecraftforge.fml.common.event.FMLEvent;
import net.minecraftforge.fml.common.event.FMLFingerprintViolationEvent;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLInterModComms;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.event.FMLServerAboutToStartEvent;
import net.minecraftforge.fml.common.event.FMLServerStartedEvent;
import net.minecraftforge.fml.common.event.FMLServerStartingEvent;
import net.minecraftforge.fml.common.event.FMLServerStoppedEvent;
import net.minecraftforge.fml.common.event.FMLServerStoppingEvent;
import net.minecraftforge.fml.common.event.FMLInterModComms.IMCEvent;
import net.minecraftforge.fml.common.network.NetworkCheckHandler;
import net.minecraftforge.fml.common.registry.GameRegistry;
import net.minecraftforge.fml.common.versioning.VersionRange;
import net.minecraftforge.fml.relauncher.Side;
/**
* This defines a Mod to FML.
* Any class found with this annotation applied will be loaded as a Mod. The instance that is loaded will
* represent the mod to other Mods in the system. It will be sent various subclasses of {@link FMLEvent}
* at pre-defined times during the loading of the game, based on where you have applied the {@link EventHandler}
* annotation.
*
* <p>This is a simple example of a Mod. It has the modId of "mymodid", the name of "My example mod", it is
* version 1.0, and depends on FML being loaded.
* <pre>{@code
* package mymod;
* // Declare that this is a mod with modId "mymodid", name "My example mod", version "1.0" and dependency on FML.
* {@literal @}Mod(modId="mymodid",name="My example mod",version="1.0",dependencies="required-after:FML")
* public class MyMod {
* // Populate this field with the instance of the mod created by FML
* {@literal @}Instance("mymodid")
* public MyMod instance;
*
* // Mark this method for receiving an {@link FMLEvent} (in this case, it's the {@link FMLPreInitializationEvent})
* {@literal @}EventHandler public void preInit(FMLPreInitializationEvent event)
* {
* // Do stuff in pre-init phase (read config, create blocks and items, register them)
* }
* }
* }
* </pre>
*
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Mod
{
/**
* The unique mod identifier for this mod.
* <b>Required to be lowercased in the english locale for compatibility. Will be truncated to 64 characters long.</b>
*
* This will be used to identify your mod for third parties (other mods), it will be used to identify your mod for registries such as block and item registries.
* By default, you will have a resource domain that matches the modid. All these uses require that constraints are imposed on the format of the modid.
*/
String modid();
/**
* A user friendly name for the mod
*/
String name() default "";
/**
* A version string for this mod.
*
* The version string here should be just numbers separated by dots,
* to make specifying {@link #dependencies()} simple for other mods.
*
* See also: <a href="https://cwiki.apache.org/confluence/display/MAVENOLD/Versioning">"Versioning" on Maven Wiki</a>
*/
String version() default "";
/**
* A dependency string for this mod, which specifies which mod(s) it depends on in order to run.
*
* A dependency string must start with a combination of these prefixes, separated by "-":
* [before, after], [required], [client, server]
* At least one "before", "after", or "required" must be specified.
* Then ":" and the mod id.
* Then a version range should be specified for the mod by adding "@" and the version range.
* The version range format is described in the javadoc here:
* {@link VersionRange#createFromVersionSpec(java.lang.String)}
* Then a ";".
*
* If a "required" mod is missing, or a mod exists with a version outside the specified range,
* the game will not start and an error screen will tell the player which versions are required.
*
* Example:
* Our example mod:
* * depends on Forge and uses new features that were introduced in Forge version 14.21.1.2395
* "required:forge@[14.21.1.2395,);"
*
* 1.12.2 Note: for compatibility with Forge older than 14.23.0.2501 the syntax must follow this older format:
* "required-after:forge@[14.21.1.2395,);"
* For more explanation see https://github.com/MinecraftForge/MinecraftForge/issues/4918
*
* * is a dedicated addon to mod1 and has to have its event handlers run after mod1's are run,
* "required-after:mod1;"
* * has optional integration with mod2 which depends on features introduced in mod2 version 4.7.0,
* "after:mod2@[4.7.0,);"
* * depends on a client-side-only rendering library called rendermod
* "required-client:rendermod;"
*
* The full dependencies string is all of those combined:
* "required:forge@[14.21.1.2395,);required-after:mod1;after:mod2@[4.7.0,);required-client:rendermod;"
*
* This will stop the game and display an error message if any of these is true:
* The installed forge is too old,
* mod1 is missing,
* an old version of mod2 is present,
* rendermod is missing on the client.
*/
String dependencies() default "";
/**
* Whether to use the mcmod.info metadata by default for this mod.
* If true, settings in the mcmod.info file will override settings in these annotations.
*/
boolean useMetadata() default false;
/**
* If true, this mod will not be loaded on the Dedicated Server environment.
* Will crash if both serverSideOnly and clientSideOnly are set to true.
*/
boolean clientSideOnly() default false;
/**
* If true, this mod will not be loaded on the Client environment.
* Will crash if both serverSideOnly and clientSideOnly are set to true.
*/
boolean serverSideOnly() default false;
/**
* The acceptable range of minecraft versions that this mod will load and run in
* The default ("empty string") indicates that the currently RUNNING minecraft version is acceptable.
* This means ANY version that the end user adds the mod to. Modders PLEASE set this.
* FML will refuse to run with an error if the minecraft version is not in this range across all mods.
* @return A version range as specified by the maven version range specification or the empty string
*/
String acceptedMinecraftVersions() default "";
/**
* A replacement for the no-longer-existing "versionRange" of NetworkMod. Specify a remote version range
* that this mod will accept as valid. Defaults to nothing, which is interpreted as "only this version".
* Another special value is '*' which means accept all versions.
*
* This is ignored if there is a {@link NetworkCheckHandler} annotation on a method in this class.
*
* @return A version range as specified by the maven version range specification or the empty string
*/
String acceptableRemoteVersions() default "";
/**
* A version range specifying compatible save version information. If your mod follows good version numbering
* practice <a href="http://semver.org/">Like this (http://semver.org/)</a> then this should be sufficient.
*
* Advanced users can specify a {@link SaveInspectionHandler} instead.
* @return A version range as specified by the maven version range specification or the empty string
*/
String acceptableSaveVersions() default "";
/**
* Specifying this field allows for a mod to expect a signed jar with a fingerprint matching this value.
* The fingerprint should be SHA-1 encoded, lowercase with ':' removed. An empty value indicates that
* the mod is not expecting to be signed.
*
* Any incorrectness of the fingerprint, be it missing or wrong, will result in the {@link FMLFingerprintViolationEvent}
* event firing <i>prior to any other event on the mod</i>.
*
* @return A certificate fingerprint that is expected for this mod.
*/
String certificateFingerprint() default "";
/**
* The language the mod is authored in. This will be used to control certain compatibility behaviours for this mod.
* Valid values are currently "java", "scala"
*
* @return The language the mod is authored in
*/
String modLanguage() default "java";
/**
* The language adapter to be used to load this mod. This overrides the value of modLanguage. The class must have a
* public zero variable constructor and implement {@link ILanguageAdapter} just like the Java and Scala adapters.
*
* A class with an invalid constructor or that doesn't implement {@link ILanguageAdapter} will throw an exception and
* halt loading.
*
* @return The full class name of the language adapter
*/
String modLanguageAdapter() default "";
/**
* If your mod doesn't have a runtime persistent effect on the state of the game, and can be disabled without side effects
* (minimap mods, graphical tweak mods) then you can set true here and receive the FMLDeactivationEvent to perform deactivation
* tasks.
* This does not affect administrative disabling through the system property fml.modStates or the config file fmlModState.properties.
* The mod will only be deactivated outside of a running game world - FML will never allow mod deactivation whilst a game server
* is running.
*
* @return if this mod can be deactivated whilst the game is open.
*/
boolean canBeDeactivated() default false;
/**
* An optional GUI factory for this mod. This is the name of a class implementing {@link IModGuiFactory} that will be instantiated
* on the client side, and will have certain configuration/options guis requested from it.
*
* @return The name of a class implementing {@link IModGuiFactory}
*/
String guiFactory() default "";
/**
* An optional URL to a JSON file that will be checked once per launch to determine if there is an updated
* version of this mod and notify the end user. For more information see ForgeVersion.
* Format is defined here: https://gist.github.com/LexManos/7aacb9aa991330523884
* @return URL to update metadata json
*/
String updateJSON() default "";
/**
* A list of custom properties for this mod. Completely up to the mod author if/when they
* want to put anything in here.
* @return an optional list of custom properties
*/
CustomProperty[] customProperties() default {};
/**
* A custom key => value property pair for use with {@link Mod#customProperties()}
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface CustomProperty
{
/**
* A key. Should be unique.
* @return A key
*/
String k();
/**
* A value. Can be anything.
* @return A value
*/
String v();
}
/**
* Marks the associated method as handling an FML lifecycle event.
* The method must have a single parameter, one of the following types. This annotation
* replaces the multiple different annotations that previously were used.
*
* Current event classes. This first section is standard lifecycle events. They are dispatched
* at various phases as the game starts. Each event should have information useful to that
* phase of the lifecycle. They are fired in this order.
*
* These suggestions are mostly just suggestions on what to do in each event.
* <ul>
* <li> {@link FMLPreInitializationEvent} : Run before anything else. Read your config, create blocks,
* items, etc, and register them with the {@link GameRegistry}.</li>
* <li> {@link FMLInitializationEvent} : Do your mod setup. Build whatever data structures you care about. Register recipes,
* send {@link FMLInterModComms} messages to other mods.</li>
* <li> {@link FMLPostInitializationEvent} : Handle interaction with other mods, complete your setup based on this.</li>
* </ul>
* <p>These are the server lifecycle events. They are fired whenever a server is running, or about to run. Each time a server
* starts they will be fired in this sequence.
* <ul>
* <li> {@link FMLServerAboutToStartEvent} : Use if you need to handle something before the server has even been created.</li>
* <li> {@link FMLServerStartingEvent} : Do stuff you need to do to set up the server. register commands, tweak the server.</li>
* <li> {@link FMLServerStartedEvent} : Do what you need to with the running server.</li>
* <li> {@link FMLServerStoppingEvent} : Do what you need to before the server has started it's shutdown sequence.</li>
* <li> {@link FMLServerStoppedEvent} : Do whatever cleanup you need once the server has shutdown. Generally only useful
* on the integrated server.</li>
* </ul>
* The second set of events are more specialized, for receiving notification of specific
* information.
* <ul>
* <li> {@link FMLFingerprintViolationEvent} : Sent just before {@link FMLPreInitializationEvent}
* if something is wrong with your mod signature</li>
* <li> {@link IMCEvent} : Sent just after {@link FMLInitializationEvent} if you have IMC messages waiting
* from other mods</li>
* </ul>
*
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface EventHandler{}
/**
* Populate the annotated field with the mod instance based on the specified ModId. This can be used
* to retrieve instances of other mods.
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Instance {
/**
* The mod object to inject into this field
*/
String value() default "";
/**
* Optional owner modid, required if this annotation is on something that is not inside the main class of a mod container.
* This is required to prevent mods from classloading other, potentially disabled mods.
*/
String owner() default "";
}
/**
* Populate the annotated field with the mod's metadata.
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Metadata {
/**
* The mod id specifying the metadata to load here
*/
String value() default "";
/**
* Optional owner modid, required if this annotation is on something that is not inside the main class of a mod container.
* This is required to prevent mods from classloading other, potentially disabled mods.
*/
String owner() default "";
}
/**
* Mod instance factory method. Should return an instance of the mod. Applies only to static methods on the same class as {@link Mod}.
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface InstanceFactory {
}
/**
* A class which will be subscribed to {@link net.minecraftforge.common.MinecraftForge.EVENT_BUS} at mod construction time.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface EventBusSubscriber {
Side[] value() default { Side.CLIENT, Side.SERVER };
/**
* Optional value, only nessasary if tis annotation is not on the same class that has a @Mod annotation.
* Needed to prevent early classloading of classes not owned by your mod.
* @return
*/
String modid() default "";
}
}

View File

@@ -0,0 +1,256 @@
/*
* 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.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import net.minecraftforge.fml.common.asm.transformers.ModAPITransformer;
import net.minecraftforge.fml.common.discovery.ASMDataTable;
import net.minecraftforge.fml.common.discovery.ModCandidate;
import net.minecraftforge.fml.common.discovery.ModDiscoverer;
import net.minecraftforge.fml.common.discovery.ASMDataTable.ASMData;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion;
import net.minecraftforge.fml.common.versioning.VersionParser;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
public class ModAPIManager {
public static final ModAPIManager INSTANCE = new ModAPIManager();
@SuppressWarnings("unused")
private ModAPITransformer transformer;
private ASMDataTable dataTable;
private Map<String,APIContainer> apiContainers;
private static class APIContainer extends DummyModContainer {
private List<ArtifactVersion> referredMods;
private ArtifactVersion ownerMod;
private ArtifactVersion ourVersion;
private String providedAPI;
private File source;
private String version;
private Set<String> currentReferents;
private Set<String> packages;
private boolean selfReferenced;
public APIContainer(String providedAPI, String apiVersion, File source, ArtifactVersion ownerMod)
{
this.providedAPI = providedAPI;
this.version = apiVersion;
this.ownerMod = ownerMod;
this.ourVersion = new DefaultArtifactVersion(providedAPI, apiVersion);
this.referredMods = Lists.newArrayList();
this.source = source;
this.currentReferents = Sets.newHashSet();
this.packages = Sets.newHashSet();
}
@Override
public File getSource()
{
return source;
}
@Override
public String getVersion()
{
return version;
}
@Override
public String getName()
{
return "API: "+providedAPI;
}
@Override
public String getModId()
{
return providedAPI;
}
@Override
public List<ArtifactVersion> getDependants()
{
return referredMods;
}
@Override
public List<ArtifactVersion> getDependencies()
{
return selfReferenced ? ImmutableList.<ArtifactVersion>of() : ImmutableList.of(ownerMod);
}
@Override
public ArtifactVersion getProcessedVersion()
{
return ourVersion;
}
public void validate(String providedAPI, String apiOwner, String apiVersion)
{
if (Loader.instance().getModClassLoader().containsSource(this.getSource())) {
FMLLog.bigWarning("The API {} from source {} is loaded from an incompatible classloader. THIS WILL NOT WORK!", providedAPI, this.getSource().getAbsolutePath());
}
// TODO Compare this annotation data to the one we first found. Maybe barf if there is inconsistency?
}
@Override
public String toString()
{
return "APIContainer{"+providedAPI+":"+version+"}";
}
public void addAPIReference(String embedded)
{
if (currentReferents.add(embedded))
{
referredMods.add(VersionParser.parseVersionReference(embedded));
}
}
public void addOwnedPackage(String apiPackage)
{
packages.add(apiPackage);
}
public void addAPIReferences(List<String> candidateIds)
{
for (String modId : candidateIds)
{
addAPIReference(modId);
}
}
void markSelfReferenced()
{
selfReferenced = true;
}
}
public void registerDataTableAndParseAPI(ASMDataTable dataTable)
{
this.dataTable = dataTable;
Set<ASMData> apiList = dataTable.getAll("net.minecraftforge.fml.common.API");
apiContainers = Maps.newHashMap();
for (ASMData data : apiList)
{
Map<String, Object> annotationInfo = data.getAnnotationInfo();
String apiPackage = data.getClassName().substring(0,data.getClassName().indexOf(".package-info"));
String providedAPI = (String) annotationInfo.get("provides");
String apiOwner = (String) annotationInfo.get("owner");
String apiVersion = (String) annotationInfo.get("apiVersion");
APIContainer container = apiContainers.get(providedAPI);
if (container == null)
{
container = new APIContainer(providedAPI, apiVersion, data.getCandidate().getModContainer(), VersionParser.parseVersionReference(apiOwner));
apiContainers.put(providedAPI, container);
}
else
{
container.validate(providedAPI, apiOwner, apiVersion);
}
container.addOwnedPackage(apiPackage);
for (ModContainer mc : data.getCandidate().getContainedMods())
{
String embeddedIn = mc.getModId();
if (container.currentReferents.contains(embeddedIn))
{
continue;
}
FMLLog.log.debug("Found API {} (owned by {} providing {}) embedded in {}",apiPackage, apiOwner, providedAPI, embeddedIn);
if (!embeddedIn.equals(apiOwner))
{
container.addAPIReference(embeddedIn);
}
}
}
for (APIContainer container : apiContainers.values())
{
for (String pkg : container.packages)
{
Set<ModCandidate> candidates = dataTable.getCandidatesFor(pkg);
for (ModCandidate candidate : candidates)
{
List<String> candidateIds = candidate.getContainedMods().stream().map(ModContainer::getModId).collect(Collectors.toCollection(ArrayList::new));
if (!candidateIds.contains(container.ownerMod.getLabel()) && !container.currentReferents.containsAll(candidateIds))
{
FMLLog.log.info("Found mod(s) {} containing declared API package {} (owned by {}) without associated API reference",candidateIds, pkg, container.ownerMod);
container.addAPIReferences(candidateIds);
}
}
}
if (apiContainers.containsKey(container.ownerMod.getLabel()))
{
ArtifactVersion owner = container.ownerMod;
do
{
APIContainer parent = apiContainers.get(owner.getLabel());
if (parent == container)
{
FMLLog.log.trace("APIContainer {} is it's own parent. skipping", owner);
container.markSelfReferenced();
break;
}
FMLLog.log.trace("Removing upstream parent {} from {}", parent.ownerMod.getLabel(), container);
container.currentReferents.remove(parent.ownerMod.getLabel());
container.referredMods.remove(parent.ownerMod);
owner = parent.ownerMod;
}
while (apiContainers.containsKey(owner.getLabel()));
}
FMLLog.log.debug("Creating API container dummy for API {}: owner: {}, dependents: {}", container.providedAPI, container.ownerMod, container.referredMods);
}
}
public void manageAPI(ModClassLoader modClassLoader, ModDiscoverer discoverer)
{
registerDataTableAndParseAPI(discoverer.getASMTable());
transformer = modClassLoader.addModAPITransformer(dataTable);
}
public void injectAPIModContainers(List<ModContainer> mods, Map<String, ModContainer> nameLookup)
{
mods.addAll(apiContainers.values());
nameLookup.putAll(apiContainers);
}
public void cleanupAPIContainers(List<ModContainer> mods)
{
mods.removeAll(apiContainers.values());
}
public boolean hasAPI(String modId)
{
return apiContainers.containsKey(modId);
}
public Iterable<? extends ModContainer> getAPIList()
{
return apiContainers.values();
}
}

View File

@@ -0,0 +1,191 @@
/*
* 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.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraft.launchwrapper.LaunchClassLoader;
import net.minecraftforge.fml.common.asm.transformers.ModAPITransformer;
import net.minecraftforge.fml.common.discovery.ASMDataTable;
import org.apache.logging.log4j.Level;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
/**
* A simple delegating class loader used to load mods into the system
*
*
* @author cpw
*
*/
public class ModClassLoader extends URLClassLoader
{
private static final List<String> STANDARD_LIBRARIES = ImmutableList.of("jinput.jar", "lwjgl.jar", "lwjgl_util.jar", "rt.jar");
private LaunchClassLoader mainClassLoader;
private List<File> sources;
public ModClassLoader(ClassLoader parent) {
super(new URL[0], null);
if (parent instanceof LaunchClassLoader)
{
this.mainClassLoader = (LaunchClassLoader)parent;
}
this.sources = Lists.newArrayList();
}
public void addFile(File modFile) throws MalformedURLException
{
URL url = modFile.toURI().toURL();
mainClassLoader.addURL(url);
this.sources.add(modFile);
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException
{
return mainClassLoader.loadClass(name);
}
public File[] getParentSources() {
try
{
List<File> files=new ArrayList<File>();
for(URL url : mainClassLoader.getSources())
{
URI uri = url.toURI();
if(uri.getScheme().equals("file"))
{
files.add(new File(uri));
}
}
return files.toArray(new File[]{});
}
catch (URISyntaxException e)
{
FMLLog.log.error("Unable to process our input to locate the minecraft code", e);
throw new LoaderException(e);
}
}
public List<String> getDefaultLibraries()
{
return STANDARD_LIBRARIES;
}
public boolean isDefaultLibrary(File file)
{
String home = System.getProperty("java.home"); // Nullcheck just in case some JVM decides to be stupid
if (home != null && file.getAbsolutePath().startsWith(home)) return true;
// Should really pull this from the json somehow, but we dont have that at runtime.
String name = file.getName();
if (!name.endsWith(".jar")) return false;
String[] prefixes =
{
"launchwrapper-",
"asm-all-",
"akka-actor_2.11-",
"config-",
"scala-",
"jopt-simple-",
"lzma-",
"realms-",
"httpclient-",
"httpcore-",
"vecmath-",
"trove4j-",
"icu4j-core-mojang-",
"codecjorbis-",
"codecwav-",
"libraryjavawound-",
"librarylwjglopenal-",
"soundsystem-",
"netty-all-",
"guava-",
"commons-lang3-",
"commons-compress-",
"commons-logging-",
"commons-io-",
"commons-codec-",
"jinput-",
"jutils-",
"gson-",
"authlib-",
"log4j-api-",
"log4j-core-",
"lwjgl-",
"lwjgl_util-",
"twitch-",
"jline-",
"jna-",
"platform-",
"oshi-core-",
"netty-",
"libraryjavasound-",
"fastutil-"
};
for (String s : prefixes)
{
if (name.startsWith(s)) return true;
}
return false;
}
public void clearNegativeCacheFor(Set<String> classList)
{
mainClassLoader.clearNegativeEntries(classList);
}
public ModAPITransformer addModAPITransformer(ASMDataTable dataTable)
{
mainClassLoader.registerTransformer("net.minecraftforge.fml.common.asm.transformers.ModAPITransformer");
List<IClassTransformer> transformers = mainClassLoader.getTransformers();
ModAPITransformer modAPI = (ModAPITransformer) transformers.get(transformers.size()-1);
modAPI.initTable(dataTable);
return modAPI;
}
List<URL> parentURLs = null;
public boolean containsSource(File source)
{
if (parentURLs == null) {
parentURLs = Arrays.asList(mainClassLoader.getURLs());
}
try
{
return parentURLs.contains(source.toURI().toURL());
} catch (MalformedURLException e)
{
// shouldn't happen
return false;
}
}
}

View File

@@ -0,0 +1,169 @@
/*
* 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.net.URL;
import java.security.cert.Certificate;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import net.minecraftforge.fml.common.versioning.VersionRange;
import com.google.common.collect.ImmutableMap;
import com.google.common.eventbus.EventBus;
import javax.annotation.Nullable;
/**
* The container that wraps around mods in the system.
* <p>
* The philosophy is that individual mod implementation technologies should not
* impact the actual loading and management of mod code. This interface provides
* a mechanism by which we can wrap actual mod code so that the loader and other
* facilities can treat mods at arms length.
* </p>
*
* @author cpw
*
*/
public interface ModContainer
{
public static enum Disableable {
YES, RESTART, NEVER, DEPENDENCIES;
}
/**
* The globally unique modid for this mod
*/
String getModId();
/**
* A human readable name
*/
String getName();
/**
* A human readable version identifier
*/
String getVersion();
/**
* The location on the file system which this mod came from
*/
File getSource();
/**
* The metadata for this mod
*/
ModMetadata getMetadata();
/**
* Attach this mod to it's metadata from the supplied metadata collection
*/
void bindMetadata(MetadataCollection mc);
/**
* Set the enabled/disabled state of this mod
*/
void setEnabledState(boolean enabled);
/**
* A list of the modids that this mod requires loaded prior to loading
*/
Set<ArtifactVersion> getRequirements();
/**
* A list of modids that should be loaded prior to this one. The special
* value <strong>*</strong> indicates to load <em>after</em> any other mod.
*/
List<ArtifactVersion> getDependencies();
/**
* A list of modids that should be loaded <em>after</em> this one. The
* special value <strong>*</strong> indicates to load <em>before</em> any
* other mod.
*/
List<ArtifactVersion> getDependants();
/**
* A representative string encapsulating the sorting preferences for this
* mod
*/
String getSortingRules();
/**
* Register the event bus for the mod and the controller for error handling
* Returns if this bus was successfully registered - disabled mods and other
* mods that don't need real events should return false and avoid further
* processing
*
* @param bus
* @param controller
*/
boolean registerBus(EventBus bus, LoadController controller);
/**
* Does this mod match the supplied mod
*
* @param mod
*/
boolean matches(Object mod);
/**
* Get the actual mod object
*/
Object getMod();
ArtifactVersion getProcessedVersion();
boolean isImmutable();
String getDisplayVersion();
VersionRange acceptableMinecraftVersionRange();
@Nullable
Certificate getSigningCertificate();
public static final Map<String,String> EMPTY_PROPERTIES = ImmutableMap.of();
Map<String,String> getCustomModProperties();
public Class<?> getCustomResourcePackClass();
Map<String, String> getSharedModDescriptor();
Disableable canBeDisabled();
String getGuiClassName();
List<String> getOwnedPackages();
boolean shouldLoadInEnvironment();
URL getUpdateUrl();
void setClassVersion(int classVersion);
int getClassVersion();
}

View File

@@ -0,0 +1,89 @@
/*
* 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.reflect.Constructor;
import java.util.Map;
import java.util.regex.Pattern;
import net.minecraftforge.fml.common.discovery.ModCandidate;
import net.minecraftforge.fml.common.discovery.asm.ASMModParser;
import net.minecraftforge.fml.common.discovery.asm.ModAnnotation;
import org.objectweb.asm.Type;
import com.google.common.collect.Maps;
import javax.annotation.Nullable;
public class ModContainerFactory
{
public static Map<Type, Constructor<? extends ModContainer>> modTypes = Maps.newHashMap();
private static ModContainerFactory INSTANCE = new ModContainerFactory();
private ModContainerFactory() {
// We always know about Mod type
registerContainerType(Type.getType(Mod.class), FMLModContainer.class);
}
public static ModContainerFactory instance() {
return INSTANCE;
}
public void registerContainerType(Type type, Class<? extends ModContainer> container)
{
try
{
Constructor<? extends ModContainer> constructor = container.getConstructor(new Class<?>[] { String.class, ModCandidate.class, Map.class });
modTypes.put(type, constructor);
}
catch (Exception e)
{
throw new RuntimeException("Critical error : cannot register mod container type " + container.getName() + ", it has an invalid constructor", e);
}
}
@Nullable
public ModContainer build(ASMModParser modParser, File modSource, ModCandidate container)
{
String className = modParser.getASMType().getClassName();
for (ModAnnotation ann : modParser.getAnnotations())
{
if (modTypes.containsKey(ann.getASMType()))
{
FMLLog.log.debug("Identified a mod of type {} ({}) - loading", ann.getASMType(), className);
try {
ModContainer ret = modTypes.get(ann.getASMType()).newInstance(className, container, ann.getValues());
if (!ret.shouldLoadInEnvironment())
{
FMLLog.log.debug("Skipping mod {}, container opted to not load.", className);
return null;
}
return ret;
} catch (Exception e) {
FMLLog.log.error("Unable to construct {} container", ann.getASMType().getClassName(), e);
return null;
}
}
}
return null;
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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.util.List;
import java.util.Set;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gson.annotations.SerializedName;
/**
* @author cpw
*
*/
public class ModMetadata
{
@SerializedName("modid")
public String modId;
public String name;
public String description = "";
public String url = "";
@Deprecated //Never really used for anything and format is undefined. See updateJSON for replacement.
public String updateUrl = "";
/**
* URL to update json file. Format is defined here: https://gist.github.com/LexManos/7aacb9aa991330523884
*/
public String updateJSON = "";
public String logoFile = "";
public String version = "";
public List<String> authorList = Lists.newArrayList();
public String credits = "";
public String parent = "";
public String[] screenshots;
// this field is not for use in the json
public transient ModContainer parentMod;
// this field is not for use in the json
public transient List<ModContainer> childMods = Lists.newArrayList();
public boolean useDependencyInformation;
public Set<ArtifactVersion> requiredMods = Sets.newHashSet();
public List<ArtifactVersion> dependencies = Lists.newArrayList();
public List<ArtifactVersion> dependants = Lists.newArrayList();
// this field is not for use in the json
public transient boolean autogenerated;
public ModMetadata()
{
}
public String getChildModCountString()
{
return String.format("%d child mod%s", childMods.size(), childMods.size() != 1 ? "s" : "");
}
public String getAuthorList()
{
return Joiner.on(", ").join(authorList);
}
public String getChildModList()
{
return Joiner.on(", ").join(childMods.stream().map(ModContainer::getName).iterator());
}
public String printableSortingRules()
{
return "";
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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.util.List;
import net.minecraft.client.gui.GuiScreen;
import net.minecraftforge.fml.client.GuiMultipleModsErrored;
import net.minecraftforge.fml.client.IDisplayableError;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import net.minecraftforge.fml.common.EnhancedRuntimeException.WrappedPrintStream;
public class MultipleModsErrored extends EnhancedRuntimeException implements IDisplayableError
{
public final List<WrongMinecraftVersionException> wrongMinecraftExceptions;
public final List<MissingModsException> missingModsExceptions;
public MultipleModsErrored(List<WrongMinecraftVersionException> wrongMinecraftExceptions, List<MissingModsException> missingModsExceptions)
{
this.wrongMinecraftExceptions = wrongMinecraftExceptions;
this.missingModsExceptions = missingModsExceptions;
}
@Override
@SideOnly(Side.CLIENT)
public GuiScreen createGui()
{
return new GuiMultipleModsErrored(this);
}
@Override
protected void printStackTrace(WrappedPrintStream stream)
{
for (WrongMinecraftVersionException wrongMinecraftVersionException : this.wrongMinecraftExceptions)
{
wrongMinecraftVersionException.printStackTrace(stream);
}
for (MissingModsException missingModsException : this.missingModsExceptions)
{
missingModsException.printStackTrace(stream);
}
}
}

View File

@@ -0,0 +1,112 @@
/*
* 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.util.Arrays;
import net.minecraftforge.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper;
import net.minecraftforge.fml.relauncher.ReflectionHelper;
import net.minecraftforge.fml.relauncher.ReflectionHelper.UnableToAccessFieldException;
import net.minecraftforge.fml.relauncher.ReflectionHelper.UnableToFindFieldException;
import org.apache.logging.log4j.Level;
/**
* Some reflection helper code.
*
* @author cpw
*
*/
public class ObfuscationReflectionHelper
{
public static <T, E> T getPrivateValue(Class<? super E> classToAccess, E instance, int fieldIndex)
{
try
{
return ReflectionHelper.getPrivateValue(classToAccess, instance, fieldIndex);
}
catch (UnableToAccessFieldException e)
{
FMLLog.log.error("There was a problem getting field index {} from {}", classToAccess.getName(), e);
throw e;
}
}
public static String[] remapFieldNames(String className, String... fieldNames)
{
String internalClassName = FMLDeobfuscatingRemapper.INSTANCE.unmap(className.replace('.', '/'));
String[] mappedNames = new String[fieldNames.length];
int i = 0;
for (String fName : fieldNames)
{
mappedNames[i++] = FMLDeobfuscatingRemapper.INSTANCE.mapFieldName(internalClassName, fName, null);
}
return mappedNames;
}
public static <T, E> T getPrivateValue(Class<? super E> classToAccess, E instance, String... fieldNames)
{
try
{
return ReflectionHelper.getPrivateValue(classToAccess, instance, remapFieldNames(classToAccess.getName(),fieldNames));
}
catch (UnableToFindFieldException e)
{
FMLLog.log.error("Unable to locate any field {} on type {}", Arrays.toString(fieldNames), classToAccess.getName(), e);
throw e;
}
catch (UnableToAccessFieldException e)
{
FMLLog.log.error("Unable to access any field {} on type {}", classToAccess.getName(), e);
throw e;
}
}
public static <T, E> void setPrivateValue(Class<? super T> classToAccess, T instance, E value, int fieldIndex)
{
try
{
ReflectionHelper.setPrivateValue(classToAccess, instance, value, fieldIndex);
}
catch (UnableToAccessFieldException e)
{
FMLLog.log.error("There was a problem setting field index {} on type {}", classToAccess.getName(), e);
throw e;
}
}
public static <T, E> void setPrivateValue(Class<? super T> classToAccess, T instance, E value, String... fieldNames)
{
try
{
ReflectionHelper.setPrivateValue(classToAccess, instance, value, remapFieldNames(classToAccess.getName(), fieldNames));
}
catch (UnableToFindFieldException e)
{
FMLLog.log.error("Unable to locate any field {} on type {}", classToAccess.getName(), e);
throw e;
}
catch (UnableToAccessFieldException e)
{
FMLLog.log.error("Unable to set any field {} on type {}", classToAccess.getName(), e);
throw e;
}
}
}

View File

@@ -0,0 +1,97 @@
/*
* 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.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Classes annotated with this will have the named interface or method removed from the runtime definition of the class
* if the modid specified is missing.
*
* @author cpw
*
*/
public final class Optional {
/**
* Not constructable
*/
private Optional() {}
/**
* Mark a list of interfaces as removable
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface InterfaceList {
/**
* Mark a list of interfaces for optional removal.
* @return
*/
Interface[] value();
}
/**
* Used to remove optional interfaces
* @author cpw
*
*/
@Repeatable(InterfaceList.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Interface {
/**
* The fully qualified name of the interface to be stripped
* @return the interface name
*/
String iface();
/**
* The modid that is required to be present for stripping NOT to occur
* @return the modid
*/
String modid();
/**
* Strip references to this interface in method declarations? (Useful to kill synthetic methods from scala f.e.)
*
* @return if references should be stripped
*/
boolean striprefs() default false;
}
/**
* Used to remove optional methods
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Method {
/**
* The modid that is required to be present for stripping NOT to occur
* @return the modid
*/
String modid();
}
}

View File

@@ -0,0 +1,160 @@
/*
* 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.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import com.google.common.base.Joiner;
/**
* Not a fully fleshed out API, may change in future MC versions.
* However feel free to use and suggest additions.
*/
public class ProgressManager
{
private static final List<ProgressBar> bars = new CopyOnWriteArrayList<ProgressBar>();
/**
* Not a fully fleshed out API, may change in future MC versions.
* However feel free to use and suggest additions.
*/
public static ProgressBar push(String title, int steps)
{
return push(title, steps, false);
}
/**
* Not a fully fleshed out API, may change in future MC versions.
* However feel free to use and suggest additions.
*/
public static ProgressBar push(String title, int steps, boolean timeEachStep)
{
ProgressBar bar = new ProgressBar(title, steps);
bars.add(bar);
if (timeEachStep)
{
bar.timeEachStep();
}
FMLCommonHandler.instance().processWindowMessages();
return bar;
}
public static boolean isDisplayVSyncForced() {
return FMLCommonHandler.instance().isDisplayVSyncForced();
}
/**
* Not a fully fleshed out API, may change in future MC versions.
* However feel free to use and suggest additions.
*/
public static void pop(ProgressBar bar)
{
if(bar.getSteps() != bar.getStep()) throw new IllegalStateException("can't pop unfinished ProgressBar " + bar.getTitle());
bars.remove(bar);
if (bar.getSteps() != 0)
{
long newTime = System.nanoTime();
if (bar.timeEachStep)
{
String timeString = String.format("%.3f", ((float) (newTime - bar.lastTime) / 1000000 / 1000));
FMLLog.log.debug("Bar Step: {} - {} took {}s", bar.getTitle(), bar.getMessage(), timeString);
}
String timeString = String.format("%.3f", ((float) (newTime - bar.startTime) / 1000000 / 1000));
if (bar.getSteps() == 1)
FMLLog.log.debug("Bar Finished: {} - {} took {}s", bar.getTitle(), bar.getMessage(), timeString);
else
FMLLog.log.debug("Bar Finished: {} took {}s", bar.getTitle(), timeString);
}
FMLCommonHandler.instance().processWindowMessages();
}
/*
* Internal use only.
*/
public static Iterator<ProgressBar> barIterator()
{
return bars.iterator();
}
/**
* Not a fully fleshed out API, may change in future MC versions.
* However feel free to use and suggest additions.
*/
public static class ProgressBar
{
private final String title;
private final int steps;
private volatile int step = 0;
private volatile String message = "";
private boolean timeEachStep = false;
private long startTime = System.nanoTime();
private long lastTime = startTime;
private ProgressBar(String title, int steps)
{
this.title = title;
this.steps = steps;
}
public void step(Class<?> classToName, String... extra)
{
step(ClassNameUtils.shortName(classToName)+Joiner.on(' ').join(extra));
}
public void step(String message)
{
if(step >= steps) throw new IllegalStateException("too much steps for ProgressBar " + title);
if (timeEachStep && step != 0)
{
long newTime = System.nanoTime();
FMLLog.log.debug(String.format("Bar Step: %s - %s took %.3fs", getTitle(), getMessage(), ((float)(newTime - lastTime) / 1000000 / 1000)));
lastTime = newTime;
}
step++;
this.message = FMLCommonHandler.instance().stripSpecialChars(message);
FMLCommonHandler.instance().processWindowMessages();
}
public String getTitle()
{
return title;
}
public int getSteps()
{
return steps;
}
public int getStep()
{
return step;
}
public String getMessage()
{
return message;
}
public void timeEachStep()
{
this.timeEachStep = true;
}
}
}

View File

@@ -0,0 +1,109 @@
/*
* 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.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Set;
import net.minecraftforge.fml.common.discovery.ASMDataTable;
import net.minecraftforge.fml.common.discovery.ASMDataTable.ASMData;
import net.minecraftforge.fml.relauncher.Side;
import org.apache.logging.log4j.Level;
import com.google.common.base.Strings;
import com.google.common.collect.SetMultimap;
/**
* @author cpw
*
*/
public class ProxyInjector
{
public static void inject(ModContainer mod, ASMDataTable data, Side side, ILanguageAdapter languageAdapter)
{
FMLLog.log.debug("Attempting to inject @SidedProxy classes into {}", mod.getModId());
SetMultimap<String, ASMData> modData = data.getAnnotationsFor(mod);
Set<ASMData> mods = modData.get(Mod.class.getName());
Set<ASMData> targets = modData.get(SidedProxy.class.getName());
ClassLoader mcl = Loader.instance().getModClassLoader();
for (ASMData targ : targets)
{
try
{
String amodid = (String)targ.getAnnotationInfo().get("modId");
if (Strings.isNullOrEmpty(amodid))
{
amodid = ASMDataTable.getOwnerModID(mods, targ);
if (Strings.isNullOrEmpty(amodid))
{
FMLLog.bigWarning("Could not determine owning mod for @SidedProxy on {} for mod {}", targ.getClassName(), mod.getModId());
continue;
}
}
if (!mod.getModId().equals(amodid))
{
FMLLog.log.debug("Skipping proxy injection for {}.{} since it is not for mod {}", targ.getClassName(), targ.getObjectName(), mod.getModId());
continue;
}
Class<?> proxyTarget = Class.forName(targ.getClassName(), true, mcl);
Field target = proxyTarget.getDeclaredField(targ.getObjectName());
if (target == null)
{
// Impossible?
FMLLog.log.fatal("Attempted to load a proxy type into {}.{} but the field was not found", targ.getClassName(), targ.getObjectName());
throw new LoaderException(String.format("Attempted to load a proxy type into %s.%s but the field was not found", targ.getClassName(), targ.getObjectName()));
}
target.setAccessible(true);
SidedProxy annotation = target.getAnnotation(SidedProxy.class);
String targetType = side.isClient() ? annotation.clientSide() : annotation.serverSide();
if(targetType.equals(""))
{
targetType = targ.getClassName() + (side.isClient() ? "$ClientProxy" : "$ServerProxy");
}
Object proxy=Class.forName(targetType, true, mcl).newInstance();
if (languageAdapter.supportsStatics() && (target.getModifiers() & Modifier.STATIC) == 0 )
{
FMLLog.log.fatal("Attempted to load a proxy type {} into {}.{}, but the field is not static", targetType, targ.getClassName(), targ.getObjectName());
throw new LoaderException(String.format("Attempted to load a proxy type %s into %s.%s, but the field is not static", targetType, targ.getClassName(), targ.getObjectName()));
}
if (!target.getType().isAssignableFrom(proxy.getClass()))
{
FMLLog.log.fatal("Attempted to load a proxy type {} into {}.{}, but the types don't match", targetType, targ.getClassName(), targ.getObjectName());
throw new LoaderException(String.format("Attempted to load a proxy type %s into %s.%s, but the types don't match", targetType, targ.getClassName(), targ.getObjectName()));
}
languageAdapter.setProxy(target, proxyTarget, proxy);
}
catch (Exception e)
{
FMLLog.log.error("An error occurred trying to load a proxy into {}.{}", targ.getObjectName(), e);
throw new LoaderException(e);
}
}
// Allow language specific proxy injection.
languageAdapter.setInternalProxies(mod, side, mcl);
}
}

View File

@@ -0,0 +1,39 @@
/*
* 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.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* A method annotated with this on the {@link Mod} will be called whenever a local save is listed in
* the save games list.
*
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SaveInspectionHandler
{
}

View File

@@ -0,0 +1,76 @@
/*
* 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.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Sided proxies are loaded based on the specific environment they find themselves loaded into.
* They are used to ensure that client-specific code (such as GUIs) is only loaded into the game
* on the client side.
* It is applied to static fields of a class, anywhere in your mod code. FML will scan
* and load any classes with this annotation at mod construction time.
*
* <p>
* This example will load a CommonProxy on the server side, and a ClientProxy on the client side.
*
* <pre>{@code
* public class MySidedProxyHolder {
* {@literal @}SidedProxy(modId="MyModId",clientSide="mymod.ClientProxy", serverSide="mymod.CommonProxy")
* public static CommonProxy proxy;
* }
*
* public class CommonProxy {
* // Common or server stuff here that needs to be overridden on the client
* }
*
* public class ClientProxy extends CommonProxy {
* // Override common stuff with client specific stuff here
* }
* }
* </pre>
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SidedProxy
{
/**
* The full name of the client side class to load and populate.
* Defaults to the nested class named "ClientProxy" in the current class.
*/
String clientSide() default "";
/**
* The full name of the server side class to load and populate.
* Defaults to the nested class named "ServerProxy" in the current class.
*/
String serverSide() default "";
/**
* The name of a mod to load this proxy for. This is required if this annotation is not in the class with @Mod annotation.
* Or there is no other way to determine the mod this annotation belongs to. When in doubt, add this value.
*/
String modId() default "";
}

View File

@@ -0,0 +1,183 @@
/*
* 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.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import net.minecraft.server.MinecraftServer;
import javax.annotation.Nullable;
public class StartupQuery {
// internal class/functionality, do not use
public static boolean confirm(String text)
{
StartupQuery query = new StartupQuery(text, new AtomicBoolean());
query.execute();
return query.getResult();
}
public static void notify(String text)
{
StartupQuery query = new StartupQuery(text, null);
query.execute();
}
public static void abort()
{
MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance();
if (server != null) server.initiateShutdown();
aborted = true; // to abort loading and go back to the main menu
throw new AbortedException(); // to halt the server
}
public static void reset()
{
pending = null;
aborted = false;
}
public static boolean check()
{
if (pending != null)
{
try
{
FMLCommonHandler.instance().queryUser(pending);
}
catch (InterruptedException e)
{
FMLLog.log.warn("query interrupted");
abort();
}
pending = null;
}
return !aborted;
}
private static volatile StartupQuery pending;
private static volatile boolean aborted = false;
private StartupQuery(String text, @Nullable AtomicBoolean result)
{
this.text = text;
this.result = result;
}
@Nullable
public Boolean getResult()
{
return result == null ? null : result.get();
}
public void setResult(boolean result)
{
this.result.set(result);
}
public String getText()
{
return text;
}
public boolean isSynchronous()
{
return synchronous;
}
public void finish()
{
signal.countDown();
}
private void execute()
{
String prop = System.getProperty("fml.queryResult");
if (result != null && prop != null)
{
FMLLog.log.info("Using fml.queryResult {} to answer the following query:\n{}", prop, text);
if (prop.equalsIgnoreCase("confirm"))
{
setResult(true);
return;
}
else if (prop.equalsIgnoreCase("cancel"))
{
setResult(false);
return;
}
FMLLog.log.warn("Invalid value for fml.queryResult: {}, expected confirm or cancel", prop);
}
synchronous = false;
pending = this; // let the other thread start the query
// from the integrated server thread: the client will eventually check pending and execute the query
// from the client thread: synchronous execution
// dedicated server: command handling in mc is synchronous, execute the server-side query directly
if (FMLCommonHandler.instance().getSide().isServer() ||
FMLCommonHandler.instance().getEffectiveSide().isClient())
{
synchronous = true;
check();
}
try
{
signal.await();
reset();
}
catch (InterruptedException e)
{
FMLLog.log.warn("query interrupted");
abort();
}
}
private String text;
@Nullable
private AtomicBoolean result;
private CountDownLatch signal = new CountDownLatch(1);
private volatile boolean synchronous;
/**
* Exception not being caught by the crash report generation logic.
*/
public static class AbortedException extends RuntimeException
{
private static final long serialVersionUID = -5933665223696833921L;
private AbortedException()
{
super();
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* 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.PrintStream;
import org.apache.logging.log4j.Logger;
/**
* PrintStream which redirects it's output to a given logger.
*
* @author Arkan
*/
public class TracingPrintStream extends PrintStream {
private Logger logger;
private int BASE_DEPTH = 3;
public TracingPrintStream(Logger logger, PrintStream original) {
super(original);
this.logger = logger;
}
@Override
public void println(Object o) {
logger.info("{}{}", getPrefix(), o);
}
@Override
public void println(String s) {
logger.info("{}{}", getPrefix(), s);
}
private String getPrefix() {
StackTraceElement[] elems = Thread.currentThread().getStackTrace();
StackTraceElement elem = elems[BASE_DEPTH]; // The caller is always at BASE_DEPTH, including this call.
if (elem.getClassName().startsWith("kotlin.io.")) {
elem = elems[BASE_DEPTH + 2]; // Kotlins IoPackage masks origins 2 deeper in the stack.
} else if (elem.getClassName().startsWith("java.lang.Throwable")) {
elem = elems[BASE_DEPTH + 4];
}
return "[" + elem.getClassName() + ":" + elem.getMethodName() + ":" + elem.getLineNumber() + "]: ";
}
}

View File

@@ -0,0 +1,32 @@
/*
* 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.util.Map;
import net.minecraft.nbt.*;
import net.minecraft.world.storage.*;
public interface WorldAccessContainer
{
NBTTagCompound getDataForWriting(SaveHandler handler, WorldInfo info);
void readData(SaveHandler handler, WorldInfo info, Map<String, NBTBase> propertyMap, NBTTagCompound tag);
}

View File

@@ -0,0 +1,59 @@
/*
* 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 net.minecraft.client.gui.GuiScreen;
import net.minecraftforge.fml.client.GuiWrongMinecraft;
import net.minecraftforge.fml.client.IDisplayableError;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import net.minecraftforge.fml.common.EnhancedRuntimeException.WrappedPrintStream;
public class WrongMinecraftVersionException extends EnhancedRuntimeException implements IDisplayableError
{
private static final long serialVersionUID = 1L;
public ModContainer mod;
private String mcVersion;
public WrongMinecraftVersionException(ModContainer mod, String mcver)
{
super(String.format("Wrong Minecraft version for %s", mod.getModId()));
this.mod = mod;
this.mcVersion = mcver;
}
@Override
protected void printStackTrace(WrappedPrintStream stream) {
stream.println("Wrong Minecraft Versions!");
stream.println("Mod: " + mod.getModId());
stream.println("Location: " + mod.getSource().toString());
stream.println("Expected: " + mod.acceptableMinecraftVersionRange().toString());
stream.println("Current: " + mcVersion);
stream.println("");
}
@Override
@SideOnly(Side.CLIENT)
public GuiScreen createGui()
{
return new GuiWrongMinecraft(this);
}
}

View File

@@ -0,0 +1,111 @@
/*
* 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.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.util.Deque;
import java.util.LinkedList;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.logging.log4j.Level;
import com.google.common.io.Files;
/**
* Copied from http://stackoverflow.com/questions/1399126/java-util-zip-recreating-directory-structure
* because the code looked very tidy and neat. Thanks, McDowell!
*
* @author McDowell
*
*/
public class ZipperUtil {
public static void zip(File directory, File zipfile) throws IOException
{
URI base = directory.toURI();
Deque<File> queue = new LinkedList<File>();
queue.push(directory);
OutputStream out = new FileOutputStream(zipfile);
Closeable res = null;
try
{
ZipOutputStream zout = new ZipOutputStream(out);
res = zout;
while (!queue.isEmpty())
{
directory = queue.pop();
for (File kid : directory.listFiles())
{
String name = base.relativize(kid.toURI()).getPath();
if (kid.isDirectory())
{
queue.push(kid);
name = name.endsWith("/") ? name : name + "/";
zout.putNextEntry(new ZipEntry(name));
} else
{
zout.putNextEntry(new ZipEntry(name));
Files.copy(kid, zout);
zout.closeEntry();
}
}
}
} finally
{
res.close();
}
}
public static void backupWorld() throws IOException
{
String dirName = FMLCommonHandler.instance().getMinecraftServerInstance().getFolderName();
backupWorld(dirName);
}
@Deprecated
public static void backupWorld(String dirName, String saveName) throws IOException
{
backupWorld(dirName);
}
public static void backupWorld(String dirName) throws IOException
{
File dstFolder = FMLCommonHandler.instance().getSavesDirectory();
File zip = new File(dstFolder, String.format("%s-%2$tY%2$tm%2$td-%2$tH%2$tM%2$tS.zip", dirName, System.currentTimeMillis()));
try
{
ZipperUtil.zip(new File(dstFolder, dirName), zip);
}
catch (IOException e)
{
FMLLog.log.warn("World backup failed.", e);
throw e;
}
FMLLog.log.info("World backup created at {}.", zip.getCanonicalPath());
}
}

View File

@@ -0,0 +1,284 @@
/*
* 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.asm;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.security.Permission;
import java.util.Map;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraft.launchwrapper.LaunchClassLoader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Maps;
import javax.annotation.Nullable;
public class ASMTransformerWrapper
{
private static final Map<String, String> wrapperModMap = Maps.newHashMap();
private static final Map<String, String> wrapperParentMap = Maps.newHashMap();
private static final LoadingCache<String, byte[]> wrapperCache = CacheBuilder.newBuilder()
.maximumSize(30)
.weakValues()
.build(new CacheLoader<String, byte[]>()
{
@Override
public byte[] load(String file) throws Exception
{
return makeWrapper(file);
}
});
private static final URL asmGenRoot;
private static boolean injected = false;
static
{
try
{
asmGenRoot = new URL("asmgen", null, -1, "/", new ASMGenHandler());
}
catch(MalformedURLException e)
{
throw new RuntimeException(e);
}
}
private static class ASMGenHandler extends URLStreamHandler
{
@Override
@Nullable
protected URLConnection openConnection(URL url) throws IOException
{
String file = url.getFile();
if(file.equals("/"))
{
return new URLConnection(url)
{
@Override
public void connect() throws IOException
{
throw new UnsupportedOperationException();
}
};
}
if(!file.startsWith("/")) throw new RuntimeException("Malformed URL: " + url);
file = file.substring(1);
if(wrapperModMap.containsKey(file))
{
return new ASMGenConnection(url, file);
}
return null;
}
}
private static class ASMGenConnection extends URLConnection
{
private final String file;
protected ASMGenConnection(URL url, String file)
{
super(url);
this.file = file;
}
@Override
public void connect() throws IOException
{
throw new UnsupportedOperationException();
}
@Override
public InputStream getInputStream()
{
return new ByteArrayInputStream(wrapperCache.getUnchecked(file));
}
@Override
@Nullable
public Permission getPermission()
{
return null;
}
}
public static String getTransformerWrapper(LaunchClassLoader launchLoader, String parentClass, String coreMod)
{
if(!injected)
{
injected = true;
launchLoader.addURL(asmGenRoot);
}
String name = getWrapperName(parentClass);
String fileName = name.replace('.', '/') + ".class";
wrapperModMap.put(fileName, coreMod);
wrapperParentMap.put(fileName, parentClass);
return name;
}
private static byte[] makeWrapper(String fileName)
{
if(!wrapperModMap.containsKey(fileName) || !wrapperParentMap.containsKey(fileName) || !fileName.endsWith(".class"))
{
throw new IllegalArgumentException("makeWrapper called with strange argument: " + fileName);
}
String name = fileName.substring(0, fileName.length() - ".class".length());
try
{
Type wrapper = Type.getType(TransformerWrapper.class);
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
writer.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, name, null, wrapper.getInternalName(), null);
Method m = Method.getMethod("void <init> ()");
GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC, m, null, null, writer);
mg.loadThis();
mg.invokeConstructor(wrapper, m);
mg.returnValue();
mg.endMethod();
m = Method.getMethod("java.lang.String getParentClass ()");
mg = new GeneratorAdapter(Opcodes.ACC_PROTECTED, m, null, null, writer);
mg.push(wrapperParentMap.get(fileName));
mg.returnValue();
mg.endMethod();
m = Method.getMethod("java.lang.String getCoreMod ()");
mg = new GeneratorAdapter(Opcodes.ACC_PROTECTED, m, null, null, writer);
mg.push(wrapperModMap.get(fileName));
mg.returnValue();
mg.endMethod();
writer.visitEnd();
return writer.toByteArray();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
private static String getWrapperName(String parentClass)
{
return "$wrapper." + parentClass;
}
@SuppressWarnings("unused")
private static class WrapperVisitor extends ClassVisitor
{
private final String name;
private final String parentClass;
public WrapperVisitor(ClassVisitor cv, String name, String parentClass)
{
super(Opcodes.ASM5, cv);
this.name = name.replace('.', '/');
this.parentClass = parentClass;
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
{
super.visit(version, access, this.name, signature, superName, interfaces);
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value)
{
if(name.equals("parentClass"))
{
return super.visitField(access, name, desc, signature, parentClass);
}
return super.visitField(access, name, desc, signature, value);
}
}
public static abstract class TransformerWrapper implements IClassTransformer
{
private final IClassTransformer parent;
public TransformerWrapper()
{
try
{
this.parent = (IClassTransformer)this.getClass().getClassLoader().loadClass(getParentClass()).newInstance();
}
catch(Exception e)
{
throw new RuntimeException(e);
}
}
@Override
public byte[] transform(String name, String transformedName, byte[] basicClass)
{
try
{
return parent.transform(name, transformedName, basicClass);
}
catch(Throwable e)
{
throw new TransformerException("Exception in class transformer " + parent + " from coremod " + getCoreMod(), e);
}
}
@Override
public String toString()
{
return "TransformerWrapper(" + getParentClass() + ", " + getCoreMod() + ")";
}
protected abstract String getParentClass();
protected abstract String getCoreMod();
}
static class TransformerException extends RuntimeException
{
private static final long serialVersionUID = -6616232415696157218L;
public TransformerException(String message, Throwable cause)
{
super(message, cause);
}
}
}

View File

@@ -0,0 +1,190 @@
/*
* 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.asm;
import java.io.File;
import java.io.InputStream;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.CodeSource;
import java.security.cert.Certificate;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import net.minecraftforge.fml.common.FMLLog;
import org.apache.commons.io.IOUtils;
import net.minecraft.launchwrapper.LaunchClassLoader;
import net.minecraftforge.fml.common.CertificateHelper;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper;
import net.minecraftforge.fml.common.patcher.ClassPatchManager;
import net.minecraftforge.fml.relauncher.FMLLaunchHandler;
import net.minecraftforge.fml.relauncher.IFMLCallHook;
import net.minecraftforge.fml.relauncher.Side;
import com.google.common.io.ByteStreams;
public class FMLSanityChecker implements IFMLCallHook
{
private static final String FMLFINGERPRINT = "51:0A:FB:4C:AF:A4:A0:F2:F5:CF:C5:0E:B4:CC:3C:30:24:4A:E3:8E".toLowerCase().replace(":", "");
private static final String FORGEFINGERPRINT = "E3:C3:D5:0C:7C:98:6D:F7:4C:64:5C:0A:C5:46:39:74:1C:90:A5:57".toLowerCase().replace(":", "");
private static final String MCFINGERPRINT = "CD:99:95:96:56:F7:53:DC:28:D8:63:B4:67:69:F7:F8:FB:AE:FC:FC".toLowerCase().replace(":", "");
private LaunchClassLoader cl;
private boolean liveEnv;
public static File fmlLocation;
@Override
public Void call() throws Exception
{
CodeSource codeSource = getClass().getProtectionDomain().getCodeSource();
boolean goodFML = false;
boolean fmlIsJar = false;
if (codeSource.getLocation().getProtocol().equals("jar"))
{
fmlIsJar = true;
Certificate[] certificates = codeSource.getCertificates();
if (certificates!=null)
{
for (Certificate cert : certificates)
{
String fingerprint = CertificateHelper.getFingerprint(cert);
if (fingerprint.equals(FMLFINGERPRINT))
{
FMLLog.log.info("Found valid fingerprint for FML. Certificate fingerprint {}", fingerprint);
goodFML = true;
}
else if (fingerprint.equals(FORGEFINGERPRINT))
{
FMLLog.log.info("Found valid fingerprint for Minecraft Forge. Certificate fingerprint {}", fingerprint);
goodFML = true;
}
else
{
FMLLog.log.error("Found invalid fingerprint for FML: {}", fingerprint);
}
}
}
}
else
{
goodFML = true;
}
// Server is not signed, so assume it's good - a deobf env is dev time so it's good too
boolean goodMC = FMLLaunchHandler.side() == Side.SERVER || !liveEnv;
int certCount = 0;
try
{
Class<?> cbr = Class.forName("net.minecraft.client.ClientBrandRetriever",false, cl);
codeSource = cbr.getProtectionDomain().getCodeSource();
}
catch (Exception e)
{
// Probably a development environment, or the server (the server is not signed)
goodMC = true;
}
JarFile mcJarFile = null;
if (fmlIsJar && !goodMC && codeSource.getLocation().getProtocol().equals("jar"))
{
try
{
String mcPath = codeSource.getLocation().getPath().substring(5);
mcPath = mcPath.substring(0, mcPath.lastIndexOf('!'));
mcPath = URLDecoder.decode(mcPath, StandardCharsets.UTF_8.name());
mcJarFile = new JarFile(mcPath,true);
mcJarFile.getManifest();
JarEntry cbrEntry = mcJarFile.getJarEntry("net/minecraft/client/ClientBrandRetriever.class");
InputStream mcJarFileInputStream = mcJarFile.getInputStream(cbrEntry);
try
{
ByteStreams.toByteArray(mcJarFileInputStream);
}
finally
{
IOUtils.closeQuietly(mcJarFileInputStream);
}
Certificate[] certificates = cbrEntry.getCertificates();
certCount = certificates != null ? certificates.length : 0;
if (certificates!=null)
{
for (Certificate cert : certificates)
{
String fingerprint = CertificateHelper.getFingerprint(cert);
if (fingerprint.equals(MCFINGERPRINT))
{
FMLLog.log.info("Found valid fingerprint for Minecraft. Certificate fingerprint {}", fingerprint);
goodMC = true;
}
}
}
}
catch (Throwable e)
{
FMLLog.log.error("A critical error occurred trying to read the minecraft jar file", e);
}
finally
{
IOUtils.closeQuietly(mcJarFile);
}
}
else
{
goodMC = true;
}
if (!goodMC)
{
FMLLog.log.error("The minecraft jar {} appears to be corrupt! There has been CRITICAL TAMPERING WITH MINECRAFT, it is highly unlikely minecraft will work! STOP NOW, get a clean copy and try again!", codeSource.getLocation().getFile());
if (!Boolean.parseBoolean(System.getProperty("fml.ignoreInvalidMinecraftCertificates","false")))
{
FMLLog.log.error("For your safety, FML will not launch minecraft. You will need to fetch a clean version of the minecraft jar file");
FMLLog.log.error("Technical information: The class net.minecraft.client.ClientBrandRetriever should have been associated with the minecraft jar file, " +
"and should have returned us a valid, intact minecraft jar location. This did not work. Either you have modified the minecraft jar file (if so " +
"run the forge installer again), or you are using a base editing jar that is changing this class (and likely others too). If you REALLY " +
"want to run minecraft in this configuration, add the flag -Dfml.ignoreInvalidMinecraftCertificates=true to the 'JVM settings' in your launcher profile.");
FMLCommonHandler.instance().exitJava(1, false);
}
else
{
FMLLog.log.error("FML has been ordered to ignore the invalid or missing minecraft certificate. This is very likely to cause a problem!");
FMLLog.log.error("Technical information: ClientBrandRetriever was at {}, there were {} certificates for it", codeSource.getLocation(), certCount);
}
}
if (!goodFML)
{
FMLLog.log.error("FML appears to be missing any signature data. This is not a good thing");
}
return null;
}
@Override
public void injectData(Map<String, Object> data)
{
liveEnv = (Boolean)data.get("runtimeDeobfuscationEnabled");
cl = (LaunchClassLoader) data.get("classLoader");
File mcDir = (File)data.get("mcLocation");
fmlLocation = (File)data.get("coremodLocation");
ClassPatchManager.INSTANCE.setup(FMLLaunchHandler.side());
FMLDeobfuscatingRemapper.INSTANCE.setup(mcDir, cl, (String) data.get("deobfuscationFileName"));
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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.asm;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Used to force certain classes to reobfuscate
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface ReobfuscationMarker {
}

View File

@@ -0,0 +1,485 @@
/*
* 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.asm.transformers;
import net.minecraftforge.fml.common.FMLLog;
import static org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_PROTECTED;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import net.minecraft.launchwrapper.IClassTransformer;
import org.apache.commons.io.IOUtils;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import com.google.common.base.Splitter;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.io.CharSource;
import com.google.common.io.LineProcessor;
import com.google.common.io.Resources;
public class AccessTransformer implements IClassTransformer
{
private static final boolean DEBUG = Boolean.parseBoolean(System.getProperty("fml.debugAccessTransformer", "false"));
class Modifier
{
public String name = "";
public String desc = "";
public int oldAccess = 0;
public int newAccess = 0;
public int targetAccess = 0;
public boolean changeFinal = false;
public boolean markFinal = false;
protected boolean modifyClassVisibility;
private void setTargetAccess(String name)
{
if (name.startsWith("public")) targetAccess = ACC_PUBLIC;
else if (name.startsWith("private")) targetAccess = ACC_PRIVATE;
else if (name.startsWith("protected")) targetAccess = ACC_PROTECTED;
if (name.endsWith("-f"))
{
changeFinal = true;
markFinal = false;
}
else if (name.endsWith("+f"))
{
changeFinal = true;
markFinal = true;
}
}
}
private Multimap<String, Modifier> modifiers = ArrayListMultimap.create();
public AccessTransformer() throws IOException
{
this("forge_at.cfg");
}
protected AccessTransformer(String rulesFile) throws IOException
{
readMapFile(rulesFile);
}
AccessTransformer(Class<? extends AccessTransformer> dummyClazz)
{
// This is a noop
}
void readMapFile(String rulesFile) throws IOException
{
File file = new File(rulesFile);
URL rulesResource;
if (file.exists())
{
rulesResource = file.toURI().toURL();
}
else
{
rulesResource = Resources.getResource(rulesFile);
}
processATFile(Resources.asCharSource(rulesResource, StandardCharsets.UTF_8));
FMLLog.log.debug("Loaded {} rules from AccessTransformer config file {}", modifiers.size(), rulesFile);
}
protected void processATFile(CharSource rulesResource) throws IOException
{
rulesResource.readLines(new LineProcessor<Void>()
{
@Override
public Void getResult()
{
return null;
}
@Override
public boolean processLine(String input) throws IOException
{
String line = Iterables.getFirst(Splitter.on('#').limit(2).split(input), "").trim();
if (line.length()==0)
{
return true;
}
List<String> parts = Lists.newArrayList(Splitter.on(" ").trimResults().split(line));
if (parts.size()>3)
{
throw new RuntimeException("Invalid config file line "+ input);
}
Modifier m = new Modifier();
m.setTargetAccess(parts.get(0));
if (parts.size() == 2)
{
m.modifyClassVisibility = true;
}
else
{
String nameReference = parts.get(2);
int parenIdx = nameReference.indexOf('(');
if (parenIdx>0)
{
m.desc = nameReference.substring(parenIdx);
m.name = nameReference.substring(0,parenIdx);
}
else
{
m.name = nameReference;
}
}
String className = parts.get(1).replace('/', '.');
modifiers.put(className, m);
if (DEBUG) FMLLog.log.debug("AT RULE: {} {} {} (type {})", toBinary(m.targetAccess), m.name, m.desc, className);
return true;
}
});
}
@Override
public byte[] transform(String name, String transformedName, byte[] bytes)
{
if (bytes == null) { return null; }
if (!modifiers.containsKey(transformedName)) { return bytes; }
if (DEBUG)
{
FMLLog.log.debug("Considering all methods and fields on {} ({})", transformedName, name);
}
ClassNode classNode = new ClassNode();
ClassReader classReader = new ClassReader(bytes);
classReader.accept(classNode, 0);
Collection<Modifier> mods = modifiers.get(transformedName);
for (Modifier m : mods)
{
if (m.modifyClassVisibility)
{
classNode.access = getFixedAccess(classNode.access, m);
if (DEBUG)
{
FMLLog.log.debug("Class: {} {} -> {}", name, toBinary(m.oldAccess), toBinary(m.newAccess));
}
continue;
}
if (m.desc.isEmpty())
{
for (FieldNode n : classNode.fields)
{
if (n.name.equals(m.name) || m.name.equals("*"))
{
n.access = getFixedAccess(n.access, m);
if (DEBUG)
{
FMLLog.log.debug("Field: {}.{} {} -> {}", name, n.name, toBinary(m.oldAccess), toBinary(m.newAccess));
}
if (!m.name.equals("*"))
{
break;
}
}
}
}
else
{
List<MethodNode> nowOverrideable = Lists.newArrayList();
for (MethodNode n : classNode.methods)
{
if ((n.name.equals(m.name) && n.desc.equals(m.desc)) || m.name.equals("*"))
{
n.access = getFixedAccess(n.access, m);
// constructors always use INVOKESPECIAL
if (!n.name.equals("<init>"))
{
// if we changed from private to something else we need to replace all INVOKESPECIAL calls to this method with INVOKEVIRTUAL
// so that overridden methods will be called. Only need to scan this class, because obviously the method was private.
boolean wasPrivate = (m.oldAccess & ACC_PRIVATE) == ACC_PRIVATE;
boolean isNowPrivate = (m.newAccess & ACC_PRIVATE) == ACC_PRIVATE;
if (wasPrivate && !isNowPrivate)
{
nowOverrideable.add(n);
}
}
if (DEBUG)
{
FMLLog.log.debug("Method: {}.{}{} {} -> {}", name, n.name, n.desc, toBinary(m.oldAccess), toBinary(m.newAccess));
}
if (!m.name.equals("*"))
{
break;
}
}
}
replaceInvokeSpecial(classNode, nowOverrideable);
}
}
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
classNode.accept(writer);
return writer.toByteArray();
}
private void replaceInvokeSpecial(ClassNode clazz, List<MethodNode> toReplace)
{
for (MethodNode method : clazz.methods)
{
for (Iterator<AbstractInsnNode> it = method.instructions.iterator(); it.hasNext();)
{
AbstractInsnNode insn = it.next();
if (insn.getOpcode() == INVOKESPECIAL)
{
MethodInsnNode mInsn = (MethodInsnNode) insn;
for (MethodNode n : toReplace)
{
if (n.name.equals(mInsn.name) && n.desc.equals(mInsn.desc))
{
mInsn.setOpcode(INVOKEVIRTUAL);
break;
}
}
}
}
}
}
private String toBinary(int num)
{
return String.format("%16s", Integer.toBinaryString(num)).replace(' ', '0');
}
private int getFixedAccess(int access, Modifier target)
{
target.oldAccess = access;
int t = target.targetAccess;
int ret = (access & ~7);
switch (access & 7)
{
case ACC_PRIVATE:
ret |= t;
break;
case 0: // default
ret |= (t != ACC_PRIVATE ? t : 0 /* default */);
break;
case ACC_PROTECTED:
ret |= (t != ACC_PRIVATE && t != 0 /* default */? t : ACC_PROTECTED);
break;
case ACC_PUBLIC:
ret |= (t != ACC_PRIVATE && t != 0 /* default */&& t != ACC_PROTECTED ? t : ACC_PUBLIC);
break;
default:
throw new RuntimeException("The fuck?");
}
// Clear the "final" marker on fields only if specified in control field
if (target.changeFinal)
{
if (target.markFinal)
{
ret |= ACC_FINAL;
}
else
{
ret &= ~ACC_FINAL;
}
}
target.newAccess = ret;
return ret;
}
public static void main(String[] args)
{
if (args.length < 2)
{
System.out.println("Usage: AccessTransformer <JarPath> <MapFile> [MapFile2]... ");
System.exit(1);
}
boolean hasTransformer = false;
AccessTransformer[] trans = new AccessTransformer[args.length - 1];
for (int x = 1; x < args.length; x++)
{
try
{
trans[x - 1] = new AccessTransformer(args[x]);
hasTransformer = true;
}
catch (IOException e)
{
System.out.println("Could not read Transformer Map: " + args[x]);
e.printStackTrace();
}
}
if (!hasTransformer)
{
System.out.println("Could not find a valid transformer to perform");
System.exit(1);
}
File orig = new File(args[0]);
File temp = new File(args[0] + ".ATBack");
if (!orig.exists() && !temp.exists())
{
System.out.println("Could not find target jar: " + orig);
System.exit(1);
}
if (!orig.renameTo(temp))
{
System.out.println("Could not rename file: " + orig + " -> " + temp);
System.exit(1);
}
try
{
processJar(temp, orig, trans);
}
catch (IOException e)
{
e.printStackTrace();
System.exit(1);
}
if (!temp.delete())
{
System.out.println("Could not delete temp file: " + temp);
}
}
private static void processJar(File inFile, File outFile, AccessTransformer[] transformers) throws IOException
{
ZipInputStream inJar = null;
ZipOutputStream outJar = null;
try
{
try
{
inJar = new ZipInputStream(new BufferedInputStream(new FileInputStream(inFile)));
}
catch (FileNotFoundException e)
{
throw new FileNotFoundException("Could not open input file: " + e.getMessage());
}
try
{
outJar = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(outFile)));
}
catch (FileNotFoundException e)
{
throw new FileNotFoundException("Could not open output file: " + e.getMessage());
}
ZipEntry entry;
while ((entry = inJar.getNextEntry()) != null)
{
if (entry.isDirectory())
{
outJar.putNextEntry(entry);
continue;
}
byte[] data = new byte[4096];
ByteArrayOutputStream entryBuffer = new ByteArrayOutputStream();
int len;
do
{
len = inJar.read(data);
if (len > 0)
{
entryBuffer.write(data, 0, len);
}
}
while (len != -1);
byte[] entryData = entryBuffer.toByteArray();
String entryName = entry.getName();
if (entryName.endsWith(".class") && !entryName.startsWith("."))
{
ClassNode cls = new ClassNode();
ClassReader rdr = new ClassReader(entryData);
rdr.accept(cls, 0);
String name = cls.name.replace('/', '.').replace('\\', '.');
for (AccessTransformer trans : transformers)
{
entryData = trans.transform(name, name, entryData);
}
}
ZipEntry newEntry = new ZipEntry(entryName);
outJar.putNextEntry(newEntry);
outJar.write(entryData);
}
}
finally
{
IOUtils.closeQuietly(outJar);
IOUtils.closeQuietly(inJar);
}
}
Multimap<String, Modifier> getModifiers()
{
return modifiers;
}
boolean isEmpty()
{
return modifiers.isEmpty();
}
}

View File

@@ -0,0 +1,133 @@
/*
* 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.asm.transformers;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraftforge.fml.common.FMLLog;
import org.apache.commons.lang3.JavaVersion;
import org.apache.commons.lang3.SystemUtils;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;
import com.google.common.collect.ImmutableSet;
public class BlamingTransformer implements IClassTransformer
{
private static final Map<String, String> classMap = new HashMap<String, String>();
private static final Set<String> naughtyMods = new HashSet<String>();
private static final Set<String> naughtyClasses = new TreeSet<String>();
private static final Set<String> orphanNaughtyClasses = new HashSet<String>();
@Override
public byte[] transform(String name, String transformedName, byte[] bytes)
{
if (bytes == null) { return null; }
ClassReader classReader = new ClassReader(bytes);
VersionVisitor visitor = new VersionVisitor();
classReader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG);
return bytes;
}
public static void blame(String modId, String cls)
{
naughtyClasses.add(cls);
naughtyMods.add(modId);
FMLLog.log.fatal("Unsupported class format in mod {}: class {}", modId, cls);
}
public static class VersionVisitor extends ClassVisitor
{
public VersionVisitor()
{
super(Opcodes.ASM5);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
{
if( (version == Opcodes.V1_8 && !SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_1_8)) ||
(version == Opcodes.V1_7 && !SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_1_7)) )
{
if(classMap.containsKey(name)) blame(classMap.get(name), name);
else orphanNaughtyClasses.add(name);
}
}
}
private static void checkPendingNaughty()
{
ImmutableSet.Builder<String> toRemove = ImmutableSet.builder();
for(String cls : orphanNaughtyClasses)
{
if(classMap.containsKey(cls))
{
String modId = classMap.get(cls);
blame(modId, cls);
toRemove.add(cls);
}
}
orphanNaughtyClasses.removeAll(toRemove.build());
}
public static void addClasses(String modId, Set<String> classList)
{
for(String cls : classList)
{
classMap.put(cls, modId);
}
checkPendingNaughty();
}
public static void onCrash(StringBuilder builder)
{
checkPendingNaughty();
if(!naughtyClasses.isEmpty())
{
builder.append("\n*** ATTENTION: detected classes with unsupported format ***\n");
builder.append("*** DO NOT SUBMIT THIS CRASH REPORT TO FORGE ***\n\n");
if(!naughtyMods.isEmpty())
{
builder.append("Contact authors of the following mods: \n");
for(String modId : naughtyMods)
{
builder.append(" ").append(modId).append("\n");
}
}
if(!orphanNaughtyClasses.isEmpty())
{
builder.append("Unidentified unsupported classes: \n");
for(String cls : orphanNaughtyClasses)
{
builder.append(" ").append(cls).append("\n");
}
}
builder.append('\n');
}
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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.asm.transformers;
import net.minecraft.launchwrapper.IClassNameTransformer;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraftforge.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper;
import net.minecraftforge.fml.common.asm.transformers.deobf.FMLRemappingAdapter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.commons.RemappingClassAdapter;
public class DeobfuscationTransformer implements IClassTransformer, IClassNameTransformer {
private static final boolean RECALC_FRAMES = Boolean.parseBoolean(System.getProperty("FORGE_FORCE_FRAME_RECALC", "false"));
private static final int WRITER_FLAGS = ClassWriter.COMPUTE_MAXS | (RECALC_FRAMES ? ClassWriter.COMPUTE_FRAMES : 0);
private static final int READER_FLAGS = RECALC_FRAMES ? ClassReader.SKIP_FRAMES : ClassReader.EXPAND_FRAMES;
// COMPUTE_FRAMES causes classes to be loaded, which could cause issues if the classes do not exist.
// However in testing this has not happened. {As we run post SideTransformer}
// If reported we need to add a custom implementation of ClassWriter.getCommonSuperClass
// that does not cause class loading.
@Override
public byte[] transform(String name, String transformedName, byte[] bytes)
{
if (bytes == null)
{
return null;
}
ClassReader classReader = new ClassReader(bytes);
ClassWriter classWriter = new ClassWriter(WRITER_FLAGS);
RemappingClassAdapter remapAdapter = new FMLRemappingAdapter(classWriter);
classReader.accept(remapAdapter, READER_FLAGS);
return classWriter.toByteArray();
}
@Override
public String remapClassName(String name)
{
return FMLDeobfuscatingRemapper.INSTANCE.map(name.replace('.','/')).replace('/', '.');
}
@Override
public String unmapClassName(String name)
{
return FMLDeobfuscatingRemapper.INSTANCE.unmap(name.replace('.', '/')).replace('/','.');
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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.asm.transformers;
import java.lang.reflect.Modifier;
import java.util.List;
import net.minecraft.launchwrapper.IClassTransformer;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
public class EventSubscriberTransformer implements IClassTransformer
{
@Override
public byte[] transform(String name, String transformedName, byte[] basicClass)
{
if (basicClass == null) return null;
ClassNode classNode = new ClassNode();
new ClassReader(basicClass).accept(classNode, 0);
boolean isSubscriber = false;
for (MethodNode methodNode : classNode.methods)
{
List<AnnotationNode> anns = methodNode.visibleAnnotations;
if (anns != null && Iterables.any(anns, SubscribeEventPredicate.INSTANCE))
{
if (Modifier.isPrivate(methodNode.access))
{
String msg = "Cannot apply @SubscribeEvent to private method %s/%s%s";
throw new RuntimeException(String.format(msg, classNode.name, methodNode.name, methodNode.desc));
}
methodNode.access = toPublic(methodNode.access);
isSubscriber = true;
}
}
if (isSubscriber)
{
classNode.access = toPublic(classNode.access);
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
classNode.accept(writer);
return writer.toByteArray();
}
return basicClass;
}
private static int toPublic(int access)
{
return access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED) | Opcodes.ACC_PUBLIC;
}
private static class SubscribeEventPredicate implements Predicate<AnnotationNode>
{
static final SubscribeEventPredicate INSTANCE = new SubscribeEventPredicate();
@Override
public boolean apply(AnnotationNode input)
{
return input.desc.equals("Lnet/minecraftforge/fml/common/eventhandler/SubscribeEvent;");
}
}
}

Some files were not shown because too many files have changed in this diff Show More