base mod created
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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(){}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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){}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 "";
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 "";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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 "";
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() + "]: ";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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('/','.');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user