Commit e13e19ac4972f7bae1a25a94e104efa7b027527f

Authored by Mumfrey
2 parents 53881326 a4937cc6

Backport all 1.12+ changes to 1.11.2

Showing 25 changed files with 720 additions and 343 deletions
build.gradle
... ... @@ -60,8 +60,8 @@ archivesBaseName = "liteloader"
60 60 version = buildVersion + (project.isReleaseBuild ? '' : '-' + project.classifier)
61 61  
62 62 // Minimum version of Java required
63   -sourceCompatibility = '1.6'
64   -targetCompatibility = '1.6'
  63 +sourceCompatibility = '1.8'
  64 +targetCompatibility = '1.8'
65 65  
66 66 repositories {
67 67 mavenLocal()
... ... @@ -72,7 +72,7 @@ repositories {
72 72 }
73 73  
74 74 dependencies {
75   - compile('org.spongepowered:mixin:0.6.8-SNAPSHOT') {
  75 + compile('org.spongepowered:mixin:0.7.1-SNAPSHOT') {
76 76 exclude module: 'asm-commons'
77 77 exclude module: 'asm-tree'
78 78 exclude module: 'launchwrapper'
... ... @@ -89,11 +89,11 @@ minecraft {
89 89  
90 90 sourceSets {
91 91 main {
92   - refMap = "mixins.liteloader.core.refmap.json"
  92 + ext.refMap = "mixins.liteloader.core.refmap.json"
93 93 }
94 94 client {
95 95 compileClasspath += main.compileClasspath + main.output
96   - refMap = "mixins.liteloader.client.refmap.json"
  96 + ext.refMap = "mixins.liteloader.client.refmap.json"
97 97 }
98 98 debug {
99 99 compileClasspath += client.compileClasspath + client.output
... ... @@ -123,7 +123,7 @@ javadoc {
123 123 afterEvaluate {
124 124 logger.lifecycle '================================================='
125 125 logger.lifecycle ' LiteLoader'
126   - logger.lifecycle ' Copyright (C) 2011-2016 Adam Mummery-Smith'
  126 + logger.lifecycle ' Copyright (C) 2011-2017 Adam Mummery-Smith'
127 127 logger.lifecycle ' Running in {} mode', (project.isReleaseBuild ? "RELEASE" : "SNAPSHOT")
128 128 logger.lifecycle '================================================='
129 129  
... ...
gradle.properties
... ... @@ -7,4 +7,4 @@ organization=LiteLoader
7 7 buildType=SNAPSHOT
8 8 buildVersion=1.11.2
9 9 mcVersion=1.11.2
10   -mcMappings=snapshot_20161224
11 10 \ No newline at end of file
  11 +mcMappings=snapshot_20161224
... ...
src/client/java/com/mumfrey/liteloader/client/LiteLoaderEventBrokerClient.java
... ... @@ -9,24 +9,9 @@ import org.lwjgl.input.Mouse;
9 9 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
10 10 import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
11 11  
12   -import com.mumfrey.liteloader.ChatRenderListener;
13   -import com.mumfrey.liteloader.EntityRenderListener;
14   -import com.mumfrey.liteloader.FrameBufferListener;
15   -import com.mumfrey.liteloader.GameLoopListener;
16   -import com.mumfrey.liteloader.HUDRenderListener;
17   -import com.mumfrey.liteloader.InitCompleteListener;
18   -import com.mumfrey.liteloader.OutboundChatFilter;
19   -import com.mumfrey.liteloader.OutboundChatListener;
20   -import com.mumfrey.liteloader.PlayerClickListener;
  12 +import com.mumfrey.liteloader.*;
21 13 import com.mumfrey.liteloader.PlayerInteractionListener.MouseButton;
22   -import com.mumfrey.liteloader.PostRenderListener;
23   -import com.mumfrey.liteloader.PreRenderListener;
24   -import com.mumfrey.liteloader.RenderListener;
25   -import com.mumfrey.liteloader.ScreenshotListener;
26   -import com.mumfrey.liteloader.Tickable;
27   -import com.mumfrey.liteloader.ViewportListener;
28 14 import com.mumfrey.liteloader.client.overlays.IEntityRenderer;
29   -import com.mumfrey.liteloader.client.overlays.IMinecraft;
30 15 import com.mumfrey.liteloader.common.LoadingProgress;
31 16 import com.mumfrey.liteloader.core.InterfaceRegistrationDelegate;
32 17 import com.mumfrey.liteloader.core.LiteLoader;
... ... @@ -51,7 +36,6 @@ import net.minecraft.client.shader.Framebuffer;
51 36 import net.minecraft.entity.Entity;
52 37 import net.minecraft.network.play.client.CPacketChatMessage;
53 38 import net.minecraft.server.integrated.IntegratedServer;
54   -import net.minecraft.util.Timer;
55 39 import net.minecraft.util.text.ITextComponent;
56 40  
57 41 public class LiteLoaderEventBrokerClient extends LiteLoaderEventBroker<Minecraft, IntegratedServer> implements IResourceManagerReloadListener
... ... @@ -427,14 +411,9 @@ public class LiteLoaderEventBrokerClient extends LiteLoaderEventBroker&lt;Minecraft
427 411 /**
428 412 * Callback from the tick hook, ticks all tickable mods
429 413 */
430   - public void onTick()
  414 + public void onTick(boolean clock, float partialTicks)
431 415 {
432 416 this.profiler.endStartSection("litemods");
433   -
434   - Timer minecraftTimer = ((IMinecraft)this.engine.getClient()).getTimer();
435   - float partialTicks = minecraftTimer.renderPartialTicks;
436   - boolean clock = minecraftTimer.elapsedTicks > 0;
437   -
438 417 Minecraft minecraft = this.engine.getClient();
439 418  
440 419 // Flag indicates whether we are in game at the moment
... ... @@ -548,7 +527,7 @@ public class LiteLoaderEventBrokerClient extends LiteLoaderEventBroker&lt;Minecraft
548 527 }
549 528  
550 529 /**
551   - * @param e
  530 + * @param ci
552 531 * @param name
553 532 * @param width
554 533 * @param height
... ...
src/client/java/com/mumfrey/liteloader/client/LiteLoaderPanelManager.java
... ... @@ -9,6 +9,7 @@ import org.lwjgl.input.Keyboard;
9 9  
10 10 import com.mumfrey.liteloader.client.gui.GuiLiteLoaderPanel;
11 11 import com.mumfrey.liteloader.common.GameEngine;
  12 +import com.mumfrey.liteloader.core.LiteLoader;
12 13 import com.mumfrey.liteloader.core.LiteLoaderMods;
13 14 import com.mumfrey.liteloader.core.LiteLoaderUpdateSite;
14 15 import com.mumfrey.liteloader.core.LiteLoaderVersion;
... ... @@ -32,6 +33,13 @@ import net.minecraft.client.resources.I18n;
32 33 */
33 34 public class LiteLoaderPanelManager implements PanelManager<GuiScreen>
34 35 {
  36 + /**
  37 + * Number of launches required before an update check is forced when the
  38 + * "force update check" option is enabled. For snapshot versions this is
  39 + * ignored and an update check is always performed.
  40 + */
  41 + private static final int UPDATE_CHECK_INTERVAL = 10;
  42 +
35 43 private final LoaderEnvironment environment;
36 44  
37 45 /**
... ... @@ -87,14 +95,14 @@ public class LiteLoaderPanelManager implements PanelManager&lt;GuiScreen&gt;
87 95 this.displayModInfoScreenTab = this.properties.getAndStoreBooleanProperty(LoaderProperties.OPTION_MOD_INFO_SCREEN, true);
88 96 this.tabAlwaysExpanded = this.properties.getAndStoreBooleanProperty(LoaderProperties.OPTION_NO_HIDE_TAB, false);
89 97  
90   - if (this.properties.getAndStoreBooleanProperty(LoaderProperties.OPTION_FORCE_UPDATE, false))
  98 + if (this.shouldCheckForUpdates())
91 99 {
92 100 int updateCheckInterval = this.properties.getIntegerProperty(LoaderProperties.OPTION_UPDATE_CHECK_INTR) + 1;
93   - LiteLoaderLogger.debug("Force update is TRUE, updateCheckInterval = %d", updateCheckInterval);
  101 + LiteLoaderLogger.debug("Regular update check enabled, updateCheckInterval = %d", updateCheckInterval);
94 102  
95   - if (updateCheckInterval > 10)
  103 + if (LiteLoader.isSnapshot() || updateCheckInterval > LiteLoaderPanelManager.UPDATE_CHECK_INTERVAL)
96 104 {
97   - LiteLoaderLogger.debug("Forcing update check!");
  105 + LiteLoaderLogger.debug("Checking for updates...");
98 106 this.checkForUpdate = true;
99 107 updateCheckInterval = 0;
100 108 }
... ... @@ -104,6 +112,16 @@ public class LiteLoaderPanelManager implements PanelManager&lt;GuiScreen&gt;
104 112 }
105 113 }
106 114  
  115 + private boolean shouldCheckForUpdates()
  116 + {
  117 + if (LiteLoader.isSnapshot() && this.properties.getAndStoreBooleanProperty(LoaderProperties.OPTION_CHECK_SNAPSHOTS, true))
  118 + {
  119 + return true;
  120 + }
  121 +
  122 + return this.properties.getAndStoreBooleanProperty(LoaderProperties.OPTION_FORCE_UPDATE, false);
  123 + }
  124 +
107 125 @Override
108 126 public void init(LiteLoaderMods mods, ConfigManager configManager)
109 127 {
... ... @@ -141,7 +159,8 @@ public class LiteLoaderPanelManager implements PanelManager&lt;GuiScreen&gt;
141 159 this.checkForUpdate = false;
142 160 if (updateSite.isCheckSucceess() && updateSite.isUpdateAvailable())
143 161 {
144   - this.setNotification(I18n.format("gui.notifications.updateavailable"));
  162 + this.setNotification(I18n.format("gui.notifications." + (LiteLoader.isSnapshot() ? "newsnapshotavailable" : "updateavailable"),
  163 + updateSite.getAvailableVersion(), updateSite.getAvailableVersionDate()));
145 164 }
146 165 }
147 166 }
... ... @@ -251,6 +270,19 @@ public class LiteLoaderPanelManager implements PanelManager&lt;GuiScreen&gt;
251 270 {
252 271 return this.properties.getBooleanProperty(LoaderProperties.OPTION_FORCE_UPDATE);
253 272 }
  273 +
  274 + @Override
  275 + public void setCheckForSnapshotsEnabled(boolean checkForSnapshots)
  276 + {
  277 + this.properties.setBooleanProperty(LoaderProperties.OPTION_CHECK_SNAPSHOTS, checkForSnapshots);
  278 + this.properties.writeProperties();
  279 + }
  280 +
  281 + @Override
  282 + public boolean isCheckForSnapshotsEnabled()
  283 + {
  284 + return this.properties.getBooleanProperty(LoaderProperties.OPTION_CHECK_SNAPSHOTS);
  285 + }
254 286  
255 287 /**
256 288 * Display the liteloader panel over the specified GUI
... ...
src/client/java/com/mumfrey/liteloader/client/api/LiteLoaderCoreAPIClient.java
... ... @@ -42,7 +42,6 @@ public class LiteLoaderCoreAPIClient extends LiteLoaderCoreAPI
42 42  
43 43 private static final String[] requiredDownstreamTransformers = {
44 44 LiteLoaderCoreAPI.PKG_LITELOADER_COMMON + ".transformers.LiteLoaderPacketTransformer",
45   - LiteLoaderCoreAPIClient.PKG_LITELOADER_CLIENT + ".transformers.MinecraftTransformer",
46 45 LiteLoaderCoreAPI.PKG_LITELOADER + ".transformers.event.json.ModEventInjectionTransformer"
47 46 };
48 47  
... ...
src/client/java/com/mumfrey/liteloader/client/gui/GuiLiteLoaderPanel.java
... ... @@ -177,11 +177,10 @@ public class GuiLiteLoaderPanel extends GuiScreen
177 177 this.startupErrorCount = mods.getStartupErrorCount();
178 178 this.criticalErrorCount = mods.getCriticalErrorCount();
179 179  
180   - String branding = LiteLoader.getBranding();
181   - if (branding != null && branding.contains("SNAPSHOT"))
  180 + this.isSnapshot = LiteLoader.isSnapshot();
  181 + if (this.isSnapshot)
182 182 {
183   - this.isSnapshot = true;
184   - this.versionText = "\247c" + branding;
  183 + this.versionText = "\247c" + LiteLoader.getBranding();
185 184 }
186 185 }
187 186  
... ... @@ -524,6 +523,7 @@ public class GuiLiteLoaderPanel extends GuiScreen
524 523  
525 524 if (annoyingTip)
526 525 {
  526 + GuiLiteLoaderPanel.displayErrorToolTip = false;
527 527 this.drawNotificationTooltip(mouseX, mouseY - 13);
528 528 }
529 529 }
... ... @@ -542,8 +542,11 @@ public class GuiLiteLoaderPanel extends GuiScreen
542 542 }
543 543 else if (this.notification != null)
544 544 {
545   - GuiLiteLoaderPanel.drawTooltip(this.fontRendererObj, this.notification, left, top, this.width, this.height,
546   - GuiLiteLoaderPanel.NOTIFICATION_TOOLTIP_FOREGROUND, GuiLiteLoaderPanel.NOTIFICATION_TOOLTIP_BACKGROUND);
  545 + boolean isCritical = this.notification.startsWith("!!");
  546 + String text = isCritical ? this.notification.substring(2) : this.notification;
  547 + int bgColour = isCritical ? GuiLiteLoaderPanel.ERROR_TOOLTIP_BACKGROUND : GuiLiteLoaderPanel.NOTIFICATION_TOOLTIP_BACKGROUND;
  548 + GuiLiteLoaderPanel.drawTooltip(this.fontRendererObj, text, left, top, this.width, this.height,
  549 + GuiLiteLoaderPanel.NOTIFICATION_TOOLTIP_FOREGROUND, bgColour);
547 550 }
548 551 }
549 552  
... ... @@ -739,21 +742,32 @@ public class GuiLiteLoaderPanel extends GuiScreen
739 742 *
740 743 * @param fontRenderer
741 744 * @param tooltipText
742   - * @param mouseX
743   - * @param mouseY
  745 + * @param left
  746 + * @param top
744 747 * @param screenWidth
745 748 * @param screenHeight
746 749 * @param colour
747 750 * @param backgroundColour
748 751 */
749   - public static void drawTooltip(FontRenderer fontRenderer, String tooltipText, int mouseX, int mouseY, int screenWidth, int screenHeight,
  752 + public static void drawTooltip(FontRenderer fontRenderer, String text, int left, int top, int screenWidth, int screenHeight,
750 753 int colour, int backgroundColour)
751 754 {
752   - int textSize = fontRenderer.getStringWidth(tooltipText);
753   - mouseX = Math.max(0, Math.min(screenWidth - 4, mouseX - 4));
754   - mouseY = Math.max(0, Math.min(screenHeight - 16, mouseY));
755   - drawRect(mouseX - textSize - 2, mouseY, mouseX + 2, mouseY + 12, backgroundColour);
756   - fontRenderer.drawStringWithShadow(tooltipText, mouseX - textSize, mouseY + 2, colour);
  755 + String[] lines = text.trim().split("\\r?\\n");
  756 + int textWidth = 0;
  757 + int textHeight = 9 * lines.length;
  758 + for (String line : lines)
  759 + {
  760 + textWidth = Math.max(textWidth, fontRenderer.getStringWidth(line));
  761 + top -= 9;
  762 + }
  763 +
  764 + left = Math.max(0, Math.min(screenWidth - 4, left - 4));
  765 + top = Math.max(0, Math.min(screenHeight - 16, top + 9));
  766 + drawRect(left - textWidth - 2, top, left + 2, top + textHeight + 2, backgroundColour);
  767 + for (String line : lines)
  768 + {
  769 + fontRenderer.drawStringWithShadow(line, left - textWidth, (top += 9) - 7, colour);
  770 + }
757 771 }
758 772  
759 773  
... ...
src/client/java/com/mumfrey/liteloader/client/gui/GuiPanelError.java
... ... @@ -12,10 +12,12 @@ import java.util.List;
12 12  
13 13 import org.lwjgl.input.Keyboard;
14 14  
  15 +import com.google.common.base.Joiner;
15 16 import com.mumfrey.liteloader.core.ModInfo;
16 17  
17 18 import net.minecraft.client.Minecraft;
18 19 import net.minecraft.client.gui.GuiButton;
  20 +import net.minecraft.client.gui.GuiScreen;
19 21 import net.minecraft.client.resources.I18n;
20 22  
21 23 public class GuiPanelError extends GuiPanel implements ScrollPanelContent
... ... @@ -98,6 +100,7 @@ public class GuiPanelError extends GuiPanel implements ScrollPanelContent
98 100  
99 101 this.scrollPane.setSizeAndPosition(MARGIN, TOP, this.width - (MARGIN * 2), this.height - TOP - BOTTOM);
100 102 this.controls.add(new GuiButton(0, this.width - 59 - MARGIN, this.height - BOTTOM + 9, 60, 20, I18n.format("gui.done")));
  103 + this.controls.add(new GuiButton(1, this.width - 204 - MARGIN, this.height - BOTTOM + 9, 140, 20, I18n.format("gui.error.copytoclipboard")));
101 104 }
102 105  
103 106 @Override
... ... @@ -131,7 +134,10 @@ public class GuiPanelError extends GuiPanel implements ScrollPanelContent
131 134 @Override
132 135 void keyPressed(char keyChar, int keyCode)
133 136 {
134   - if (keyCode == Keyboard.KEY_ESCAPE) this.close();
  137 + if (keyCode == Keyboard.KEY_ESCAPE)
  138 + {
  139 + this.close();
  140 + }
135 141 }
136 142  
137 143 @Override
... ... @@ -161,6 +167,14 @@ public class GuiPanelError extends GuiPanel implements ScrollPanelContent
161 167 @Override
162 168 void actionPerformed(GuiButton control)
163 169 {
164   - if (control.id == 0) this.close();
  170 + if (control.id == 0)
  171 + {
  172 + this.close();
  173 + }
  174 +
  175 + if (control.id == 1)
  176 + {
  177 + GuiScreen.setClipboardString(Joiner.on('\n').join(this.scrollPaneContent));
  178 + }
165 179 }
166 180 }
... ...
src/client/java/com/mumfrey/liteloader/client/gui/GuiPanelSettings.java
... ... @@ -17,25 +17,29 @@ import net.minecraft.client.resources.I18n;
17 17  
18 18 class GuiPanelSettings extends GuiPanel
19 19 {
20   - private GuiLiteLoaderPanel parentScreen;
  20 + private final GuiLiteLoaderPanel parentScreen;
  21 +
  22 + private final boolean isSnapshot;
21 23  
22   - private GuiCheckbox chkShowTab, chkNoHide, chkForceUpdate;
  24 + private GuiCheckbox chkShowTab, chkNoHide, chkForceUpdate, chkCheckForSnapshots;
23 25  
24 26 private boolean hide;
25 27  
26 28 private String[] helpText = new String[5];
27   -
  29 +
28 30 GuiPanelSettings(GuiLiteLoaderPanel parentScreen, Minecraft minecraft)
29 31 {
30 32 super(minecraft);
31 33  
32 34 this.parentScreen = parentScreen;
  35 + this.isSnapshot = LiteLoader.isSnapshot();
33 36  
  37 + String helpKey = this.isSnapshot ? "checkforsnapshots" : "forceupdate";
34 38 this.helpText[0] = I18n.format("gui.settings.showtab.help1");
35 39 this.helpText[1] = I18n.format("gui.settings.showtab.help2");
36 40 this.helpText[2] = I18n.format("gui.settings.notabhide.help1");
37   - this.helpText[3] = I18n.format("gui.settings.forceupdate.help1");
38   - this.helpText[4] = I18n.format("gui.settings.forceupdate.help2");
  41 + this.helpText[3] = I18n.format("gui.settings." + helpKey + ".help1");
  42 + this.helpText[4] = I18n.format("gui.settings." + helpKey + ".help2");
39 43 }
40 44  
41 45 @Override
... ... @@ -61,6 +65,10 @@ class GuiPanelSettings extends GuiPanel
61 65 this.controls.add(this.chkShowTab = new GuiCheckbox(0, 34, 90, I18n.format("gui.settings.showtab.label")));
62 66 this.controls.add(this.chkNoHide = new GuiCheckbox(1, 34, 128, I18n.format("gui.settings.notabhide.label")));
63 67 this.controls.add(this.chkForceUpdate = new GuiCheckbox(2, 34, 158, I18n.format("gui.settings.forceupdate.label")));
  68 + this.controls.add(this.chkCheckForSnapshots = new GuiCheckbox(2, 34, 158, I18n.format("gui.settings.checkforsnapshots.label")));
  69 +
  70 + this.chkForceUpdate.visible = !this.isSnapshot;
  71 + this.chkCheckForSnapshots.visible = this.isSnapshot;
64 72  
65 73 this.updateCheckBoxes();
66 74 }
... ... @@ -72,6 +80,7 @@ class GuiPanelSettings extends GuiPanel
72 80 this.chkShowTab.checked = panelManager.isTabVisible();
73 81 this.chkNoHide.checked = panelManager.isTabAlwaysExpanded();
74 82 this.chkForceUpdate.checked = panelManager.isForceUpdateEnabled();
  83 + this.chkCheckForSnapshots.checked = panelManager.isCheckForSnapshotsEnabled();
75 84 }
76 85  
77 86 private void updateSettings()
... ... @@ -81,6 +90,7 @@ class GuiPanelSettings extends GuiPanel
81 90 panelManager.setTabVisible(this.chkShowTab.checked);
82 91 panelManager.setTabAlwaysExpanded(this.chkNoHide.checked);
83 92 panelManager.setForceUpdateEnabled(this.chkForceUpdate.checked);
  93 + panelManager.setCheckForSnapshotsEnabled(this.chkCheckForSnapshots.checked);
84 94 }
85 95  
86 96 @Override
... ...
src/client/java/com/mumfrey/liteloader/client/gui/modlist/ModList.java
... ... @@ -15,6 +15,7 @@ import org.lwjgl.input.Keyboard;
15 15 import com.mumfrey.liteloader.LiteMod;
16 16 import com.mumfrey.liteloader.api.ModInfoDecorator;
17 17 import com.mumfrey.liteloader.client.gui.GuiLiteLoaderPanel;
  18 +import com.mumfrey.liteloader.core.EnabledModsList.Enabled;
18 19 import com.mumfrey.liteloader.core.LiteLoaderMods;
19 20 import com.mumfrey.liteloader.core.ModInfo;
20 21 import com.mumfrey.liteloader.interfaces.Loadable;
... ... @@ -75,9 +76,12 @@ public class ModList
75 76 // Disabled mods
76 77 for (ModInfo<?> disabledMod : mods.getDisabledMods())
77 78 {
78   - ModListEntry modListEntry = new ModListEntry(this, mods, environment, minecraft.fontRendererObj, brandColour, decorators, disabledMod);
  79 + if (environment.getEnabledModsList().getEnabled(environment.getProfile(), disabledMod.getIdentifier()) != Enabled.FILTERED)
  80 + {
  81 + ModListEntry modListEntry = new ModListEntry(this, mods, environment, minecraft.fontRendererObj, brandColour, decorators, disabledMod);
79 82 sortedMods.put(modListEntry.getKey(), modListEntry);
80 83 }
  84 + }
81 85  
82 86 // Show bad containers if no other containers are found, should help users realise they have the wrong mod version!
83 87 if (sortedMods.size() == 0)
... ...
src/client/java/com/mumfrey/liteloader/client/mixin/MixinMinecraft.java
... ... @@ -19,7 +19,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
19 19 import com.mumfrey.liteloader.PlayerInteractionListener.MouseButton;
20 20 import com.mumfrey.liteloader.client.LiteLoaderEventBrokerClient;
21 21 import com.mumfrey.liteloader.client.ducks.IFramebuffer;
  22 +import com.mumfrey.liteloader.client.gui.startup.LoadingBar;
22 23 import com.mumfrey.liteloader.client.overlays.IMinecraft;
  24 +import com.mumfrey.liteloader.launch.LiteLoaderTweaker;
23 25  
24 26 import net.minecraft.client.Minecraft;
25 27 import net.minecraft.client.renderer.OpenGlHelper;
... ... @@ -35,6 +37,7 @@ public abstract class MixinMinecraft implements IMinecraft
35 37 @Shadow @Final private List<IResourcePack> defaultResourcePacks;
36 38 @Shadow private String serverName;
37 39 @Shadow private int serverPort;
  40 + @Shadow private boolean isGamePaused;
38 41  
39 42 @Shadow abstract void resize(int width, int height);
40 43 @Shadow private void clickMouse() {}
... ... @@ -43,6 +46,25 @@ public abstract class MixinMinecraft implements IMinecraft
43 46  
44 47 private LiteLoaderEventBrokerClient broker;
45 48  
  49 + @Inject(method = "init()V", at = @At(value = "NEW", target = "net/minecraft/client/renderer/EntityRenderer"))
  50 + private void init(CallbackInfo ci)
  51 + {
  52 + LiteLoaderTweaker.init();
  53 + LiteLoaderTweaker.postInit();
  54 + }
  55 +
  56 + @Inject(method = "init()V", at = @At(value = "NEW", target = "net/minecraft/client/renderer/texture/TextureMap"))
  57 + private void initTextures(CallbackInfo ci)
  58 + {
  59 + LoadingBar.initTextures();
  60 + }
  61 +
  62 + @Inject(method = "init()V", at = @At("INVOKE"))
  63 + private void progress(CallbackInfo ci)
  64 + {
  65 + LoadingBar.incrementProgress();
  66 + }
  67 +
46 68 @Inject(method = "init()V", at = @At("RETURN"))
47 69 private void onStartupComplete(CallbackInfo ci)
48 70 {
... ... @@ -79,7 +101,9 @@ public abstract class MixinMinecraft implements IMinecraft
79 101 ))
80 102 private void onTick(CallbackInfo ci)
81 103 {
82   - this.broker.onTick();
  104 + boolean clock = this.timer.elapsedTicks > 0;
  105 + float partialTicks = this.timer.renderPartialTicks;
  106 + this.broker.onTick(clock, partialTicks);
83 107 }
84 108  
85 109 @Redirect(method = "runGameLoop()V", at = @At(
... ...
src/client/java/com/mumfrey/liteloader/client/transformers/MinecraftTransformer.java deleted 100644 → 0
1   -/*
2   - * This file is part of LiteLoader.
3   - * Copyright (C) 2012-16 Adam Mummery-Smith
4   - * All Rights Reserved.
5   - */
6   -package com.mumfrey.liteloader.client.transformers;
7   -
8   -import java.util.Iterator;
9   -
10   -import org.objectweb.asm.Opcodes;
11   -import org.objectweb.asm.tree.AbstractInsnNode;
12   -import org.objectweb.asm.tree.ClassNode;
13   -import org.objectweb.asm.tree.InsnList;
14   -import org.objectweb.asm.tree.LdcInsnNode;
15   -import org.objectweb.asm.tree.MethodInsnNode;
16   -import org.objectweb.asm.tree.MethodNode;
17   -import org.objectweb.asm.tree.TypeInsnNode;
18   -
19   -import com.mumfrey.liteloader.core.runtime.Obf;
20   -import com.mumfrey.liteloader.launch.LiteLoaderTweaker;
21   -import com.mumfrey.liteloader.transformers.ClassTransformer;
22   -import com.mumfrey.liteloader.util.log.LiteLoaderLogger;
23   -
24   -public class MinecraftTransformer extends ClassTransformer
25   -{
26   - private static final String TWEAKCLASS = LiteLoaderTweaker.class.getName().replace('.', '/');
27   -
28   - @Override
29   - public byte[] transform(String name, String transformedName, byte[] basicClass)
30   - {
31   - if ((Obf.Minecraft.name.equals(transformedName) || Obf.Minecraft.obf.equals(transformedName)))
32   - {
33   - ClassNode classNode = this.readClass(basicClass, true);
34   - for (MethodNode method : classNode.methods)
35   - {
36   - if (Obf.startGame.obf.equals(method.name) || Obf.startGame.srg.equals(method.name) || Obf.startGame.name.equals(method.name))
37   - {
38   - this.transformStartGame(method);
39   - }
40   - }
41   - return this.writeClass(classNode);
42   - }
43   - return basicClass;
44   - }
45   -
46   -
47   - private void transformStartGame(MethodNode method)
48   - {
49   - InsnList insns = new InsnList();
50   -
51   - boolean loadingBarEnabled = LiteLoaderTweaker.loadingBarEnabled();
52   - boolean found = false;
53   -
54   - Iterator<AbstractInsnNode> iter = method.instructions.iterator();
55   - while (iter.hasNext())
56   - {
57   - AbstractInsnNode insn = iter.next();
58   - if (loadingBarEnabled && insn instanceof MethodInsnNode)
59   - {
60   - insns.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Obf.LoadingBar.ref, "incrementProgress", "()V", false));
61   - }
62   -
63   - insns.add(insn);
64   -
65   - if (insn instanceof TypeInsnNode && insn.getOpcode() == Opcodes.NEW && insns.getLast() != null)
66   - {
67   - TypeInsnNode typeNode = (TypeInsnNode)insn;
68   - if (!found && (Obf.EntityRenderer.obf.equals(typeNode.desc) || Obf.EntityRenderer.ref.equals(typeNode.desc)))
69   - {
70   - LiteLoaderLogger.info("MinecraftTransformer found INIT injection point, this is good.");
71   - found = true;
72   -
73   - insns.add(new MethodInsnNode(Opcodes.INVOKESTATIC, MinecraftTransformer.TWEAKCLASS, Obf.init.name, "()V", false));
74   - insns.add(new MethodInsnNode(Opcodes.INVOKESTATIC, MinecraftTransformer.TWEAKCLASS, Obf.postInit.name, "()V", false));
75   - }
76   - }
77   -
78   - if (loadingBarEnabled && insn instanceof LdcInsnNode)
79   - {
80   - LdcInsnNode ldcInsn = (LdcInsnNode)insn;
81   - if ("textures/blocks".equals(ldcInsn.cst))
82   - {
83   - insns.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Obf.LoadingBar.ref, "initTextures", "()V", false));
84   - }
85   - }
86   - }
87   -
88   - method.instructions = insns;
89   -
90   - if (!found) LiteLoaderLogger.severe("MinecraftTransformer failed to find INIT injection point, the game will probably crash pretty soon.");
91   - }
92   -}
src/client/java/com/mumfrey/liteloader/modconfig/AbstractConfigPanel.java
... ... @@ -7,15 +7,21 @@ package com.mumfrey.liteloader.modconfig;
7 7  
8 8 import java.util.ArrayList;
9 9 import java.util.List;
  10 +import java.util.regex.Pattern;
10 11  
11 12 import org.lwjgl.input.Keyboard;
12 13  
  14 +import com.mumfrey.liteloader.client.gui.GuiLiteLoaderPanel;
13 15 import com.mumfrey.liteloader.client.mixin.IGuiButton;
14 16  
15 17 import net.minecraft.client.Minecraft;
  18 +import net.minecraft.client.gui.FontRenderer;
16 19 import net.minecraft.client.gui.GuiButton;
17 20 import net.minecraft.client.gui.GuiLabel;
18 21 import net.minecraft.client.gui.GuiScreen;
  22 +import net.minecraft.client.gui.GuiTextField;
  23 +import net.minecraft.client.renderer.RenderHelper;
  24 +import net.minecraft.client.resources.I18n;
19 25  
20 26 /**
21 27 * A general-purpose base class for mod config panels which implements a lot of
... ... @@ -42,74 +48,361 @@ public abstract class AbstractConfigPanel implements ConfigPanel
42 48 }
43 49  
44 50 /**
45   - * Struct which keeps a control together with its callback object
  51 + * A handle to an option text field, used to get and retrieve text and set
  52 + * the max length. It is possible to obtain the native text field as well,
  53 + * however caution should be used when doing so to avoid breaking the
  54 + * contract of the text field wrapper used in the config panel itself.
  55 + */
  56 + public interface ConfigTextField
  57 + {
  58 + /**
  59 + * Get the inner text field
  60 + */
  61 + public abstract GuiTextField getNativeTextField();
  62 +
  63 + /**
  64 + * Get the text field's text
  65 + */
  66 + public abstract String getText();
  67 +
  68 + /**
  69 + * Set the text field's text
  70 + *
  71 + * @param text text to set
  72 + * @return fluent interface
  73 + */
  74 + public abstract ConfigTextField setText(String text);
  75 +
  76 + /**
  77 + * Set a validation regex for this text box.
  78 + *
  79 + * @param regex Validation regex to use for this text field
  80 + * @param force If set to <tt>false</tt>, invalid values will only cause
  81 + * the text field to display an error when invalid text is present.
  82 + * If set to <tt>true</tt>, invalid values will be forcibly
  83 + * prohibited from being entered.
  84 + * @return fluent interfaces
  85 + */
  86 + public abstract ConfigTextField setRegex(String regex, boolean force);
  87 +
  88 + /**
  89 + * If the validation regex is not set, always returns true. Otherwise
  90 + * returns true if the current text value matches the validation regex.
  91 + *
  92 + * @return validation state of the current text value
  93 + */
  94 + public abstract boolean isValid();
  95 +
  96 + /**
  97 + * Set the max allowed string length, defaults to 32
46 98 *
47   - * @param <T> control type
  99 + * @param maxLength max string length to use
  100 + * @return fluent interface
  101 + */
  102 + public abstract ConfigTextField setMaxLength(int maxLength);
  103 + }
  104 +
  105 + /**
  106 + * Base for config option handle structs
48 107 */
49   - class ConfigOption<T extends GuiButton>
  108 + static abstract class ConfigOption
50 109 {
51   - final GuiLabel label;
52   - final T control;
53   - final ConfigOptionListener<T> listener;
  110 + void onTick()
  111 + {
  112 + }
  113 +
  114 + abstract void draw(Minecraft minecraft, int mouseX, int mouseY, float partialTicks);
  115 +
  116 + void mouseReleased(Minecraft minecraft, int mouseX, int mouseY)
  117 + {
  118 + }
  119 +
  120 + boolean mousePressed(Minecraft minecraft, int mouseX, int mouseY)
  121 + {
  122 + return false;
  123 + }
54 124  
55   - ConfigOption(GuiLabel label)
  125 + boolean keyPressed(Minecraft minecraft, char keyChar, int keyCode)
  126 + {
  127 + return false;
  128 + }
  129 + }
  130 +
  131 + /**
  132 + * Struct for labels
  133 + */
  134 + static class ConfigOptionLabel extends ConfigOption
  135 + {
  136 + private final GuiLabel label;
  137 +
  138 + ConfigOptionLabel(GuiLabel label)
56 139 {
57 140 this.label = label;
58   - this.control = null;
59   - this.listener = null;
60 141 }
61 142  
62   - ConfigOption(T control, ConfigOptionListener<T> listener)
  143 + @Override
  144 + void draw(Minecraft minecraft, int mouseX, int mouseY, float partialTicks)
  145 + {
  146 + this.label.drawLabel(minecraft, mouseX, mouseY);
  147 + }
  148 + }
  149 +
  150 + /**
  151 + * Struct which keeps a control together with its callback object
  152 + *
  153 + * @param <T> control type
  154 + */
  155 + static class ConfigOptionButton<T extends GuiButton> extends ConfigOption
  156 + {
  157 + private final T control;
  158 + private final ConfigOptionListener<T> listener;
  159 +
  160 + ConfigOptionButton(T control, ConfigOptionListener<T> listener)
63 161 {
64   - this.label = null;
65 162 this.control = control;
66 163 this.listener = listener;
67 164 }
68 165  
  166 + @Override
69 167 void draw(Minecraft minecraft, int mouseX, int mouseY, float partialTicks)
70 168 {
71   - if (this.label != null)
  169 + this.control.drawButton(minecraft, mouseX, mouseY);
  170 + }
  171 +
  172 + @Override
  173 + boolean mousePressed(Minecraft minecraft, int mouseX, int mouseY)
  174 + {
  175 + if (this.control.mousePressed(minecraft, mouseX, mouseY))
  176 + {
  177 + this.control.playPressSound(minecraft.getSoundHandler());
  178 + if (this.listener != null)
  179 + {
  180 + this.listener.actionPerformed(this.control);
  181 + }
  182 + return true;
  183 + }
  184 +
  185 + return false;
  186 + }
  187 +
  188 + @Override
  189 + void mouseReleased(Minecraft minecraft, int mouseX, int mouseY)
  190 + {
  191 + this.control.mouseReleased(mouseX, mouseY);
  192 + }
  193 + }
  194 +
  195 + /**
  196 + * Struct for text fields
  197 + */
  198 + class ConfigOptionTextField extends ConfigOption implements ConfigTextField
  199 + {
  200 + /**
  201 + * List for accessing via tab order
  202 + */
  203 + private final List<GuiConfigTextField> tabOrder;
  204 +
  205 + /**
  206 + * Tab index
  207 + */
  208 + private final int tabIndex;
  209 +
  210 + /**
  211 + * Inner text field
  212 + */
  213 + private final GuiConfigTextField textField;
  214 +
  215 + ConfigOptionTextField(List<GuiConfigTextField> tabOrder, GuiConfigTextField textField)
  216 + {
  217 + this.tabOrder = tabOrder;
  218 + this.tabIndex = tabOrder.indexOf(textField);
  219 + this.textField = textField;
  220 +
  221 + if (this.tabIndex == 0)
72 222 {
73   - this.label.drawLabel(minecraft, mouseX, mouseY);
  223 + textField.setFocused(true);
  224 + }
74 225 }
75 226  
76   - if (this.control != null)
  227 + @Override
  228 + void onTick()
77 229 {
78   - this.control.drawButton(minecraft, mouseX, mouseY);
  230 + this.textField.updateCursorCounter();
79 231 }
  232 +
  233 + @Override
  234 + void draw(Minecraft minecraft, int mouseX, int mouseY, float partialTicks)
  235 + {
  236 + this.textField.drawTextBox(mouseX, mouseY);
80 237 }
81 238  
  239 + @Override
82 240 boolean mousePressed(Minecraft minecraft, int mouseX, int mouseY)
83 241 {
84   - if (this.control != null && this.control.mousePressed(minecraft, mouseX, mouseY))
  242 + this.textField.mouseClicked(mouseX, mouseY, 0);
  243 + if (this.textField.isFocused())
85 244 {
86   - this.control.playPressSound(minecraft.getSoundHandler());
87   - if (this.listener != null)
  245 + // Unfocus all other text fields
  246 + for (GuiTextField textField : this.tabOrder)
88 247 {
89   - this.listener.actionPerformed(this.control);
  248 + if (textField != this.textField)
  249 + {
  250 + textField.setFocused(false);
  251 + }
90 252 }
  253 + }
  254 +
  255 + return false;
  256 + }
  257 +
  258 + @Override
  259 + boolean keyPressed(Minecraft minecraft, char keyChar, int keyCode)
  260 + {
  261 + if (!this.textField.isFocused())
  262 + {
  263 + return false;
  264 + }
  265 +
  266 + if (keyCode == Keyboard.KEY_TAB)
  267 + {
  268 + this.textField.setFocused(false);
  269 + int tabOrderSize = this.tabOrder.size();
  270 + this.tabOrder.get((this.tabIndex + (GuiScreen.isShiftKeyDown() ? -1 : 1) + tabOrderSize) % tabOrderSize).setFocused(true);
91 271 return true;
92 272 }
93 273  
  274 + return this.textField.textboxKeyTyped(keyChar, keyCode);
  275 + }
  276 +
  277 + @Override
  278 + public GuiTextField getNativeTextField()
  279 + {
  280 + return this.textField;
  281 + }
  282 +
  283 + @Override
  284 + public String getText()
  285 + {
  286 + return this.textField.getText();
  287 + }
  288 +
  289 + @Override
  290 + public ConfigTextField setText(String text)
  291 + {
  292 + this.textField.setText(text);
  293 + return this;
  294 + }
  295 +
  296 + @Override
  297 + public ConfigTextField setMaxLength(int maxLength)
  298 + {
  299 + this.textField.setMaxStringLength(maxLength);
  300 + return this;
  301 + }
  302 +
  303 + @Override
  304 + public ConfigTextField setRegex(String regex, boolean force)
  305 + {
  306 + this.textField.setRegex(Pattern.compile(regex), force);;
  307 + return this;
  308 + }
  309 +
  310 + @Override
  311 + public boolean isValid()
  312 + {
  313 + return this.textField.isValid();
  314 + }
  315 + }
  316 +
  317 + /**
  318 + * Custom text field which supports "soft" validation by regex (draws red
  319 + * border and error message when invalid)
  320 + */
  321 + class GuiConfigTextField extends GuiTextField
  322 + {
  323 + private final FontRenderer fontRenderer;
  324 + private final int width, height;
  325 + private Pattern regex;
  326 + private boolean valid, drawing;
  327 +
  328 + GuiConfigTextField(int id, FontRenderer fontRenderer, int x, int y, int width, int height)
  329 + {
  330 + super(id, fontRenderer, x, y, width, height);
  331 + this.fontRenderer = fontRenderer;
  332 + this.width = width;
  333 + this.height = height;
  334 + this.setRegex(null, false);
  335 + }
  336 +
  337 + void setRegex(Pattern regex, boolean restrict)
  338 + {
  339 + if (restrict && regex != null)
  340 + {
  341 + this.setValidator((text) -> regex.matcher(text).matches());
  342 + this.regex = null;
  343 + }
  344 + else
  345 + {
  346 + this.setValidator((text) -> {
  347 + this.validate(text);
  348 + return true;
  349 + });
  350 + this.regex = regex;
  351 + this.validate(this.getText());
  352 + }
  353 + }
  354 +
  355 + private boolean validate(String text)
  356 + {
  357 + this.valid = (this.regex == null || this.regex.matcher(text).matches());
  358 + return true;
  359 + }
  360 +
  361 + boolean isValid()
  362 + {
  363 + return this.valid;
  364 + }
  365 +
  366 + @Override
  367 + public boolean getEnableBackgroundDrawing()
  368 + {
  369 + boolean bg = super.getEnableBackgroundDrawing();
  370 + if (bg && this.drawing && !this.isValid())
  371 + {
  372 + drawRect(this.xPosition - 1, this.yPosition - 1, this.xPosition + this.width + 1, this.yPosition + this.height + 1, 0xFFFF5555);
  373 + drawRect(this.xPosition, this.yPosition, this.xPosition + this.width, this.yPosition + this.height, 0xFF000000);
94 374 return false;
95 375 }
96 376  
97   - void mouseReleased(Minecraft mc, int mouseX, int mouseY)
  377 + return bg;
  378 + }
  379 +
  380 + public void drawTextBox(int mouseX, int mouseY)
98 381 {
99   - if (this.control != null)
  382 + this.drawing = true;
  383 + super.drawTextBox();
  384 + if (!this.isValid())
  385 + {
  386 + drawRect(this.xPosition + this.width - 10, this.yPosition, this.xPosition + this.width, this.yPosition + this.height, 0x66000000);
  387 + this.fontRenderer.drawString("\247l!", this.xPosition + this.width - 6, this.yPosition + (this.height / 2) - 4, 0xFFFF5555);
  388 + if (mouseX >= this.xPosition && mouseX < this.xPosition + this.width && mouseY >= this.yPosition && mouseY < this.yPosition + this.height)
100 389 {
101   - this.control.mouseReleased(mouseX, mouseY);
  390 + AbstractConfigPanel.this.drawHoveringText(I18n.format("gui.invalidvalue"), mouseX, mouseY);
  391 + }
102 392 }
  393 + this.drawing = false;
103 394 }
104 395 }
105 396  
106 397 protected final Minecraft mc;
107 398  
108   - private final List<ConfigOption<?>> options = new ArrayList<ConfigOption<?>>();
  399 + private final List<ConfigOption> options = new ArrayList<ConfigOption>();
  400 +
  401 + private final List<GuiConfigTextField> textFields = new ArrayList<GuiConfigTextField>();
109 402  
110 403 private int contentHeight = 0;
111 404  
112   - private ConfigOption<?> selected;
  405 + private ConfigOption selected;
113 406  
114 407 public AbstractConfigPanel()
115 408 {
... ... @@ -170,7 +463,7 @@ public abstract class AbstractConfigPanel implements ConfigPanel
170 463 label.addLine(line);
171 464 }
172 465 this.contentHeight = Math.max(y + height, this.contentHeight);
173   - this.options.add(new ConfigOption<GuiButton>(label));
  466 + this.options.add(new ConfigOptionLabel(label));
174 467 }
175 468  
176 469 /**
... ... @@ -185,12 +478,33 @@ public abstract class AbstractConfigPanel implements ConfigPanel
185 478 if (control != null)
186 479 {
187 480 this.contentHeight = Math.max(control.yPosition + ((IGuiButton)control).getButtonHeight(), this.contentHeight);
188   - this.options.add(new ConfigOption<T>(control, listener));
  481 + this.options.add(new ConfigOptionButton<T>(control, listener));
189 482 }
190 483  
191 484 return control;
192 485 }
193 486  
  487 + /**
  488 + * Add a text field to the panel, returns a handle through which the created
  489 + * text field can be accessed
  490 + *
  491 + * @param id control id
  492 + * @param x text field x position
  493 + * @param y text field y position
  494 + * @param width text field width
  495 + * @param height text field height
  496 + * @return text field handle
  497 + */
  498 + protected ConfigTextField addTextField(int id, int x, int y, int width, int height)
  499 + {
  500 + GuiConfigTextField textField = new GuiConfigTextField(id, this.mc.fontRendererObj, x + 2, y, width, height);
  501 + this.textFields.add(textField);
  502 +
  503 + ConfigOptionTextField configOption = new ConfigOptionTextField(this.textFields, textField);
  504 + this.options.add(configOption);
  505 + return configOption;
  506 + }
  507 +
194 508 @Override
195 509 public void onPanelResize(ConfigPanelHost host)
196 510 {
... ... @@ -199,12 +513,16 @@ public abstract class AbstractConfigPanel implements ConfigPanel
199 513 @Override
200 514 public void onTick(ConfigPanelHost host)
201 515 {
  516 + for (ConfigOption configOption : this.options)
  517 + {
  518 + configOption.onTick();
  519 + }
202 520 }
203 521  
204 522 @Override
205 523 public void drawPanel(ConfigPanelHost host, int mouseX, int mouseY, float partialTicks)
206 524 {
207   - for (ConfigOption<?> configOption : this.options)
  525 + for (ConfigOption configOption : this.options)
208 526 {
209 527 configOption.draw(this.mc, mouseX, mouseY, partialTicks);
210 528 }
... ... @@ -219,7 +537,7 @@ public abstract class AbstractConfigPanel implements ConfigPanel
219 537 return;
220 538 }
221 539  
222   - for (ConfigOption<?> configOption : this.options)
  540 + for (ConfigOption configOption : this.options)
223 541 {
224 542 if (configOption.mousePressed(this.mc, mouseX, mouseY))
225 543 {
... ... @@ -251,5 +569,22 @@ public abstract class AbstractConfigPanel implements ConfigPanel
251 569 host.close();
252 570 return;
253 571 }
  572 +
  573 + for (ConfigOption configOption : this.options)
  574 + {
  575 + if (configOption.keyPressed(this.mc, keyChar, keyCode))
  576 + {
  577 + break;
  578 + }
  579 + }
  580 + }
  581 +
  582 + protected final void drawHoveringText(String text, int x, int y)
  583 + {
  584 + if (this.mc.currentScreen != null)
  585 + {
  586 + GuiLiteLoaderPanel.drawTooltip(this.mc.fontRendererObj, text, x, y, Integer.MAX_VALUE, Integer.MAX_VALUE, 0xFFFF5555, 0xB0000000);
  587 + RenderHelper.disableStandardItemLighting();
  588 + }
254 589 }
255 590 }
... ...
src/client/resources/mixins.liteloader.client.json
1 1 {
2 2 "required": true,
3   - "minVersion": "0.6",
  3 + "minVersion": "0.7",
  4 + "compatibilityLevel": "JAVA_8",
4 5 "target": "@env(DEFAULT)",
5 6 "package": "com.mumfrey.liteloader.client.mixin",
6 7 "refmap": "mixins.liteloader.client.refmap.json",
... ...
src/main/java/com/mumfrey/liteloader/core/EnabledModsList.java
... ... @@ -24,6 +24,34 @@ import com.google.gson.GsonBuilder;
24 24 */
25 25 public final class EnabledModsList
26 26 {
  27 + /**
  28 + * Tristate for enablement which allows us to determine whether mod is
  29 + * forcibly disabled by user or passively disabled by mod name filter
  30 + */
  31 + public enum Enabled
  32 + {
  33 + ENABLED(true),
  34 + DISABLED(false),
  35 + FILTERED(false);
  36 +
  37 + private final boolean value;
  38 +
  39 + private Enabled(boolean value)
  40 + {
  41 + this.value = value;
  42 + }
  43 +
  44 + public boolean booleanValue()
  45 + {
  46 + return this.value;
  47 + }
  48 +
  49 + public static Enabled of(Boolean value)
  50 + {
  51 + return value == null ? Enabled.FILTERED : (value ? Enabled.ENABLED : Enabled.DISABLED);
  52 + }
  53 + }
  54 +
27 55 @SuppressWarnings("unused")
28 56 private static final transient long serialVersionUID = -6449451105617763769L;
29 57  
... ... @@ -45,7 +73,7 @@ public final class EnabledModsList
45 73 * the mods list because the command line is supposed to be an override
46 74 * rather than a new mask. These two values provide this behaviour.
47 75 */
48   - private transient Boolean defaultEnabledValue = Boolean.TRUE;
  76 + private transient Enabled defaultEnabledValue = Enabled.ENABLED;
49 77 private transient boolean allowSave = true;
50 78  
51 79 /**
... ... @@ -59,22 +87,27 @@ public final class EnabledModsList
59 87 }
60 88  
61 89 /**
62   - * Check whether a particular mod is enabled
  90 + * Check whether a particular container is enabled
63 91 *
64 92 * @param profileName
65 93 * @param identifier
66 94 */
67 95 public boolean isEnabled(String profileName, String identifier)
68 96 {
  97 + return this.getEnabled(profileName, identifier).booleanValue();
  98 + }
  99 +
  100 + public Enabled getEnabled(String profileName, String identifier)
  101 + {
69 102 Map<String, Boolean> profile = this.getProfile(profileName);
70 103 identifier = identifier.toLowerCase().trim();
71 104  
72   - if (!profile.containsKey(identifier))
  105 + if (!profile.containsKey(identifier) && this.defaultEnabledValue != Enabled.FILTERED)
73 106 {
74   - profile.put(identifier, this.defaultEnabledValue);
  107 + profile.put(identifier, this.defaultEnabledValue.booleanValue());
75 108 }
76 109  
77   - return profile.get(identifier);
  110 + return Enabled.of(profile.get(identifier));
78 111 }
79 112  
80 113 /**
... ... @@ -87,7 +120,7 @@ public final class EnabledModsList
87 120 public void setEnabled(String profileName, String identifier, boolean enabled)
88 121 {
89 122 Map<String, Boolean> profile = this.getProfile(profileName);
90   - profile.put(identifier.toLowerCase().trim(), Boolean.valueOf(enabled));
  123 + profile.put(identifier.toLowerCase().trim(), enabled ? Boolean.TRUE : Boolean.FALSE);
91 124  
92 125 this.allowSave = true;
93 126 }
... ... @@ -106,12 +139,9 @@ public final class EnabledModsList
106 139 {
107 140 if (modNameFilter != null)
108 141 {
109   - for (String modName : profile.keySet())
110   - {
111   - profile.put(modName, Boolean.FALSE);
112   - }
  142 + profile.clear();
113 143  
114   - this.defaultEnabledValue = Boolean.FALSE;
  144 + this.defaultEnabledValue = Enabled.FILTERED;
115 145 this.allowSave = false;
116 146  
117 147 for (String filterEntry : modNameFilter)
... ... @@ -122,8 +152,8 @@ public final class EnabledModsList
122 152 }
123 153 catch (Exception ex)
124 154 {
125   - this.defaultEnabledValue = Boolean.TRUE;
126   - this.allowSave = true;
  155 + this.defaultEnabledValue = Enabled.ENABLED;
  156 +// this.allowSave = true;
127 157 }
128 158 }
129 159  
... ... @@ -135,7 +165,7 @@ public final class EnabledModsList
135 165 private Map<String, Boolean> getProfile(String profileName)
136 166 {
137 167 if (profileName == null) profileName = "default";
138   - if (this.mods == null) this.mods = new TreeMap<String, TreeMap<String,Boolean>>();
  168 + if (this.mods == null) this.mods = new TreeMap<String, TreeMap<String, Boolean>>();
139 169  
140 170 if (!this.mods.containsKey(profileName))
141 171 {
... ...
src/main/java/com/mumfrey/liteloader/core/LiteLoader.java
... ... @@ -532,6 +532,15 @@ public final class LiteLoader
532 532 {
533 533 return "true".equals(System.getProperty("mcpenv"));
534 534 }
  535 +
  536 + /**
  537 + * Get whether the current running version is a snapshot build
  538 + */
  539 + public static boolean isSnapshot()
  540 + {
  541 + String branding = LiteLoader.getBranding();
  542 + return branding != null && branding.contains("SNAPSHOT");
  543 + }
535 544  
536 545 /**
537 546 * Dump debugging information to the console
... ...
src/main/java/com/mumfrey/liteloader/core/LiteLoaderUpdateSite.java
... ... @@ -8,6 +8,10 @@ package com.mumfrey.liteloader.core;
8 8 import java.io.File;
9 9 import java.io.IOException;
10 10 import java.io.InputStream;
  11 +import java.util.Date;
  12 +import java.util.Map;
  13 +import java.util.regex.Matcher;
  14 +import java.util.regex.Pattern;
11 15  
12 16 import com.google.common.io.ByteSink;
13 17 import com.google.common.io.Files;
... ... @@ -21,6 +25,8 @@ public class LiteLoaderUpdateSite extends UpdateSite
21 25 private static final String UPDATE_SITE_URL = "http://dl.liteloader.com/versions/";
22 26 private static final String UPDATE_SITE_VERSIONS_JSON = "versions.json";
23 27 private static final String UPDATE_SITE_ARTEFACT_NAME = "com.mumfrey:liteloader";
  28 +
  29 + private static final Pattern SNAPSHOT_REGEX = Pattern.compile("^([0-9\\._]+)-SNAPSHOT-(r[0-9a-z]+)-(b([0-9]+))-(.*)$", Pattern.CASE_INSENSITIVE);
24 30  
25 31 private String mcVersion;
26 32  
... ... @@ -28,6 +34,10 @@ public class LiteLoaderUpdateSite extends UpdateSite
28 34 private File jarFile = null;
29 35  
30 36 private boolean updateForced = false;
  37 + private boolean isSnapshot = false;
  38 +
  39 + private int currentBuild, availableBuild;
  40 + private String snapshotDate = null;
31 41  
32 42 public LiteLoaderUpdateSite(String targetVersion, long currentTimeStamp)
33 43 {
... ... @@ -36,6 +46,64 @@ public class LiteLoaderUpdateSite extends UpdateSite
36 46  
37 47 this.mcVersion = targetVersion;
38 48 }
  49 +
  50 + @Override
  51 + public void beginUpdateCheck()
  52 + {
  53 + this.isSnapshot = LiteLoader.isSnapshot();
  54 + super.beginUpdateCheck();
  55 + }
  56 +
  57 + @Override
  58 + public boolean isSnapshot()
  59 + {
  60 + return this.isSnapshot;
  61 + }
  62 +
  63 + @Override
  64 + public String getAvailableVersion()
  65 + {
  66 + if (this.isSnapshot() && this.availableBuild > 0)
  67 + {
  68 + return String.valueOf(this.availableBuild);
  69 + }
  70 +
  71 + return super.getAvailableVersion();
  72 + }
  73 +
  74 + @Override
  75 + public String getAvailableVersionDate()
  76 + {
  77 + if (this.snapshotDate != null)
  78 + {
  79 + return this.snapshotDate;
  80 + }
  81 +
  82 + return super.getAvailableVersionDate();
  83 + }
  84 +
  85 + @Override
  86 + protected boolean compareArtefact(Map<?, ?> artefact, long bestTimeStamp, Long remoteTimeStamp)
  87 + {
  88 + if (this.isSnapshot())
  89 + {
  90 + String remoteBuild = artefact.get("build").toString();
  91 + String myBuild = LiteLoader.getBranding();
  92 +
  93 + Matcher remoteMatcher = LiteLoaderUpdateSite.SNAPSHOT_REGEX.matcher(remoteBuild);
  94 + Matcher myMatcher = LiteLoaderUpdateSite.SNAPSHOT_REGEX.matcher(myBuild);
  95 +
  96 + if (remoteMatcher.matches() && myMatcher.matches())
  97 + {
  98 + this.currentBuild = Integer.parseInt(myMatcher.group(4));
  99 + this.availableBuild = Integer.parseInt(remoteMatcher.group(4));
  100 + this.snapshotDate = String.format("%1$tY-%1$tm-%1$td %1$tH:%1$tM", new Date(remoteTimeStamp * 1000L));
  101 + return this.availableBuild > this.currentBuild;
  102 + }
  103 + }
  104 +
  105 + return super.compareArtefact(artefact, bestTimeStamp, remoteTimeStamp);
  106 + }
39 107  
40 108 public boolean canForceUpdate(LoaderProperties properties)
41 109 {
... ...
src/main/java/com/mumfrey/liteloader/core/runtime/Obf.java
... ... @@ -26,14 +26,11 @@ public class Obf
26 26 public static final Obf BakedProfilingHandlerList = new Obf("com.mumfrey.liteloader.core.event.ProfilingHandlerList$BakedList" );
27 27 public static final Obf PacketEvents = new Obf("com.mumfrey.liteloader.core.PacketEvents" );
28 28 public static final Obf PacketEventsClient = new Obf("com.mumfrey.liteloader.client.PacketEventsClient" );
29   - public static final Obf LoadingBar = new Obf("com.mumfrey.liteloader.client.gui.startup.LoadingBar" );
30 29 public static final Obf GameProfile = new Obf("com.mojang.authlib.GameProfile" );
31 30 public static final Obf MinecraftMain = new Obf("net.minecraft.client.main.Main" );
32 31 public static final Obf MinecraftServer = new Obf("net.minecraft.server.MinecraftServer" );
33 32 public static final Obf GL11 = new Obf("org.lwjgl.opengl.GL11" );
34 33 public static final Obf RealmsMainScreen = new Obf("com.mojang.realmsclient.RealmsMainScreen" );
35   - public static final Obf init = new Obf("init" );
36   - public static final Obf postInit = new Obf("postInit" );
37 34 public static final Obf constructor = new Obf("<init>" );
38 35  
39 36 // CHECKSTYLE:OFF
... ...
src/main/java/com/mumfrey/liteloader/core/runtime/Packets.java
... ... @@ -5,6 +5,7 @@
5 5 */
6 6 package com.mumfrey.liteloader.core.runtime;
7 7  
  8 +import java.lang.reflect.Field;
8 9 import java.util.HashMap;
9 10 import java.util.Map;
10 11  
... ... @@ -157,125 +158,7 @@ public final class Packets extends Obf
157 158  
158 159 // CHECKSTYLE:ON
159 160  
160   - public static final Packets[] packets = new Packets[] {
161   - CPacketEncryptionResponse,
162   - CPacketLoginStart,
163   - SPacketDisconnectLogin,
164   - SPacketEnableCompression,
165   - SPacketEncryptionRequest,
166   - SPacketLoginSuccess,
167   - CPacketAnimation,
168   - CPacketChatMessage,
169   - CPacketClickWindow,
170   - CPacketClientSettings,
171   - CPacketClientStatus,
172   - CPacketCloseWindow,
173   - CPacketConfirmTeleport,
174   - CPacketConfirmTransaction,
175   - CPacketCreativeInventoryAction,
176   - CPacketCustomPayload,
177   - CPacketEnchantItem,
178   - CPacketEntityAction,
179   - CPacketHeldItemChange,
180   - CPacketInput,
181   - C00Handshake,
182   - CPacketKeepAlive,
183   - CPacketPlayer,
184   - CPacketPlayerPosition,
185   - CPacketPlayerPositionRotation,
186   - CPacketPlayerRotation,
187   - CPacketPlayerAbilities,
188   - CPacketPlayerDigging,
189   - CPacketPlayerTryUseItem,
190   - CPacketPlayerTryUseItemOnBlock,
191   - CPacketResourcePackStatus,
192   - CPacketSpectate,
193   - CPacketSteerBoat,
194   - CPacketTabComplete,
195   - CPacketUpdateSign,
196   - CPacketUseEntity,
197   - CPacketVehicleMove,
198   - SPacketAnimation,
199   - SPacketBlockAction,
200   - SPacketBlockBreakAnim,
201   - SPacketBlockChange,
202   - SPacketCamera,
203   - SPacketChangeGameState,
204   - SPacketChat,
205   - SPacketChunkData,
206   - SPacketCloseWindow,
207   - SPacketCollectItem,
208   - SPacketCombatEvent,
209   - SPacketConfirmTransaction,
210   - SPacketCooldown,
211   - SPacketCustomPayload,
212   - SPacketCustomSound,
213   - SPacketDestroyEntities,
214   - SPacketDisconnect,
215   - SPacketDisplayObjective,
216   - SPacketEffect,
217   - SPacketEntity,
218   - S15PacketEntityRelMove,
219   - S16PacketEntityLook,
220   - S17PacketEntityLookMove,
221   - SPacketEntityAttach,
222   - SPacketEntityEffect,
223   - SPacketEntityEquipment,
224   - SPacketEntityHeadLook,
225   - SPacketEntityMetadata,
226   - SPacketEntityProperties,
227   - SPacketEntityStatus,
228   - SPacketEntityTeleport,
229   - SPacketEntityVelocity,
230   - SPacketExplosion,
231   - SPacketHeldItemChange,
232   - SPacketJoinGame,
233   - SPacketKeepAlive,
234   - SPacketMaps,
235   - SPacketMoveVehicle,
236   - SPacketMultiBlockChange,
237   - SPacketOpenWindow,
238   - SPacketParticles,
239   - SPacketPlayerAbilities,
240   - SPacketPlayerListHeaderFooter,
241   - SPacketPlayerListItem,
242   - SPacketPlayerPosLook,
243   - SPacketRemoveEntityEffect,
244   - SPacketResourcePackSend,
245   - SPacketRespawn,
246   - SPacketScoreboardObjective,
247   - SPacketServerDifficulty,
248   - SPacketSetExperience,
249   - SPacketSetPassengers,
250   - SPacketSetSlot,
251   - SPacketSignEditorOpen,
252   - SPacketSoundEffect,
253   - SPacketSpawnExperienceOrb,
254   - SPacketSpawnGlobalEntity,
255   - SPacketSpawnMob,
256   - SPacketSpawnObject,
257   - SPacketSpawnPainting,
258   - SPacketSpawnPlayer,
259   - SPacketSpawnPosition,
260   - SPacketStatistics,
261   - SPacketTabComplete,
262   - SPacketTeams,
263   - SPacketTimeUpdate,
264   - SPacketTitle,
265   - SPacketUnloadChunk,
266   - SPacketUpdateBossInfo,
267   - SPacketUpdateHealth,
268   - SPacketUpdateScore,
269   - SPacketUpdateTileEntity,
270   - SPacketUseBed,
271   - SPacketWindowItems,
272   - SPacketWindowProperty,
273   - SPacketWorldBorder,
274   - CPacketPing,
275   - CPacketServerQuery,
276   - SPacketPong,
277   - SPacketServerInfo
278   - };
  161 + public static final Packets[] packets = Packets.toArray();
279 162  
280 163 private static int nextPacketIndex;
281 164  
... ... @@ -295,6 +178,24 @@ public final class Packets extends Obf
295 178 this.context = context;
296 179 }
297 180  
  181 + private static Packets[] toArray()
  182 + {
  183 + Field[] fields = Packets.class.getFields();
  184 + Packets[] packets = new Packets[Packets.nextPacketIndex];
  185 + for (int index = 0; index < Packets.nextPacketIndex; index++)
  186 + {
  187 + try
  188 + {
  189 + packets[index] = (Packets)fields[index].get(null);
  190 + }
  191 + catch (Exception ex)
  192 + {
  193 + throw new RuntimeException(ex);
  194 + }
  195 + }
  196 + return packets;
  197 + }
  198 +
298 199 public int getIndex()
299 200 {
300 201 return this.index;
... ...
src/main/java/com/mumfrey/liteloader/interfaces/PanelManager.java
... ... @@ -88,4 +88,14 @@ public interface PanelManager&lt;TParentScreen&gt; extends TickObserver, PostRenderObs
88 88 * Get whether "force update" is enabled
89 89 */
90 90 public abstract boolean isForceUpdateEnabled();
  91 +
  92 + /**
  93 + * Set whether "check for new snapshots" is enabled
  94 + */
  95 + public abstract void setCheckForSnapshotsEnabled(boolean checkForSnapshots);
  96 +
  97 + /**
  98 + * Get whether "check for new snapshots" is enabled
  99 + */
  100 + public abstract boolean isCheckForSnapshotsEnabled();
91 101 }
... ...
src/main/java/com/mumfrey/liteloader/launch/LoaderProperties.java
... ... @@ -99,6 +99,7 @@ public interface LoaderProperties
99 99 public static final String OPTION_FORCE_UPDATE = "allowForceUpdate";
100 100 public static final String OPTION_UPDATE_CHECK_INTR = "updateCheckInterval";
101 101 public static final String OPTION_JINPUT_DISABLE = "disableJInput";
  102 + public static final String OPTION_CHECK_SNAPSHOTS = "checkForNewSnapshots";
102 103  
103 104 // Enumerator properties
104 105 public static final String OPTION_SEARCH_MODS = "search.mods";
... ...
src/main/java/com/mumfrey/liteloader/modconfig/ConfigManager.java
... ... @@ -17,6 +17,7 @@ import com.google.common.collect.Maps;
17 17 import com.google.common.io.Files;
18 18 import com.mumfrey.liteloader.Configurable;
19 19 import com.mumfrey.liteloader.LiteMod;
  20 +import com.mumfrey.liteloader.util.log.LiteLoaderLogger;
20 21  
21 22 /**
22 23 * Registry where we keep the mod config panel classes and config file writers
... ... @@ -165,6 +166,10 @@ public class ConfigManager
165 166 }
166 167 catch (InstantiationException ex) {}
167 168 catch (IllegalAccessException ex) {}
  169 + catch (Exception ex)
  170 + {
  171 + LiteLoaderLogger.severe("Error creating mod configuration panel <%s> for mod %s", this.configPanels.get(modClass), modClass);
  172 + }
168 173  
169 174 // If instantiation fails, remove the panel
170 175 this.configPanels.remove(modClass);
... ...
src/main/java/com/mumfrey/liteloader/permissions/PermissionsManagerClient.java
... ... @@ -47,37 +47,37 @@ public final class PermissionsManagerClient implements PermissionsManager, Plugi
47 47 * Permissions permissible which is a proxy for permissions that are common
48 48 * to all mods.
49 49 */
50   - private static Permissible allMods = new PermissibleAllMods();
51   -
52   - /**
53   - * Minecraft instance
54   - */
55   - private GameEngine<?, ?> engine;
  50 + private final static Permissible allMods = new PermissibleAllMods();
56 51  
57 52 /**
58 53 * List of registered client mods supporting permissions
59 54 */
60   - private Map<String, Permissible> registeredClientMods = new HashMap<String, Permissible>();
  55 + private final Map<String, Permissible> registeredClientMods = new HashMap<String, Permissible>();
61 56  
62 57 /**
63 58 * List of registered client permissions, grouped by mod
64 59 */
65   - private Map<Permissible, TreeSet<String>> registeredClientPermissions = new HashMap<Permissible, TreeSet<String>>();
  60 + private final Map<Permissible, TreeSet<String>> registeredClientPermissions = new HashMap<Permissible, TreeSet<String>>();
66 61  
67 62 /**
68 63 * Objects which listen to events generated by this object
69 64 */
70   - private Set<Permissible> permissibles = new HashSet<Permissible>();
  65 + private final Set<Permissible> permissibles = new HashSet<Permissible>();
71 66  
72 67 /**
73 68 * Local permissions, used when server permissions are not available
74 69 */
75   - private LocalPermissions localPermissions = new LocalPermissions();
  70 + private final LocalPermissions localPermissions = new LocalPermissions();
76 71  
77 72 /**
78 73 * Server permissions, indexed by mod
79 74 */
80   - private Map<String, ServerPermissions> serverPermissions = new HashMap<String, ServerPermissions>();
  75 + private final Map<String, ServerPermissions> serverPermissions = new HashMap<String, ServerPermissions>();
  76 +
  77 + /**
  78 + * Minecraft instance
  79 + */
  80 + private GameEngine<?, ?> engine;
81 81  
82 82 /**
83 83 * Last time onTick was called, used to detect tamper condition if no ticks
... ... @@ -124,8 +124,11 @@ public final class PermissionsManagerClient implements PermissionsManager, Plugi
124 124 if (mod == null) mod = allMods;
125 125 String modName = mod.getPermissibleModName();
126 126  
127   - ServerPermissions modPermissions = this.serverPermissions.get(modName);
128   - return modPermissions != null ? modPermissions : this.localPermissions;
  127 + synchronized (this.serverPermissions)
  128 + {
  129 + ServerPermissions modPermissions = this.serverPermissions.get(modName);
  130 + return modPermissions != null ? modPermissions : this.localPermissions;
  131 + }
129 132 }
130 133  
131 134 /* (non-Javadoc)
... ... @@ -138,8 +141,11 @@ public final class PermissionsManagerClient implements PermissionsManager, Plugi
138 141 if (mod == null) mod = allMods;
139 142 String modName = mod.getPermissibleModName();
140 143  
141   - ServerPermissions modPermissions = this.serverPermissions.get(modName);
142   - return modPermissions != null ? modPermissions.getReplicationTime() : 0;
  144 + synchronized (this.serverPermissions)
  145 + {
  146 + ServerPermissions modPermissions = this.serverPermissions.get(modName);
  147 + return modPermissions != null ? modPermissions.getReplicationTime() : 0;
  148 + }
143 149 }
144 150  
145 151 /**
... ... @@ -208,7 +214,10 @@ public final class PermissionsManagerClient implements PermissionsManager, Plugi
208 214 */
209 215 protected void clearServerPermissions()
210 216 {
211   - this.serverPermissions.clear();
  217 + synchronized (this.serverPermissions)
  218 + {
  219 + this.serverPermissions.clear();
  220 + }
212 221  
213 222 for (Permissible permissible : this.permissibles)
214 223 {
... ... @@ -264,7 +273,10 @@ public final class PermissionsManagerClient implements PermissionsManager, Plugi
264 273 }
265 274 else
266 275 {
267   - this.serverPermissions.remove(modName);
  276 + synchronized (this.serverPermissions)
  277 + {
  278 + this.serverPermissions.remove(modName);
  279 + }
268 280 }
269 281 }
270 282  
... ... @@ -289,12 +301,15 @@ public final class PermissionsManagerClient implements PermissionsManager, Plugi
289 301 }
290 302 }
291 303  
292   - for (Map.Entry<String, ServerPermissions> modPermissions : this.serverPermissions.entrySet())
  304 + synchronized (this.serverPermissions)
293 305 {
294   - if (!modPermissions.getValue().isValid())
  306 + for (Map.Entry<String, ServerPermissions> modPermissions : this.serverPermissions.entrySet())
295 307 {
296   - modPermissions.getValue().notifyRefreshPending();
297   - this.sendPermissionQuery(this.registeredClientMods.get(modPermissions.getKey()));
  308 + if (!modPermissions.getValue().isValid())
  309 + {
  310 + modPermissions.getValue().notifyRefreshPending();
  311 + this.sendPermissionQuery(this.registeredClientMods.get(modPermissions.getKey()));
  312 + }
298 313 }
299 314 }
300 315  
... ... @@ -336,7 +351,10 @@ public final class PermissionsManagerClient implements PermissionsManager, Plugi
336 351  
337 352 if (modPermissions != null && modPermissions.getModName() != null)
338 353 {
339   - this.serverPermissions.put(modPermissions.getModName(), modPermissions);
  354 + synchronized (this.serverPermissions)
  355 + {
  356 + this.serverPermissions.put(modPermissions.getModName(), modPermissions);
  357 + }
340 358  
341 359 Permissible permissible = this.registeredClientMods.get(modPermissions.getModName());
342 360 if (permissible != null) permissible.onPermissionsChanged(this);
... ...
src/main/java/com/mumfrey/liteloader/update/UpdateSite.java
... ... @@ -160,6 +160,11 @@ public class UpdateSite implements Comparator&lt;Long&gt;
160 160 }
161 161 }
162 162 }
  163 +
  164 + public boolean isSnapshot()
  165 + {
  166 + return false;
  167 + }
