diff --git a/src/main/java/me/xmrvizzy/skyblocker/SkyblockerMod.java b/src/main/java/me/xmrvizzy/skyblocker/SkyblockerMod.java index 64c09a2f13..2ab1b45e77 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/SkyblockerMod.java +++ b/src/main/java/me/xmrvizzy/skyblocker/SkyblockerMod.java @@ -11,11 +11,13 @@ import me.xmrvizzy.skyblocker.skyblock.api.RepositoryUpdate; import me.xmrvizzy.skyblocker.skyblock.api.StatsCommand; import me.xmrvizzy.skyblocker.skyblock.dungeon.DungeonBlaze; +import me.xmrvizzy.skyblocker.skyblock.dungeon.LividColor; import me.xmrvizzy.skyblocker.skyblock.dwarven.DwarvenHud; import me.xmrvizzy.skyblocker.skyblock.item.PriceInfoTooltip; import me.xmrvizzy.skyblocker.skyblock.item.WikiLookup; import me.xmrvizzy.skyblocker.skyblock.itemlist.ItemRegistry; import me.xmrvizzy.skyblocker.skyblock.quicknav.QuickNav; +import me.xmrvizzy.skyblocker.utils.MessageScheduler; import me.xmrvizzy.skyblocker.utils.Scheduler; import me.xmrvizzy.skyblocker.utils.UpdateChecker; import me.xmrvizzy.skyblocker.utils.Utils; @@ -32,6 +34,7 @@ public class SkyblockerMod implements ClientModInitializer { @SuppressWarnings("deprecation") public final Scheduler scheduler = new Scheduler(); + public final MessageScheduler messageScheduler = new MessageScheduler(); public final ContainerSolverManager containerSolverManager = new ContainerSolverManager(); public final StatusBarTracker statusBarTracker = new StatusBarTracker(); @@ -66,20 +69,24 @@ public void onInitializeClient() { ChatMessageListener.init(); UpdateChecker.init(); DiscordRPCManager.init(); + LividColor.init(); FishingHelper.init(); containerSolverManager.init(); scheduler.scheduleCyclic(Utils::sbChecker, 20); scheduler.scheduleCyclic(DiscordRPCManager::update, 100); scheduler.scheduleCyclic(DungeonBlaze::update, 4); + scheduler.scheduleCyclic(LividColor::update, 10); scheduler.scheduleCyclic(BackpackPreview::tick, 50); scheduler.scheduleCyclic(DwarvenHud::update, 40); } /** * Ticks the scheduler. Called once at the end of every client tick through {@link ClientTickEvents#END_CLIENT_TICK}. + * * @param client the Minecraft client. */ public void tick(MinecraftClient client) { scheduler.tick(); + messageScheduler.tick(); } } diff --git a/src/main/java/me/xmrvizzy/skyblocker/config/SkyblockerConfig.java b/src/main/java/me/xmrvizzy/skyblocker/config/SkyblockerConfig.java index 13f70137ff..c87f0ad937 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/config/SkyblockerConfig.java +++ b/src/main/java/me/xmrvizzy/skyblocker/config/SkyblockerConfig.java @@ -269,10 +269,19 @@ public static class Dungeons { public boolean solveThreeWeirdos = true; public boolean blazesolver = true; public boolean solveTrivia = true; + @ConfigEntry.Gui.CollapsibleObject + public LividColor lividColor = new LividColor(); @ConfigEntry.Gui.CollapsibleObject() public Terminals terminals = new Terminals(); } + public static class LividColor { + @ConfigEntry.Gui.Tooltip() + public boolean enableLividColor = true; + @ConfigEntry.Gui.Tooltip() + public String lividColorText = "The livid color is [color]"; + } + public static class Terminals { public boolean solveColor = true; public boolean solveOrder = true; diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/LividColor.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/LividColor.java new file mode 100644 index 0000000000..276a41b6c4 --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/LividColor.java @@ -0,0 +1,43 @@ +package me.xmrvizzy.skyblocker.skyblock.dungeon; + +import me.xmrvizzy.skyblocker.SkyblockerMod; +import me.xmrvizzy.skyblocker.config.SkyblockerConfig; +import me.xmrvizzy.skyblocker.utils.Utils; +import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.util.math.BlockPos; + +public class LividColor { + private static int tenTicks = 0; + + public static void init() { + ClientReceiveMessageEvents.ALLOW_GAME.register((message, overlay) -> { + if (SkyblockerConfig.get().locations.dungeons.lividColor.enableLividColor && message.getString().equals("[BOSS] Livid: I respect you for making it to here, but I'll be your undoing.")) { + tenTicks = 8; + } + return true; + }); + } + + public static void update() { + MinecraftClient client = MinecraftClient.getInstance(); + if (tenTicks != 0) { + if (SkyblockerConfig.get().locations.dungeons.lividColor.enableLividColor && Utils.isInDungeons() && client.world != null) { + if (tenTicks == 1) { + SkyblockerMod.getInstance().messageScheduler.sendMessageAfterCooldown(SkyblockerConfig.get().locations.dungeons.lividColor.lividColorText.replace("[color]", "red")); + tenTicks = 0; + return; + } + String key = client.world.getBlockState(new BlockPos(5, 110, 42)).getBlock().getTranslationKey(); + if (key.startsWith("block.minecraft.") && key.endsWith("wool") && !key.endsWith("red_wool")) { + SkyblockerMod.getInstance().messageScheduler.sendMessageAfterCooldown(SkyblockerConfig.get().locations.dungeons.lividColor.lividColorText.replace("[color]", key.substring(16, key.length() - 5))); + tenTicks = 0; + return; + } + tenTicks--; + } else { + tenTicks = 0; + } + } + } +} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/Reparty.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/Reparty.java index fdc39c429d..29c37b9429 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/Reparty.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/Reparty.java @@ -29,7 +29,7 @@ public Reparty() { ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal("rp").executes(context -> { if (!Utils.isOnSkyblock() || this.repartying || client.player == null) return 0; this.repartying = true; - client.player.networkHandler.sendCommand("p list"); + SkyblockerMod.getInstance().messageScheduler.sendMessageAfterCooldown("/p list"); return 0; }))); } @@ -63,16 +63,15 @@ private void reparty() { this.repartying = false; return; } - sendCommand(playerEntity, "p disband", 1); + sendCommand("/p disband", 1); for (int i = 0; i < this.players.length; ++i) { - String command = "p invite " + this.players[i]; - sendCommand(playerEntity, command, i + 2); + String command = "/p invite " + this.players[i]; + sendCommand(command, i + 2); } skyblocker.scheduler.schedule(() -> this.repartying = false, this.players.length + 2); } - private void sendCommand(ClientPlayerEntity player, String command, int delay) { - // skyblocker.scheduler.schedule(() -> player.sendChatMessage(command, Text.of(command)), delay * BASE_DELAY); - skyblocker.scheduler.schedule(() -> player.networkHandler.sendCommand(command), delay * BASE_DELAY); + private void sendCommand(String command, int delay) { + skyblocker.messageScheduler.queueMessage(command, delay * BASE_DELAY); } } diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/quicknav/QuickNavButton.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/quicknav/QuickNavButton.java index e31827abd1..7269840ac3 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/quicknav/QuickNavButton.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/quicknav/QuickNavButton.java @@ -2,6 +2,7 @@ import com.mojang.blaze3d.systems.RenderSystem; +import me.xmrvizzy.skyblocker.SkyblockerMod; import me.xmrvizzy.skyblocker.mixin.HandledScreenAccessor; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -57,7 +58,7 @@ private void updateCoordinates() { public void onClick(double mouseX, double mouseY) { if (!this.toggled) { this.toggled = true; - CLIENT.player.networkHandler.sendCommand(command.replace("/", "")); + SkyblockerMod.getInstance().messageScheduler.sendMessageAfterCooldown(command); // TODO : add null check with log error } } 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 0000000000..ac6aa293ff --- /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/Scheduler.java b/src/main/java/me/xmrvizzy/skyblocker/utils/Scheduler.java index efba49958a..021621408a 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/utils/Scheduler.java +++ b/src/main/java/me/xmrvizzy/skyblocker/utils/Scheduler.java @@ -11,8 +11,8 @@ */ public class Scheduler { private static final Logger LOGGER = LoggerFactory.getLogger(Scheduler.class); - private int currentTick; - private final PriorityQueue tasks; + private int currentTick = 0; + private final PriorityQueue tasks = new PriorityQueue<>(); /** * Do not instantiate this class. Use {@link SkyblockerMod#scheduler} instead. @@ -20,49 +20,62 @@ public class Scheduler { @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 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(task, currentTick + delay); tasks.add(tmp); } /** * Schedules a task to run every period ticks. - * @param task the task to run + * + * @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(); + } } 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(); } } + /** + * 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 inner the task to run * @param period the period in ticks */ - private record CyclicTask(Runnable inner, int period) implements Runnable { + protected record CyclicTask(Runnable inner, int period) implements Runnable { @Override public void run() { SkyblockerMod.getInstance().scheduler.schedule(this, period); @@ -72,10 +85,11 @@ public void run() { /** * A task that runs at a specific tick, relative to {@link #currentTick}. - * @param inner the task to run + * + * @param inner the task to run * @param schedule the tick to run at */ - private record ScheduledTask(Runnable inner, int schedule) implements Comparable, Runnable { + protected record ScheduledTask(Runnable inner, int schedule) implements Comparable, Runnable { @Override public int compareTo(ScheduledTask o) { return schedule - o.schedule; diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index 80e3e05e16..922f73a9bd 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -160,6 +160,11 @@ "text.autoconfig.skyblocker.option.locations.dungeons.solveThreeWeirdos": "Solve Three Weirdos Puzzle", "text.autoconfig.skyblocker.option.locations.dungeons.blazesolver": "Solve Blaze Puzzle", "text.autoconfig.skyblocker.option.locations.dungeons.solveTrivia": "Solve Trivia Puzzle", + "text.autoconfig.skyblocker.option.locations.dungeons.lividColor": "Livid Color", + "text.autoconfig.skyblocker.option.locations.dungeons.lividColor.enableLividColor": "Enable Livid Color", + "text.autoconfig.skyblocker.option.locations.dungeons.lividColor.enableLividColor.@Tooltip": "Send the livid color in the chat during the Livid boss fight.", + "text.autoconfig.skyblocker.option.locations.dungeons.lividColor.lividColorText": "Livid Color Text", + "text.autoconfig.skyblocker.option.locations.dungeons.lividColor.lividColorText.@Tooltip": "Text which will be sent in the chat during the Livid boss fight. The string \"[color]\" will be replaced with the livid color.", "text.autoconfig.skyblocker.option.locations.dungeons.terminals": "Terminal Solvers", "text.autoconfig.skyblocker.option.locations.dungeons.terminals.solveColor": "Solve Select Colored", "text.autoconfig.skyblocker.option.locations.dungeons.terminals.solveOrder": "Solve Click In Order",