aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/de/hysky/skyblocker
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/de/hysky/skyblocker')
-rw-r--r--src/main/java/de/hysky/skyblocker/SkyblockerMod.java1
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java7
-rw-r--r--src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/events/SkyblockEvents.java16
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/GenericContainerScreenHandlerMixin.java21
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/InventoryScreenMixin.java17
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/MinecraftClientMixin.java12
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockInventoryScreen.java194
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/Utils.java4
9 files changed, 273 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);
}