aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java4
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscgui/GuiEnchantColour.java39
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/options/NEUConfig.java3
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/overlays/TextOverlay.java3
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/overlays/TimersOverlay.java162
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/util/SBInfo.java7
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/util/TabListUtils.java21
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/commands/help/SettingsCommand.kt6
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/events/SidebarChangeEvent.kt25
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/events/TabListChangeEvent.kt26
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodo.kt96
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodoEditor.kt257
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodoHud.kt116
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodoList.kt99
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/util/MoulConfig.kt39
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/util/TemplateUtil.kt99
-rw-r--r--src/main/resources/assets/notenoughupdates/gui/customtodos/edit.xml122
-rw-r--r--src/main/resources/assets/notenoughupdates/gui/customtodos/overview.xml54
18 files changed, 1067 insertions, 111 deletions
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java b/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java
index f878dbf1..f20c5338 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java
@@ -56,6 +56,7 @@ import io.github.moulberry.notenoughupdates.recipes.RecipeGenerator;
import io.github.moulberry.notenoughupdates.util.Utils;
import io.github.moulberry.notenoughupdates.util.brigadier.BrigadierRoot;
import io.github.moulberry.notenoughupdates.util.hypixelapi.HypixelItemAPI;
+import io.github.moulberry.notenoughupdates.util.kotlin.KotlinTypeAdapterFactory;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiScreen;
@@ -150,7 +151,8 @@ public class NotEnoughUpdates {
put("MYTHIC", EnumChatFormatting.LIGHT_PURPLE.toString());
}};
public static ProfileViewer profileViewer;
- private final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create();
+ private final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation()
+ .registerTypeAdapterFactory(KotlinTypeAdapterFactory.INSTANCE).create();
public NEUManager manager;
public NEUOverlay overlay;
public NEUConfig config;
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscgui/GuiEnchantColour.java b/src/main/java/io/github/moulberry/notenoughupdates/miscgui/GuiEnchantColour.java
index 41208681..174650d9 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscgui/GuiEnchantColour.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscgui/GuiEnchantColour.java
@@ -25,10 +25,12 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
+import info.bliki.api.Template;
import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
import io.github.moulberry.notenoughupdates.core.util.lerp.LerpingInteger;
import io.github.moulberry.notenoughupdates.itemeditor.GuiElementTextField;
import io.github.moulberry.notenoughupdates.options.NEUConfig;
+import io.github.moulberry.notenoughupdates.util.TemplateUtil;
import io.github.moulberry.notenoughupdates.util.Utils;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Gui;
@@ -51,6 +53,7 @@ import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
+import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -527,16 +530,7 @@ public class GuiEnchantColour extends GuiScreen {
private boolean validShareContents() {
try {
String base64 = (String) Toolkit.getDefaultToolkit().getSystemClipboard().getData(DataFlavor.stringFlavor);
-
- if (base64.length() <= sharePrefix.length()) return false;
-
- base64 = base64.trim();
-
- try {
- return new String(Base64.getDecoder().decode(base64)).startsWith(sharePrefix);
- } catch (IllegalArgumentException e) {
- return false;
- }
+ return Objects.equals(TemplateUtil.getTemplatePrefix(base64), sharePrefix);
} catch (HeadlessException | IOException | UnsupportedFlavorException | IllegalStateException e) {
return false;
}
@@ -647,26 +641,7 @@ public class GuiEnchantColour extends GuiScreen {
} catch (HeadlessException | IOException | UnsupportedFlavorException e) {
return;
}
-
- if (base64.length() <= sharePrefix.length()) return;
-
- base64 = base64.trim();
-
- String jsonString;
- try {
- jsonString = new String(Base64.getDecoder().decode(base64));
- if (!jsonString.startsWith(sharePrefix)) return;
- jsonString = jsonString.substring(sharePrefix.length());
- } catch (IllegalArgumentException e) {
- return;
- }
-
- JsonArray presetArray;
- try {
- presetArray = new JsonParser().parse(jsonString).getAsJsonArray();
- } catch (IllegalStateException | JsonParseException e) {
- return;
- }
+ JsonArray presetArray = TemplateUtil.maybeDecodeTemplate(sharePrefix, base64, JsonArray.class);
ArrayList<String> presetList = new ArrayList<>();
for (int i = 0; i < presetArray.size(); i++) {
@@ -690,8 +665,8 @@ public class GuiEnchantColour extends GuiScreen {
for (String s : result) {
jsonArray.add(new JsonPrimitive(s));
}
- String base64String = Base64.getEncoder().encodeToString((sharePrefix +
- jsonArray).getBytes(StandardCharsets.UTF_8));
+
+ String base64String = TemplateUtil.encodeTemplate(sharePrefix, jsonArray);
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(base64String), null);
} else if (mouseY > guiTopSidebar + 2 + (24 * 2) && mouseY < guiTopSidebar + 20 + 2 + 24 * 2) {
NotEnoughUpdates.INSTANCE.config.hidden.enchantColours = NEUConfig.createDefaultEnchantColours();
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/options/NEUConfig.java b/src/main/java/io/github/moulberry/notenoughupdates/options/NEUConfig.java
index 2d64db59..200b1c0c 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/options/NEUConfig.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/options/NEUConfig.java
@@ -35,6 +35,7 @@ import io.github.moulberry.notenoughupdates.miscfeatures.IQTest;
import io.github.moulberry.notenoughupdates.miscgui.GuiEnchantColour;
import io.github.moulberry.notenoughupdates.miscgui.GuiInvButtonEditor;
import io.github.moulberry.notenoughupdates.miscgui.NEUOverlayPlacements;
+import io.github.moulberry.notenoughupdates.miscgui.customtodos.CustomTodo;
import io.github.moulberry.notenoughupdates.options.customtypes.NEUDebugFlag;
import io.github.moulberry.notenoughupdates.options.separatesections.AHGraph;
import io.github.moulberry.notenoughupdates.options.separatesections.AHTweaks;
@@ -493,6 +494,8 @@ public class NEUConfig extends Config {
public static class Hidden {
@Expose
+ public List<CustomTodo> customTodos = new ArrayList<>();
+ @Expose
public HashMap<String, NEUConfig.HiddenProfileSpecific> profileSpecific = new HashMap<>();
@Expose
public HashMap<String, NEUConfig.HiddenLocationSpecific> locationSpecific = new HashMap<>();
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/overlays/TextOverlay.java b/src/main/java/io/github/moulberry/notenoughupdates/overlays/TextOverlay.java
index cefb6929..c4768b3e 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/overlays/TextOverlay.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/overlays/TextOverlay.java
@@ -184,6 +184,9 @@ public abstract class TextOverlay {
for (String s2 : s.split("\n")) {
Vector2f pos = new Vector2f(x + paddingX, y + paddingY + yOff);
renderLine(s2, pos, dummy);
+ if (s2.startsWith("CUSTOM")) {
+ s2 = s2.split(":", 2)[1];
+ }
int xPad = (int) pos.x;
int yPad = (int) pos.y;
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/overlays/TimersOverlay.java b/src/main/java/io/github/moulberry/notenoughupdates/overlays/TimersOverlay.java
index bf73e2ec..ca244303 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/overlays/TimersOverlay.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/overlays/TimersOverlay.java
@@ -22,6 +22,7 @@ package io.github.moulberry.notenoughupdates.overlays;
import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
import io.github.moulberry.notenoughupdates.core.config.Position;
import io.github.moulberry.notenoughupdates.events.SlotClickEvent;
+import io.github.moulberry.notenoughupdates.miscgui.customtodos.CustomTodoHud;
import io.github.moulberry.notenoughupdates.options.NEUConfig;
import io.github.moulberry.notenoughupdates.util.ItemUtils;
import io.github.moulberry.notenoughupdates.util.SBInfo;
@@ -168,91 +169,97 @@ public class TimersOverlay extends TextTabOverlay {
return;
}
GlStateManager.enableDepth();
-
ItemStack icon = null;
String clean = Utils.cleanColour(line);
String beforeColon = clean.split(":")[0];
- switch (beforeColon) {
- case "Cakes":
- icon = CAKES_ICON;
- break;
- case "Puzzler":
- icon = PUZZLER_ICON;
- break;
- case "Godpot":
- icon = NotEnoughUpdates.INSTANCE.manager.jsonToStack(NotEnoughUpdates.INSTANCE.manager
- .getItemInformation()
- .get("GOD_POTION"));
- break;
- case "Fetchur": {
- if (FETCHUR_ICONS == null) {
- FETCHUR_ICONS = new ItemStack[]{
- new ItemStack(Blocks.wool, 50, 14),
- new ItemStack(Blocks.stained_glass, 20, 4),
- new ItemStack(Items.compass, 1, 0),
- new ItemStack(Items.prismarine_crystals, 20, 0),
- new ItemStack(Items.fireworks, 1, 0),
- NotEnoughUpdates.INSTANCE.manager.jsonToStack(NotEnoughUpdates.INSTANCE.manager
- .getItemInformation()
- .get("CHEAP_COFFEE")),
- new ItemStack(Items.oak_door, 1, 0),
- new ItemStack(Items.rabbit_foot, 3, 0),
- NotEnoughUpdates.INSTANCE.manager.jsonToStack(NotEnoughUpdates.INSTANCE.manager
- .getItemInformation()
- .get("SUPERBOOM_TNT")),
- new ItemStack(Blocks.pumpkin, 1, 0),
- new ItemStack(Items.flint_and_steel, 1, 0),
- new ItemStack(Items.emerald, 50, 0),
- //new ItemStack(Items.ender_pearl, 16, 0)
- };
- }
+ if (beforeColon.startsWith("CUSTOM")) {
+ var item = Item.getByNameOrId(beforeColon.substring(6));
+ if (item == null) {
+ item = Items.paper;
+ }
+ icon = new ItemStack(item);
+ } else
+ switch (beforeColon) {
+ case "Cakes":
+ icon = CAKES_ICON;
+ break;
+ case "Puzzler":
+ icon = PUZZLER_ICON;
+ break;
+ case "Godpot":
+ icon = NotEnoughUpdates.INSTANCE.manager.jsonToStack(NotEnoughUpdates.INSTANCE.manager
+ .getItemInformation()
+ .get("GOD_POTION"));
+ break;
+ case "Fetchur": {
+ if (FETCHUR_ICONS == null) {
+ FETCHUR_ICONS = new ItemStack[]{
+ new ItemStack(Blocks.wool, 50, 14),
+ new ItemStack(Blocks.stained_glass, 20, 4),
+ new ItemStack(Items.compass, 1, 0),
+ new ItemStack(Items.prismarine_crystals, 20, 0),
+ new ItemStack(Items.fireworks, 1, 0),
+ NotEnoughUpdates.INSTANCE.manager.jsonToStack(NotEnoughUpdates.INSTANCE.manager
+ .getItemInformation()
+ .get("CHEAP_COFFEE")),
+ new ItemStack(Items.oak_door, 1, 0),
+ new ItemStack(Items.rabbit_foot, 3, 0),
+ NotEnoughUpdates.INSTANCE.manager.jsonToStack(NotEnoughUpdates.INSTANCE.manager
+ .getItemInformation()
+ .get("SUPERBOOM_TNT")),
+ new ItemStack(Blocks.pumpkin, 1, 0),
+ new ItemStack(Items.flint_and_steel, 1, 0),
+ new ItemStack(Blocks.emerald_ore, 50, 0),
+ //new ItemStack(Items.ender_pearl, 16, 0)
+ };
+ }
- ZonedDateTime currentTimeEST = ZonedDateTime.now(ZoneId.of("America/Atikokan"));
+ ZonedDateTime currentTimeEST = ZonedDateTime.now(ZoneId.of("America/Atikokan"));
- long fetchurIndex = ((currentTimeEST.getDayOfMonth() + 1) % 12) - 1;
- //Added because disabled fetchur and enabled it again but it was showing the wrong item
- //Lets see if this stays correct
+ long fetchurIndex = ((currentTimeEST.getDayOfMonth() + 1) % 12) - 1;
+ //Added because disabled fetchur and enabled it again but it was showing the wrong item
+ //Lets see if this stays correct
- if (fetchurIndex < 0) fetchurIndex += 12;
+ if (fetchurIndex < 0) fetchurIndex += 12;
- icon = FETCHUR_ICONS[(int) fetchurIndex];
- break;
+ icon = FETCHUR_ICONS[(int) fetchurIndex];
+ break;
+ }
+ case "Commissions":
+ icon = COMMISSIONS_ICON;
+ break;
+ case "Experiments":
+ icon = EXPERIMENTS_ICON;
+ break;
+ case "Cookie Buff":
+ icon = COOKIE_ICON;
+ break;
+ case "Mithril Powder":
+ icon = NotEnoughUpdates.INSTANCE.manager.jsonToStack(NotEnoughUpdates.INSTANCE.manager
+ .getItemInformation()
+ .get("MITHRIL_ORE"));
+ break;
+ case "Gemstone Powder":
+ icon = NotEnoughUpdates.INSTANCE.manager.jsonToStack(NotEnoughUpdates.INSTANCE.manager
+ .getItemInformation()
+ .get("PERFECT_AMETHYST_GEM"));
+ break;
+ case "Heavy Pearls":
+ icon = NotEnoughUpdates.INSTANCE.manager.jsonToStack(NotEnoughUpdates.INSTANCE.manager
+ .getItemInformation()
+ .get("HEAVY_PEARL"));
+ break;
+ case "Free Rift Infusion":
+ icon = new ItemStack(Blocks.double_plant, 1, 1);
+ break;
+ case "Crimson Isle Quests":
+ icon = QUEST_ICON;
+ break;
+ case "NPC Buy Daily Limit":
+ icon = SHOP_ICON;
+ break;
}
- case "Commissions":
- icon = COMMISSIONS_ICON;
- break;
- case "Experiments":
- icon = EXPERIMENTS_ICON;
- break;
- case "Cookie Buff":
- icon = COOKIE_ICON;
- break;
- case "Mithril Powder":
- icon = NotEnoughUpdates.INSTANCE.manager.jsonToStack(NotEnoughUpdates.INSTANCE.manager
- .getItemInformation()
- .get("MITHRIL_ORE"));
- break;
- case "Gemstone Powder":
- icon = NotEnoughUpdates.INSTANCE.manager.jsonToStack(NotEnoughUpdates.INSTANCE.manager
- .getItemInformation()
- .get("PERFECT_AMETHYST_GEM"));
- break;
- case "Heavy Pearls":
- icon = NotEnoughUpdates.INSTANCE.manager.jsonToStack(NotEnoughUpdates.INSTANCE.manager
- .getItemInformation()
- .get("HEAVY_PEARL"));
- break;
- case "Free Rift Infusion":
- icon = new ItemStack(Blocks.double_plant, 1, 1);
- break;
- case "Crimson Isle Quests":
- icon = QUEST_ICON;
- break;
- case "NPC Buy Daily Limit":
- icon = SHOP_ICON;
- break;
- }
if (icon != null) {
GlStateManager.pushMatrix();
@@ -1047,6 +1054,9 @@ public class TimersOverlay extends TextTabOverlay {
overlayStrings.add(text);
}
}
+
+ CustomTodoHud.processInto(overlayStrings);
+
if (overlayStrings.isEmpty()) overlayStrings = null;
}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/SBInfo.java b/src/main/java/io/github/moulberry/notenoughupdates/util/SBInfo.java
index 82708669..ad0000d4 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/util/SBInfo.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/util/SBInfo.java
@@ -23,6 +23,7 @@ import com.google.common.reflect.TypeToken;
import com.google.gson.JsonObject;
import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe;
+import io.github.moulberry.notenoughupdates.events.SidebarChangeEvent;
import io.github.moulberry.notenoughupdates.miscfeatures.CookieWarning;
import io.github.moulberry.notenoughupdates.miscfeatures.customblockzones.LocationChangeEvent;
import io.github.moulberry.notenoughupdates.miscgui.minionhelper.MinionHelperManager;
@@ -310,6 +311,7 @@ public class SBInfo {
public ArrayList<String> completedQuests = new ArrayList<>();
private static final Pattern SKILL_LEVEL_PATTERN = Pattern.compile("([^0-9:]+) (\\d{1,2})");
+ private static List<String> lastLines = new ArrayList<>();
public void tick() {
@@ -364,6 +366,11 @@ public class SBInfo {
try {
List<String> lines = SidebarUtil.readSidebarLines(true, false);
+
+ if (lines.equals(lastLines)) return;
+ new SidebarChangeEvent(lines, lastLines).post();
+ lastLines = lines;
+
boolean tempIsInDungeon = false;
for (String line : lines) {
if (line.contains("Cleared:") && line.contains("%")) {
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/TabListUtils.java b/src/main/java/io/github/moulberry/notenoughupdates/util/TabListUtils.java
index a0266122..f36db034 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/util/TabListUtils.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/util/TabListUtils.java
@@ -21,10 +21,14 @@ package io.github.moulberry.notenoughupdates.util;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Ordering;
+import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe;
+import io.github.moulberry.notenoughupdates.events.TabListChangeEvent;
import net.minecraft.client.Minecraft;
import net.minecraft.client.network.NetworkPlayerInfo;
import net.minecraft.scoreboard.ScorePlayerTeam;
import net.minecraft.world.WorldSettings;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import net.minecraftforge.fml.common.gameevent.TickEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@@ -32,6 +36,7 @@ import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
+@NEUAutoSubscribe
public class TabListUtils {
private static final Ordering<NetworkPlayerInfo> playerOrdering = Ordering.from(new PlayerComparator());
@@ -55,7 +60,23 @@ public class TabListUtils {
}
}
+ public static List<String> tabList = new ArrayList<>();
+ public static List<String> tabListLastTick = new ArrayList<>();
+
+ @SubscribeEvent
+ public void onTick(TickEvent.ClientTickEvent event) {
+ if (Minecraft.getMinecraft().thePlayer == null) return;
+ if (event.phase != TickEvent.Phase.END) return;
+ tabListLastTick = tabList;
+ tabList = getTabList0();
+ new TabListChangeEvent(tabList, tabListLastTick).post();
+ }
+
public static List<String> getTabList() {
+ return tabList;
+ }
+
+ private List<String> getTabList0() {
List<NetworkPlayerInfo> players =
playerOrdering.sortedCopy(Minecraft.getMinecraft().thePlayer.sendQueue.getPlayerInfoMap());
diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/commands/help/SettingsCommand.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/commands/help/SettingsCommand.kt
index eb5d51b1..40919a8e 100644
--- a/src/main/kotlin/io/github/moulberry/notenoughupdates/commands/help/SettingsCommand.kt
+++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/commands/help/SettingsCommand.kt
@@ -21,6 +21,7 @@ package io.github.moulberry.notenoughupdates.commands.help
import io.github.moulberry.moulconfig.GuiTextures
import io.github.moulberry.moulconfig.annotations.ConfigOption
+import io.github.moulberry.moulconfig.common.MyResourceLocation
import io.github.moulberry.moulconfig.gui.GuiOptionEditor
import io.github.moulberry.moulconfig.gui.GuiScreenElementWrapper
import io.github.moulberry.moulconfig.gui.MoulConfigEditor
@@ -34,7 +35,6 @@ import io.github.moulberry.notenoughupdates.miscfeatures.IQTest
import io.github.moulberry.notenoughupdates.options.NEUConfig
import io.github.moulberry.notenoughupdates.util.brigadier.*
import net.minecraft.client.gui.GuiScreen
-import net.minecraft.util.ResourceLocation
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import java.lang.reflect.Field
@@ -118,6 +118,7 @@ object SettingsCommand {
return object : GuiScreenElementWrapper(createConfigElement(search)) {
}
}
+
fun createConfigElement(search: String): MoulConfigEditor<NEUConfig> {
val processor = BlockingMoulConfigProcessor()
BuiltinMoulConfigGuis.addProcessors(processor)
@@ -131,7 +132,8 @@ object SettingsCommand {
lastEditor = editor
return editor
}
+
init {
- GuiTextures.setTextureRoot(ResourceLocation("notenoughupdates:core"))
+ GuiTextures.setTextureRoot(MyResourceLocation("notenoughupdates", "core"))
}
}
diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/events/SidebarChangeEvent.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/events/SidebarChangeEvent.kt
new file mode 100644
index 00000000..0dc4b9f6
--- /dev/null
+++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/events/SidebarChangeEvent.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 NotEnoughUpdates contributors
+ *
+ * This file is part of NotEnoughUpdates.
+ *
+ * NotEnoughUpdates is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * NotEnoughUpdates 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 NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package io.github.moulberry.notenoughupdates.events
+
+class SidebarChangeEvent(
+ val lines: List<String>,
+ val lastLines: List<String>,
+) : NEUEvent()
diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/events/TabListChangeEvent.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/events/TabListChangeEvent.kt
new file mode 100644
index 00000000..b5677598
--- /dev/null
+++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/events/TabListChangeEvent.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 NotEnoughUpdates contributors
+ *
+ * This file is part of NotEnoughUpdates.
+ *
+ * NotEnoughUpdates is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * NotEnoughUpdates 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 NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package io.github.moulberry.notenoughupdates.events
+
+class TabListChangeEvent(
+ val lastLines: List<String>,
+ val newLines: List<String>,
+) : NEUEvent() {
+}
diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodo.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodo.kt
new file mode 100644
index 00000000..7f8c6d1a
--- /dev/null
+++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodo.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2023 NotEnoughUpdates contributors
+ *
+ * This file is part of NotEnoughUpdates.
+ *
+ * NotEnoughUpdates is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * NotEnoughUpdates 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 NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package io.github.moulberry.notenoughupdates.miscgui.customtodos
+
+import com.google.gson.annotations.Expose
+import io.github.moulberry.notenoughupdates.util.SBInfo
+import io.github.moulberry.notenoughupdates.util.TemplateUtil
+import io.github.moulberry.notenoughupdates.util.kotlin.KSerializable
+
+@KSerializable
+data class CustomTodo(
+ @Expose var label: String,
+ @Expose var timer: Int,
+ @Expose var trigger: String,
+ @Expose var icon: String,
+ @Expose var isResetOffset: Boolean,
+ @Expose var triggerTarget: TriggerTarget = TriggerTarget.CHAT,
+ @Expose var triggerMatcher: TriggerMatcher = TriggerMatcher.CONTAINS,
+ @Expose var readyAt: MutableMap<String, Long> = mutableMapOf(),
+ @Expose var enabled: MutableMap<String, Boolean> = mutableMapOf(),
+) {
+ enum class TriggerMatcher {
+ REGEX, STARTS_WITH, CONTAINS, EQUALS
+ }
+
+ enum class TriggerTarget {
+ CHAT, ACTIONBAR, TAB_LIST, SIDEBAR
+ }
+
+ fun isValid(): Boolean {
+ return timer >= 0 && !trigger.isBlank()
+ }
+
+ fun setDoneNow() {
+ val t = System.currentTimeMillis()
+ readyAt[SBInfo.getInstance().currentProfile ?: return] =
+ if (isResetOffset) {
+ t + DAY - t % DAY + timer * 1000L
+ } else {
+ t + timer * 1000L
+ }
+ }
+
+ var readyAtOnCurrentProfile: Long?
+ get() {
+ return readyAt[SBInfo.getInstance().currentProfile ?: return null]
+ }
+ set(value) {
+ readyAt[SBInfo.getInstance().currentProfile ?: return] = value ?: return
+ }
+
+ var isEnabledOnCurrentProfile: Boolean
+ get() {
+ return enabled[SBInfo.getInstance().currentProfile ?: return true] ?: true
+ }
+ set(value) {
+ enabled[SBInfo.getInstance().currentProfile ?: return] = value
+ }
+
+
+ companion object {
+ val templatePrefix = "NEU:CUSTOMTODO/"
+ val DAY = (24 * 60 * 60 * 100)
+ fun fromTemplate(data: String): CustomTodo? {
+ return TemplateUtil.maybeDecodeTemplate(templatePrefix, data, CustomTodo::class.java)
+ ?.also {
+ it.enabled.clear()
+ it.readyAt.clear()
+ }
+ }
+ }
+
+ fun toTemplate(): String {
+ return TemplateUtil.encodeTemplate(
+ templatePrefix,
+ this.copy(enabled = mutableMapOf(), readyAt = mutableMapOf())
+ )
+ }
+}
diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodoEditor.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodoEditor.kt
new file mode 100644
index 00000000..1c5c16ed
--- /dev/null
+++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodoEditor.kt
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2023 NotEnoughUpdates contributors
+ *
+ * This file is part of NotEnoughUpdates.
+ *
+ * NotEnoughUpdates is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * NotEnoughUpdates 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 NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package io.github.moulberry.notenoughupdates.miscgui.customtodos
+
+import io.github.moulberry.moulconfig.common.IItemStack
+import io.github.moulberry.moulconfig.forge.ForgeItemStack
+import io.github.moulberry.moulconfig.internal.ClipboardUtils
+import io.github.moulberry.moulconfig.observer.ObservableList
+import io.github.moulberry.moulconfig.xml.Bind
+import io.github.moulberry.moulconfig.xml.XMLUniverse
+import io.github.moulberry.notenoughupdates.util.SBInfo
+import io.github.moulberry.notenoughupdates.util.Utils
+import io.github.moulberry.notenoughupdates.util.loadResourceLocation
+import net.minecraft.client.Minecraft
+import net.minecraft.init.Items
+import net.minecraft.item.Item
+import net.minecraft.item.ItemStack
+import net.minecraft.util.ResourceLocation
+
+class CustomTodoEditor(
+ val from: CustomTodo,
+ val todos: ObservableList<CustomTodoEditor>,
+ val xmlUniverse: XMLUniverse
+) {
+ @field:Bind
+ var label: String = from.label
+
+ @field:Bind
+ var enabled: Boolean = from.isEnabledOnCurrentProfile
+
+ @field:Bind
+ var timer: String = from.timer.toString()
+
+ @field:Bind
+ var trigger: String = from.trigger
+
+ @field:Bind
+ var icon: String = from.icon
+
+ @field:Bind
+ var isResetOffset: Boolean = from.isResetOffset
+
+ var target = from.triggerTarget
+ var matchMode = from.triggerMatcher
+ var lastCustomTodo: CustomTodo? = null
+
+ fun into(): CustomTodo {
+ val nextCustomTodo = CustomTodo(
+ label,
+ timer.toIntOrNull() ?: 0,
+ trigger,
+ icon,
+ isResetOffset,
+ target, matchMode,
+ from.readyAt,
+ from.enabled.toMutableMap().also { it[SBInfo.getInstance().currentProfile ?: return@also] = enabled }
+ )
+ if (nextCustomTodo != lastCustomTodo) {
+ lastCustomTodo = nextCustomTodo
+ CustomTodoList(todos, xmlUniverse).save()
+ }
+ return nextCustomTodo
+ }
+
+ @Bind
+ fun setChat() {
+ target = CustomTodo.TriggerTarget.CHAT
+ }
+
+ @Bind
+ fun setActionbar() {
+ target = CustomTodo.TriggerTarget.ACTIONBAR
+ }
+
+ @Bind
+ fun setSidebar() {
+ target = CustomTodo.TriggerTarget.SIDEBAR
+ }
+
+ @Bind
+ fun setTablist() {
+ target = CustomTodo.TriggerTarget.TAB_LIST
+ }
+
+ private fun colorFromBool(b: Boolean): String {
+ return if (b) "§a" else "§c"
+ }
+
+ @Bind
+ fun getChat(): String {
+ return colorFromBool(target == CustomTodo.TriggerTarget.CHAT) + "Chat"
+ }
+
+ @Bind
+ fun getActionbar(): String {
+ return colorFromBool(target == CustomTodo.TriggerTarget.ACTIONBAR) + "Actionbar"
+ }
+
+ @Bind
+ fun getSidebar(): String {
+ return colorFromBool(target == CustomTodo.TriggerTarget.SIDEBAR) + "Sidebar"
+ }
+
+ @Bind
+ fun getTablist(): String {
+ return colorFromBool(target == CustomTodo.TriggerTarget.TAB_LIST) + "Tablist"
+ }
+
+ @Bind
+ fun setRegex() {
+ matchMode = CustomTodo.TriggerMatcher.REGEX
+ }
+
+ @Bind
+ fun setStartsWith() {
+ matchMode = CustomTodo.TriggerMatcher.STARTS_WITH
+ }
+
+ @Bind
+ fun setContains() {
+ matchMode = CustomTodo.TriggerMatcher.CONTAINS
+ }
+
+ @Bind
+ fun setEquals() {
+ matchMode = CustomTodo.TriggerMatcher.EQUALS
+ }
+
+ @Bind
+ fun getRegex(): String {
+ return colorFromBool(matchMode == CustomTodo.TriggerMatcher.REGEX) + "Regex"
+ }
+
+ @Bind
+ fun getStartsWith(): String {
+ return colorFromBool(matchMode == CustomTodo.TriggerMatcher.STARTS_WITH) + "Starts With"
+ }
+
+ @Bind
+ fun getContains(): String {
+ return colorFromBool(matchMode == CustomTodo.TriggerMatcher.CONTAINS) + "Contains"
+ }
+
+ @Bind
+ fun getEquals(): String {
+ return colorFromBool(matchMode == CustomTodo.TriggerMatcher.EQUALS) + "Equals"
+ }
+
+ @Bind
+ fun getItemStack(): IItemStack {
+ val item = Item.getByNameOrId(icon) ?: (Items.paper)
+ return ForgeItemStack.of(ItemStack(item))
+ }
+
+ @Bind
+ fun copyTemplate() {
+ ClipboardUtils.copyToClipboard(into().toTemplate())
+ }
+
+ @Bind
+ fun markAsReady() {
+ from.readyAtOnCurrentProfile = System.currentTimeMillis()
+ }
+
+ @Bind
+ fun markAsCompleted() {
+ from.setDoneNow()
+ }
+
+ @Bind
+ fun getFancyTime(): String {
+ val tint = timer.toIntOrNull() ?: return "§3Invalid Time"
+ val timeFormat = Utils.prettyTime(tint * 1000L)
+ if (isResetOffset) {
+ return "Reset $timeFormat after 00:00 GMT"
+ }
+ return "Reset $timeFormat after completion"
+ }
+
+ fun changeTimer(value: Int) {
+ timer = ((timer.toIntOrNull() ?: 0) + value).coerceAtLeast(0).toString()
+ }
+
+ @Bind
+ fun plusDay() {
+ changeTimer(60 * 60 * 24)
+ }
+
+ @Bind
+ fun minusDay() {
+ changeTimer(-60 * 60 * 24)
+ }
+
+ @Bind
+ fun minusHour() {
+ changeTimer(-60 * 60)
+ }
+
+ @Bind
+ fun plusHour() {
+ changeTimer(60 * 60)
+ }
+
+ @Bind
+ fun plusMinute() {
+ changeTimer(60)
+ }
+
+ @Bind
+ fun minusMinute() {
+ changeTimer(-60)
+ }
+
+ @Bind
+ fun delete() {
+ todos.remove(this)
+ CustomTodoList(todos, xmlUniverse).save()
+ }
+
+ @Bind
+ fun getTitle(): String {
+ return "Editing ${into().label}"
+ }
+
+ @Bind
+ fun close() {
+ Minecraft.getMinecraft().displayGuiScreen(
+ CustomTodoList(
+ todos, xmlUniverse
+ ).open()
+ )
+ }
+
+ @Bind
+ fun edit() {
+ Minecraft.getMinecraft().displayGuiScreen(
+ xmlUniverse.loadResourceLocation(this, ResourceLocation("notenoughupdates:gui/customtodos/edit.xml"))
+ )
+ }
+}
diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodoHud.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodoHud.kt
new file mode 100644
index 00000000..591bcedb
--- /dev/null
+++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodoHud.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2023 NotEnoughUpdates contributors
+ *
+ * This file is part of NotEnoughUpdates.
+ *
+ * NotEnoughUpdates is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * NotEnoughUpdates 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 NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package io.github.moulberry.notenoughupdates.miscgui.customtodos
+
+import io.github.moulberry.notenoughupdates.NotEnoughUpdates
+import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe
+import io.github.moulberry.notenoughupdates.core.util.StringUtils
+import io.github.moulberry.notenoughupdates.events.SidebarChangeEvent
+import io.github.moulberry.notenoughupdates.events.TabListChangeEvent
+import io.github.moulberry.notenoughupdates.util.Utils
+import net.minecraft.util.EnumChatFormatting
+import net.minecraftforge.client.event.ClientChatReceivedEvent
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+@NEUAutoSubscribe
+object CustomTodoHud {
+
+ private fun matchString(todo: CustomTodo, text: String): Boolean {
+ return when (todo.triggerMatcher) {
+ CustomTodo.TriggerMatcher.REGEX -> text.matches(todo.trigger.toRegex())
+ CustomTodo.TriggerMatcher.STARTS_WITH -> text.startsWith(todo.trigger)
+ CustomTodo.TriggerMatcher.CONTAINS -> text.contains(todo.trigger)
+ CustomTodo.TriggerMatcher.EQUALS -> text == todo.trigger
+ }
+ }
+
+ @SubscribeEvent
+ fun onTabList(event: TabListChangeEvent) {
+ NotEnoughUpdates.INSTANCE.config.hidden.customTodos
+ .forEach { todo ->
+ if (todo.triggerTarget != CustomTodo.TriggerTarget.TAB_LIST) return@forEach
+ event.newLines.forEach { text ->
+ val doesMatch = matchString(todo, text)
+ if (doesMatch) {
+ todo.setDoneNow()
+ }
+ }
+ }
+ }
+
+ @SubscribeEvent
+ fun onSidebar(event: SidebarChangeEvent) {
+ NotEnoughUpdates.INSTANCE.config.hidden.customTodos
+ .forEach { todo ->
+ if (todo.triggerTarget != CustomTodo.TriggerTarget.SIDEBAR) return@forEach
+ event.lines.forEach { text ->
+ val doesMatch = matchString(todo, text)
+ if (doesMatch) {
+ todo.setDoneNow()
+ }
+ }
+ }
+ }
+
+ @SubscribeEvent
+ fun onChat(event: ClientChatReceivedEvent) {
+ val text = StringUtils.cleanColour(event.message.unformattedText)
+ NotEnoughUpdates.INSTANCE.config.hidden.customTodos
+ .forEach {
+ val isCorrectTrigger = when (it.triggerTarget) {
+ CustomTodo.TriggerTarget.CHAT -> event.type != 2.toByte()
+ CustomTodo.TriggerTarget.ACTIONBAR -> event.type == 2.toByte()
+ CustomTodo.TriggerTarget.TAB_LIST -> false
+ CustomTodo.TriggerTarget.SIDEBAR -> false
+ }
+ val doesMatch = matchString(it, text)
+ if (isCorrectTrigger && doesMatch)
+ it.setDoneNow()
+ }
+ }
+
+ @JvmStatic
+ fun processInto(strings: MutableList<String>) {
+ NotEnoughUpdates.INSTANCE.config.hidden.customTodos
+ .filter { it.isEnabledOnCurrentProfile }
+ .forEach {
+ val readyAt = it.readyAtOnCurrentProfile ?: (System.currentTimeMillis() - 1000L)
+ val until = readyAt - System.currentTimeMillis()
+ strings.add(
+ "CUSTOM" + it.icon + ":§3" + it.label + ": " +
+ if (until <= 0)
+ EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.miscOverlays.readyColour].toString() + "Ready"
+ else if (until < 60 * 30 * 1000L)
+ EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.miscOverlays.verySoonColour].toString()
+ + Utils.prettyTime(until)
+ else if (until < 60 * 60 * 1000L)
+ EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.miscOverlays.soonColour].toString()
+ + Utils.prettyTime(until)
+ else if (until < 3 * 60 * 60 * 1000L)
+ EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.miscOverlays.kindaSoonColour].toString()
+ + Utils.prettyTime(until)
+ else
+ EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.miscOverlays.defaultColour].toString()
+ + Utils.prettyTime(until)
+ )
+ }
+ }
+
+}
diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodoList.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodoList.kt
new file mode 100644
index 00000000..1b278990
--- /dev/null
+++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodoList.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 NotEnoughUpdates contributors
+ *
+ * This file is part of NotEnoughUpdates.
+ *
+ * NotEnoughUpdates is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * NotEnoughUpdates 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 NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package io.github.moulberry.notenoughupdates.miscgui.customtodos
+
+import io.github.moulberry.moulconfig.internal.ClipboardUtils
+import io.github.moulberry.moulconfig.observer.ObservableList
+import io.github.moulberry.moulconfig.xml.Bind
+import io.github.moulberry.moulconfig.xml.XMLUniverse
+import io.github.moulberry.notenoughupdates.NotEnoughUpdates
+import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe
+import io.github.moulberry.notenoughupdates.events.RegisterBrigadierCommandEvent
+import io.github.moulberry.notenoughupdates.util.brigadier.thenExecute
+import io.github.moulberry.notenoughupdates.util.brigadier.withHelp
+import io.github.moulberry.notenoughupdates.util.loadResourceLocation
+import net.minecraft.client.gui.GuiScreen
+import net.minecraft.util.ResourceLocation
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+class CustomTodoList(
+ @field:Bind
+ val todos: ObservableList<CustomTodoEditor>,
+ val xmlUniverse: XMLUniverse,
+) {
+ @NEUAutoSubscribe
+ companion object {
+ @SubscribeEvent
+ fun onCommand(event: RegisterBrigadierCommandEvent) {
+ event.command("neutodos", "neucustomtodos") {
+ thenExecute {
+ NotEnoughUpdates.INSTANCE.openGui = create().open()
+ }
+ }.withHelp("Edit NEUs custom TODOs")
+ }
+
+ fun create(): CustomTodoList {
+ val universe = XMLUniverse.getDefaultUniverse()
+ val list = ObservableList<CustomTodoEditor>(mutableListOf())
+ NotEnoughUpdates.INSTANCE.config.hidden.customTodos.forEach {
+ list.add(CustomTodoEditor(it, list, universe))
+ }
+ return CustomTodoList(
+ list,
+ universe
+ )
+ }
+ }
+
+ fun open(): GuiScreen {
+ return xmlUniverse.loadResourceLocation(this, ResourceLocation("notenoughupdates:gui/customtodos/overview.xml"))
+ }
+
+ @Bind
+ fun pasteTodo() {
+ val customTodo = CustomTodo.fromTemplate(ClipboardUtils.getClipboardContent())
+ ?: return
+ todos.add(CustomTodoEditor(customTodo, todos, xmlUniverse))
+ save()
+ }
+
+ fun save() {
+ NotEnoughUpdates.INSTANCE.config.hidden.customTodos = todos.map { it.into() }.toMutableList()
+ NotEnoughUpdates.INSTANCE.saveConfig()
+ }
+
+ @Bind
+ fun addTodo() {
+ todos.add(
+ CustomTodoEditor(
+ CustomTodo(
+ "Custom Todo # ${todos.size + 1}",
+ 0,
+ "",
+ "",
+ false,
+ ),
+ todos,
+ xmlUniverse,
+ )
+ )
+ save()
+ }
+}
diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/MoulConfig.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/MoulConfig.kt
new file mode 100644
index 00000000..9c626627
--- /dev/null
+++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/MoulConfig.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 NotEnoughUpdates contributors
+ *
+ * This file is part of NotEnoughUpdates.
+ *
+ * NotEnoughUpdates is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * NotEnoughUpdates 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 NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package io.github.moulberry.notenoughupdates.util
+
+import io.github.moulberry.moulconfig.gui.GuiContext
+import io.github.moulberry.moulconfig.gui.GuiScreenElementWrapperNew
+import io.github.moulberry.moulconfig.xml.XMLUniverse
+import net.minecraft.client.Minecraft
+import net.minecraft.client.gui.GuiScreen
+import net.minecraft.util.ResourceLocation
+
+
+fun XMLUniverse.loadResourceLocation(obj: Any, resourceLocation: ResourceLocation): GuiScreen {
+ return GuiScreenElementWrapperNew(
+ GuiContext(
+ load(
+ obj,
+ Minecraft.getMinecraft().resourceManager.getResource(resourceLocation).inputStream
+ )
+ )
+ )
+}
diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/TemplateUtil.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/TemplateUtil.kt
new file mode 100644
index 00000000..2cd90acc
--- /dev/null
+++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/TemplateUtil.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 NotEnoughUpdates contributors
+ *
+ * This file is part of NotEnoughUpdates.
+ *
+ * NotEnoughUpdates is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * NotEnoughUpdates 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 NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package io.github.moulberry.notenoughupdates.util
+
+import com.google.gson.GsonBuilder
+import io.github.moulberry.notenoughupdates.util.kotlin.KotlinTypeAdapterFactory
+import java.util.*
+
+object TemplateUtil {
+ val gson = GsonBuilder()
+ .registerTypeAdapterFactory(KotlinTypeAdapterFactory)
+ .create()
+
+ @JvmStatic
+ fun getTemplatePrefix(data: String): String? {
+ val decoded = maybeFromBase64Encoded(data) ?: return null
+ return decoded.replaceAfter("/", "", "").ifBlank { null }
+ }
+
+ @JvmStatic
+ fun intoBase64Encoded(raw: String): String {
+ return Base64.getEncoder().encodeToString(raw.encodeToByteArray())
+ }
+
+ private val base64Alphabet = charArrayOf(
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '='
+ )
+
+ @JvmStatic
+ fun maybeFromBase64Encoded(raw: String): String? {
+ val raw = raw.trim()
+ if (raw.any { it !in base64Alphabet }) {
+ return null
+ }
+ return try {
+ Base64.getDecoder().decode(raw).decodeToString()
+ } catch (ex: Exception) {
+ null
+ }
+ }
+
+
+ /**
+ * Returns a base64 encoded string, truncated such that for all `x`, `x.startsWith(prefix)` implies
+ * `base64Encoded(x).startsWith(getPrefixComparisonSafeBase64Encoding(prefix))`
+ * (however, the inverse may not always be true).
+ */
+ @JvmStatic
+ fun getPrefixComparisonSafeBase64Encoding(prefix: String): String {
+ val rawEncoded =
+ Base64.getEncoder().encodeToString(prefix.encodeToByteArray())
+ .replace("=", "")
+ return rawEncoded.substring(0, rawEncoded.length - rawEncoded.length % 4)
+ }
+
+ @JvmStatic
+ fun encodeTemplate(sharePrefix: String, data: Any): String {
+ require(sharePrefix.endsWith("/"))
+ return intoBase64Encoded(sharePrefix + gson.toJson(data))
+ }
+
+ @JvmStatic
+ fun <T : Any> maybeDecodeTemplate(sharePrefix: String, data: String, type: Class<T>): T? {
+ require(sharePrefix.endsWith("/"))
+ val data = data.trim()
+ if (!data.startsWith(getPrefixComparisonSafeBase64Encoding(sharePrefix)))
+ return null
+ val decoded = maybeFromBase64Encoded(data) ?: return null
+ if (!decoded.startsWith(sharePrefix))
+ return null
+ return try {
+ gson.fromJson(decoded.substring(sharePrefix.length), type)
+ } catch (e: Exception) {
+ null
+ }
+ }
+
+}
diff --git a/src/main/resources/assets/notenoughupdates/gui/customtodos/edit.xml b/src/main/resources/assets/notenoughupdates/gui/customtodos/edit.xml
new file mode 100644
index 00000000..9ccf71c2
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/gui/customtodos/edit.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ ~ Copyright (C) 2023 NotEnoughUpdates contributors
+ ~
+ ~ This file is part of NotEnoughUpdates.
+ ~
+ ~ NotEnoughUpdates is free software: you can redistribute it
+ ~ and/or modify it under the terms of the GNU Lesser General Public
+ ~ License as published by the Free Software Foundation, either
+ ~ version 3 of the License, or (at your option) any later version.
+ ~
+ ~ NotEnoughUpdates 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 NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ -->
+
+<Root xmlns="http://notenoughupdates.org/moulconfig">
+ <Gui>
+ <Column>
+
+ <Row>
+ <Button onClick="@close">
+ <Text text="←"/>
+ </Button>
+ <Text text="@getTitle"/>
+ </Row>
+ <Row>
+ <Text text="Enabled: " width="150"/>
+ <Switch value="@enabled"/>
+ </Row>
+ <Row>
+ <Text text="Label: " width="150"/>
+ <TextField width="300" value="@label" suggestion="Label"/>
+ </Row>
+ <Row>
+ <Text text="Icon: " width="132"/>
+ <ItemStack value="@getItemStack"/>
+ <TextField width="300" value="@icon" suggestion="Item ID"/>
+ </Row>
+ <Row>
+ <Text text="Reset after (seconds): " width="150"/>
+ <TextField width="300" value="@timer"/>
+ </Row>
+ <Row>
+ <Text text="Rest at fixed time: " width="150"/>
+ <Switch value="@isResetOffset"/>
+ </Row>
+ <Row>
+ <Text text="@getFancyTime" width="300"/>
+ </Row>
+ <Row>
+ <Button onClick="@minusDay">
+ <Text text="-Day"/>
+ </Button>
+ <Button onClick="@minusHour">
+ <Text text="-Hour"/>
+ </Button>
+ <Button onClick="@minusMinute">
+ <Text text="-Minute"/>
+ </Button>
+ <Button onClick="@plusMinute">
+ <Text text="+Minute"/>
+ </Button>
+ <Button onClick="@plusHour">
+ <Text text="+Hour"/>
+ </Button>
+ <Button onClick="@plusDay">
+ <Text text="+Day"/>
+ </Button>
+ </Row>
+ <Row>
+ <Text text="Trigger: " width="150"/>
+ <TextField value="@trigger" width="300" suggestion="Trigger"/>
+ </Row>
+ <Row>
+ <Button onClick="@setChat">
+ <Text text="@getChat"/>
+ </Button>
+ <Button onClick="@setActionbar">
+ <Text text="@getActionbar"/>
+ </Button>
+
+ <Button onClick="@setSidebar">
+ <Text text="@getSidebar"/>
+ </Button>
+
+ <Button onClick="@setTablist">
+ <Text text="@getTablist"/>
+ </Button>
+ </Row>
+ <Row>
+ <Button onClick="@setRegex">
+ <Text text="@getRegex"/>
+ </Button>
+ <Button onClick="@setStartsWith">
+ <Text text="@getStartsWith"/>
+ </Button>
+ <Button onClick="@setContains">
+ <Text text="@getContains"/>
+ </Button>
+ <Button onClick="@setEquals">
+ <Text text="@getEquals"/>
+ </Button>
+ </Row>
+ <Row>
+ <Button onClick="@copyTemplate">
+ <Text text="Copy Preset"/>
+ </Button>
+ <Button onClick="@markAsReady">
+ <Text text="Mark as Ready"/>
+ </Button>
+ <Button onClick="@markAsCompleted">
+ <Text text="Mark as Done"/>
+ </Button>
+ </Row>
+ </Column>
+ </Gui>
+</Root>
diff --git a/src/main/resources/assets/notenoughupdates/gui/customtodos/overview.xml b/src/main/resources/assets/notenoughupdates/gui/customtodos/overview.xml
new file mode 100644
index 00000000..b1cb1521
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/gui/customtodos/overview.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ ~ Copyright (C) 2023 NotEnoughUpdates contributors
+ ~
+ ~ This file is part of NotEnoughUpdates.
+ ~
+ ~ NotEnoughUpdates is free software: you can redistribute it
+ ~ and/or modify it under the terms of the GNU Lesser General Public
+ ~ License as published by the Free Software Foundation, either
+ ~ version 3 of the License, or (at your option) any later version.
+ ~
+ ~ NotEnoughUpdates 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 NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ -->
+
+<Root xmlns="http://notenoughupdates.org/moulconfig">
+ <Gui>
+ <Column>
+ <Center>
+ <Scale scale="2">
+ <Text text="Custom TODOs"/>
+ </Scale>
+ </Center>
+ <Spacer height="5"/>
+ <ScrollPanel width="350" height="200">
+ <Array data="@todos">
+ <Row>
+ <Text text="@label" width="200"/>
+ <Switch value="@enabled"/>
+ <Button onClick="@edit">
+ <Text text="Edit"/>
+ </Button>
+ <Button onClick="@delete">
+ <Text text="Delete"/>
+ </Button>
+ </Row>
+ </Array>
+ </ScrollPanel>
+ <Row>
+ <Button onClick="@addTodo">
+ <Text text="Add new TODO"/>
+ </Button>
+ <Button onClick="@pasteTodo">
+ <Text text="Paste TODO from Clipboard"/>
+ </Button>
+ </Row>
+ </Column>
+ </Gui>
+</Root>