package de.hysky.skyblocker.skyblock.item; import com.mojang.serialization.Codec; import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.events.SkyblockEvents; import de.hysky.skyblocker.mixins.accessors.SlotAccessor; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.scheduler.MessageScheduler; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.ingame.InventoryScreen; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.inventory.Inventory; import net.minecraft.inventory.SimpleInventory; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.StringNbtReader; import net.minecraft.nbt.visitor.StringNbtWriter; import net.minecraft.screen.slot.Slot; import net.minecraft.util.Identifier; import org.apache.commons.lang3.ArrayUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import java.util.stream.Collectors; /** * Opened here {@code de.hysky.skyblocker.mixins.MinecraftClientMixin#skyblocker$skyblockInventoryScreen} *
* Book button is moved here {@code de.hysky.skyblocker.mixins.InventoryScreenMixin#skyblocker} */ public class SkyblockInventoryScreen extends InventoryScreen { private static final Logger LOGGER = LoggerFactory.getLogger("Equipment"); private static final Supplier EMPTY_EQUIPMENT = () -> new ItemStack[]{ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY}; public static final ItemStack[] equipment = EMPTY_EQUIPMENT.get(); public static final ItemStack[] equipment_rift = EMPTY_EQUIPMENT.get(); private static final Codec CODEC = ItemUtils.EMPTY_ALLOWING_ITEMSTACK_CODEC.listOf(4, 8) // min size at 4 for backwards compat .xmap(itemStacks -> itemStacks.toArray(ItemStack[]::new), List::of).fieldOf("items").codec(); private static final Identifier SLOT_TEXTURE = Identifier.ofVanilla("container/slot"); private static final Identifier EMPTY_SLOT = Identifier.of(SkyblockerMod.NAMESPACE, "equipment/empty_icon"); private static final Path FOLDER = SkyblockerMod.CONFIG_DIR.resolve("equipment"); private final Slot[] equipmentSlots = new Slot[4]; private static void save(String profileId) { try { Files.createDirectories(FOLDER); } catch (IOException e) { LOGGER.error("[Skyblocker] Failed to create folder for equipment!", e); } Path resolve = FOLDER.resolve(profileId + ".nbt"); try (BufferedWriter writer = Files.newBufferedWriter(resolve)) { writer.write(new StringNbtWriter().apply(CODEC.encodeStart(NbtOps.INSTANCE, ArrayUtils.addAll(equipment, equipment_rift)).getOrThrow())); } catch (Exception e) { LOGGER.error("[Skyblocker] Failed to save Equipment data", e); } } private static void load(String profileId) { Path resolve = FOLDER.resolve(profileId + ".nbt"); CompletableFuture.supplyAsync(() -> { try (BufferedReader reader = Files.newBufferedReader(resolve)) { return CODEC.parse(NbtOps.INSTANCE, StringNbtReader.parse(reader.lines().collect(Collectors.joining()))).getOrThrow(); } catch (NoSuchFileException ignored) { } catch (Exception e) { LOGGER.error("[Skyblocker] Failed to load Equipment data", e); } return EMPTY_EQUIPMENT.get(); // Schedule on main thread to avoid any async weirdness }).thenAccept(itemStacks -> MinecraftClient.getInstance().execute(() -> { System.arraycopy(itemStacks, 0, equipment, 0, Math.min(itemStacks.length, 4)); if (itemStacks.length <= 4) return; System.arraycopy(itemStacks, 4, equipment_rift, 0, Math.clamp(itemStacks.length - 4, 0, 4)); })); } public static void initEquipment() { SkyblockEvents.PROFILE_CHANGE.register(((prevProfileId, profileId) -> { if (!prevProfileId.isEmpty()) CompletableFuture.runAsync(() -> save(prevProfileId)).thenRun(() -> load(profileId)); else load(profileId); })); ClientLifecycleEvents.CLIENT_STOPPING.register(client1 -> { String profileId = Utils.getProfileId(); if (!profileId.isBlank()) { CompletableFuture.runAsync(() -> save(profileId)); } }); } public SkyblockInventoryScreen(PlayerEntity player) { super(player); SimpleInventory inventory = new SimpleInventory(Utils.isInTheRift() ? equipment_rift: equipment); Slot slot = handler.slots.get(45); ((SlotAccessor) slot).setX(slot.x + 21); for (int i = 0; i < 4; i++) { equipmentSlots[i] = new EquipmentSlot(inventory, i, 77, 8 + i * 18); } } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { for (Slot equipmentSlot : equipmentSlots) { if (isPointWithinBounds(equipmentSlot.x, equipmentSlot.y, 16, 16, mouseX, mouseY)) { MessageScheduler.INSTANCE.sendMessageAfterCooldown("/equipment"); return true; } } return super.mouseClicked(mouseX, mouseY, button); } /** * Draws the equipment slots in the foreground layer after vanilla slots are drawn * in {@link net.minecraft.client.gui.screen.ingame.HandledScreen#render(DrawContext, int, int, float) HandledScreen#render(DrawContext, int, int, float)}. */ @Override protected void drawForeground(DrawContext context, int mouseX, int mouseY) { for (Slot equipmentSlot : equipmentSlots) { drawSlot(context, equipmentSlot); if (isPointWithinBounds(equipmentSlot.x, equipmentSlot.y, 16, 16, mouseX, mouseY)) drawSlotHighlight(context, equipmentSlot.x, equipmentSlot.y, 0); } super.drawForeground(context, mouseX, mouseY); } @Override protected void drawMouseoverTooltip(DrawContext context, int x, int y) { super.drawMouseoverTooltip(context, x, y); if (!handler.getCursorStack().isEmpty()) return; for (Slot equipmentSlot : equipmentSlots) { if (isPointWithinBounds(equipmentSlot.x, equipmentSlot.y, 16, 16, x, y) && equipmentSlot.hasStack()) { ItemStack itemStack = equipmentSlot.getStack(); context.drawTooltip(this.textRenderer, this.getTooltipFromItem(itemStack), itemStack.getTooltipData(), x, y); } } } @Override public void removed() { super.removed(); // put the handler back how it was, the handler is the same while the player is alive/in the same world Slot slot = handler.slots.get(45); ((SlotAccessor) slot).setX(slot.x - 21); } @Override protected void drawBackground(DrawContext context, float delta, int mouseX, int mouseY) { super.drawBackground(context, delta, mouseX, mouseY); for (int i = 0; i < 4; i++) { context.drawGuiTexture(SLOT_TEXTURE, x + 76 + (i == 3 ? 21 : 0), y + 7 + i * 18, 18, 18); } } @Override protected void drawSlot(DrawContext context, Slot slot) { super.drawSlot(context, slot); if (slot instanceof EquipmentSlot && !slot.hasStack()) { context.drawGuiTexture(EMPTY_SLOT, slot.x, slot.y, 16, 16); } } private static class EquipmentSlot extends Slot { public EquipmentSlot(Inventory inventory, int index, int x, int y) { super(inventory, index, x, y); } @Override public boolean canTakeItems(PlayerEntity playerEntity) { return false; } @Override public boolean canInsert(ItemStack stack) { return false; } } }