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!",