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 ed67b4561b..352c59c853 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.ElementListWidget; import net.minecraft.text.Text; +import java.util.Collection; import java.util.List; -public class ItemTickList extends ElementListWidget { - private final List filters; - private final List allItems; +/** + * A checkbox list for filter configuring purposes. + */ +public class ItemTickList extends ElementListWidget { + private final Collection filters; + private final Collection allItems; + private final boolean whitelist; - public ItemTickList(MinecraftClient minecraftClient, int width, int height, int y, int entryHeight, List filters, List 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 filters, Collection 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 filters, Collection allItems, boolean whitelist) { + super(minecraftClient, width, height, y, entryHeight); + this.filters = filters; + this.allItems = allItems; + this.whitelist = whitelist; } public void clearAndInit() { @@ -26,11 +59,11 @@ public void clearAndInit() { init(); } - public ItemTickList init() { - for (String item : allItems) { + public ItemTickList 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); 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 84337d7bd9..fbd2668a23 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 @@ protected void init() { 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 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 7fd6844da8..ceff495e28 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRule.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRule.java @@ -1,16 +1,17 @@ package de.hysky.skyblocker.skyblock.chat; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import de.hysky.skyblocker.utils.Location; import de.hysky.skyblocker.utils.Utils; import net.minecraft.sound.SoundEvent; +import org.jetbrains.annotations.Nullable; +import java.util.EnumSet; import java.util.List; -import java.util.Objects; import java.util.Optional; 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 */ @@ -22,7 +23,7 @@ public class ChatRule { 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), + Location.SET_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), @@ -33,20 +34,21 @@ public class ChatRule { private String name; - //inputs - private Boolean enabled; - private Boolean isPartialMatch; - private Boolean isRegex; - private Boolean isIgnoreCase; + // Inputs + private boolean enabled; + private boolean isPartialMatch; + private boolean isRegex; + private boolean isIgnoreCase; private String filter; - private String validLocations; + private EnumSet validLocations; - //output - private Boolean hideMessage; - private Boolean showActionBar; - private Boolean showAnnouncement; + // Outputs + private boolean hideMessage; + private boolean showActionBar; + private boolean showAnnouncement; private String replaceMessage; private SoundEvent customSound; + /** * Creates a chat rule with default options. */ @@ -58,7 +60,7 @@ protected ChatRule() { this.isRegex = false; this.isIgnoreCase = true; this.filter = ""; - this.validLocations = ""; + this.validLocations = EnumSet.noneOf(Location.class); this.hideMessage = true; this.showActionBar = false; @@ -67,7 +69,7 @@ protected ChatRule() { 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) { + public ChatRule(String name, boolean enabled, boolean isPartialMatch, boolean isRegex, boolean isIgnoreCase, String filter, EnumSet validLocations, boolean hideMessage, boolean showActionBar, boolean showAnnouncement, @Nullable String replaceMessage, @Nullable SoundEvent customSound) { this.name = name; this.enabled = enabled; this.isPartialMatch = isPartialMatch; @@ -82,7 +84,7 @@ public ChatRule(String name, Boolean enabled, Boolean isPartialMatch, Boolean is 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 replaceMessage, Optional customSound) { + private ChatRule(String name, boolean enabled, boolean isPartialMatch, boolean isRegex, boolean isIgnoreCase, String filter, EnumSet validLocations, boolean hideMessage, boolean showActionBar, boolean showAnnouncement, Optional replaceMessage, Optional customSound) { this(name, enabled, isPartialMatch, isRegex, isIgnoreCase, filter, validLocations, hideMessage, showActionBar, showAnnouncement, replaceMessage.orElse(null), customSound.orElse(null)); } @@ -94,35 +96,35 @@ protected void setName(String name) { this.name = name; } - protected Boolean getEnabled() { + protected boolean getEnabled() { return enabled; } - protected void setEnabled(Boolean enabled) { + protected void setEnabled(boolean enabled) { this.enabled = enabled; } - protected Boolean getPartialMatch() { + protected boolean getPartialMatch() { return isPartialMatch; } - protected void setPartialMatch(Boolean partialMatch) { + protected void setPartialMatch(boolean partialMatch) { isPartialMatch = partialMatch; } - protected Boolean getRegex() { + protected boolean getRegex() { return isRegex; } - protected void setRegex(Boolean regex) { + protected void setRegex(boolean regex) { isRegex = regex; } - protected Boolean getIgnoreCase() { + protected boolean getIgnoreCase() { return isIgnoreCase; } - protected void setIgnoreCase(Boolean ignoreCase) { + protected void setIgnoreCase(boolean ignoreCase) { isIgnoreCase = ignoreCase; } @@ -134,27 +136,27 @@ protected void setFilter(String filter) { this.filter = filter; } - protected Boolean getHideMessage() { + protected boolean getHideMessage() { return hideMessage; } - protected void setHideMessage(Boolean hideMessage) { + protected void setHideMessage(boolean hideMessage) { this.hideMessage = hideMessage; } - protected Boolean getShowActionBar() { + protected boolean getShowActionBar() { return showActionBar; } - protected void setShowActionBar(Boolean showActionBar) { + protected void setShowActionBar(boolean showActionBar) { this.showActionBar = showActionBar; } - protected Boolean getShowAnnouncement() { + protected boolean getShowAnnouncement() { return showAnnouncement; } - protected void setShowAnnouncement(Boolean showAnnouncement) { + protected void setShowAnnouncement(boolean showAnnouncement) { this.showAnnouncement = showAnnouncement; } @@ -182,11 +184,11 @@ protected void setCustomSound(SoundEvent customSound) { this.customSound = customSound; } - protected String getValidLocations() { + protected EnumSet getValidLocations() { return validLocations; } - protected void setValidLocations(String validLocations) { + protected void setValidLocations(EnumSet validLocations) { this.validLocations = validLocations; } @@ -195,7 +197,7 @@ protected void setValidLocations(String validLocations) { * @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) { + protected boolean isMatch(String inputString) { //enabled if (!enabled) return false; @@ -227,36 +229,10 @@ protected Boolean isMatch(String inputString) { } } - //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; + // 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. + return validLocations.isEmpty() || validLocations.contains(Utils.getLocation()); } } 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 11d5b72b22..8000cf5d42 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleConfigScreen.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleConfigScreen.java @@ -1,6 +1,7 @@ package de.hysky.skyblocker.skyblock.chat; import it.unimi.dsi.fastutil.ints.IntIntPair; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.tooltip.Tooltip; @@ -12,7 +13,6 @@ import net.minecraft.text.Text; import net.minecraft.util.Identifier; - import java.awt.*; import java.util.List; import java.util.Map; @@ -20,327 +20,331 @@ 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 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 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(); - currentPos = IntIntPair.of(currentPos.leftInt(), currentPos.rightInt() + SPACER_Y); - - locationLabelTextPos = currentPos; - lineXOffset = client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.locations")) + SPACER_X; - locationsInput = new TextFieldWidget(client.textRenderer, currentPos.leftInt() + lineXOffset, currentPos.rightInt(), 200, 20, Text.of("")); - locationsInput.setMaxLength(96); - locationsInput.setText(chatRule.getValidLocations()); - MutableText locationToolTip = Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.locations.@Tooltip"); - locationToolTip.append("\n"); - ChatRulesHandler.locationsList.forEach(location -> locationToolTip.append(" " + location + ",\n")); - locationsInput.setTooltip(Tooltip.of(locationToolTip)); - currentPos = IntIntPair.of(currentPos.leftInt(), currentPos.rightInt() + SPACER_Y); - - outputsLabelTextPos = IntIntPair.of(currentPos.leftInt() - 10,currentPos.rightInt()); - currentPos = IntIntPair.of(currentPos.leftInt(), currentPos.rightInt() + SPACER_Y); - - hideMessageTextPos = currentPos; - lineXOffset = client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.hideMessage")) + SPACER_X; - hideMessageToggle = ButtonWidget.builder(enabledButtonText(chatRule.getHideMessage()), a -> { - chatRule.setHideMessage(!chatRule.getHideMessage()); - hideMessageToggle.setMessage(enabledButtonText(chatRule.getHideMessage())); - }) - .position(currentPos.leftInt() + lineXOffset, currentPos.rightInt()) - .size(buttonWidth,20) - .tooltip(Tooltip.of(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.hideMessage.@Tooltip"))) - .build(); - lineXOffset += buttonWidth + SPACER_X; - actionBarTextPos = IntIntPair.of(currentPos.leftInt() + lineXOffset,currentPos.rightInt()); - lineXOffset += client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.actionBar")) + SPACER_X; - actionBarToggle = ButtonWidget.builder(enabledButtonText(chatRule.getShowActionBar()), a -> { - chatRule.setShowActionBar(!chatRule.getShowActionBar()); - actionBarToggle.setMessage(enabledButtonText(chatRule.getShowActionBar())); - }) - .position(currentPos.leftInt() + lineXOffset, currentPos.rightInt()) - .size(buttonWidth,20) - .tooltip(Tooltip.of(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.actionBar.@Tooltip"))) - .build(); - lineXOffset = 0; - currentPos = IntIntPair.of(currentPos.leftInt(), currentPos.rightInt() + SPACER_Y); - - announcementTextPos = IntIntPair.of(currentPos.leftInt() + lineXOffset, currentPos.rightInt()); - lineXOffset += client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.announcement")) + SPACER_X; - announcementToggle = ButtonWidget.builder(enabledButtonText(chatRule.getShowAnnouncement()), a -> { - chatRule.setShowAnnouncement(!chatRule.getShowAnnouncement()); - announcementToggle.setMessage(enabledButtonText(chatRule.getShowAnnouncement())); - }) - .position(currentPos.leftInt() + lineXOffset, currentPos.rightInt()) - .size(buttonWidth,20) - .tooltip(Tooltip.of(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.announcement.@Tooltip"))) - .build(); - lineXOffset += buttonWidth + SPACER_X; - customSoundLabelTextPos = IntIntPair.of(currentPos.leftInt() + lineXOffset,currentPos.rightInt()); - lineXOffset += client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.sounds")) + SPACER_X; - soundsToggle = ButtonWidget.builder(getSoundName(), a -> { - currentSoundIndex += 1; - if (currentSoundIndex == soundsLookup.size()) { - currentSoundIndex = -1; - } - MutableText newText = getSoundName(); - soundsToggle.setMessage(newText); - SoundEvent sound = soundsLookup.get(newText); - chatRule.setCustomSound(sound); - if (client.player != null && sound != null) { - client.player.playSound(sound, 100f, 0.1f); - }}) - .position(currentPos.leftInt() + lineXOffset, currentPos.rightInt()) - .size(buttonWidth,20) - .tooltip(Tooltip.of(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.@Tooltip"))) - .build(); - currentPos = IntIntPair.of(currentPos.leftInt(), currentPos.rightInt() + SPACER_Y); - - replaceMessageLabelTextPos = currentPos; - lineXOffset = client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.replace")) + SPACER_X; - replaceMessageInput = new TextFieldWidget(client.textRenderer, currentPos.leftInt() + lineXOffset, currentPos.rightInt(), 200, 20, Text.of("")); - replaceMessageInput.setMaxLength(96); - replaceMessageInput.setTooltip(Tooltip.of(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.replace.@Tooltip"))); - replaceMessageInput.setText(chatRule.getReplaceMessage()); - - ButtonWidget finishButton = ButtonWidget.builder(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.finish"), a -> close()) - .position(this.width - buttonWidth - SPACER_Y, this.height - SPACER_Y) - .size(buttonWidth, 20) - .build(); - - addDrawableChild(nameInput); - addDrawableChild(filterInput); - addDrawableChild(partialMatchToggle); - addDrawableChild(regexToggle); - addDrawableChild(ignoreCaseToggle); - addDrawableChild(locationsInput); - addDrawableChild(hideMessageToggle); - addDrawableChild(actionBarToggle); - addDrawableChild(announcementToggle); - addDrawableChild(soundsToggle); - addDrawableChild(replaceMessageInput); - addDrawableChild(finishButton); - } - - /** - * if the maxUsedWidth is above the available width decrease the button width to fix this - */ - private void calculateMaxButtonWidth() { - if (client == null || client.currentScreen == null) return; - buttonWidth = 75; - int available = client.currentScreen.width - getMaxUsedWidth() - SPACER_X * 2; - if (available >= 0) return; //keep the largest size if room - buttonWidth += available / 3; //remove the needed width from the width of the total 3 buttons - buttonWidth = Math.max(10,buttonWidth); //do not let the width go below 10 - } - - /** - * Works out the width of the maximum line - * @return the max used width - */ - private int getMaxUsedWidth() { - if (client == null) return 0; - //text - int total = client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.partialMatch")); - total += client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.regex")); - total += client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.ignoreCase")); - //space - total += SPACER_X * 6; - //button width - total += buttonWidth * 3; - return total; - } - - /** - * Works out the height used - * @return height used by the gui - */ - private int getMaxUsedHeight() { - //there are 8 rows so just times the spacer by 8 - return SPACER_Y * 8; - } - - private Text enabledButtonText(boolean enabled) { - if (enabled) { - return Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.true").withColor(Color.GREEN.getRGB()); - } else { - return Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.false").withColor(Color.RED.getRGB()); - } - } - - @Override - public void render(DrawContext context, int mouseX, int mouseY, float delta) { - super.render(context, mouseX, mouseY, delta); - context.drawCenteredTextWithShadow(this.textRenderer, this.title, this.width / 2, 16, 0xFFFFFFFF); - - //draw labels ands text - int yOffset = (SPACER_Y - this.textRenderer.fontHeight) / 2; - context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.inputs"), inputsLabelTextPos.leftInt(), inputsLabelTextPos.rightInt() + yOffset, 0xFFFFFFFF); - context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.name"), nameLabelTextPos.leftInt(), nameLabelTextPos.rightInt() + yOffset, 0xFFFFFFFF); - context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.filter"), filterLabelTextPos.leftInt(), filterLabelTextPos.rightInt() + yOffset, 0xFFFFFFFF); - context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.partialMatch"), partialMatchTextPos.leftInt(), partialMatchTextPos.rightInt() + yOffset, 0xFFFFFFFF); - context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.regex"), regexTextPos.leftInt(), regexTextPos.rightInt() + yOffset, 0xFFFFFFFF); - context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.ignoreCase"), ignoreCaseTextPos.leftInt(), ignoreCaseTextPos.rightInt() + yOffset, 0xFFFFFFFF); - context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.locations"), locationLabelTextPos.leftInt(), locationLabelTextPos.rightInt() + yOffset, 0xFFFFFFFF); - context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.outputs"), outputsLabelTextPos.leftInt(), outputsLabelTextPos.rightInt() + yOffset, 0xFFFFFFFF); - context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.hideMessage"), hideMessageTextPos.leftInt(), hideMessageTextPos.rightInt() + yOffset, 0xFFFFFFFF); - context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.actionBar"), actionBarTextPos.leftInt(), actionBarTextPos.rightInt() + yOffset, 0xFFFFFFFF); - context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.announcement"), announcementTextPos.leftInt(), announcementTextPos.rightInt() + yOffset, 0xFFFFFFFF); - context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.sounds"), customSoundLabelTextPos.leftInt(), customSoundLabelTextPos.rightInt() + yOffset, 0xFFFFFFFF); - context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.replace"), replaceMessageLabelTextPos.leftInt(), replaceMessageLabelTextPos.rightInt() + yOffset, 0xFFFFFFFF); - } - - /** - * Saves and returns to parent screen - */ - @Override - public void close() { - if (client != null) { - save(); - client.setScreen(parent); - } - } - - private void save() { - chatRule.setName(nameInput.getText()); - chatRule.setFilter(filterInput.getText()); - chatRule.setReplaceMessage(replaceMessageInput.getText()); - chatRule.setValidLocations(locationsInput.getText()); - - ChatRulesHandler.chatRuleList.set(chatRuleIndex, chatRule); - } - - private MutableText getSoundName() { - if (currentSoundIndex == -1) { - return Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.none"); - } - - return soundsLookup.keySet().stream().toList().get(currentSoundIndex); - } + private static final int SPACER_X = 5; + private static final int SPACER_Y = 25; + + private final Map 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 final TextFieldWidget nameInput; + private final TextFieldWidget filterInput; + private ButtonWidget partialMatchToggle; + private ButtonWidget regexToggle; + private ButtonWidget ignoreCaseToggle; + private final ButtonWidget locationsConfigButton; + private ButtonWidget hideMessageToggle; + private ButtonWidget actionBarToggle; + private ButtonWidget announcementToggle; + private ButtonWidget soundsToggle; + private final ButtonWidget finishButton; + private final TextFieldWidget replaceMessageInput; + + //textLocations + private final IntIntPair nameLabelTextPos; + private final IntIntPair inputsLabelTextPos; + private final IntIntPair filterLabelTextPos; + private final IntIntPair partialMatchTextPos; + private final IntIntPair regexTextPos; + private final IntIntPair ignoreCaseTextPos; + private final IntIntPair locationLabelTextPos; + private final IntIntPair outputsLabelTextPos; + private final IntIntPair hideMessageTextPos; + private final IntIntPair actionBarTextPos; + private final IntIntPair announcementTextPos; + private final IntIntPair customSoundLabelTextPos; + private final 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(); + + //Early initialization of values from the static instance because we want to initialize this stuff in the constructor + this.client = MinecraftClient.getInstance(); + this.width = client.getWindow().getScaledWidth(); + this.height = client.getWindow().getScaledHeight(); + + //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 = client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.name")) + SPACER_X; + nameLabelTextPos = currentPos; + + 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(); + currentPos = IntIntPair.of(currentPos.leftInt(), currentPos.rightInt() + SPACER_Y); + + locationLabelTextPos = currentPos; + lineXOffset = client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.locations")) + SPACER_X; + locationsConfigButton = ButtonWidget.builder(Text.translatable("text.skyblocker.open"), + widget -> client.setScreen(new ChatRuleLocationConfigScreen(this, chatRule))) + .tooltip(Tooltip.of(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.locations.@Tooltip"))) + .dimensions(currentPos.leftInt() + lineXOffset, currentPos.rightInt(), buttonWidth, 20) + .build(); + + currentPos = IntIntPair.of(currentPos.leftInt(), currentPos.rightInt() + SPACER_Y); + + outputsLabelTextPos = IntIntPair.of(currentPos.leftInt() - 10, currentPos.rightInt()); + currentPos = IntIntPair.of(currentPos.leftInt(), currentPos.rightInt() + SPACER_Y); + + hideMessageTextPos = currentPos; + lineXOffset = client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.hideMessage")) + SPACER_X; + hideMessageToggle = ButtonWidget.builder(enabledButtonText(chatRule.getHideMessage()), a -> { + chatRule.setHideMessage(!chatRule.getHideMessage()); + hideMessageToggle.setMessage(enabledButtonText(chatRule.getHideMessage())); + }) + .position(currentPos.leftInt() + lineXOffset, currentPos.rightInt()) + .size(buttonWidth, 20) + .tooltip(Tooltip.of(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.hideMessage.@Tooltip"))) + .build(); + lineXOffset += buttonWidth + SPACER_X; + actionBarTextPos = IntIntPair.of(currentPos.leftInt() + lineXOffset, currentPos.rightInt()); + lineXOffset += client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.actionBar")) + SPACER_X; + actionBarToggle = ButtonWidget.builder(enabledButtonText(chatRule.getShowActionBar()), a -> { + chatRule.setShowActionBar(!chatRule.getShowActionBar()); + actionBarToggle.setMessage(enabledButtonText(chatRule.getShowActionBar())); + }) + .position(currentPos.leftInt() + lineXOffset, currentPos.rightInt()) + .size(buttonWidth, 20) + .tooltip(Tooltip.of(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.actionBar.@Tooltip"))) + .build(); + lineXOffset = 0; + currentPos = IntIntPair.of(currentPos.leftInt(), currentPos.rightInt() + SPACER_Y); + + announcementTextPos = IntIntPair.of(currentPos.leftInt() + lineXOffset, currentPos.rightInt()); + lineXOffset += client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.announcement")) + SPACER_X; + announcementToggle = ButtonWidget.builder(enabledButtonText(chatRule.getShowAnnouncement()), a -> { + chatRule.setShowAnnouncement(!chatRule.getShowAnnouncement()); + announcementToggle.setMessage(enabledButtonText(chatRule.getShowAnnouncement())); + }) + .position(currentPos.leftInt() + lineXOffset, currentPos.rightInt()) + .size(buttonWidth, 20) + .tooltip(Tooltip.of(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.announcement.@Tooltip"))) + .build(); + lineXOffset += buttonWidth + SPACER_X; + customSoundLabelTextPos = IntIntPair.of(currentPos.leftInt() + lineXOffset, currentPos.rightInt()); + lineXOffset += client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.sounds")) + SPACER_X; + soundsToggle = ButtonWidget.builder(getSoundName(), a -> { + currentSoundIndex += 1; + if (currentSoundIndex == soundsLookup.size()) { + currentSoundIndex = -1; + } + MutableText newText = getSoundName(); + soundsToggle.setMessage(newText); + SoundEvent sound = soundsLookup.get(newText); + chatRule.setCustomSound(sound); + if (client.player != null && sound != null) { + client.player.playSound(sound, 100f, 0.1f); + } + }) + .position(currentPos.leftInt() + lineXOffset, currentPos.rightInt()) + .size(buttonWidth, 20) + .tooltip(Tooltip.of(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.@Tooltip"))) + .build(); + currentPos = IntIntPair.of(currentPos.leftInt(), currentPos.rightInt() + SPACER_Y); + + replaceMessageLabelTextPos = currentPos; + lineXOffset = client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.replace")) + SPACER_X; + replaceMessageInput = new TextFieldWidget(client.textRenderer, currentPos.leftInt() + lineXOffset, currentPos.rightInt(), 200, 20, Text.of("")); + replaceMessageInput.setMaxLength(96); + replaceMessageInput.setTooltip(Tooltip.of(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.replace.@Tooltip"))); + replaceMessageInput.setText(chatRule.getReplaceMessage()); + + finishButton = ButtonWidget.builder(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.finish"), a -> close()) + .position(this.width - buttonWidth - SPACER_Y, this.height - SPACER_Y) + .size(buttonWidth, 20) + .build(); + } + + private int getCurrentSoundIndex() { + if (chatRule.getCustomSound() == null) return -1; //if no sound just return -1 + + List 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() { + addDrawableChild(nameInput); + addDrawableChild(filterInput); + addDrawableChild(partialMatchToggle); + addDrawableChild(regexToggle); + addDrawableChild(ignoreCaseToggle); + addDrawableChild(locationsConfigButton); + addDrawableChild(hideMessageToggle); + addDrawableChild(actionBarToggle); + addDrawableChild(announcementToggle); + addDrawableChild(soundsToggle); + addDrawableChild(replaceMessageInput); + addDrawableChild(finishButton); + } + + /** + * if the maxUsedWidth is above the available width decrease the button width to fix this + */ + private void calculateMaxButtonWidth() { + if (client == null || client.currentScreen == null) return; + buttonWidth = 75; + int available = client.currentScreen.width - getMaxUsedWidth() - SPACER_X * 2; + if (available >= 0) return; //keep the largest size if room + buttonWidth += available / 3; //remove the needed width from the width of the total 3 buttons + buttonWidth = Math.max(10, buttonWidth); //do not let the width go below 10 + } + + /** + * Works out the width of the maximum line + * + * @return the max used width + */ + private int getMaxUsedWidth() { + if (client == null) return 0; + //text + int total = client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.partialMatch")); + total += client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.regex")); + total += client.textRenderer.getWidth(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.ignoreCase")); + //space + total += SPACER_X * 6; + //button width + total += buttonWidth * 3; + return total; + } + + /** + * Works out the height used + * + * @return height used by the gui + */ + private int getMaxUsedHeight() { + //there are 8 rows so just times the spacer by 8 + return SPACER_Y * 8; + } + + private Text enabledButtonText(boolean enabled) { + if (enabled) { + return Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.true").withColor(Color.GREEN.getRGB()); + } else { + return Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.false").withColor(Color.RED.getRGB()); + } + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta) { + super.render(context, mouseX, mouseY, delta); + context.drawCenteredTextWithShadow(this.textRenderer, this.title, this.width / 2, 16, 0xFFFFFFFF); + + //draw labels ands text + int yOffset = (SPACER_Y - this.textRenderer.fontHeight) / 2; + context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.inputs"), inputsLabelTextPos.leftInt(), inputsLabelTextPos.rightInt() + yOffset, 0xFFFFFFFF); + context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.name"), nameLabelTextPos.leftInt(), nameLabelTextPos.rightInt() + yOffset, 0xFFFFFFFF); + context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.filter"), filterLabelTextPos.leftInt(), filterLabelTextPos.rightInt() + yOffset, 0xFFFFFFFF); + context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.partialMatch"), partialMatchTextPos.leftInt(), partialMatchTextPos.rightInt() + yOffset, 0xFFFFFFFF); + context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.regex"), regexTextPos.leftInt(), regexTextPos.rightInt() + yOffset, 0xFFFFFFFF); + context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.ignoreCase"), ignoreCaseTextPos.leftInt(), ignoreCaseTextPos.rightInt() + yOffset, 0xFFFFFFFF); + context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.locations"), locationLabelTextPos.leftInt(), locationLabelTextPos.rightInt() + yOffset, 0xFFFFFFFF); + context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.outputs"), outputsLabelTextPos.leftInt(), outputsLabelTextPos.rightInt() + yOffset, 0xFFFFFFFF); + context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.hideMessage"), hideMessageTextPos.leftInt(), hideMessageTextPos.rightInt() + yOffset, 0xFFFFFFFF); + context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.actionBar"), actionBarTextPos.leftInt(), actionBarTextPos.rightInt() + yOffset, 0xFFFFFFFF); + context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.announcement"), announcementTextPos.leftInt(), announcementTextPos.rightInt() + yOffset, 0xFFFFFFFF); + context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.sounds"), customSoundLabelTextPos.leftInt(), customSoundLabelTextPos.rightInt() + yOffset, 0xFFFFFFFF); + context.drawTextWithShadow(this.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.replace"), replaceMessageLabelTextPos.leftInt(), replaceMessageLabelTextPos.rightInt() + yOffset, 0xFFFFFFFF); + } + + /** + * Saves and returns to parent screen + */ + @Override + public void close() { + if (client != null) { + save(); + client.setScreen(parent); + } + } + + private void save() { + chatRule.setName(nameInput.getText()); + chatRule.setFilter(filterInput.getText()); + chatRule.setReplaceMessage(replaceMessageInput.getText()); + + ChatRulesHandler.chatRuleList.set(chatRuleIndex, chatRule); + } + + private MutableText getSoundName() { + if (currentSoundIndex == -1) { + return Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.sounds.none"); + } + + return soundsLookup.keySet().stream().toList().get(currentSoundIndex); + } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleLocationConfigScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleLocationConfigScreen.java new file mode 100644 index 0000000000..3df254f501 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleLocationConfigScreen.java @@ -0,0 +1,72 @@ +package de.hysky.skyblocker.skyblock.chat; + +import de.hysky.skyblocker.config.screens.powdertracker.ItemTickList; +import de.hysky.skyblocker.utils.Location; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.GridWidget; +import net.minecraft.client.gui.widget.SimplePositioningWidget; +import net.minecraft.screen.ScreenTexts; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.Nullable; + +import java.util.EnumSet; + +public class ChatRuleLocationConfigScreen extends Screen { + @Nullable + private final Screen parent; + private final ChatRule chatRule; + private final EnumSet enabledLocations; + + public ChatRuleLocationConfigScreen(@Nullable Screen parent, ChatRule chatRule) { + super(Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.locationsConfigScreen")); + this.parent = parent; + this.chatRule = chatRule; + this.enabledLocations = EnumSet.copyOf(chatRule.getValidLocations()); // Copy the list so we can undo changes when necessary + } + + @Override + protected void init() { + assert client != null; + addDrawable((context, mouseX, mouseY, delta) -> { + context.drawCenteredTextWithShadow(client.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.locationsConfigScreen").formatted(Formatting.BOLD), width / 2, (32 - client.textRenderer.fontHeight) / 2, 0xFFFFFF); + context.drawCenteredTextWithShadow(client.textRenderer, Text.translatable("skyblocker.config.chat.chatRules.screen.ruleScreen.locationsConfigScreen.note"), width / 2, (38 - client.textRenderer.fontHeight), 0xFFFFFF); + }); + + ItemTickList itemTickList = addDrawableChild(new ItemTickList<>(client, width, height - 107, 43, 24, enabledLocations, EnumSet.allOf(Location.class), true).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); + GridWidget.Adder adder = gridWidget.createAdder(2); + + adder.add(ButtonWidget.builder(Text.translatable("text.skyblocker.reset"), button -> { + enabledLocations.clear(); + itemTickList.clearAndInit(); + }).build()); + adder.add(ButtonWidget.builder(Text.translatable("text.skyblocker.undo"), button -> { + enabledLocations.clear(); + enabledLocations.addAll(chatRule.getValidLocations()); + itemTickList.clearAndInit(); + }).build()); + adder.add(ButtonWidget.builder(ScreenTexts.DONE, button -> { + saveFilters(); + close(); + }) + .width((ButtonWidget.DEFAULT_WIDTH * 2) + 10) + .build(), 2); + gridWidget.refreshPositions(); + SimplePositioningWidget.setPos(gridWidget, 0, this.height - 64, this.width, 64); + gridWidget.forEachChild(this::addDrawableChild); + } + + public void saveFilters() { + chatRule.setValidLocations(enabledLocations); + } + + @Override + public void close() { + assert client != null; + client.setScreen(parent); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigListWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigListWidget.java index 97d593a262..cf483bfedc 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigListWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigListWidget.java @@ -51,6 +51,7 @@ protected void addRuleAfterSelected() { children().add(newIndex + 1, new ChatRuleConfigEntry(newIndex)); } + @Override protected boolean removeEntry(AbstractChatRuleEntry entry) { hasChanged = true; return super.removeEntry(entry); @@ -65,7 +66,7 @@ protected boolean hasChanges() { return (hasChanged || children().stream().filter(ChatRuleConfigEntry.class::isInstance).map(ChatRuleConfigEntry.class::cast).anyMatch(ChatRuleConfigEntry::isChange)); } - protected static abstract class AbstractChatRuleEntry extends ElementListWidget.Entry { + protected abstract static class AbstractChatRuleEntry extends ElementListWidget.Entry { } private class ChatRuleLabelsEntry extends AbstractChatRuleEntry { @@ -115,9 +116,8 @@ public ChatRuleConfigEntry(int chatRuleIndex) { .position(width / 2 - 25, 5) .build(); - openConfigButton = ButtonWidget.builder(Text.translatable("skyblocker.config.chat.chatRules.screen.editRule"), a -> { - client.setScreen(new ChatRuleConfigScreen(screen, chatRuleIndex)); - }) + openConfigButton = ButtonWidget.builder(Text.translatable("skyblocker.config.chat.chatRules.screen.editRule"), + a -> client.setScreen(new ChatRuleConfigScreen(screen, chatRuleIndex))) .size(50, 20) .position(width / 2 + 45, 5) .tooltip(Tooltip.of(Text.translatable("skyblocker.config.chat.chatRules.screen.editRule.@Tooltip"))) @@ -192,7 +192,7 @@ public void render(DrawContext context, int index, int y, int x, int entryWidth, } public boolean isChange() { - return (!chatRule.getEnabled().equals(ChatRulesHandler.chatRuleList.get(chatRuleIndex).getEnabled())); + return chatRule.getEnabled() != ChatRulesHandler.chatRuleList.get(chatRuleIndex).getEnabled(); } } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesHandler.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesHandler.java index 659a71e6fe..2e1ebbf4ce 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesHandler.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesHandler.java @@ -7,6 +7,7 @@ import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.annotations.Init; +import de.hysky.skyblocker.utils.Location; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.render.title.Title; import de.hysky.skyblocker.utils.render.title.TitleContainer; @@ -30,7 +31,7 @@ public class ChatRulesHandler { private static final MinecraftClient CLIENT = MinecraftClient.getInstance(); - private static final Logger LOGGER = LoggerFactory.getLogger(ChatRule.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ChatRulesHandler.class); private static final Path CHAT_RULE_FILE = SkyblockerMod.CONFIG_DIR.resolve("chat_rules.json"); private static final Codec>> MAP_CODEC = Codec.unboundedMap(Codec.STRING, ChatRule.LIST_CODEC); /** @@ -83,9 +84,9 @@ private static void loadChatRules() { private static void registerDefaultChatRules() { //clean hub chat - ChatRule cleanHubRule = new ChatRule("Clean Hub Chat", false, true, true, true, "(selling)|(buying)|(lowb)|(visit)|(/p)|(/ah)|(my ah)", "hub", true, false, false, "", null); + ChatRule cleanHubRule = new ChatRule("Clean Hub Chat", false, true, true, true, "(selling)|(buying)|(lowb)|(visit)|(/p)|(/ah)|(my ah)", EnumSet.of(Location.HUB), true, false, false, "", null); //mining Ability - ChatRule miningAbilityRule = new ChatRule("Mining Ability Alert", false, true, false, true, "is now available!", "Crystal Hollows, Dwarven Mines", false, false, true, "&1Ability", SoundEvents.ENTITY_ARROW_HIT_PLAYER); + ChatRule miningAbilityRule = new ChatRule("Mining Ability Alert", false, true, false, true, "is now available!", EnumSet.of(Location.DWARVEN_MINES, Location.CRYSTAL_HOLLOWS), false, false, true, "&1Ability", SoundEvents.ENTITY_ARROW_HIT_PLAYER); chatRuleList.add(cleanHubRule); chatRuleList.add(miningAbilityRule); @@ -113,38 +114,38 @@ private static boolean checkMessage(Text message, boolean overlay) { String plain = Formatting.strip(message.getString()); for (ChatRule rule : chatRuleList) { - if (rule.isMatch(plain)) { - //get a replacement message - Text newMessage; - if (!rule.getReplaceMessage().isBlank()) { - newMessage = formatText(rule.getReplaceMessage()); - } else { - newMessage = message; - } - - if (rule.getShowAnnouncement()) { - TitleContainer.addTitle(new Title(newMessage.copy()), SkyblockerConfigManager.get().chat.chatRuleConfig.announcementLength) ; - } + if (!rule.isMatch(plain)) continue; + + //get a replacement message + Text newMessage; + if (!rule.getReplaceMessage().isBlank()) { + newMessage = formatText(rule.getReplaceMessage()); + } else { + newMessage = message; + } - //show in action bar - if (rule.getShowActionBar() && CLIENT.player != null) { - CLIENT.player.sendMessage(newMessage, true); - } + if (rule.getShowAnnouncement()) { + TitleContainer.addTitle(new Title(newMessage.copy()), SkyblockerConfigManager.get().chat.chatRuleConfig.announcementLength) ; + } - //show replacement message in chat - //bypass MessageHandler#onGameMessage to avoid activating chat rules again - if (!rule.getHideMessage() && CLIENT.player != null) { - Utils.sendMessageToBypassEvents(newMessage); - } + //show in action bar + if (rule.getShowActionBar() && CLIENT.player != null) { + CLIENT.player.sendMessage(newMessage, true); + } - //play sound - if (rule.getCustomSound() != null && CLIENT.player != null) { - CLIENT.player.playSound(rule.getCustomSound(), 100f, 0.1f); - } + //show replacement message in chat + //bypass MessageHandler#onGameMessage to avoid activating chat rules again + if (!rule.getHideMessage() && CLIENT.player != null) { + Utils.sendMessageToBypassEvents(newMessage); + } - //do not send original message - return false; + //play sound + if (rule.getCustomSound() != null && CLIENT.player != null) { + CLIENT.player.playSound(rule.getCustomSound(), 100f, 0.1f); } + + //do not send original message + return false; } return true; } diff --git a/src/main/java/de/hysky/skyblocker/utils/CodecUtils.java b/src/main/java/de/hysky/skyblocker/utils/CodecUtils.java index 4a5acf41fa..5b17dfa472 100644 --- a/src/main/java/de/hysky/skyblocker/utils/CodecUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/CodecUtils.java @@ -2,19 +2,9 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; +import it.unimi.dsi.fastutil.objects.*; -import it.unimi.dsi.fastutil.objects.Object2BooleanMap; -import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2DoubleMap; -import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; - -import java.util.Optional; -import java.util.OptionalDouble; -import java.util.OptionalInt; +import java.util.*; import java.util.function.Function; public final class CodecUtils { @@ -32,18 +22,29 @@ public static MapCodec optionalDouble(MapCodec> } public static Codec> object2BooleanMapCodec(Codec keyCodec) { - return Codec.unboundedMap(keyCodec, Codec.BOOL).xmap(Object2BooleanOpenHashMap::new, Function.identity()); + return Codec.unboundedMap(keyCodec, Codec.BOOL).xmap(Object2BooleanOpenHashMap::new, Function.identity()); } public static Codec> object2IntMapCodec(Codec keyCodec) { - return Codec.unboundedMap(keyCodec, Codec.INT).xmap(Object2IntOpenHashMap::new, Function.identity()); + return Codec.unboundedMap(keyCodec, Codec.INT).xmap(Object2IntOpenHashMap::new, Function.identity()); } public static Codec> object2DoubleMapCodec(Codec keyCodec) { - return Codec.unboundedMap(keyCodec, Codec.DOUBLE).xmap(Object2DoubleOpenHashMap::new, Function.identity()); + return Codec.unboundedMap(keyCodec, Codec.DOUBLE).xmap(Object2DoubleOpenHashMap::new, Function.identity()); } public static Codec> object2ObjectMapCodec(Codec keyCodec, Codec valueCodec) { - return Codec.unboundedMap(keyCodec, valueCodec).xmap(Object2ObjectOpenHashMap::new, Function.identity()); + return Codec.unboundedMap(keyCodec, valueCodec).xmap(Object2ObjectOpenHashMap::new, Function.identity()); + } + + /** + * Creates a {@link EnumSet} codec for the given enum codec and class. + * + * @param enumCodec Codec of the enum + * @param The enum type + * @return EnumSet codec for the given enum + */ + public static > Codec> enumSetCodec(Codec enumCodec) { + return enumCodec.listOf().xmap(EnumSet::copyOf, List::copyOf); } } diff --git a/src/main/java/de/hysky/skyblocker/utils/Location.java b/src/main/java/de/hysky/skyblocker/utils/Location.java index 225df3086d..03847b00f1 100644 --- a/src/main/java/de/hysky/skyblocker/utils/Location.java +++ b/src/main/java/de/hysky/skyblocker/utils/Location.java @@ -2,92 +2,34 @@ import com.mojang.serialization.Codec; import net.minecraft.util.StringIdentifiable; +import org.apache.commons.text.WordUtils; import java.util.Arrays; +import java.util.EnumSet; /** * All Skyblock locations */ public enum Location implements StringIdentifiable { - /** - * mode: dynamic - */ PRIVATE_ISLAND("dynamic"), - /** - * mode: garden - */ GARDEN("garden"), - /** - * mode: hub - */ HUB("hub"), - /** - * mode: farming_1 - */ THE_FARMING_ISLAND("farming_1"), - /** - * mode: foraging_1 - */ THE_PARK("foraging_1"), - /** - * mode: combat_1 - */ - SPIDERS_DEN("combat_1"), - /** - * mode: combat_2 - */ + SPIDERS_DEN("combat_1", "Spider's Den"), BLAZING_FORTRESS("combat_2"), - /** - * mode: combat_3 - */ THE_END("combat_3"), - /** - * mode: crimson_isle - */ CRIMSON_ISLE("crimson_isle"), - /** - * mode: mining_1 - */ GOLD_MINE("mining_1"), - /** - * mode: mining_2 - */ DEEP_CAVERNS("mining_2"), - /** - * mode: mining_3 - */ DWARVEN_MINES("mining_3"), - /** - * mode: dungeon_hub - */ DUNGEON_HUB("dungeon_hub"), - /** - * mode: winter - */ - WINTER_ISLAND("winter"), - /** - * mode: rift - */ + WINTER_ISLAND("winter", "Jerry's Workshop"), THE_RIFT("rift"), - /** - * mode: dark_auction - */ DARK_AUCTION("dark_auction"), - /** - * mode: crystal_hollows - */ CRYSTAL_HOLLOWS("crystal_hollows"), - /** - * mode: dungeon - */ - DUNGEON("dungeon"), - /** - * mode: kuudra - */ - KUUDRAS_HOLLOW("kuudra"), - /** - * The freezing cold Glacite Mineshafts! *brr... so cold... :(* - */ + DUNGEON("dungeon", "Dungeons"), + KUUDRAS_HOLLOW("kuudra", "Kuudra's Hollow"), GLACITE_MINESHAFT("mineshaft"), /** * Goodbye 1.8 hello 1.21 (and foraging 50 for all)! @@ -99,17 +41,34 @@ public enum Location implements StringIdentifiable { UNKNOWN("unknown"); public static final Codec CODEC = StringIdentifiable.createCodec(Location::values); + public static final Codec> SET_CODEC = CodecUtils.enumSetCodec(CODEC); /** * location id from Hypixel API */ private final String id; + /** + * friendly name from Hypixel API + */ + private final String friendlyName; + /** * @param id location id from Hypixel API + * @param friendlyName friendly name from Hypixel API + */ + Location(String id, String friendlyName) { + this.id = id; + this.friendlyName = friendlyName; + } + + /** + * Alternative constructor to avoid replicating simple friendlyNames that can be obtained with manipulating the enum's name. + * @param id location id from Hypixel API */ Location(String id) { this.id = id; + this.friendlyName = WordUtils.capitalizeFully(name(), '_').replace('_', ' '); } /** @@ -131,4 +90,9 @@ public String asString() { public static Location from(String id) { return Arrays.stream(Location.values()).filter(loc -> id.equals(loc.id())).findFirst().orElse(UNKNOWN); } + + @Override + public String toString() { + return friendlyName; + } } diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index 3e6b5d30b6..7f613fd0f0 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -462,10 +462,12 @@ "skyblocker.config.chat.chatRules.screen.ruleScreen.hideMessage": "Hide Message:", "skyblocker.config.chat.chatRules.screen.ruleScreen.hideMessage.@Tooltip": "Remove the message from chat.", "skyblocker.config.chat.chatRules.screen.ruleScreen.ignoreCase": "Ignore Case:", - "skyblocker.config.chat.chatRules.screen.ruleScreen.ignoreCase.@Tooltip": "if the filter is case-sensitive.", + "skyblocker.config.chat.chatRules.screen.ruleScreen.ignoreCase.@Tooltip": "If the filter is case-sensitive.", "skyblocker.config.chat.chatRules.screen.ruleScreen.inputs": "Inputs:", + "skyblocker.config.chat.chatRules.screen.ruleScreen.locationsConfigScreen": "Chat Rule Location Config", + "skyblocker.config.chat.chatRules.screen.ruleScreen.locationsConfigScreen.note": "Note: When all locations are unchecked, the chat rule will work in every location.", "skyblocker.config.chat.chatRules.screen.ruleScreen.locations": "Valid Locations:", - "skyblocker.config.chat.chatRules.screen.ruleScreen.locations.@Tooltip": "List of locations where the filter will work. Separate each location with a \",\" and use a \"!\" if you want it to work anywhere but a location. Location Names:", + "skyblocker.config.chat.chatRules.screen.ruleScreen.locations.@Tooltip": "Whitelist of locations where the filter will work.", "skyblocker.config.chat.chatRules.screen.ruleScreen.name": "Name:", "skyblocker.config.chat.chatRules.screen.ruleScreen.name.@Tooltip": "The name of the rule.", "skyblocker.config.chat.chatRules.screen.ruleScreen.outputs": "Outputs:",