aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRime <81419447+Emirlol@users.noreply.github.com>2025-02-27 23:48:55 +0300
committerGitHub <noreply@github.com>2025-02-28 04:48:55 +0800
commitfc478774143a73aa1470e6348d75896231fa21ca (patch)
treed0f808a784f2eae1d394cf43fa23949ec4fdc61b
parent37422acb185653f1d33c606687a97715db1a7264 (diff)
downloadSkyblocker-fc478774143a73aa1470e6348d75896231fa21ca.tar.gz
Skyblocker-fc478774143a73aa1470e6348d75896231fa21ca.tar.bz2
Skyblocker-fc478774143a73aa1470e6348d75896231fa21ca.zip
Chat rule location config overhaul (#1138)
* Chat rule location config overhaul * Fix incorrect logic in ItemTickList * Boolean → boolean * Update location name * Remove locations list * Revamp widgets in `ChatRuleConfigScreen` * Complete codec to decode both string and enumset * Take negated locations into account * Fix exclusion parsing and add tests * Clean up codec with Codec::either * Dynamic width calculation Also moves `Ignore Case` button to the next row, with the location config button. * Remove stale javadoc * Small code cleanup * Remove `UNKNOWN` and `MODERN_FORAGING_ISLAND` from the location selector * Future-proofing * Consider valid locations set of only `Location.UNKNOWN` as empty --------- Co-authored-by: Kevin <92656833+kevinthegreat1@users.noreply.github.com>
-rw-r--r--src/main/java/de/hysky/skyblocker/config/screens/powdertracker/ItemTickList.java58
-rw-r--r--src/main/java/de/hysky/skyblocker/config/screens/powdertracker/PowderFilterConfigScreen.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRule.java508
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleConfigScreen.java857
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleLocationConfigScreen.java72
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigListWidget.java12
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesHandler.java84
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/CodecUtils.java33
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/CollectionUtils.java13
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/Location.java219
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/WidgetUtils.java27
-rw-r--r--src/main/resources/assets/skyblocker/lang/en_us.json6
-rw-r--r--src/test/java/de/hysky/skyblocker/skyblock/chat/ChatRuleTest.java94
13 files changed, 1181 insertions, 804 deletions
diff --git a/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/ItemTickList.java b/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/ItemTickList.java
index ed67b456..db3dc933 100644
--- a/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/ItemTickList.java
+++ b/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/ItemTickList.java
@@ -9,16 +9,49 @@ import net.minecraft.client.gui.widget.CheckboxWidget;
import net.minecraft.client.gui.widget.ElementListWidget;
import net.minecraft.text.Text;
+import java.util.Collection;
import java.util.List;
-public class ItemTickList extends ElementListWidget<ItemTickList.ItemTickEntry> {
- private final List<String> filters;
- private final List<String> allItems;
+/**
+ * A checkbox list for filter configuring purposes.
+ */
+public class ItemTickList<T> extends ElementListWidget<ItemTickList.ItemTickEntry> {
+ private final Collection<T> filters;
+ private final Collection<T> allItems;
+ private final boolean whitelist;
- public ItemTickList(MinecraftClient minecraftClient, int width, int height, int y, int entryHeight, List<String> filters, List<String> allItems) {
+ /**
+ *
+ * @param minecraftClient Minecraft client.
+ * @param width The width of the list.
+ * @param height The height of the list.
+ * @param y The y value at which the list should render.
+ * @param entryHeight Height of a single item
+ * @param filters The items that will be marked. This should be a subset of allItems.
+ * @param allItems All possible values
+ */
+ public ItemTickList(MinecraftClient minecraftClient, int width, int height, int y, int entryHeight, Collection<T> filters, Collection<T> allItems) {
super(minecraftClient, width, height, y, entryHeight);
this.filters = filters;
this.allItems = allItems;
+ this.whitelist = false;
+ }
+
+ /**
+ * @param minecraftClient Minecraft client.
+ * @param width The width of the list.
+ * @param height The height of the list.
+ * @param y The y value at which the list should render.
+ * @param entryHeight Height of a single item
+ * @param filters The items that will be marked. This should be a subset of allItems.
+ * @param allItems All possible values
+ * @param whitelist Whether the filter logic works as a whitelist or blacklist, to change whether the boxes for items in the filters collection should be checked. As an example: PowderFilter keeps which items to remove inside the filter (blacklist), while ChatRuleLocation keeps which locations the feature should work in (whitelist).
+ */
+ public ItemTickList(MinecraftClient minecraftClient, int width, int height, int y, int entryHeight, Collection<T> filters, Collection<T> allItems, boolean whitelist) {
+ super(minecraftClient, width, height, y, entryHeight);
+ this.filters = filters;
+ this.allItems = allItems;
+ this.whitelist = whitelist;
}
public void clearAndInit() {
@@ -26,14 +59,19 @@ public class ItemTickList extends ElementListWidget<ItemTickList.ItemTickEntry>
init();
}
- public ItemTickList init() {
- for (String item : allItems) {
+ public ItemTickList<T> init() {
+ for (T item : allItems) {
ItemTickEntry entry = new ItemTickEntry(
- CheckboxWidget.builder(Text.of(item), client.textRenderer)
- .checked(!filters.contains(item))
+ CheckboxWidget.builder(Text.of(item.toString()), client.textRenderer)
+ .checked(whitelist == filters.contains(item))
.callback((checkbox1, checked) -> {
- if (checked) filters.remove(item);
- else filters.add(item);
+ if (whitelist) {
+ if (checked) filters.add(item);
+ else filters.remove(item);
+ } else {
+ if (checked) filters.remove(item);
+ else filters.add(item);
+ }
})
.build()
);
diff --git a/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/PowderFilterConfigScreen.java b/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/PowderFilterConfigScreen.java
index 84337d7b..fbd2668a 100644
--- a/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/PowderFilterConfigScreen.java
+++ b/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/PowderFilterConfigScreen.java
@@ -34,7 +34,7 @@ public class PowderFilterConfigScreen extends Screen {
assert client != null;
context.drawCenteredTextWithShadow(client.textRenderer, Text.translatable("skyblocker.config.mining.crystalHollows.powderTrackerFilter.screenTitle").formatted(Formatting.BOLD), width / 2, (32 - client.textRenderer.fontHeight) / 2, 0xFFFFFF);
});
- ItemTickList itemTickList = addDrawableChild(new ItemTickList(MinecraftClient.getInstance(), width, height - 96, 32, 24, filters, allItems).init());
+ ItemTickList<String> itemTickList = addDrawableChild(new ItemTickList<>(MinecraftClient.getInstance(), width, height - 96, 32, 24, filters, allItems).init());
//Grid code gratuitously stolen from WaypointsScreen. Same goes for the y and heights above.
GridWidget gridWidget = new GridWidget();
gridWidget.getMainPositioner().marginX(5).marginY(2);
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRule.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRule.java
index 7fd6844d..d60b18e5 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRule.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRule.java
@@ -1,263 +1,277 @@
package de.hysky.skyblocker.skyblock.chat;
+import com.mojang.datafixers.util.Either;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+import de.hysky.skyblocker.utils.CollectionUtils;
+import de.hysky.skyblocker.utils.Location;
import de.hysky.skyblocker.utils.Utils;
import net.minecraft.sound.SoundEvent;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.VisibleForTesting;
+import java.util.Arrays;
+import java.util.EnumSet;
import java.util.List;
-import java.util.Objects;
import java.util.Optional;
+import java.util.function.Function;
import java.util.regex.Pattern;
-import com.mojang.serialization.Codec;
-import com.mojang.serialization.codecs.RecordCodecBuilder;
-
/**
* Data class to contain all the settings for a chat rule
*/
public class ChatRule {
- private static final Codec<ChatRule> CODEC = RecordCodecBuilder.create(instance -> instance.group(
- Codec.STRING.fieldOf("name").forGetter(ChatRule::getName),
- Codec.BOOL.fieldOf("enabled").forGetter(ChatRule::getEnabled),
- Codec.BOOL.fieldOf("isPartialMatch").forGetter(ChatRule::getPartialMatch),
- Codec.BOOL.fieldOf("isRegex").forGetter(ChatRule::getRegex),
- Codec.BOOL.fieldOf("isIgnoreCase").forGetter(ChatRule::getIgnoreCase),
- Codec.STRING.fieldOf("filter").forGetter(ChatRule::getFilter),
- Codec.STRING.fieldOf("validLocations").forGetter(ChatRule::getValidLocations),
- Codec.BOOL.fieldOf("hideMessage").forGetter(ChatRule::getHideMessage),
- Codec.BOOL.fieldOf("showActionBar").forGetter(ChatRule::getShowActionBar),
- Codec.BOOL.fieldOf("showAnnouncement").forGetter(ChatRule::getShowAnnouncement),
- Codec.STRING.optionalFieldOf("replaceMessage").forGetter(ChatRule::getReplaceMessageOpt),
- SoundEvent.CODEC.optionalFieldOf("customSound").forGetter(ChatRule::getCustomSoundOpt))
- .apply(instance, ChatRule::new));
- public static final Codec<List<ChatRule>> LIST_CODEC = CODEC.listOf();
-
- private String name;
-
- //inputs
- private Boolean enabled;
- private Boolean isPartialMatch;
- private Boolean isRegex;
- private Boolean isIgnoreCase;
- private String filter;
- private String validLocations;
-
- //output
- private Boolean hideMessage;
- private Boolean showActionBar;
- private Boolean showAnnouncement;
- private String replaceMessage;
- private SoundEvent customSound;
- /**
- * Creates a chat rule with default options.
- */
- protected ChatRule() {
- this.name = "New Rule";
-
- this.enabled = true;
- this.isPartialMatch = false;
- this.isRegex = false;
- this.isIgnoreCase = true;
- this.filter = "";
- this.validLocations = "";
-
- this.hideMessage = true;
- this.showActionBar = false;
- this.showAnnouncement = false;
- this.replaceMessage = null;
- this.customSound = null;
- }
-
- public ChatRule(String name, Boolean enabled, Boolean isPartialMatch, Boolean isRegex, Boolean isIgnoreCase, String filter, String validLocations, Boolean hideMessage, Boolean showActionBar, Boolean showAnnouncement, String replaceMessage, SoundEvent customSound) {
- this.name = name;
- this.enabled = enabled;
- this.isPartialMatch = isPartialMatch;
- this.isRegex = isRegex;
- this.isIgnoreCase = isIgnoreCase;
- this.filter = filter;
- this.validLocations = validLocations;
- this.hideMessage = hideMessage;
- this.showActionBar = showActionBar;
- this.showAnnouncement = showAnnouncement;
- this.replaceMessage = replaceMessage;
- this.customSound = customSound;
- }
-
- private ChatRule(String name, Boolean enabled, Boolean isPartialMatch, Boolean isRegex, Boolean isIgnoreCase, String filter, String validLocations, Boolean hideMessage, Boolean showActionBar, Boolean showAnnouncement, Optional<String> replaceMessage, Optional<SoundEvent> customSound) {
- this(name, enabled, isPartialMatch, isRegex, isIgnoreCase, filter, validLocations, hideMessage, showActionBar, showAnnouncement, replaceMessage.orElse(null), customSound.orElse(null));
- }
-
- protected String getName() {
- return name;
- }
-
- protected void setName(String name) {
- this.name = name;
- }
-
- protected Boolean getEnabled() {
- return enabled;
- }
-
- protected void setEnabled(Boolean enabled) {
- this.enabled = enabled;
- }
-
- protected Boolean getPartialMatch() {
- return isPartialMatch;
- }
-
- protected void setPartialMatch(Boolean partialMatch) {
- isPartialMatch = partialMatch;
- }
-
- protected Boolean getRegex() {
- return isRegex;
- }
-
- protected void setRegex(Boolean regex) {
- isRegex = regex;
- }
-
- protected Boolean getIgnoreCase() {
- return isIgnoreCase;
- }
-
- protected void setIgnoreCase(Boolean ignoreCase) {
- isIgnoreCase = ignoreCase;
- }
-
- protected String getFilter() {
- return filter;
- }
-
- protected void setFilter(String filter) {
- this.filter = filter;
- }
-
- protected Boolean getHideMessage() {
- return hideMessage;
- }
-
- protected void setHideMessage(Boolean hideMessage) {
- this.hideMessage = hideMessage;
- }
-
- protected Boolean getShowActionBar() {
- return showActionBar;
- }
-
- protected void setShowActionBar(Boolean showActionBar) {
- this.showActionBar = showActionBar;
- }
-
- protected Boolean getShowAnnouncement() {
- return showAnnouncement;
- }
-
- protected void setShowAnnouncement(Boolean showAnnouncement) {
- this.showAnnouncement = showAnnouncement;
- }
-
- protected String getReplaceMessage() {
- return replaceMessage;
- }
-
- private Optional<String> getReplaceMessageOpt() {
- return replaceMessage == null ? Optional.empty() : Optional.of(replaceMessage);
- }
-
- protected void setReplaceMessage(String replaceMessage) {
- this.replaceMessage = replaceMessage;
- }
-
- protected SoundEvent getCustomSound() {
- return customSound;
- }
-
- private Optional<SoundEvent> getCustomSoundOpt() {
- return customSound == null ? Optional.empty() : Optional.of(customSound);
- }
-
- protected void setCustomSound(SoundEvent customSound) {
- this.customSound = customSound;
- }
-
- protected String getValidLocations() {
- return validLocations;
- }
-
- protected void setValidLocations(String validLocations) {
- this.validLocations = validLocations;
- }
-
- /**
- * checks every input option and if the games state and the inputted str matches them returns true.
- * @param inputString the chat message to check if fits
- * @return if the inputs are all true and the outputs should be performed
- */
- protected Boolean isMatch(String inputString) {
- //enabled
- if (!enabled) return false;
-
- //ignore case
- String testString;
- String testFilter;
-
- if (isIgnoreCase) {
- testString = inputString.toLowerCase();
- testFilter = filter.toLowerCase();
- } else {
- testString = inputString;
- testFilter = filter;
- }
-
- //filter
- if (testFilter.isBlank()) return false;
- if (isRegex) {
- if (isPartialMatch) {
- if (!Pattern.compile(testFilter).matcher(testString).find()) return false;
- } else {
- if (!testString.matches(testFilter)) return false;
- }
- } else {
- if (isPartialMatch) {
- if (!testString.contains(testFilter)) return false;
- } else {
- if (!testFilter.equals(testString)) return false;
- }
- }
-
- //location
- if (validLocations.isBlank()) { //if no locations do not check
- return true;
- }
-
- String cleanedMapLocation = Utils.getMap().toLowerCase().replace(" ", "");
- Boolean isLocationValid = null;
- for (String validLocation : validLocations.replace(" ", "").toLowerCase().split(",")) {//the locations are split by "," and start with ! if not locations
- if (validLocation == null) continue;
- if (validLocation.startsWith("!")) {//not location
- if (Objects.equals(validLocation.substring(1), cleanedMapLocation)) {
- isLocationValid = false;
- break;
- } else {
- isLocationValid = true;
- }
- } else {
- if (Objects.equals(validLocation, cleanedMapLocation)) { //normal location
- isLocationValid = true;
- break;
- }
- }
- }
-
- //if location is not in the list at all and is a not a "!" location or and is a normal location
- if (isLocationValid != null && isLocationValid) {
- return true;
- }
-
- return false;
- }
+ /**
+ * Codec that can decode both {@link String} and {@link EnumSet} of locations, while encoding only {@link EnumSet} of locations.
+ * <br>
+ * This is necessary due to a change in how the locations are stored in the config.
+ */
+ @VisibleForTesting
+ static final Codec<EnumSet<Location>> LOCATION_FIXING_CODEC = Codec.either(Location.SET_CODEC, Codec.STRING).xmap(
+ either -> either.map(Function.identity(), ChatRule::encodeString),
+ Either::left
+ );
+
+ private static final Codec<ChatRule> CODEC = RecordCodecBuilder.create(instance -> instance.group(
+ Codec.STRING.fieldOf("name").forGetter(ChatRule::getName),
+ Codec.BOOL.fieldOf("enabled").forGetter(ChatRule::getEnabled),
+ Codec.BOOL.fieldOf("isPartialMatch").forGetter(ChatRule::getPartialMatch),
+ Codec.BOOL.fieldOf("isRegex").forGetter(ChatRule::getRegex),
+ Codec.BOOL.fieldOf("isIgnoreCase").forGetter(ChatRule::getIgnoreCase),
+ Codec.STRING.fieldOf("filter").forGetter(ChatRule::getFilter),
+ LOCATION_FIXING_CODEC.fieldOf("validLocations").forGetter(ChatRule::getValidLocations),
+ Codec.BOOL.fieldOf("hideMessage").forGetter(ChatRule::getHideMessage),
+ Codec.BOOL.fieldOf("showActionBar").forGetter(ChatRule::getShowActionBar),
+ Codec.BOOL.fieldOf("showAnnouncement").forGetter(ChatRule::getShowAnnouncement),
+ Codec.STRING.optionalFieldOf("replaceMessage").forGetter(ChatRule::getReplaceMessageOpt),
+ SoundEvent.CODEC.optionalFieldOf("customSound").forGetter(ChatRule::getCustomSoundOpt)
+ ).apply(instance, ChatRule::new));
+ public static final Codec<List<ChatRule>> LIST_CODEC = CODEC.listOf();
+
+ private String name;
+
+ // Inputs
+ private boolean enabled;
+ private boolean isPartialMatch;
+ private boolean isRegex;
+ private boolean isIgnoreCase;
+ private String filter;
+ private EnumSet<Location> validLocations;
+
+ // Outputs
+ private boolean hideMessage;
+ private boolean showActionBar;
+ private boolean showAnnouncement;
+ private String replaceMessage;
+ private SoundEvent customSound;
+
+ /**
+ * Creates a chat rule with default options.
+ */
+ protected ChatRule() {
+ this.name = "New Rule";
+
+ this.enabled = true;
+ this.isPartialMatch = false;
+ this.isRegex = false;
+ this.isIgnoreCase = true;
+ this.filter = "";
+ this.validLocations = EnumSet.noneOf(Location.class);
+
+ this.hideMessage = true;
+ this.showActionBar = false;
+ this.showAnnouncement = false;
+ this.replaceMessage = null;
+ this.customSound = null;
+ }
+
+ public ChatRule(String name, boolean enabled, boolean isPartialMatch, boolean isRegex, boolean isIgnoreCase, String filter, EnumSet<Location> validLocations, boolean hideMessage, boolean showActionBar, boolean showAnnouncement, @Nullable String replaceMessage, @Nullable SoundEvent customSound) {
+ this.name = name;
+ this.enabled = enabled;
+ this.isPartialMatch = isPartialMatch;
+ this.isRegex = isRegex;
+ this.isIgnoreCase = isIgnoreCase;
+ this.filter = filter;
+ this.validLocations = validLocations;
+ this.hideMessage = hideMessage;
+ this.showActionBar = showActionBar;
+ this.showAnnouncement = showAnnouncement;
+ this.replaceMessage = replaceMessage;
+ this.customSound = customSound;
+ }
+
+ private ChatRule(String name, boolean enabled, boolean isPartialMatch, boolean isRegex, boolean isIgnoreCase, String filter, EnumSet<Location> validLocations, boolean hideMessage, boolean showActionBar, boolean showAnnouncement, Optional<String> replaceMessage, Optional<SoundEvent> customSound) {
+ this(name, enabled, isPartialMatch, isRegex, isIgnoreCase, filter, validLocations, hideMessage, showActionBar, showAnnouncement, replaceMessage.orElse(null), customSound.orElse(null));
+ }
+
+ protected String getName() {
+ return name;
+ }
+
+ protected void setName(String name) {
+ this.name = name;
+ }
+
+ protected boolean getEnabled() {
+ return enabled;
+ }
+
+ protected void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ protected boolean getPartialMatch() {
+ return isPartialMatch;
+ }
+
+ protected void setPartialMatch(boolean partialMatch) {
+ isPartialMatch = partialMatch;
+ }
+
+ protected boolean getRegex() {
+ return isRegex;
+ }
+
+ protected void setRegex(boolean regex) {
+ isRegex = regex;
+ }
+
+ protected boolean getIgnoreCase() {
+ return isIgnoreCase;
+ }
+
+ protected void setIgnoreCase(boolean ignoreCase) {
+ isIgnoreCase = ignoreCase;
+ }
+
+ protected String getFilter() {
+ return filter;
+ }
+
+ protected void setFilter(String filter) {
+ this.filter = filter;
+ }
+
+ protected boolean getHideMessage() {
+ return hideMessage;
+ }
+
+ protected void setHideMessage(boolean hideMessage) {
+ this.hideMessage = hideMessage;
+ }
+
+ protected boolean getShowActionBar() {
+ return showActionBar;
+ }
+
+ protected void setShowActionBar(boolean showActionBar) {
+ this.showActionBar = showActionBar;
+ }
+
+ protected boolean getShowAnnouncement() {
+ return showAnnouncement;
+ }
+
+ protected void setShowAnnouncement(boolean showAnnouncement) {
+ this.showAnnouncement = showAnnouncement;
+ }
+
+ protected String getReplaceMessage() {
+ return replaceMessage;
+ }
+
+ private Optional<String> getReplaceMessageOpt() {
+ return Optional.ofNullable(replaceMessage);
+ }
+
+ protected void setReplaceMessage(String replaceMessage) {
+ this.replaceMessage = replaceMessage;
+ }
+
+ protected SoundEvent getCustomSound() {
+ return customSound;
+ }
+
+ private Optional<SoundEvent> getCustomSoundOpt() {
+ return Optional.ofNullable(customSound);
+ }
+
+ protected void setCustomSound(SoundEvent customSound) {
+ this.customSound = customSound;
+ }
+
+ protected EnumSet<Location> getValidLocations() {
+ return validLocations;
+ }
+
+ protected void setValidLocations(EnumSet<Location> validLocations) {
+ this.validLocations = validLocations;
+ }
+
+ /**
+ * checks every input option and if the games state and the inputted str matches them returns true.
+ *
+ * @param inputString the chat message to check if fits
+ * @return if the inputs are all true and the outputs should be performed
+ */
+ protected boolean isMatch(String inputString) {
+ //enabled
+ if (!enabled) return false;
+
+ //ignore case
+ String testString;
+ String testFilter;
+
+ if (isIgnoreCase) {
+ testString = inputString.toLowerCase();
+ testFilter = filter.toLowerCase();
+ } else {
+ testString = inputString;
+ testFilter = filter;
+ }
+
+ //filter
+ if (testFilter.isBlank()) return false;
+ if (isRegex) {
+ if (isPartialMatch) {
+ if (!Pattern.compile(testFilter).matcher(testString).find()) return false;
+ } else {
+ if (!testString.matches(testFilter)) return false;
+ }
+ } else {
+ if (isPartialMatch) {
+ if (!testString.contains(testFilter)) return false;
+ } else {
+ if (!testFilter.equals(testString)) return false;
+ }
+ }
+
+ // As a special case, if there are no valid locations all locations are valid.
+ // This exists because it doesn't make sense to remove all valid locations, you should disable the chat rule if you want to do that.
+ // This way, we can also default to an empty set for validLocations.
+ if (validLocations.isEmpty()) return true;
+ // UNKNOWN isn't a valid location, so we act the same as the list being empty.
+ if (validLocations.size() == 1 && validLocations.contains(Location.UNKNOWN)) return true;
+ return validLocations.contains(Utils.getLocation());
+ }
+
+ // This maps invalid entries to `Location.UNKNOWN`, which is better than failing outright.
+ private static EnumSet<Location> encodeString(String string) {
+ // Necessary for empty strings, which would've been decoded as UNKNOWN otherwise.
+ if (string.isEmpty()) return EnumSet.noneOf(Location.class);
+
+ // If a location's name contains a ! prefix, it's negated, meaning every location except that one is valid.
+ if (string.contains("!")) return EnumSet.complementOf(
+ Arrays.stream(string.split(", ?"))
+ .filter(s1 -> s1.startsWith("!")) // Filter out the non-negated locations because the negation of any element in the list already implies those non-negated locations being valid.
+ .map(s -> s.substring(1)) // Skip the `!`
+ .map(Location::fromFriendlyName)
+ .collect(CollectionUtils.enumSetCollector(Location.class))
+ );
+ return Arrays.stream(string.split(", ?"))
+ .map(Location::fromFriendlyName)
+ .collect(CollectionUtils.enumSetCollector(Location.class));
+ }
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleConfigScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleConfigScreen.java
index 11d5b72b..54236d07 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleConfigScreen.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleConfigScreen.java
@@ -1,18 +1,23 @@
package de.hysky.skyblocker.skyblock.chat;
+import de.hysky.skyblocker.utils.WidgetUtils;
+import it.unimi.dsi.fastutil.ints.IntIntImmutablePair;
+import it.unimi.dsi.fastutil.ints.IntIntMutablePair;
import it.unimi.dsi.fastutil.ints.IntIntPair;
-import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.tooltip.Tooltip;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.TextFieldWidget;
+import net.minecraft.client.gui.widget.TextWidget;
+import net.minecraft.client.gui.widget.Widget;
import net.minecraft.sound.SoundEvent;
import net.minecraft.sound.SoundEvents;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
-
import java.awt.*;
import java.util.List;
import java.util.Map;
@@ -20,327 +25,529 @@ import java.util.Map;
import static java.util.Map.entry;
public class ChatRuleConfigScreen extends Screen {
- private static final int SPACER_X = 5;
- private static final int SPACER_Y = 25;
-
- private final Map<MutableText, SoundEvent> soundsLookup = Map.ofEntries(
- entry(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.pling"), SoundEvents.BLOCK_NOTE_BLOCK_PLING.value()),
- entry(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.cave"), SoundEvents.AMBIENT_CAVE.value()),
- entry(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.zombie"), SoundEvents.ENTITY_ZOMBIE_AMBIENT),
- entry(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.crit"), SoundEvents.ENTITY_PLAYER_ATTACK_CRIT),
- entry(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.arrowHit"), SoundEvents.ENTITY_ARROW_HIT_PLAYER),
- entry(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.amethyst"), SoundEvents.BLOCK_AMETHYST_BLOCK_HIT),
- entry(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.anvil"), SoundEvents.BLOCK_ANVIL_LAND)
- );
-
- private int buttonWidth = 75;
-
- private final int chatRuleIndex;
- private final ChatRule chatRule;
- private TextFieldWidget nameInput;
- private TextFieldWidget filterInput;
- private ButtonWidget partialMatchToggle;
- private ButtonWidget regexToggle;
- private ButtonWidget ignoreCaseToggle;
- private TextFieldWidget locationsInput;
- private ButtonWidget hideMessageToggle;
- private ButtonWidget actionBarToggle;
- private ButtonWidget announcementToggle;
- private ButtonWidget soundsToggle;
- private TextFieldWidget replaceMessageInput;
-
- //textLocations
- private IntIntPair nameLabelTextPos;
- private IntIntPair inputsLabelTextPos;
- private IntIntPair filterLabelTextPos;
- private IntIntPair partialMatchTextPos;
- private IntIntPair regexTextPos;
- private IntIntPair ignoreCaseTextPos;
- private IntIntPair locationLabelTextPos;
- private IntIntPair outputsLabelTextPos;
- private IntIntPair hideMessageTextPos;
- private IntIntPair actionBarTextPos;
- private IntIntPair announcementTextPos;
- private IntIntPair customSoundLabelTextPos;
- private IntIntPair replaceMessageLabelTextPos;
-
- private int currentSoundIndex;
-
- private final Screen parent;
-
- public ChatRuleConfigScreen(Screen parent, int chatRuleIndex) {
- super(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen"));
- this.chatRuleIndex = chatRuleIndex;
- this.chatRule = ChatRulesHandler.chatRuleList.get(chatRuleIndex);
- this.parent = parent;
- this.currentSoundIndex = getCurrentSoundIndex();
- }
-
- private int getCurrentSoundIndex() {
- if (chatRule.getCustomSound() == null) return -1; //if no sound just return -1
-
- List<SoundEvent> soundOptions = soundsLookup.values().stream().toList();
- Identifier ruleSoundId = chatRule.getCustomSound().id();
-
- for (int i = 0; i < soundOptions.size(); i++) {
- if (soundOptions.get(i).id().compareTo(ruleSoundId) == 0) {
- return i;
- }
- }
- //not found
- return -1;
- }
-
- @Override
- protected void init() {
- super.init();
- if (client == null) return;
- //start centered on the X and 1/3 down on the Y
- calculateMaxButtonWidth();
- IntIntPair currentPos = IntIntPair.of((this.width - getMaxUsedWidth()) / 2,(int)((this.height - getMaxUsedHeight()) * 0.33));
- int lineXOffset;
-
- nameLabelTextPos = currentPos;
- lineXOffset = client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.name")) + SPACER_X;
- nameInput = new TextFieldWidget(client.textRenderer, currentPos.leftInt() + lineXOffset, currentPos.rightInt(), 100, 20, Text.of(""));
- nameInput.setText(chatRule.getName());
- nameInput.setTooltip(Tooltip.of(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.name.@Tooltip")));
- currentPos = IntIntPair.of(currentPos.leftInt(), currentPos.rightInt() + SPACER_Y);
-
- inputsLabelTextPos = currentPos;
- currentPos = IntIntPair.of(currentPos.leftInt() + 10, currentPos.rightInt() + SPACER_Y);
-
- filterLabelTextPos = currentPos;
- lineXOffset = client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.filter")) + SPACER_X;
- filterInput = new TextFieldWidget(client.textRenderer, currentPos.leftInt() + lineXOffset, currentPos.rightInt(), 200, 20, Text.of(""));
- filterInput.setMaxLength(96);
- filterInput.setText(chatRule.getFilter());
- filterInput.setTooltip(Tooltip.of(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.filter.@Tooltip")));
- currentPos = IntIntPair.of(currentPos.leftInt(),currentPos.rightInt() + SPACER_Y);
- lineXOffset = 0;
-
- partialMatchTextPos = IntIntPair.of(currentPos.leftInt() + lineXOffset, currentPos.rightInt());
- lineXOffset += client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.partialMatch")) + SPACER_X;
- partialMatchToggle = ButtonWidget.builder(enabledButtonText(chatRule.getPartialMatch()), a -> {
- chatRule.setPartialMatch(!chatRule.getPartialMatch());
- partialMatchToggle.setMessage(enabledButtonText(chatRule.getPartialMatch()));
- })
- .position(currentPos.leftInt() + lineXOffset, currentPos.rightInt())
- .size(buttonWidth,20)
- .tooltip(Tooltip.of(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.partialMatch.@Tooltip")))
- .build();
- lineXOffset += buttonWidth + SPACER_X;
- regexTextPos = IntIntPair.of(currentPos.leftInt() + lineXOffset,currentPos.rightInt());
- lineXOffset += client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.regex")) + SPACER_X;
- regexToggle = ButtonWidget.builder(enabledButtonText(chatRule.getRegex()), a -> {
- chatRule.setRegex(!chatRule.getRegex());
- regexToggle.setMessage(enabledButtonText(chatRule.getRegex()));
- })
- .position(currentPos.leftInt() + lineXOffset, currentPos.rightInt())
- .size(buttonWidth,20)
- .tooltip(Tooltip.of(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.regex.@Tooltip")))
- .build();
- lineXOffset += buttonWidth + SPACER_X;
- ignoreCaseTextPos = IntIntPair.of(currentPos.leftInt() + lineXOffset,currentPos.rightInt());
- lineXOffset += client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.ignoreCase")) + SPACER_X;
- ignoreCaseToggle = ButtonWidget.builder(enabledButtonText(chatRule.getIgnoreCase()), a -> {
- chatRule.setIgnoreCase(!chatRule.getIgnoreCase());
- ignoreCaseToggle.setMessage(enabledButtonText(chatRule.getIgnoreCase()));
- })
- .position(currentPos.leftInt() + lineXOffset, currentPos.rightInt())
- .size(buttonWidth,20)
- .tooltip(Tooltip.of(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.ignoreCase.@Tooltip")))
- .build();