aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/de/hysky/skyblocker
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/de/hysky/skyblocker')
-rw-r--r--src/main/java/de/hysky/skyblocker/SkyblockerMod.java6
-rw-r--r--src/main/java/de/hysky/skyblocker/config/HudConfigScreen.java83
-rw-r--r--src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java36
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java8
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java8
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/LocationsCategory.java51
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/MessageFilterCategory.java18
-rw-r--r--src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java16
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRule.java30
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleAnnouncementScreen.java53
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleConfigScreen.java57
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigListWidget.java119
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigScreen.java39
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesHandler.java29
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/Boulder.java221
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderBoard.java129
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderObject.java9
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderSolver.java202
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/end/EndHudConfigScreen.java35
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/end/EndHudWidget.java65
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/end/TheEnd.java249
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java6
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java13
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/TableComponent.java11
25 files changed, 1358 insertions, 137 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
index 2ee6120e..fc72ea3f 100644
--- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
+++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
@@ -5,10 +5,12 @@ import com.google.gson.GsonBuilder;
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.boulder.Boulder;
import de.hysky.skyblocker.skyblock.dungeon.puzzle.CreeperBeams;
import de.hysky.skyblocker.skyblock.dungeon.puzzle.DungeonBlaze;
import de.hysky.skyblocker.skyblock.dungeon.puzzle.TicTacToe;
@@ -18,6 +20,7 @@ import de.hysky.skyblocker.skyblock.dungeon.secrets.SecretsTracker;
import de.hysky.skyblocker.skyblock.dwarven.CrystalsHud;
import de.hysky.skyblocker.skyblock.dwarven.CrystalsLocationsManager;
import de.hysky.skyblocker.skyblock.dwarven.DwarvenHud;
+import de.hysky.skyblocker.skyblock.end.TheEnd;
import de.hysky.skyblocker.skyblock.end.BeaconHighlighter;
import de.hysky.skyblocker.skyblock.item.*;
import de.hysky.skyblocker.skyblock.item.tooltip.BackpackPreview;
@@ -109,6 +112,7 @@ public class SkyblockerMod implements ClientModInitializer {
ChatMessageListener.init();
Shortcuts.init();
ChatRulesHandler.init();
+ ChatRuleAnnouncementScreen.init();
DiscordRPCManager.init();
LividColor.init();
FishingHelper.init();
@@ -123,6 +127,7 @@ public class SkyblockerMod implements ClientModInitializer {
FireFreezeStaffTimer.init();
GuardianHealth.init();
TheRift.init();
+ TheEnd.init();
SearchOverManager.init();
TitleContainer.init();
ScreenMaster.init();
@@ -137,6 +142,7 @@ public class SkyblockerMod implements ClientModInitializer {
SpecialEffects.init();
ItemProtection.init();
CreeperBeams.init();
+ Boulder.init();
ItemRarityBackgrounds.init();
MuseumItemCache.init();
SecretsTracker.init();
diff --git a/src/main/java/de/hysky/skyblocker/config/HudConfigScreen.java b/src/main/java/de/hysky/skyblocker/config/HudConfigScreen.java
new file mode 100644
index 00000000..c33e2f54
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/config/HudConfigScreen.java
@@ -0,0 +1,83 @@
+package de.hysky.skyblocker.config;
+
+import de.hysky.skyblocker.skyblock.tabhud.widget.Widget;
+import de.hysky.skyblocker.utils.render.RenderHelper;
+import it.unimi.dsi.fastutil.ints.IntIntImmutablePair;
+import it.unimi.dsi.fastutil.ints.IntIntPair;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.text.Text;
+
+import java.awt.*;
+
+public abstract class HudConfigScreen extends Screen {
+ private final Widget widget;
+ private final Screen parent;
+
+ private int hudX = 0;
+ private int hudY = 0;
+ public HudConfigScreen(Text title, Widget widget, Screen parent) {
+ super(title);
+ this.widget = widget;
+ this.parent = parent;
+
+ int[] posFromConfig = getPosFromConfig(SkyblockerConfigManager.get());
+ hudX = posFromConfig[0];
+ hudY = posFromConfig[1];
+ }
+
+ @Override
+ public void render(DrawContext context, int mouseX, int mouseY, float delta) {
+ super.render(context, mouseX, mouseY, delta);
+ renderBackground(context, mouseX, mouseY, delta);
+ renderWidget(context, hudX, hudY);
+ context.drawCenteredTextWithShadow(textRenderer, "Right Click To Reset Position", width / 2, height / 2, Color.GRAY.getRGB());
+ }
+
+ @Override
+ public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
+ IntIntPair dims = getDimensions();
+ if (RenderHelper.pointIsInArea(mouseX, mouseY, hudX, hudY, hudX + dims.leftInt(), hudY + dims.rightInt()) && button == 0) {
+ hudX = (int) Math.max(Math.min(mouseX - (double) dims.leftInt() / 2, this.width - dims.leftInt()), 0);
+ hudY = (int) Math.max(Math.min(mouseY - (double) dims.rightInt() / 2, this.height - dims.rightInt()), 0);
+ }
+ return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
+ }
+
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY, int button) {
+ if (button == 1) {
+ IntIntPair dims = getDimensions();
+ hudX = this.width / 2 - dims.leftInt();
+ hudY = this.height / 2 - dims.rightInt();
+ }
+ return super.mouseClicked(mouseX, mouseY, button);
+ }
+
+ abstract protected int[] getPosFromConfig(SkyblockerConfig config);
+
+ protected IntIntPair getDimensions() {
+ return new IntIntImmutablePair(widget.getHeight(), widget.getWidth());
+ }
+
+ @Override
+ public void close() {
+ SkyblockerConfig skyblockerConfig = SkyblockerConfigManager.get();
+ savePos(skyblockerConfig, hudX, hudY);
+ SkyblockerConfigManager.save();
+
+ client.setScreen(parent);
+ }
+
+ /**
+ * This method should save the passed position to the config
+ * <p>
+ * NOTE: The parent class will call {@link SkyblockerConfigManager#save()} right after this method
+ * @param configManager the config so you don't have to get it
+ * @param x x
+ * @param y y
+ */
+ abstract protected void savePos(SkyblockerConfig configManager, int x, int y);
+
+ abstract protected void renderWidget(DrawContext context, int x, int y);
+}
diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
index 175c3bdf..ea1f7d43 100644
--- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
@@ -175,6 +175,9 @@ public class SkyblockerConfig {
public boolean dontStripSkinAlphaValues = true;
@SerialEntry
+ public boolean dungeonQuality = true;
+
+ @SerialEntry
public boolean visitorHelper = true;
@SerialEntry
@@ -652,6 +655,9 @@ public class SkyblockerConfig {
public Rift rift = new Rift();
@SerialEntry
+ public TheEnd end = new TheEnd();
+
+ @SerialEntry
public SpidersDen spidersDen = new SpidersDen();
@SerialEntry
@@ -714,6 +720,9 @@ public class SkyblockerConfig {
public boolean solveWaterboard = true;
@SerialEntry
+ public boolean solveBoulder = true;
+
+ @SerialEntry
public boolean fireFreezeStaffTimer = true;
@SerialEntry
@@ -1042,6 +1051,24 @@ public class SkyblockerConfig {
public int mcGrubberStacks = 0;
}
+ public static class TheEnd {
+
+ @SerialEntry
+ public boolean hudEnabled = true;
+
+ @SerialEntry
+ public boolean enableBackground = true;
+
+ @SerialEntry
+ public boolean waypoint = true;
+
+ @SerialEntry
+ public int x = 10;
+
+ @SerialEntry
+ public int y = 10;
+ }
+
public static class SpidersDen {
@SerialEntry
public Relics relics = new Relics();
@@ -1159,6 +1186,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 3b685f9a..5eb9a066 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
@@ -409,6 +409,14 @@ public class DungeonsCategory {
.controller(ConfigUtils::createBooleanController)
.build())
.option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.solveBoulder"))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.solveBoulder.@Tooltip")))
+ .binding(defaults.locations.dungeons.solveBoulder,
+ () -> config.locations.dungeons.solveBoulder,
+ newValue -> config.locations.dungeons.solveBoulder = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<Boolean>createBuilder()
.name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.fireFreezeStaffTimer"))
.description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.fireFreezeStaffTimer.@Tooltip")))
.binding(defaults.locations.dungeons.fireFreezeStaffTimer,
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java
index bb333f79..8a7d832c 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java
@@ -79,6 +79,14 @@ public class GeneralCategory {
.flag(OptionFlag.ASSET_RELOAD)
.build())
.option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.general.dungeonQuality"))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.general.dungeonQuality.@Tooltip")))
+ .binding(defaults.general.dungeonQuality,
+ () -> config.general.dungeonQuality,
+ newValue -> config.general.dungeonQuality = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<Boolean>createBuilder()
.name(Text.translatable("text.autoconfig.skyblocker.option.general.visitorHelper"))
.binding(defaults.general.visitorHelper,
() -> config.general.visitorHelper,
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/LocationsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/LocationsCategory.java
index 9bdcf2e9..75c83a9b 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/LocationsCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/LocationsCategory.java
@@ -2,11 +2,11 @@ package de.hysky.skyblocker.config.categories;
import de.hysky.skyblocker.config.ConfigUtils;
import de.hysky.skyblocker.config.SkyblockerConfig;
-import dev.isxander.yacl3.api.ConfigCategory;
-import dev.isxander.yacl3.api.Option;
-import dev.isxander.yacl3.api.OptionDescription;
-import dev.isxander.yacl3.api.OptionGroup;
+import de.hysky.skyblocker.skyblock.end.EndHudConfigScreen;
+import de.hysky.skyblocker.skyblock.end.TheEnd;
+import dev.isxander.yacl3.api.*;
import dev.isxander.yacl3.api.controller.IntegerSliderControllerBuilder;
+import net.minecraft.client.MinecraftClient;
import net.minecraft.text.Text;
public class LocationsCategory {
@@ -79,6 +79,49 @@ public class LocationsCategory {
.build())
.build())
+ // The end
+ .group(OptionGroup.createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.end"))
+ .collapsed(false)
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.end.hudEnabled"))
+ .binding(defaults.locations.end.hudEnabled,
+ () -> config.locations.end.hudEnabled,
+ newValue -> config.locations.end.hudEnabled = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.dwarvenHud.enableBackground")) // Reusing that string cuz sure
+ .binding(defaults.locations.end.enableBackground,
+ () -> config.locations.end.enableBackground,
+ newValue -> config.locations.end.enableBackground = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.end.waypoint"))
+ .binding(defaults.locations.end.waypoint,
+ () -> config.locations.end.waypoint,
+ newValue -> config.locations.end.waypoint = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(ButtonOption.createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.end.screen"))
+ .text(Text.translatable("text.skyblocker.open")) // Reusing again lol
+ .action((screen, opt) -> MinecraftClient.getInstance().setScreen(new EndHudConfigScreen(screen)))
+ .build())
+ .option(ButtonOption.createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.end.resetName"))
+ .text(Text.translatable("text.autoconfig.skyblocker.option.locations.end.resetText"))
+ .action((screen, opt) -> {
+ TheEnd.zealotsKilled = 0;
+ TheEnd.zealotsSinceLastEye = 0;
+ TheEnd.eyes = 0;
+ })
+ .build())
+ .build()
+
+ )
+
//Spider's Den
.group(OptionGroup.createBuilder()
.name(Text.translatable("text.autoconfig.skyblocker.option.locations.spidersDen"))
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 ddad018a..0f95bcaa 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/MessageFilterCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/MessageFilterCategory.java
@@ -6,6 +6,7 @@ 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.*;
+import dev.isxander.yacl3.api.controller.IntegerSliderControllerBuilder;
import net.minecraft.client.MinecraftClient;
import net.minecraft.text.Text;
@@ -136,7 +137,22 @@ public class MessageFilterCategory {
.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/mixin/ClientPlayNetworkHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java
index 1f56fe34..4c414212 100644
--- a/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java
@@ -4,6 +4,7 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.WrapWithCondition;
import com.llamalad7.mixinextras.sugar.Local;
import de.hysky.skyblocker.skyblock.FishingHelper;
+import de.hysky.skyblocker.skyblock.end.TheEnd;
import de.hysky.skyblocker.skyblock.dungeon.DungeonScore;
import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager;
import de.hysky.skyblocker.skyblock.end.BeaconHighlighter;
@@ -52,12 +53,6 @@ public abstract class ClientPlayNetworkHandlerMixin {
return !Utils.isOnHypixel();
}
- @ModifyExpressionValue(method = "onEntityStatus", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/s2c/play/EntityStatusS2CPacket;getEntity(Lnet/minecraft/world/World;)Lnet/minecraft/entity/Entity;"))
- private Entity skyblocker$onEntityDeath(Entity entity, @Local(argsOnly = true) EntityStatusS2CPacket packet) {
- if (packet.getStatus() == EntityStatuses.PLAY_DEATH_SOUND_OR_ADD_PROJECTILE_HIT_PARTICLES) DungeonScore.handleEntityDeath(entity);
- return entity;
- }
-
@WrapWithCondition(method = "onPlayerList", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)V", remap = false))
private boolean skyblocker$cancelPlayerListWarning(Logger instance, String format, Object arg1, Object arg2) {
return !Utils.isOnHypixel();
@@ -87,4 +82,13 @@ public abstract class ClientPlayNetworkHandlerMixin {
private void skyblocker$onParticle(ParticleS2CPacket packet, CallbackInfo ci) {
MythologicalRitual.onParticle(packet);
}
+
+ @ModifyExpressionValue(method = "onEntityStatus", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/s2c/play/EntityStatusS2CPacket;getEntity(Lnet/minecraft/world/World;)Lnet/minecraft/entity/Entity;"))
+ private Entity skyblocker$onEntityDeath(Entity entity, @Local(argsOnly = true) EntityStatusS2CPacket packet) {
+ if (packet.getStatus() == EntityStatuses.PLAY_DEATH_SOUND_OR_ADD_PROJECTILE_HIT_PARTICLES) {
+ DungeonScore.handleEntityDeath(entity);
+ TheEnd.onEntityDeath(entity);
+ }
+ return entity;
+ }
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRule.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRule.java
index 9643b413..97431305 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRule.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRule.java
@@ -3,6 +3,8 @@ package de.hysky.skyblocker.skyblock.chat;
import de.hysky.skyblocker.utils.Utils;
import net.minecraft.client.sound.Sound;
import net.minecraft.item.ItemStack;
+import net.minecraft.sound.SoundEvent;
+import net.minecraft.sound.SoundEvents;
import java.util.List;
import java.util.Objects;
@@ -28,7 +30,7 @@ public class ChatRule {
private Boolean showActionBar;
private Boolean showAnnouncement;
private String replaceMessage; //todo extract parts of original message
- private Sound customSound;
+ private SoundEvent customSound;
/**
* Creates a chat rule with default options.
*/
@@ -49,22 +51,6 @@ public class ChatRule {
this.customSound = null;
}
-
- public ChatRule(String name, Boolean enabled, Boolean isPartialMatch, Boolean isRegex, Boolean isIgnoreCase, String filter, String validLocation, List<ItemStack> validItems, Boolean hideMessage, Boolean showActionBar, Boolean showAnnouncement, String replaceMessage, Sound customSound) {
- this.name = name;
- this.enabled = enabled;
- this.isPartialMatch = isPartialMatch;
- this.isRegex = isRegex;
- this.isIgnoreCase = isIgnoreCase;
- this.filter = filter;
- this.validLocations = validLocation;
- this.hideMessage = hideMessage;
- this.showActionBar = showActionBar;
- this.showAnnouncement = showAnnouncement;
- this.replaceMessage = replaceMessage;
- this.customSound = customSound;
- }
-
public Boolean getEnabled() { //todo remove unused getters and set
return enabled;
}
@@ -137,11 +123,11 @@ public class ChatRule {
this.replaceMessage = replaceMessage;
}
- public Sound getCustomSound() {
+ public SoundEvent getCustomSound() {
return customSound;
}
- public void setCustomSound(Sound customSound) {
+ public void setCustomSound(SoundEvent customSound) {
this.customSound = customSound;
}
@@ -174,7 +160,7 @@ public class ChatRule {
}
//filter
- if (testFilter.isEmpty()) return false;
+ if (testFilter.isBlank()) return false;
if(isRegex) {
if (isPartialMatch) {
if (! Pattern.compile(testFilter).matcher(testString).find()) return false;
@@ -190,7 +176,7 @@ public class ChatRule {
}
//location
- if (validLocations.isEmpty()){ //if no locations do not check
+ if (validLocations.isBlank()){ //if no locations do not check
return true;
}
String rawLocation = Utils.getLocationRaw();
@@ -208,7 +194,7 @@ public class ChatRule {
}
}
}
- if (isLocationValid == null || !isLocationValid){//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){//if location is not in the list at all and is a not a "!" location or and is a normal location
return true;
}
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..e6300808
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleAnnouncementScreen.java
@@ -0,0 +1,53 @@
+package de.hysky.skyblocker.skyblock.chat;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.dwarven.CrystalsHudConfigScreen;
+import de.hysky.skyblocker.utils.scheduler.Scheduler;
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
+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, 0xFFFFFF);
+
+ 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
index e866520f..f46af79b 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleConfigScreen.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleConfigScreen.java
@@ -6,16 +6,32 @@ import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
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 java.awt.*;
+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)
+ );//todo amathis / more sounds
+
private final int chatRuleIndex;
private final ChatRule chatRule;
@@ -32,8 +48,8 @@ public class ChatRuleConfigScreen extends Screen {
private ButtonWidget hideMessageToggle;
private ButtonWidget actionBarToggle;
private ButtonWidget announcementToggle;
+ private ButtonWidget soundsToggle;
private TextFieldWidget replaceMessageInput;
- //todo custom sound thing
//textLocations
private IntIntPair nameLabelTextPos;
@@ -51,11 +67,10 @@ public class ChatRuleConfigScreen extends Screen {
private IntIntPair hideMessageTextPos;
private IntIntPair actionBarTextPos;
private IntIntPair announcementTextPos;
-
- private IntIntPair replaceMessageLabelTextPos;
-
private IntIntPair customSoundLabelTextPos;
+ private IntIntPair replaceMessageLabelTextPos;
+ private int currentSoundIndex;
private final Screen parent;
@@ -66,6 +81,7 @@ public class ChatRuleConfigScreen extends Screen {
this.chatRuleIndex = chatRuleIndex;
this.chatRule = ChatRulesHandler.chatRuleList.get(chatRuleIndex);
this.parent = parent;
+ this.currentSoundIndex = soundsLookup.values().stream().toList().indexOf(chatRule.getCustomSound());
}
@Override
@@ -88,6 +104,7 @@ public class ChatRuleConfigScreen extends Screen {
filterLabelTextPos = currentPos;
lineXOffset = client.textRenderer.getWidth(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.filter")) + SPACER_X;
filterInput = new TextFieldWidget(MinecraftClient.getInstance().textRenderer, currentPos.leftInt() + lineXOffset, currentPos.rightInt(), 200, 20, Text.of(""));
+ filterInput.setMaxLength(96);
filterInput.setText(chatRule.getFilter());
currentPos = IntIntPair.of(currentPos.leftInt(),currentPos.rightInt() + SPACER_Y);
lineXOffset = 0;
@@ -163,16 +180,33 @@ public class ChatRuleConfigScreen extends Screen {
.position(currentPos.leftInt() + lineXOffset, currentPos.rightInt())
.size(75,20)
.build();
+ lineXOffset += 75 + 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);
+ chatRule.setCustomSound(soundsLookup.get(newText));
+ })
+ .position(currentPos.leftInt() + lineXOffset, currentPos.rightInt())
+ .size(100,20)
+ .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(MinecraftClient.getInstance().textRenderer, currentPos.leftInt() + lineXOffset, currentPos.rightInt(), 200, 20, Text.of(""));
+ replaceMessageInput.setMaxLength(96);
replaceMessageInput.setText(chatRule.getReplaceMessage());
finishButton = ButtonWidget.builder(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.finish"), a -> {
close();
})
- .position((int) (this.width * 0.66), this.height - SPACER_Y)
+ .position(this.width - 75 - SPACER_Y, this.height - SPACER_Y)
.size(75,20)
.build();
@@ -185,13 +219,14 @@ public class ChatRuleConfigScreen extends Screen {
addDrawableChild(hideMessageToggle);
addDrawableChild(actionBarToggle);
addDrawableChild(announcementToggle);
+ addDrawableChild(soundsToggle);
addDrawableChild(replaceMessageInput);
addDrawableChild(finishButton);
}
/**
* works out the width of the maximum line
- * @return
+ * @return the max used width
*/
private int getMaxUsedWidth() {
if (client == null) return 0;
@@ -237,6 +272,7 @@ public class ChatRuleConfigScreen extends Screen {
context.drawTextWithShadow(this.textRenderer,Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.hideMessage"), hideMessageTextPos.leftInt(), hideMessageTextPos.rightInt() + yOffset, 0xFFFFFF);
context.drawTextWithShadow(this.textRenderer,Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.actionBar"), actionBarTextPos.leftInt(), actionBarTextPos.rightInt() + yOffset, 0xFFFFFF);
context.drawTextWithShadow(this.textRenderer,Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.announcement"), announcementTextPos.leftInt(), announcementTextPos.rightInt() + yOffset, 0xFFFFFF);
+ context.drawTextWithShadow(this.textRenderer,Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.sounds"), customSoundLabelTextPos.leftInt(), customSoundLabelTextPos.rightInt() + yOffset, 0xFFFFFF);
context.drawTextWithShadow(this.textRenderer,Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.replace"), replaceMessageLabelTextPos.leftInt(), replaceMessageLabelTextPos.rightInt() + yOffset, 0xFFFFFF);
}
@@ -257,4 +293,13 @@ public class ChatRuleConfigScreen extends Screen {
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
index 07397f98..4e8038d6 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigListWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigListWidget.java
@@ -4,17 +4,19 @@ 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.widget.ButtonWidget;
import net.minecraft.client.gui.widget.ElementListWidget;
+import net.minecraft.screen.ScreenTexts;
import net.minecraft.text.Text;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.util.List;
-public class ChatRulesConfigListWidget extends ElementListWidget<ChatRulesConfigListWidget.chatRuleConfigEntry> {
+public class ChatRulesConfigListWidget extends ElementListWidget<ChatRulesConfigListWidget.AbstractChatRuleEntry> {
private final ChatRulesConfigScreen screen;
@@ -25,6 +27,8 @@ public class ChatRulesConfigListWidget extends ElementListWidget<ChatRulesConfig
super(minecraftClient, 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 < (long) ChatRulesHandler.chatRuleList.size(); i++){
addEntry(new chatRuleConfigEntry(i));
@@ -41,20 +45,15 @@ public class ChatRulesConfigListWidget extends ElementListWidget<ChatRulesConfig
return super.getScrollbarPositionX() + 50;
}
- @Override
- public void setSelected(@Nullable ChatRulesConfigListWidget.chatRuleConfigEntry entry) {
- super.setSelected(entry);
- screen.updateButtons();
- }
protected void addRuleAfterSelected() {
hasChanged = true;
int newIndex = children().indexOf(getSelectedOrNull()) + 1;
ChatRulesHandler.chatRuleList.add(newIndex, new ChatRule());
- children().add(newIndex, new chatRuleConfigEntry(newIndex));
+ children().add(newIndex + 1, new chatRuleConfigEntry(newIndex));
}
- protected boolean removeEntry(chatRuleConfigEntry entry) {
+ protected boolean removeEntry(AbstractChatRuleEntry entry) {
hasChanged = true;
return super.removeEntry(entry);
}
@@ -67,28 +66,52 @@ public class ChatRulesConfigListWidget extends ElementListWidget<ChatRulesConfig
protected boolean hasChanges(){
- return (hasChanged || children().stream().anyMatch(chatRuleConfigEntry::hasChange));
+ return (hasChanged || children().stream().filter(chatRuleConfigEntry.class::isInstance).map(chatRuleConfigEntry.class::cast).anyMatch(chatRuleConfigEntry::isChange));
+ }
+
+ protected static abstract class AbstractChatRuleEntry extends Entry<ChatRulesConfigListWidget.AbstractChatRuleEntry> {
}
- public class chatRuleConfigEntry extends Entry<chatRuleConfigEntry> {
+ private class chatRuleLabelsEntry extends AbstractChatRuleEntry {
+
+ @Override
+ public List<? extends Selectable> selectableChildren() {
+ return List.of();
+ }
- private static final int SPACING = 20;
+ @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, 0xFFFFFF);
+ context.drawCenteredTextWithShadow(client.textRenderer, Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleEnabled"), width / 2, y + 5, 0xFFFFFF);
+ context.drawCenteredTextWithShadow(client.textRenderer, Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.modify"), width / 2 + 100, y + 5, 0xFFFFFF);
+ }
+ }
+ private class chatRuleConfigEntry extends AbstractChatRuleEntry {
//data
private int chatRuleIndex;
private ChatRule chatRule;
+ private final List<? extends Element> children;
- private final List<? extends Element> children;
- //widgets
- private final ButtonWidget enabledWidget;
- private final ButtonWidget openConfigWidget;
+ //widgets
+ private final ButtonWidget enabledButton;
+ private final ButtonWidget openConfigButton;
+ private final ButtonWidget deleteButton;
+
//text locations
- private final int labelX;
- private final int enabledX;
+ private final int nameX = width / 2 - 125;
+
+ //saved data
+ private double oldScrollAmount = 0;
public chatRuleConfigEntry(int chatRuleIndex) {
@@ -96,48 +119,57 @@ public class ChatRulesConfigListWidget extends ElementListWidget<ChatRulesConfig
this.chatRule = ChatRulesHandler.chatRuleList.get(chatRuleIndex);
//initialize the widgets
- int currentX = width / 2 - 160;
- labelX = currentX;
- currentX += client.textRenderer.getWidth("Rule: \"" + chatRule.getName() + "\"");
- currentX += SPACING; //spacer
-
- enabledX = currentX;
- currentX += client.textRenderer.getWidth("Enabled:");
- enabledWidget = ButtonWidget.builder(enabledButtonText() , a -> {
+
+ enabledButton = ButtonWidget.builder(enabledButtonText() , a -> {
toggleEnabled();
})
.size(50,20)
- .position(currentX,5)
+ .position(width / 2 - 25,5)
.build()
;
- currentX += 50;
- currentX += SPACING; //spacer
- openConfigWidget = ButtonWidget.builder(Text.of("Edit Rule"), a -> {
+ openConfigButton = ButtonWidget.builder(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.editRule"), a -> {
client.setScreen(new ChatRuleConfigScreen(screen, chatRuleIndex));
})
- .size(100,20)
- .position(currentX,5)
+ .size(50,20)
+ .position(width / 2 + 45,5)
.build()
;
+ deleteButton = ButtonWidget.builder(Text.translatable("selectServer.delete"), a -> {
+ oldScrollAmount = getScrollAmount();
+ client.setScreen(new ConfirmScreen(this::deleteEntry, Text.translatable("skyblocker.shortcuts.deleteQuestion"), Text.translatable("skyblocker.shortcuts.deleteWarning", chatRule.getName()), Text.translatable("selectServer.deleteButton"), ScreenTexts.CANCEL));
+ })
+ .size(50,20)
+ .position(width / 2 + 105,5)
+ .build()
+ ;
+
- children = List.of(enabledWidget, openConfigWidget);
+ children = List.of(enabledButton, openConfigButton, deleteButton);
}
private Text enabledButtonText() {
if (chatRule.getEnabled()){
- return Text.literal("TRUE").withColor(Color.green.getRGB());
+ return Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.true").withColor(Color.green.getRGB());
}else {
- return Text.literal("FALSE").withColor(Color.red.getRGB());
+ 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());
- enabledWidget.setMessage(enabledButtonText());
+ 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() {
@@ -162,16 +194,17 @@ public class ChatRulesConfigListWidget extends ElementListWidget<ChatRulesConfig
@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
- enabledWidget.setY(y);
- enabledWidget.render(context, mouseX, mouseY, tickDelta);
- openConfigWidget.setY(y);
- openConfigWidget.render(context, mouseX, mouseY, tickDelta);
+ 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.drawTextWithShadow(client.textRenderer, "Rule: \"" + chatRule.getName() + "\"", labelX, y + 5, 0xFFFFFF);
- context.drawTextWithShadow(client.textRenderer, "enabled:", enabledX, y + 5, 0xFFFFFF);
+ context.drawCenteredTextWithShadow(client.textRenderer, chatRule.getName(), nameX, y + 5, 0xFFFFFF);
}
- public boolean hasChange() {
+ 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
index bbc3f14b..2cbb735b 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigScreen.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigScreen.java
@@ -14,10 +14,8 @@ import net.minecraft.text.Text;
public class ChatRulesConfigScreen extends Screen {
private ChatRulesConfigListWidget chatRulesConfigListWidget;
- private ButtonWidget buttonDelete;
private ButtonWidget buttonNew;
private ButtonWidget buttonDone;
- private double scrollAmount;
private final Screen parent;
public ChatRulesConfigScreen() {
@@ -25,7 +23,7 @@ public class ChatRulesConfigScreen extends Screen {
}
public ChatRulesConfigScreen(Screen parent) {
- super(Text.translatable("skyblocker.shortcuts.config")); //todo correct name for whole code
+ super(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen"));
this.parent = parent;
}
@@ -41,44 +39,21 @@ public class ChatRulesConfigScreen extends Screen {
addDrawableChild(chatRulesConfigListWidget);
GridWidget gridWidget = new GridWidget();
gridWidget.getMainPositioner().marginX(5).marginY(2);
- GridWidget.Adder adder = gridWidget.createAdder(2);
- buttonDelete = ButtonWidget.builder(Text.translatable("selectServer.delete"), button -> {
- ChatRulesConfigListWidget.chatRuleConfigEntry currentChatRuleConfigEntry = chatRulesConfigListWidget.getSelectedOrNull();
- if (client != null && currentChatRuleConfigEntry != null ) {
- scrollAmount = chatRulesConfigListWidget.getScrollAmount();
- client.setScreen(new ConfirmScreen(this::deleteEntry, Text.translatable("skyblocker.shortcuts.deleteQuestion"), Text.translatable("skyblocker.shortcuts.deleteWarning", currentChatRuleConfigEntry), Text.translatable("selectServer.deleteButton"), ScreenTexts.CANCEL)); //todo load text for this config
- }
- }).build();
- adder.add(buttonDelete);
- buttonNew = ButtonWidget.builder(Text.translatable("skyblocker.shortcuts.new"), buttonNew -> chatRulesConfigListWidget.addRuleAfterSelected()).build();
- adder.add(buttonNew);
+ GridWidget.Adder adder = gridWidget.createAdder(3);
adder.add(ButtonWidget.builder(ScreenTexts.CANCEL, button -> {
if (client != null) {
close();
}
}).build());
+ buttonNew = ButtonWidget.builder(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.new"), buttonNew -> chatRulesConfigListWidget.addRuleAfterSelected()).build();
+ adder.add(buttonNew);
buttonDone = ButtonWidget.builder(ScreenTexts.DONE, button -> {
chatRulesConfigListWidget.saveRules();
if (client != null) {
close();
}
- }).tooltip(Tooltip.of(Text.translatable("skyblocker.shortcuts.commandSuggestionTooltip"))).build();
- adder.add(buttonDone);
- gridWidget.refreshPositions();
- SimplePositioningWidget.setPos(gridWidget, 0, this.height - 64, this.width, 64);
+ }).build();
gridWidget.forEachChild(this::addDrawableChild);
- updateButtons();
- }
-
- private void deleteEntry(boolean confirmedAction) {
- if (client != null) {
- ChatRulesConfigListWidget.chatRuleConfigEntry currentChatRuleConfigEntry = chatRulesConfigListWidget.getSelectedOrNull();
- if (confirmedAction && currentChatRuleConfigEntry != null) {
- chatRulesConfigListWidget.removeEntry(currentChatRuleConfigEntry);
- }
- client.setScreen(this); // Re-inits the screen and keeps the old instance of ShortcutsConfigListWidget
- chatRulesConfigListWidget.setScrollAmount(scrollAmount);
- }
}
@Override
@@ -101,8 +76,4 @@ public class ChatRulesConfigScreen extends Screen {
this.client.setScreen(parent);
}
}
-
- protected void updateButtons() {
- buttonDelete.active = chatRulesConfigListWidget.getSelectedOrNull() != null;
- }
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesHandler.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesHandler.java
index f8823589..29ee1386 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesHandler.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesHandler.java
@@ -1,16 +1,12 @@
package de.hysky.skyblocker.skyblock.chat;
-import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import de.hysky.skyblocker.SkyblockerMod;
-import de.hysky.skyblocker.skyblock.shortcut.Shortcuts;
import de.hysky.skyblocker.utils.Utils;
-import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
-import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
-import net.fabricmc.fabric.api.client.message.v1.ClientSendMessageEvents;
import net.minecraft.client.MinecraftClient;
+import net.minecraft.sound.SoundEvents;
import net.minecraft.text.Text;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -29,7 +25,7 @@ import java.util.Map;
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("chatRules.json");
+ private static final Path CHAT_RULE_FILE = SkyblockerMod.CONFIG_DIR.resolve("chat_rules.json");
protected static final List<ChatRule> chatRuleList = new ArrayList<>();
@@ -74,27 +70,36 @@ public class ChatRulesHandler {
if (!Utils.isOnSkyblock()) return true; //do not work not on skyblock
if (overlay) return true; //ignore messages in overlay
String plain = trimItemColor(message.getString());
- for (ChatRule rule : chatRuleList) {
+ for (ChatRule rule : chatRuleList) {
if (rule.isMatch(plain)) {
//get a replacement message
Text newMessage;
- if (!rule.getReplaceMessage().isEmpty()){
+ if (!rule.getReplaceMessage().isBlank()) {
newMessage = Text.of(rule.getReplaceMessage());
}
else {
- newMessage =message;
+ newMessage = message;
}
- //todo show announcement
+ 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){
+ 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;
}
@@ -102,7 +107,7 @@ public class ChatRulesHandler {
return true;
}
private static String trimItemColor(String str) {
- if (str.isEmpty()) return str;
+ if (str.isBlank()) return str;
return str.replaceAll("§[0-9a-g]", "");
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/Boulder.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/Boulder.java
new file mode 100644
index 00000000..878c8d0a
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/Boulder.java
@@ -0,0 +1,221 @@
+package de.hysky.skyblocker.skyblock.dungeon.puzzle.boulder;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.dungeon.puzzle.DungeonPuzzle;
+import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager;
+import de.hysky.skyblocker.skyblock.dungeon.secrets.Room;
+import de.hysky.skyblocker.utils.render.RenderHelper;
+import de.hysky.skyblocker.utils.render.title.Title;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockState;
+import net.minecraft.block.Blocks;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.world.ClientWorld;
+import net.minecraft.util.DyeColor;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Box;
+import net.minecraft.util.math.Vec3d;
+import net.minecraft.world.BlockView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class Boulder extends DungeonPuzzle {
+ private static final Logger LOGGER = LoggerFactory.getLogger(Boulder.class.getName());
+ private static final Boulder INSTANCE = new Boulder();
+ private static final float[] RED_COLOR_COMPONENTS = DyeColor.RED.getColorComponents();
+ private static final float[] ORANGE_COLOR_COMPONENTS = DyeColor.ORANGE.getColorComponents();
+ private static final int BASE_Y = 65;
+ static Vec3d[] linePoints;
+ static Box boundingBox;
+
+ private Boulder() {
+ super("boulder", "boxes-room");
+ }
+
+ public static void init() {
+ }
+
+ @Override
+ public void tick(MinecraftClient client) {
+
+ if (!shouldSolve() || !SkyblockerConfigManager.get().locations.dungeons.solveBoulder || client.world == null || !DungeonManager.isCurrentRoomMatched()) {
+ return;
+ }
+
+ Room room = DungeonManager.getCurrentRoom();
+
+ BlockPos chestPos = new BlockPos(15, BASE_Y, 29);
+ BlockPos start = new BlockPos(25, BASE_Y, 25);
+ BlockPos end = new BlockPos(5, BASE_Y, 8);
+ // Create a target BoulderObject for the puzzle
+ BoulderObject target = new BoulderObject(chestPos.getX(), chestPos.getX(), chestPos.getZ(), "T");
+ // Create a BoulderBoard representing the puzzle's grid
+ BoulderBoard board = new BoulderBoard(8, 7, target);
+
+ // Populate the BoulderBoard grid with BoulderObjects based on block types in the room
+ int column = 1;
+ for (int z = start.getZ(); z > end.getZ(); z--) {
+ int row = 0;
+ for (int x = start.getX(); x > end.getX(); x--) {
+ if (Math.abs(start.getX() - x) % 3 == 1 && Math.abs(start.getZ() - z) % 3 == 1) {
+ String blockType = getBlockType(client.world, x, BASE_Y, z);
+ board.placeObject(column, row, new BoulderObject(x, BASE_Y, z, blockType));
+ row++;
+ }
+ }
+ if (row == board.getWidth()) {
+ column++;
+ }
+ }
+
+ // Generate initial game states for the A* solver
+ char[][] boardArray = board.getBoardCharArray();
+ List<BoulderSolver.GameState> initialStates = Arrays.asList(
+ new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 0),
+ new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 1),
+ new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 2),
+ new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 3),
+ new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 4),
+ new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 5),
+ new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 6)
+ );
+
+ // Solve the puzzle using the A* algorithm
+ List<int[]> solution = BoulderSolver.aStarSolve(initialStates);
+
+ if (solution != null) {
+ linePoints = new Vec3d[solution.size()];
+ int index = 0;
+ // Convert solution coordinates to Vec3d points for rendering
+ for (int[] coord : solution) {
+ int x = coord[0];
+ int y = coord[1];
+ // Convert relative coordinates to actual coordinates
+ linePoints[index++] = Vec3d.ofCenter(room.relativeToActual(board.getObject3DPosition(x, y)));
+ }
+
+ BlockPos button = null;
+ if (linePoints != null && linePoints.length > 0) {
+ // Check for buttons along the path of the solution
+ for (int i = 0; i < linePoints.length - 1; i++) {
+ Vec3d point1 = linePoints[i];
+ Vec3d point2 = linePoints[i + 1];
+ button = checkForButtonBlocksOnLine(client.world, point1, point2);
+ if (button != null) {
+ // If a button is found, calculate its bounding box
+ boundingBox = getBlockBoundingBox(client.world, button);
+ break;
+ }
+ }
+ if (button == null){
+ // If no button is found along the path the puzzle is solved; reset the puzzle
+ reset();
+ }
+ }
+ } else {
+ // If no solution is found, display a title message and reset the puzzle
+ Title title = new Title("skyblocker.dungeons.puzzle.boulder.noSolution", Formatting.GREEN);
+ RenderHelper.displayInTitleContainerAndPlaySound(title, 15);
+ reset();
+ }
+ }
+
+ /**
+ * Retrieves the type of block at the specified position in the world.
+ * If the block is Birch or Jungle plank, it will return "B"; otherwise, it will return ".".
+ *
+ * @param world The client world.
+ * @param x The x-coordinate of the block.
+ * @param y The y-coordinate of the block.
+ * @param z The z-coordinate of the block.
+ * @return The type of block at the specified position.
+ */
+ public static String getBlockType(ClientWorld world, int x, int y, int z) {
+ Block block = world.getBlockState(DungeonManager.getCurrentRoom().relativeToActual(new BlockPos(x, y, z))).getBlock();
+ return (block == Blocks.BIRCH_PLANKS || block == Blocks.JUNGLE_PLANKS) ? "B" : ".";
+ }
+
+ /**
+ * Checks for blocks along the line between two points in the world.
+ * Returns the position of a block if it found a button on the line, if any.
+ *
+ * @param world The client world.
+ * @param point1 The starting point of the line.
+ * @param point2 The ending point of the line.
+ * @return The position of the block found on the line, or null if no block is found.
+ */
+ private static BlockPos checkForButtonBlocksOnLine(ClientWorld world, Vec3d point1, Vec3d point2) {
+ double x1 = point1.getX();
+ double y1 = point1.getY() + 1;
+ double z1 = point1.getZ();
+
+ double x2 = point2.getX();
+ double y2 = point2.getY() + 1;
+ double z2 = point2.getZ();
+
+ int steps = (int) Math.max(Math.abs(x2 - x1), Math.max(Math.abs(y2 - y1), Math.abs(z2 - z1)));
+
+ double xStep = (x2 - x1) / steps;
+ double yStep = (y2 - y1) / steps;
+ double zStep = (z2 - z1) / steps;
+
+
+ for (int step = 0; step <= steps; step++) {
+ double currentX = x1 + step * xStep;
+ double currentY = y1 + step * yStep;
+ double currentZ = z1 + step * zStep;
+
+ BlockPos blockPos = BlockPos.ofFloored(currentX, currentY, currentZ);
+ Block block = world.getBlockState(blockPos).getBlock();
+
+ if (block == Blocks.STONE_BUTTON) {
+ return blockPos;
+ }
+
+ }
+ return null;
+ }
+
+ /**
+ * Retrieves the bounding box of a block in the world.
+ *
+ * @param world The client world.
+ * @param pos The position of the block.
+ * @return The bounding box of the block.
+ */
+ public static Box getBlockBoundingBox(BlockView world, BlockPos pos) {
+ BlockState blockState = world.getBlockState(pos);
+ return blockState.getOutlineShape(world, pos).getBoundingBox().offset(pos);
+ }
+
+ @Override
+ public void render(WorldRenderContext context) {
+ if (!shouldSolve() || !SkyblockerConfigManager.get().locations.dungeons.solveBoulder || !DungeonManager.isCurrentRoomMatched())
+ return;
+ float alpha = 1.0f;
+ float lineWidth = 5.0f;
+
+ if (linePoints != null && linePoints.length > 0) {
+ for (int i = 0; i < linePoints.length - 1; i++) {
+ Vec3d startPoint = linePoints[i];
+ Vec3d endPoint = linePoints[i + 1];
+ RenderHelper.renderLinesFromPoints(context, new Vec3d[]{startPoint, endPoint}, ORANGE_COLOR_COMPONENTS, alpha, lineWidth, true);
+ }
+ if (boundingBox != null) {
+ RenderHelper.renderOutline(context, boundingBox, RED_COLOR_COMPONENTS, 5, false);
+ }
+ }
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ linePoints = null;
+ boundingBox = null;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderBoard.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderBoard.java
new file mode 100644
index 00000000..d29097e4
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderBoard.java
@@ -0,0 +1,129 @@
+package de.hysky.skyblocker.skyblock.dungeon.puzzle.boulder;
+
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Direction;
+
+/**
+ * Represents the game board for the Boulder puzzle, managing the grid of BoulderObjects.
+ * This class handles operations such as placing objects on the board, retrieving objects,
+ * and generating a character representation of the game board.
+ */
+public class BoulderBoard {
+ private final int height;
+ private final int width;
+ private final BoulderObject[][] grid;
+
+ /**
+ * Constructs a BoulderBoard with the specified height, width, and target BoulderObject.
+ *
+ * @param height The height of the board.
+ * @param width The width of the board.
+ * @param target The target BoulderObject that needs to be reached to solve the puzzle.
+ */
+ public BoulderBoard(int height, int width, BoulderObject target) {
+ this.height = height;
+ this.width = width;
+ this.grid = new BoulderObject[height][width];
+
+ int offsetX = target.x() - 23;
+ int y = 65;
+
+ for (int z = 0; z < width; z++) {
+ if (z == width / 2) {
+ grid[0][z] = target;
+ } else {
+ grid[0][z] = new BoulderObject(offsetX, y, z, "B");
+ }
+ grid[height - 1][z] = new BoulderObject(24 - (3 * z), y, 6, "P");
+ }
+ }
+
+ /**
+ * Retrieves the BoulderObject at the specified position on the board.
+ *
+ * @param x The x-coordinate of the position.
+ * @param y The y-coordinate of the position.
+ * @return The BoulderObject at the specified position, or null if no object is present.
+ */
+ public BoulderObject getObjectAtPosition(int x, int y) {
+ return isValidPosition(x, y) ? grid[x][y] : null;
+ }
+
+ /**
+ * Retrieves the 3D position of the BoulderObject at the specified position on the board.
+ *
+ * @param x The x-coordinate of the position.
+ * @param y The y-coordinate of the position.
+ * @return The BlockPos representing the 3D position of the BoulderObject,
+ * or null if no object is present at the specified position.
+ */
+ public BlockPos getObject3DPosition(int x, int y) {
+ BoulderObject object = getObjectAtPosition(x, y);
+ return (object != null) ? object.get3DPosition().offset(Direction.Axis.Y, -1) : null;
+ }
+
+ /**
+ * Places a BoulderObject at the specified position on the board.
+ *
+ * @param x The x-coordinate of the position.
+ * @param y The y-coordinate of the position.
+ * @param object The BoulderObject to place on the board.
+ */
+ public void placeObject(int x, int y, BoulderObject object) {
+ grid[x][y] = object;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ /**
+ * Checks whether the specified position is valid within the bounds of the game board.
+ *
+ * @param x The x-coordinate of the position to check.
+ * @param y The y-coordinate of the position to check.
+ * @return {@code true} if the position is valid within the bounds of the board, {@code false} otherwise.
+ */
+ private boolean isValidPosition(int x, int y) {
+ return x >= 0 && y >= 0 && x < height && y < width;
+ }
+
+ /**
+ * Generates a character array representation of the game board.
+ * Each character represents a type of BoulderObject or an empty space.
+ *
+ * @return A 2D character array representing the game board.
+ */
+ public char[][] getBoardCharArray() {
+ char[][] boardCharArray = new char[height][width];
+ for (int x = 0; x < height; x++) {
+ for (int y = 0; y < width; y++) {
+ BoulderObject boulderObject = grid[x][y];
+ boardCharArray[x][y] = (boulderObject != null) ? boulderObject.type().charAt(0) : '.';
+ }
+ }
+ return boardCharArray;
+ }
+
+ /**
+ * Prints the current state of the game board to the console.
+ * Each character represents a type of BoulderObject or an empty space.
+ */
+ public String boardToString() {
+ StringBuilder sb = new StringBuilder();
+ for (int x = 0; x < height; x++) {
+ for (int y = 0; y < width; y++) {
+ BoulderObject boulderObject = grid[x][y];
+ String displayChar = (boulderObject != null) ? boulderObject.type() : ".";
+ sb.append(displayChar);
+ }
+ sb.append("\n");
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderObject.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderObject.java
new file mode 100644
index 00000000..645dd456
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderObject.java
@@ -0,0 +1,9 @@
+package de.hysky.skyblocker.skyblock.dungeon.puzzle.boulder;
+
+import net.minecraft.util.math.BlockPos;
+
+public record BoulderObject(int x, int y, int z, String type) {
+ public BlockPos get3DPosition() {
+ return new BlockPos(x, y, z);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderSolver.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderSolver.java
new file mode 100644
index 00000000..f407ce8a
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderSolver.java
@@ -0,0 +1,202 @@
+package de.hysky.skyblocker.skyblock.dungeon.puzzle.boulder;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.PriorityQueue;
+import java.util.Set;
+import it.unimi.dsi.fastutil.Pair;
+
+/**
+ * A utility class that provides methods to solve the Boulder puzzle using the A* search algorithm.
+ * The BoulderSolver class is responsible for finding the shortest path from the starting position
+ * to the target position by exploring possible moves and evaluating their costs.
+ */
+public class BoulderSolver {
+
+ /**
+ * Finds the shortest path to solve the Boulder puzzle using the A* search algorithm.
+ *
+ * @param initialStates The list of initial game states from which to start the search.
+ * @return A list of coordinates representing the shortest path to solve the puzzle,
+ * or null if no solution is found within the maximum number of iterations.
+ */
+ public static List<int[]> aStarSolve(List<GameState> initialStates) {
+ Set<GameState> visited = new HashSet<>();
+ PriorityQueue<Pair<GameState, List<int[]>>> queue = new PriorityQueue<>(new AStarComparator());
+
+ for (GameState initialState : initialStates) {
+ queue.add(Pair.of(initialState, new ArrayList<>()));
+ }
+
+ int maxIterations = 10000;
+ int iterations = 0;
+
+ while (!queue.isEmpty() && iterations < maxIterations) {
+ Pair<GameState, List<int[]>> pair = queue.poll();
+ GameState state = pair.left();
+ List<int[]> path = pair.right();
+
+ if (state.isSolved()) {
+ return path;
+ }
+
+ if (visited.contains(state)) {
+ continue;
+ }
+ visited.add(state);
+
+ int[] currentCoord = {state.playerX, state.playerY};
+ path.add(currentCoord);
+
+ for (int[] direction : new int[][]{{-1, 0}, {0, -1}, {0, 1}, {1, 0}}) {
+ GameState newState = new GameState(state.grid, state.playerX, state.playerY);
+ if (newState.movePlayer(direction[0], direction[1])) {
+ queue.add(Pair.of(newState, new ArrayList<>(path)));
+ }
+ }
+ iterations++;
+ }
+
+ return null;
+ }
+
+ /**
+ * A comparator used to compare game states based on their A* search cost.
+ * States with lower costs are prioritized for exploration.
+ */
+ private static class AStarComparator implements Comparator<Pair<GameState, List<int[]>>> {
+ /**
+ * Compares two pairs of game states and their associated paths based on their costs.
+ *
+ * @param a The first pair to compare.
+ * @param b The second pair to compare.
+ * @return A negative integer if a has a lower cost than b,
+ * a positive integer if a has a higher cost than b,
+ * or zero if both have the same cost.
+ */
+ @Override
+ public int compare(Pair<GameState, List<int[]>> a, Pair<GameState, List<int[]>> b) {
+ int costA = a.right().size() + a.left().heuristic();
+ int costB = b.right().size() + b.left().heuristic();
+ return Integer.compare(costA, costB);
+ }
+ }
+
+ /**
+ * Represents the game state for the Boulder puzzle, including the current grid configuration
+ * and the position of the theoretical player.
+ */
+ public static class GameState {
+ private final char[][] grid;
+ private int playerX;
+ private int playerY;
+
+ /**
+ * Constructs a new game state with the specified grid and theoretical player position.
+ *
+ * @param grid The grid representing the Boulder puzzle configuration.
+ * @param playerX The x-coordinate of the player's position.
+ * @param playerY The y-coordinate of the player's position.
+ */
+ public GameState(char[][] grid, int playerX, int playerY) {
+ this.grid = copyGrid(grid);
+ this.playerX = playerX;
+ this.playerY = playerY;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null || getClass() != obj.getClass()) return false;
+ GameState gameState = (GameState) obj;
+ return Arrays.deepEquals(grid, gameState.grid) && playerX == gameState.playerX && playerY == gameState.playerY;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Arrays.deepHashCode(grid);
+ result = 31 * result + playerX;
+ result = 31 * result + playerY;
+ return result;
+ }
+
+ /**
+ * Moves the theoretical player in the specified direction and updates the game state accordingly.
+ *
+ * @param dx The change in x-coordinate (horizontal movement).
+ * @param dy The change in y-coordinate (vertical movement).
+ * @return true if the move is valid and the player is moved, false otherwise.
+ */
+ public boolean movePlayer(int dx, int dy) {
+ int newX = playerX + dx;
+ int newY = playerY + dy;
+
+ if (isValidPosition(newX, newY)) {
+ if (grid[newX][newY] == 'B') {
+ int nextToBoxX = newX + dx;
+ int nextToBoxY = newY + dy;
+ if (isValidPosition(nextToBoxX, nextToBoxY) && grid[nextToBoxX][nextToBoxY] == '.') {
+ grid[newX][newY] = '.';
+ grid[nextToBoxX][nextToBoxY] = 'B';
+ playerX = newX;
+ playerY = newY;
+ return true;
+ }
+ } else {
+ playerX = newX;
+ playerY = newY;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isValidPosition(int x, int y) {
+ return x >= 0 && y >= 0 && x < grid.length && y < grid[0].length;
+ }
+
+ /**
+ * Checks if the puzzle is solved, i.e., if the player is positioned on the target BoulderObject.
+ *
+ * @return true if the theoretical puzzle is solved, false otherwise.
+ */
+ public boolean isSolved() {
+ return grid[playerX][playerY] == 'T';
+ }
+
+ /**
+ * Calculates the heuristic value for the current game state, representing the estimated
+ * distance from the player's position to the target BoulderObject.
+ *
+ * @return The heuristic value for the current game state.
+ */
+ public int heuristic() {
+ // should be improved maybe prioritize empty path first
+ for (int i = 0; i < grid.length; i++) {
+ for (int j = 0; j < grid[0].length; j++) {
+ if (grid[i][j] == 'T') {
+ return Math.abs(playerX - i) + Math.abs(playerY - j);
+ }
+ }
+ }
+ return Integer.MAX_VALUE;
+ }
+
+ /**
+ * Creates a deep copy of the grid array to avoid modifying the original grid.
+ *
+ * @param original The original grid array to copy.
+ * @return A deep copy of the original grid array.
+ */
+ private char[][] copyGrid(char[][] original) {
+ char[][] copy = new char[original.length][];
+ for (int i = 0; i < original.length; i++) {
+ copy[i] = original[i].clone();
+ }
+ return copy;
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java
index 08dcde7d..5913a3c6 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java
@@ -37,7 +37,7 @@ public class DwarvenHud {
"(?:Lava Springs|Cliffside Veins|Rampart's Quarry|Upper Mines|Royal Mines) Mithril",
"(?:Lava Springs|Cliffside Veins|Rampart's Quarry|Upper Mines|Royal Mines) Titanium",
"Goblin Raid",
- "(?:Powder Ghast|Star Sentry) Puncher",
+ "(?:Powder Ghast|Star Sentry|Treasure Hoarder) Puncher",
"(?<!Lucky )Raffle",
"Lucky Raffle",
"2x Mithril Powder Collector",
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudConfigScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudConfigScreen.java
new file mode 100644
index 00000000..2502afd7
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudConfigScreen.java
@@ -0,0 +1,35 @@
+package de.hysky.skyblocker.skyblock.end;
+
+import de.hysky.skyblocker.config.HudConfigScreen;
+import de.hysky.skyblocker.config.SkyblockerConfig;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.text.Text;
+
+public class EndHudConfigScreen extends HudConfigScreen {
+ public EndHudConfigScreen(Screen parent) {
+ super(Text.literal("End HUD Config"), EndHudWidget.INSTANCE, parent);
+ }
+
+ @Override
+ protected int[] getPosFromConfig(SkyblockerConfig config) {
+ return new int[]{
+ config.locations.end.x,
+ config.locations.end.y,
+ };
+ }
+
+ @Override
+ protected void savePos(SkyblockerConfig configManager, int x, int y) {
+ configManager.locations.end.x = x;
+ configManager.locations.end.y = y;
+ }
+
+ @Override
+ protected void renderWidget(DrawContext context, int x, int y) {
+ EndHudWidget.INSTANCE.setX(x);
+ EndHudWidget.INSTANCE.setY(y);
+ EndHudWidget.INSTANCE.render(context, SkyblockerConfigManager.get().locations.end.enableBackground);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudWidget.java
new file mode 100644
index 00000000..59a637dd
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudWidget.java
@@ -0,0 +1,65 @@
+package de.hysky.skyblocker.skyblock.end;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.tabhud.widget.Widget;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent;
+import net.minecraft.enchantment.Enchantments;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.text.NumberFormat;
+
+public class EndHudWidget extends Widget {
+ private static final MutableText TITLE = Text.literal("The End").formatted(Formatting.LIGHT_PURPLE, Formatting.BOLD);
+
+ public static final EndHudWidget INSTANCE = new EndHudWidget(TITLE, Formatting.DARK_PURPLE.getColorValue());
+
+ public EndHudWidget(MutableText title, Integer colorValue) {
+ super(title, colorValue);
+ this.setX(5);
+ this.setY(5);
+ this.update();
+ }
+
+ private static final ItemStack ENDERMAN_HEAD = new ItemStack(Items.PLAYER_HEAD);
+ private static final ItemStack POPPY = new ItemStack(Items.POPPY);
+
+ static {
+ ENDERMAN_HEAD.getOrCreateNbt().putString("SkullOwner", "MHF_Enderman");
+ POPPY.addEnchantment(Enchantments.INFINITY, 1);
+
+ INSTANCE.setX(SkyblockerConfigManager.get().locations.end.x);
+ INSTANCE.setY(SkyblockerConfigManager.get().locations.end.y);
+ }
+
+
+ @Override
+ public void updateContent() {
+ // Zealots
+ addComponent(new IcoTextComponent(ENDERMAN_HEAD, Text.literal("Zealots").formatted(Formatting.BOLD)));
+ addComponent(new PlainTextComponent(Text.translatable("skyblocker.end.hud.zealotsSinceLastEye", TheEnd.zealotsSinceLastEye)));
+ addComponent(new PlainTextComponent(Text.translatable("skyblocker.end.hud.zealotsTotalKills", TheEnd.zealotsKilled)));
+ NumberFormat instance = NumberFormat.getInstance();
+ instance.setMinimumFractionDigits(0);
+ instance.setMaximumFractionDigits(2);
+ String avg = TheEnd.eyes == 0 ? "???" : instance.format((float)TheEnd.zealotsKilled / TheEnd.eyes);
+ addComponent(new PlainTextComponent(Text.translatable("skyblocker.end.hud.avgKillsPerEye", avg)));
+
+ // Endstone protector
+ addComponent(new IcoTextComponent(POPPY, Text.literal("Endstone Protector").formatted(Formatting.BOLD)));
+ if (TheEnd.stage == 5) {
+ addComponent(new PlainTextComponent(Text.translatable("skyblocker.end.hud.stage", "IMMINENT")));
+ } else {
+ addComponent(new PlainTextComponent(Text.translatable("skyblocker.end.hud.stage", String.valueOf(TheEnd.stage))));
+ }
+ if (TheEnd.currentProtectorLocation == null) {
+ addComponent(new PlainTextComponent(Text.translatable("skyblocker.end.hud.location", "?")));
+ } else {
+ addComponent(new PlainTextComponent(Text.translatable("skyblocker.end.hud.location", TheEnd.currentProtectorLocation.name())));
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/end/TheEnd.java b/src/main/java/de/hysky/skyblocker/skyblock/end/TheEnd.java
new file mode 100644
index 00000000..1db27769
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/end/TheEnd.java
@@ -0,0 +1,249 @@
+package de.hysky.skyblocker.skyblock.end;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.Utils;
+import de.hysky.skyblocker.utils.waypoint.Waypoint;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientChunkEvents;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
+import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
+import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
+import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
+import net.fabricmc.fabric.api.event.player.AttackEntityCallback;
+import net.minecraft.block.Blocks;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.decoration.ArmorStandEntity;
+import net.minecraft.entity.mob.EndermanEntity;
+import net.minecraft.text.Text;
+import net.minecraft.util.ActionResult;
+import net.minecraft.util.DyeColor;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Box;
+import net.minecraft.util.math.ChunkPos;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+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 TheEnd {
+ protected static final Logger LOGGER = LoggerFactory.getLogger(TheEnd.class);
+
+ public static Set<UUID> hitZealots = new HashSet<>();
+ public static int zealotsSinceLastEye = 0;
+ public static int zealotsKilled = 0;
+ public static int eyes = 0;
+ /**
+ * needs to be saved?
+ */
+ private static boolean dirty = false;
+ private static String currentProfile = "";
+ private static JsonObject PROFILES_STATS;
+
+ private static final Path FILE = SkyblockerMod.CONFIG_DIR.resolve("end.json");
+ public static List<ProtectorLocation> protectorLocations = List.of(
+ new ProtectorLocation(-649, -219, Text.translatable("skyblocker.end.hud.protectorLocations.left")),
+ new ProtectorLocation(-644, -269, Text.translatable("skyblocker.end.hud.protectorLocations.front")),
+ new ProtectorLocation(-689, -273, Text.translatable("skyblocker.end.hud.protectorLocations.center")),
+ new ProtectorLocation(-727, -284, Text.translatable("skyblocker.end.hud.protectorLocations.back")),
+ new ProtectorLocation(-639, -328, Text.translatable("skyblocker.end.hud.protectorLocations.rightFront")),
+ new ProtectorLocation(-678, -332, Text.translatable("skyblocker.end.hud.protectorLocations.rightBack"))
+ );
+
+ public static ProtectorLocation currentProtectorLocation = null;
+ public static int stage = 0;
+
+ public static void init() {
+ AttackEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> {
+ if (entity instanceof EndermanEntity enderman && isZealot(enderman)) {
+ hitZealots.add(enderman.getUuid());
+ }
+ return ActionResult.PASS;
+ });
+
+
+ HudRenderCallback.EVENT.register((drawContext, tickDelta) -> {
+ if (!Utils.isInTheEnd()) return;
+ if (!SkyblockerConfigManager.get().locations.end.hudEnabled) return;
+
+ EndHudWidget.INSTANCE.render(drawContext, SkyblockerConfigManager.get().locations.end.enableBackground);
+ });
+
+ ClientChunkEvents.CHUNK_LOAD.register((world, chunk) -> {
+ String lowerCase = Utils.getIslandArea().toLowerCase();
+ if (Utils.isInTheEnd() || lowerCase.contains("the end") || lowerCase.contains("dragon's nest")) {
+ ChunkPos pos = chunk.getPos();
+ //
+ Box box = new Box(pos.getStartX(), 0, pos.getStartZ(), pos.getEndX(), 1, pos.getEndZ());
+ locationsLoop: for (ProtectorLocation protectorLocation : protectorLocations) {
+ if (box.contains(protectorLocation.x, 0.5, protectorLocation.z)) {
+ //MinecraftClient.getInstance().player.sendMessage(Text.literal("Checking: ").append(protectorLocation.name));//MinecraftClient.getInstance().player.sendMessage(Text.literal(pos.getStartX() + " " + pos.getStartZ() + " " + pos.getEndX() + " " + pos.getEndZ()));
+ for (int i = 0; i < 5; i++) {
+ if (world.getBlockState(new BlockPos(protectorLocation.x, i+5, protectorLocation.z)).isOf(Blocks.PLAYER_HEAD)) {
+ stage = i + 1;
+ currentProtectorLocation = protectorLocation;
+ EndHudWidget.INSTANCE.update();
+ break locationsLoop;
+ }
+ }
+ }
+ }
+ if (currentProfile.isEmpty()) load(); // Wacky fix for when you join skyblock, and you are directly in the end (profile id isn't parsed yet most of the time)
+ }
+
+
+ });
+ // Reset when changing island
+ // TODO: Replace when a changed island event is added
+ ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> {
+ resetLocation();
+ save();
+ load();
+ });
+ // Save when leaving as well
+ ClientLifecycleEvents.CLIENT_STOPPING.register((client) -> save());
+
+ ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
+ if (Utils.isInTheEnd()) return;
+ String lowerCase = message.getString().toLowerCase();
+ if (lowerCase.contains("tremor") && stage != 0) stage += 1; // TODO: If stage is 0 re-scan.
+ else if (lowerCase.contains("rises from below")) stage = 5;
+ else if (lowerCase.contains("protector down") || lowerCase.contains("has risen")) resetLocation();
+ else return;
+ EndHudWidget.INSTANCE.update();
+ });
+
+ WorldRenderEvents.AFTER_TRANSLUCENT.register(TheEnd::renderWaypoint);
+ ClientLifecycleEvents.CLIENT_STARTED.register((client -> loadFile()));
+ }
+
+ private static void resetLocation() {
+ stage = 0;
+ currentProtectorLocation = null;
+ }
+
+ public static void onEntityDeath(Entity entity) {
+ if (!(entity instanceof EndermanEntity enderman) || !isZealot(enderman)) return;
+ if (hitZealots.contains(enderman.getUuid())) {
+ //MinecraftClient.getInstance().player.sendMessage(Text.literal("You killed a zealot!!!"));
+ if (isSpecialZealot(enderman)) {
+ zealotsSinceLastEye = 0;
+ eyes++;
+ }
+ else zealotsSinceLastEye++;
+ zealotsKilled++;
+ dirty = true;
+ hitZealots.remove(enderman.getUuid());
+ EndHudWidget.INSTANCE.update();
+ }
+ }
+
+ public static boolean isZealot(EndermanEntity enderman) {
+ if (enderman.getName().getString().toLowerCase().contains("zealot")) return true; // Future-proof. If they someday decide to actually rename the entities
+ assert MinecraftClient.getInstance().world != null;
+ List<ArmorStandEntity> entities = MinecraftClient.getInstance().world.getEntitiesByClass(
+ ArmorStandEntity.class,
+ enderman.getDimensions(null).getBoxAt(enderman.getPos()).expand(1),
+ armorStandEntity -> armorStandEntity.getName().getString().toLowerCase().contains("zealot"));
+ if (entities.isEmpty()) {
+ return false;
+ }
+ return entities.get(0).getName().getString().toLowerCase().contains("zealot");
+ }
+
+ public static boolean isSpecialZealot(EndermanEntity enderman) {
+ return isZealot(enderman) && enderman.getCarriedBlock() != null && enderman.getCarriedBlock().isOf(Blocks.END_PORTAL_FRAME);
+ }
+
+ /**
+ * Loads if needed
+ */
+ public static void load() {
+ if (!Utils.isOnSkyblock() || Utils.getProfileId().isEmpty()) return;
+ String id = MinecraftClient.getInstance().getSession().getUuidOrNull().toString().replaceAll("-", "");
+ String profile = Utils.getProfileId();
+ if (!profile.equals(currentProfile) && PROFILES_STATS != null) {
+ currentProfile = profile;
+ JsonElement jsonElement = PROFILES_STATS.get(id);
+ if (jsonElement == null) return;
+ JsonElement jsonElement1 = jsonElement.getAsJsonObject().get(profile);
+ if (jsonElement1 == null) return;
+ zealotsKilled = jsonElement1.getAsJsonObject().get("totalZealotKills").getAsInt();
+ zealotsSinceLastEye = jsonElement1.getAsJsonObject().get("zealotsSinceLastEye").getAsInt();
+ eyes = jsonElement1.getAsJsonObject().get("eyes").getAsInt();
+ EndHudWidget.INSTANCE.update();
+ }
+ }
+
+ private static void loadFile() {
+ CompletableFuture.runAsync(() -> {
+ try (BufferedReader reader = Files.newBufferedReader(FILE)) {
+ PROFILES_STATS = SkyblockerMod.GSON.fromJson(reader, JsonObject.class);
+ LOGGER.debug("[Skyblocker End] Loaded end stats");
+ } catch (NoSuchFileException ignored) {
+ PROFILES_STATS = new JsonObject();
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker End] Failed to load end stats", e);
+ }
+ });
+ }
+
+ /**
+ * Saves if dirty
+ */
+ public static void save() {
+ if (dirty && PROFILES_STATS != null) {
+ String uuid = MinecraftClient.getInstance().getSession().getUuidOrNull().toString().replaceAll("-", "");
+ JsonObject jsonObject = PROFILES_STATS.getAsJsonObject(uuid);
+ if (jsonObject == null) {
+ PROFILES_STATS.add(uuid, new JsonObject());
+ jsonObject = PROFILES_STATS.getAsJsonObject(uuid);
+ }
+
+ jsonObject.add(currentProfile, new JsonObject());
+ JsonElement jsonElement1 = jsonObject.get(currentProfile);
+
+ jsonElement1.getAsJsonObject().addProperty("totalZealotKills", zealotsKilled);
+ jsonElement1.getAsJsonObject().addProperty("zealotsSinceLastEye", zealotsSinceLastEye);
+ jsonElement1.getAsJsonObject().addProperty("eyes", eyes);
+
+ if (Utils.isOnSkyblock()) {
+ CompletableFuture.runAsync(TheEnd::performSave);
+ } else {
+ performSave();
+ }
+ }
+ }
+
+ private static void performSave() {
+ try (BufferedWriter writer = Files.newBufferedWriter(FILE)) {
+ SkyblockerMod.GSON.toJson(PROFILES_STATS, writer);
+ LOGGER.info("[Skyblocker End] Saved end stats");
+ dirty = false;
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker End] Failed to save end stats", e);
+ }
+ }
+
+ private static void renderWaypoint(WorldRenderContext context) {
+ if (!SkyblockerConfigManager.get().locations.end.waypoint) return;
+ if (currentProtectorLocation == null || stage != 5) return;
+ currentProtectorLocation.waypoint().render(context);
+ }
+
+ public record ProtectorLocation(int x, int z, Text name, Waypoint waypoint) {
+ public ProtectorLocation(int x, int z, Text name) {
+ this(x, z, name, new Waypoint(new BlockPos(x, 0, z), Waypoint.Type.WAYPOINT, DyeColor.MAGENTA.getColorComponents()));
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java b/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java
index d0d58606..90513a4b 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java
@@ -1,12 +1,14 @@
package de.hysky.skyblocker.skyblock.entity;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.end.TheEnd;
import de.hysky.skyblocker.skyblock.dungeon.LividColor;
import de.hysky.skyblocker.utils.SlayerUtils;
import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.render.culling.OcclusionCulling;
import net.minecraft.entity.Entity;
import net.minecraft.entity.decoration.ArmorStandEntity;
+import net.minecraft.entity.mob.EndermanEntity;
import net.minecraft.entity.passive.BatEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
@@ -71,6 +73,9 @@ public class MobGlow {
&& isNukekubiHead(armorStandEntity);
}
+ // Special Zelot
+ if (entity instanceof EndermanEntity enderman && TheEnd.isSpecialZealot(enderman)) return true;
+
return false;
}
@@ -92,6 +97,7 @@ public class MobGlow {
default -> 0xf57738;
};
}
+ if (entity instanceof EndermanEntity enderman && TheEnd.isSpecialZealot(enderman)) return Formatting.RED.getColorValue();
// copypaste nukekebi head logic
if (entity instanceof ArmorStandEntity armorStandEntity && isNukekubiHead(armorStandEntity)) return 0x990099;
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 e4e18f8b..19f2e6fd 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
@@ -140,6 +140,18 @@ public class ItemTooltip {
}
}
+ if (SkyblockerConfigManager.get().general.dungeonQuality) {
+ NbtCompound ea = ItemUtils.getExtraAttributes(stack);
+ if (ea != null && ea.contains("baseStatBoostPercentage")) {
+ int baseStatBoostPercentage = ea.getInt("baseStatBoostPercentage");
+ if (baseStatBoostPercentage == 50) {
+ lines.add(Text.literal(String.format("%-17s", "Item Quality:") + baseStatBoostPercentage + "/50").formatted(Formatting.RED).formatted(Formatting.BOLD));
+ } else {
+ lines.add(Text.literal(String.format("%-21s", "Item Quality:") + baseStatBoostPercentage + "/50").formatted(Formatting.BLUE));
+ }
+ }
+ }
+
if (TooltipInfoType.MOTES.isTooltipEnabledAndHasOrNullWarning(internalID)) {
lines.add(Text.literal(String.format("%-20s", "Motes Price:"))
.formatted(Formatting.LIGHT_PURPLE)
@@ -282,7 +294,6 @@ public class ItemTooltip {
return internalName;
}
-
private static Text getCoinsMessage(double price, int count) {
// Format the price string once
String priceString = String.format(Locale.ENGLISH, "%1$,.1f", price);
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/TableComponent.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/TableComponent.java
index dbc0bf55..2d813b56 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/TableComponent.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/TableComponent.java
@@ -25,11 +25,7 @@ public class TableComponent extends Component {
// pad extra to add a vertical line later
this.cellW = Math.max(this.cellW, c.width + PAD_S + PAD_L);
-
- // assume all rows are equally high so overwriting doesn't matter
- // if this wasn't the case, drawing would need more math
- // not doing any of that if it's not needed
- this.cellH = c.height + PAD_S;
+ this.cellH = Math.max(c.height + PAD_S, cellH);
this.width = this.cellW * this.cols;
this.height = (this.cellH * this.rows) - PAD_S / 2;
@@ -40,8 +36,9 @@ public class TableComponent extends Component {
public void render(DrawContext context, int xpos, int ypos) {
for (int x = 0; x < cols; x++) {
for (int y = 0; y < rows; y++) {
- if (comps[x][y] != null) {
- comps[x][y].render(context, xpos + (x * cellW), ypos + y * cellH);
+ Component comp = comps[x][y];
+ if (comp != null) {
+ comp.render(context, xpos + (x * cellW), ypos + y * cellH + (cellH / 2 - comp.height / 2));
}
}
// add a line before the col if we're not drawing the first one