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 replaceMessageLabelTextPos;
+
+ private int currentSoundIndex;
+
+ private final Screen parent;
+
+ public ChatRuleConfigScreen(Screen parent, int chatRuleIndex) {
+ super(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen"));
+ this.chatRuleIndex = chatRuleIndex;
+ this.chatRule = ChatRulesHandler.chatRuleList.get(chatRuleIndex);
+ this.parent = parent;
+ this.currentSoundIndex = getCurrentSoundIndex();
+ }
+
+ private int getCurrentSoundIndex() {
+ if (chatRule.getCustomSound() == null) return -1; //if no sound just return -1
+
+ List<SoundEvent> soundOptions = soundsLookup.values().stream().toList();
+ Identifier ruleSoundId = chatRule.getCustomSound().getId();
+
+ for (int i = 0; i < soundOptions.size(); i++) {
+ if (soundOptions.get(i).getId().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("text.autoconfig.skyblocker.option.messages.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("text.autoconfig.skyblocker.option.messages.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("text.autoconfig.skyblocker.option.messages.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("text.autoconfig.skyblocker.option.messages.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("text.autoconfig.skyblocker.option.messages.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("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.partialMatch.@Tooltip")))
+ .build();
+ lineXOffset += buttonWidth + SPACER_X;
+ regexTextPos = IntIntPair.of(currentPos.leftInt() + lineXOffset,currentPos.rightInt());
+ lineXOffset += client.textRenderer.getWidth(Text.translatable("text.autoconfig.skyblocker.option.messages.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("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.regex.@Tooltip")))
+ .build();
+ lineXOffset += buttonWidth + SPACER_X;
+ ignoreCaseTextPos = IntIntPair.of(currentPos.leftInt() + lineXOffset,currentPos.rightInt());
+ lineXOffset += client.textRenderer.getWidth(Text.translatable("text.autoconfig.skyblocker.option.messages.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("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.ignoreCase.@Tooltip")))
+ .build();
+ currentPos = IntIntPair.of(currentPos.leftInt(), currentPos.rightInt() + SPACER_Y);
+
+ locationLabelTextPos = currentPos;
+ lineXOffset = client.textRenderer.getWidth(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.locations")) + SPACER_X;
+ locationsInput = new TextFieldWidget(client.textRenderer, currentPos.leftInt() + lineXOffset, currentPos.rightInt(), 200, 20, Text.of(""));
+ locationsInput.setText(chatRule.getValidLocations());
+ MutableText locationToolTip = Text.translatable("text.autoconfig.skyblocker.option.messages.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("text.autoconfig.skyblocker.option.messages.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("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.hideMessage.@Tooltip")))
+ .build();
+ lineXOffset += buttonWidth + SPACER_X;
+ actionBarTextPos = IntIntPair.of(currentPos.leftInt() + lineXOffset,currentPos.rightInt());
+ lineXOffset += client.textRenderer.getWidth(Text.translatable("text.autoconfig.skyblocker.option.messages.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("text.autoconfig.skyblocker.option.messages.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("text.autoconfig.skyblocker.option.messages.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("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.announcement.@Tooltip")))
+ .build();
+ lineXOffset += buttonWidth + SPACER_X;
+ customSoundLabelTextPos = IntIntPair.of(currentPos.leftInt() + lineXOffset,currentPos.rightInt());
+ lineXOffset += client.textRenderer.getWidth(Text.translatable("text.autoconfig.skyblocker.option.messages.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("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.sounds.@Tooltip")))
+ .build();
+ currentPos = IntIntPair.of(currentPos.leftInt(), currentPos.rightInt() + SPACER_Y);
+
+ replaceMessageLabelTextPos = currentPos;
+ lineXOffset = client.textRenderer.getWidth(Text.translatable("text.autoconfig.skyblocker.option.messages.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("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.replace.@Tooltip")));
+ replaceMessageInput.setText(chatRule.getReplaceMessage());
+
+ ButtonWidget finishButton = ButtonWidget.builder(Text.translatable("text.autoconfig.skyblocker.option.messages.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("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.partialMatch"));
+ total += client.textRenderer.getWidth(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.regex"));
+ total += client.textRenderer.getWidth(Text.translatable("text.autoconfig.skyblocker.option.messages.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("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.true").withColor(Color.GREEN.getRGB());
+ } else {
+ return Text.translatable("text.autoconfig.skyblocker.option.messages.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("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.inputs"), inputsLabelTextPos.leftInt(), inputsLabelTextPos.rightInt() + yOffset, 0xFFFFFFFF);
+ context.drawTextWithShadow(this.textRenderer, Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.name"), nameLabelTextPos.leftInt(), nameLabelTextPos.rightInt() + yOffset, 0xFFFFFFFF);
+ context.drawTextWithShadow(this.textRenderer, Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.filter"), filterLabelTextPos.leftInt(), filterLabelTextPos.rightInt() + yOffset, 0xFFFFFFFF);
+ context.drawTextWithShadow(this.textRenderer, Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.partialMatch"), partialMatchTextPos.leftInt(), partialMatchTextPos.rightInt() + yOffset, 0xFFFFFFFF);
+ context.drawTextWithShadow(this.textRenderer, Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.regex"), regexTextPos.leftInt(), regexTextPos.rightInt() + yOffset, 0xFFFFFFFF);
+ context.drawTextWithShadow(this.textRenderer, Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.ignoreCase"), ignoreCaseTextPos.leftInt(), ignoreCaseTextPos.rightInt() + yOffset, 0xFFFFFFFF);
+ context.drawTextWithShadow(this.textRenderer, Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.locations"), locationLabelTextPos.leftInt(), locationLabelTextPos.rightInt() + yOffset, 0xFFFFFFFF);
+ context.drawTextWithShadow(this.textRenderer, Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.outputs"), outputsLabelTextPos.leftInt(), outputsLabelTextPos.rightInt() + yOffset, 0xFFFFFFFF);
+ context.drawTextWithShadow(this.textRenderer, Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.hideMessage"), hideMessageTextPos.leftInt(), hideMessageTextPos.rightInt() + yOffset, 0xFFFFFFFF);
+ context.drawTextWithShadow(this.textRenderer, Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.actionBar"), actionBarTextPos.leftInt(), actionBarTextPos.rightInt() + yOffset, 0xFFFFFFFF);
+ context.drawTextWithShadow(this.textRenderer, Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.announcement"), announcementTextPos.leftInt(), announcementTextPos.rightInt() + yOffset, 0xFFFFFFFF);
+ context.drawTextWithShadow(this.textRenderer, Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.sounds"), customSoundLabelTextPos.leftInt(), customSoundLabelTextPos.rightInt() + yOffset, 0xFFFFFFFF);
+ context.drawTextWithShadow(this.textRenderer, Text.translatable("text.autoconfig.skyblocker.option.messages.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("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.sounds.none");
+ }
+
+ return soundsLookup.keySet().stream().toList().get(currentSoundIndex);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigListWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigListWidget.java
new file mode 100644
index 00000000..a1b9317a
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigListWidget.java
@@ -0,0 +1,198 @@
+package de.hysky.skyblocker.skyblock.chat;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.Element;
+import net.minecraft.client.gui.Selectable;
+import net.minecraft.client.gui.screen.ConfirmScreen;
+import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder;
+import net.minecraft.client.gui.screen.narration.NarrationPart;
+import net.minecraft.client.gui.tooltip.Tooltip;
+import net.minecraft.client.gui.widget.ButtonWidget;
+import net.minecraft.client.gui.widget.ElementListWidget;
+import net.minecraft.screen.ScreenTexts;
+import net.minecraft.text.Text;
+
+import java.awt.*;
+import java.util.List;
+
+public class ChatRulesConfigListWidget extends ElementListWidget<ChatRulesConfigListWidget.AbstractChatRuleEntry> {
+ private final ChatRulesConfigScreen screen;
+ private Boolean hasChanged;
+
+ public ChatRulesConfigListWidget(MinecraftClient client, ChatRulesConfigScreen screen, int width, int height, int y, int itemHeight) {
+ super(client, width, height, y, itemHeight);
+ this.screen = screen;
+ this.hasChanged = false;
+
+ //add labels
+ addEntry(new ChatRuleLabelsEntry());
+ //add entry fall all existing rules
+ for (int i = 0; i < ChatRulesHandler.chatRuleList.size(); i++){
+ addEntry(new ChatRuleConfigEntry(i));
+ }
+ }
+
+ @Override
+ public int getRowWidth() {
+ return super.getRowWidth() + 100;
+ }
+
+ @Override
+ protected int getScrollbarPositionX() {
+ return super.getScrollbarPositionX() + 50;
+ }
+
+ protected void addRuleAfterSelected() {
+ hasChanged = true;
+ int newIndex = children().indexOf(getSelectedOrNull()) + 1;
+
+ ChatRulesHandler.chatRuleList.add(newIndex, new ChatRule());
+ children().add(newIndex + 1, new ChatRuleConfigEntry(newIndex));
+ }
+
+ protected boolean removeEntry(AbstractChatRuleEntry entry) {
+ hasChanged = true;
+ return super.removeEntry(entry);
+ }
+
+ protected void saveRules() {
+ hasChanged = false;
+ ChatRulesHandler.saveChatRules();
+ }
+
+ 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<ChatRulesConfigListWidget.AbstractChatRuleEntry> {
+ }
+
+ private class ChatRuleLabelsEntry extends AbstractChatRuleEntry {
+
+ @Override
+ public List<? extends Selectable> selectableChildren() {
+ return List.of();
+ }
+
+ @Override
+ public List<? extends Element> children() {
+ return List.of();
+ }
+
+ @Override
+ public void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) {
+ context.drawCenteredTextWithShadow(client.textRenderer, Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleName"), width / 2 - 125, y + 5, 0xFFFFFFFF);
+ context.drawCenteredTextWithShadow(client.textRenderer, Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleEnabled"), width / 2, y + 5, 0xFFFFFFFF);
+ context.drawCenteredTextWithShadow(client.textRenderer, Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.modify"), width / 2 + 100, y + 5, 0xFFFFFFFF);
+ }
+ }
+
+ private class ChatRuleConfigEntry extends AbstractChatRuleEntry {
+ //data
+ private final int chatRuleIndex;
+ private final ChatRule chatRule;
+
+ private final List<? extends Element> children;
+
+ //widgets
+ private final ButtonWidget enabledButton;
+ private final ButtonWidget openConfigButton;
+ private final ButtonWidget deleteButton;
+
+ //text location
+ private final int nameX = width / 2 - 125;
+ //saved data
+ private double oldScrollAmount = 0;
+
+
+ public ChatRuleConfigEntry(int chatRuleIndex) {
+ this.chatRuleIndex = chatRuleIndex;
+ this.chatRule = ChatRulesHandler.chatRuleList.get(chatRuleIndex);
+
+ enabledButton = ButtonWidget.builder(enabledButtonText(), a -> toggleEnabled())
+ .size(50, 20)
+ .position(width / 2 - 25, 5)
+ .build();
+
+ openConfigButton = ButtonWidget.builder(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.editRule"), a -> {
+ client.setScreen(new ChatRuleConfigScreen(screen, chatRuleIndex));
+ })
+ .size(50, 20)
+ .position(width / 2 + 45, 5)
+ .tooltip(Tooltip.of(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.editRule.@Tooltip")))
+ .build();
+
+ deleteButton = ButtonWidget.builder(Text.translatable("selectServer.delete"), a -> {
+ oldScrollAmount = getScrollAmount();
+ client.setScreen(new ConfirmScreen(this::deleteEntry, Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.deleteQuestion"), Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.deleteWarning", chatRule.getName()), Text.translatable("selectServer.deleteButton"), ScreenTexts.CANCEL));
+ })
+ .size(50, 20)
+ .position(width / 2 + 105, 5)
+ .build();
+
+ children = List.of(enabledButton, openConfigButton, deleteButton);
+ }
+
+ private Text enabledButtonText() {
+ if (chatRule.getEnabled()) {
+ return Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.true").withColor(Color.GREEN.getRGB());
+ } else {
+ return Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.false").withColor(Color.RED.getRGB());
+ }
+ }
+ private void toggleEnabled() {
+ hasChanged = true;
+ chatRule.setEnabled(!chatRule.getEnabled());
+ enabledButton.setMessage(enabledButtonText());
+ }
+
+ private void deleteEntry(boolean confirmedAction) {
+ if (confirmedAction) {
+ //delete this
+ ChatRulesHandler.chatRuleList.remove(chatRuleIndex);
+ removeEntry(this);
+ }
+
+ client.setScreen(screen);
+ setScrollAmount(oldScrollAmount);
+ }
+
+ @Override
+ public List<? extends Selectable> selectableChildren() {
+ return List.of(new Selectable() {
+ @Override
+ public SelectionType getType() {
+ return SelectionType.HOVERED;
+ }
+
+ @Override
+ public void appendNarrations(NarrationMessageBuilder builder) {
+ builder.put(NarrationPart.TITLE,chatRule.getName());
+ }
+ });
+ }
+
+ @Override
+ public List<? extends Element> children() {
+ return children;
+ }
+
+ @Override
+ public void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { //todo get strings form en_us.json
+ //widgets
+ enabledButton.setY(y);
+ enabledButton.render(context, mouseX, mouseY, tickDelta);
+ openConfigButton.setY(y);
+ openConfigButton.render(context, mouseX, mouseY, tickDelta);
+ deleteButton.setY(y);
+ deleteButton.render(context, mouseX, mouseY, tickDelta);
+ //text
+ context.drawCenteredTextWithShadow(client.textRenderer, chatRule.getName(), nameX, y + 5, 0xFFFFFFFF);
+ }
+
+ public boolean isChange() {
+ return (!chatRule.getEnabled().equals(ChatRulesHandler.chatRuleList.get(chatRuleIndex).getEnabled()));
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigScreen.java
new file mode 100644
index 00000000..49ef735d
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigScreen.java
@@ -0,0 +1,75 @@
+package de.hysky.skyblocker.skyblock.chat;
+
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.ConfirmScreen;
+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;
+
+public class ChatRulesConfigScreen extends Screen {
+
+ private ChatRulesConfigListWidget chatRulesConfigListWidget;
+ private final Screen parent;
+
+ public ChatRulesConfigScreen(Screen parent) {
+ super(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen"));
+ this.parent = parent;
+ }
+
+ @Override
+ public void setTooltip(Text tooltip) {
+ super.setTooltip(tooltip);
+ }
+
+ @Override
+ protected void init() {
+ super.init();
+ chatRulesConfigListWidget = new ChatRulesConfigListWidget(client, this, width, height - 96, 32, 25);
+ addDrawableChild(chatRulesConfigListWidget);
+ GridWidget gridWidget = new GridWidget();
+ gridWidget.getMainPositioner().marginX(5).marginY(2);
+ GridWidget.Adder adder = gridWidget.createAdder(3);
+ adder.add(ButtonWidget.builder(ScreenTexts.CANCEL, button -> {
+ if (client != null) {
+ close();
+ }
+ }).build());
+ ButtonWidget buttonNew1 = ButtonWidget.builder(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.new"), buttonNew -> chatRulesConfigListWidget.addRuleAfterSelected()).build();
+ adder.add(buttonNew1);
+ ButtonWidget buttonDone = ButtonWidget.builder(ScreenTexts.DONE, button -> {
+ chatRulesConfigListWidget.saveRules();
+ if (client != null) {
+ close();
+ }
+ }).build();
+ adder.add(buttonDone);
+ gridWidget.refreshPositions();
+ SimplePositioningWidget.setPos(gridWidget, 0, this.height - 64, this.width, 64);
+ gridWidget.forEachChild(this::addDrawableChild);
+
+ }
+
+ @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);
+ }
+
+ @Override
+ public void close() {
+ if (client != null && chatRulesConfigListWidget.hasChanges()) {
+ client.setScreen(new ConfirmScreen(confirmedAction -> {
+ if (confirmedAction) {
+ this.client.setScreen(parent);
+ } else {
+ client.setScreen(this);
+ }
+ }, Text.translatable("text.skyblocker.quit_config"), Text.translatable("text.skyblocker.quit_config_sure"), Text.translatable("text.skyblocker.quit_discard"), ScreenTexts.CANCEL));
+ } else {
+ this.client.setScreen(parent);
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesHandler.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesHandler.java
new file mode 100644
index 00000000..13115bee
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesHandler.java
@@ -0,0 +1,182 @@
+package de.hysky.skyblocker.skyblock.chat;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.JsonOps;
+
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.utils.Http;
+import de.hysky.skyblocker.utils.Utils;
+import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.sound.SoundEvents;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Style;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+
+public class ChatRulesHandler {
+ private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
+ private static final Logger LOGGER = LoggerFactory.getLogger(ChatRule.class);
+ private static final Path CHAT_RULE_FILE = SkyblockerMod.CONFIG_DIR.resolve("chat_rules.json");
+ private static final Codec<Map<String, List<ChatRule>>> MAP_CODEC = Codec.unboundedMap(Codec.STRING, ChatRule.LIST_CODEC);
+ /**
+ * look up table for the locations input by the users to raw locations
+ */
+ protected static final HashMap<String, String> locations = new HashMap<>();
+ /**
+ * list of possible locations still formatted for the tool tip
+ */
+ protected static final List<String> locationsList = new ArrayList<>();
+
+ protected static final List<ChatRule> chatRuleList = new ArrayList<>();
+
+ public static void init() {
+ CompletableFuture.runAsync(ChatRulesHandler::loadChatRules);
+ CompletableFuture.runAsync(ChatRulesHandler::loadLocations);
+ ClientReceiveMessageEvents.ALLOW_GAME.register(ChatRulesHandler::checkMessage);
+ }
+
+ private static void loadChatRules() {
+ try (BufferedReader reader = Files.newBufferedReader(CHAT_RULE_FILE)) {
+ Map<String, List<ChatRule>> chatRules = MAP_CODEC.parse(JsonOps.INSTANCE, JsonParser.parseReader(reader)).result().orElseThrow();
+ LOGGER.info("[Sky: " + chatRules.toString());
+
+ chatRuleList.addAll(chatRules.get("rules"));
+
+ LOGGER.info("[Skyblocker] Loaded chat rules");
+ } catch (NoSuchFileException e) {
+ registerDefaultChatRules();
+ LOGGER.warn("[Skyblocker] chat rule file not found, using default rules. This is normal when using for the first time.");
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker] Failed to load shortcuts file", e);
+ }
+ }
+
+ 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);
+ //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);
+
+ chatRuleList.add(cleanHubRule);
+ chatRuleList.add(miningAbilityRule);
+ }
+
+ private static void loadLocations() {
+ try {
+ String response = Http.sendGetRequest("https://api.hypixel.net/v2/resources/games");
+ JsonObject locationsJson = JsonParser.parseString(response).getAsJsonObject().get("games").getAsJsonObject().get("SKYBLOCK").getAsJsonObject().get("modeNames").getAsJsonObject();
+ for (Map.Entry<String, JsonElement> entry : locationsJson.entrySet()) {
+ //fix old naming todo remove when hypixel fix
+ if (Objects.equals(entry.getKey(), "instanced")) {
+ locationsList.add(entry.getValue().getAsString());
+ locations.put(entry.getValue().getAsString().replace(" ", "").toLowerCase(), "kuudra");
+ continue;
+ }
+ locationsList.add(entry.getValue().getAsString());
+ //add to list in a simplified for so more lenient for user input
+ locations.put(entry.getValue().getAsString().replace(" ", "").toLowerCase(), entry.getKey());
+ }
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker] Failed to load locations!", e);
+ }
+ }
+
+ protected static void saveChatRules() {
+ JsonObject chatRuleJson = new JsonObject();
+ chatRuleJson.add("rules", ChatRule.LIST_CODEC.encodeStart(JsonOps.INSTANCE, chatRuleList).result().orElseThrow());
+ try (BufferedWriter writer = Files.newBufferedWriter(CHAT_RULE_FILE)) {
+ SkyblockerMod.GSON.toJson(chatRuleJson, writer);
+ LOGGER.info("[Skyblocker] Saved chat rules file");
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker] Failed to save chat rules file", e);
+ }
+ }
+
+ /**
+ * Checks each rule in {@link ChatRulesHandler#chatRuleList} to see if they are a match for the message and if so change outputs based on the options set in the {@link ChatRule}.
+ * @param message the chat message
+ * @param overlay if its overlay
+ */
+ private static boolean checkMessage(Text message, boolean overlay) {
+ if (!Utils.isOnSkyblock()) return true; //do not work not on skyblock
+ if (overlay) return true; //ignore messages in 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()) {
+ ChatRuleAnnouncementScreen.setText(newMessage);
+ }
+
+ //show in action bar
+ if (rule.getShowActionBar() && CLIENT.player != null) {
+ CLIENT.player.sendMessage(newMessage, true);
+ }
+
+ //hide message
+ if (!rule.getHideMessage() && CLIENT.player != null) {
+ CLIENT.player.sendMessage(newMessage, 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;
+ }
+
+ /**
+ * Converts a string with color codes into a formatted Text object
+ * @param codedString the string with color codes in
+ * @return formatted text
+ */
+ protected static MutableText formatText(String codedString) {
+ if (codedString.contains(String.valueOf(Formatting.FORMATTING_CODE_PREFIX)) || codedString.contains("&")) {
+ MutableText newText = Text.literal("");
+ String[] parts = codedString.split("[" + Formatting.FORMATTING_CODE_PREFIX +"&]");
+ Style style = Style.EMPTY;
+
+ for (String part : parts) {
+ if (part.isEmpty()) continue;
+ Formatting formatting = Formatting.byCode(part.charAt(0));
+
+ if (formatting != null) {
+ style = style.withFormatting(formatting);
+ Text.literal(part.substring(1)).getWithStyle(style).forEach(newText::append);
+ } else {
+ newText.append(Text.of(part));
+ }
+ }
+ return newText;
+ }
+ return Text.literal(codedString);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CroesusHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CroesusHelper.java
index e95b47c9..01422770 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CroesusHelper.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CroesusHelper.java
@@ -25,8 +25,12 @@ public class CroesusHelper extends ContainerSolver {
List<ColorHighlight> highlights = new ArrayList<>();
for (Map.Entry<Integer, ItemStack> entry : slots.entrySet()) {
ItemStack stack = entry.getValue();
- if (stack != null && stack.getNbt() != null && (stack.getNbt().toString().contains("No more Chests to open!") || stack.getNbt().toString().contains("Opened Chest:"))) {
- highlights.add(ColorHighlight.gray(entry.getKey()));
+ if (stack != null && stack.getNbt() != null) {
+ if (stack.getNbt().toString().contains("Opened Chest:")) {
+ highlights.add(ColorHighlight.gray(entry.getKey()));
+ } else if (stack.getNbt().toString().contains("No more Chests to open!")) {
+ highlights.add(ColorHighlight.red(entry.getKey()));
+ }
}
}
return highlights;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CroesusProfit.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CroesusProfit.java
new file mode 100644
index 00000000..ca166915
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CroesusProfit.java
@@ -0,0 +1,267 @@
+package de.hysky.skyblocker.skyblock.dungeon;
+
+import com.google.gson.JsonObject;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType;
+import de.hysky.skyblocker.utils.ItemUtils;
+import de.hysky.skyblocker.utils.render.gui.ColorHighlight;
+import de.hysky.skyblocker.utils.render.gui.ContainerSolver;
+import net.minecraft.item.ItemStack;
+import net.minecraft.text.Text;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class CroesusProfit extends ContainerSolver {
+ private static final Pattern ESSENCE_PATTERN = Pattern.compile("(?<type>[A-Za-z]+) Essence x(?<amount>[0-9]+)");
+ public CroesusProfit() {
+ super(".*Catacombs - Floor.*");
+ }
+
+ @Override
+ protected boolean isEnabled() {
+ return SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.croesusProfit;
+ }
+
+ @Override
+ protected List<ColorHighlight> getColors(String[] groups, Map<Integer, ItemStack> slots) {
+ List<ColorHighlight> highlights = new ArrayList<>();
+ ItemStack bestChest = null, secondBestChest = null;
+ long bestValue = 0, secondBestValue = 0; // If negative value of chest - it is out of the question
+ long dungeonKeyPriceData = getItemPrice("DUNGEON_CHEST_KEY") * 2; // lesser ones don't worth the hassle
+
+ for (Map.Entry<Integer, ItemStack> entry : slots.entrySet()) {
+ ItemStack stack = entry.getValue();
+ if (stack != null && stack.getNbt() != null && stack.getName().toString().contains("Chest")) {
+ long value = valueChest(stack);
+ if (value > bestValue) {
+ secondBestChest = bestChest;
+ secondBestValue = bestValue;
+ bestChest = stack;
+ bestValue = value;
+ } else if (value > secondBestValue) {
+ secondBestChest = stack;
+ secondBestValue = value;
+ }
+ }
+ }
+
+ for (Map.Entry<Integer, ItemStack> entry : slots.entrySet()) {
+ ItemStack stack = entry.getValue();
+ if (stack != null && stack.getNbt() != null) {
+ if (stack.equals(bestChest)) {
+ highlights.add(ColorHighlight.green(entry.getKey()));
+ } else if (stack.equals(secondBestChest) && secondBestValue > dungeonKeyPriceData) {
+ highlights.add(ColorHighlight.yellow(entry.getKey()));
+ }
+ }
+ }
+ return highlights;
+ }
+
+
+ private long valueChest(@NotNull ItemStack chest) {
+ long chestValue = 0;
+ int chestPrice = 0;
+ List<String> chestItems = new ArrayList<>();
+
+ boolean processingContents = false;
+ for (Text line : ItemUtils.getNbtTooltips(chest)) {
+ String lineString = line.getString();
+ if (lineString.contains("Contents")) {
+ processingContents = true;
+ continue;
+ } else if (lineString.isEmpty()) {
+ processingContents = false;
+ } else if (lineString.contains("Coins") && !processingContents) {
+ chestPrice = Integer.parseInt(lineString.replaceAll(",", "").replaceAll("\\D", ""));
+ }
+
+ if (processingContents) {
+ if (lineString.contains("Essence")) {
+ Matcher matcher = ESSENCE_PATTERN.matcher(lineString);
+ if (matcher.matches()) { // add to chest value result of multiplying price of essence on it's amount
+ chestValue += getItemPrice(("ESSENCE_" + matcher.group("type")).toUpperCase()) * Integer.parseInt(matcher.group("amount"));
+ }
+ } else {
+ if (lineString.contains("Spirit")) { // TODO: make code like this to detect recombed gear (it can drop with 1% chance, according to wiki, tho I never saw any?)
+ chestValue += line.getStyle().toString().contains("color=dark_purple") ? getItemPrice("Spirit Epic") : getItemPrice(lineString);
+ } else {
+ chestItems.add(lineString);
+ }
+ }
+ }
+ }
+ for (String item : chestItems){
+ chestValue += getItemPrice(item);
+ }
+ return chestValue-chestPrice;
+ }
+
+
+ private long getItemPrice(String itemDisplayName) {
+ JsonObject bazaarPrices = TooltipInfoType.BAZAAR.getData();
+ JsonObject lbinPrices = TooltipInfoType.LOWEST_BINS.getData();
+ long itemValue = 0;
+ String id = dungeonDropsNameToId.get(itemDisplayName);
+
+ if (bazaarPrices == null || lbinPrices == null) return 0;
+
+ if (bazaarPrices.has(id)) {
+ JsonObject item = bazaarPrices.get(id).getAsJsonObject();
+ boolean isPriceNull = item.get("sellPrice").isJsonNull();
+ return (isPriceNull ? 0L : item.get("sellPrice").getAsLong());
+ } else if (lbinPrices.has(id)) {
+ return lbinPrices.get(id).getAsLong();
+ }
+ return itemValue;
+ }
+
+
+ // I did a thing :(
+ final Map<String, String> dungeonDropsNameToId = new HashMap<>() {{
+ put("Enchanted Book (Ultimate Jerry I)", "ENCHANTMENT_ULTIMATE_JERRY_1"); // ultimate books start
+ put("Enchanted Book (Ultimate Jerry II)", "ENCHANTMENT_ULTIMATE_JERRY_2");
+ put("Enchanted Book (Ultimate Jerry III)", "ENCHANTMENT_ULTIMATE_JERRY_3");
+ put("Enchanted Book (Bank I)", "ENCHANTMENT_ULTIMATE_BANK_1");
+ put("Enchanted Book (Bank II)", "ENCHANTMENT_ULTIMATE_BANK_2");
+ put("Enchanted Book (Bank III)", "ENCHANTMENT_ULTIMATE_BANK_3");
+ put("Enchanted Book (Combo I)", "ENCHANTMENT_ULTIMATE_COMBO_1");
+ put("Enchanted Book (Combo II)", "ENCHANTMENT_ULTIMATE_COMBO_2");
+ put("Enchanted Book (No Pain No Gain I)", "ENCHANTMENT_ULTIMATE_NO_PAIN_NO_GAIN_1");
+ put("Enchanted Book (No Pain No Gain II)", "ENCHANTMENT_ULTIMATE_NO_PAIN_NO_GAIN_2");
+ put("Enchanted Book (Ultimate Wise I)", "ENCHANTMENT_ULTIMATE_WISE_1");
+ put("Enchanted Book (Ultimate Wise II)", "ENCHANTMENT_ULTIMATE_WISE_2");
+ put("Enchanted Book (Wisdom I)", "ENCHANTMENT_ULTIMATE_WISDOM_1");
+ put("Enchanted Book (Wisdom II)", "ENCHANTMENT_ULTIMATE_WISDOM_2");
+ put("Enchanted Book (Last Stand I)", "ENCHANTMENT_ULTIMATE_LAST_STAND_1");
+ put("Enchanted Book (Last Stand II)", "ENCHANTMENT_ULTIMATE_LAST_STAND_2");
+ put("Enchanted Book (Rend I)", "ENCHANTMENT_ULTIMATE_REND_1");
+ put("Enchanted Book (Rend II)", "ENCHANTMENT_ULTIMATE_REND_2");
+ put("Enchanted Book (Legion I)", "ENCHANTMENT_ULTIMATE_LEGION_1");
+ put("Enchanted Book (Swarm I)", "ENCHANTMENT_ULTIMATE_SWARM_1");
+ put("Enchanted Book (One For All I)", "ENCHANTMENT_ULTIMATE_ONE_FOR_ALL_1");
+ put("Enchanted Book (Soul Eater I)", "ENCHANTMENT_ULTIMATE_SOUL_EATER_1"); // ultimate books end
+ put("Enchanted Book (Infinite Quiver VI)", "ENCHANTMENT_INFINITE_QUIVER_6"); // enchanted books start
+ put("Enchanted Book (Infinite Quiver VII)", "ENCHANTMENT_INFINITE_QUIVER_7");
+ put("Enchanted Book (Feather Falling VI)", "ENCHANTMENT_FEATHER_FALLING_6");
+ put("Enchanted Book (Feather Falling VII)", "ENCHANTMENT_FEATHER_FALLING_7");
+ put("Enchanted Book (Rejuvenate I)", "ENCHANTMENT_REJUVENATE_1");
+ put("Enchanted Book (Rejuvenate II)", "ENCHANTMENT_REJUVENATE_2");
+ put("Enchanted Book (Rejuvenate III)", "ENCHANTMENT_REJUVENATE_3");
+ put("Enchanted Book (Overload)", "ENCHANTMENT_OVERLOAD_1");
+ put("Enchanted Book (Lethality VI)", "ENCHANTMENT_LETHALITY_6");
+ put("Enchanted Book (Thunderlord VII)", "ENCHANTMENT_THUNDERLORD_7"); // enchanted books end
+
+ put("Hot Potato Book", "HOT_POTATO_BOOK"); // HPB, FPB, Recomb (universal drops)
+ put("Fuming Potato Book", "FUMING_POTATO_BOOK");
+ put("Recombobulator 3000", "RECOMBOBULATOR_3000");
+ put("Necromancer's Brooch", "NECROMANCER_BROOCH");
+ put("ESSENCE_WITHER","ESSENCE_WITHER"); // Essences. Really stupid way of doing this
+ put("ESSENCE_UNDEAD", "ESSENCE_UNDEAD");
+ put("ESSENCE_DRAGON", "ESSENCE_DRAGON");
+ put("ESSENCE_SPIDER", "ESSENCE_SPIDER");
+ put("ESSENCE_ICE", "ESSENCE_ICE");
+ put("ESSENCE_DIAMOND", "ESSENCE_DIAMOND");
+ put("ESSENCE_GOLD", "ESSENCE_GOLD");
+ put("ESSENCE_CRIMSON", "ESSENCE_CRIMSON");
+ put("DUNGEON_CHEST_KEY", "DUNGEON_CHEST_KEY");
+
+ put("Bonzo's Staff", "BONZO_STAFF"); // F1 M1
+ put("Master Skull - Tier 1", "MASTER_SKULL_TIER_1");
+ put("Bonzo's Mask", "BONZO_MASK");
+ put("Balloon Snake", "BALLOON_SNAKE");
+ put("Red Nose", "RED_NOSE");
+
+ put("Red Scarf", "RED_SCARF"); // F2 M2
+ put("Adaptive Blade", "STONE_BLADE");
+ put("Master Skull - Tier 2", "MASTER_SKULL_TIER_2");
+ put("Adaptive Belt", "ADAPTIVE_BELT");
+ put("Scarf's Studies", "SCARF_STUDIES");
+
+ put("First Master Star", "FIRST_MASTER_STAR"); // F3 M3
+ put("Adaptive Helmet", "ADAPTIVE_HELMET");
+ put("Adaptive Chestplate", "ADAPTIVE_CHESTPLATE");
+ put("Adaptive Leggings", "ADAPTIVE_LEGGINGS");
+ put("Adaptive Boots", "ADAPTIVE_BOOTS");
+ put("Master Skull - Tier 3", "MASTER_SKULL_TIER_3");
+ put("Suspicious Vial", "SUSPICIOUS_VIAL");
+
+ put("Spirit Sword", "SPIRIT_SWORD"); // F4 M4
+ put("Spirit Shortbow", "ITEM_SPIRIT_BOW");
+ put("Spirit Boots", "THORNS_BOOTS");
+ put("Spirit", "LVL_1_LEGENDARY_SPIRIT"); // Spirit pet (Legendary)
+ put("Spirit Epic", "LVL_1_EPIC_SPIRIT");
+
+ put("Second Master Star", "SECOND_MASTER_STAR");
+ put("Spirit Wing", "SPIRIT_WING");
+ put("Spirit Bone", "SPIRIT_BONE");
+ put("Spirit Stone", "SPIRIT_DECOY");
+
+ put("Shadow Fury", "SHADOW_FURY"); // F5 M5
+ put("Last Breath", "LAST_BREATH");
+ put("Third Master Star", "THIRD_MASTER_STAR");
+ put("Warped Stone", "AOTE_STONE");
+ put("Livid Dagger", "LIVID_DAGGER");
+ put("Shadow Assassin Helmet", "SHADOW_ASSASSIN_HELMET");
+ put("Shadow Assassin Chestplate", "SHADOW_ASSASSIN_CHESTPLATE");
+ put("Shadow Assassin Leggings", "SHADOW_ASSASSIN_LEGGINGS");
+ put("Shadow Assassin Boots", "SHADOW_ASSASSIN_BOOTS");
+ put("Shadow Assassin Cloak", "SHADOW_ASSASSIN_CLOAK");
+ put("Master Skull - Tier 4", "MASTER_SKULL_TIER_4");
+ put("Dark Orb", "DARK_ORB");
+
+ put("Precursor Eye", "PRECURSOR_EYE"); // F6 M6
+ put("Giant's Sword", "GIANTS_SWORD");
+ put("Necromancer Lord Helmet", "NECROMANCER_LORD_HELMET");
+ put("Necromancer Lord Chestplate", "NECROMANCER_LORD_CHESTPLATE");
+ put("Necromancer Lord Leggings", "NECROMANCER_LORD_LEGGINGS");
+ put("Necromancer Lord Boots", "NECROMANCER_LORD_BOOTS");
+ put("Fourth Master Star", "FOURTH_MASTER_STAR");
+ put("Summoning Ring", "SUMMONING_RING");
+ put("Fel Skull", "FEL_SKULL");
+ put("Necromancer Sword", "NECROMANCER_SWORD");
+ put("Soulweaver Gloves", "SOULWEAVER_GLOVES");
+ put("Sadan's Brooch", "SADAN_BROOCH");
+ put("Giant Tooth", "GIANT_TOOTH");
+
+ put("Precursor Gear", "PRECURSOR_GEAR"); // F7 M7
+ put("Necron Dye", "DYE_NECRON");
+ put("Storm the Fish", "STORM_THE_FISH");
+ put("Maxor the Fish", "MAXOR_THE_FISH");
+ put("Goldor the Fish", "GOLDOR_THE_FISH");
+ put("Dark Claymore", "DARK_CLAYMORE");
+ put("Necron's Handle", "NECRON_HANDLE");
+ put("Master Skull - Tier 5", "MASTER_SKULL_TIER_5");
+ put("Shadow Warp", "SHADOW_WARP_SCROLL");
+ put("Wither Shield", "WITHER_SHIELD_SCROLL");
+ put("Implosion", "IMPLOSION_SCROLL");
+ put("Fifth Master Star", "FIFTH_MASTER_STAR");
+ put("Auto Recombobulator", "AUTO_RECOMBOBULATOR");
+ put("Wither Helmet", "WITHER_HELMET");
+ put("Wither Chestplate", "WITHER_CHESTPLATE");
+ put("Wither Leggings", "WITHER_LEGGINGS");
+ put("Wither Boots", "WITHER_BOOTS");
+ put("Wither Catalyst", "WITHER_CATALYST");
+ put("Wither Cloak Sword", "WITHER_CLOAK");
+ put("Wither Blood", "WITHER_BLOOD");
+
+ put("Shiny Wither Helmet", "SHINY_WITHER_HELMET"); // M7 shiny drops
+ put("Shiny Wither Chestplate", "SHINY_WITHER_CHESTPLATE");
+ put("Shiny Wither Leggings", "SHINY_WITHER_LEGGINGS");
+ put("Shiny Wither Boots", "SHINY_WITHER_BOOTS");
+ put("Shiny Necron's Handle", "SHINY_NECRON_HANDLE"); // cool thing
+
+ put("Dungeon Disc", "DUNGEON_DISC_1");
+ put("Clown Disc", "DUNGEON_DISC_2");
+ put("Watcher Disc", "DUNGEON_DISC_3");
+ put("Old Disc", "DUNGEON_DISC_4");
+ put("Necron Disc", "DUNGEON_DISC_5");
+ }};
+}
+
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/Fetchur.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/Fetchur.java
index 6cc5f194..27cd62ad 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/Fetchur.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/Fetchur.java
@@ -42,8 +42,6 @@ public class Fetchur extends ChatPatternListener {
answers.put("yellow and see through", Text.translatable("block.minecraft.yellow_stained_glass").getString());
answers.put("circular and sometimes moves", Text.translatable("item.minecraft.compass").getString());
- // TODO remove when typo fixed by hypixel
- answers.put("circlular and sometimes moves", Text.translatable("item.minecraft.compass").getString());
answers.put("expensive minerals", "Mithril");
answers.put("useful during celebrations", Text.translatable("item.minecraft.firework_rocket").getString());
answers.put("hot and gives energy", "Cheap / Decent / Black Coffee");
@@ -56,10 +54,5 @@ public class Fetchur extends ChatPatternListener {
answers.put("shiny and makes sparks", Text.translatable("item.minecraft.flint_and_steel").getString());
answers.put("green and some dudes trade stuff for it", Text.translatable("item.minecraft.emerald").getString());
answers.put("red and soft", Text.translatable("block.minecraft.red_wool").getString());
-
- // old riddles that should no longer be active
- // TODO remove if not seen for a few months
- answers.put("round and green, or purple", Text.translatable("item.minecraft.ender_pearl").getString()); // removed Aug 21, 2021
- answers.put("red and white and you can mine it", Text.translatable("block.minecraft.nether_quartz_ore").getString()); // removed Sep 26, 2023
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java
index fbef1bcb..1b3f402d 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java
@@ -249,7 +249,7 @@ public class ItemTooltip {
public static void nullWarning() {
if (!sentNullWarning && client.player != null) {
- client.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.itemTooltip.nullMessage")), false);
+ LOGGER.warn(Constants.PREFIX.get().append(Text.translatable("skyblocker.itemTooltip.nullMessage")).getString());
sentNullWarning = true;
}
}
@@ -355,15 +355,19 @@ public class ItemTooltip {
// If these options is true beforehand, the client will get first data of these options while loading.
// After then, it will only fetch the data if it is on Skyblock.
- public static int minute = -1;
+ public static int minute = 0;
public static void init() {
Scheduler.INSTANCE.scheduleCyclic(() -> {
- if (!Utils.isOnSkyblock() && 0 < minute++) {
+ if (!Utils.isOnSkyblock() && 0 < minute) {
sentNullWarning = false;
return;
}
+ if (++minute % 60 == 0) {
+ sentNullWarning = false;
+ }
+
List<CompletableFuture<Void>> futureList = new ArrayList<>();
TooltipInfoType.NPC.downloadIfEnabled(futureList);
@@ -387,9 +391,10 @@ public class ItemTooltip {
TooltipInfoType.MUSEUM.downloadIfEnabled(futureList);
TooltipInfoType.COLOR.downloadIfEnabled(futureList);
- minute++;
- CompletableFuture.allOf(futureList.toArray(CompletableFuture[]::new))
- .whenComplete((unused, throwable) -> sentNullWarning = false);
+ CompletableFuture.allOf(futureList.toArray(CompletableFuture[]::new)).exceptionally(e -> {
+ LOGGER.error("Encountered unknown error while downloading tooltip data", e);
+ return null;
+ });
}, 1200, true);
}
} \ No newline at end of file
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java
index fc5c7087..d798451e 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java
@@ -15,8 +15,8 @@ import java.util.function.Predicate;
public enum TooltipInfoType implements Runnable {
NPC("https://hysky.de/api/npcprice", itemTooltip -> itemTooltip.enableNPCPrice, true),
- BAZAAR("https://hysky.de/api/bazaar", itemTooltip -> itemTooltip.enableBazaarPrice || SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator || SkyblockerConfigManager.get().general.chestValue.enableChestValue, itemTooltip -> itemTooltip.enableBazaarPrice, false),
- LOWEST_BINS("https://hysky.de/api/auctions/lowestbins", itemTooltip -> itemTooltip.enableLowestBIN || SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator || SkyblockerConfigManager.get().general.chestValue.enableChestValue, itemTooltip -> itemTooltip.enableLowestBIN, false),
+ BAZAAR("https://hysky.de/api/bazaar", itemTooltip -> itemTooltip.enableBazaarPrice || SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator || SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.croesusProfit || SkyblockerConfigManager.get().general.chestValue.enableChestValue, itemTooltip -> itemTooltip.enableBazaarPrice, false),
+ LOWEST_BINS("https://hysky.de/api/auctions/lowestbins", itemTooltip -> itemTooltip.enableLowestBIN || SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator || SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.croesusProfit || SkyblockerConfigManager.get().general.chestValue.enableChestValue, itemTooltip -> itemTooltip.enableLowestBIN, false),
ONE_DAY_AVERAGE("https://moulberry.codes/auction_averages_lbin/1day.json", itemTooltip -> itemTooltip.enableAvgBIN, false),
THREE_DAY_AVERAGE("https://moulberry.codes/auction_averages_lbin/3day.json", itemTooltip -> itemTooltip.enableAvgBIN || SkyblockerConfigManager.get().general.searchOverlay.enableAuctionHouse, itemTooltip -> itemTooltip.enableAvgBIN, false),
MOTES("https://hysky.de/api/motesprice", itemTooltip -> itemTooltip.enableMotesPrice, itemTooltip -> itemTooltip.enableMotesPrice && Utils.isInTheRift(), true),
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/MythologicalRitual.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/MythologicalRitual.java
index 6629c377..45a52388 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/MythologicalRitual.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/MythologicalRitual.java
@@ -92,8 +92,8 @@ public class MythologicalRitual {
griffinBurrows.get(pos).init();
}
} else if (ParticleTypes.DUST.equals(packet.getParameters().getType())) {
- BlockPos pos = BlockPos.ofFloored(packet.getX(), packet.getY(), packet.getZ()).down(2);
- GriffinBurrow burrow = griffinBurrows.get(pos);
+ BlockPos pos = BlockPos.ofFloored(packet.getX(), packet.getY(), packet.getZ());
+ GriffinBurrow burrow = griffinBurrows.get(pos.down(2));
if (burrow == null) {
return;
}
@@ -106,7 +106,7 @@ public class MythologicalRitual {
if (burrow.nextBurrowLine == null) {
burrow.nextBurrowLine = new Vec3d[1001];
}
- fillLine(burrow.nextBurrowLine, Vec3d.of(pos), nextBurrowDirection);
+ fillLine(burrow.nextBurrowLine, Vec3d.ofCenter(pos.up()), nextBurrowDirection);
} else if (ParticleTypes.DRIPPING_LAVA.equals(packet.getParameters().getType()) && packet.getCount() == 2) {
if (System.currentTimeMillis() > lastEchoTime + 10_000) {
return;
diff --git a/src/main/java/de/hysky/skyblocker/utils/FileUtils.java b/src/main/java/de/hysky/skyblocker/utils/FileUtils.java
new file mode 100644
index 00000000..22611441
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/FileUtils.java
@@ -0,0 +1,36 @@
+package de.hysky.skyblocker.utils;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.slf4j.Logger;
+
+import com.mojang.logging.LogUtils;
+
+public class FileUtils {
+ private static final Logger LOGGER = LogUtils.getLogger();
+
+ public static void recursiveDelete(Path dir) throws IOException {
+ if (Files.isDirectory(dir) && !Files.isSymbolicLink(dir)) {
+ Files.list(dir).forEach(child -> {
+ try {
+ recursiveDelete(child);
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker] Encountered an exception while deleting a file! Path: {}", child.toAbsolutePath(), e);
+ }
+ });
+ }
+
+ Files.delete(dir);
+ }
+
+ /**
+ * Replaces any characters that do not match the regex: [^a-z0-9_.-]
+ *
+ * @implNote Designed to convert a file path to an {@link net.minecraft.util.Identifier}
+ */
+ public static String normalizePath(Path path) {
+ return path.toString().toLowerCase().replaceAll("[^a-z0-9_.-]", "");
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/Http.java b/src/main/java/de/hysky/skyblocker/utils/Http.java
index 58deced2..871eac78 100644
--- a/src/main/java/de/hysky/skyblocker/utils/Http.java
+++ b/src/main/java/de/hysky/skyblocker/utils/Http.java
@@ -33,10 +33,6 @@ public class Http {
.followRedirects(Redirect.NORMAL)
.build();
- public static String sendGetRequest(String url) throws IOException, InterruptedException {
- return sendCacheableGetRequest(url).content();
- }
-
private static ApiResponse sendCacheableGetRequest(String url) throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
.GET()
@@ -55,6 +51,26 @@ public class Http {
return new ApiResponse(body, response.statusCode(), getCacheStatuses(headers), getAge(headers));
}
+
+ public static InputStream downloadContent(String url) throws IOException, InterruptedException {
+ HttpRequest request = HttpRequest.newBuilder()
+ .GET()
+ .header("Accept", "*/*")
+ .header("Accept-Encoding", "gzip, deflate")
+ .header("User-Agent", USER_AGENT)
+ .version(Version.HTTP_2)
+ .uri(URI.create(url))
+ .build();
+
+ HttpResponse<InputStream> response = HTTP_CLIENT.send(request, BodyHandlers.ofInputStream());
+ InputStream decodedInputStream = getDecodedInputStream(response);
+
+ return decodedInputStream;
+ }
+
+ public static String sendGetRequest(String url) throws IOException, InterruptedException {
+ return sendCacheableGetRequest(url).content();
+ }
public static HttpHeaders sendHeadRequest(String url) throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
diff --git a/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java b/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java
index 870e94da..c779d666 100644
--- a/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java
+++ b/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java
@@ -13,13 +13,10 @@ import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.File;
-import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.CompletableFuture;
-import java.util.stream.Stream;
/**
* Initializes the NEU repo, which contains item metadata and fairy souls location data. Clones the repo if it does not exist and checks for updates. Use {@link #runAsyncAfterLoad(Runnable)} to run code after the repo is initialized.
@@ -76,7 +73,7 @@ public class NEURepoManager {
CompletableFuture.runAsync(() -> {
try {
ItemRepository.setFilesImported(false);
- recursiveDelete(NEURepoManager.LOCAL_REPO_DIR);
+ FileUtils.recursiveDelete(NEURepoManager.LOCAL_REPO_DIR);
} catch (Exception ex) {
if (MinecraftClient.getInstance().player != null)
MinecraftClient.getInstance().player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.updaterepository.failed")), false);
@@ -86,21 +83,6 @@ public class NEURepoManager {
});
}
- @SuppressWarnings("ResultOfMethodCallIgnored")
- private static void recursiveDelete(Path dir) throws IOException {
- if (Files.isDirectory(dir) && !Files.isSymbolicLink(dir)) {
- Files.list(dir).forEach(child -> {
- try {
- recursiveDelete(child);
- } catch (Exception e) {
- LOGGER.error("[Skyblocker] Encountered an exception while deleting a file! Path: {}", child.toAbsolutePath(), e);
- }
- });
- }
-
- Files.delete(dir);
- }
-
/**
* Runs the given runnable after the NEU repo is initialized.
* @param runnable the runnable to run
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java
index f78e5184..460f34dd 100644
--- a/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java
+++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java
@@ -3,6 +3,7 @@ package de.hysky.skyblocker.utils.render.gui;
import com.mojang.blaze3d.systems.RenderSystem;
import de.hysky.skyblocker.mixin.accessor.HandledScreenAccessor;
import de.hysky.skyblocker.skyblock.dungeon.CroesusHelper;
+import de.hysky.skyblocker.skyblock.dungeon.CroesusProfit;
import de.hysky.skyblocker.skyblock.dungeon.terminal.ColorTerminal;
import de.hysky.skyblocker.skyblock.dungeon.terminal.OrderTerminal;
import de.hysky.skyblocker.skyblock.dungeon.terminal.StartsWithTerminal;
@@ -40,6 +41,7 @@ public class ContainerSolverManager {
new OrderTerminal(),
new StartsWithTerminal(),
new CroesusHelper(),
+ new CroesusProfit(),
new ChronomatronSolver(),
new SuperpairsSolver(),
UltrasequencerSolver.INSTANCE