From 9c106069c2775cafc43da402348d7f35d3a2d67b Mon Sep 17 00:00:00 2001
From: viciscat <51047087+viciscat@users.noreply.github.com>
Date: Thu, 15 Feb 2024 21:13:19 +0100
Subject: [PATCH] Add End HUD Widget (#524)
---
.../de/hysky/skyblocker/SkyblockerMod.java | 2 +
.../skyblocker/config/HudConfigScreen.java | 83 ++++++
.../skyblocker/config/SkyblockerConfig.java | 21 ++
.../config/categories/LocationsCategory.java | 51 +++-
.../mixin/ClientPlayNetworkHandlerMixin.java | 16 +-
.../skyblock/end/EndHudConfigScreen.java | 35 +++
.../skyblocker/skyblock/end/EndHudWidget.java | 65 +++++
.../hysky/skyblocker/skyblock/end/TheEnd.java | 249 ++++++++++++++++++
.../skyblocker/skyblock/entity/MobGlow.java | 6 +
.../assets/skyblocker/lang/en_us.json | 19 ++
10 files changed, 537 insertions(+), 10 deletions(-)
create mode 100644 src/main/java/de/hysky/skyblocker/config/HudConfigScreen.java
create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/end/EndHudConfigScreen.java
create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/end/EndHudWidget.java
create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/end/TheEnd.java
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
index 71623ea7c1..b76e8b531c 100644
--- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
+++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
@@ -17,6 +17,7 @@
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;
@@ -121,6 +122,7 @@ public void onInitializeClient() {
FireFreezeStaffTimer.init();
GuardianHealth.init();
TheRift.init();
+ TheEnd.init();
SearchOverManager.init();
TitleContainer.init();
ScreenMaster.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 0000000000..c33e2f54f9
--- /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
+ *
+ * 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 175c3bdf51..826495fbd2 100644
--- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
@@ -651,6 +651,9 @@ public static class Locations {
@SerialEntry
public Rift rift = new Rift();
+ @SerialEntry
+ public TheEnd end = new TheEnd();
+
@SerialEntry
public SpidersDen spidersDen = new SpidersDen();
@@ -1042,6 +1045,24 @@ public static class Rift {
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();
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 9bdcf2e992..75c83a9b75 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 @@
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 static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig
.build())
.build())
+ // The end
+ .group(OptionGroup.createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.end"))
+ .collapsed(false)
+ .option(Option.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.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.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/mixin/ClientPlayNetworkHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java
index 1f56fe3412..4c41421207 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.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/end/EndHudConfigScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudConfigScreen.java
new file mode 100644
index 0000000000..2502afd7af
--- /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 0000000000..59a637ddcb
--- /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 0000000000..1db277694c
--- /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 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 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 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 d0d58606f4..90513a4b67 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 static boolean shouldMobGlow(Entity entity) {
&& isNukekubiHead(armorStandEntity);
}
+ // Special Zelot
+ if (entity instanceof EndermanEntity enderman && TheEnd.isSpecialZealot(enderman)) return true;
+
return false;
}
@@ -92,6 +97,7 @@ public static int getGlowColor(Entity entity) {
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/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json
index 83d387cf64..4eb3573bbc 100644
--- a/src/main/resources/assets/skyblocker/lang/en_us.json
+++ b/src/main/resources/assets/skyblocker/lang/en_us.json
@@ -329,6 +329,13 @@
"text.autoconfig.skyblocker.option.locations.rift.mcGrubberStacks": "McGrubber Stacks",
"text.autoconfig.skyblocker.option.locations.rift.mcGrubberStacks.@Tooltip": "Used for calculating Motes sell prices.",
+ "text.autoconfig.skyblocker.option.locations.end": "The End",
+ "text.autoconfig.skyblocker.option.locations.end.hudEnabled": "HUD Enabled",
+ "text.autoconfig.skyblocker.option.locations.end.screen": "End HUD Config...",
+ "text.autoconfig.skyblocker.option.locations.end.waypoint": "End Protector Waypoint",
+ "text.autoconfig.skyblocker.option.locations.end.resetName": "Reset stored End stats",
+ "text.autoconfig.skyblocker.option.locations.end.resetText": "Reset",
+
"text.autoconfig.skyblocker.category.messages": "Messages",
"text.autoconfig.skyblocker.option.messages.chatFilterResult.PASS": "Disabled",
"text.autoconfig.skyblocker.option.messages.chatFilterResult.FILTER": "Filter",
@@ -410,6 +417,18 @@
"skyblocker.exotic.glitched": "GLITCHED",
"skyblocker.exotic.exotic": "EXOTIC",
+ "skyblocker.end.hud.zealotsSinceLastEye": "Since last eye: %d",
+ "skyblocker.end.hud.zealotsTotalKills": "Total kills: %d",
+ "skyblocker.end.hud.avgKillsPerEye": "Avg kills per eye: %d",
+ "skyblocker.end.hud.stage": "Stage: %s",
+ "skyblocker.end.hud.location": "Location: %s",
+ "skyblocker.end.hud.protectorLocations.left": "Left",
+ "skyblocker.end.hud.protectorLocations.front": "Front",
+ "skyblocker.end.hud.protectorLocations.center": "Center",
+ "skyblocker.end.hud.protectorLocations.back": "Back",
+ "skyblocker.end.hud.protectorLocations.rightFront": "Right Front",
+ "skyblocker.end.hud.protectorLocations.rightBack": "Right Back",
+
"skyblocker.fishing.reelNow": "Reel in now!",
"skyblocker.rift.healNow": "Heal now!",
"skyblocker.rift.iceNow": "Ice now!",