aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/de/hysky/skyblocker/SkyblockerMod.java7
-rw-r--r--src/main/java/de/hysky/skyblocker/config/ConfigUtils.java21
-rw-r--r--src/main/java/de/hysky/skyblocker/config/ImageRepoLoader.java144
-rw-r--r--src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java12
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java8
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/MessageFilterCategory.java34
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRule.java264
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleAnnouncementScreen.java48
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleConfigScreen.java345
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigListWidget.java198
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigScreen.java75
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesHandler.java182
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/CroesusHelper.java8
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/CroesusProfit.java267
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/Fetchur.java7
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java17
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/waypoint/MythologicalRitual.java6
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/FileUtils.java36
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/Http.java24
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java20
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java2
22 files changed, 1683 insertions, 46 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
index 36ef5a4f..2987f493 100644
--- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
+++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
@@ -2,10 +2,14 @@ package de.hysky.skyblocker;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
+
+import de.hysky.skyblocker.config.ImageRepoLoader;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.debug.Debug;
import de.hysky.skyblocker.skyblock.*;
+import de.hysky.skyblocker.skyblock.chat.ChatRuleAnnouncementScreen;
import de.hysky.skyblocker.skyblock.crimson.kuudra.Kuudra;
+import de.hysky.skyblocker.skyblock.chat.ChatRulesHandler;
import de.hysky.skyblocker.skyblock.dungeon.*;
import de.hysky.skyblocker.skyblock.dungeon.partyfinder.PartyFinderScreen;
import de.hysky.skyblocker.skyblock.dungeon.puzzle.*;
@@ -92,6 +96,7 @@ public class SkyblockerMod implements ClientModInitializer {
SkyblockerConfigManager.init();
Tips.init();
NEURepoManager.init();
+ ImageRepoLoader.init();
ItemRepository.init();
PlayerHeadHashCache.init();
HotbarSlotLock.init();
@@ -108,6 +113,8 @@ public class SkyblockerMod implements ClientModInitializer {
CrystalsLocationsManager.init();
ChatMessageListener.init();
Shortcuts.init();
+ ChatRulesHandler.init();
+ ChatRuleAnnouncementScreen.init();
DiscordRPCManager.init();
LividColor.init();
FishingHelper.init();
diff --git a/src/main/java/de/hysky/skyblocker/config/ConfigUtils.java b/src/main/java/de/hysky/skyblocker/config/ConfigUtils.java
index 8b0f27a7..781f7f15 100644
--- a/src/main/java/de/hysky/skyblocker/config/ConfigUtils.java
+++ b/src/main/java/de/hysky/skyblocker/config/ConfigUtils.java
@@ -1,16 +1,26 @@
package de.hysky.skyblocker.config;
import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.OptionDescription;
import dev.isxander.yacl3.api.controller.*;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
+import net.minecraft.util.Identifier;
+
+import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.Nullable;
+
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.utils.FileUtils;
+import java.nio.file.Path;
import java.util.function.Function;
public class ConfigUtils {
public static final ValueFormatter<Formatting> FORMATTING_FORMATTER = formatting -> Text.literal(StringUtils.capitalize(formatting.getName().replaceAll("_", " ")));
public static final ValueFormatter<Float> FLOAT_TWO_FORMATTER = value -> Text.literal(String.format("%,.2f", value).replaceAll("[\u00a0\u202F]", " "));
+ private static final Path IMAGE_DIRECTORY = ImageRepoLoader.REPO_DIRECTORY.resolve("Skyblocker-Assets-images");
public static BooleanControllerBuilder createBooleanController(Option<Boolean> opt) {
return BooleanControllerBuilder.create(opt).yesNoFormatter().coloured(true);
@@ -34,4 +44,15 @@ public class ConfigUtils {
public static <E extends Enum<E>> Function<Option<E>, ControllerBuilder<E>> getEnumDropdownControllerFactory(ValueFormatter<E> formatter) {
return opt -> EnumDropdownControllerBuilder.create(opt).formatValue(formatter);
}
+
+ /**
+ * Creates an {@link OptionDescription} with an image and text.
+ */
+ @SafeVarargs
+ public static OptionDescription withImage(Path imagePath, @Nullable Text... texts) {
+ return OptionDescription.createBuilder()
+ .text(ArrayUtils.isNotEmpty(texts) ? texts : new Text[] {})
+ .image(IMAGE_DIRECTORY.resolve(imagePath), new Identifier(SkyblockerMod.NAMESPACE, "config_image_" + FileUtils.normalizePath(imagePath)))
+ .build();
+ }
}
diff --git a/src/main/java/de/hysky/skyblocker/config/ImageRepoLoader.java b/src/main/java/de/hysky/skyblocker/config/ImageRepoLoader.java
new file mode 100644
index 00000000..0591cd96
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/config/ImageRepoLoader.java
@@ -0,0 +1,144 @@
+package de.hysky.skyblocker.config;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.concurrent.CompletableFuture;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import org.slf4j.Logger;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.mojang.logging.LogUtils;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.JsonOps;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.utils.FileUtils;
+import de.hysky.skyblocker.utils.Http;
+
+public class ImageRepoLoader {
+ private static final Logger LOGGER = LogUtils.getLogger();
+ static final Path REPO_DIRECTORY = SkyblockerMod.CONFIG_DIR.resolve("image-repo");
+ private static final String BRANCH_INFO = "https://api.github.com/repos/SkyblockerMod/Skyblocker-Assets/branches/images";
+ private static final String REPO_DOWNLOAD = "https://github.com/SkyblockerMod/Skyblocker-Assets/archive/refs/heads/images.zip";
+ private static final String PLACEHOLDER_HASH = "None!";
+
+ public static void init() {
+ update(0);
+ }
+
+ /**
+ * Attempts to update/load the image repository, if any errors are encountered it will try 3 times.
+ */
+ private static void update(int retries) {
+ CompletableFuture.runAsync(() -> {
+ if (retries < 3) {
+ try {
+ long start = System.currentTimeMillis();
+ //Retrieve the saved commit hash
+ String savedCommitHash = checkSavedCommitData();
+
+ //Fetch the latest commit data
+ JsonObject response = JsonParser.parseString(Http.sendGetRequest(BRANCH_INFO)).getAsJsonObject();
+ String latestCommitHash = response.getAsJsonObject("commit").get("sha").getAsString();
+
+ //Download the repository if there was a new commit
+ if (!savedCommitHash.equals(latestCommitHash)) {
+ InputStream in = Http.downloadContent(REPO_DOWNLOAD);
+
+ //Delete all directories to clear potentially now unused/old files
+ //TODO change this to only delete periodically?
+ deleteDirectories();
+
+ try (ZipInputStream zis = new ZipInputStream(in)) {
+ ZipEntry entry;
+
+ while ((entry = zis.getNextEntry()) != null) {
+ Path outputFile = REPO_DIRECTORY.resolve(entry.getName());
+
+ if (entry.isDirectory()) {
+ Files.createDirectories(outputFile);
+ } else {
+ Files.createDirectories(outputFile.getParent());
+ Files.copy(zis, outputFile, StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+ }
+
+ writeCommitData(latestCommitHash);
+
+ long end = System.currentTimeMillis();
+ LOGGER.info("[Skyblocker] Successfully updated the Image Respository in {} ms! {} → {}", end - start, savedCommitHash, latestCommitHash);
+ } else {
+ LOGGER.info("[Skyblocker] The Image Respository is up to date!");
+ }
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker] Error while downloading image repo on attempt {}!", retries, e);
+ update(retries + 1);
+ }
+ }
+ });
+ }
+
+ /**
+ * @return The stored hash or the {@link #PLACEHOLDER_HASH}.
+ */
+ private static String checkSavedCommitData() throws IOException {
+ Path file = REPO_DIRECTORY.resolve("image_repo.json");
+
+ if (Files.exists(file)) {
+ try (BufferedReader reader = Files.newBufferedReader(file)) {
+ CommitData commitData = CommitData.CODEC.parse(JsonOps.INSTANCE, JsonParser.parseReader(reader)).result().orElseThrow();
+
+ return commitData.commit();
+ }
+ }
+
+ return PLACEHOLDER_HASH;
+ }
+
+ /**
+ * Writes the {@code newHash} into a file to be used to check for repo updates.
+ *
+ * @implNote Checking whether the directory exists or not isn't needed as this is called after all files are written successfully.
+ */
+ private static void writeCommitData(String newHash) throws IOException {
+ Path file = REPO_DIRECTORY.resolve("image_repo.json");
+ CommitData commitData = new CommitData(newHash, System.currentTimeMillis());
+
+ try (BufferedWriter writer = Files.newBufferedWriter(file)) {
+ SkyblockerMod.GSON.toJson(CommitData.CODEC.encodeStart(JsonOps.INSTANCE, commitData).result().orElseThrow(), writer);
+ }
+ }
+
+ /**
+ * Deletes all directories (not files) inside of the {@link #REPO_DIRECTORY}
+ * @throws IOException
+ */
+ private static void deleteDirectories() throws IOException {
+ Files.list(REPO_DIRECTORY)
+ .filter(Files::isDirectory)
+ .forEach(dir -> {
+ try {
+ FileUtils.recursiveDelete(dir);
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker] Encountered an exception while deleting a directory! Path: {}", dir.toAbsolutePath(), e);
+ }
+ });
+ }
+
+ record CommitData(String commit, long lastUpdated) {
+ static final Codec<CommitData> CODEC = RecordCodecBuilder.create(instance -> instance.group(
+ Codec.STRING.fieldOf("commit").forGetter(CommitData::commit),
+ Codec.LONG.fieldOf("lastUpdated").forGetter(CommitData::lastUpdated))
+ .apply(instance, CommitData::new));
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
index 78605c00..4626003d 100644
--- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
@@ -863,6 +863,9 @@ public class SkyblockerConfig {
public boolean includeEssence = true;
@SerialEntry
+ public boolean croesusProfit = true;
+
+ @SerialEntry
public int neutralThreshold = 1000;
@SerialEntry
@@ -1192,6 +1195,15 @@ public class SkyblockerConfig {
@SerialEntry
public ChatFilterResult hideDicer = ChatFilterResult.PASS;
+
+ @SerialEntry
+ public ChatRuleConfig chatRuleConfig = new ChatRuleConfig();
+ }
+ public static class ChatRuleConfig {
+ @SerialEntry
+ public int announcementLength = 60;
+ @SerialEntry
+ public int announcementScale = 3;
}
public enum Info {
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
index 3ebd5d76..9d6e1beb 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
@@ -279,6 +279,14 @@ public class DungeonsCategory {
newValue -> config.locations.dungeons.dungeonChestProfit.includeEssence = newValue)
.controller(ConfigUtils::createBooleanController)
.build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit.croesusProfit"))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit.croesusProfit.@Tooltip")))
+ .binding(defaults.locations.dungeons.dungeonChestProfit.croesusProfit,
+ () -> config.locations.dungeons.dungeonChestProfit.croesusProfit,
+ newValue -> config.locations.dungeons.dungeonChestProfit.croesusProfit = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
.option(Option.<Integer>createBuilder()
.name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit.neutralThreshold"))
.description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit.neutralThreshold.@Tooltip")))
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/MessageFilterCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/MessageFilterCategory.java
index acdc8169..0f95bcaa 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/MessageFilterCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/MessageFilterCategory.java
@@ -2,10 +2,12 @@ package de.hysky.skyblocker.config.categories;
import de.hysky.skyblocker.config.ConfigUtils;
import de.hysky.skyblocker.config.SkyblockerConfig;
+import de.hysky.skyblocker.skyblock.chat.ChatRulesConfigScreen;
+import de.hysky.skyblocker.skyblock.dwarven.CrystalsHudConfigScreen;
import de.hysky.skyblocker.utils.chat.ChatFilterResult;
-import dev.isxander.yacl3.api.ConfigCategory;
-import dev.isxander.yacl3.api.Option;
-import dev.isxander.yacl3.api.OptionDescription;
+import dev.isxander.yacl3.api.*;
+import dev.isxander.yacl3.api.controller.IntegerSliderControllerBuilder;
+import net.minecraft.client.MinecraftClient;
import net.minecraft.text.Text;
public class MessageFilterCategory {
@@ -126,6 +128,32 @@ public class MessageFilterCategory {
newValue -> config.messages.hideDicer = newValue)
.controller(ConfigUtils::createEnumCyclingListController)
.build())
+ //chat rules options
+ .group(OptionGroup.createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules"))
+ .collapsed(false)
+ .option(ButtonOption.createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen"))
+ .text(Text.translatable("text.skyblocker.open"))
+ .action((screen, opt) -> MinecraftClient.getInstance().setScreen(new ChatRulesConfigScreen(screen)))
+ .build())
+ .option(Option.<Integer>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.announcementLength"))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.announcementLength.@Tooltip")))
+ .binding(defaults.messages.chatRuleConfig.announcementLength,
+ () -> config.messages.chatRuleConfig.announcementLength,
+ newValue -> config.messages.chatRuleConfig.announcementLength = newValue)
+ .controller(opt -> IntegerSliderControllerBuilder.create(opt).range(5, 200).step(1))
+ .build())
+ .option(Option.<Integer>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.announcementScale"))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.announcementScale.@Tooltip")))
+ .binding(defaults.messages.chatRuleConfig.announcementScale,
+ () -> config.messages.chatRuleConfig.announcementScale,
+ newValue -> config.messages.chatRuleConfig.announcementScale = newValue)
+ .controller(opt -> IntegerSliderControllerBuilder.create(opt).range(1, 8).step(1))
+ .build())
+ .build())
.build();
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRule.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRule.java
new file mode 100644
index 00000000..34cc6352
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRule.java
@@ -0,0 +1,264 @@
+package de.hysky.skyblocker.skyblock.chat;
+
+import de.hysky.skyblocker.utils.Utils;
+import net.minecraft.sound.SoundEvent;
+
+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
+ */
+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 rawLocation = Utils.getLocationRaw();
+ Boolean isLocationValid = null;
+
+ for (String validLocation : validLocations.replace(" ", "").toLowerCase().split(",")) {//the locations are raw locations split by "," and start with ! if not locations
+ String rawValidLocation = ChatRulesHandler.locations.get(validLocation.replace("!",""));
+ if (rawValidLocation == null) continue;
+ if (validLocation.startsWith("!")) {//not location
+ if (Objects.equals(rawValidLocation, rawLocation.toLowerCase())) {
+ isLocationValid = false;
+ break;
+ }
+ } else {
+ if (Objects.equals(rawValidLocation, rawLocation.toLowerCase())) { //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;
+ }
+}
+
+
+
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleAnnouncementScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleAnnouncementScreen.java
new file mode 100644
index 00000000..bafada27
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleAnnouncementScreen.java
@@ -0,0 +1,48 @@
+package de.hysky.skyblocker.skyblock.chat;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.text.Text;
+
+public class ChatRuleAnnouncementScreen {
+ private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
+ private static float timer;
+ private static Text text = null;
+
+ public static void init() {
+ HudRenderCallback.EVENT.register((context, tickDelta) -> {
+ if (timer <= 0 || text == null) {
+ return;
+ }
+ render(context, tickDelta);
+ });
+ }
+
+ /**
+ * renders {@link ChatRuleAnnouncementScreen#text} to the middle of the top of the screen.
+ * @param context render context
+ * @param tickDelta difference from last render to remove from timer
+ */
+ private static void render(DrawContext context, float tickDelta) {
+ int scale = SkyblockerConfigManager.get().messages.chatRuleConfig.announcementScale;
+ //decrement timer
+ timer -= tickDelta;
+ //scale text up and center
+ MatrixStack matrices = context.getMatrices();
+ matrices.push();
+ matrices.translate(context.getScaledWindowWidth() / 2f, context.getScaledWindowHeight() * 0.3, 0f);
+ matrices.scale(scale, scale, 0f);
+ //render text
+ context.drawCenteredTextWithShadow(CLIENT.textRenderer, text, 0, 0, 0xFFFFFFFF);
+
+ matrices.pop();
+ }
+
+ protected static void setText(Text newText) {
+ text = newText;
+ timer = SkyblockerConfigManager.get().messages.chatRuleConfig.announcementLength;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleConfigScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleConfigScreen.java
new file mode 100644
index 00000000..c99aeed8
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleConfigScreen.java
@@ -0,0 +1,345 @@
+package de.hysky.skyblocker.skyblock.chat;
+
+import it.unimi.dsi.fastutil.ints.IntIntPair;
+import net.minecraft.client.gui.DrawContext;
+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.sound.SoundEvent;
+import net.minecraft.sound.SoundEvents;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Identifier;
+
+
+import java.awt.*;
+import java.util.List;
+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("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.sounds.pling"), SoundEvents.BLOCK_NOTE_BLOCK_PLING.value()),
+ entry(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.sounds.cave"), SoundEvents.AMBIENT_CAVE.value()),
+ entry(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.sounds.zombie"), SoundEvents.ENTITY_ZOMBIE_AMBIENT),
+ entry(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.sounds.crit"), SoundEvents.ENTITY_PLAYER_ATTACK_CRIT),
+ entry(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.sounds.arrowHit"), SoundEvents.ENTITY_ARROW_HIT_PLAYER),
+ entry(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.sounds.amethyst"), SoundEvents.BLOCK_AMETHYST_BLOCK_HIT),
+ entry(Text.translatable("text.autoconfig.skyblocker.option.messages.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 replaceMessageLabelTex