aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/me/xmrvizzy/skyblocker/utils
diff options
context:
space:
mode:
authorSpencer <spenceralj@gmail.com>2023-07-10 17:52:19 -0400
committerSpencer <spenceralj@gmail.com>2023-07-10 17:52:19 -0400
commit0f0f322d85c6b5ec40bdf3e569db67bf1252f4bc (patch)
tree4081363c9ecf4c4de324d8bf485b2766175d8d04 /src/main/java/me/xmrvizzy/skyblocker/utils
parent7a223d5a93b26a701911f7606d135296c1d5822c (diff)
parent4e5b4fb480339e303e0b31ab0a3a07c90c3912fc (diff)
downloadSkyblocker-0f0f322d85c6b5ec40bdf3e569db67bf1252f4bc.tar.gz
Skyblocker-0f0f322d85c6b5ec40bdf3e569db67bf1252f4bc.tar.bz2
Skyblocker-0f0f322d85c6b5ec40bdf3e569db67bf1252f4bc.zip
Fix merge conflicts
Diffstat (limited to 'src/main/java/me/xmrvizzy/skyblocker/utils')
-rw-r--r--src/main/java/me/xmrvizzy/skyblocker/utils/FrustumUtils.java5
-rw-r--r--src/main/java/me/xmrvizzy/skyblocker/utils/MessageScheduler.java63
-rw-r--r--src/main/java/me/xmrvizzy/skyblocker/utils/NEURepo.java101
-rw-r--r--src/main/java/me/xmrvizzy/skyblocker/utils/RenderHelper.java86
-rw-r--r--src/main/java/me/xmrvizzy/skyblocker/utils/Scheduler.java96
-rw-r--r--src/main/java/me/xmrvizzy/skyblocker/utils/SlayerUtils.java66
-rw-r--r--src/main/java/me/xmrvizzy/skyblocker/utils/Utils.java182
-rw-r--r--src/main/java/me/xmrvizzy/skyblocker/utils/title/Title.java53
-rw-r--r--src/main/java/me/xmrvizzy/skyblocker/utils/title/TitleContainer.java179
-rw-r--r--src/main/java/me/xmrvizzy/skyblocker/utils/title/TitleContainerConfigScreen.java164
10 files changed, 967 insertions, 28 deletions
diff --git a/src/main/java/me/xmrvizzy/skyblocker/utils/FrustumUtils.java b/src/main/java/me/xmrvizzy/skyblocker/utils/FrustumUtils.java
index 6973aa1e..9ea90c16 100644
--- a/src/main/java/me/xmrvizzy/skyblocker/utils/FrustumUtils.java
+++ b/src/main/java/me/xmrvizzy/skyblocker/utils/FrustumUtils.java
@@ -1,6 +1,7 @@
package me.xmrvizzy.skyblocker.utils;
import me.xmrvizzy.skyblocker.mixin.AccessorWorldRenderer;
+import me.xmrvizzy.skyblocker.mixin.accessor.FrustumInvoker;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.Frustum;
import net.minecraft.util.math.Box;
@@ -14,4 +15,8 @@ public class FrustumUtils {
public static boolean isBoxVisible(Box box) {
return getFrustum().isVisible(box);
}
+
+ public static boolean isVisible(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
+ return ((FrustumInvoker) getFrustum()).isVisible(minX, minY, minZ, maxX, maxY, maxZ);
+ }
} \ No newline at end of file
diff --git a/src/main/java/me/xmrvizzy/skyblocker/utils/MessageScheduler.java b/src/main/java/me/xmrvizzy/skyblocker/utils/MessageScheduler.java
new file mode 100644
index 00000000..ac6aa293
--- /dev/null
+++ b/src/main/java/me/xmrvizzy/skyblocker/utils/MessageScheduler.java
@@ -0,0 +1,63 @@
+package me.xmrvizzy.skyblocker.utils;
+
+import net.minecraft.client.MinecraftClient;
+
+/**
+ * A scheduler for sending chat messages or commands. Use the instance in {@link me.xmrvizzy.skyblocker.SkyblockerMod#messageScheduler SkyblockerMod.messageScheduler}. Do not instantiate this class.
+ */
+@SuppressWarnings("deprecation")
+public class MessageScheduler extends Scheduler {
+ /**
+ * The minimum delay that the server will accept between chat messages.
+ */
+ private static final int MIN_DELAY = 200;
+ /**
+ * The timestamp of the last message send,
+ */
+ private long lastMessage = 0;
+
+ /**
+ * Sends a chat message or command after the minimum cooldown. Prefer this method to send messages or commands to the server.
+ *
+ * @param message the message to send
+ */
+ public void sendMessageAfterCooldown(String message) {
+ if (lastMessage + MIN_DELAY < System.currentTimeMillis()) {
+ sendMessage(message);
+ lastMessage = System.currentTimeMillis();
+ } else {
+ queueMessage(message, 0);
+ }
+ }
+
+ private void sendMessage(String message) {
+ if (MinecraftClient.getInstance().player != null) {
+ MinecraftClient.getInstance().inGameHud.getChatHud().addToMessageHistory(message);
+ if (message.startsWith("/")) {
+ MinecraftClient.getInstance().player.networkHandler.sendCommand(message.substring(1));
+ } else {
+ MinecraftClient.getInstance().player.networkHandler.sendChatMessage(message);
+ }
+ }
+ }
+
+ /**
+ * Queues a chat message or command to send in {@code delay} ticks. Use this method to send messages or commands a set time in the future. The minimum cooldown is still respected.
+ *
+ * @param message the message to send
+ * @param delay the delay before sending the message in ticks
+ */
+ public void queueMessage(String message, int delay) {
+ schedule(() -> sendMessage(message), delay);
+ }
+
+ @Override
+ protected boolean runTask(Runnable task) {
+ if (lastMessage + MIN_DELAY < System.currentTimeMillis()) {
+ task.run();
+ lastMessage = System.currentTimeMillis();
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/me/xmrvizzy/skyblocker/utils/NEURepo.java b/src/main/java/me/xmrvizzy/skyblocker/utils/NEURepo.java
new file mode 100644
index 00000000..29b39aa3
--- /dev/null
+++ b/src/main/java/me/xmrvizzy/skyblocker/utils/NEURepo.java
@@ -0,0 +1,101 @@
+package me.xmrvizzy.skyblocker.utils;
+
+import me.xmrvizzy.skyblocker.SkyblockerMod;
+import me.xmrvizzy.skyblocker.skyblock.itemlist.ItemRegistry;
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.text.Text;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.TransportException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Initializes the NEU repo, which contains item metadata and fairy souls location data. Clones the repo if it does not exist and checks for updates. Use {@link #runAsyncAfterLoad(Runnable)} to run code after the repo is initialized.
+ */
+public class NEURepo {
+ private static final Logger LOGGER = LoggerFactory.getLogger(NEURepo.class);
+ public static final String REMOTE_REPO_URL = "https://github.com/NotEnoughUpdates/NotEnoughUpdates-REPO.git";
+ public static final Path LOCAL_REPO_DIR = SkyblockerMod.CONFIG_DIR.resolve("item-repo");
+ private static final CompletableFuture<Void> REPO_INITIALIZED = initRepository();
+
+ /**
+ * Adds command to update repository manually from ingame.
+ * <p></p>
+ * TODO A button could be added to the settings menu that will trigger this command.
+ */
+ public static void init() {
+ ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) ->
+ dispatcher.register(ClientCommandManager.literal(SkyblockerMod.NAMESPACE)
+ .then(ClientCommandManager.literal("updaterepository").executes(context -> {
+ deleteAndDownloadRepository();
+ return 1;
+ }))));
+ }
+
+ private static CompletableFuture<Void> initRepository() {
+ return CompletableFuture.runAsync(() -> {
+ try {
+ if (Files.isDirectory(NEURepo.LOCAL_REPO_DIR)) {
+ try (Git localRepo = Git.open(NEURepo.LOCAL_REPO_DIR.toFile())) {
+ localRepo.pull().setRebase(true).call();
+ LOGGER.info("[Skyblocker] NEU Repository Updated");
+ }
+ } else {
+ Git.cloneRepository().setURI(REMOTE_REPO_URL).setDirectory(NEURepo.LOCAL_REPO_DIR.toFile()).setBranchesToClone(List.of("refs/heads/master")).setBranch("refs/heads/master").call().close();
+ LOGGER.info("[Skyblocker] NEU Repository Downloaded");
+ }
+ } catch (TransportException e){
+ LOGGER.error("[Skyblocker] Transport operation failed. Most likely unable to connect to the remote NEU repo on github", e);
+ } catch (RepositoryNotFoundException e) {
+ LOGGER.warn("[Skyblocker] Local NEU Repository not found or corrupted, downloading new one", e);
+ deleteAndDownloadRepository();
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker] Encountered unknown exception while initializing NEU Repository", e);
+ }
+ });
+ }
+
+ private static void deleteAndDownloadRepository() {
+ CompletableFuture.runAsync(() -> {
+ try {
+ ItemRegistry.filesImported = false;
+ File dir = NEURepo.LOCAL_REPO_DIR.toFile();
+ recursiveDelete(dir);
+ } catch (Exception ex) {
+ if (MinecraftClient.getInstance().player != null)
+ MinecraftClient.getInstance().player.sendMessage(Text.translatable("skyblocker.updaterepository.failed"), false);
+ return;
+ }
+ initRepository();
+ });
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ private static void recursiveDelete(File dir) {
+ File[] children;
+ if (dir.isDirectory() && !Files.isSymbolicLink(dir.toPath()) && (children = dir.listFiles()) != null) {
+ for (File child : children) {
+ recursiveDelete(child);
+ }
+ }
+ dir.delete();
+ }
+
+ /**
+ * Runs the given runnable after the NEU repo is initialized.
+ * @param runnable the runnable to run
+ * @return a completable future of the given runnable
+ */
+ public static CompletableFuture<Void> runAsyncAfterLoad(Runnable runnable) {
+ return REPO_INITIALIZED.thenRunAsync(runnable);
+ }
+}
diff --git a/src/main/java/me/xmrvizzy/skyblocker/utils/RenderHelper.java b/src/main/java/me/xmrvizzy/skyblocker/utils/RenderHelper.java
new file mode 100644
index 00000000..6fa93735
--- /dev/null
+++ b/src/main/java/me/xmrvizzy/skyblocker/utils/RenderHelper.java
@@ -0,0 +1,86 @@
+package me.xmrvizzy.skyblocker.utils;
+
+import me.x150.renderer.render.Renderer3d;
+import me.xmrvizzy.skyblocker.mixin.accessor.BeaconBlockEntityRendererInvoker;
+import me.xmrvizzy.skyblocker.utils.title.Title;
+import me.xmrvizzy.skyblocker.utils.title.TitleContainer;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.render.block.entity.BeaconBlockEntityRenderer;
+import net.minecraft.sound.SoundEvent;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Vec3d;
+
+import java.awt.*;
+
+public class RenderHelper {
+ private static final Vec3d ONE = new Vec3d(1, 1, 1);
+
+ public static void renderFilledThroughWallsWithBeaconBeam(WorldRenderContext context, BlockPos pos, float[] colorComponents, float alpha) {
+ renderFilledThroughWalls(context, pos, colorComponents, alpha);
+ renderBeaconBeam(context, pos, colorComponents);
+ }
+
+ public static void renderFilledThroughWalls(WorldRenderContext context, BlockPos pos, float[] colorComponents, float alpha) {
+ Renderer3d.renderThroughWalls();
+ renderFilled(context, pos, colorComponents, alpha);
+ Renderer3d.stopRenderThroughWalls();
+ }
+
+ public static void renderFilledIfVisible(WorldRenderContext context, BlockPos pos, float[] colorComponents, float alpha) {
+ if (FrustumUtils.isVisible(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1)) {
+ renderFilled(context, pos, colorComponents, alpha);
+ }
+ }
+
+ public static void renderFilled(WorldRenderContext context, BlockPos pos, float[] colorComponents, float alpha) {
+ Renderer3d.renderFilled(context.matrixStack(), new Color(colorComponents[0], colorComponents[1], colorComponents[2], alpha), Vec3d.of(pos), ONE);
+ }
+
+ public static void renderBeaconBeam(WorldRenderContext context, BlockPos pos, float[] colorComponents) {
+ context.matrixStack().push();
+ context.matrixStack().translate(pos.getX() - context.camera().getPos().x, pos.getY() - context.camera().getPos().y, pos.getZ() - context.camera().getPos().z);
+ BeaconBlockEntityRendererInvoker.renderBeam(context.matrixStack(), context.consumers(), context.tickDelta(), context.world().getTime(), 0, BeaconBlockEntityRenderer.MAX_BEAM_HEIGHT, colorComponents);
+ context.matrixStack().pop();
+ }
+
+ public static void displayTitleAndPlaySound(int stayTicks, int fadeOutTicks, String titleKey, Formatting formatting) {
+ MinecraftClient.getInstance().inGameHud.setTitleTicks(0, stayTicks, fadeOutTicks);
+ MinecraftClient.getInstance().inGameHud.setTitle(Text.translatable(titleKey).formatted(formatting));
+ playNotificationSound();
+ }
+
+ /**
+ * Adds the title to {@link TitleContainer} and {@link #playNotificationSound() plays the notification sound} if the title is not in the {@link TitleContainer} already.
+ * No checking needs to be done on whether the title is in the {@link TitleContainer} already by the caller.
+ *
+ * @param title the title
+ */
+ public static void displayInTitleContainerAndPlaySound(Title title) {
+ if (TitleContainer.addTitle(title)) {
+ playNotificationSound();
+ }
+ }
+
+ /**
+ * Adds the title to {@link TitleContainer} for a set number of ticks and {@link #playNotificationSound() plays the notification sound} if the title is not in the {@link TitleContainer} already.
+ * No checking needs to be done on whether the title is in the {@link TitleContainer} already by the caller.
+ *
+ * @param title the title
+ * @param ticks the number of ticks the title will remain
+ */
+ public static void displayInTitleContainerAndPlaySound(Title title, int ticks) {
+ if (TitleContainer.addTitle(title, ticks)) {
+ playNotificationSound();
+ }
+ }
+
+ private static void playNotificationSound() {
+ if (MinecraftClient.getInstance().player != null) {
+ MinecraftClient.getInstance().player.playSound(SoundEvent.of(new Identifier("entity.experience_orb.pickup")), 100f, 0.1f);
+ }
+ }
+}
diff --git a/src/main/java/me/xmrvizzy/skyblocker/utils/Scheduler.java b/src/main/java/me/xmrvizzy/skyblocker/utils/Scheduler.java
index 16e5b023..7b19e284 100644
--- a/src/main/java/me/xmrvizzy/skyblocker/utils/Scheduler.java
+++ b/src/main/java/me/xmrvizzy/skyblocker/utils/Scheduler.java
@@ -1,60 +1,116 @@
package me.xmrvizzy.skyblocker.utils;
+import me.xmrvizzy.skyblocker.SkyblockerMod;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.screen.Screen;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.PriorityQueue;
+import java.util.function.Supplier;
+/**
+ * A scheduler for running tasks at a later time. Tasks will be run synchronously on the main client thread. Use the instance stored in {@link SkyblockerMod#scheduler}. Do not instantiate this class.
+ */
public class Scheduler {
private static final Logger LOGGER = LoggerFactory.getLogger(Scheduler.class);
- private int currentTick;
- private final PriorityQueue<ScheduledTask> tasks;
+ private int currentTick = 0;
+ private final PriorityQueue<ScheduledTask> tasks = new PriorityQueue<>();
+ /**
+ * Do not instantiate this class. Use {@link SkyblockerMod#scheduler} instead.
+ */
+ @SuppressWarnings("DeprecatedIsStillUsed")
+ @Deprecated
public Scheduler() {
- currentTick = 0;
- tasks = new PriorityQueue<>();
}
+ /**
+ * Schedules a task to run after a delay.
+ *
+ * @param task the task to run
+ * @param delay the delay in ticks
+ */
public void schedule(Runnable task, int delay) {
- if (delay < 0)
+ if (delay < 0) {
LOGGER.warn("Scheduled a task with negative delay");
- ScheduledTask tmp = new ScheduledTask(currentTick + delay, task);
+ }
+ ScheduledTask tmp = new ScheduledTask(task, currentTick + delay);
tasks.add(tmp);
}
+ /**
+ * Schedules a task to run every period ticks.
+ *
+ * @param task the task to run
+ * @param period the period in ticks
+ */
public void scheduleCyclic(Runnable task, int period) {
- if (period <= 0)
+ if (period <= 0) {
LOGGER.error("Attempted to schedule a cyclic task with period lower than 1");
- else
+ } else {
new CyclicTask(task, period).run();
+ }
+ }
+
+ /**
+ * Schedules a screen to open in the next tick. Used in commands to avoid screen immediately closing after the command is executed.
+ *
+ * @param screenSupplier the supplier of the screen to open
+ */
+ public void queueOpenScreen(Supplier<Screen> screenSupplier) {
+ queueOpenScreen(screenSupplier.get());
+ }
+
+ /**
+ * Schedules a screen to open in the next tick. Used in commands to avoid screen immediately closing after the command is executed.
+ *
+ * @param screen the supplier of the screen to open
+ */
+ public void queueOpenScreen(Screen screen) {
+ MinecraftClient.getInstance().send(() -> MinecraftClient.getInstance().setScreen(screen));
}
public void tick() {
currentTick += 1;
ScheduledTask task;
- while ((task = tasks.peek()) != null && task.schedule <= currentTick) {
+ while ((task = tasks.peek()) != null && task.schedule <= currentTick && runTask(task)) {
tasks.poll();
- task.run();
}
}
- private class CyclicTask implements Runnable {
- private final Runnable inner;
- private final int period;
-
- public CyclicTask(Runnable task, int period) {
- this.inner = task;
- this.period = period;
- }
+ /**
+ * Runs the task if able.
+ *
+ * @param task the task to run
+ * @return {@code true} if the task is run, and {@link false} if task is not run.
+ */
+ protected boolean runTask(Runnable task) {
+ task.run();
+ return true;
+ }
+ /**
+ * A task that runs every period ticks. More specifically, this task reschedules itself to run again after period ticks every time it runs.
+ *
+ * @param inner the task to run
+ * @param period the period in ticks
+ */
+ protected record CyclicTask(Runnable inner, int period) implements Runnable {
@Override
public void run() {
- schedule(this, period);
+ SkyblockerMod.getInstance().scheduler.schedule(this, period);
inner.run();
}
}
- private record ScheduledTask(int schedule, Runnable inner) implements Comparable<ScheduledTask>, Runnable {
+ /**
+ * A task that runs at a specific tick, relative to {@link #currentTick}.
+ *
+ * @param inner the task to run
+ * @param schedule the tick to run at
+ */
+ protected record ScheduledTask(Runnable inner, int schedule) implements Comparable<ScheduledTask>, Runnable {
@Override
public int compareTo(ScheduledTask o) {
return schedule - o.schedule;
diff --git a/src/main/java/me/xmrvizzy/skyblocker/utils/SlayerUtils.java b/src/main/java/me/xmrvizzy/skyblocker/utils/SlayerUtils.java
new file mode 100644
index 00000000..6bc09456
--- /dev/null
+++ b/src/main/java/me/xmrvizzy/skyblocker/utils/SlayerUtils.java
@@ -0,0 +1,66 @@
+package me.xmrvizzy.skyblocker.utils;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.decoration.ArmorStandEntity;
+import net.minecraft.scoreboard.Scoreboard;
+import net.minecraft.scoreboard.ScoreboardObjective;
+import net.minecraft.scoreboard.ScoreboardPlayerScore;
+import net.minecraft.scoreboard.Team;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+//TODO Slayer Packet system that can provide information about the current slayer boss, abstract so that different bosses can have different info
+public class SlayerUtils {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SlayerUtils.class);
+
+ //TODO: Cache this, probably included in Packet system
+ public static List<Entity> getEntityArmorStands(Entity entity) {
+ return entity.getEntityWorld().getOtherEntities(entity, entity.getBoundingBox().expand(1F, 2.5F, 1F), x -> x instanceof ArmorStandEntity && x.hasCustomName());
+ }
+
+ //Eventually this should be modified so that if you hit a slayer boss all slayer features will work on that boss.
+ public static Entity getSlayerEntity() {
+ if (MinecraftClient.getInstance().world != null) {
+ for (Entity entity : MinecraftClient.getInstance().world.getEntities()) {
+ //Check if entity is Bloodfiend
+ if (entity.hasCustomName() && entity.getCustomName().getString().contains("Bloodfiend")) {
+ //Grab the players username
+ String username = MinecraftClient.getInstance().getSession().getUsername();
+ //Check all armor stands around the boss
+ for (Entity armorStand : getEntityArmorStands(entity)) {
+ //Check if the display name contains the players username
+ if (armorStand.getDisplayName().getString().contains(username)) {
+ return entity;
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ public static boolean isInSlayer() {
+ try {
+ ClientPlayerEntity client = MinecraftClient.getInstance().player;
+ if (client == null) return false;
+ Scoreboard scoreboard = MinecraftClient.getInstance().player.getScoreboard();
+ ScoreboardObjective objective = scoreboard.getObjectiveForSlot(1);
+ for (ScoreboardPlayerScore score : scoreboard.getAllPlayerScores(objective)) {
+ Team team = scoreboard.getPlayerTeam(score.getPlayerName());
+ if (team != null) {
+ String line = team.getPrefix().getString() + team.getSuffix().getString();
+ if (line.contains("Slay the boss!")) {
+ return true;
+ }
+ }
+ }
+ } catch (NullPointerException e) {
+ LOGGER.error("[Skyblocker] Error while checking if player is in slayer", e);
+ }
+ return false;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/me/xmrvizzy/skyblocker/utils/Utils.java b/src/main/java/me/xmrvizzy/skyblocker/utils/Utils.java
index 532de0dd..35dfd368 100644
--- a/src/main/java/me/xmrvizzy/skyblocker/utils/Utils.java
+++ b/src/main/java/me/xmrvizzy/skyblocker/utils/Utils.java
@@ -1,26 +1,121 @@
package me.xmrvizzy.skyblocker.utils;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import me.xmrvizzy.skyblocker.SkyblockerMod;
import me.xmrvizzy.skyblocker.skyblock.item.PriceInfoTooltip;
+import me.xmrvizzy.skyblocker.skyblock.rift.TheRift;
import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback;
+import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
+import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
+import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.client.network.PlayerListEntry;
import net.minecraft.scoreboard.Scoreboard;
import net.minecraft.scoreboard.ScoreboardObjective;
import net.minecraft.scoreboard.ScoreboardPlayerScore;
import net.minecraft.scoreboard.Team;
+import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+/**
+ * Utility variables and methods for retrieving Skyblock related information.
+ */
public class Utils {
- public static boolean isOnSkyblock = false;
- public static boolean isInDungeons = false;
- public static boolean isInjected = false;
+ private static final String PROFILE_PREFIX = "Profile: ";
+ private static boolean isOnSkyblock = false;
+ private static boolean isInDungeons = false;
+ private static boolean isInjected = false;
+ /**
+ * The following fields store data returned from /locraw: {@link #profile}, {@link #server}, {@link #gameType}, {@link #locationRaw}, and {@link #map}.
+ */
+ @SuppressWarnings("JavadocDeclaration")
+ private static String profile = "";
+ private static String server = "";
+ private static String gameType = "";
+ private static String locationRaw = "";
+ private static String map = "";
+ private static long clientWorldJoinTime = 0;
+ private static boolean sentLocRaw = false;
+ private static long lastLocRaw = 0;
- public static void sbChecker() {
+ public static boolean isOnSkyblock() {
+ return isOnSkyblock;
+ }
+
+ public static boolean isInDungeons() {
+ return isInDungeons;
+ }
+
+ public static boolean isInTheRift() {
+ return getLocationRaw().equals(TheRift.LOCATION);
+ }
+
+ public static boolean isInjected() {
+ return isInjected;
+ }
+
+ /**
+ * @return the profile parsed from the player list.
+ */
+ public static String getProfile() {
+ return profile;
+ }
+
+ /**
+ * @return the server parsed from /locraw.
+ */
+ public static String getServer() {
+ return server;
+ }
+
+ /**
+ * @return the game type parsed from /locraw.
+ */
+ public static String getGameType() {
+ return gameType;
+ }
+
+ /**
+ * @return the location raw parsed from /locraw.
+ */
+ public static String getLocationRaw() {
+ return locationRaw;
+ }
+
+ /**
+ * @return the map parsed from /locraw.
+ */
+ public static String getMap() {
+ return map;
+ }
+
+ public static void init() {
+ ClientPlayConnectionEvents.JOIN.register(Utils::onClientWorldJoin);
+ ClientReceiveMessageEvents.ALLOW_GAME.register(Utils::onChatMessage);
+ ClientReceiveMessageEvents.GAME_CANCELED.register(Utils::onChatMessage); // Somehow this works even though onChatMessage returns a boolean
+ }
+
+ /**
+ * Updates all the fields stored in this class from the sidebar, player list, and /locraw.
+ */
+ public static void update() {
MinecraftClient client = MinecraftClient.getInstance();
+ updateFromScoreboard(client);
+ updateFromPlayerList(client);
+ updateLocRaw();
+ }
+
+ /**
+ * Updates {@link #isOnSkyblock}, {@link #isInDungeons}, and {@link #isInjected} from the scoreboard.
+ */
+ public static void updateFromScoreboard(MinecraftClient client) {
List<String> sidebar;
if (client.world == null || client.isInSingleplayer() || (sidebar = getSidebar()) == null) {
@@ -37,13 +132,13 @@ public class Utils {
isInjected = true;
ItemTooltipCallback.EVENT.register(PriceInfoTooltip::onInjectTooltip);
}
- SkyblockEvents.JOIN.invoker().onSkyblockJoin();
isOnSkyblock = true;
+ SkyblockEvents.JOIN.invoker().onSkyblockJoin();
}
} else if (isOnSkyblock) {
- SkyblockEvents.LEAVE.invoker().onSkyblockLeave();
isOnSkyblock = false;
isInDungeons = false;
+ SkyblockEvents.LEAVE.invoker().onSkyblockLeave();
}
isInDungeons = isOnSkyblock && string.contains("The Catacombs");
}
@@ -52,12 +147,13 @@ public class Utils {
String location = null;
List<String> sidebarLines = getSidebar();
try {
- if( sidebarLines != null) {
+ if (sidebarLines != null) {
for (String sidebarLine : sidebarLines) {
if (sidebarLine.contains("⏣")) location = sidebarLine;
+ if (sidebarLine.contains("ф")) location = sidebarLine; //Rift
}
if (location == null) location = "Unknown";
- location = location.replace('⏣', ' ').strip();
+ location = location.strip();
}
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
@@ -134,4 +230,74 @@ public class Utils {
return null;
}
}
+
+ private static void updateFromPlayerList(MinecraftClient client) {
+ if (client.getNetworkHandler() == null) {
+ return;
+ }
+ for (PlayerListEntry playerListEntry : client.getNetworkHandler().getPlayerList()) {
+ if (playerListEntry.getDisplayName() == null) {
+ continue;
+ }
+ String name = playerListEntry.getDisplayName().getString();
+ if (name.startsWith(PROFILE_PREFIX)) {
+ profile = name.substring(PROFILE_PREFIX.length());
+ }
+ }
+ }
+
+ public static void onClientWorldJoin(ClientPlayNetworkHandler handler, PacketSender sender, MinecraftClient client) {
+ clientWorldJoinTime = System.currentTimeMillis();
+ resetLocRawInfo();
+ }
+
+ /**
+ * Sends /locraw to the server if the player is on skyblock and on a new island.
+ */
+ private static void updateLocRaw() {
+ if (isOnSkyblock) {
+ long currentTime = System.currentTimeMillis();
+ if (!sentLocRaw && currentTime > clientWorldJoinTime + 1000 && currentTime > lastLocRaw + 15000) {
+ SkyblockerMod.getInstance().messageScheduler.sendMessageAfterCooldown("/locraw");
+ sentLocRaw = true;
+ lastLocRaw = currentTime;
+ }
+ } else {
+ resetLocRawInfo();
+ }
+ }
+
+ /**
+ * Parses the /locraw reply from the server
+ *
+ * @return not display the message in chat is the command is sent by the mod
+ */
+ public static boolean onChatMessage(Text text, boolean overlay) {
+ String message = text.getString();
+ if (message.startsWith("{\"server\":") && message.endsWith("}")) {
+ JsonObject locRaw = JsonParser.parseString(message).getAsJsonObject();
+ if (locRaw.has("server")) {
+ server = locRaw.get("server").getAsString();
+ if (locRaw.has("gameType")) {
+ gameType = locRaw.get("gameType").getAsString();
+ }
+ if (locRaw.has("mode")) {
+ locationRaw = locRaw.get("mode").getAsString();
+ }
+ if (locRaw.has("map")) {
+ map = locRaw.get("map").getAsString();
+ }
+ return !sentLocRaw;
+ }
+ }
+ return true;
+ }
+
+ private static void resetLocRawInfo() {
+ sentLocRaw = false;
+ server = "";
+ gameType = "";
+ locationRaw = "";
+ map = "";
+ }
} \ No newline at end of file
diff --git a/src/main/java/me/xmrvizzy/skyblocker/utils/title/Title.java b/src/main/java/me/xmrvizzy/skyblocker/utils/title/Title.java
new file mode 100644
index 00000000..0a3bc845
--- /dev/null
+++ b/src/main/java/me/xmrvizzy/skyblocker/utils/title/Title.java
@@ -0,0 +1,53 @@
+package me.xmrvizzy.skyblocker.utils.title;
+
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+/**
+ * Represents a title used for {@link TitleContainer}.
+ *
+ * @see TitleContainer
+ */
+public class Title {
+ private MutableText text;
+ protected float x = -1;
+ protected float y = -1;
+
+ /**
+ * Constructs a new title with the given translation key and formatting to be applied.
+ *
+ * @param textKey the translation key
+ * @param formatting the formatting to be applied to the text
+ */
+ public Title(String textKey, Formatting formatting) {
+ this(Text.translatable(textKey).formatted(formatting));
+ }
+
+ /**
+ * Constructs a new title with the given {@link MutableText}.
+ * Use {@link Text#literal(String)} or {@link Text#translatable(String)} to create a {@link MutableText}
+ *
+ * @param text the mutable text
+ */
+ public Title(MutableText text) {
+ this.text = text;
+ }
+
+ public MutableText getText() {
+ return text;
+ }
+
+ public void setText(MutableText text) {
+ this.text = text;
+ }
+
+ protected boolean isDefaultPos() {
+ return x == -1 && y == -1;
+ }
+
+ protected void resetPos() {
+ this.x = -1;
+ this.y = -1;
+ }
+}
diff --git a/src/main/java/me/xmrvizzy/skyblocker/utils/title/TitleContainer.java b/src/main/java/me/xmrvizzy/skyblocker/utils/title/TitleContainer.java
new file mode 100644
index 00000000..a4e445ee
--- /dev/null
+++ b/src/main/java/me/xmrvizzy/skyblocker/utils/title/TitleContainer.java
@@ -0,0 +1,179 @@
+package me.xmrvizzy.skyblocker.utils.title;
+
+import com.mojang.brigadier.Command;
+import me.xmrvizzy.skyblocker.SkyblockerMod;
+import me.xmrvizzy.skyblocker.config.SkyblockerConfig;
+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.font.TextRenderer;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.text.Text;
+import net.minecraft.util.math.MathHelper;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class TitleContainer {
+ /**
+ * The set of titles which will be rendered.
+ *
+ * @see #containsTitle(Title)
+ * @see #addTitle(Title)
+ * @see #addTitle(Title, int)
+ * @see #removeTitle(Title)
+ */
+ private static final Set<Title> titles = new LinkedHashSet<>();
+
+ public static void init() {
+ HudRenderCallback.EVENT.register(TitleContainer::render);
+ ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal("skyblocker")
+ .then(ClientCommandManager.literal("hud")
+ .then(ClientCommandManager.literal("titleContainer")
+ .executes(context -> {
+ SkyblockerMod.getInstance().scheduler.queueOpenScreen(new TitleContainerConfigScreen(Text.of("Title Container HUD Config")));
+ return Command.SINGLE_SUCCESS;
+ })))));
+ }
+
+ /**
+ * Returns {@code true} if the title is currently shown.
+ *
+ * @param title the title to check
+ * @return whether the title in currently shown
+ */
+ public static boolean containsTitle(Title title) {
+ return titles.contains(title);
+ }
+
+ /**
+ * Adds a title to be shown
+ *
+ * @param title the title to be shown
+ * @return whether the title is already currently being shown
+ */
+ public static boolean addTitle(Title title) {
+ if (titles.add(title)) {
+ title.resetPos();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Adds a title to be shown for a set number of ticks
+ *
+ * @param title the title to be shown
+ * @param ticks the number of ticks to show the title
+ * @return whether the title is already currently being shown
+ */
+ public static boolean addTitle(Title title, int ticks) {
+ if (addTitle(title)) {
+ SkyblockerMod.getInstance().scheduler.schedule(() -> TitleContainer.removeTitle(title), ticks);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Stops showing a title
+ *
+ * @param title the title to stop showing
+ */
+ public static void removeTitle(Title title) {
+ titles.remove(title);
+ }
+
+ private static void render(DrawContext context, float tickDelta) {
+ render(context, titles, SkyblockerConfig.get().general.titleContainer.x, SkyblockerConfig.get().general.titleContainer.y, tickDelta);
+ }
+
+ protected static void render(DrawContext context, Set<Title> titles, int xPos, int yPos, float tickDelta) {
+ var client = MinecraftClient.getInstance();
+ TextRenderer textRenderer = client.textRenderer;
+
+ // Calculate Scale to use
+ float scale = 3F * (SkyblockerConfig.get().general.titleContainer.titleContainerScale / 100F);
+
+ // Grab direction and alignment values
+ SkyblockerConfig.Direction direction = SkyblockerConfig.get().general.titleContainer.direction;
+ SkyblockerConfig.Alignment alignment = SkyblockerConfig.get().general.titleContainer.alignment;
+ // x/y refer to the starting position for the text
+ // y always starts at yPos
+ float x = 0;
+ float y = yPos;
+
+ //Calculate the width of combined text
+ float width = 0;
+ for (Title title : titles) {
+ width += textRenderer.getWidth(title.getText()) * scale + 10;
+ }
+
+ if (alignment == SkyblockerConfig.Alignment.MIDDLE) {
+ if (direction == SkyblockerConfig.Direction.HORIZONTAL) {
+ //If middle aligned horizontally, start the xPosition at half of the width to the left.
+ x = xPos - (width / 2);
+ } else {
+ //If middle aligned vertically, start at xPos, we will shift each text to the left later
+ x = xPos;
+ }
+ }
+ if (alignment == SkyblockerConfig.Alignment.LEFT || alignment == SkyblockerConfig.Alignment.RIGHT) {
+ //If left or right aligned, start at xPos, we will shift each text later
+ x = xPos;
+ }
+
+ for (Title title : titles) {
+
+ //Calculate which x the text should use
+ float xToUse;
+ if (direction == SkyblockerConfig.Direction.HORIZONTAL) {
+ xToUse = alignment == SkyblockerConfig.Alignment.RIGHT ?
+ x - (textRenderer.getWidth(title.getText()) * scale) : //if right aligned we need the text position to be aligned on the right side.
+ x;
+ } else {
+ xToUse = alignment == SkyblockerConfig.Alignment.MIDDLE ?
+ x - (textRenderer.getWidth(title.getText()) * scale) / 2 : //if middle aligned we need the text position to be aligned in the middle.
+ alignment == SkyblockerConfig.Alignment.RIGHT ?
+ x - (textRenderer.getWidth(title.getText()) * scale) : //if right aligned we need the text position to be aligned on the right side.
+ x;
+ }
+
+ //Start displaying the title at the correct position, not at the default position
+ if (title.isDefaultPos()) {
+ title.x = xToUse;
+ title.y = y;
+ }
+
+ //Lerp the texts x and y variables
+ title.x = MathHelper.lerp(tickDelta * 0.5F, title.x, xToUse);
+ title.y = MathHelper.lerp(tickDelta * 0.5F, title.y, y);
+
+ //Translate the matrix to the texts position and scale
+ context.getMatrices().push();
+ context.getMatrices().translate(title.x, title.y, 200);
+ context.getMatrices().scale(scale, scale, scale);
+
+ //Draw text
+ context.drawTextWithShadow(textRenderer, title.getText(), 0, 0, 0xFFFFFF);
+ context.getMatrices().pop();
+
+ //Calculate the x and y positions for the next title
+ if (direction == SkyblockerConfig.Direction.HORIZONTAL) {
+ if (alignment == SkyblockerConfig.Alignment.MIDDLE || alignment == SkyblockerConfig.Alignment.LEFT) {
+ //Move to the right if middle or left aligned
+ x += textRenderer.getWidth(title.getText()) * scale + 10;
+ }
+
+ if (alignment == SkyblockerConfig.Alignment.RIGHT) {
+ //Move to the left if right aligned
+ x -= textRenderer.getWidth(title.getText()) * scale + 10;
+ }
+ } else {
+ //Y always moves by the same amount if vertical
+ y += textRenderer.fontHeight * scale + 10;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/me/xmrvizzy/skyblocker/utils/title/TitleContainerConfigScreen.java b/src/main/java/me/xmrvizzy/skyblocker/utils/title/TitleContainerConfigScreen.java
new file mode 100644
index 00000000..e729ea15
--- /dev/null
+++ b/src/main/java/me/xmrvizzy/skyblocker/utils/title/TitleContainerConfigScreen.java
@@ -0,0 +1,164 @@
+package me.xmrvizzy.skyblocker.utils.title;
+
+import me.shedaniel.autoconfig.AutoConfig;
+import me.xmrvizzy.skyblocker.config.SkyblockerConfig;
+import me.xmrvizzy.skyblocker.utils.RenderUtils;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.util.math.Vector2f;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.Pair;
+import org.lwjgl.glfw.GLFW;
+
+import java.awt.*;
+import java.util.Set;
+
+public class TitleContainerConfigScreen extends Screen {
+ private final Title example1 = new Title(Text.literal("Test1").formatted(Formatting.RED));
+ private final Title example2 = new Title(Text.literal("Test23").formatted(Formatting.AQUA));
+ private final Title example3 = new Title(Text.literal("Testing1234").formatted(Formatting.DARK_GREEN));
+ private float hudX = SkyblockerConfig.get().general.titleContainer.x;
+ private float hudY = SkyblockerConfig.get().general.titleContainer.y;
+
+ protected TitleContainerConfigScreen(Text title) {
+ super(title);
+ }
+
+ @Override
+ public void render(DrawContext context, int mouseX, int mouseY, float delta) {
+ super.render(context, mouseX, mouseY, delta);
+ renderBackground(context);
+ TitleContainer.render(context, Set.of(example1, example2, example3), (int) hudX, (int) hudY, delta);
+ SkyblockerConfig.Direction direction = SkyblockerConfig.get().general.titleContainer.direction;
+ SkyblockerConfig.Alignment alignment = SkyblockerConfig.get().general.titleContainer.alignment;
+ context.drawCenteredTextWithShadow(textRenderer, "Press Q/E to change Alignment: " + alignment, width / 2, textRenderer.fontHeight * 2, Color.WHITE.getRGB());
+ context.drawCenteredTextWithShadow(textRenderer, "Press R to change Direction: " + direction, width / 2, textRenderer.fontHeight * 3 + 5, Color.WHITE.getRGB());
+ context.drawCenteredTextWithShadow(textRenderer, "Press +/- to change Scale", width / 2, textRenderer.fontHeight * 4 + 10, Color.WHITE.getRGB());
+ context.drawCenteredTextWithShadow(textRenderer, "Right Click To Reset Position", width / 2, textRenderer.fontHeight * 5 + 15, Color.GRAY.getRGB());
+
+ Pair<Vector2f, Vector2f> boundingBox = getSelectionBoundingBox();
+ int x1 = (int) boundingBox.getLeft().getX();
+ int y1 = (int) boundingBox.getLeft().getY();
+ int x2 = (int) boundingBox.getRight().getX();
+ int y2 = (int) boundingBox.getRight().getY();
+
+ context.drawHorizontalLine(x1, x2, y1, Color.RED.getRGB());
+ context.drawHorizontalLine(x1, x2, y2, Color.RED.getRGB());
+ context.drawVerticalLine(x1, y1, y2, Color.RED.getRGB());
+ context.drawVerticalLine(x2, y1, y2, Color.RED.getRGB());
+ }
+
+ private Pair<Vector2f, Vector2f> getSelectionBoundingBox() {
+ SkyblockerConfig.Alignment alignment = SkyblockerConfig.get().general.titleContainer.alignment;
+
+ float midWidth = getSelectionWidth() / 2F;
+ float x1 = 0;
+ float x2 = 0;
+ float y1 = hudY;
+ float y2 = hudY + getSelectionHeight();
+ switch (alignment) {
+ case RIGHT -> {
+ x1 = hudX - midWidth * 2;
+ x2 = hudX;
+ }
+ case MIDDLE -> {
+ x1 = hudX - midWidth;
+ x2 = hudX + midWidth;
+ }
+ case LEFT -> {
+ x1 = hudX;
+ x2 = hudX + midWidth * 2;
+ }
+ }
+ return new Pair<>(new Vector2f(x1, y1), new Vector2f(x2, y2));
+ }
+
+ private float getSelectionHeight() {
+ float scale = (3F * (SkyblockerConfig.get().general.titleContainer.titleContainerScale / 100F));
+ return SkyblockerConfig.get().general.titleContainer.direction == SkyblockerConfig.Direction.HORIZONTAL ?
+ (textRenderer.fontHeight * scale) :
+ (textRenderer.fontHeight + 10F) * 3F * scale;
+ }
+
+ private float getSelectionWidth() {
+ float scale = (3F * (SkyblockerConfig.get().general.titleContainer.titleContainerScale / 100F));
+ return SkyblockerConfig.get().general.titleContainer.direction == SkyblockerConfig.Direction.HORIZONTAL ?
+ (textRenderer.getWidth("Test1") + 10 + textRenderer.getWidth("Test23") + 10 + textRenderer.getWidth("Testing1234")) * scale :
+ textRenderer.getWidth("Testing1234") * scale;
+ }
+
+ @Override
+ public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
+ float midWidth = getSelectionWidth() / 2;
+ float midHeight = getSelectionHeight() / 2;
+ var alignment = SkyblockerConfig.get().general.titleContainer.alignment;
+
+ Pair<Vector2f, Vector2f> boundingBox = getSelectionBoundingBox();
+ float x1 = boundingBox.getLeft().getX();
+ float y1 = boundingBox.getLeft().getY();
+ float x2 = boundingBox.getRight().getX();
+ float y2 = boundingBox.getRight().getY();
+
+ if (RenderUtils.pointExistsInArea((int) mouseX, (int) mouseY, (int) x1, (int) y1, (int) x2, (int) y2) && button == 0) {
+ hudX = switch (alignment) {
+ case LEFT -> (int) mouseX - midWidth;
+ case MIDDLE -> (int) mouseX;
+ case RIGHT -> (int) mouseX + midWidth;
+ };
+ hudY = (int) (mouseY - midHeight);
+ }
+ return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
+ }
+
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY, int button) {
+ if (button == 1) {
+ hudX = (float) this.width / 2;
+ hudY = this.height * 0.6F;
+ }
+ return super.mouseClicked(mouseX, mouseY, button);
+ }
+
+ @Override
+ public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
+ if (keyCode == GLFW.GLFW_KEY_Q) {
+ SkyblockerConfig.Alignment current = SkyblockerConfig.get().general.titleContainer.alignment;
+ SkyblockerConfig.get().general.titleContainer.alignment = switch (current) {
+ case LEFT -> SkyblockerConfig.Alignment.MIDDLE;
+ case MIDDLE -> SkyblockerConfig.Alignment.RIGHT;
+ case RIGHT -> SkyblockerConfig.Alignment.LEFT;
+ };
+ }
+ if (keyCode == GLFW.GLFW_KEY_E) {
+ SkyblockerConfig.Alignment current = SkyblockerConfig.get().general.titleContainer.alignment;
+ SkyblockerConfig.get().general.titleContainer.alignment = switch (current) {
+ case LEFT -> SkyblockerConfig.Alignment.RIGHT;
+ case MIDDLE -> SkyblockerConfig.Alignment.LEFT;
+ case RIGHT -> SkyblockerConfig.Alignment.MIDDLE;
+ };
+ }
+ if (keyCode == GLFW.GLFW_KEY_R) {
+ SkyblockerConfig.Direction current = SkyblockerConfig.get().general.titleContainer.direction;
+ SkyblockerConfig.get().general.titleContainer.direction = switch (current) {
+ case HORIZONTAL -> SkyblockerConfig.Direction.VERTICAL;
+ case VERTICAL -> SkyblockerConfig.Direction.HORIZONTAL;
+ };
+ }
+ if (keyCode == GLFW.GLFW_KEY_EQUAL) {
+ SkyblockerConfig.get().general.titleContainer.titleContainerScale += 10;
+ }
+ if (keyCode == GLFW.GLFW_KEY_MINUS) {
+ SkyblockerConfig.get().general.titleContainer.titleContainerScale -= 10;
+ }
+ return super.keyPressed(keyCode, scanCode, modifiers);
+ }
+
+ @Override
+ public void close() {
+ SkyblockerConfig.get().general.titleContainer.x = (int) hudX;
+ SkyblockerConfig.get().general.titleContainer.y = (int) hudY;
+ AutoConfig.getConfigHolder(SkyblockerConfig.class).save();
+ super.close();
+ }
+}