diff options
Diffstat (limited to 'src/main/java')
6 files changed, 298 insertions, 72 deletions
diff --git a/src/main/java/de/hysky/skyblocker/debug/Debug.java b/src/main/java/de/hysky/skyblocker/debug/Debug.java index 16d91635..446de66f 100644 --- a/src/main/java/de/hysky/skyblocker/debug/Debug.java +++ b/src/main/java/de/hysky/skyblocker/debug/Debug.java @@ -7,6 +7,7 @@ import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.mixins.accessors.HandledScreenAccessor; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.ItemUtils; +import de.hysky.skyblocker.utils.datafixer.ItemStackComponentizationFixer; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; @@ -51,7 +52,7 @@ public class Debug { ScreenKeyboardEvents.afterKeyPress(screen).register((_screen, key, scancode, modifier) -> { Slot focusedSlot = ((HandledScreenAccessor) handledScreen).getFocusedSlot(); if (key == GLFW.GLFW_KEY_U && client.player != null && focusedSlot != null && focusedSlot.hasStack()) { - client.player.sendMessage(Text.literal("[Skyblocker Debug] Hovered Item: " + SkyblockerMod.GSON_COMPACT.toJson(ItemStack.CODEC.encodeStart(JsonOps.INSTANCE, focusedSlot.getStack())))); + client.player.sendMessage(Text.literal("[Skyblocker Debug] Hovered Item: " + SkyblockerMod.GSON_COMPACT.toJson(ItemStack.CODEC.encodeStart(ItemStackComponentizationFixer.getRegistryLookup().getOps(JsonOps.INSTANCE), focusedSlot.getStack()).getOrThrow()))); } }); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/ChestValue.java b/src/main/java/de/hysky/skyblocker/skyblock/ChestValue.java index c997cda0..67928b07 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/ChestValue.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/ChestValue.java @@ -10,18 +10,29 @@ import de.hysky.skyblocker.utils.Utils; import it.unimi.dsi.fastutil.doubles.DoubleBooleanPair; import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; import net.fabricmc.fabric.api.client.screen.v1.Screens; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.ingame.GenericContainerScreen; import net.minecraft.client.gui.tooltip.Tooltip; import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.TextWidget; import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; import net.minecraft.screen.GenericContainerScreenHandler; import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Style; import net.minecraft.text.Text; +import net.minecraft.util.Util; +import net.minecraft.util.math.MathHelper; import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.text.DecimalFormat; +import java.text.ParseException; import java.util.List; import java.util.Set; import java.util.regex.Matcher; @@ -31,6 +42,7 @@ public class ChestValue { private static final Logger LOGGER = LoggerFactory.getLogger(ChestValue.class); private static final Set<String> DUNGEON_CHESTS = Set.of("Wood Chest", "Gold Chest", "Diamond Chest", "Emerald Chest", "Obsidian Chest", "Bedrock Chest"); private static final Pattern ESSENCE_PATTERN = Pattern.compile("(?<type>[A-Za-z]+) Essence x(?<amount>[0-9]+)"); + private static final Pattern MINION_PATTERN = Pattern.compile("Minion (I|II|III|IV|V|VI|VII|VIII|IX|X|XI|XII)$"); private static final DecimalFormat FORMATTER = new DecimalFormat("#,###"); public static void init() { @@ -40,20 +52,27 @@ public class ChestValue { String titleString = title.getString(); if (DUNGEON_CHESTS.contains(titleString)) { if (SkyblockerConfigManager.get().dungeons.dungeonChestProfit.enableProfitCalculator) { - ScreenEvents.afterTick(screen).register(screen_ -> - ((ScreenAccessor) screen).setTitle(getDungeonChestProfit(genericContainerScreen.getScreenHandler(), title, titleString)) - ); + ScreenEvents.afterTick(screen).register(ignored -> { + Text dungeonChestProfit = getDungeonChestProfit(genericContainerScreen.getScreenHandler()); + if (dungeonChestProfit != null) + addValueToContainer(genericContainerScreen, dungeonChestProfit, title); + }); } } else if (SkyblockerConfigManager.get().uiAndVisuals.chestValue.enableChestValue && !titleString.equals("SkyBlock Menu")) { + boolean minion = MINION_PATTERN.matcher(title.getString().trim()).find(); Screens.getButtons(screen).add(ButtonWidget .builder(Text.literal("$"), buttonWidget -> { Screens.getButtons(screen).remove(buttonWidget); - ScreenEvents.afterTick(screen).register(screen_ -> - ((ScreenAccessor) screen).setTitle(getChestValue(genericContainerScreen.getScreenHandler(), title, titleString)) - ); + ScreenEvents.afterTick(screen).register(ignored -> { + Text chestValue = getChestValue(genericContainerScreen.getScreenHandler(), minion); + if (chestValue != null) { + addValueToContainer(genericContainerScreen, chestValue, title); + } + }); + }) .dimensions(((HandledScreenAccessor) genericContainerScreen).getX() + ((HandledScreenAccessor) genericContainerScreen).getBackgroundWidth() - 16, ((HandledScreenAccessor) genericContainerScreen).getY() + 4, 12, 12) - .tooltip(Tooltip.of(Text.translatable("skyblocker.config.general.chestValue.@Tooltip"))) + .tooltip(minion ? Tooltip.of(Text.translatable("skyblocker.config.general.minionValue.@Tooltip")) : Tooltip.of(Text.translatable("skyblocker.config.general.chestValue.@Tooltip"))) .build() ); } @@ -61,14 +80,14 @@ public class ChestValue { }); } - private static Text getDungeonChestProfit(GenericContainerScreenHandler handler, Text title, String titleString) { + private static @Nullable Text getDungeonChestProfit(GenericContainerScreenHandler handler) { try { double profit = 0; boolean hasIncompleteData = false, usedKismet = false; List<Slot> slots = handler.slots.subList(0, handler.getRows() * 9); //If the item stack for the "Open Reward Chest" button or the kismet button hasn't been sent to the client yet - if (slots.get(31).getStack().isEmpty() || slots.get(50).getStack().isEmpty()) return title; + if (slots.get(31).getStack().isEmpty() || slots.get(50).getStack().isEmpty()) return null; for (Slot slot : slots) { ItemStack stack = slot.getStack(); @@ -136,25 +155,35 @@ public class ChestValue { profit -= kismetPriceData.leftDouble(); } - return Text.literal(titleString).append(getProfitText((long) profit, hasIncompleteData)); + return getProfitText((long) profit, hasIncompleteData); } catch (Exception e) { LOGGER.error("[Skyblocker Profit Calculator] Failed to calculate dungeon chest profit! ", e); } - return title; + return null; } - private static Text getChestValue(GenericContainerScreenHandler handler, Text title, String titleString) { + private static @Nullable Text getChestValue(GenericContainerScreenHandler handler, boolean minion) { try { double value = 0; boolean hasIncompleteData = false; - List<Slot> slots = handler.slots.subList(0, handler.getRows() * 9); + List<Slot> slots = minion ? getMinionSlots(handler) : handler.slots.subList(0, handler.getRows() * 9); for (Slot slot : slots) { ItemStack stack = slot.getStack(); if (stack.isEmpty()) { continue; } + String coinsLine; + if (minion && slot.id == 28 && stack.isOf(Items.HOPPER) && (coinsLine = ItemUtils.getLoreLineIf(stack, s -> s.contains("Held Coins:"))) != null) { + String source = coinsLine.split(":")[1]; + try { + value += DecimalFormat.getNumberInstance(java.util.Locale.US).parse(source.trim()).doubleValue(); + } catch (ParseException e) { + LOGGER.warn("[Skyblocker] Failed to parse {}", source); + } + continue; + } String id = stack.getSkyblockApiId(); @@ -167,12 +196,20 @@ public class ChestValue { } } - return Text.literal(titleString).append(getValueText((long) value, hasIncompleteData)); + return getValueText((long) value, hasIncompleteData); } catch (Exception e) { LOGGER.error("[Skyblocker Value Calculator] Failed to calculate dungeon chest value! ", e); } - return title; + return null; + } + + private static @NotNull List<Slot> getMinionSlots(GenericContainerScreenHandler handler) { + return handler.slots.subList(0, handler.getRows() * 9).stream().filter(slot -> { + int x = slot.id % 9; + int y = slot.id / 9; + return x > 2 && x < 8 && y > 1 && y < 5 || slot.id == 28; + }).toList(); } /** @@ -191,4 +228,58 @@ public class ChestValue { UIAndVisualsConfig.ChestValue config = SkyblockerConfigManager.get().uiAndVisuals.chestValue; return Text.literal(' ' + FORMATTER.format(value) + " Coins").formatted(hasIncompleteData ? config.incompleteColor : config.color); } + + private static void addValueToContainer(GenericContainerScreen genericContainerScreen, Text chestValue, Text title) { + Screens.getButtons(genericContainerScreen).removeIf(clickableWidget -> clickableWidget instanceof ChestValueTextWidget); + int backgroundWidth = ((HandledScreenAccessor) genericContainerScreen).getBackgroundWidth(); + int y = ((HandledScreenAccessor) genericContainerScreen).getY(); + int x = ((HandledScreenAccessor) genericContainerScreen).getX(); + ((ScreenAccessor) genericContainerScreen).setTitle(Text.empty()); + TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + int chestValueWidth = Math.min(textRenderer.getWidth(chestValue), Math.max((backgroundWidth - 8) / 2 - 2, backgroundWidth - 8 - textRenderer.getWidth(title))); + + TextWidget chestValueWidget = new ChestValueTextWidget(chestValueWidth, textRenderer.fontHeight, chestValue, textRenderer); + chestValueWidget.setPosition(x + backgroundWidth - chestValueWidget.getWidth() - 4, y + 6); + Screens.getButtons(genericContainerScreen).add(chestValueWidget); + + ChestValueTextWidget chestTitleWidget = new ChestValueTextWidget(backgroundWidth - 8 - chestValueWidth - 2, textRenderer.fontHeight, title.copy().fillStyle(Style.EMPTY.withColor(4210752)), textRenderer); + chestTitleWidget.shadow = false; + chestTitleWidget.setPosition(x + 8, y + 6); + Screens.getButtons(genericContainerScreen).add(chestTitleWidget); + } + + private static class ChestValueTextWidget extends TextWidget { + + public boolean shadow = true; + + public ChestValueTextWidget(int width, int height, Text message, TextRenderer textRenderer) { + super(width, height, message, textRenderer); + alignLeft(); + } + + @Override + public void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + draw(context, getTextRenderer(), getMessage(), getX(), getRight()); + } + + // Yoinked from ClickableWidget + protected void draw( + DrawContext context, TextRenderer textRenderer, Text text, int startX, int endX + ) { + int i = textRenderer.getWidth(text); + int k = endX - startX; + if (i > k) { + int l = i - k; + double d = (double) Util.getMeasuringTimeMs() / 600.0; + double e = Math.max((double) l * 0.5, 3.0); + double f = Math.sin((Math.PI / 2) * Math.cos((Math.PI * 2) * d / e)) / 2.0 + 0.5; + double g = MathHelper.lerp(f, 0.0, l); + context.enableScissor(startX, getY(), endX, getY() + textRenderer.fontHeight); + context.drawText(textRenderer, text, startX - (int) g, getY(), -1, shadow); + context.disableScissor(); + } else { + context.drawText(textRenderer, text, startX, getY(), -1, shadow); + } + } + } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/EggFinder.java b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/EggFinder.java index 6926fda8..6d390039 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/EggFinder.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/EggFinder.java @@ -12,6 +12,7 @@ import de.hysky.skyblocker.utils.scheduler.MessageScheduler; import de.hysky.skyblocker.utils.waypoint.Waypoint; import it.unimi.dsi.fastutil.objects.ObjectImmutableList; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; 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.WorldRenderContext; @@ -24,7 +25,7 @@ import net.minecraft.text.ClickEvent; import net.minecraft.text.HoverEvent; import net.minecraft.text.Text; import net.minecraft.util.Formatting; -import org.apache.commons.lang3.text.WordUtils; +import org.apache.commons.text.WordUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,18 +40,40 @@ public class EggFinder { private static final Pattern eggFoundPattern = Pattern.compile("^(?:HOPPITY'S HUNT You found a Chocolate|You have already collected this Chocolate) (Breakfast|Lunch|Dinner)"); private static final Pattern newEggPattern = Pattern.compile("^HOPPITY'S HUNT A Chocolate (Breakfast|Lunch|Dinner) Egg has appeared!$"); private static final Logger logger = LoggerFactory.getLogger("Skyblocker Egg Finder"); + //This is most likely unnecessary with the addition of the location change packet, but it works fine and might be doing something so might as well keep it private static final LinkedList<ArmorStandEntity> armorStandQueue = new LinkedList<>(); + /** + * The locations that the egg finder should work while the player is in. + */ private static final Location[] possibleLocations = {Location.CRIMSON_ISLE, Location.CRYSTAL_HOLLOWS, Location.DUNGEON_HUB, Location.DWARVEN_MINES, Location.HUB, Location.THE_END, Location.THE_PARK, Location.GOLD_MINE, Location.DEEP_CAVERNS, Location.SPIDERS_DEN, Location.THE_FARMING_ISLAND}; + /** + * Whether the player is in a location where the egg finder should work. + * This is set to false upon world change and will be checked with the location change event afterward. + */ private static boolean isLocationCorrect = false; - private EggFinder() { - } + private EggFinder() {} public static void init() { - ClientPlayConnectionEvents.JOIN.register((ignored, ignored2, ignored3) -> invalidateState()); + ClientPlayConnectionEvents.JOIN.register((ignored, ignored2, ignored3) -> isLocationCorrect = false); SkyblockEvents.LOCATION_CHANGE.register(EggFinder::handleLocationChange); ClientReceiveMessageEvents.GAME.register(EggFinder::onChatMessage); WorldRenderEvents.AFTER_TRANSLUCENT.register(EggFinder::renderWaypoints); + ClientTickEvents.END_CLIENT_TICK.register(client -> { + if (!SkyblockerConfigManager.get().helpers.chocolateFactory.enableEggFinder || client.player == null) return; + if (!isLocationCorrect || SkyblockTime.skyblockSeason.get() != SkyblockTime.Season.SPRING) return; + for (EggType type : EggType.entries) { + Egg egg = type.egg; + if (egg != null && !egg.seen && client.player.canSee(egg.entity)) { + type.setSeen(); + } + } + }); + SkyblockTime.HOUR_CHANGE.register(hour -> { + for (EggType type : EggType.entries) { + if (hour == type.resetHour) type.collected = false; + } + }); ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE) .then(literal("eggFinder") .then(literal("shareLocation") @@ -73,7 +96,6 @@ public class EggFinder { armorStandQueue.clear(); return; } - while (!armorStandQueue.isEmpty()) { handleArmorStand(armorStandQueue.poll()); } @@ -87,7 +109,7 @@ public class EggFinder { if (!SkyblockerConfigManager.get().helpers.chocolateFactory.enableEggFinder) return; if (SkyblockTime.skyblockSeason.get() != SkyblockTime.Season.SPRING) return; if (armorStand.hasCustomName() || !armorStand.isInvisible() || !armorStand.shouldHideBasePlate()) return; - if (Utils.getLocation() == Location.UNKNOWN) { //The location is unknown upon world change and will be changed via /locraw soon, so we can queue it for now + if (Utils.getLocation() == Location.UNKNOWN) { //The location is unknown upon world change and will be changed via location change packets soon, so we can queue it for now armorStandQueue.add(armorStand); return; } @@ -99,7 +121,7 @@ public class EggFinder { ItemUtils.getHeadTextureOptional(itemStack).ifPresent(texture -> { for (EggType type : EggType.entries) { //Compare blockPos rather than entity to avoid incorrect matches when the entity just moves rather than a new one being spawned elsewhere if (texture.equals(type.texture) && (type.egg == null || !type.egg.entity.getBlockPos().equals(armorStand.getBlockPos()))) { - handleFoundEgg(armorStand, type); + type.egg = new Egg(armorStand, new Waypoint(armorStand.getBlockPos().up(2), SkyblockerConfigManager.get().helpers.chocolateFactory.waypointType, ColorUtils.getFloatComponents(type.color)), false); return; } } @@ -107,34 +129,11 @@ public class EggFinder { } } - private static void invalidateState() { - if (!SkyblockerConfigManager.get().helpers.chocolateFactory.enableEggFinder) return; - isLocationCorrect = false; - for (EggType type : EggType.entries) { - type.egg = null; - } - } - - private static void handleFoundEgg(ArmorStandEntity entity, EggType eggType) { - eggType.egg = new Egg(entity, new Waypoint(entity.getBlockPos().up(2), SkyblockerConfigManager.get().helpers.chocolateFactory.waypointType, ColorUtils.getFloatComponents(eggType.color))); - - if (!SkyblockerConfigManager.get().helpers.chocolateFactory.sendEggFoundMessages || System.currentTimeMillis() - eggType.messageLastSent < 1000) return; - eggType.messageLastSent = System.currentTimeMillis(); - MinecraftClient.getInstance().player.sendMessage( - Constants.PREFIX.get() - .append("Found a ") - .append(Text.literal("Chocolate " + eggType + " Egg") - .withColor(eggType.color)) - .append(" at " + entity.getBlockPos().up(2).toShortString() + "!") - .styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/skyblocker eggFinder shareLocation " + entity.getBlockX() + " " + (entity.getBlockY() + 2) + " " + entity.getBlockZ() + " " + eggType)) - .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.literal("Click to share the location in chat!").formatted(Formatting.GREEN))))); - } - private static void renderWaypoints(WorldRenderContext context) { if (!SkyblockerConfigManager.get().helpers.chocolateFactory.enableEggFinder) return; for (EggType type : EggType.entries) { Egg egg = type.egg; - if (egg != null && egg.waypoint.shouldRender()) egg.waypoint.render(context); + if (egg != null && egg.waypoint.shouldRender() && egg.seen) egg.waypoint.render(context); } } @@ -143,7 +142,9 @@ public class EggFinder { Matcher matcher = eggFoundPattern.matcher(text.getString()); if (matcher.find()) { try { - Egg egg = EggType.valueOf(matcher.group(1).toUpperCase()).egg; + EggType eggType = EggType.valueOf(matcher.group(1).toUpperCase()); + eggType.collected = true; + Egg egg = eggType.egg; if (egg != null) egg.waypoint.setFound(); } catch (IllegalArgumentException e) { logger.error("[Skyblocker Egg Finder] Failed to find egg type for egg found message. Tried to match against: " + matcher.group(0), e); @@ -153,43 +154,75 @@ public class EggFinder { matcher.usePattern(newEggPattern); if (matcher.find()) { try { - EggType.valueOf(matcher.group(1).toUpperCase()).egg = null; + EggType.valueOf(matcher.group(1).toUpperCase()); } catch (IllegalArgumentException e) { logger.error("[Skyblocker Egg Finder] Failed to find egg type for egg spawn message. Tried to match against: " + matcher.group(0), e); } } } - record Egg(ArmorStandEntity entity, Waypoint waypoint) {} + static class Egg { + private final ArmorStandEntity entity; + private final Waypoint waypoint; + private boolean seen; + + Egg(ArmorStandEntity entity, Waypoint waypoint, boolean seen) { + this.entity = entity; + this.waypoint = waypoint; + this.seen = seen; + } + } @SuppressWarnings("DataFlowIssue") //Removes that pesky "unboxing of Integer might cause NPE" warning when we already know it's not null public enum EggType { - LUNCH(Formatting.BLUE.getColorValue(), "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjU2ODExMiwKICAicHJvZmlsZUlkIiA6ICI3NzUwYzFhNTM5M2Q0ZWQ0Yjc2NmQ4ZGUwOWY4MjU0NiIsCiAgInByb2ZpbGVOYW1lIiA6ICJSZWVkcmVsIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzdhZTZkMmQzMWQ4MTY3YmNhZjk1MjkzYjY4YTRhY2Q4NzJkNjZlNzUxZGI1YTM0ZjJjYmM2NzY2YTAzNTZkMGEiCiAgICB9CiAgfQp9"), - DINNER(Formatting.GREEN.getColorValue(), "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjY0OTcwMSwKICAicHJvZmlsZUlkIiA6ICI3NGEwMzQxNWY1OTI0ZTA4YjMyMGM2MmU1NGE3ZjJhYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJNZXp6aXIiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTVlMzYxNjU4MTlmZDI4NTBmOTg1NTJlZGNkNzYzZmY5ODYzMTMxMTkyODNjMTI2YWNlMGM0Y2M0OTVlNzZhOCIKICAgIH0KICB9Cn0"), - BREAKFAST(Formatting.GOLD.getColorValue(), "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjY3MzE0OSwKICAicHJvZmlsZUlkIiA6ICJiN2I4ZTlhZjEwZGE0NjFmOTY2YTQxM2RmOWJiM2U4OCIsCiAgInByb2ZpbGVOYW1lIiA6ICJBbmFiYW5hbmFZZzciLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTQ5MzMzZDg1YjhhMzE1ZDAzMzZlYjJkZjM3ZDhhNzE0Y2EyNGM1MWI4YzYwNzRmMWI1YjkyN2RlYjUxNmMyNCIKICAgIH0KICB9Cn0"); + LUNCH(Formatting.BLUE.getColorValue(), 14, "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjU2ODExMiwKICAicHJvZmlsZUlkIiA6ICI3NzUwYzFhNTM5M2Q0ZWQ0Yjc2NmQ4ZGUwOWY4MjU0NiIsCiAgInByb2ZpbGVOYW1lIiA6ICJSZWVkcmVsIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzdhZTZkMmQzMWQ4MTY3YmNhZjk1MjkzYjY4YTRhY2Q4NzJkNjZlNzUxZGI1YTM0ZjJjYmM2NzY2YTAzNTZkMGEiCiAgICB9CiAgfQp9"), + DINNER(Formatting.GREEN.getColorValue(), 21, "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjY0OTcwMSwKICAicHJvZmlsZUlkIiA6ICI3NGEwMzQxNWY1OTI0ZTA4YjMyMGM2MmU1NGE3ZjJhYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJNZXp6aXIiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTVlMzYxNjU4MTlmZDI4NTBmOTg1NTJlZGNkNzYzZmY5ODYzMTMxMTkyODNjMTI2YWNlMGM0Y2M0OTVlNzZhOCIKICAgIH0KICB9Cn0"), + BREAKFAST(Formatting.GOLD.getColorValue(), 7, "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjY3MzE0OSwKICAicHJvZmlsZUlkIiA6ICJiN2I4ZTlhZjEwZGE0NjFmOTY2YTQxM2RmOWJiM2U4OCIsCiAgInByb2ZpbGVOYW1lIiA6ICJBbmFiYW5hbmFZZzciLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTQ5MzMzZDg1YjhhMzE1ZDAzMzZlYjJkZjM3ZDhhNzE0Y2EyNGM1MWI4YzYwNzRmMWI1YjkyN2RlYjUxNmMyNCIKICAgIH0KICB9Cn0"); private Egg egg = null; public final int color; public final String texture; + public final int resetHour; + boolean collected = false; /* When a new egg spawns in the player's range, the order of packets/messages goes like this: - set_equipment -> new egg message -> set_entity_data + set_equipment → new egg message → set_entity_data We have to set the egg to null to prevent the highlight from staying where it was before the new egg spawned, - and doing so causes the found message to get sent twice. This is the reason for the existence of this field, so that we can not send the 2nd message. - This doesn't fix the field being set twice, but that's not an issue anyway. It'd be much harder to fix the highlight issue mentioned above if it wasn't being set twice. + and doing so causes the found message to get sent twice. + This is the reason for the existence of this field, so that we don't send the 2nd message. + This doesn't fix the field being set twice, but that's not an issue anyway. + It'd be much harder to fix the highlight issue mentioned above if it wasn't being set twice. */ private long messageLastSent = 0; //This is to not create an array each time we iterate over the values public static final ObjectImmutableList<EggType> entries = ObjectImmutableList.of(EggType.values()); - EggType(int color, String texture) { + EggType(int color, int resetHour, String texture) { this.color = color; + this.resetHour = resetHour; this.texture = texture; } + public void setSeen() { + egg.seen = true; + if (!SkyblockerConfigManager.get().helpers.chocolateFactory.sendEggFoundMessages || System.currentTimeMillis() - messageLastSent < 1000) return; + if (collected) { + egg.waypoint.setFound(); + return; + } + messageLastSent = System.currentTimeMillis(); + MinecraftClient.getInstance().player.sendMessage( + Constants.PREFIX.get() + .append("Found a ") + .append(Text.literal("Chocolate " + this + " Egg") + .withColor(color)) + .append(" at " + egg.entity.getBlockPos().up(2).toShortString() + "!") + .styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/skyblocker eggFinder shareLocation " + PosUtils.toSpaceSeparatedString(egg.waypoint.pos) + " " + this)) + .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.literal("Click to share the location in chat!").formatted(Formatting.GREEN))))); + } + @Override - @SuppressWarnings("deprecation") // It's either a new dependency or a deprecated method, and I'd rather use the deprecated method public String toString() { return WordUtils.capitalizeFully(this.name()); } diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java index 0b86a58b..8ddfbf1b 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java @@ -14,6 +14,7 @@ import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.skyblock.PetCache; import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; import de.hysky.skyblocker.skyblock.item.tooltip.adders.ObtainedDateTooltip; +import de.hysky.skyblocker.utils.datafixer.ItemStackComponentizationFixer; import it.unimi.dsi.fastutil.doubles.DoubleBooleanPair; import it.unimi.dsi.fastutil.ints.IntIntPair; import it.unimi.dsi.fastutil.longs.LongBooleanPair; @@ -61,7 +62,7 @@ public final class ItemUtils { public static LiteralArgumentBuilder<FabricClientCommandSource> dumpHeldItemCommand() { return literal("dumpHeldItem").executes(context -> { - context.getSource().sendFeedback(Text.literal("[Skyblocker Debug] Held Item: " + SkyblockerMod.GSON_COMPACT.toJson(ItemStack.CODEC.encodeStart(JsonOps.INSTANCE, context.getSource().getPlayer().getMainHandStack()).getOrThrow()))); + context.getSource().sendFeedback(Text.literal("[Skyblocker Debug] Held Item: " + SkyblockerMod.GSON_COMPACT.toJson(ItemStack.CODEC.encodeStart(ItemStackComponentizationFixer.getRegistryLookup().getOps(JsonOps.INSTANCE), context.getSource().getPlayer().getMainHandStack()).getOrThrow()))); return Command.SINGLE_SUCCESS; }); } diff --git a/src/main/java/de/hysky/skyblocker/utils/PosUtils.java b/src/main/java/de/hysky/skyblocker/utils/PosUtils.java index 4ca37a83..b6205088 100644 --- a/src/main/java/de/hysky/skyblocker/utils/PosUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/PosUtils.java @@ -25,4 +25,8 @@ public final class PosUtils { public static String getPosString(BlockPos blockPos) { return blockPos.getX() + "," + blockPos.getY() + "," + blockPos.getZ(); } + + public static String toSpaceSeparatedString(BlockPos blockPos) { + return blockPos.getX() + " " + blockPos.getY() + " " + blockPos.getZ(); + } } diff --git a/src/main/java/de/hysky/skyblocker/utils/SkyblockTime.java b/src/main/java/de/hysky/skyblocker/utils/SkyblockTime.java index ec3effaf..739fd148 100644 --- a/src/main/java/de/hysky/skyblocker/utils/SkyblockTime.java +++ b/src/main/java/de/hysky/skyblocker/utils/SkyblockTime.java @@ -1,6 +1,8 @@ package de.hysky.skyblocker.utils; import de.hysky.skyblocker.utils.scheduler.Scheduler; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,39 +15,109 @@ public class SkyblockTime { public static final AtomicReference<Season> skyblockSeason = new AtomicReference<>(Season.SPRING); public static final AtomicReference<Month> skyblockMonth = new AtomicReference<>(Month.EARLY_SPRING); public static final AtomicInteger skyblockDay = new AtomicInteger(0); + public static final AtomicInteger skyblockHour = new AtomicInteger(0); private static final Logger LOGGER = LoggerFactory.getLogger("Skyblocker Time"); + //All time lengths are in milliseconds + public static final double HOUR_LENGTH = 50000; + public static final double DAY_LENGTH = HOUR_LENGTH * 24; + public static final double MONTH_LENGTH = DAY_LENGTH * 31; + public static final double SEASON_LENGTH = MONTH_LENGTH * 3; + public static final double YEAR_LENGTH = SEASON_LENGTH * 4; + + public static final Event<OnHourChange> HOUR_CHANGE = EventFactory.createArrayBacked(OnHourChange.class, listeners -> hour -> { + for (OnHourChange listener : listeners) { + listener.onHourChange(hour); + } + }); + public static final Event<OnDayChange> DAY_CHANGE = EventFactory.createArrayBacked(OnDayChange.class, listeners -> day -> { + for (OnDayChange listener : listeners) { + listener.onDayChange(day); + } + }); + public static final Event<OnMonthChange> MONTH_CHANGE = EventFactory.createArrayBacked(OnMonthChange.class, listeners -> month -> { + for (OnMonthChange listener : listeners) { + listener.onMonthChange(month); + } + }); + public static final Event<OnSeasonChange> SEASON_CHANGE = EventFactory.createArrayBacked(OnSeasonChange.class, listeners -> season -> { + for (OnSeasonChange listener : listeners) { + listener.onSeasonChange(season); + } + }); + public static final Event<OnYearChange> YEAR_CHANGE = EventFactory.createArrayBacked(OnYearChange.class, listeners -> year -> { + for (OnYearChange listener : listeners) { + listener.onYearChange(year); + } + }); + public static final Event<OnTimeUpdate> TIME_UPDATE = EventFactory.createArrayBacked(OnTimeUpdate.class, listeners -> (year, season, month, day, hour) -> { + for (OnTimeUpdate listener : listeners) { + listener.onTimeUpdate(year, season, month, day, hour); + } + }); + private SkyblockTime() { } + /** + * Updates the time and schedules a cyclic scheduler which will update the time every hour, to be run on the next hour change. + */ public static void init() { updateTime(); - //ScheduleCyclic already runs the task upon scheduling, so there's no need to call updateTime() here - Scheduler.INSTANCE.schedule(() -> Scheduler.INSTANCE.scheduleCyclic(SkyblockTime::updateTime, 1200 * 20), (int) (1200000 - (getSkyblockMillis() % 1200000)) / 50); + //scheduleCyclic already runs the task upon scheduling, so there's no need to call updateTime() in the lambda as well + //The division by 50 is to convert the time to ticks + Scheduler.INSTANCE.schedule(() -> Scheduler.INSTANCE.scheduleCyclic(SkyblockTime::updateTime, (int) (HOUR_LENGTH / 50)), (int) (HOUR_LENGTH - (getSkyblockMillis() % HOUR_LENGTH)) / 50); } public static long getSkyblockMillis() { return System.currentTimeMillis() - SKYBLOCK_EPOCH; } - private static int getSkyblockYear() { - return (int) (Math.floor(getSkyblockMillis() / 446400000.0) + 1); + private static int calculateSkyblockYear() { + return (int) (Math.floor(getSkyblockMillis() / YEAR_LENGTH) + 1); + } + + private static int calculateSkyblockMonth() { + return (int) (Math.floor(getSkyblockMillis() / MONTH_LENGTH) % 12); } - private static int getSkyblockMonth() { - return (int) (Math.floor(getSkyblockMillis() / 37200000.0) % 12); + private static int calculateSkyblockDay() { + return (int) (Math.floor(getSkyblockMillis() / DAY_LENGTH) % 31 + 1); } - private static int getSkyblockDay() { - return (int) (Math.floor(getSkyblockMillis() / 1200000.0) % 31 + 1); + private static int calculateSkyblockHour() { + return (int) (Math.floor(getSkyblockMillis() / HOUR_LENGTH) % 24); } + //This could probably be compacted by abstracting the logic into a method that takes a Supplier and a Consumer, etc. but there's really no need private static void updateTime() { - skyblockYear.set(getSkyblockYear()); - skyblockSeason.set(Season.values()[getSkyblockMonth() / 3]); - skyblockMonth.set(Month.values()[getSkyblockMonth()]); - skyblockDay.set(getSkyblockDay()); - LOGGER.info("[Skyblocker Time] Skyblock time updated to Year {}, Season {}, Month {}, Day {}", skyblockYear.get(), skyblockSeason.get(), skyblockMonth.get(), skyblockDay.get()); + int year = calculateSkyblockYear(); + if (skyblockYear.get() != year) { + skyblockYear.set(year); + YEAR_CHANGE.invoker().onYearChange(year); + } + Season season = Season.values()[calculateSkyblockMonth() / 3]; + if (skyblockSeason.get() != season) { + skyblockSeason.set(season); + SEASON_CHANGE.invoker().onSeasonChange(season); + } + Month month = Month.values()[calculateSkyblockMonth()]; + if (skyblockMonth.get() != month) { + skyblockMonth.set(month); + MONTH_CHANGE.invoker().onMonthChange(month); + } + int day = calculateSkyblockDay(); + if (skyblockDay.get() != day) { + skyblockDay.set(day); + DAY_CHANGE.invoker().onDayChange(day); + } + int hour = calculateSkyblockHour(); + if (skyblockHour.get() != hour) { + skyblockHour.set(hour); + HOUR_CHANGE.invoker().onHourChange(hour); + } + TIME_UPDATE.invoker().onTimeUpdate(year, season, month, day, hour); + LOGGER.info("[Skyblocker Time] Skyblock time updated to Year {}, Season {}, Month {}, Day {}, Hour {}", year, season, month, day, hour); } public enum Season { @@ -58,4 +130,28 @@ public class SkyblockTime { EARLY_FALL, FALL, LATE_FALL, EARLY_WINTER, WINTER, LATE_WINTER } + + public interface OnHourChange { + void onHourChange(int hour); + } + + public interface OnDayChange { + void onDayChange(int day); + } + + public interface OnMonthChange { + void onMonthChange(Month month); + } + + public interface OnSeasonChange { + void onSeasonChange(Season season); + } + + public interface OnYearChange { + void onYearChange(int year); + } + + public interface OnTimeUpdate { + void onTimeUpdate(int year, Season season, Month month, int day, int hour); + } } |
