From 55714816bdefd1e786deffa08ff92469671251ca Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Sun, 31 May 2026 19:59:41 -0700 Subject: [PATCH] display missing cakes and highlight cakes that are missing --- .../java/skybakery/SkyBakeryClient.java | 68 +++++++++++++++++++ .../mixin/client/HandledScreenAccessor.java | 14 ++++ .../repository/NewYearCakeRepository.java | 10 +++ .../resources/skybakery.client.mixins.json | 3 +- 4 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 src/client/java/skybakery/mixin/client/HandledScreenAccessor.java diff --git a/src/client/java/skybakery/SkyBakeryClient.java b/src/client/java/skybakery/SkyBakeryClient.java index d3cdcdc..a4d38dc 100644 --- a/src/client/java/skybakery/SkyBakeryClient.java +++ b/src/client/java/skybakery/SkyBakeryClient.java @@ -1,19 +1,42 @@ package skybakery; +import java.util.List; import java.util.Optional; import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; +import net.minecraft.client.gui.screen.ingame.HandledScreen; import net.minecraft.client.network.ServerInfo; import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; import skybakery.assets.NewYearCakeAsset; +import skybakery.mixin.client.HandledScreenAccessor; import skybakery.repository.NewYearCakeRepository; import skybakery.utilities.ChatMenu; public class SkyBakeryClient implements ClientModInitializer { public static final String HYPIXEL_URL = "hypixel.net"; public static final NewYearCakeRepository CAKE_REPOSITORY = new NewYearCakeRepository(); + private static final int MAX_CAKE_YEAR = 500; + + private static String formatRanges(List years) { + StringBuilder sb = new StringBuilder(); + int start = years.get(0); + int end = start; + for (int i = 1; i < years.size(); i++) { + if (years.get(i) == end + 1) { + end = years.get(i); + } else { + sb.append(start == end ? start : start + "-" + end).append(", "); + start = end = years.get(i); + } + } + sb.append(start == end ? start : start + "-" + end); + return sb.toString(); + } @Override public void onInitializeClient() { @@ -41,6 +64,51 @@ public class SkyBakeryClient implements ClientModInitializer { CAKE_REPOSITORY.save(); }); + ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> { + dispatcher.register( + ClientCommandManager.literal("skybakery") + .then(ClientCommandManager.literal("missing") + .executes(ctx -> { + List missing = CAKE_REPOSITORY.getMissingYears(MAX_CAKE_YEAR); + if (missing.isEmpty()) { + ChatMenu.send("You have all cakes from Year 1 to " + MAX_CAKE_YEAR + "!"); + } else { + ChatMenu.send("Missing " + missing.size() + "/" + MAX_CAKE_YEAR + " cakes: " + formatRanges(missing)); + } + return 1; + })) + .then(ClientCommandManager.literal("flush") + .executes(ctx -> { + CAKE_REPOSITORY.save(); + ChatMenu.send("Cake collection flushed to disk."); + return 1; + })) + ); + }); + + ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> { + if (!(screen instanceof HandledScreen handledScreen)) return; + if (!screen.getTitle().getString().contains("Auction")) return; + + ScreenEvents.afterRender(screen).register((s, drawContext, mouseX, mouseY, tickDelta) -> { + HandledScreenAccessor accessor = (HandledScreenAccessor) handledScreen; + for (Slot slot : handledScreen.getScreenHandler().slots) { + ItemStack stack = slot.getStack(); + if (stack.isEmpty()) continue; + if (!stack.getName().getString().contains("New Year Cake")) continue; + + Optional asset = NewYearCakeAsset.from(stack); + if (asset.isEmpty()) continue; + + if (CAKE_REPOSITORY.getByYear(asset.get().year()).isEmpty()) { + int x = accessor.getGuiLeft() + slot.x; + int y = accessor.getGuiTop() + slot.y; + drawContext.fill(x, y, x + 16, y + 16, 0x80FF0000); + } + } + }); + }); + ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> { if (client.player == null) return; diff --git a/src/client/java/skybakery/mixin/client/HandledScreenAccessor.java b/src/client/java/skybakery/mixin/client/HandledScreenAccessor.java new file mode 100644 index 0000000..4ba0bce --- /dev/null +++ b/src/client/java/skybakery/mixin/client/HandledScreenAccessor.java @@ -0,0 +1,14 @@ +package skybakery.mixin.client; + +import net.minecraft.client.gui.screen.ingame.HandledScreen; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(HandledScreen.class) +public interface HandledScreenAccessor { + @Accessor("x") + int getGuiLeft(); + + @Accessor("y") + int getGuiTop(); +} diff --git a/src/client/java/skybakery/repository/NewYearCakeRepository.java b/src/client/java/skybakery/repository/NewYearCakeRepository.java index d066805..3294365 100644 --- a/src/client/java/skybakery/repository/NewYearCakeRepository.java +++ b/src/client/java/skybakery/repository/NewYearCakeRepository.java @@ -59,6 +59,16 @@ public class NewYearCakeRepository { return cakes.isEmpty(); } + public List getMissingYears(int maxYear) { + List missing = new ArrayList<>(); + for (int year = 1; year <= maxYear; year++) { + List forYear = cakes.get(year); + if (forYear == null || forYear.isEmpty()) + missing.add(year); + } + return missing; + } + public void load() { if (!Files.exists(file)) return; diff --git a/src/client/resources/skybakery.client.mixins.json b/src/client/resources/skybakery.client.mixins.json index f96a58d..42e3a87 100644 --- a/src/client/resources/skybakery.client.mixins.json +++ b/src/client/resources/skybakery.client.mixins.json @@ -3,7 +3,8 @@ "package": "skybakery.mixin.client", "compatibilityLevel": "JAVA_21", "client": [ - "ExampleClientMixin" + "ExampleClientMixin", + "HandledScreenAccessor" ], "injectors": { "defaultRequire": 1