aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCow <cow@volloeko.de>2021-08-02 12:43:41 +0200
committerCow <cow@volloeko.de>2021-08-02 12:43:41 +0200
commitd2490aad11dec8227b132d55b5afd9a65834011b (patch)
tree7aa8433f9d8eb2f5137ad90f2da7f7ccc253ac29
parentac41ecd855a511c76499d61983ddb6c723ae0844 (diff)
downloadCowlection-d2490aad11dec8227b132d55b5afd9a65834011b.tar.gz
Cowlection-d2490aad11dec8227b132d55b5afd9a65834011b.tar.bz2
Cowlection-d2490aad11dec8227b132d55b5afd9a65834011b.zip
Added Party Notes rule editor
-rw-r--r--CHANGELOG.md2
-rw-r--r--src/main/java/de/cowtipper/cowlection/Cowlection.java7
-rw-r--r--src/main/java/de/cowtipper/cowlection/command/MooCommand.java13
-rw-r--r--src/main/java/de/cowtipper/cowlection/config/MooConfig.java78
-rw-r--r--src/main/java/de/cowtipper/cowlection/config/gui/MooConfigCategoryScrolling.java14
-rw-r--r--src/main/java/de/cowtipper/cowlection/data/DataHelper.java27
-rw-r--r--src/main/java/de/cowtipper/cowlection/listener/skyblock/DungeonsListener.java84
-rw-r--r--src/main/java/de/cowtipper/cowlection/partyfinder/Rule.java200
-rw-r--r--src/main/java/de/cowtipper/cowlection/partyfinder/RuleEditorGui.java695
-rw-r--r--src/main/java/de/cowtipper/cowlection/partyfinder/Rules.java148
-rw-r--r--src/main/java/de/cowtipper/cowlection/util/Utils.java19
-rw-r--r--src/main/resources/assets/cowlection/lang/en_US.lang10
12 files changed, 1227 insertions, 70 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d331c11..3da1145 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- (Dungeons) player lookups:
- added ironman icon ♲
- added average secrets per completion
+- Dungeon Party Finder: customizable Party Notes filters (`/moo dungeon rules` or `/moo dr`)
+ - Add and edit additional rules to mark Dungeon parties based on their Party notes
- Added data for Enderman slayer, Voidling minions, and Hard Stone minions
- New keybindings to...
1) run `/moo waila` command (disabled by default; MC Options > Controls > `Cowlection`)
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
diff --git a/src/main/resources/assets/cowlection/lang/en_US.lang b/src/main/resources/assets/cowlection/lang/en_US.lang
index 30b15d7..2c6480b 100644
--- a/src/main/resources/assets/cowlection/lang/en_US.lang
+++ b/src/main/resources/assets/cowlection/lang/en_US.lang
@@ -130,10 +130,6 @@ cowlection.config.dungDungeonReqMin=Minimum "Dungeon level required"
cowlection.config.dungDungeonReqMin.tooltip=Marks parties with lower "Dungeon level required" set than this value
cowlection.config.dungClassMin=Minimum preferred class level
cowlection.config.dungClassMin.tooltip=Marks parties with members with lower class level than this value
-cowlection.config.dungMarkPartiesWithCarry=Mark 'carry' parties?
-cowlection.config.dungMarkPartiesWithCarry.tooltip=Mark parties that have 'carry' in their notes?
-cowlection.config.dungMarkPartiesWithHyperion=Mark 'hyperion' parties?
-cowlection.config.dungMarkPartiesWithHyperion.tooltip=Mark parties that have 'hyperion' in their notes?
cowlection.config.dungMarkPartiesWithArcher=Mark parties with Archer class …
cowlection.config.dungMarkPartiesWithArcher.tooltip=Mark parties with Archer class either:\n ‣ always, or if duplicated: §c⬛ (blocked)\n ‣ always, or if duplicated: §6⬛ §e (unideal)\n ‣ do not mark
cowlection.config.dungMarkPartiesWithBerserk=Mark parties with Berserk class …
@@ -144,6 +140,12 @@ cowlection.config.dungMarkPartiesWithMage=Mark parties with Mage class …
cowlection.config.dungMarkPartiesWithMage.tooltip=Mark parties with Mage class either:\n ‣ always, or if duplicated: §c⬛ (blocked)\n ‣ always, or if duplicated: §6⬛ §e (unideal)\n ‣ do not mark
cowlection.config.dungMarkPartiesWithTank=Mark parties with Tank class …
cowlection.config.dungMarkPartiesWithTank.tooltip=Mark parties with Tank class either:\n ‣ always, or if duplicated: §c⬛ (blocked)\n ‣ always, or if duplicated: §6⬛ §e (unideal)\n ‣ do not mark
+cowlection.config.gotoPartyFinderRulesEditor=Open Party Finder Notes Rules Editor
+cowlection.config.gotoPartyFinderRulesEditor.tooltip=Add and edit additional rules to mark Dungeon parties based on their Party notes
+cowlection.config.dungPartyFinderRuleEditorSimplified=Use simplified Party Finder Notes Rules Editor
+cowlection.config.dungPartyFinderRuleEditorSimplified.tooltip=Use the simplified variant? Only deactivate this if you know what Regular Expressions are and how to use them!
+cowlection.config.dungPartyFinderRuleEditorShowOpenButton=Show '§8Rule editor ↗§r' button in Party Finder?
+cowlection.config.dungPartyFinderRuleEditorShowOpenButton.tooltip=If enabled, displays a button to open the Rules editor from the Dungeon Party Finder
cowlection.config.dungSendWrongFloorWarning=Warn if queued & entered floor are different
cowlection.config.dungSendWrongFloorWarning.tooltip=Send a warning when entering a dungeons floor which is different from the originally queued floor?
cowlection.commands.generic.exception=%s