aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/de
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/de')
-rw-r--r--src/main/java/de/hysky/skyblocker/SkyblockerMod.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java6
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java17
-rw-r--r--src/main/java/de/hysky/skyblocker/mixin/HandledScreenProviderMixin.java12
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonManager.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java48
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockCraftingTableScreen.java194
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockCraftingTableScreenHandler.java69
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java222
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java24
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java31
12 files changed, 595 insertions, 35 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
index 197b3c80..75918fa9 100644
--- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
+++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
@@ -26,6 +26,7 @@ import de.hysky.skyblocker.skyblock.garden.FarmingHud;
import de.hysky.skyblocker.skyblock.garden.LowerSensitivity;
import de.hysky.skyblocker.skyblock.garden.VisitorHelper;
import de.hysky.skyblocker.skyblock.item.*;
+import de.hysky.skyblocker.skyblock.item.tooltip.AccessoriesHelper;
import de.hysky.skyblocker.skyblock.item.tooltip.BackpackPreview;
import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip;
import de.hysky.skyblocker.skyblock.itemlist.ItemRepository;
@@ -104,6 +105,7 @@ public class SkyblockerMod implements ClientModInitializer {
PlayerHeadHashCache.init();
HotbarSlotLock.init();
ItemTooltip.init();
+ AccessoriesHelper.init();
WikiLookup.init();
FairySouls.init();
Relics.init();
diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
index 29678683..06ac748a 100644
--- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
@@ -160,6 +160,9 @@ public class SkyblockerConfig {
public boolean betterPartyFinder = true;
@SerialEntry
+ public boolean fancyCraftingTable = true;
+
+ @SerialEntry
public boolean backpackPreviewWithoutShift = false;
@SerialEntry
@@ -558,6 +561,9 @@ public class SkyblockerConfig {
@SerialEntry
public boolean enableExoticTooltip = true;
+
+ @SerialEntry
+ public boolean enableAccessoriesHelper = true;
}
public static class ItemInfoDisplay {
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java
index fe73a6a7..e310cb85 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java
@@ -42,6 +42,13 @@ public class GeneralCategory {
.controller(ConfigUtils::createBooleanController)
.build())
.option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.general.fancyCraftingTable"))
+ .binding(defaults.general.fancyCraftingTable,
+ () -> config.general.fancyCraftingTable,
+ newValue -> config.general.fancyCraftingTable = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<Boolean>createBuilder()
.name(Text.translatable("text.autoconfig.skyblocker.option.general.backpackPreviewWithoutShift"))
.binding(defaults.general.backpackPreviewWithoutShift,
() -> config.general.backpackPreviewWithoutShift,
@@ -442,6 +449,16 @@ public class GeneralCategory {
newValue -> config.general.itemTooltip.enableExoticTooltip = newValue)
.controller(ConfigUtils::createBooleanController)
.build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper"))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[0]"), Text.literal("\n\n✔ Collected").formatted(Formatting.GREEN), Text.translatable("text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[1]"),
+ Text.literal("\n✦ Upgrade").withColor(0x218bff), Text.translatable("text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[2]"), Text.literal("\n↑ Upgradable").withColor(0xf8d048), Text.translatable("text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[3]"),
+ Text.literal("\n↓ Downgrade").formatted(Formatting.GRAY), Text.translatable("text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[4]"), Text.literal("\n✖ Missing").formatted(Formatting.RED), Text.translatable("text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[5]")))
+ .binding(defaults.general.itemTooltip.enableAccessoriesHelper,
+ () -> config.general.itemTooltip.enableAccessoriesHelper,
+ newValue -> config.general.itemTooltip.enableAccessoriesHelper = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
.build())
//Item Info Display
diff --git a/src/main/java/de/hysky/skyblocker/mixin/HandledScreenProviderMixin.java b/src/main/java/de/hysky/skyblocker/mixin/HandledScreenProviderMixin.java
index 94eb53a5..975c9c51 100644
--- a/src/main/java/de/hysky/skyblocker/mixin/HandledScreenProviderMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixin/HandledScreenProviderMixin.java
@@ -3,6 +3,9 @@ package de.hysky.skyblocker.mixin;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.skyblock.dungeon.partyfinder.PartyFinderScreen;
+import de.hysky.skyblocker.skyblock.item.SkyblockCraftingTableScreenHandler;
+import de.hysky.skyblocker.skyblock.item.SkyblockCraftingTableScreen;
+import de.hysky.skyblocker.utils.Utils;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.ingame.HandledScreens;
import net.minecraft.client.network.ClientPlayerEntity;
@@ -19,11 +22,11 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
public interface HandledScreenProviderMixin<T extends ScreenHandler> {
@Inject(method = "open", at = @At("HEAD"), cancellable = true)
default void skyblocker$open(Text name, ScreenHandlerType<T> type, MinecraftClient client, int id, CallbackInfo ci) {
- if (!SkyblockerConfigManager.get().general.betterPartyFinder) return;
ClientPlayerEntity player = client.player;
if (player == null) return;
+ if (!Utils.isOnSkyblock()) return;
T screenHandler = type.create(id, player.getInventory());
- if (screenHandler instanceof GenericContainerScreenHandler containerScreenHandler && PartyFinderScreen.possibleInventoryNames.contains(name.getString().toLowerCase())) {
+ if (SkyblockerConfigManager.get().general.betterPartyFinder && screenHandler instanceof GenericContainerScreenHandler containerScreenHandler && PartyFinderScreen.possibleInventoryNames.contains(name.getString().toLowerCase())) {
if (client.currentScreen != null) {
String lowerCase = client.currentScreen.getTitle().getString().toLowerCase();
if (lowerCase.contains("group builder")) return;
@@ -42,6 +45,11 @@ public interface HandledScreenProviderMixin<T extends ScreenHandler> {
}
ci.cancel();
+ } else if (SkyblockerConfigManager.get().general.fancyCraftingTable && screenHandler instanceof GenericContainerScreenHandler containerScreenHandler && name.getString().toLowerCase().contains("craft item")) {
+ SkyblockCraftingTableScreenHandler skyblockCraftingTableScreenHandler = new SkyblockCraftingTableScreenHandler(containerScreenHandler, player.getInventory());
+ client.player.currentScreenHandler = skyblockCraftingTableScreenHandler;
+ client.setScreen(new SkyblockCraftingTableScreen(skyblockCraftingTableScreenHandler, player.getInventory(), Text.literal("Craft Item")));
+ ci.cancel();
}
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonManager.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonManager.java
index d5d57e70..4f5da8de 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonManager.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonManager.java
@@ -86,7 +86,7 @@ import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.lit
public class DungeonManager {
protected static final Logger LOGGER = LoggerFactory.getLogger(DungeonManager.class);
private static final String DUNGEONS_PATH = "dungeons";
- private static final Path CUSTOM_WAYPOINTS_DIR = SkyblockerMod.CONFIG_DIR.resolve("custom_secret_waypoints.json");
+ private static Path CUSTOM_WAYPOINTS_DIR;
private static final Pattern KEY_FOUND = Pattern.compile("^(?:\\[.+] )?(?<name>\\w+) has obtained (?<type>Wither|Blood) Key!$");
/**
* Maps the block identifier string to a custom numeric block id used in dungeon rooms data.
@@ -214,6 +214,7 @@ public class DungeonManager {
* Use {@link #isRoomsLoaded()} to check for completion of loading.
*/
public static void init() {
+ CUSTOM_WAYPOINTS_DIR = SkyblockerMod.CONFIG_DIR.resolve("custom_secret_waypoints.json");
if (!SkyblockerConfigManager.get().locations.dungeons.secretWaypoints.enableRoomMatching) {
return;
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java
index b4ffd409..3297ef5a 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java
@@ -43,8 +43,8 @@ public class DwarvenHud {
"First Event",
"(?:Ruby|Amber|Sapphire|Jade|Amethyst|Topaz) Gemstone Collector",
"(?:Amber|Sapphire|Jade|Amethyst|Topaz) Crystal Hunter",
- "Chest Looter").map(s -> Pattern.compile("(" + s + "): (\\d+\\.?\\d*%|DONE)"))
- .collect(Collectors.toList());
+ "Chest Looter").map(s -> Pattern.compile("(" + s + "): (\\d+\\.?\\d*%|DONE)")
+ ).collect(Collectors.toList());
public static final Pattern MITHRIL_PATTERN = Pattern.compile("Mithril Powder: [0-9,]+");
public static final Pattern GEMSTONE_PATTERN = Pattern.compile("Gemstone Powder: [0-9,]+");
@@ -52,10 +52,13 @@ public class DwarvenHud {
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal("skyblocker")
.then(ClientCommandManager.literal("hud")
.then(ClientCommandManager.literal("dwarven")
- .executes(Scheduler.queueOpenScreenCommand(DwarvenHudConfigScreen::new))))));
+ .executes(Scheduler.queueOpenScreenCommand(DwarvenHudConfigScreen::new))
+ )
+ )
+ ));
HudRenderCallback.EVENT.register((context, tickDelta) -> {
- if ((!SkyblockerConfigManager.get().locations.dwarvenMines.dwarvenHud.enabledCommissions && !SkyblockerConfigManager.get().locations.dwarvenMines.dwarvenHud.enabledPowder)
+ if (!SkyblockerConfigManager.get().locations.dwarvenMines.dwarvenHud.enabledCommissions && !SkyblockerConfigManager.get().locations.dwarvenMines.dwarvenHud.enabledPowder
|| client.options.playerListKey.isPressed()
|| client.player == null
|| (!Utils.isInDwarvenMines() && !Utils.isInCrystalHollows())) {
@@ -103,24 +106,21 @@ public class DwarvenHud {
percentage = 100f;
}
- context
- .drawTextWithShadow(client.textRenderer,
- Text.literal(commission.commission + ": ").formatted(Formatting.AQUA)
- .append(Text.literal(commission.progression).formatted(Colors.hypixelProgressColor(percentage))),
- comHudX + 5, comHudY + y + 5, 0xFFFFFFFF);
+ context.drawTextWithShadow(client.textRenderer,
+ Text.literal(commission.commission + ": ").formatted(Formatting.AQUA)
+ .append(Text.literal(commission.progression).formatted(Colors.hypixelProgressColor(percentage))),
+ comHudX + 5, comHudY + y + 5, 0xFFFFFFFF);
y += 20;
}
}
- if(SkyblockerConfigManager.get().locations.dwarvenMines.dwarvenHud.enabledPowder) {
+ if (SkyblockerConfigManager.get().locations.dwarvenMines.dwarvenHud.enabledPowder) {
//render mithril powder then gemstone
- context
- .drawTextWithShadow(client.textRenderer,
- Text.literal("Mithril: " + mithrilPowder).formatted(Formatting.AQUA),
- powderHudX + 5, powderHudY + 5, 0xFFFFFFFF);
- context
- .drawTextWithShadow(client.textRenderer,
- Text.literal("Gemstone: " + gemStonePowder).formatted(Formatting.DARK_PURPLE),
- powderHudX + 5, powderHudY + 25, 0xFFFFFFFF);
+ context.drawTextWithShadow(client.textRenderer,
+ Text.literal("Mithril: " + mithrilPowder).formatted(Formatting.AQUA),
+ powderHudX + 5, powderHudY + 5, 0xFFFFFFFF);
+ context.drawTextWithShadow(client.textRenderer,
+ Text.literal("Gemstone: " + gemStonePowder).formatted(Formatting.DARK_PURPLE),
+ powderHudX + 5, powderHudY + 25, 0xFFFFFFFF);
}
}
@@ -146,22 +146,22 @@ public class DwarvenHud {
hcw.update();
hcw.setX(comHudX);
hcw.setY(comHudY);
- hcw.render(context,
- SkyblockerConfigManager.get().general.tabHud.enableHudBackground);
+ hcw.render(context, SkyblockerConfigManager.get().general.tabHud.enableHudBackground);
}
if (SkyblockerConfigManager.get().locations.dwarvenMines.dwarvenHud.enabledPowder) {
hpw.update();
hpw.setX(powderHudX);
hpw.setY(powderHudY);
- hpw.render(context,
- SkyblockerConfigManager.get().general.tabHud.enableHudBackground);
+ hpw.render(context, SkyblockerConfigManager.get().general.tabHud.enableHudBackground);
}
}
public static void update() {
- if (client.player == null || client.getNetworkHandler() == null || (!SkyblockerConfigManager.get().locations.dwarvenMines.dwarvenHud.enabledCommissions && !SkyblockerConfigManager.get().locations.dwarvenMines.dwarvenHud.enabledPowder)
- || (!Utils.isInCrystalHollows() && !Utils.isInDwarvenMines()))
+ if (client.player == null || client.getNetworkHandler() == null
+ || !SkyblockerConfigManager.get().locations.dwarvenMines.dwarvenHud.enabledCommissions && !SkyblockerConfigManager.get().locations.dwarvenMines.dwarvenHud.enabledPowder
+ || !Utils.isInCrystalHollows() && !Utils.isInDwarvenMines()) {
return;
+ }
commissionList = new ArrayList<>();
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java
index bde8b3ea..6640d413 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java
@@ -78,7 +78,7 @@ public class VisitorHelper {
}
public static void onSlotClick(Slot slot, int slotId, String title) {
- if (slotId == 29 || slotId == 13) {
+ if (slotId == 29 || slotId == 13 || slotId == 33) {
itemMap.remove(title);
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockCraftingTableScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockCraftingTableScreen.java
new file mode 100644
index 00000000..14ddb238
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockCraftingTableScreen.java
@@ -0,0 +1,194 @@
+package de.hysky.skyblocker.skyblock.item;
+
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.skyblock.itemlist.ItemListWidget;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.ButtonTextures;
+import net.minecraft.client.gui.screen.ingame.HandledScreen;
+import net.minecraft.client.gui.screen.recipebook.RecipeBookWidget;
+import net.minecraft.client.gui.tooltip.Tooltip;
+import net.minecraft.client.gui.widget.TexturedButtonWidget;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.entity.player.PlayerInventory;
+import net.minecraft.inventory.SimpleInventory;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+import net.minecraft.recipe.Recipe;
+import net.minecraft.recipe.RecipeEntry;
+import net.minecraft.recipe.RecipeMatcher;
+import net.minecraft.recipe.book.RecipeBookCategory;
+import net.minecraft.screen.AbstractRecipeScreenHandler;
+import net.minecraft.screen.ScreenHandlerType;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.screen.slot.SlotActionType;
+import net.minecraft.text.Text;
+import net.minecraft.util.Identifier;
+
+public class SkyblockCraftingTableScreen extends HandledScreen<SkyblockCraftingTableScreenHandler> {
+ private static final Identifier TEXTURE = new Identifier("textures/gui/container/crafting_table.png");
+ protected static final ButtonTextures MORE_CRAFTS_TEXTURES = new ButtonTextures(
+ new Identifier(SkyblockerMod.NAMESPACE, "quick_craft/more_button"),
+ new Identifier(SkyblockerMod.NAMESPACE, "quick_craft/more_button_disabled"),
+ new Identifier(SkyblockerMod.NAMESPACE, "quick_craft/more_button_highlighted")
+ );
+
+ protected static final Identifier QUICK_CRAFT = new Identifier(SkyblockerMod.NAMESPACE, "quick_craft/quick_craft_overlay");
+ private final ItemListWidget recipeBook = new ItemListWidget();
+ private boolean narrow;
+ private TexturedButtonWidget moreCraftsButton;
+
+ public SkyblockCraftingTableScreen(SkyblockCraftingTableScreenHandler handler, PlayerInventory inventory, Text title) {
+ super(handler, inventory, title);
+ this.backgroundWidth += 22;
+ }
+
+ @Override
+ protected void init() {
+ super.init();
+ this.narrow = this.width < 379;
+ this.recipeBook.initialize(this.width, this.height, this.client, this.narrow, new DummyRecipeScreenHandler());
+ this.x = this.recipeBook.findLeftEdge(this.width, this.backgroundWidth) + 11;
+ this.addDrawableChild(new TexturedButtonWidget(this.x + 5, this.height / 2 - 49, 20, 18, RecipeBookWidget.BUTTON_TEXTURES, button -> {
+ this.recipeBook.toggleOpen();
+ this.x = this.recipeBook.findLeftEdge(this.width, this.backgroundWidth) + 11;
+ button.setPosition(this.x + 5, this.height / 2 - 49);
+ if (moreCraftsButton != null) moreCraftsButton.setPosition(this.x + 174, this.y + 62);
+ }));
+ moreCraftsButton = new TexturedButtonWidget(this.x + 174, y + 62, 16, 16, MORE_CRAFTS_TEXTURES,
+ button -> this.onMouseClick(handler.slots.get(26), handler.slots.get(26).id, 0, SlotActionType.PICKUP));
+ moreCraftsButton.setTooltipDelay(250);
+ moreCraftsButton.setTooltip(Tooltip.of(Text.literal("More Crafts")));
+ this.addDrawableChild(moreCraftsButton);
+ assert (client != null ? client.player : null) != null;
+ client.player.currentScreenHandler = handler; // recipe book replaces it with the Dummy one fucking DUMBASS
+ this.addSelectableChild(this.recipeBook);
+ this.setInitialFocus(this.recipeBook);
+ this.titleX = 29;
+ }
+
+ @Override
+ public void handledScreenTick() {
+ super.handledScreenTick();
+ this.recipeBook.update();
+ if (moreCraftsButton == null) return;
+ ItemStack stack = handler.slots.get(26).getStack();
+ moreCraftsButton.active = stack.isEmpty() || stack.isOf(Items.PLAYER_HEAD);
+ }
+
+ @Override
+ public void render(DrawContext context, int mouseX, int mouseY, float delta) {
+ if (this.recipeBook.isOpen() && this.narrow) {
+ this.renderBackground(context, mouseX, mouseY, delta);
+ this.recipeBook.render(context, mouseX, mouseY, delta);
+ } else {
+ super.render(context, mouseX, mouseY, delta);
+ this.recipeBook.render(context, mouseX, mouseY, delta);
+ this.recipeBook.drawGhostSlots(context, this.x, this.y, true, delta);
+ }
+ this.drawMouseoverTooltip(context, mouseX, mouseY);
+ this.recipeBook.drawTooltip(context, this.x, this.y, mouseX, mouseY);
+ }
+
+
+ @Override
+ protected void drawSlot(DrawContext context, Slot slot) {
+ if (slot.id == 23 && slot.getStack().isOf(Items.BARRIER)) return;
+ super.drawSlot(context, slot);
+ }
+
+ @Override
+ protected void drawBackground(DrawContext context, float delta, int mouseX, int mouseY) {
+ int i = this.x;
+ int j = (this.height - this.backgroundHeight) / 2;
+ context.drawTexture(TEXTURE, i, j, 0, 0, this.backgroundWidth, this.backgroundHeight);
+ context.drawGuiTexture(QUICK_CRAFT, i + 173, j, 0, 25, 84);
+ }
+
+ @Override
+ protected boolean isPointWithinBounds(int x, int y, int width, int height, double pointX, double pointY) {
+ return (!this.narrow || !this.recipeBook.isOpen()) && super.isPointWithinBounds(x, y, width, height, pointX, pointY);
+ }
+
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY, int button) {
+ if (this.recipeBook.mouseClicked(mouseX, mouseY, button)) {
+ this.setFocused(this.recipeBook);
+ return true;
+ }
+ if (this.narrow && this.recipeBook.isOpen()) {
+ return true;
+ }
+ return super.mouseClicked(mouseX, mouseY, button);
+ }
+
+ @Override
+ protected boolean isClickOutsideBounds(double mouseX, double mouseY, int left, int top, int button) {
+ boolean bl = mouseX < (double) left || mouseY < (double) top || mouseX >= (double) (left + this.backgroundWidth) || mouseY >= (double) (top + this.backgroundHeight);
+ return this.recipeBook.isClickOutsideBounds(mouseX, mouseY, this.x, this.y, this.backgroundWidth, this.backgroundHeight, button) && bl;
+ }
+
+ @Override
+ protected void onMouseClick(Slot slot, int slotId, int button, SlotActionType actionType) {
+ super.onMouseClick(slot, slotId, button, actionType);
+ this.recipeBook.slotClicked(slot);
+ }
+
+
+ static class DummyRecipeScreenHandler extends AbstractRecipeScreenHandler<SimpleInventory> {
+
+ public DummyRecipeScreenHandler() {
+ super(ScreenHandlerType.GENERIC_9X6, -69);
+ }
+
+ @Override
+ public void populateRecipeFinder(RecipeMatcher finder) {}
+
+ @Override
+ public void clearCraftingSlots() {}
+
+ @Override
+ public boolean matches(RecipeEntry<? extends Recipe<SimpleInventory>> recipe) {
+ return false;
+ }
+
+ @Override
+ public int getCraftingResultSlotIndex() {
+ return 0;
+ }
+
+ @Override
+ public int getCraftingWidth() {
+ return 0;
+ }
+
+ @Override
+ public int getCraftingHeight() {
+ return 0;
+ }
+
+ @Override
+ public int getCraftingSlotCount() {
+ return 0;
+ }
+
+ @Override
+ public RecipeBookCategory getCategory() {
+ return RecipeBookCategory.CRAFTING;
+ }
+
+ @Override
+ public boolean canInsertIntoSlot(int index) {
+ return false;
+ }
+
+ @Override
+ public ItemStack quickMove(PlayerEntity player, int slot) {
+ return ItemStack.EMPTY;
+ }
+
+ @Override
+ public boolean canUse(PlayerEntity player) {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockCraftingTableScreenHandler.java b/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockCraftingTableScreenHandler.java
new file mode 100644
index 00000000..04974ade
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockCraftingTableScreenHandler.java
@@ -0,0 +1,69 @@
+package de.hysky.skyblocker.skyblock.item;
+
+import net.minecraft.entity.player.PlayerInventory;
+import net.minecraft.inventory.Inventory;
+import net.minecraft.screen.GenericContainerScreenHandler;
+import net.minecraft.screen.ScreenHandlerType;
+import net.minecraft.screen.slot.Slot;
+
+import java.util.Arrays;
+
+public class SkyblockCraftingTableScreenHandler extends GenericContainerScreenHandler {
+
+ private static final int[] normalSlots = new int[]{
+ 10, 11, 12, 16,
+ 19, 20, 21, 23, 25,
+ 28, 29, 30, 34
+ };
+
+ public SkyblockCraftingTableScreenHandler(ScreenHandlerType<?> type, int syncId, PlayerInventory playerInventory, Inventory inventory, int rows) {
+ super(type, syncId, playerInventory, inventory, rows);
+ for (int i = 0; i < rows * 9; i++) {
+ Slot originalSlot = slots.get(i);
+ if (Arrays.binarySearch(normalSlots, i) >= 0) {
+ int[] coords = getCoords(i);
+ Slot slot = new Slot(originalSlot.inventory, originalSlot.getIndex(), coords[0], coords[1]);
+ slot.id = i;
+ slots.set(i, slot);
+ } else {
+ DisabledSlot slot = new DisabledSlot(originalSlot.inventory, originalSlot.getIndex(), originalSlot.x, originalSlot.y);
+ slot.id = i;
+ slots.set(i, slot);
+ }
+ }
+ int yOffset = (rows - 4) * 18 + 19;
+ for (int i = rows * 9; i < slots.size(); i++) {
+ Slot originalSlot = slots.get(i);
+ Slot slot = new Slot(originalSlot.inventory, originalSlot.getIndex(), originalSlot.x, originalSlot.y - yOffset);
+ slot.id = i;
+ slots.set(i, slot);
+ }
+ }
+
+ public SkyblockCraftingTableScreenHandler(GenericContainerScreenHandler handler, PlayerInventory playerInventory) {
+ this(handler.getType(), handler.syncId, playerInventory, handler.getInventory(), handler.getRows());
+ }
+
+ private int[] getCoords(int slot) {
+ if (slot == 23) return new int[]{124, 35};
+ if (slot == 16 || slot == 25 || slot == 34) {
+ int y = (slot / 9 - 1) * 18 + 8;
+ return new int[]{174, y};
+ }
+ int gridX = slot % 9 - 1;
+ int gridY = slot / 9 - 1;
+ return new int[]{30 + gridX * 18, 17 + gridY * 18};
+ }
+
+ public static class DisabledSlot extends Slot {
+
+ public DisabledSlot(Inventory inventory, int index, int x, int y) {
+ super(inventory, index, x, y);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java
new file mode 100644
index 00000000..69bc6f1c
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java
@@ -0,0 +1,222 @@
+package de.hysky.skyblocker.skyblock.item.tooltip;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.OptionalInt;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Predicate;
+import java.util.function.ToIntFunction;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.slf4j.Logger;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.mojang.logging.LogUtils;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.JsonOps;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+import com.mojang.util.UndashedUuid;
+
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.utils.ItemUtils;
+import de.hysky.skyblocker.utils.Utils;
+import it.unimi.dsi.fastutil.Pair;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
+import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.screen.ingame.GenericContainerScreen;
+import net.minecraft.screen.GenericContainerScreenHandler;
+import net.minecraft.screen.slot.Slot;
+
+public class AccessoriesHelper {
+ private static final Logger LOGGER = LogUtils.getLogger();
+ private static final Path FILE = SkyblockerMod.CONFIG_DIR.resolve("collected_accessories.json");
+ private static final Pattern ACCESSORY_BAG_TITLE = Pattern.compile("Accessory Bag \\((?<page>\\d+)/\\d+\\)");
+ //UUID -> Profile Id & Data
+ private static final Object2ObjectOpenHashMap<String, Object2ObjectOpenHashMap<String, ProfileAccessoryData>> COLLECTED_ACCESSORIES = new Object2ObjectOpenHashMap<>();
+ private static final Predicate<String> NON_EMPTY = s -> !s.isEmpty();
+ private static final Predicate<Accessory> HAS_FAMILY = Accessory::hasFamily;
+ private static final ToIntFunction<Accessory> ACCESSORY_TIER = Accessory::tier;
+
+ private static Map<String, Accessory> ACCESSORY_DATA = new Object2ObjectOpenHashMap<>();
+ //remove??
+ private static CompletableFuture<Void> loaded;
+
+ public static void init() {
+ ClientLifecycleEvents.CLIENT_STARTED.register((_client) -> load());
+ ClientLifecycleEvents.CLIENT_STOPPING.register((_client) -> save());
+ ScreenEvents.BEFORE_INIT.register((_client, screen, _scaledWidth, _scaledHeight) -> {
+ if (Utils.isOnSkyblock() && TooltipInfoType.ACCESSORIES.isTooltipEnabled() && !Utils.getProfileId().isEmpty() && screen instanceof GenericContainerScreen genericContainerScreen) {
+ Matcher matcher = ACCESSORY_BAG_TITLE.matcher(genericContainerScreen.getTitle().getString());
+
+ if (matcher.matches()) {
+ ScreenEvents.afterTick(screen).register(_screen -> {
+ GenericContainerScreenHandler handler = genericContainerScreen.getScreenHandler();
+
+ collectAccessories(handler.slots.subList(0, handler.getRows() * 9), Integer.parseInt(matcher.group("page")));
+ });
+ }
+ }
+ });
+ }
+
+ //Note: JsonOps.COMPRESSED must be used if you're using maps with non-string keys
+ private static void load() {
+ loaded = CompletableFuture.runAsync(() -> {
+ try (BufferedReader reader = Files.newBufferedReader(FILE)) {
+ COLLECTED_ACCESSORIES.putAll(ProfileAccessoryData.SERIALIZATION_CODEC.parse(JsonOps.COMPRESSED, JsonParser.parseReader(reader)).result().orElseThrow());
+ } catch (NoSuchFileException ignored) {
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker Accessory Helper] Failed to load accessory file!", e);
+ }
+ });
+ }
+
+ private static void save() {
+ try (BufferedWriter writer = Files.newBufferedWriter(FILE)) {
+ SkyblockerMod.GSON.toJson(ProfileAccessoryData.SERIALIZATION_CODEC.encodeStart(JsonOps.COMPRESSED, COLLECTED_ACCESSORIES).result().orElseThrow(), writer);
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker Accessory Helper] Failed to save accessory file!", e);
+ }
+ }
+
+ private static void collectAccessories(List<Slot> slots, int page) {
+ //Is this even needed?
+ if (!loaded.isDone()) return;
+
+ List<String> accessoryIds = slots.stream()
+ .map(Slot::getStack)
+ .map(ItemUtils::getItemId)
+ .filter(NON_EMPTY)
+ .toList();
+
+ String uuid = UndashedUuid.toString(MinecraftClient.getInstance().getSession().getUuidOrNull());
+
+ COLLECTED_ACCESSORIES.computeIfAbsent(uuid, _uuid -> new Object2ObjectOpenHashMap<>()).computeIfAbsent(Utils.getProfileId(), profileId -> ProfileAccessoryData.createDefault()).pages()
+ .put(page, new ObjectOpenHashSet<>(accessoryIds));
+ }
+
+ static Pair<AccessoryReport, String> calculateReport4Accessory(String accessoryId) {
+ if (!ACCESSORY_DATA.containsKey(accessoryId) || Utils.getProfileId().isEmpty()) return Pair.of(AccessoryReport.INELIGIBLE, null);
+
+ Accessory accessory = ACCESSORY_DATA.get(accessoryId);
+ String uuid = UndashedUuid.toString(MinecraftClient.getInstance().getSession().getUuidOrNull());
+ Set<Accessory> collectedAccessories = COLLECTED_ACCESSORIES.computeIfAbsent(uuid, _uuid -> new Object2ObjectOpenHashMap<>()).computeIfAbsent(Utils.getProfileId(), profileId -> ProfileAccessoryData.createDefault()).pages().values().stream()
+ .flatMap(ObjectOpenHashSet::stream)
+ .filter(ACCESSORY_DATA::containsKey)
+ .map(ACCESSORY_DATA::get)
+ .collect(Collectors.toSet());
+
+ //If the player has this accessory, and it doesn't belong to a family
+ if (collectedAccessories.contains(accessory) && accessory.family().isEmpty()) return Pair.of(AccessoryReport.HAS_HIGHEST_TIER, null);
+
+ Predicate<Accessory> HAS_SAME_FAMILY = accessory::hasSameFamily;
+ Set<Accessory> collectedAccessoriesInTheSameFamily = collectedAccessories.stream()
+ .filter(HAS_FAMILY)
+ .filter(HAS_SAME_FAMILY)
+ .collect(Collectors.toSet());
+
+ //If the player doesn't have any collected accessories with same family
+ if (collectedAccessoriesInTheSameFamily.isEmpty()) return Pair.of(AccessoryReport.MISSING, null);
+
+ Set<Accessory> accessoriesInTheSameFamily = ACCESSORY_DATA.values().stream()
+ .filter(HAS_FAMILY)
+ .filter(HAS_SAME_FAMILY)
+ .collect(Collectors.toSet());
+
+ ///If the player has the highest tier accessory in this family
+ //Take the accessories in the same family as {@code accessory}, then get the one with the highest tier
+ Optional<Accessory> highestTierOfFamily = accessoriesInTheSameFamily.stream()
+ .max(Comparator.comparingInt(ACCESSORY_TIER));
+ int maxTierInFamily = highestTierOfFamily.orElse(Accessory.EMPTY).tier();
+
+ if (collectedAccessoriesInTheSameFamily.stream().anyMatch(ca -> ca.tier() == maxTierInFamily)) return Pair.of(AccessoryReport.HAS_HIGHEST_TIER, null);
+
+ //If this accessory is a higher tier than all the other collected accessories in the same family
+ OptionalInt highestTierOfAllCollectedInFamily = collectedAccessoriesInTheSameFamily.stream()
+ .mapToInt(ACCESSORY_TIER)
+ .max();
+
+ if (accessory.tier() > highestTierOfAllCollectedInFamily.getAsInt()) return Pair.of(AccessoryReport.IS_GREATER_TIER, String.format("(%d→%d/%d)", highestTierOfAllCollectedInFamily.orElse(0), accessory.tier(), maxTierInFamily));
+
+ //If this accessory is a lower tier than one already obtained from same family
+ if (accessory.tier() < highestTierOfAllCollectedInFamily.getAsInt()) return Pair.of(AccessoryReport.OW