diff options
Diffstat (limited to 'src')
11 files changed, 275 insertions, 2 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java index 1f766000..f1eb4321 100644 --- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java +++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java @@ -186,6 +186,7 @@ public class SkyblockerMod implements ClientModInitializer { DojoManager.init(); RenderHelper.init(); FancyStatusBars.init(); + SkyblockInventoryScreen.initEquipment(); EventNotifications.init(); containerSolverManager.init(); statusBarTracker.init(); diff --git a/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java index a2a0f815..889b253a 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java @@ -68,6 +68,13 @@ public class UIAndVisualsCategory { newValue -> config.uiAndVisuals.hideStatusEffectOverlay = newValue) .controller(ConfigUtils::createBooleanController) .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.config.uiAndVisuals.showEquipmentInInventory")) + .binding(defaults.uiAndVisuals.showEquipmentInInventory, + () -> config.uiAndVisuals.showEquipmentInInventory, + newValue -> config.uiAndVisuals.showEquipmentInInventory = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) //Chest Value FIXME change dropdown to color controller .group(OptionGroup.createBuilder() diff --git a/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java index e016988b..80bdb1c9 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java @@ -28,6 +28,9 @@ public class UIAndVisualsConfig { public boolean hideStatusEffectOverlay = false; @SerialEntry + public boolean showEquipmentInInventory = true; + + @SerialEntry public ChestValue chestValue = new ChestValue(); @SerialEntry diff --git a/src/main/java/de/hysky/skyblocker/events/SkyblockEvents.java b/src/main/java/de/hysky/skyblocker/events/SkyblockEvents.java index c268103d..93426143 100644 --- a/src/main/java/de/hysky/skyblocker/events/SkyblockEvents.java +++ b/src/main/java/de/hysky/skyblocker/events/SkyblockEvents.java @@ -26,6 +26,16 @@ public final class SkyblockEvents { } }); + /** + * Called when the player's Skyblock profile changes. + * @implNote This is called upon receiving the chat message for the profile change rather than the exact moment of profile change, so it may be delayed by a few seconds. + */ + public static final Event<ProfileChange> PROFILE_CHANGE = EventFactory.createArrayBacked(ProfileChange.class, callbacks -> (prev, profile) -> { + for (ProfileChange callback : callbacks) { + callback.onSkyblockProfileChange(prev, profile); + } + }); + @Environment(EnvType.CLIENT) @FunctionalInterface public interface SkyblockJoin { @@ -43,4 +53,10 @@ public final class SkyblockEvents { public interface SkyblockLocationChange { void onSkyblockLocationChange(Location location); } + + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface ProfileChange { + void onSkyblockProfileChange(String prevProfileId, String profileId); + } } diff --git a/src/main/java/de/hysky/skyblocker/mixins/GenericContainerScreenHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixins/GenericContainerScreenHandlerMixin.java index f75af09a..3c3dbd52 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/GenericContainerScreenHandlerMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/GenericContainerScreenHandlerMixin.java @@ -2,7 +2,11 @@ package de.hysky.skyblocker.mixins; import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.skyblock.dungeon.partyfinder.PartyFinderScreen; +import de.hysky.skyblocker.skyblock.item.SkyblockInventoryScreen; +import de.hysky.skyblocker.utils.Utils; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.GenericContainerScreen; import net.minecraft.item.ItemStack; import net.minecraft.screen.GenericContainerScreenHandler; import net.minecraft.screen.ScreenHandler; @@ -22,8 +26,21 @@ public abstract class GenericContainerScreenHandlerMixin extends ScreenHandler { public void setStackInSlot(int slot, int revision, ItemStack stack) { super.setStackInSlot(slot, revision, stack); SkyblockerMod.getInstance().containerSolverManager.markDirty(); - if (MinecraftClient.getInstance().currentScreen instanceof PartyFinderScreen screen) { - screen.markDirty(); + + Screen currentScreen = MinecraftClient.getInstance().currentScreen; + switch (currentScreen) { + case PartyFinderScreen screen -> screen.markDirty(); + case GenericContainerScreen screen when screen.getTitle().getString().toLowerCase().contains("equipment") -> { + int line = slot/9; + if (line > 0 && line < 5 && slot % 9 == 1) { + boolean empty = stack.getName().getString().trim().toLowerCase().startsWith("empty"); + if (Utils.isInTheRift()) + SkyblockInventoryScreen.equipment_rift[line - 1] = empty ? ItemStack.EMPTY : stack; + else + SkyblockInventoryScreen.equipment[line - 1] = empty ? ItemStack.EMPTY : stack; + } + } + case null, default -> {} } } diff --git a/src/main/java/de/hysky/skyblocker/mixins/InventoryScreenMixin.java b/src/main/java/de/hysky/skyblocker/mixins/InventoryScreenMixin.java index 0d833c22..2194e7a8 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/InventoryScreenMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/InventoryScreenMixin.java @@ -1,11 +1,16 @@ package de.hysky.skyblocker.mixins; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.itemlist.ItemListWidget; import de.hysky.skyblocker.utils.Utils; +import net.minecraft.client.gui.screen.ButtonTextures; import net.minecraft.client.gui.screen.ingame.InventoryScreen; import net.minecraft.client.gui.screen.recipebook.RecipeBookWidget; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.TexturedButtonWidget; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -15,4 +20,16 @@ public abstract class InventoryScreenMixin { private RecipeBookWidget skyblocker$replaceRecipeBook(RecipeBookWidget original) { return SkyblockerConfigManager.get().general.itemList.enableItemList && Utils.isOnSkyblock() ? new ItemListWidget() : original; } + + @WrapOperation(method = "init", at = @At(value = "NEW", target = "(IIIILnet/minecraft/client/gui/screen/ButtonTextures;Lnet/minecraft/client/gui/widget/ButtonWidget$PressAction;)Lnet/minecraft/client/gui/widget/TexturedButtonWidget;")) + private TexturedButtonWidget skyblocker$moveButton(int x, int y, int width, int height, ButtonTextures textures, ButtonWidget.PressAction pressAction, Operation<TexturedButtonWidget> original) { + if (!Utils.isOnSkyblock() || !SkyblockerConfigManager.get().uiAndVisuals.showEquipmentInInventory) return original.call(x, y, width, height, textures, pressAction); + return new TexturedButtonWidget(x + 21, y, width, height, textures, pressAction); + } + + @WrapOperation(method = "method_19891", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/widget/ButtonWidget;setPosition(II)V")) + private void skyblocker$moveButtonWhenPressed(ButtonWidget instance, int i, int j, Operation<Void> original) { + if (!Utils.isOnSkyblock() || !SkyblockerConfigManager.get().uiAndVisuals.showEquipmentInInventory) original.call(instance, i, j); + else instance.setPosition(i + 21, j); + } } diff --git a/src/main/java/de/hysky/skyblocker/mixins/MinecraftClientMixin.java b/src/main/java/de/hysky/skyblocker/mixins/MinecraftClientMixin.java index b04f958f..f91ddc86 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/MinecraftClientMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/MinecraftClientMixin.java @@ -1,7 +1,11 @@ package de.hysky.skyblocker.mixins; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.item.HotbarSlotLock; import de.hysky.skyblocker.skyblock.item.ItemProtection; +import de.hysky.skyblocker.skyblock.item.SkyblockInventoryScreen; import de.hysky.skyblocker.utils.JoinWorldPlaceholderScreen; import de.hysky.skyblocker.utils.ReconfiguringPlaceholderScreen; import de.hysky.skyblocker.utils.Utils; @@ -9,8 +13,10 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.DownloadingTerrainScreen; import net.minecraft.client.gui.screen.ReconfiguringScreen; import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.InventoryScreen; import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.entity.player.PlayerEntity; import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -54,4 +60,10 @@ public abstract class MinecraftClientMixin { private Screen modifyJoinWorld(Screen screen) { return Utils.isOnSkyblock() ? new JoinWorldPlaceholderScreen() : screen; } + + @WrapOperation(method = "handleInputEvents", at = @At(value = "NEW", target = "(Lnet/minecraft/entity/player/PlayerEntity;)Lnet/minecraft/client/gui/screen/ingame/InventoryScreen;")) + private InventoryScreen skyblocker$skyblockInventoryScreen(PlayerEntity player, Operation<InventoryScreen> original) { + if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().uiAndVisuals.showEquipmentInInventory) return new SkyblockInventoryScreen(player); + else return original.call(player); + } }
\ No newline at end of file diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockInventoryScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockInventoryScreen.java new file mode 100644 index 00000000..42a52a85 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockInventoryScreen.java @@ -0,0 +1,194 @@ +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} + * <br> + * 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<ItemStack[]> 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<ItemStack[]> 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; + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/Utils.java b/src/main/java/de/hysky/skyblocker/utils/Utils.java index dad7ec02..7a00c1a0 100644 --- a/src/main/java/de/hysky/skyblocker/utils/Utils.java +++ b/src/main/java/de/hysky/skyblocker/utils/Utils.java @@ -463,7 +463,11 @@ public class Utils { if (message.startsWith(PROFILE_MESSAGE_PREFIX)) { profile = message.substring(PROFILE_MESSAGE_PREFIX.length()).split("§b")[0]; } else if (message.startsWith(PROFILE_ID_PREFIX)) { + String prevProfileId = profileId; profileId = message.substring(PROFILE_ID_PREFIX.length()); + if (!prevProfileId.equals(profileId)) { + SkyblockEvents.PROFILE_CHANGE.invoker().onSkyblockProfileChange(prevProfileId, profileId); + } MuseumItemCache.tick(profileId); } diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index cf1cc4c2..bab98150 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -615,6 +615,8 @@ "skyblocker.config.uiAndVisuals.searchOverlay.maxSuggestions": "Maximum Suggestions", "skyblocker.config.uiAndVisuals.searchOverlay.maxSuggestions.@Tooltip": "The maximum number of suggested items to show.", + "skyblocker.config.uiAndVisuals.showEquipmentInInventory": "Show Equipment in Inventory", + "skyblocker.config.uiAndVisuals.tabHud": "Fancy tab HUD (Temporarily disabled outside dungeons)", "skyblocker.config.uiAndVisuals.tabHud.enableHudBackground": "Enable HUD Background", "skyblocker.config.uiAndVisuals.tabHud.enableHudBackground.@Tooltip": "Enables the background of the non-tab HUD.", diff --git a/src/main/resources/assets/skyblocker/textures/gui/sprites/equipment/empty_icon.png b/src/main/resources/assets/skyblocker/textures/gui/sprites/equipment/empty_icon.png Binary files differnew file mode 100644 index 00000000..be89af40 --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/sprites/equipment/empty_icon.png |