diff options
Diffstat (limited to 'src/main/java/de')
10 files changed, 1219 insertions, 66 deletions
diff --git a/src/main/java/de/cowtipper/cowlection/Cowlection.java b/src/main/java/de/cowtipper/cowlection/Cowlection.java index e91f0b6..f4c1602 100644 --- a/src/main/java/de/cowtipper/cowlection/Cowlection.java +++ b/src/main/java/de/cowtipper/cowlection/Cowlection.java @@ -9,6 +9,7 @@ import de.cowtipper.cowlection.handler.FriendsHandler; import de.cowtipper.cowlection.handler.PlayerCache; import de.cowtipper.cowlection.listener.ChatListener; import de.cowtipper.cowlection.listener.PlayerListener; +import de.cowtipper.cowlection.partyfinder.Rules; import de.cowtipper.cowlection.util.ChatHelper; import de.cowtipper.cowlection.util.VersionChecker; import net.minecraft.client.Minecraft; @@ -41,6 +42,7 @@ public class Cowlection { private File modOutDir; private MooConfig config; private CredentialStorage moo; + private Rules partyFinderRules; private FriendsHandler friendsHandler; private VersionChecker versionChecker; private ChatHelper chatHelper; @@ -64,6 +66,7 @@ public class Cowlection { friendsHandler = new FriendsHandler(this, new File(configDir, "friends.json")); moo = new CredentialStorage(new Configuration(new File(configDir, "do-not-share-me-with-other-players.cfg"))); + partyFinderRules = new Rules(this, new File(configDir, "partyfinder-rules.json")); config = new MooConfig(this, new Configuration(new File(configDir, MODID + ".cfg"), "2")); } @@ -103,6 +106,10 @@ public class Cowlection { return moo; } + public Rules getPartyFinderRules() { + return partyFinderRules; + } + public FriendsHandler getFriendsHandler() { return friendsHandler; } diff --git a/src/main/java/de/cowtipper/cowlection/command/MooCommand.java b/src/main/java/de/cowtipper/cowlection/command/MooCommand.java index 396e6d1..b8e772b 100644 --- a/src/main/java/de/cowtipper/cowlection/command/MooCommand.java +++ b/src/main/java/de/cowtipper/cowlection/command/MooCommand.java @@ -15,6 +15,7 @@ import de.cowtipper.cowlection.data.*; import de.cowtipper.cowlection.data.HySkyBlockStats.Profile.Pet; import de.cowtipper.cowlection.handler.DungeonCache; import de.cowtipper.cowlection.listener.skyblock.DungeonsPartyListener; +import de.cowtipper.cowlection.partyfinder.RuleEditorGui; import de.cowtipper.cowlection.search.GuiSearch; import de.cowtipper.cowlection.util.*; import net.minecraft.client.Minecraft; @@ -84,10 +85,9 @@ public class MooCommand extends CommandBase { if (args.length == 0) { main.getChatHelper().sendMessage(EnumChatFormatting.GOLD, "Tried to say " + EnumChatFormatting.YELLOW + getCommandName() + EnumChatFormatting.GOLD + "? Use " + EnumChatFormatting.YELLOW + getCommandName() + " say [optional text]" + EnumChatFormatting.GOLD + " instead.\n" + "Tried to use the command " + EnumChatFormatting.YELLOW + "/" + getCommandName() + EnumChatFormatting.GOLD + "? Use " + EnumChatFormatting.YELLOW + "/" + getCommandName() + " help" + EnumChatFormatting.GOLD + " for a list of available commands"); - return; } //region sub commands: Best friends, friends & other players - if (args[0].equalsIgnoreCase("say")) { + else if (args[0].equalsIgnoreCase("say")) { // work-around so you can still say 'moo' in chat without triggering the client-side command String msg = CommandBase.buildString(args, 1); Minecraft.getMinecraft().thePlayer.sendChatMessage(getCommandName() + (!msg.isEmpty() ? " " + msg : "")); @@ -129,7 +129,8 @@ public class MooCommand extends CommandBase { } handleWhatAmILookingAt(sender, showAllInfo); } else if (args[0].equalsIgnoreCase("dungeon") || args[0].equalsIgnoreCase("dung") - || /* dungeon party: */ args[0].equalsIgnoreCase("dp")) { + || /* dungeon party: */ args[0].equalsIgnoreCase("dp") + || /* dungeon party finder rules: */ args[0].equalsIgnoreCase("dr")) { handleDungeon(args); } //endregion @@ -1025,6 +1026,9 @@ public class MooCommand extends CommandBase { }, 10 * 20); // register dungeon listener dungeonsPartyListener = new DungeonsPartyListener(main); + } else if (args.length == 2 && args[1].equalsIgnoreCase("rules") + || args.length == 1 && args[0].equalsIgnoreCase("dr")) { + displayGuiScreen(new RuleEditorGui()); } else if (dungeonCache.isInDungeon()) { dungeonCache.sendDungeonPerformance(); } else { @@ -1137,6 +1141,7 @@ public class MooCommand extends CommandBase { .appendSibling(createCmdHelpEntry("waila", "Copy the 'thing' you're looking at (optional keybinding: Minecraft controls > Cowlection)")) .appendSibling(createCmdHelpEntry("dungeon", "SkyBlock Dungeons: display current dungeon performance")) .appendSibling(createCmdHelpEntry("dungeon party", "SkyBlock Dungeons: Shows armor and dungeon info about current party members " + EnumChatFormatting.GRAY + "(alias: " + EnumChatFormatting.WHITE + "/" + getCommandName() + " dp" + EnumChatFormatting.GRAY + ") §d§l⚷")) + .appendSibling(createCmdHelpEntry("dungeon rules", "SkyBlock Dungeons: Edit rules for Party Finder " + EnumChatFormatting.GRAY + "(alias: " + EnumChatFormatting.WHITE + "/" + getCommandName() + " dr" + EnumChatFormatting.GRAY + ")")) .appendSibling(createCmdHelpSection(3, "Miscellaneous")) .appendSibling(createCmdHelpEntry("search", "Open Minecraft log search")) .appendSibling(createCmdHelpEntry("worldage", "Check how long the current world is loaded")) @@ -1182,7 +1187,7 @@ public class MooCommand extends CommandBase { } else if (args.length == 2 && args[0].equalsIgnoreCase("remove")) { return getListOfStringsMatchingLastWord(args, main.getFriendsHandler().getBestFriends()); } else if (args.length == 2 && args[0].equalsIgnoreCase("dungeon")) { - return getListOfStringsMatchingLastWord(args, "party", "enter", "leave"); + return getListOfStringsMatchingLastWord(args, "party", "rules", "enter", "leave"); } else if (args.length == 2 && (args[0].equalsIgnoreCase("worldage") || args[0].equalsIgnoreCase("serverage"))) { return getListOfStringsMatchingLastWord(args, "off", "on", "disable", "enable"); } else if (args.length == 2 && (args[0].equalsIgnoreCase("chestAnalyzer") || args[0].equalsIgnoreCase("chestAnalyser") || args[0].equalsIgnoreCase("analyzeChests") || args[0].equalsIgnoreCase("analyseChests"))) { diff --git a/src/main/java/de/cowtipper/cowlection/config/MooConfig.java b/src/main/java/de/cowtipper/cowlection/config/MooConfig.java index 026308a..ee5f784 100644 --- a/src/main/java/de/cowtipper/cowlection/config/MooConfig.java +++ b/src/main/java/de/cowtipper/cowlection/config/MooConfig.java @@ -7,6 +7,7 @@ import de.cowtipper.cowlection.command.TabCompletableCommand; import de.cowtipper.cowlection.config.gui.MooConfigGui; import de.cowtipper.cowlection.config.gui.MooConfigPreview; import de.cowtipper.cowlection.data.DataHelper; +import de.cowtipper.cowlection.partyfinder.Rule; import de.cowtipper.cowlection.util.MooChatComponent; import de.cowtipper.cowlection.util.Utils; import net.minecraft.client.Minecraft; @@ -118,13 +119,13 @@ public class MooConfig { public static boolean dungPartiesSize; public static int dungDungeonReqMin; public static int dungClassMin; - private static String dungMarkPartiesWithCarry; - private static String dungMarkPartiesWithHyperion; private static String dungMarkPartiesWithArcher; private static String dungMarkPartiesWithBerserk; private static String dungMarkPartiesWithHealer; private static String dungMarkPartiesWithMage; private static String dungMarkPartiesWithTank; + public static boolean dungPartyFinderRuleEditorSimplified; + public static boolean dungPartyFinderRuleEditorShowOpenButton; public static boolean dungSendWrongFloorWarning; private static Configuration cfg = null; @@ -221,6 +222,32 @@ public class MooConfig { changedSomething = true; } } + Property dungMarkPartiesWithCarry = sbDungCategory.get("dungMarkPartiesWithCarry"); + if (dungMarkPartiesWithCarry != null) { + if (!"do not mark".equals(dungMarkPartiesWithCarry.getString())) { + for (Rule rule : main.getPartyFinderRules().getRules()) { + if (rule.getTriggerText().contains("carr")) { + rule.setPartyType(DataHelper.PartyType.getByFancyString(dungMarkPartiesWithCarry.getString())); + rule.setEnabled(true); + } + } + } + sbDungCategory.remove("dungMarkPartiesWithCarry"); + changedSomething = true; + } + Property dungMarkPartiesWithHyperion = sbDungCategory.get("dungMarkPartiesWithHyperion"); + if (dungMarkPartiesWithHyperion != null) { + if (!"do not mark".equals(dungMarkPartiesWithHyperion.getString())) { + for (Rule rule : main.getPartyFinderRules().getRules()) { + if (rule.getTriggerText().contains("hyp")) { + rule.setPartyType(DataHelper.PartyType.getByFancyString(dungMarkPartiesWithHyperion.getString())); + rule.setEnabled(true); + } + } + } + sbDungCategory.remove("dungMarkPartiesWithHyperion"); + changedSomething = true; + } } // else if (MathHelper.parseIntWithDefault(oldVersion, 9999) < newVersion) { // matches all versions >= 2 if (changedSomething) { @@ -651,17 +678,6 @@ public class MooConfig { .setMinValue(0).setMaxValue(50), new MooConfigPreview(new MooChatComponent("Marked with: " + EnumChatFormatting.RED + EnumChatFormatting.BOLD + "ᐯ").gray())); - Property propDungMarkPartiesWithCarry = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), - "dungMarkPartiesWithCarry", "do not mark", "Mark parties with carry in the notes", - new String[]{"suitable " + EnumChatFormatting.GREEN + "⬛", "unideal " + EnumChatFormatting.GOLD + "⬛", "block " + EnumChatFormatting.RED + "⬛", "do not mark"}), - new MooConfigPreview(new MooChatComponent("Marked with: " + EnumChatFormatting.AQUA + "carry" - + EnumChatFormatting.GRAY + " or " + EnumChatFormatting.GREEN + "carry " + EnumChatFormatting.GRAY + "('free' carries)").gray())); - - Property propDungMarkPartiesWithHyperion = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), - "dungMarkPartiesWithHyperion", "do not mark", "Mark parties with hyperion in the notes", - new String[]{"suitable " + EnumChatFormatting.GREEN + "⬛", "unideal " + EnumChatFormatting.GOLD + "⬛", "block " + EnumChatFormatting.RED + "⬛", "do not mark"}), - new MooConfigPreview(new MooChatComponent("Marked with: " + EnumChatFormatting.AQUA + "hyper").gray())); - Property propDungMarkPartiesWithArcher = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), "dungMarkPartiesWithArcher", "if dupe: " + EnumChatFormatting.GOLD + "⬛", "Mark parties with Archer class?", new String[]{ @@ -702,6 +718,12 @@ public class MooConfig { "do not mark"}), new MooConfigPreview(DataHelper.DungeonClass.TANK)); + Property propDungPartyFinderRuleEditorSimplified = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "dungPartyFinderRuleEditorSimplified", true, "Use simplified version of rules editor?")); + + Property propDungPartyFinderRuleEditorShowOpenButton = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "dungPartyFinderRuleEditorShowOpenButton", true, "Show button to open rules editor?")); + Property propDungSendWrongFloorWarning = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), "dungSendWrongFloorWarning", true, "Send warning when entering wrong floor?")); @@ -780,13 +802,13 @@ public class MooConfig { dungPartiesSize = propDungPartiesSize.getBoolean(); dungDungeonReqMin = propDungDungeonReqMin.getInt(); dungClassMin = propDungClassMin.getInt(); - dungMarkPartiesWithCarry = propDungMarkPartiesWithCarry.getString(); - dungMarkPartiesWithHyperion = propDungMarkPartiesWithHyperion.getString(); dungMarkPartiesWithArcher = propDungMarkPartiesWithArcher.getString(); dungMarkPartiesWithBerserk = propDungMarkPartiesWithBerserk.getString(); dungMarkPartiesWithHealer = propDungMarkPartiesWithHealer.getString(); dungMarkPartiesWithMage = propDungMarkPartiesWithMage.getString(); dungMarkPartiesWithTank = propDungMarkPartiesWithTank.getString(); + dungPartyFinderRuleEditorSimplified = propDungPartyFinderRuleEditorSimplified.getBoolean(); + dungPartyFinderRuleEditorShowOpenButton = propDungPartyFinderRuleEditorShowOpenButton.getBoolean(); dungSendWrongFloorWarning = propDungSendWrongFloorWarning.getBoolean(); if (!hasOpenedConfigGui && (!propDungOverlayEnabled.isDefault() || !propDungOverlayPositionX.isDefault() || !propDungOverlayPositionY.isDefault() || !propDungOverlayGuiScale.isDefault())) { @@ -872,13 +894,13 @@ public class MooConfig { propDungPartiesSize.set(dungPartiesSize); propDungDungeonReqMin.set(dungDungeonReqMin); propDungClassMin.set(dungClassMin); - propDungMarkPartiesWithCarry.set(dungMarkPartiesWithCarry); - propDungMarkPartiesWithHyperion.set(dungMarkPartiesWithHyperion); propDungMarkPartiesWithArcher.set(dungMarkPartiesWithArcher); propDungMarkPartiesWithBerserk.set(dungMarkPartiesWithBerserk); propDungMarkPartiesWithHealer.set(dungMarkPartiesWithHealer); propDungMarkPartiesWithMage.set(dungMarkPartiesWithMage); propDungMarkPartiesWithTank.set(dungMarkPartiesWithTank); + propDungPartyFinderRuleEditorSimplified.set(dungPartyFinderRuleEditorSimplified); + propDungPartyFinderRuleEditorShowOpenButton.set(dungPartyFinderRuleEditorShowOpenButton); propDungSendWrongFloorWarning.set(dungSendWrongFloorWarning); if (saveToFile && cfg.hasChanged()) { @@ -1109,28 +1131,6 @@ public class MooConfig { return Setting.get(dungPartyFinderPlayerLookup); } - public static DataHelper.PartyType getDungPartyFinderMarkCarry() { - return getPartyType(dungMarkPartiesWithCarry); - } - - public static DataHelper.PartyType getDungPartyFinderMarkHyperion() { - return getPartyType(dungMarkPartiesWithHyperion); - } - - private static DataHelper.PartyType getPartyType(String configValue) { - String configValueBeginning = configValue.length() >= 5 ? configValue.substring(0, 5) : "invalid"; - switch (configValueBeginning) { - case "suita": // "suitable " + EnumChatFormatting.GREEN + "⬛" - return DataHelper.PartyType.SUITABLE; - case "unide": // "unideal " + EnumChatFormatting.GOLD + "⬛" - return DataHelper.PartyType.UNIDEAL; - case "block": // "block " + EnumChatFormatting.RED + "⬛" - return DataHelper.PartyType.UNJOINABLE_OR_BLOCK; - default: // "do not mark" - return DataHelper.PartyType.NONE; - } - } - public static Mark dungeonPartyMarker(DataHelper.DungeonClass dungeonClass) { String setting; switch (dungeonClass) { diff --git a/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigCategoryScrolling.java b/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigCategoryScrolling.java index 731e9f9..19dd4a9 100644 --- a/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigCategoryScrolling.java +++ b/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigCategoryScrolling.java @@ -4,6 +4,7 @@ import com.google.common.collect.Lists; import de.cowtipper.cowlection.Cowlection; import de.cowtipper.cowlection.config.MooConfig; import de.cowtipper.cowlection.config.MooConfigCategory; +import de.cowtipper.cowlection.partyfinder.RuleEditorGui; import de.cowtipper.cowlection.search.GuiSearch; import de.cowtipper.cowlection.util.GuiHelper; import de.cowtipper.cowlection.util.Utils; @@ -89,8 +90,8 @@ public class MooConfigCategoryScrolling extends GuiListExtended { // add control buttons to navigate to other guis if ("Other settings".equals(subCategory.getDisplayName())) { - this.listEntries.add(new GuiSwitchEntry("gotoKeyBindings", "Controls", () -> mc.displayGuiScreen(new GuiControls(MooConfigCategoryScrolling.this.parent, mc.gameSettings)))); - this.listEntries.add(new GuiSwitchEntry("gotoLogSearchConfig", "Log Search", () -> mc.displayGuiScreen(new GuiSearch("")))); + this.listEntries.add(new GuiSwitchEntry("gotoKeyBindings", "Controls ↗", () -> mc.displayGuiScreen(new GuiControls(MooConfigCategoryScrolling.this.parent, mc.gameSettings)))); + this.listEntries.add(new GuiSwitchEntry("gotoLogSearchConfig", "Log Search ↗", () -> mc.displayGuiScreen(new GuiSearch("")))); continue; // don't add properties to main config gui } @@ -105,6 +106,9 @@ public class MooConfigCategoryScrolling extends GuiListExtended { // add config elements for (Property configEntry : subCategory.getConfigEntries()) { addConfigEntryToGui(subCategory, configEntry); + if ("dungMarkPartiesWithTank".equals(configEntry.getName())) { + this.listEntries.add(new GuiSwitchEntry("gotoPartyFinderRulesEditor", "Rule editor ↗", () -> mc.displayGuiScreen(new RuleEditorGui()))); + } } } } @@ -216,13 +220,17 @@ public class MooConfigCategoryScrolling extends GuiListExtended { if (labelWidth > this.maxListLabelWidth) { this.maxListLabelWidth = labelWidth; } - this.listEntries.add(new GuiSwitchEntry("gotoLogSearchConfig", "Log Search", () -> mc.displayGuiScreen(new GuiSearch("")))); + this.listEntries.add(new GuiSwitchEntry("gotoLogSearchConfig", "Log Search ↗", () -> mc.displayGuiScreen(new GuiSearch("")))); hasLogSearchBeenAdded = true; } else if (hasLogSearchBeenAdded) { // already added the replacement-entry, thus don't increase entry counter --entryNr; } } else { + if ("cowlection.config.dungPartyFinderRuleEditorSimplified".equals(configEntry.getLanguageKey())) { + // add rule editor button to 'Use simplified editor' entry + this.listEntries.add(new GuiSwitchEntry("gotoPartyFinderRulesEditor", "Rule editor ↗", () -> mc.displayGuiScreen(new RuleEditorGui()))); + } addConfigEntryToGui(subCategory, configEntry); // add preview for this entry MooConfigPreview preview = subCategory.getPreview(configEntry); diff --git a/src/main/java/de/cowtipper/cowlection/data/DataHelper.java b/src/main/java/de/cowtipper/cowlection/data/DataHelper.java index 6956d1c..25212ff 100644 --- a/src/main/java/de/cowtipper/cowlection/data/DataHelper.java +++ b/src/main/java/de/cowtipper/cowlection/data/DataHelper.java @@ -109,18 +109,20 @@ public final class DataHelper { } public enum PartyType { - SUITABLE(0xff22B14C, 240), - UNIDEAL(0xffCD8032, 240), - UNJOINABLE_OR_BLOCK(0xffEB6E6E, 279), - CURRENT(0xff5FDE6C, 240), - NONE(0xffFF0000, 279); + NONE(0xffFF0000, 279, EnumChatFormatting.ITALIC + "none"), + SUITABLE(0xff22B14C, 240, "suitable " + EnumChatFormatting.GREEN + "⬛"), + UNIDEAL(0xffCD8032, 240, "unideal " + EnumChatFormatting.GOLD + "⬛"), + UNJOINABLE_OR_BLOCK(0xffEB6E6E, 279, "block " + EnumChatFormatting.RED + "⬛"), + CURRENT(0xff5FDE6C, 240, "current"); private final float zIndex; private final int color; + private final String fancyString; - PartyType(int color, float zIndex) { + PartyType(int color, float zIndex, String fancyString) { this.color = color; this.zIndex = zIndex; + this.fancyString = fancyString; } public float getZIndex() { @@ -130,6 +132,19 @@ public final class DataHelper { public int getColor() { return color; } + + public String toFancyString() { + return fancyString; + } + + public static PartyType getByFancyString(String fancyString) { + for (PartyType partyType : values()) { + if (partyType.fancyString.equals(fancyString)) { + return partyType; + } + } + return PartyType.NONE; + } } public enum DungeonClass { diff --git a/src/main/java/de/cowtipper/cowlection/listener/skyblock/DungeonsListener.java b/src/main/java/de/cowtipper/cowlection/listener/skyblock/DungeonsListener.java index 014c4c9..bdf7553 100644 --- a/src/main/java/de/cowtipper/cowlection/listener/skyblock/DungeonsListener.java +++ b/src/main/java/de/cowtipper/cowlection/listener/skyblock/DungeonsListener.java @@ -8,6 +8,8 @@ import de.cowtipper.cowlection.config.gui.MooConfigGui; import de.cowtipper.cowlection.data.DataHelper; import de.cowtipper.cowlection.data.DataHelper.DungeonClass; import de.cowtipper.cowlection.handler.DungeonCache; +import de.cowtipper.cowlection.partyfinder.Rule; +import de.cowtipper.cowlection.partyfinder.RuleEditorGui; import de.cowtipper.cowlection.util.GuiHelper; import de.cowtipper.cowlection.util.TickDelay; import de.cowtipper.cowlection.util.Utils; @@ -15,6 +17,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.audio.SoundCategory; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.inventory.GuiChest; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.init.Items; @@ -31,6 +34,7 @@ import net.minecraftforge.client.event.GuiScreenEvent; import net.minecraftforge.client.event.RenderGameOverlayEvent; import net.minecraftforge.common.util.Constants; import net.minecraftforge.event.entity.player.ItemTooltipEvent; +import net.minecraftforge.fml.client.config.GuiButtonExt; import net.minecraftforge.fml.common.eventhandler.EventPriority; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.gameevent.TickEvent; @@ -107,6 +111,7 @@ public class DungeonsListener { private final Pattern DUNGEON_CLASS_MILESTONE_PATTERN = Pattern.compile("^[A-Za-z]+ Milestone (.): "); private DungeonClass activeDungeonClass; + private GuiButton btnOpenRuleEditor; public DungeonsListener(Cowlection main) { this.main = main; @@ -305,7 +310,7 @@ public class DungeonsListener { // not a player skull, don't draw party status indicator return; } - DataHelper.PartyType partyType = DataHelper.PartyType.NONE; + DataHelper.PartyType partyType = DataHelper.PartyType.SUITABLE; List<String> itemTooltip = item.getTooltip(Minecraft.getMinecraft().thePlayer, false); if (itemTooltip.size() < 5) { @@ -356,20 +361,28 @@ public class DungeonsListener { --partySize; } else if (toolTipLineWithoutFormatting.startsWith("Note: ")) { String partyNote = toolTipLineWithoutFormatting.toLowerCase(); - DataHelper.PartyType partyTypeCarry = MooConfig.getDungPartyFinderMarkCarry(); - DataHelper.PartyType partyTypeHyperion = MooConfig.getDungPartyFinderMarkHyperion(); - // TODO make trigger words in party notes configurable - if (partyTypeCarry != DataHelper.PartyType.NONE && (partyNote.contains("carry") || partyNote.contains("carries"))) { - partyType = partyTypeCarry; - if (partyTypeCarry != DataHelper.PartyType.UNJOINABLE_OR_BLOCK) { - middleText = (partyNote.contains("free") ? EnumChatFormatting.GREEN : "") + "carry"; - } - } else if (partyTypeHyperion != DataHelper.PartyType.NONE && partyNote.contains("hyp")) { - partyType = partyTypeHyperion; - if (partyTypeHyperion != DataHelper.PartyType.UNJOINABLE_OR_BLOCK) { - middleText = "hyper"; + + StringBuilder middleTextBuilder = new StringBuilder(); + for (Rule rule : main.getPartyFinderRules().getRules()) { + if (rule.isTriggered(partyNote)) { + DataHelper.PartyType rulePartyType = rule.getPartyType(); + if (partyType == DataHelper.PartyType.UNJOINABLE_OR_BLOCK) { + // party type cannot get worse: abort + break; + } else if (rulePartyType.compareTo(partyType) > 0) { + // only "downgrade" party type from suitable > unideal > block + partyType = rulePartyType; + } + String text = rule.getMiddleText(); + if (text != null) { + if (middleTextBuilder.length() > 0) { + middleTextBuilder.append("§8,§r"); + } + middleTextBuilder.append(text); + } } } + middleText = middleTextBuilder.toString(); } else if (toolTipLineWithoutFormatting.startsWith("Dungeon Level Required: ")) { int minDungLevelReq = MathHelper.parseIntWithDefault(toolTipLineWithoutFormatting.substring(toolTipLineWithoutFormatting.lastIndexOf(' ') + 1), 100); if (minDungLevelReq < MooConfig.dungDungeonReqMin) { @@ -383,7 +396,7 @@ public class DungeonsListener { GlStateManager.translate(0, 0, 281); double scaleFactor = 0.5; GlStateManager.scale(scaleFactor, scaleFactor, 0); - font.drawStringWithShadow(middleText, (float) ((x + 1) / scaleFactor), (float) ((y + 5) / scaleFactor), new Color(85, 240, 240, 255).getRGB()); + font.drawStringWithShadow(middleText, (float) (x / scaleFactor), (float) ((y + 5) / scaleFactor), 0xFFffffff); GlStateManager.popMatrix(); } if (partyType != DataHelper.PartyType.UNJOINABLE_OR_BLOCK) { @@ -469,6 +482,49 @@ public class DungeonsListener { return null; } + @SubscribeEvent + public void onGuiInit(GuiScreenEvent.InitGuiEvent.Post e) { + if (MooConfig.dungPartyFinderRuleEditorShowOpenButton && e.gui instanceof GuiChest) { + // add "Open Rule Editor" button + GuiChest guiChest = (GuiChest) e.gui; + + Container inventorySlots = guiChest.inventorySlots; + IInventory inventory = inventorySlots.getSlot(0).inventory; + if (inventory.getName().equals("Party Finder")) { + // formulas from GuiContainer#initGui (guiTop) and GuiChest (ySize) + int inventoryRows = inventory.getSizeInventory() / 9; + int ySize = 222 - 108 + inventoryRows * 18; + int guiTop = (guiChest.height - ySize) / 2; + btnOpenRuleEditor = new GuiButtonExt(8765, 3, guiTop, 74, 18, "Rule editor ↗"); + e.buttonList.add(btnOpenRuleEditor); + } else { + btnOpenRuleEditor = null; + } + } else { + btnOpenRuleEditor = null; + } + } + + @SubscribeEvent + public void onGuiButtonClick(GuiScreenEvent.ActionPerformedEvent.Post e) { + if (MooConfig.dungPartyFinderRuleEditorShowOpenButton && e.gui instanceof GuiChest && e.button.id == 8765 && "Rule editor ↗".equals(e.button.displayString)) { + Minecraft mc = Minecraft.getMinecraft(); + mc.thePlayer.closeScreen(); + mc.displayGuiScreen(new RuleEditorGui()); + } + } + + @SubscribeEvent + public void onGuiButtonHover(GuiScreenEvent.DrawScreenEvent.Post e) { + if (MooConfig.dungPartyFinderRuleEditorShowOpenButton && e.gui instanceof GuiChest && btnOpenRuleEditor != null && btnOpenRuleEditor.isMouseOver()) { + Minecraft mc = Minecraft.getMinecraft(); + List<String> btnTooltip = new ArrayList<>(); + btnTooltip.add("Open Cowlection's Party Notes Rule Editor"); + btnTooltip.add(EnumChatFormatting.GRAY + "Want to remove this button? " + EnumChatFormatting.YELLOW + "/moo config editor"); + GuiHelper.drawHoveringText(btnTooltip, e.mouseX, e.mouseY, mc.displayWidth, mc.displayHeight, 300); + } + } + // Events inside dungeons // priority = highest to ignore other mods modifying the chat output @SubscribeEvent(priority = EventPriority.HIGHEST, receiveCanceled = true) diff --git a/src/main/java/de/cowtipper/cowlection/partyfinder/Rule.java b/src/main/java/de/cowtipper/cowlection/partyfinder/Rule.java new file mode 100644 index 0000000..34dd03f --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/partyfinder/Rule.java @@ -0,0 +1,200 @@ +package de.cowtipper.cowlection.partyfinder; + +import de.cowtipper.cowlection.data.DataHelper; +import de.cowtipper.cowlection.util.Utils; +import net.minecraft.util.EnumChatFormatting; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +public class Rule { + private final transient List<String> patternException; + private boolean isEnabled; + private transient boolean isValid; + private TriggerType triggerType; + private String triggerText; + private transient Pattern triggerRegex; + private String middleText; + private DataHelper.PartyType partyType; + + public Rule(boolean isEnabled, String trigger, TriggerType triggerType, String middleText, DataHelper.PartyType partyType) { + this.patternException = new ArrayList<>(); + this.isEnabled = isEnabled; + this.triggerText = trigger; + this.triggerType = triggerType; + this.middleText = middleText; + this.partyType = partyType; + postConstructor(); + } + + /** + * No-args constructor for GSON + */ + @SuppressWarnings("unused") + public Rule() { + patternException = new ArrayList<>(); + } + + public void postConstructor() { + setTriggerTextAndRevalidateRule(triggerText); + if (partyType == null) { + partyType = DataHelper.PartyType.SUITABLE; + } + } + + public boolean isEnabled() { + return isEnabled; + } + + public void setEnabled(boolean enabled) { + isEnabled = enabled; + } + + public boolean isTriggered(String notes) { + return isEnabled && isValid && ( + (triggerType == TriggerType.TEXT && notes.contains(triggerText)) + || (triggerRegex != null && triggerRegex.matcher(notes).find())); + } + + public TriggerType getTriggerType() { + return triggerType; + } + + /** + * @return human readable trigger text + */ + public String getTriggerText() { + return this.triggerText; + } + + public String getTriggerRegex() { + return triggerRegex != null ? triggerRegex.pattern() : ""; + } + + public void setTriggerTextAndRevalidateRule(String trigger) { + if (trigger == null) { + trigger = ""; + } + if (trigger.isEmpty()) { + triggerText = ""; + triggerRegex = null; + isValid = false; + patternException.clear(); + patternException.add(EnumChatFormatting.RED + "Pattern cannot be empty!"); + } else { + isValid = true; + triggerText = trigger.toLowerCase(Locale.ROOT); + } + boolean hasCommas = trigger.contains(","); + boolean hasBrackets = trigger.contains("(") || trigger.contains(")"); + if (triggerType == TriggerType.REGEX) { + try { + triggerRegex = Pattern.compile(trigger); + } catch (PatternSyntaxException e) { + handleInvalidPattern(e); + } + } else if (hasCommas || hasBrackets) { + if (triggerType == TriggerType.TEXT) { + this.triggerType = TriggerType.SIMPLE_REGEX; + } + try { + trigger = regexify(triggerText, hasBrackets, hasCommas); + triggerRegex = Pattern.compile(trigger); + } catch (PatternSyntaxException e) { + handleInvalidPattern(e); + } + } else { + triggerType = TriggerType.TEXT; + triggerRegex = null; + } + } + + private String regexify(String trigger, boolean hasBrackets, boolean hasCommas) { + trigger = trigger.replaceAll("([\\\\+*?\\[\\]{}|.^$])", "\\\\$1"); + if (hasBrackets) { + Matcher matcher = Pattern.compile("([^ ])?\\(([^)]+)\\)([^ ])?").matcher(trigger); + if (matcher.find()) { + if (matcher.group(1) == null) { + // negative lookbehind + trigger = matcher.replaceFirst("(?<!$2)$3"); + } else { + // negative lookahead + trigger = matcher.replaceFirst("$1(?!$2)"); + } + // escape additional brackets + trigger = trigger.replaceAll("\\(([^?][^)]*)\\)", "\\\\($1\\\\)"); + } + } + if (hasCommas) { + trigger = trigger.replace(",", "|"); + } + return trigger; + } + + private void handleInvalidPattern(PatternSyntaxException e) { + isValid = false; + triggerRegex = null; + patternException.clear(); + patternException.add(EnumChatFormatting.RED + "Invalid pattern:"); + String exceptionMessage = e.getLocalizedMessage(); + if (exceptionMessage == null) { + patternException.add(EnumChatFormatting.ITALIC + "unknown cause"); + } else if (exceptionMessage.contains("\n")) { + patternException.addAll(Arrays.asList(exceptionMessage.split("\r?\n"))); + } + } + + /** + * @return true, if triggerText is a valid value; false if regex is invalid + */ + public boolean isValid() { + return isValid; + } + + public List<String> getPatternException() { + return patternException; + } + + public void toggleTriggerType() { + if (this.triggerType == TriggerType.REGEX) { + this.triggerType = TriggerType.TEXT; + } else { // TEXT or SIMPLE_REGEX: + this.triggerType = TriggerType.REGEX; + } + } + + public String getMiddleText() { + return middleText; + } + + public void setMiddleText(String middleText) { + this.middleText = Utils.toMcColorCodes(middleText); + } + + public DataHelper.PartyType getPartyType() { + return partyType; + } + + public void setPartyType(DataHelper.PartyType partyType) { + this.partyType = partyType; + } + + enum TriggerType { + TEXT("Text"), SIMPLE_REGEX("Text+"), REGEX("Regex"); + + private final String buttonText; + + TriggerType(String fancyString) { + this.buttonText = fancyString; + } + + public String toButtonText() { + return this.buttonText; + } + } +} diff --git a/src/main/java/de/cowtipper/cowlection/partyfinder/RuleEditorGui.java b/src/main/java/de/cowtipper/cowlection/partyfinder/RuleEditorGui.java new file mode 100644 index 0000000..c1a9dc4 --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/partyfinder/RuleEditorGui.java @@ -0,0 +1,695 @@ +package de.cowtipper.cowlection.partyfinder; + +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import de.cowtipper.cowlection.Cowlection; +import de.cowtipper.cowlection.config.MooConfig; +import de.cowtipper.cowlection.data.DataHelper; +import de.cowtipper.cowlection.util.Utils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.*; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.resources.I18n; +import net.minecraft.command.CommandBase; +import net.minecraft.init.Items; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTUtil; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.client.GuiScrollingList; +import net.minecraftforge.fml.client.config.*; +import net.minecraftforge.fml.relauncher.ReflectionHelper; +import org.lwjgl.input.Keyboard; +import org.lwjgl.opengl.GL11; + +import java.awt.*; +import java.net.URI; +import java.util.List; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Based on GuiModList + */ +public class RuleEditorGui extends GuiScreen { + private final Rules rules; + private RulesListGui rulesList; + private GuiUnicodeGlyphButton btnUndoAll; + private GuiUnicodeGlyphButton btnDefaultAll; + private GuiButtonExt btnHelp; + private GuiButtonExt btnClose; + private static float lastScrollDistance; + private final boolean expertMode; + private final static List<String> colorCodesExplanation; + + static { + colorCodesExplanation = new ArrayList<>(); + colorCodesExplanation.add(EnumChatFormatting.BOLD + "Minecraft color codes:"); + for (EnumChatFormatting chatFormatting : EnumChatFormatting.values()) { + if (chatFormatting.isColor()) { + colorCodesExplanation.add(Utils.toHumanColorCodes(chatFormatting.toString()) + " → " + chatFormatting.toString() + chatFormatting.getFriendlyName()); + } + } + } + + public RuleEditorGui() { + this.rules = Cowlection.getInstance().getPartyFinderRules(); + expertMode = !MooConfig.dungPartyFinderRuleEditorSimplified; + } + + @Override + public void initGui() { + Keyboard.enableRepeatEvents(true); + this.buttonList.clear(); + + // undo all button + this.buttonList.add(this.btnUndoAll = new GuiUnicodeGlyphButton(2002, this.width - 94, 4, 20, 20, "", GuiUtils.UNDO_CHAR, 2)); + // reset all button + this.buttonList.add(this.btnDefaultAll = new GuiUnicodeGlyphButton(2001, this.width - 72, 4, 20, 20, "", GuiUtils.RESET_CHAR, 2)); + // help button + this.buttonList.add(this.btnHelp = new GuiButtonExt(1, this.width - 47, 4, 20, 20, "?")); + // close button + this.buttonList.add(this.btnClose = new GuiButtonExt(1, this.width - 25, 4, 20, 20, EnumChatFormatting.RED + "X")); + + updateLastScrollDistance(); + // scrollable commands list + rulesList = new RulesListGui(this.width - 30, this.rules.getRules(), lastScrollDistance); + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + drawDefaultBackground(); + super.drawScreen(mouseX, mouseY, partialTicks); + GlStateManager.pushMatrix(); + double scaleFactor = 1.5; + GlStateManager.scale(scaleFactor, scaleFactor, 0); + this.drawString(this.fontRendererObj, "Dungeon Party Finder: Rules editor", 30, 6, 0xFFCC00); + GlStateManager.popMatrix(); + this.rulesList.drawScreen(mouseX, mouseY, partialTicks); + if (btnUndoAll.isMouseOver()) { + drawHoveringText(Collections.singletonList("Undo all changes"), mouseX, mouseY); + } else if (btnDefaultAll.isMouseOver()) { + drawHoveringText(Collections.singletonList("Reset rules to default"), mouseX, mouseY); + } else if (btnHelp.isMouseOver()) { + List<String> helpTooltip = new ArrayList<>(); + helpTooltip.add("" + EnumChatFormatting.GOLD + EnumChatFormatting.BOLD + "Dungeon Party Finder: Rules editor"); + helpTooltip.add(EnumChatFormatting.GREEN + "If you have any questions or need help with the rules editor: Join the Cowshed discord!"); + helpTooltip.add(""); + helpTooltip.add(EnumChatFormatting.YELLOW + GuiUtils.UNDO_CHAR + EnumChatFormatting.RESET + " = discard all changes"); + helpTooltip.add(EnumChatFormatting.YELLOW + GuiUtils.RESET_CHAR + EnumChatFormatting.RESET + " = remove all rules and add default rules"); + helpTooltip.add(EnumChatFormatting.GRAY + "If Party notes filter contains:"); + helpTooltip.add(EnumChatFormatting.YELLOW + " ➊ " + EnumChatFormatting.RESET + "brackets " + EnumChatFormatting.YELLOW + "()" + EnumChatFormatting.RESET + " = exclude certain terms, for example \"" + + EnumChatFormatting.GRAY + "(free )carry" + EnumChatFormatting.RESET + "\" matches \"" + EnumChatFormatting.GRAY + "carry" + EnumChatFormatting.RESET + "\" but not \"" + EnumChatFormatting.GRAY + "free carry" + EnumChatFormatting.RESET + "\""); + helpTooltip.add(EnumChatFormatting.YELLOW + " ➋ " + EnumChatFormatting.RESET + "comma " + EnumChatFormatting.YELLOW + "," + EnumChatFormatting.RESET + " = alternatives, for example \"" + + EnumChatFormatting.GRAY + "plz,pls,help" + EnumChatFormatting.RESET + "\" matches \"" + EnumChatFormatting.GRAY + "plz" + EnumChatFormatting.RESET + "\", \"" + EnumChatFormatting.GRAY + "pls" + EnumChatFormatting.RESET + "\" and \"" + EnumChatFormatting.GRAY + "help" + EnumChatFormatting.RESET + "\""); + helpTooltip.add(EnumChatFormatting.YELLOW + " ➊+➋ " + EnumChatFormatting.RESET + "combination of both " + EnumChatFormatting.YELLOW + "(,)" + EnumChatFormatting.RESET + ": for example \"" + + EnumChatFormatting.GRAY + "carry( pls, plz)" + EnumChatFormatting.RESET + "\" matches \"" + EnumChatFormatting.GRAY + "carry" + EnumChatFormatting.RESET + "\", but not \"" + EnumChatFormatting.GRAY + "carry pls" + EnumChatFormatting.RESET + "\" or \"" + EnumChatFormatting.GRAY + "carry plz" + EnumChatFormatting.RESET + "\""); + drawHoveringText(helpTooltip, mouseX, mouseY); + } else if (btnClose.isMouseOver()) { + drawHoveringText(Arrays.asList(EnumChatFormatting.RED + "Save & Close", "" + EnumChatFormatting.GRAY + EnumChatFormatting.ITALIC + "Hint:" + EnumChatFormatting.RESET + " alternatively press ESC"), mouseX, mouseY); + } + } + + @Override + public void updateScreen() { + if (this.rulesList != null) { + this.rulesList.updateFocusedTextField(); + } + } + + @Override + protected void actionPerformed(GuiButton button) { + if (button == btnUndoAll) { + GuiYesNo guiUndoAll = new GuiYesNo(this, + EnumChatFormatting.BOLD + "Discard " + EnumChatFormatting.RED + EnumChatFormatting.BOLD + "all " + EnumChatFormatting.RESET + EnumChatFormatting.BOLD + "changes?", + EnumChatFormatting.RED + "This action cannot be reverted!", + 21000000); + guiUndoAll.setButtonDelay(60); + mc.displayGuiScreen(guiUndoAll); + } else if (button == btnDefaultAll) { + GuiYesNo guiDefaultAll = new GuiYesNo(this, + EnumChatFormatting.BOLD + "Reset to default rules?", + "This will remove all custom rules! " + EnumChatFormatting.RED + "This action cannot be reverted once saved!", + 42000000); + guiDefaultAll.setButtonDelay(60); + mc.displayGuiScreen(guiDefaultAll); + } else if (button == btnHelp) { + GuiConfirmOpenLink guiHelp = new GuiConfirmOpenLink(this, "https://discord.gg/fU2tFPf", 50000000, true); + guiHelp.disableSecurityWarning(); + mc.displayGuiScreen(guiHelp); + } else if (button == btnClose) { + rules.saveToFile(); + mc.displayGuiScreen(null); + } + } + + @Override + protected void keyTyped(char eventChar, int eventKey) { + if (eventKey == Keyboard.KEY_ESCAPE) { + rules.saveToFile(); + mc.displayGuiScreen(null); + } else { + int focusedRuleId = this.rulesList.keyTyped(eventChar, eventKey); + if (focusedRuleId >= 0 && (eventKey == Keyboard.KEY_UP || eventKey == Keyboard.KEY_DOWN)) { + boolean moved = rules.move(focusedRuleId, eventKey == Keyboard.KEY_DOWN); + if (moved) { + initGui(); + } + } + } + } + + @Override + public void onGuiClosed() { + Keyboard.enableRepeatEvents(false); + updateLastScrollDistance(); + } + + private void updateLastScrollDistance() { + if (this.rulesList != null) { + try { + lastScrollDistance = ReflectionHelper.getPrivateValue(GuiScrollingList.class, this.rulesList, "scrollDistance"); + } catch (ReflectionHelper.UnableToAccessFieldException ignored) { + lastScrollDistance = 0; + } + } + } + + @Override + public void confirmClicked(boolean result, int id) { + if (result) { + if (id == 21000000) { + // discard all changes + rules.loadFromFile(); + } else if (id == 42000000) { + // reset all rules to default + rules.resetToDefault(); + } else if (id == 50000000) { + try { + Desktop.getDesktop().browse(new URI("https://discord.gg/fU2tFPf")); + } catch (Throwable throwable) { + Cowlection.getInstance().getLogger().error("Couldn't open link https://discord.gg/fU2tFPf", throwable); + } + } else if (id < 9000000) { + // user confirmed rule deletion + deleteRule(id); + } + } + mc.displayGuiScreen(this); + } + + private void addNewRule(int slotIndex) { + this.rules.add(slotIndex); + initGui(); + } + + public void deleteRule(int id) { + Rule removedRule = rules.remove(id); + if (removedRule != null) { + initGui(); + } + } + + /** + * Based on GuiModList.Info + */ + private class RulesListGui extends GuiScrollingList { + private final List<RuleEntry> ruleEntries; + private GuiTextField focusedTextField; + private RuleEntry hoveredEntry; + + public RulesListGui(int width, List<Rule> rules, float lastScrollDistance) { + super(RuleEditorGui.this.mc, + width, + RuleEditorGui.this.height, + 30, RuleEditorGui.this.height - 5, 12, 18, + RuleEditorGui.this.width, + RuleEditorGui.this.height); + setHeaderInfo(true, 20); + this.ruleEntries = convertToEntries(rules); + try { + // scroll to previous location + ReflectionHelper.setPrivateValue(GuiScrollingList.class, this, lastScrollDistance, "scrollDistance"); + } catch (ReflectionHelper.UnableToAccessFieldException ignored) { + } + } + + private List<RuleEntry> convertToEntries(List<Rule> rules) { + List<RuleEntry> ruleEntries = new ArrayList<>(); + for (int i = 0, rulesSize = rules.size(); i < rulesSize; i++) { + Rule rule = rules.get(i); + ruleEntries.add(new RuleEntry(rule, i)); + } + return ruleEntries; + } + + @Override + protected void drawHeader(int entryRight, int relativeY, Tessellator tess) { + if (relativeY >= 0) { + int currentX = this.left + 15; + GlStateManager.enableBlend(); + RuleEditorGui.this.fontRendererObj.drawStringWithShadow(EnumChatFormatting.BOLD + "If Party notes contain…", currentX, relativeY + 5, 0xFFFFFF); + currentX += 180; + RuleEditorGui.this.fontRendererObj.drawStringWithShadow(EnumChatFormatting.BOLD + "…mark party:", currentX, relativeY + 5, 0xFFFFFF); + GlStateManager.disableAlpha(); + GlStateManager.disableBlend(); + } + } + + @Override + protected int getSize() { + return rules.getCount(); + } + + @Override + protected void elementClicked(int index, boolean doubleClick) { + this.ruleEntries.get(index).mousePressed(mouseX, mouseY, index); + } + + @Override + protected boolean isSelected(int index) { + return false; + } + + @Override + protected void drawBackground() { + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + hoveredEntry = null; + super.drawScreen(mouseX, mouseY, partialTicks); + if (hoveredEntry != null) { + hoveredEntry.drawToolTip(mouseX, mouseY); + } + } + + protected void updateFocusedTextField() { + if (focusedTextField != null) { + if (focusedTextField.isFocused()) { + focusedTextField.updateCursorCounter(); + } else { + focusedTextField = null; + } + } + } + + @Override + protected void drawSlot(int slotIdx, int entryRight, int slotTop, int slotBuffer, Tessellator tess) { + RuleEntry rule = ruleEntries.get(slotIdx); + if (rule != null) { + rule.drawEntry(this.left + 4, slotTop); + if (mouseY >= slotTop && mouseY < slotTop + this.slotHeight) { + // mouse is over this slot + hoveredEntry = rule; + } + } + } + + public int keyTyped(char eventChar, int eventKey) { + int focusedRuleId = -1; + for (int i = 0, ruleEntriesSize = ruleEntries.size(); i < ruleEntriesSize; i++) { + RuleEntry ruleEntry = ruleEntries.get(i); + boolean isFieldFocused = ruleEntry.keyTyped(eventChar, eventKey); + if (isFieldFocused) { + focusedRuleId = i; + break; + } + } + return focusedRuleId; + } + + /** + * Based on: + * + * @see GuiConfigEntries.StringEntry + * @see GuiEditArrayEntries.BaseEntry + */ + private class RuleEntry { + private final Rule rule; + private final GuiCheckBox chkEnabled; + private final HoverChecker hoverCheckerEnabled; + private final GuiTextField fieldTrigger; + private final List<String> fieldTriggerTooltip; + private final GuiButton btnTriggerType; + private final List<String> triggerTypeExplanation; + private final GuiButton btnPartyType; + private final HoverChecker hoverCheckerPartyType; + private final GuiTextField fieldMiddleText; + private final ItemStack demoItem; + protected final GuiButton btnAddNewEntryAbove; + private final HoverChecker hoverCheckerAddNewEntryAbove; + protected final GuiButton btnRemoveEntry; + private final HoverChecker hoverCheckerRemoveEntry; + private final List<String> enabledToolTip; + private final List<String> partyTypeToolTip; + private final List<String> addNewToolTip; + private final List<String> removeToolTip; + + public RuleEntry(Rule rule, int index) { + this.rule = rule; + // enabled + this.chkEnabled = new GuiCheckBox(32, 0, 0, "", rule.isEnabled()); + + // trigger: + this.fieldTrigger = new GuiTextField(52, fontRendererObj, 0, 0, 120, 12); + this.fieldTrigger.setText(rule.getTriggerText()); + this.fieldTriggerTooltip = new ArrayList<>(); + this.btnTriggerType = new GuiButtonExt(50, 0, 0, 36, 14, this.rule.getTriggerType().toButtonText()); + this.triggerTypeExplanation = new ArrayList<>(); + this.triggerTypeExplanation.add(EnumChatFormatting.BOLD + "Trigger type:"); + this.triggerTypeExplanation.add("Text " + EnumChatFormatting.LIGHT_PURPLE + "= " + EnumChatFormatting.RESET + "basic Text filter"); + this.triggerTypeExplanation.add("Text+ " + EnumChatFormatting.LIGHT_PURPLE + "= " + EnumChatFormatting.RESET + "Advanced text filter with optional excluding \"" + EnumChatFormatting.YELLOW + "(don't match this)" + EnumChatFormatting.RESET + + "\" and alternative trigger words \"" + EnumChatFormatting.YELLOW + "one,two" + EnumChatFormatting.RESET + "\""); + this.triggerTypeExplanation.add("Regex " + EnumChatFormatting.LIGHT_PURPLE + "= " + EnumChatFormatting.RESET + "Java Regular Expressions (experts only)"); + + // action: + btnPartyType = new GuiButtonExt(60, 0, 0, 60, 16, this.rule.getPartyType().toFancyString()); + + this.fieldMiddleText = new GuiTextField(54, fontRendererObj, 0, 0, 60, 12); + String middleText = rule.getMiddleText(); + fieldMiddleText.setText(middleText == null ? "" : Utils.toHumanColorCodes(middleText)); + + // preview: + NBTTagCompound partyNbt = new NBTTagCompound(); + NBTTagCompound skinDetails = new NBTTagCompound(); + String skinUuid; + String skinTexture; + switch (index % 4) { + case 1: // cake + skinUuid = "afb489c4-9fc8-48a4-98f2-dd7bea414c9a"; + skinTexture = "ZWMyNDFhNTk3YzI4NWUxMDRjMjcxMTk2ZDc4NWRiNGNkMDExMGE0MGM4ZjhlNWQzNTRjNTY0NDE1OTU2N2M5ZCJ9fX0="; + break; + case 2: // villager + skinUuid = "bd482739-767c-45dc-a1f8-c33c40530952"; + skinTexture = "YjRiZDgzMjgxM2FjMzhlNjg2NDg5MzhkN2EzMmY2YmEyOTgwMWFhZjMxNzQwNDM2N2YyMTRiNzhiNGQ0NzU0YyJ9fX0="; + break; + case 3: // squid + skinUuid = "72e64683-e313-4c36-a408-c66b64e94af5"; + skinTexture = "NWU4OTEwMWQ1Y2M3NGFhNDU4MDIxYTA2MGY2Mjg5YTUxYTM1YTdkMzRkOGNhZGRmYzNjZGYzYjJjOWEwNzFhIn19fQ=="; + break; + default: // cow + skinUuid = "f159b274-c22e-4340-b7c1-52abde147713"; + skinTexture = "ZDBlNGU2ZmJmNWYzZGNmOTQ0MjJhMWYzMTk0NDhmMTUyMzY5ZDE3OWRiZmJjZGYwMGU1YmZlODQ5NWZhOTc3In19fQ=="; + break; + } + GameProfile gameProfile = new GameProfile(UUID.fromString(skinUuid), null); + gameProfile.getProperties().put("textures", new Property("url", "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUv" + skinTexture)); + NBTUtil.writeGameProfile(skinDetails, gameProfile); + partyNbt.setTag("SkullOwner", skinDetails); + demoItem = new ItemStack(Items.skull, 1, 3); + demoItem.setTagCompound(partyNbt); + + // add new & remove entry: + this.btnAddNewEntryAbove = new GuiButtonExt(0, 0, 0, 18, 18, "+"); + this.btnAddNewEntryAbove.packedFGColour = GuiUtils.getColorCode('2', true); + + this.btnRemoveEntry = new GuiButtonExt(0, 0, 0, 18, 18, "x"); + this.btnRemoveEntry.packedFGColour = GuiUtils.getColorCode('c', true); + + // hover checkers + this.hoverCheckerEnabled = new HoverChecker(this.chkEnabled, 300); + this.hoverCheckerPartyType = new HoverChecker(this.btnPartyType, 300); + this.hoverCheckerAddNewEntryAbove = new HoverChecker(this.btnAddNewEntryAbove, 300); + this.hoverCheckerRemoveEntry = new HoverChecker(this.btnRemoveEntry, 300); + + // tooltips + this.enabledToolTip = Collections.singletonList("Enable/disable this rule"); + this.partyTypeToolTip = new ArrayList<>(); + partyTypeToolTip.add("Priority: " + EnumChatFormatting.RED + "⬛" + EnumChatFormatting.WHITE + " > " + EnumChatFormatting.GOLD + "⬛" + EnumChatFormatting.WHITE + " > " + EnumChatFormatting.GREEN + "⬛"); + partyTypeToolTip.add(EnumChatFormatting.GRAY + "= once a party is marked with " + EnumChatFormatting.RED + "⬛" + EnumChatFormatting.GRAY + ", another rule does/can not change it back to e.g. " + EnumChatFormatting.GREEN + "⬛"); + partyTypeToolTip.add(" ‣ suitable " + EnumChatFormatting.GREEN + "⬛"); + partyTypeToolTip.add(" ‣ unideal " + EnumChatFormatting.GOLD + "⬛"); + partyTypeToolTip.add(" ‣ block " + EnumChatFormatting.RED + "⬛"); + this.addNewToolTip = Collections.singletonList(I18n.format("fml.configgui.tooltip.addNewEntryAbove")); + this.removeToolTip = Collections.singletonList(I18n.format("fml.configgui.tooltip.removeEntry")); + refreshInputElements(); + } + + private void refreshInputElements() { + // trigger: + btnTriggerType.displayString = rule.getTriggerType().toButtonText(); + fieldTriggerTooltip.clear(); + if (rule.isValid()) { + String triggerText = this.rule.getTriggerText(); + if (rule.getTriggerType() == Rule.TriggerType.SIMPLE_REGEX) { + String triggerRegex = rule.getTriggerRegex(); + // matches: + String triggerMatch = triggerRegex.replaceAll("\\(\\?<?![^)]+\\)", "").replace("\\(", "(").replace("\\)", ")"); + if (triggerMatch.contains("|")) { + triggerMatch = addFancySeparators(triggerMatch); + } + fieldTriggerTooltip.add("Matches: " + EnumChatFormatting.YELLOW + triggerMatch); + + // if not preceded/followed by: + Pattern exclusionPattern = Pattern.compile("\\(\\?(<?)!([^)]+)\\)"); + Matcher exclusionMatcher = exclusionPattern.matcher(triggerRegex); + while (exclusionMatcher.find()) { + boolean isFollowing = exclusionMatcher.group(1).isEmpty(); + String notMatching = exclusionMatcher.group(2); + if (notMatching.contains("|")) { + notMatching = addFancySeparators(notMatching); + } + fieldTriggerTooltip.add("If not " + (isFollowing ? "followed" : "preceded") + " by: " + EnumChatFormatting.YELLOW + notMatching); + } + } else if (rule.getTriggerType() == Rule.TriggerType.REGEX) { + fieldTriggerTooltip.add("Matches regex: " + EnumChatFormatting.YELLOW + triggerText); + } else { + // TEXT + fieldTriggerTooltip.add("Matches: " + EnumChatFormatting.YELLOW + triggerText); + } + } else { + fieldTriggerTooltip.addAll(rule.getPatternException()); + } + } + + /** + * Change | separated string into enumeration + * + * @see CommandBase#joinNiceString + */ + public String addFancySeparators(String string) { + StringBuilder sb = new StringBuilder(); + String[] segments = string.split("\\|"); + for (int i = 0; i < segments.length; ++i) { + if (i > 0) { + if (i == segments.length - 1) { + sb.append(EnumChatFormatting.WHITE).append(", or ").append(EnumChatFormatting.YELLOW); + } else { + sb.append(EnumChatFormatting.WHITE).append(", ").append(EnumChatFormatting.YELLOW); + } + } + sb.append(segments[i]); + } + return sb.toString(); + } + + public void drawEntry(int x, int y) { + int currentX = x; + + // enabled checkbox: + chkEnabled.xPosition = currentX; + chkEnabled.yPosition = y + 2; + chkEnabled.drawButton(mc, mouseX, mouseY); + currentX += 20; // button width + + // trigger: + fieldTrigger.xPosition = currentX; + fieldTrigger.yPosition = y + 1; + fieldTrigger.drawTextBox(); + if (rule.isValid()) { + fieldTrigger.setTextColor(14737632); + } else { + fieldTrigger.setTextColor(0xFFdd2222); + } + currentX += 130; // field width + + if (expertMode) { + btnTriggerType.xPosition = currentX - 9; + btnTriggerType.yPosition = y; + btnTriggerType.drawButton(mc, mouseX, mouseY); + currentX += 40; // button width + space + } else { + currentX += 10; // space + } + + // action: + btnPartyType.xPosition = currentX; + btnPartyType.yPosition = y - 1; + btnPartyType.drawButton(mc, mouseX, mouseY); + currentX += 65; // button width + + fieldMiddleText.xPosition = currentX; + fieldMiddleText.yPosition = y + 1; + fieldMiddleText.drawTextBox(); + currentX += 65; // field width + + + // party preview: + drawItemsPreview(demoItem, currentX, y - 2); + + GlStateManager.pushMatrix(); + GlStateManager.translate(0, 0, 281); + double scaleFactor = 0.5; + GlStateManager.scale(scaleFactor, scaleFactor, 0); + RuleEditorGui.this.fontRendererObj.drawStringWithShadow(rule.getMiddleText(), (float) ((currentX + 1) / scaleFactor), (float) ((y + 5) / scaleFactor), 0xFFffffff); + GlStateManager.popMatrix(); + + if (MooConfig.dungPartyFinderOverlayDrawBackground) { + DataHelper.PartyType partyType = rule.getPartyType(); + GlStateManager.pushMatrix(); + if (partyType == DataHelper.PartyType.UNJOINABLE_OR_BLOCK) { + GlStateManager.translate(0, 0, partyType.getZIndex()); + } + Gui.drawRect(currentX + 1, y - 1, currentX + 16 + 1, y + 16 - 1, partyType.getColor()); + GlStateManager.popMatrix(); + } + currentX += 35; + + // draw add & remove entry buttons: + this.btnAddNewEntryAbove.visible = true; + this.btnAddNewEntryAbove.xPosition = currentX; + this.btnAddNewEntryAbove.yPosition = y - 2; + this.btnAddNewEntryAbove.drawButton(mc, mouseX, mouseY); + currentX += 20; + + if (getSize() > 1) { + this.btnRemoveEntry.xPosition = currentX; + this.btnRemoveEntry.yPosition = y - 2; + this.btnRemoveEntry.drawButton(mc, mouseX, mouseY); + } else { + this.btnRemoveEntry.visible = false; + } + + if (!rule.isEnabled()) { + drawRect(x + 18, y - 1, currentX - 36, y + 15, 0x99666666); + } + } + + private void drawItemsPreview(ItemStack item, int xFakeHotbar, int yFakeHotbar) { + GlStateManager.pushMatrix(); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + Minecraft.getMinecraft().getTextureManager().bindTexture(new ResourceLocation("textures/gui/container/inventory.png")); + + GuiUtils.drawTexturedModalRect(xFakeHotbar, yFakeHotbar, 87, 25, 18, 18, 0); + + GlStateManager.enableRescaleNormal(); + GlStateManager.enableBlend(); + + GlStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, 1, 0); + RenderHelper.enableGUIStandardItemLighting(); + + int xItem = xFakeHotbar + 1; + int yItem = yFakeHotbar + 1; + Minecraft.getMinecraft().getRenderItem().renderItemAndEffectIntoGUI(item, xItem, yItem); + RenderHelper.disableStandardItemLighting(); + GlStateManager.disableRescaleNormal(); + GlStateManager.disableBlend(); + GlStateManager.popMatrix(); + } + + public void drawToolTip(int mouseX, int mouseY) { + GlStateManager.pushMatrix(); + if (mouseX >= fieldTrigger.xPosition && mouseX <= fieldTrigger.xPosition + fieldTrigger.width + && mouseY >= fieldTrigger.yPosition && mouseY <= fieldTrigger.yPosition + fieldTrigger.height) { + drawHoveringText(this.fieldTriggerTooltip, mouseX, mouseY); + } else if (this.hoverCheckerEnabled.checkHover(mouseX, mouseY, true)) { + drawHoveringText(this.enabledToolTip, mouseX, mouseY); + } else if (btnTriggerType.isMouseOver()) { + drawHoveringText(this.triggerTypeExplanation, mouseX, mouseY); + } else if (mouseX >= fieldMiddleText.xPosition && mouseX <= fieldMiddleText.xPosition + fieldMiddleText.width + && mouseY >= fieldMiddleText.yPosition && mouseY <= fieldMiddleText.yPosition + fieldMiddleText.height) { + drawHoveringText(colorCodesExplanation, mouseX, mouseY); + } else if (this.hoverCheckerPartyType.checkHover(mouseX, mouseY, true)) { + drawHoveringText(partyTypeToolTip, mouseX, mouseY); + } else if (this.hoverCheckerAddNewEntryAbove.checkHover(mouseX, mouseY, true)) { + drawHoveringText(this.addNewToolTip, mouseX, mouseY); + } else if (this.hoverCheckerRemoveEntry.checkHover(mouseX, mouseY, true)) { + drawHoveringText(this.removeToolTip, mouseX, mouseY); + } + RenderHelper.disableStandardItemLighting(); + GlStateManager.popMatrix(); + } + + public void mousePressed(int mouseX, int mouseY, int slotIndex) { + if (focusedTextField != null && focusedTextField.isFocused()) { + // un-focus previous text field + focusedTextField.setFocused(false); + } + fieldTrigger.mouseClicked(mouseX, mouseY, /* left click: */ 0); + fieldMiddleText.mouseClicked(mouseX, mouseY, /* left click: */ 0); + if (fieldTrigger.isFocused()) { + focusedTextField = fieldTrigger; + } else if (fieldMiddleText.isFocused()) { + focusedTextField = fieldMiddleText; + } else if (chkEnabled.mousePressed(mc, mouseX, mouseY)) { + this.rule.setEnabled(chkEnabled.isChecked()); + } else if (btnTriggerType.mousePressed(mc, mouseX, mouseY)) { + boolean isRegex = rule.getTriggerType() == Rule.TriggerType.REGEX; + GuiYesNo guiCycleTriggerType = new GuiYesNo((result, id) -> { + if (result) { + this.rule.toggleTriggerType(); + refreshInputElements(); + } + mc.displayGuiScreen(RuleEditorGui.this); + }, EnumChatFormatting.BOLD + "Switch to " + (isRegex ? "text matcher?" : "Regex?"), + isRegex ? "Do you want to change this trigger text back to a text matcher?\n" + EnumChatFormatting.YELLOW + "This will probably break this Party notes rule." + : "Do you want to change this trigger text to Regular Expressions?\nIf you never heard this term, it's not a good idea to do so, because " + EnumChatFormatting.YELLOW + "you can easily break your Party notes rules.", + 40000000); + guiCycleTriggerType.setButtonDelay(60); + mc.displayGuiScreen(guiCycleTriggerType); + } else if (btnPartyType.mousePressed(mc, mouseX, mouseY)) { + switch (rule.getPartyType()) { + case SUITABLE: + rule.setPartyType(DataHelper.PartyType.UNIDEAL); + break; + case UNIDEAL: + rule.setPartyType(DataHelper.PartyType.UNJOINABLE_OR_BLOCK); + break; + default: // UNJOINABLE_OR_BLOCK + rule.setPartyType(DataHelper.PartyType.SUITABLE); + break; + } + btnPartyType.displayString = rule.getPartyType().toFancyString(); + } else if (btnAddNewEntryAbove.mousePressed(mc, mouseX, mouseY)) { + addNewRule(slotIndex); + } else if (btnRemoveEntry.mousePressed(mc, mouseX, mouseY)) { + if (isShiftKeyDown()) { + deleteRule(slotIndex); + } else { + // show rule deletion confirmation screen + GuiYesNo guiDeleteRule = new GuiYesNo(RuleEditorGui.this, + EnumChatFormatting.BOLD + "Delete the rule " + EnumChatFormatting.RESET + EnumChatFormatting.YELLOW + fieldTrigger.getText() + EnumChatFormatting.RESET + EnumChatFormatting.BOLD + "?", + EnumChatFormatting.RED + "This action cannot be reverted!\n\n" + EnumChatFormatting.GRAY + "Hint: You can hold SHIFT while clicking the " + EnumChatFormatting.RED + "x " + EnumChatFormatting.GRAY + "button to skip this confirmation screen.", + slotIndex); + mc.displayGuiScreen(guiDeleteRule); + } + } + } + + public boolean keyTyped(char eventChar, int eventKey) { + boolean isFieldFocused = false; + if (fieldTrigger.isFocused()) { + isFieldFocused = true; + boolean changedText = fieldTrigger.textboxKeyTyped(eventChar, eventKey); + if (changedText) { + rule.setTriggerTextAndRevalidateRule(fieldTrigger.getText()); + refreshInputElements(); + } + } else if (fieldMiddleText.isFocused()) { + isFieldFocused = true; + boolean changedText = fieldMiddleText.textboxKeyTyped(eventChar, eventKey); + if (changedText) { + rule.setMiddleText(fieldMiddleText.getText()); + refreshInputElements(); + } + } + return isFieldFocused; + } + } + } +} diff --git a/src/main/java/de/cowtipper/cowlection/partyfinder/Rules.java b/src/main/java/de/cowtipper/cowlection/partyfinder/Rules.java new file mode 100644 index 0000000..2421554 --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/partyfinder/Rules.java @@ -0,0 +1,148 @@ +package de.cowtipper.cowlection.partyfinder; + +import com.google.gson.JsonParseException; +import com.google.gson.reflect.TypeToken; +import de.cowtipper.cowlection.Cowlection; +import de.cowtipper.cowlection.data.DataHelper; +import de.cowtipper.cowlection.util.GsonUtils; +import net.minecraft.util.MathHelper; +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Rules { + private final List<Rule> partyFinderRules; + private final File file; + private final Cowlection main; + + public Rules(Cowlection main, File file) { + this.partyFinderRules = new ArrayList<>(); + this.file = file; + this.main = main; + if (file.exists()) { + loadFromFile(); + } else { + try { + file.createNewFile(); + addDefaultRules(); + saveToFile(); + } catch (IOException e) { + main.getLogger().error("Couldn't create dungeons party finder rules" + this.file, e); + } + } + } + + public List<Rule> getRules() { + return partyFinderRules; + } + + public int getCount() { + return partyFinderRules.size(); + } + + /** + * Add new empty rule + */ + public void add(int slotIndex) { + this.partyFinderRules.add(slotIndex, new Rule(true, "", Rule.TriggerType.TEXT, "", DataHelper.PartyType.SUITABLE)); + } + + public Rule remove(int ruleId) { + Rule removedRule = partyFinderRules.remove(ruleId); + if (removedRule != null) { + removedRule.setEnabled(false); + } + return removedRule; + } + + public boolean move(int ruleId, boolean moveDown) { + int lastIndex = partyFinderRules.size() - 1; + if (partyFinderRules.size() >= 2 && (moveDown && ruleId != lastIndex || !moveDown && ruleId > 0)) { + int otherRuleId = MathHelper.clamp_int(ruleId + (moveDown ? 1 : -1), 0, lastIndex); + if (ruleId != otherRuleId) { + Collections.swap(partyFinderRules, ruleId, otherRuleId); + return true; + } + } + return false; + } + + public void saveToFile() { + partyFinderRules.stream().filter(rule -> rule.getTriggerText().isEmpty()).forEach(rule -> rule.setEnabled(false)); + try { + String partyFinderRules = GsonUtils.toJson(this.partyFinderRules); + FileUtils.writeStringToFile(file, partyFinderRules, StandardCharsets.UTF_8); + } catch (IOException e) { + main.getLogger().error("Couldn't save dungeons party finder rules" + this.file, e); + } + } + + public void loadFromFile() { + this.partyFinderRules.clear(); + try { + String ruleData = FileUtils.readFileToString(this.file, StandardCharsets.UTF_8); + if (ruleData.length() > 0) { + this.partyFinderRules.addAll(parseJson(ruleData)); + } + } catch (IOException e) { + main.getLogger().error("Couldn't read dungeons party finder rules file " + this.file, e); + } catch (JsonParseException e) { + main.getLogger().error("Couldn't parse dungeons party finder rules file " + this.file, e); + try { + Files.copy(file.toPath(), file.toPath().resolveSibling("partyfinder-rules_corrupted.json"), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException ioException) { + main.getLogger().error("Couldn't copy the corrupted dungeons party finder rules file " + this.file, e); + } + } + } + + public void resetToDefault() { + partyFinderRules.clear(); + addDefaultRules(); + } + + private void addDefaultRules() { + partyFinderRules.add(new Rule(false, + "(free )carr", Rule.TriggerType.SIMPLE_REGEX, + "§bcarry", DataHelper.PartyType.SUITABLE)); + partyFinderRules.add(new Rule(false, + "free carr", Rule.TriggerType.TEXT, + "§acarry", DataHelper.PartyType.SUITABLE)); + partyFinderRules.add(new Rule(true, + "hyp", Rule.TriggerType.TEXT, + "§ahyp", DataHelper.PartyType.SUITABLE)); + partyFinderRules.add(new Rule(false, + "pls,plz,help", Rule.TriggerType.SIMPLE_REGEX, + "", DataHelper.PartyType.UNIDEAL)); + partyFinderRules.add(new Rule(true, + "speed", Rule.TriggerType.TEXT, + "§f✦", DataHelper.PartyType.SUITABLE)); + partyFinderRules.add(new Rule(true, + "frag", Rule.TriggerType.TEXT, + "§ffrag", DataHelper.PartyType.SUITABLE)); + partyFinderRules.add(new Rule(true, + "exp", Rule.TriggerType.TEXT, + "§fxp", DataHelper.PartyType.SUITABLE)); + partyFinderRules.add(new Rule(true, + "s+", Rule.TriggerType.TEXT, + "§fS+", DataHelper.PartyType.SUITABLE)); + } + + private List<Rule> parseJson(String rulesData) { + Type collectionType = new TypeToken<List<Rule>>() { + }.getType(); + List<Rule> rules = GsonUtils.fromJson(rulesData, collectionType); + for (Rule rule : rules) { + rule.postConstructor(); + } + return rules; + } +} diff --git a/src/main/java/de/cowtipper/cowlection/util/Utils.java b/src/main/java/de/cowtipper/cowlection/util/Utils.java index 35b1231..2db72d1 100644 --- a/src/main/java/de/cowtipper/cowlection/util/Utils.java +++ b/src/main/java/de/cowtipper/cowlection/util/Utils.java @@ -24,12 +24,15 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; public final class Utils { public static final Pattern VALID_UUID_PATTERN = Pattern.compile("^(\\w{8})-(\\w{4})-(\\w{4})-(\\w{4})-(\\w{12})$"); private static final Pattern VALID_USERNAME = Pattern.compile("^[\\w]{1,16}$"); + private static final Pattern ALTERNATE_COLOR_CODES_PATTERN = Pattern.compile("&([0-9a-fk-or])"); + private static final Pattern MC_COLOR_CODES_PATTERN = Pattern.compile("§([0-9a-fk-or])"); private static final NavigableMap<Double, Character> NUMBER_SUFFIXES = new TreeMap<>(); private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#,##0.#", new DecimalFormatSymbols(Locale.ENGLISH)); private static final DecimalFormat NUMBER_FORMAT = new DecimalFormat("#,##0", new DecimalFormatSymbols(Locale.ENGLISH)); @@ -57,6 +60,22 @@ public final class Utils { } /** + * Replaces &colorCode with §colorCode + */ + public static String toMcColorCodes(String string) { + Matcher matcher = ALTERNATE_COLOR_CODES_PATTERN.matcher(string); + return matcher.replaceAll("§$1"); + } + + /** + * Replaces §colorCode with &colorCode + */ + public static String toHumanColorCodes(String string) { + Matcher matcher = MC_COLOR_CODES_PATTERN.matcher(string); + return matcher.replaceAll("&$1"); + } + + /** * Turn timestamp into pretty-formatted duration and date details. * * @param timestamp last login/logout |