163 168  
164 169 /**
165 170 * Gets whether a check is in progress
... ... @@ -319,7 +324,7 @@ public class UpdateSite implements Comparator&lt;Long&gt;
319 324 */
320 325 private void handleVersionData(Object key, Map<?, ?> value)
321 326 {
322   - if ("artefacts".equals(key))
  327 + if ((this.isSnapshot() && "snapshots".equals(key)) || (!this.isSnapshot() && "artefacts".equals(key)))
323 328 {
324 329 if (value.containsKey(this.artefact))
325 330 {
... ... @@ -387,7 +392,7 @@ public class UpdateSite implements Comparator&lt;Long&gt;
387 392 this.availableVersion = artefact.get("version").toString();
388 393 this.availableVersionDate = DateFormat.getDateTimeInstance().format(new Date(remoteTimeStamp * 1000L));
389 394 this.availableVersionURL = this.createArtefactURL(artefact.get("file").toString());
390   - this.updateAvailable = this.compareTimeStamps(bestTimeStamp, remoteTimeStamp);
  395 + this.updateAvailable = this.compareArtefact(artefact, bestTimeStamp, remoteTimeStamp);
391 396  
392 397 return true;
393 398 }
... ... @@ -395,6 +400,11 @@ public class UpdateSite implements Comparator&lt;Long&gt;
395 400 return false;
396 401 }
397 402  
  403 + protected boolean compareArtefact(Map<?, ?> artefact, long bestTimeStamp, Long remoteTimeStamp)
  404 + {
  405 + return this.compareTimeStamps(bestTimeStamp, remoteTimeStamp);
  406 + }
  407 +
398 408 /**
399 409 * @param bestTimeStamp
400 410 * @param remoteTimeStamp
... ...
src/main/resources/assets/liteloader/lang/en_us.lang
... ... @@ -16,6 +16,9 @@ gui.settings.notabhide.help1=Only applies if the above option is also checked
16 16 gui.settings.forceupdate.label=Periodically Check For Updates
17 17 gui.settings.forceupdate.help1=This option is §cexperimental§r and may not work properly
18 18 gui.settings.forceupdate.help2=yet, it also enables the "force update" capability.
  19 +gui.settings.checkforsnapshots.label=Check For New Snapshots
  20 +gui.settings.checkforsnapshots.help1=If this option is enabled, LiteLoader will check
  21 +gui.settings.checkforsnapshots.help2=for new SNAPSHOT releases each time it it launched
19 22  
20 23 gui.about.taboptions=§nLiteLoader Panel Options
21 24  
... ... @@ -83,7 +86,11 @@ gui.log.uploadsuccess=Upload succeeded, log available at
83 86 gui.log.closedialog=Close
84 87  
85 88 gui.error.title=Startup errors for %s
  89 +gui.error.copytoclipboard=Copy error to clipboard
86 90  
87 91 gui.error.tooltip=%d mod startup error(s) detected (%d critical)
88 92  
89   -gui.notifications.updateavailable=LiteLoader Update Available!
90 93 \ No newline at end of file
  94 +gui.notifications.updateavailable=LiteLoader Update Available!
  95 +gui.notifications.newsnapshotavailable=!!New Snapshot Available:%nBuild #%s (%s)
  96 +
  97 +gui.invalidvalue=§cInvalid value!
91 98 \ No newline at end of file
... ...
src/main/resources/mixins.liteloader.core.json
1 1 {
2 2 "required": true,
3   - "minVersion": "0.5.3",
  3 + "minVersion": "0.7",
  4 + "compatibilityLevel": "JAVA_8",
4 5 "target": "@env(DEFAULT)",
5 6 "package": "com.mumfrey.liteloader.common.mixin",
6 7 "refmap": "mixins.liteloader.core.refmap.json",
... ...