From 986ce538f123cdec7e0da12ed89ba7225539df0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linnea=20Gr=C3=A4f?= Date: Wed, 10 Jul 2024 01:34:37 +0200 Subject: [PATCH] Add interactive storage overlay --- .gitignore | 2 +- .../nea/firmament/init/ClientPlayerRiser.java | 77 ++++ .../moe/nea/firmament/init/EarlyRiser.java | 67 +-- .../firmament/init/HandledScreenRiser.java | 86 ++++ .../moe/nea/firmament/init/RiserUtils.java | 32 ++ .../mixins/ScreenChangeEventPatch.java | 7 +- .../mixins/customgui/OriginalSlotCoords.java | 48 +++ .../mixins/customgui/PatchHandledScreen.java | 175 ++++++++ .../kotlin/moe/nea/firmament/commands/rome.kt | 4 +- .../nea/firmament/events/ScreenChangeEvent.kt | 2 + .../events/subscription/Subscription.kt | 2 +- .../nea/firmament/features/FeatureManager.kt | 6 +- .../storageoverlay/StorageBackingHandle.kt | 8 +- .../storageoverlay/StorageOverlay.kt | 49 +-- .../storageoverlay/StorageOverlayCustom.kt | 97 +++++ .../storageoverlay/StorageOverlayScreen.kt | 383 +++++++++++++----- .../storageoverlay/StorageOverviewScreen.kt | 126 ++++++ .../nea/firmament/rei/FirmamentReiPlugin.kt | 22 +- .../util/customgui/CoordRememberingSlot.kt | 19 + .../nea/firmament/util/customgui/CustomGui.kt | 77 ++++ .../firmament/util/customgui/HasCustomGui.kt | 22 + .../storageoverlay/player_inventory.png | Bin 0 -> 1019 bytes .../player_inventory.png.license | 3 + .../storageoverlay/scroll_bar_background.png | Bin 0 -> 4348 bytes .../scroll_bar_background.png.license | 3 + .../scroll_bar_background.png.mcmeta | 10 + .../scroll_bar_background.png.mcmeta.license | 3 + .../storageoverlay/scroll_bar_knob.png | Bin 0 -> 4583 bytes .../scroll_bar_knob.png.license | 2 + .../sprites/storageoverlay/storage_row.png | Bin 0 -> 649 bytes .../storageoverlay/storage_row.png.license | 3 + .../storageoverlay/storage_row.png.mcmeta | 9 + .../storage_row.png.mcmeta.license | 3 + .../storageoverlay/upper_background.png | Bin 0 -> 1396 bytes .../upper_background.png.license | 3 + .../upper_background.png.mcmeta | 10 + .../upper_background.png.mcmeta.license | 3 + src/main/resources/firmament.accesswidener | 4 + .../process/SubscribeAnnotationProcessor.kt | 2 +- 39 files changed, 1141 insertions(+), 228 deletions(-) create mode 100644 src/main/java/moe/nea/firmament/init/ClientPlayerRiser.java create mode 100644 src/main/java/moe/nea/firmament/init/HandledScreenRiser.java create mode 100644 src/main/java/moe/nea/firmament/init/RiserUtils.java create mode 100644 src/main/java/moe/nea/firmament/mixins/customgui/OriginalSlotCoords.java create mode 100644 src/main/java/moe/nea/firmament/mixins/customgui/PatchHandledScreen.java create mode 100644 src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayCustom.kt create mode 100644 src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverviewScreen.kt create mode 100644 src/main/kotlin/moe/nea/firmament/util/customgui/CoordRememberingSlot.kt create mode 100644 src/main/kotlin/moe/nea/firmament/util/customgui/CustomGui.kt create mode 100644 src/main/kotlin/moe/nea/firmament/util/customgui/HasCustomGui.kt create mode 100644 src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/player_inventory.png create mode 100644 src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/player_inventory.png.license create mode 100644 src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png create mode 100644 src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png.license create mode 100644 src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png.mcmeta create mode 100644 src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png.mcmeta.license create mode 100644 src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_knob.png create mode 100644 src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_knob.png.license create mode 100644 src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png create mode 100644 src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png.license create mode 100644 src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png.mcmeta create mode 100644 src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png.mcmeta.license create mode 100644 src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png create mode 100644 src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png.license create mode 100644 src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png.mcmeta create mode 100644 src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png.mcmeta.license diff --git a/.gitignore b/.gitignore index 7db1db8..dca36f7 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,4 @@ gradle-app.setting # loom runClient /run/ *.xsd - +*.png~ diff --git a/src/main/java/moe/nea/firmament/init/ClientPlayerRiser.java b/src/main/java/moe/nea/firmament/init/ClientPlayerRiser.java new file mode 100644 index 0000000..1d80a9e --- /dev/null +++ b/src/main/java/moe/nea/firmament/init/ClientPlayerRiser.java @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.init; + +import me.shedaniel.mm.api.ClassTinkerers; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.VarInsnNode; + +import java.lang.reflect.Modifier; +import java.util.Objects; + +public class ClientPlayerRiser extends RiserUtils { + String PlayerEntity = remapper.mapClassName("intermediary", "net.minecraft.class_1657"); + String World = remapper.mapClassName("intermediary", "net.minecraft.class_1937"); + String GameProfile = "com.mojang.authlib.GameProfile"; + String BlockPos = remapper.mapClassName("intermediary", "net.minecraft.class_2338"); + String AbstractClientPlayerEntity = remapper.mapClassName("intermediary", "net.minecraft.class_742"); + String GuiPlayer = "moe.nea.firmament.gui.entity.GuiPlayer"; + // World world, BlockPos pos, float yaw, GameProfile gameProfile + Type constructorDescriptor = Type.getMethodType(Type.VOID_TYPE, getTypeForClassName(World), getTypeForClassName(BlockPos), Type.FLOAT_TYPE, getTypeForClassName(GameProfile)); + + + private void mapClassNode(ClassNode classNode, Type superClass) { + for (MethodNode method : classNode.methods) { + if (Objects.equals(method.name, "") && Type.getMethodType(method.desc).equals(constructorDescriptor)) { + modifyConstructor(method, superClass); + return; + } + } + var node = new MethodNode(Opcodes.ASM9, "", constructorDescriptor.getDescriptor(), null, null); + classNode.methods.add(node); + modifyConstructor(node, superClass); + } + + + private void modifyConstructor(MethodNode method, Type superClass) { + method.access = (method.access | Modifier.PUBLIC) & ~Modifier.PRIVATE & ~Modifier.PROTECTED; + if (method.instructions.size() != 0) return; // Some other mod has already made a constructor here + + // World world, BlockPos pos, float yaw, GameProfile gameProfile + // ALOAD this + method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + + // ALOAD World + method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); + + // ALOAD BlockPos + method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 2)); + + // ALOAD yaw + method.instructions.add(new VarInsnNode(Opcodes.FLOAD, 3)); + + // ALOAD gameProfile + method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 4)); + + // Call super + method.instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, superClass.getInternalName(), "", constructorDescriptor.getDescriptor(), false)); + + // Return + method.instructions.add(new InsnNode(Opcodes.RETURN)); + } + + @Override + public void addTinkerers() { + ClassTinkerers.addTransformation(AbstractClientPlayerEntity, it -> mapClassNode(it, getTypeForClassName(PlayerEntity))); + ClassTinkerers.addTransformation(GuiPlayer, it -> mapClassNode(it, getTypeForClassName(AbstractClientPlayerEntity))); + } +} diff --git a/src/main/java/moe/nea/firmament/init/EarlyRiser.java b/src/main/java/moe/nea/firmament/init/EarlyRiser.java index 268e3f3..6ea0cd4 100644 --- a/src/main/java/moe/nea/firmament/init/EarlyRiser.java +++ b/src/main/java/moe/nea/firmament/init/EarlyRiser.java @@ -6,73 +6,10 @@ package moe.nea.firmament.init; -import me.shedaniel.mm.api.ClassTinkerers; -import net.fabricmc.loader.api.FabricLoader; -import net.fabricmc.loader.api.MappingResolver; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.*; - -import java.lang.reflect.Modifier; -import java.util.Objects; - public class EarlyRiser implements Runnable { - MappingResolver remapper = FabricLoader.getInstance().getMappingResolver(); - String PlayerEntity = remapper.mapClassName("intermediary", "net.minecraft.class_1657"); - String World = remapper.mapClassName("intermediary", "net.minecraft.class_1937"); - String GameProfile = "com.mojang.authlib.GameProfile"; - String BlockPos = remapper.mapClassName("intermediary", "net.minecraft.class_2338"); - String AbstractClientPlayerEntity = remapper.mapClassName("intermediary", "net.minecraft.class_742"); - String GuiPlayer = "moe.nea.firmament.gui.entity.GuiPlayer"; - // World world, BlockPos pos, float yaw, GameProfile gameProfile - Type constructorDescriptor = Type.getMethodType(Type.VOID_TYPE, getTypeForClassName(World), getTypeForClassName(BlockPos), Type.FLOAT_TYPE, getTypeForClassName(GameProfile)); - @Override public void run() { - ClassTinkerers.addTransformation(AbstractClientPlayerEntity, it -> mapClassNode(it, getTypeForClassName(PlayerEntity))); - ClassTinkerers.addTransformation(GuiPlayer, it -> mapClassNode(it, getTypeForClassName(AbstractClientPlayerEntity))); - } - - private void mapClassNode(ClassNode classNode, Type superClass) { - for (MethodNode method : classNode.methods) { - if (Objects.equals(method.name, "") && Type.getMethodType(method.desc).equals(constructorDescriptor)) { - modifyConstructor(method, superClass); - return; - } - } - var node = new MethodNode(Opcodes.ASM9, "", constructorDescriptor.getDescriptor(), null, null); - classNode.methods.add(node); - modifyConstructor(node, superClass); - } - - private Type getTypeForClassName(String className) { - return Type.getObjectType(className.replace('.', '/')); - } - - private void modifyConstructor(MethodNode method, Type superClass) { - method.access = (method.access | Modifier.PUBLIC) & ~Modifier.PRIVATE & ~Modifier.PROTECTED; - if (method.instructions.size() != 0) return; // Some other mod has already made a constructor here - - // World world, BlockPos pos, float yaw, GameProfile gameProfile - // ALOAD this - method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); - - // ALOAD World - method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); - - // ALOAD BlockPos - method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 2)); - - // ALOAD yaw - method.instructions.add(new VarInsnNode(Opcodes.FLOAD, 3)); - - // ALOAD gameProfile - method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 4)); - - // Call super - method.instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, superClass.getInternalName(), "", constructorDescriptor.getDescriptor(), false)); - - // Return - method.instructions.add(new InsnNode(Opcodes.RETURN)); + new ClientPlayerRiser().addTinkerers(); + new HandledScreenRiser().addTinkerers(); } } diff --git a/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java b/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java new file mode 100644 index 0000000..3222a91 --- /dev/null +++ b/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java @@ -0,0 +1,86 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.init; + +import me.shedaniel.mm.api.ClassTinkerers; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.VarInsnNode; + +import java.lang.reflect.Modifier; + +public class HandledScreenRiser extends RiserUtils { + String Screen = remapper.mapClassName("intermediary", "net.minecraft.class_437"); + String HandledScreen = remapper.mapClassName("intermediary", "net.minecraft.class_465"); + Type mouseScrolledDesc = Type.getMethodType(Type.BOOLEAN_TYPE, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE); + String mouseScrolled = remapper.mapMethodName("intermediary", "net.minecraft.class_364", "method_25401", + mouseScrolledDesc.getDescriptor()); + + @Override + public void addTinkerers() { + ClassTinkerers.addTransformation(HandledScreen, this::handle); + } + + void handle(ClassNode classNode) { + MethodNode mouseScrolledNode = findMethod(classNode, mouseScrolled, mouseScrolledDesc); + if (mouseScrolledNode == null) { + mouseScrolledNode = new MethodNode( + Modifier.PUBLIC, + mouseScrolled, + mouseScrolledDesc.getDescriptor(), + null, + new String[0] + ); + var insns = mouseScrolledNode.instructions; + // ALOAD 0, load this + insns.add(new VarInsnNode(Opcodes.ALOAD, 0)); + // DLOAD 1-4, load the 4 argument doubles. Note that since doubles are two entries wide we skip 2 each time. + insns.add(new VarInsnNode(Opcodes.DLOAD, 1)); + insns.add(new VarInsnNode(Opcodes.DLOAD, 3)); + insns.add(new VarInsnNode(Opcodes.DLOAD, 5)); + insns.add(new VarInsnNode(Opcodes.DLOAD, 7)); + // INVOKESPECIAL call super method + insns.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, getTypeForClassName(Screen).getInternalName(), mouseScrolled, mouseScrolledDesc.getDescriptor())); + // IRETURN return int on stack (booleans are int at runtime) + insns.add(new InsnNode(Opcodes.IRETURN)); + classNode.methods.add(mouseScrolledNode); + } + + var insns = new InsnList(); + // ALOAD 0, load this + insns.add(new VarInsnNode(Opcodes.ALOAD, 0)); + // DLOAD 1-4, load the 4 argument doubles. Note that since doubles are two entries wide we skip 2 each time. + insns.add(new VarInsnNode(Opcodes.DLOAD, 1)); + insns.add(new VarInsnNode(Opcodes.DLOAD, 3)); + insns.add(new VarInsnNode(Opcodes.DLOAD, 5)); + insns.add(new VarInsnNode(Opcodes.DLOAD, 7)); + // INVOKEVIRTUAL call custom handler + insns.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, + getTypeForClassName(HandledScreen).getInternalName(), + "mouseScrolled_firmament", + mouseScrolledDesc.getDescriptor())); + // Create jump target (but not insert it yet) + var jumpIfFalse = new LabelNode(); + // IFEQ (if returned boolean == 0), jump to jumpIfFalse + insns.add(new JumpInsnNode(Opcodes.IFEQ, jumpIfFalse)); + // LDC 1 (as int, which is what booleans are at runtime) + insns.add(new LdcInsnNode(1)); + // IRETURN return int on stack (booleans are int at runtime) + insns.add(new InsnNode(Opcodes.IRETURN)); + insns.add(jumpIfFalse); + mouseScrolledNode.instructions.insert(insns); + } + +} diff --git a/src/main/java/moe/nea/firmament/init/RiserUtils.java b/src/main/java/moe/nea/firmament/init/RiserUtils.java new file mode 100644 index 0000000..4c68dd4 --- /dev/null +++ b/src/main/java/moe/nea/firmament/init/RiserUtils.java @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.init; + +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.MappingResolver; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +public abstract class RiserUtils { + protected Type getTypeForClassName(String className) { + return Type.getObjectType(className.replace('.', '/')); + } + + protected MappingResolver remapper = FabricLoader.getInstance().getMappingResolver(); + + public abstract void addTinkerers(); + + protected MethodNode findMethod(ClassNode classNode, String name, Type desc) { + for (MethodNode method : classNode.methods) { + if (method.name.equals(name) && desc.getDescriptor().equals(method.desc)) + return method; + } + return null; + } + +} diff --git a/src/main/java/moe/nea/firmament/mixins/ScreenChangeEventPatch.java b/src/main/java/moe/nea/firmament/mixins/ScreenChangeEventPatch.java index 1f46f25..6fa950b 100644 --- a/src/main/java/moe/nea/firmament/mixins/ScreenChangeEventPatch.java +++ b/src/main/java/moe/nea/firmament/mixins/ScreenChangeEventPatch.java @@ -1,11 +1,14 @@ /* * SPDX-FileCopyrightText: 2023 Linnea Gräf + * SPDX-FileCopyrightText: 2024 Linnea Gräf * * SPDX-License-Identifier: GPL-3.0-or-later */ package moe.nea.firmament.mixins; +import com.llamalad7.mixinextras.sugar.Local; +import com.llamalad7.mixinextras.sugar.ref.LocalRef; import moe.nea.firmament.events.ScreenChangeEvent; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.Screen; @@ -23,10 +26,12 @@ public abstract class ScreenChangeEventPatch { public Screen currentScreen; @Inject(method = "setScreen", at = @At("HEAD"), cancellable = true) - public void onScreenChange(Screen screen, CallbackInfo ci) { + public void onScreenChange(Screen screen, CallbackInfo ci, @Local(argsOnly = true) LocalRef screenLocalRef) { var event = new ScreenChangeEvent(currentScreen, screen); if (ScreenChangeEvent.Companion.publish(event).getCancelled()) { ci.cancel(); + } else if (event.getOverrideScreen() != null) { + screenLocalRef.set(event.getOverrideScreen()); } } } diff --git a/src/main/java/moe/nea/firmament/mixins/customgui/OriginalSlotCoords.java b/src/main/java/moe/nea/firmament/mixins/customgui/OriginalSlotCoords.java new file mode 100644 index 0000000..f5330bf --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/customgui/OriginalSlotCoords.java @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.mixins.customgui; + +import moe.nea.firmament.util.customgui.CoordRememberingSlot; +import net.minecraft.screen.slot.Slot; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; + +@Mixin(Slot.class) +public class OriginalSlotCoords implements CoordRememberingSlot { + + @Shadow + public int x; + @Shadow + public int y; + @Unique + public int originalX; + @Unique + public int originalY; + + @Override + public void rememberCoords_firmament() { + this.originalX = this.x; + this.originalY = this.y; + } + + @Override + public void restoreCoords_firmament() { + this.x = this.originalX; + this.y = this.originalY; + } + + @Override + public int getOriginalX_firmament() { + return originalX; + } + + @Override + public int getOriginalY_firmament() { + return originalY; + } +} diff --git a/src/main/java/moe/nea/firmament/mixins/customgui/PatchHandledScreen.java b/src/main/java/moe/nea/firmament/mixins/customgui/PatchHandledScreen.java new file mode 100644 index 0000000..59dc259 --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/customgui/PatchHandledScreen.java @@ -0,0 +1,175 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.mixins.customgui; + +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import moe.nea.firmament.util.customgui.CoordRememberingSlot; +import moe.nea.firmament.util.customgui.CustomGui; +import moe.nea.firmament.util.customgui.HasCustomGui; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.HandledScreen; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.collection.DefaultedList; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(HandledScreen.class) +public class PatchHandledScreen extends Screen implements HasCustomGui { + @Shadow + @Final + protected T handler; + @Shadow + protected int x; + @Shadow + protected int y; + @Unique + public CustomGui override; + @Unique + public boolean hasRememberedSlots = false; + + protected PatchHandledScreen(Text title) { + super(title); + } + + @Nullable + @Override + public CustomGui getCustomGui_Firmament() { + return override; + } + + @Override + public void setCustomGui_Firmament(@Nullable CustomGui gui) { + this.override = gui; + } + + public boolean mouseScrolled_firmament(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { + return override != null && override.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount); + } + + @Inject(method = "init", at = @At("TAIL")) + private void onInit(CallbackInfo ci) { + if (override != null) { + override.onInit(); + } + } + + @Inject(method = "drawForeground", at = @At("HEAD"), cancellable = true) + private void onDrawForeground(DrawContext context, int mouseX, int mouseY, CallbackInfo ci) { + if (override != null && !override.shouldDrawForeground()) + ci.cancel(); + } + + + @Unique + private Slot didBeforeSlotRender; + + @WrapOperation( + method = "render", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/util/collection/DefaultedList;get(I)Ljava/lang/Object;")) + private Object beforeSlotRender(DefaultedList instance, int index, Operation original, @Local(argsOnly = true) DrawContext context) { + var slot = (Slot) original.call(instance, index); + if (override != null) { + didBeforeSlotRender = slot; + override.beforeSlotRender(context, slot); + } + return slot; + } + + @Inject(method = "render", + at = @At(value = "INVOKE", target = "Lnet/minecraft/util/collection/DefaultedList;size()I")) + private void afterSlotRender(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { + if (override != null && didBeforeSlotRender != null) { + override.afterSlotRender(context, didBeforeSlotRender); + didBeforeSlotRender = null; + } + } + + @Inject(method = "isClickOutsideBounds", at = @At("HEAD"), cancellable = true) + public void onIsClickOutsideBounds(double mouseX, double mouseY, int left, int top, int button, CallbackInfoReturnable cir) { + if (override != null) { + cir.setReturnValue(override.isClickOutsideBounds(mouseX, mouseY)); + } + } + + @Inject(method = "isPointWithinBounds", at = @At("HEAD"), cancellable = true) + public void onIsPointWithinBounds(int x, int y, int width, int height, double pointX, double pointY, CallbackInfoReturnable cir) { + if (override != null) { + cir.setReturnValue(override.isPointWithinBounds(x + this.x, y + this.y, width, height, pointX, pointY)); + } + } + + @Inject(method = "isPointOverSlot", at = @At("HEAD"), cancellable = true) + public void onIsPointOverSlot(Slot slot, double pointX, double pointY, CallbackInfoReturnable cir) { + if (override != null) { + cir.setReturnValue(override.isPointOverSlot(slot, this.x, this.y, pointX, pointY)); + } + } + + @Inject(method = "render", at = @At("HEAD")) + public void moveSlots(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { + if (override != null) { + for (Slot slot : handler.slots) { + if (!hasRememberedSlots) { + ((CoordRememberingSlot) slot).rememberCoords_firmament(); + } + override.moveSlot(slot); + } + hasRememberedSlots = true; + } else { + if (hasRememberedSlots) { + for (Slot slot : handler.slots) { + ((CoordRememberingSlot) slot).restoreCoords_firmament(); + } + hasRememberedSlots = false; + } + } + } + + @Inject(at = @At("HEAD"), method = "close", cancellable = true) + private void onVoluntaryExit(CallbackInfo ci) { + if (override != null) { + if (!override.onVoluntaryExit()) + ci.cancel(); + } + } + + @WrapWithCondition(method = "renderBackground", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawBackground(Lnet/minecraft/client/gui/DrawContext;FII)V")) + public boolean preventDrawingBackground(HandledScreen instance, DrawContext drawContext, float delta, int mouseX, int mouseY) { + if (override != null) { + override.render(drawContext, delta, mouseX, mouseY); + } + return override == null; + } + + @WrapOperation( + method = "mouseClicked", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;mouseClicked(DDI)Z")) + public boolean overrideMouseClicks(HandledScreen instance, double mouseX, double mouseY, int button, + Operation original) { + if (override != null) { + if (override.mouseClick(mouseX, mouseY, button)) + return true; + } + return original.call(instance, mouseX, mouseY, button); + } +} diff --git a/src/main/kotlin/moe/nea/firmament/commands/rome.kt b/src/main/kotlin/moe/nea/firmament/commands/rome.kt index 6ec09f2..4211963 100644 --- a/src/main/kotlin/moe/nea/firmament/commands/rome.kt +++ b/src/main/kotlin/moe/nea/firmament/commands/rome.kt @@ -15,7 +15,7 @@ import net.minecraft.text.Text import moe.nea.firmament.apis.UrsaManager import moe.nea.firmament.events.CommandEvent import moe.nea.firmament.features.inventory.buttons.InventoryButtons -import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen +import moe.nea.firmament.features.inventory.storageoverlay.StorageOverviewScreen import moe.nea.firmament.features.world.FairySouls import moe.nea.firmament.gui.config.AllConfigsGui import moe.nea.firmament.gui.config.BooleanHandler @@ -107,7 +107,7 @@ fun firmamentCommand() = literal("firmament") { } thenLiteral("storage") { thenExecute { - ScreenUtil.setScreenLater(StorageOverlayScreen()) + ScreenUtil.setScreenLater(StorageOverviewScreen()) MC.player?.networkHandler?.sendChatCommand("storage") } } diff --git a/src/main/kotlin/moe/nea/firmament/events/ScreenChangeEvent.kt b/src/main/kotlin/moe/nea/firmament/events/ScreenChangeEvent.kt index c6b0a7f..4683fac 100644 --- a/src/main/kotlin/moe/nea/firmament/events/ScreenChangeEvent.kt +++ b/src/main/kotlin/moe/nea/firmament/events/ScreenChangeEvent.kt @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2023 Linnea Gräf + * SPDX-FileCopyrightText: 2024 Linnea Gräf * * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -9,5 +10,6 @@ package moe.nea.firmament.events import net.minecraft.client.gui.screen.Screen data class ScreenChangeEvent(val old: Screen?, val new: Screen?) : FirmamentEvent.Cancellable() { + var overrideScreen: Screen? = null companion object : FirmamentEventBus() } diff --git a/src/main/kotlin/moe/nea/firmament/events/subscription/Subscription.kt b/src/main/kotlin/moe/nea/firmament/events/subscription/Subscription.kt index a4542e7..bf29543 100644 --- a/src/main/kotlin/moe/nea/firmament/events/subscription/Subscription.kt +++ b/src/main/kotlin/moe/nea/firmament/events/subscription/Subscription.kt @@ -15,7 +15,7 @@ interface SubscriptionOwner { } data class Subscription( - val owner: SubscriptionOwner, + val owner: Any, val invoke: (T) -> Unit, val eventBus: FirmamentEventBus, ) diff --git a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt b/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt index f2b2d25..f047ad3 100644 --- a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt +++ b/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt @@ -98,11 +98,7 @@ object FeatureManager : DataHolder(serializer(), "feature } private fun subscribeSingleEvent(it: Subscription) { - if (it.owner.delegateFeature in features.values) { // TODO: better check here, somehow. probably implement some interface method - it.eventBus.subscribe(false, it.invoke) // TODO: pass through receivesCancelled from the annotation - } else { - Firmament.logger.error("Ignoring event listener for ${it.eventBus} in ${it.owner}") - } + it.eventBus.subscribe(false, it.invoke) } fun loadFeature(feature: FirmamentFeature) { diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageBackingHandle.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageBackingHandle.kt index 006ad54..a38896f 100644 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageBackingHandle.kt +++ b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageBackingHandle.kt @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2023 Linnea Gräf + * SPDX-FileCopyrightText: 2024 Linnea Gräf * * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -21,11 +22,6 @@ sealed interface StorageBackingHandle { val handler: GenericContainerScreenHandler } - /** - * No open "server side" screen. - */ - object None : StorageBackingHandle - /** * The main storage overview is open. Clicking on a slot will open that page. This page is accessible via `/storage` */ @@ -48,7 +44,7 @@ sealed interface StorageBackingHandle { * selection screen. */ fun fromScreen(screen: Screen?): StorageBackingHandle? { - if (screen == null) return None + if (screen == null) return null if (screen !is GenericContainerScreen) return null val title = screen.title.unformattedString if (title == "Storage") return Overview(screen.screenHandler) diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlay.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlay.kt index 0426e34..f3a0f47 100644 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlay.kt +++ b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlay.kt @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2023 Linnea Gräf + * SPDX-FileCopyrightText: 2024 Linnea Gräf * * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -9,12 +10,14 @@ package moe.nea.firmament.features.inventory.storageoverlay import java.util.SortedMap import kotlinx.serialization.serializer import net.minecraft.client.gui.screen.Screen +import net.minecraft.client.gui.screen.ingame.GenericContainerScreen +import net.minecraft.client.gui.screen.ingame.HandledScreen import moe.nea.firmament.annotations.Subscribe import moe.nea.firmament.events.ScreenChangeEvent import moe.nea.firmament.events.TickEvent import moe.nea.firmament.features.FirmamentFeature import moe.nea.firmament.gui.config.ManagedConfig -import moe.nea.firmament.util.ScreenUtil.setScreenLater +import moe.nea.firmament.util.customgui.customGui import moe.nea.firmament.util.data.ProfileSpecificDataHolder object StorageOverlay : FirmamentFeature { @@ -33,49 +36,41 @@ object StorageOverlay : FirmamentFeature { val margin by integer("margin", 1, 60) { 20 } } + fun adjustScrollSpeed(amount: Double): Double { + return amount * TConfig.scrollSpeed * (if (TConfig.inverseScroll) 1 else -1) + } + override val config: TConfig get() = TConfig var lastStorageOverlay: Screen? = null - var shouldReturnToStorageOverlayFrom: Screen? = null - var shouldReturnToStorageOverlay: Screen? = null - var currentHandler: StorageBackingHandle? = StorageBackingHandle.None + var currentHandler: StorageBackingHandle? = null @Subscribe fun onTick(event: TickEvent) { rememberContent(currentHandler ?: return) } - @Subscribe - fun onScreenChangeLegacy(event: ScreenChangeEvent) { - currentHandler = StorageBackingHandle.fromScreen(event.new) - if (event.old is StorageOverlayScreen && !event.old.isClosing) { - event.old.setHandler(currentHandler) - if (currentHandler != null) - // TODO: Consider instead only replacing rendering? might make a lot of stack handling easier - event.cancel() - } - } - @Subscribe fun onScreenChange(it: ScreenChangeEvent) { - if (lastStorageOverlay != null && it.new != null) { - shouldReturnToStorageOverlay = lastStorageOverlay - shouldReturnToStorageOverlayFrom = it.new - lastStorageOverlay = null - } else if (it.old === shouldReturnToStorageOverlayFrom) { - if (shouldReturnToStorageOverlay != null && it.new == null) - setScreenLater(shouldReturnToStorageOverlay) - shouldReturnToStorageOverlay = null - shouldReturnToStorageOverlayFrom = null + val storageOverlayScreen = it.old as? StorageOverlayScreen + ?: ((it.old as? HandledScreen<*>)?.customGui as? StorageOverlayCustom)?.overview + if (it.new == null && storageOverlayScreen != null && !storageOverlayScreen.isExiting) { + it.overrideScreen = storageOverlayScreen + return } + val screen = it.new as? GenericContainerScreen ?: return + currentHandler = StorageBackingHandle.fromScreen(screen) + screen.customGui = StorageOverlayCustom( + currentHandler as? StorageBackingHandle.Page ?: return, + screen, + storageOverlayScreen ?: return) } - private fun rememberContent(handler: StorageBackingHandle) { + fun rememberContent(handler: StorageBackingHandle) { // TODO: Make all of these functions work on deltas / updates instead of the entire contents val data = Data.data?.storageInventories ?: return when (handler) { - StorageBackingHandle.None -> {} is StorageBackingHandle.Overview -> rememberStorageOverview(handler, data) is StorageBackingHandle.Page -> rememberPage(handler, data) } @@ -90,7 +85,7 @@ object StorageOverlay : FirmamentFeature { // Ignore unloaded item stacks if (stack.isEmpty) continue val slot = StoragePageSlot.fromOverviewSlotIndex(index) ?: continue - val isEmpty = stack.item in StorageOverlayScreen.emptyStorageSlotItems + val isEmpty = stack.item in StorageOverviewScreen.emptyStorageSlotItems if (slot in data) { if (isEmpty) data.remove(slot) diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayCustom.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayCustom.kt new file mode 100644 index 0000000..d1cdef2 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayCustom.kt @@ -0,0 +1,97 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.features.inventory.storageoverlay + +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import net.minecraft.client.MinecraftClient +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.screen.ingame.GenericContainerScreen +import net.minecraft.entity.player.PlayerInventory +import net.minecraft.screen.slot.Slot +import moe.nea.firmament.mixins.accessor.AccessorHandledScreen +import moe.nea.firmament.util.customgui.CustomGui + +class StorageOverlayCustom( + val handler: StorageBackingHandle.Page, + val screen: GenericContainerScreen, + val overview: StorageOverlayScreen, +) : CustomGui() { + override fun onVoluntaryExit(): Boolean { + overview.isExiting = true + return super.onVoluntaryExit() + } + + override fun getBounds(): List { + return overview.getBounds() + } + + override fun afterSlotRender(context: DrawContext, slot: Slot) { + if (slot.inventory !is PlayerInventory) + context.disableScissor() + } + + override fun beforeSlotRender(context: DrawContext, slot: Slot) { + if (slot.inventory !is PlayerInventory) + overview.createScissors(context) + } + + override fun onInit() { + overview.init(MinecraftClient.getInstance(), screen.width, screen.height) + } + + override fun isPointOverSlot(slot: Slot, xOffset: Int, yOffset: Int, pointX: Double, pointY: Double): Boolean { + if (!super.isPointOverSlot(slot, xOffset, yOffset, pointX, pointY)) + return false + if (slot.inventory !is PlayerInventory) { + if (!overview.getScrollPanelInner().contains(pointX, pointY)) + return false + } + return true + } + + override fun shouldDrawForeground(): Boolean { + return false + } + + override fun mouseClick(mouseX: Double, mouseY: Double, button: Int): Boolean { + return overview.mouseClicked(mouseX, mouseY, button, handler.storagePageSlot) + } + + override fun render(drawContext: DrawContext, delta: Float, mouseX: Int, mouseY: Int) { + overview.drawBackgrounds(drawContext) + overview.drawPages(drawContext, + mouseX, + mouseY, + delta, + handler.storagePageSlot, + screen.screenHandler.slots.take(screen.screenHandler.rows * 9).drop(9), + Point((screen as AccessorHandledScreen).x_Firmament, screen.y_Firmament)) + overview.drawScrollBar(drawContext) + } + + override fun moveSlot(slot: Slot) { + val index = slot.index + if (index in 0..<36) { + val (x, y) = overview.getPlayerInventorySlotPosition(index) + slot.x = x - (screen as AccessorHandledScreen).x_Firmament + slot.y = y - screen.y_Firmament + } else { + slot.x = -100000 + slot.y = -100000 + } + } + + override fun mouseScrolled( + mouseX: Double, + mouseY: Double, + horizontalAmount: Double, + verticalAmount: Double + ): Boolean { + return overview.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount) + } +} diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayScreen.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayScreen.kt index e0cd3c8..ac89f8c 100644 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayScreen.kt +++ b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayScreen.kt @@ -1,106 +1,75 @@ /* - * SPDX-FileCopyrightText: 2023 Linnea Gräf + * SPDX-FileCopyrightText: 2024 Linnea Gräf * * SPDX-License-Identifier: GPL-3.0-or-later */ package moe.nea.firmament.features.inventory.storageoverlay -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.assertNotNullOr -import moe.nea.firmament.util.toShedaniel -import net.minecraft.block.Blocks +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle import net.minecraft.client.gui.DrawContext import net.minecraft.client.gui.screen.Screen -import net.minecraft.item.Item -import net.minecraft.item.Items -import net.minecraft.network.packet.c2s.play.CloseHandledScreenC2SPacket +import net.minecraft.screen.slot.Slot import net.minecraft.text.Text -import net.minecraft.util.DyeColor -import kotlin.math.max +import net.minecraft.util.Identifier +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.CommandEvent +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.ScreenUtil +import moe.nea.firmament.util.assertTrueOr + +class StorageOverlayScreen : Screen(Text.literal("")) { -class StorageOverlayScreen() : Screen(Text.empty()) { companion object { - val emptyStorageSlotItems = listOf( - Blocks.RED_STAINED_GLASS_PANE.asItem(), - Blocks.BROWN_STAINED_GLASS_PANE.asItem(), - Items.GRAY_DYE - ) - val pageWidth get() = 19 * 9 - } + val PLAYER_WIDTH = 184 + val PLAYER_HEIGHT = 91 + val PLAYER_Y_INSET = 3 + val SLOT_SIZE = 18 + val PADDING = 10 + val PAGE_WIDTH = SLOT_SIZE * 9 + val HOTBAR_X = 12 + val HOTBAR_Y = 67 + val MAIN_INVENTORY_Y = 9 + val SCROLL_BAR_WIDTH = 8 + val SCROLL_BAR_HEIGHT = 16 - private var handler: StorageBackingHandle = StorageBackingHandle.None - val content = StorageOverlay.Data.data ?: StorageData() - var isClosing = false - - private fun discardOldHandle() { - val player = assertNotNullOr(MC.player) { return } - val handle = this.handler - if (handle is StorageBackingHandle.HasBackingScreen) { - player.networkHandler.sendPacket(CloseHandledScreenC2SPacket(handle.handler.syncId)) - if (player.currentScreenHandler === handle.handler) { - player.currentScreenHandler = player.playerScreenHandler + @Subscribe + fun onCommand(event: CommandEvent.SubCommand) { + event.subcommand("teststorage") { + executes { + ScreenUtil.setScreenLater(StorageOverlayScreen()) + MC.sendCommand("ec") + 0 + } } } } - fun setHandler(handler: StorageBackingHandle?) { - discardOldHandle() - if (handler != null) - this.handler = handler + var isExiting: Boolean = false + var scroll: Float = 0F + var pageWidthCount = StorageOverlay.TConfig.rows + + inner class Measurements { + val innerScrollPanelWidth = PAGE_WIDTH * pageWidthCount + (pageWidthCount - 1) * PADDING + val overviewWidth = innerScrollPanelWidth + 3 * PADDING + SCROLL_BAR_WIDTH + val x = width / 2 - overviewWidth / 2 + val overviewHeight = minOf(3 * 18 * 6, height - PLAYER_HEIGHT - minOf(80, height / 10)) + val innerScrollPanelHeight = overviewHeight - PADDING * 2 + val y = height / 2 - (overviewHeight + PLAYER_HEIGHT) / 2 + val playerX = width / 2 - PLAYER_WIDTH / 2 + val playerY = y + overviewHeight - PLAYER_Y_INSET } - var scroll = 0 - var lastRenderedHeight = 0 + var measurements = Measurements() - override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { - super.render(context, mouseX, mouseY, delta) - context.fill(0, 0, width, height, 0x90000000.toInt()) - layoutedForEach { (key, value), offsetX, offsetY -> - context.matrices.push() - context.matrices.translate(offsetX.toFloat(), offsetY.toFloat(), 0F) - renderStoragePage(context, value, mouseX - offsetX, mouseY - offsetY) - context.matrices.pop() - } - } - - inline fun layoutedForEach(onEach: (data: Pair, offsetX: Int, offsetY: Int) -> Unit) { - var offsetY = 0 - var currentMaxHeight = StorageOverlay.config.margin - StorageOverlay.config.padding - scroll - var totalHeight = -currentMaxHeight - content.storageInventories.onEachIndexed { index, (key, value) -> - val pageX = (index % StorageOverlay.config.rows) - if (pageX == 0) { - currentMaxHeight += StorageOverlay.config.padding - offsetY += currentMaxHeight - totalHeight += currentMaxHeight - currentMaxHeight = 0 - } - val xPosition = - width / 2 - (StorageOverlay.config.rows * (pageWidth + StorageOverlay.config.padding) - StorageOverlay.config.padding) / 2 + pageX * (pageWidth + StorageOverlay.config.padding) - onEach(Pair(key, value), xPosition, offsetY) - val height = getStorePageHeight(value) - currentMaxHeight = max(currentMaxHeight, height) - } - lastRenderedHeight = totalHeight + currentMaxHeight - } - - override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { - layoutedForEach { (k, p), x, y -> - val rx = mouseX - x - val ry = mouseY - y - if (rx in (0.0..pageWidth.toDouble()) && ry in (0.0..getStorePageHeight(p).toDouble())) { - close() - StorageOverlay.lastStorageOverlay = this - k.navigateTo() - return true - } - } - return super.mouseClicked(mouseX, mouseY, button) - } - - fun getStorePageHeight(page: StorageData.StorageInventory): Int { - return page.inventory?.rows?.let { it * 19 + MC.font.fontHeight + 2 } ?: 60 + var lastRenderedInnerHeight = 0 + override fun init() { + super.init() + pageWidthCount = StorageOverlay.TConfig.rows + .coerceAtMost((width - PADDING) / (PAGE_WIDTH + PADDING)) + .coerceAtLeast(1) + measurements = Measurements() } override fun mouseScrolled( @@ -109,39 +78,233 @@ class StorageOverlayScreen() : Screen(Text.empty()) { horizontalAmount: Double, verticalAmount: Double ): Boolean { - scroll = - (scroll + verticalAmount * StorageOverlay.config.scrollSpeed * - (if (StorageOverlay.config.inverseScroll) 1 else -1)).toInt() - .coerceAtMost(lastRenderedHeight - height + 2 * StorageOverlay.config.margin).coerceAtLeast(0) + scroll = (scroll + StorageOverlay.adjustScrollSpeed(verticalAmount)).toFloat() + .coerceAtMost(getMaxScroll()) + .coerceAtLeast(0F) return true } - private fun renderStoragePage(context: DrawContext, page: StorageData.StorageInventory, mouseX: Int, mouseY: Int) { - context.drawText(MC.font, page.title, 2, 2, -1, true) - val inventory = page.inventory - if (inventory == null) { - // TODO: Missing texture - context.fill(0, 0, pageWidth, 60, DyeColor.RED.toShedaniel().darker(4.0).color) - context.drawCenteredTextWithShadow(MC.font, Text.literal("Not loaded yet"), pageWidth / 2, 30, -1) - return - } + fun getMaxScroll() = lastRenderedInnerHeight.toFloat() - getScrollPanelInner().height - for ((index, stack) in inventory.stacks.withIndex()) { - val x = (index % 9) * 19 - val y = (index / 9) * 19 + MC.font.fontHeight + 2 - if (((mouseX - x) in 0 until 18) && ((mouseY - y) in 0 until 18)) { - context.fill(x, y, x + 18, y + 18, 0x80808080.toInt()) - } else { - context.fill(x, y, x + 18, y + 18, 0x40808080.toInt()) - } - context.drawItem(stack, x + 1, y + 1) - context.drawItemInSlot(MC.font, stack, x + 1, y + 1) - } - } + val playerInventorySprite = Identifier.of("firmament:storageoverlay/player_inventory") + val upperBackgroundSprite = Identifier.of("firmament:storageoverlay/upper_background") + val slotRowSprite = Identifier.of("firmament:storageoverlay/storage_row") + val scrollbarBackground = Identifier.of("firmament:storageoverlay/scroll_bar_background") + val scrollbarKnob = Identifier.of("firmament:storageoverlay/scroll_bar_knob") override fun close() { - discardOldHandle() - isClosing = true + isExiting = true super.close() } + + override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { + super.render(context, mouseX, mouseY, delta) + drawBackgrounds(context) + drawPages(context, mouseX, mouseY, delta, null, null, Point()) + drawScrollBar(context) + drawPlayerInventory(context, mouseX, mouseY, delta) + } + + fun getScrollbarPercentage(): Float { + return scroll / getMaxScroll() + } + + fun drawScrollBar(context: DrawContext) { + val sbRect = getScrollBarRect() + context.drawGuiTexture( + scrollbarBackground, + sbRect.minX, sbRect.minY, + sbRect.width, sbRect.height, + ) + context.drawGuiTexture( + scrollbarKnob, + sbRect.minX, sbRect.minY + (getScrollbarPercentage() * (sbRect.height - SCROLL_BAR_HEIGHT)).toInt(), + SCROLL_BAR_WIDTH, SCROLL_BAR_HEIGHT + ) + } + + fun drawBackgrounds(context: DrawContext) { + context.drawGuiTexture(upperBackgroundSprite, + measurements.x, + measurements.y, + 0, + measurements.overviewWidth, + measurements.overviewHeight) + context.drawGuiTexture(playerInventorySprite, + measurements.playerX, + measurements.playerY, + 0, + PLAYER_WIDTH, + PLAYER_HEIGHT) + } + + fun getPlayerInventorySlotPosition(int: Int): Pair { + if (int < 9) { + return Pair(measurements.playerX + int * SLOT_SIZE + HOTBAR_X, HOTBAR_Y + measurements.playerY) + } + return Pair( + measurements.playerX + (int % 9) * SLOT_SIZE + HOTBAR_X, + measurements.playerY + (int / 9 - 1) * SLOT_SIZE + MAIN_INVENTORY_Y + ) + } + + fun drawPlayerInventory(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { + val items = MC.player?.inventory?.main ?: return + items.withIndex().forEach { (index, item) -> + val (x, y) = getPlayerInventorySlotPosition(index) + context.drawItem(item, x, y, 0) + context.drawItemInSlot(textRenderer, item, x, y) + } + } + + fun getScrollBarRect(): Rectangle { + return Rectangle(measurements.x + PADDING + measurements.innerScrollPanelWidth + PADDING, + measurements.y + PADDING, + SCROLL_BAR_WIDTH, + measurements.innerScrollPanelHeight) + } + + fun getScrollPanelInner(): Rectangle { + return Rectangle(measurements.x + PADDING, + measurements.y + PADDING, + measurements.innerScrollPanelWidth, + measurements.innerScrollPanelHeight) + } + + fun createScissors(context: DrawContext) { + val rect = getScrollPanelInner() + context.enableScissor( + rect.minX, rect.minY, + rect.maxX, rect.maxY + ) + } + + fun drawPages( + context: DrawContext, mouseX: Int, mouseY: Int, delta: Float, + excluding: StoragePageSlot?, + slots: List?, + slotOffset: Point + ) { + createScissors(context) + val data = StorageOverlay.Data.data ?: StorageData() + layoutedForEach(data) { rect, page, inventory -> + drawPage(context, + rect.x, + rect.y, + page, inventory, + if (excluding == page) slots else null, + slotOffset + ) + } + context.disableScissor() + } + + override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { + return mouseClicked(mouseX, mouseY, button, null) + } + + fun mouseClicked(mouseX: Double, mouseY: Double, button: Int, activePage: StoragePageSlot?): Boolean { + if (getScrollPanelInner().contains(mouseX, mouseY)) { + val data = StorageOverlay.Data.data ?: StorageData() + layoutedForEach(data) { rect, page, _ -> + if (rect.contains(mouseX, mouseY) && activePage != page && button == 0) { + page.navigateTo() + return true + } + } + return false + } + val sbRect = getScrollBarRect() + if (sbRect.contains(mouseX, mouseY)) { + // TODO: support dragging of the mouse and such + val percentage = (mouseY - sbRect.getY()) / sbRect.getHeight() + scroll = (getMaxScroll() * percentage).toFloat() + mouseScrolled(0.0, 0.0, 0.0, 0.0) + return true + } + return false + } + + private inline fun layoutedForEach( + data: StorageData, + func: ( + rectangle: Rectangle, + page: StoragePageSlot, inventory: StorageData.StorageInventory, + ) -> Unit + ) { + var yOffset = -scroll.toInt() + var xOffset = 0 + var maxHeight = 0 + for ((page, inventory) in data.storageInventories.entries) { + val currentHeight = inventory.inventory?.let { it.rows * SLOT_SIZE + 4 + textRenderer.fontHeight } + ?: 18 + maxHeight = maxOf(maxHeight, currentHeight) + val rect = Rectangle( + measurements.x + PADDING + (PAGE_WIDTH + PADDING) * xOffset, + yOffset + measurements.y + PADDING, + PAGE_WIDTH, + currentHeight + ) + func(rect, page, inventory) + xOffset++ + if (xOffset >= pageWidthCount) { + yOffset += maxHeight + xOffset = 0 + maxHeight = 0 + } + } + lastRenderedInnerHeight = maxHeight + yOffset + scroll.toInt() + } + + fun drawPage( + context: DrawContext, + x: Int, + y: Int, + page: StoragePageSlot, + inventory: StorageData.StorageInventory, + slots: List?, + slotOffset: Point, + ): Int { + val inv = inventory.inventory + if (inv == null) { + context.drawGuiTexture(upperBackgroundSprite, x, y, PAGE_WIDTH, 18) + context.drawText(textRenderer, + Text.literal("TODO: open this page"), + x + 4, + y + 4, + -1, + true) + return 18 + } + assertTrueOr(slots == null || slots.size == inv.stacks.size) { return 0 } + val name = page.defaultName() + context.drawText(textRenderer, Text.literal(name), x + 4, y + 2, + if (slots == null) 0xFFFFFFFF.toInt() else 0xFFFFFF00.toInt(), true) + context.drawGuiTexture(slotRowSprite, x, y + 4 + textRenderer.fontHeight, PAGE_WIDTH, inv.rows * SLOT_SIZE) + inv.stacks.forEachIndexed { index, stack -> + val slotX = (index % 9) * SLOT_SIZE + x + 1 + val slotY = (index / 9) * SLOT_SIZE + y + 4 + textRenderer.fontHeight + 1 + if (slots == null) { + context.drawItem(stack, slotX, slotY) + context.drawItemInSlot(textRenderer, stack, slotX, slotY) + } else { + val slot = slots[index] + slot.x = slotX - slotOffset.x + slot.y = slotY - slotOffset.y + } + } + return inv.rows * SLOT_SIZE + 4 + textRenderer.fontHeight + } + + fun getBounds(): List { + return listOf( + Rectangle(measurements.x, + measurements.y, + measurements.overviewWidth, + measurements.overviewHeight), + Rectangle(measurements.playerX, + measurements.playerY, + PLAYER_WIDTH, + PLAYER_HEIGHT)) + } } diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverviewScreen.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverviewScreen.kt new file mode 100644 index 0000000..22b612b --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverviewScreen.kt @@ -0,0 +1,126 @@ +/* + * SPDX-FileCopyrightText: 2023 Linnea Gräf + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.features.inventory.storageoverlay + +import kotlin.math.max +import net.minecraft.block.Blocks +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.screen.Screen +import net.minecraft.item.Item +import net.minecraft.item.Items +import net.minecraft.text.Text +import net.minecraft.util.DyeColor +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.toShedaniel + +class StorageOverviewScreen() : Screen(Text.empty()) { + companion object { + val emptyStorageSlotItems = listOf( + Blocks.RED_STAINED_GLASS_PANE.asItem(), + Blocks.BROWN_STAINED_GLASS_PANE.asItem(), + Items.GRAY_DYE + ) + val pageWidth get() = 19 * 9 + } + + val content = StorageOverlay.Data.data ?: StorageData() + var isClosing = false + + var scroll = 0 + var lastRenderedHeight = 0 + + override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { + super.render(context, mouseX, mouseY, delta) + context.fill(0, 0, width, height, 0x90000000.toInt()) + layoutedForEach { (key, value), offsetX, offsetY -> + context.matrices.push() + context.matrices.translate(offsetX.toFloat(), offsetY.toFloat(), 0F) + renderStoragePage(context, value, mouseX - offsetX, mouseY - offsetY) + context.matrices.pop() + } + } + + inline fun layoutedForEach(onEach: (data: Pair, offsetX: Int, offsetY: Int) -> Unit) { + var offsetY = 0 + var currentMaxHeight = StorageOverlay.config.margin - StorageOverlay.config.padding - scroll + var totalHeight = -currentMaxHeight + content.storageInventories.onEachIndexed { index, (key, value) -> + val pageX = (index % StorageOverlay.config.rows) + if (pageX == 0) { + currentMaxHeight += StorageOverlay.config.padding + offsetY += currentMaxHeight + totalHeight += currentMaxHeight + currentMaxHeight = 0 + } + val xPosition = + width / 2 - (StorageOverlay.config.rows * (pageWidth + StorageOverlay.config.padding) - StorageOverlay.config.padding) / 2 + pageX * (pageWidth + StorageOverlay.config.padding) + onEach(Pair(key, value), xPosition, offsetY) + val height = getStorePageHeight(value) + currentMaxHeight = max(currentMaxHeight, height) + } + lastRenderedHeight = totalHeight + currentMaxHeight + } + + override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { + layoutedForEach { (k, p), x, y -> + val rx = mouseX - x + val ry = mouseY - y + if (rx in (0.0..pageWidth.toDouble()) && ry in (0.0..getStorePageHeight(p).toDouble())) { + close() + StorageOverlay.lastStorageOverlay = this + k.navigateTo() + return true + } + } + return super.mouseClicked(mouseX, mouseY, button) + } + + fun getStorePageHeight(page: StorageData.StorageInventory): Int { + return page.inventory?.rows?.let { it * 19 + MC.font.fontHeight + 2 } ?: 60 + } + + override fun mouseScrolled( + mouseX: Double, + mouseY: Double, + horizontalAmount: Double, + verticalAmount: Double + ): Boolean { + scroll = + (scroll + StorageOverlay.adjustScrollSpeed(verticalAmount)).toInt() + .coerceAtMost(lastRenderedHeight - height + 2 * StorageOverlay.config.margin).coerceAtLeast(0) + return true + } + + private fun renderStoragePage(context: DrawContext, page: StorageData.StorageInventory, mouseX: Int, mouseY: Int) { + context.drawText(MC.font, page.title, 2, 2, -1, true) + val inventory = page.inventory + if (inventory == null) { + // TODO: Missing texture + context.fill(0, 0, pageWidth, 60, DyeColor.RED.toShedaniel().darker(4.0).color) + context.drawCenteredTextWithShadow(MC.font, Text.literal("Not loaded yet"), pageWidth / 2, 30, -1) + return + } + + for ((index, stack) in inventory.stacks.withIndex()) { + val x = (index % 9) * 19 + val y = (index / 9) * 19 + MC.font.fontHeight + 2 + if (((mouseX - x) in 0 until 18) && ((mouseY - y) in 0 until 18)) { + context.fill(x, y, x + 18, y + 18, 0x80808080.toInt()) + } else { + context.fill(x, y, x + 18, y + 18, 0x40808080.toInt()) + } + context.drawItem(stack, x + 1, y + 1) + context.drawItemInSlot(MC.font, stack, x + 1, y + 1) + } + } + + override fun close() { + isClosing = true + super.close() + } +} diff --git a/src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt b/src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt index a916b3e..85efd15 100644 --- a/src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt +++ b/src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt @@ -19,20 +19,21 @@ import me.shedaniel.rei.api.client.registry.transfer.TransferHandlerRegistry import me.shedaniel.rei.api.common.entry.EntryStack import me.shedaniel.rei.api.common.entry.type.EntryTypeRegistry import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes -import moe.nea.firmament.events.HandledScreenPushREIEvent -import moe.nea.firmament.features.inventory.CraftingOverlay -import moe.nea.firmament.rei.recipes.SBCraftingRecipe -import moe.nea.firmament.rei.recipes.SBForgeRecipe -import moe.nea.firmament.repo.RepoManager -import moe.nea.firmament.util.SkyblockId -import moe.nea.firmament.util.skyblockId -import moe.nea.firmament.util.unformattedString import net.minecraft.client.gui.screen.ingame.GenericContainerScreen import net.minecraft.client.gui.screen.ingame.HandledScreen import net.minecraft.item.ItemStack import net.minecraft.text.Text import net.minecraft.util.Identifier +import moe.nea.firmament.events.HandledScreenPushREIEvent +import moe.nea.firmament.features.inventory.CraftingOverlay +import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen +import moe.nea.firmament.rei.recipes.SBCraftingRecipe +import moe.nea.firmament.rei.recipes.SBForgeRecipe import moe.nea.firmament.rei.recipes.SBMobDropRecipe +import moe.nea.firmament.repo.RepoManager +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.skyblockId +import moe.nea.firmament.util.unformattedString class FirmamentReiPlugin : REIClientPlugin { @@ -44,6 +45,7 @@ class FirmamentReiPlugin : REIClientPlugin { val SKYBLOCK_ITEM_TYPE_ID = Identifier.of("firmament", "skyblockitems") } + override fun registerTransferHandlers(registry: TransferHandlerRegistry) { registry.register(TransferHandler { context -> val screen = context.containerScreen @@ -69,6 +71,7 @@ class FirmamentReiPlugin : REIClientPlugin { override fun registerExclusionZones(zones: ExclusionZones) { zones.register(HandledScreen::class.java) { HandledScreenPushREIEvent.publish(HandledScreenPushREIEvent(it)).rectangles } + zones.register(StorageOverlayScreen::class.java) { it.getBounds() } } override fun registerDisplays(registry: DisplayRegistry) { @@ -80,7 +83,8 @@ class FirmamentReiPlugin : REIClientPlugin { SBForgeRecipe.Category.categoryIdentifier, SkyblockForgeRecipeDynamicGenerator ) - registry.registerDisplayGenerator(SBMobDropRecipe.Category.categoryIdentifier, SkyblockMobDropRecipeDynamicGenerator) + registry.registerDisplayGenerator(SBMobDropRecipe.Category.categoryIdentifier, + SkyblockMobDropRecipeDynamicGenerator) } override fun registerCollapsibleEntries(registry: CollapsibleEntryRegistry) { diff --git a/src/main/kotlin/moe/nea/firmament/util/customgui/CoordRememberingSlot.kt b/src/main/kotlin/moe/nea/firmament/util/customgui/CoordRememberingSlot.kt new file mode 100644 index 0000000..cf290af --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/util/customgui/CoordRememberingSlot.kt @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.util.customgui + +import net.minecraft.screen.slot.Slot + +interface CoordRememberingSlot { + fun rememberCoords_firmament() + fun restoreCoords_firmament() + fun getOriginalX_firmament(): Int + fun getOriginalY_firmament(): Int +} + +val Slot.originalX get() = (this as CoordRememberingSlot).getOriginalX_firmament() +val Slot.originalY get() = (this as CoordRememberingSlot).getOriginalY_firmament() diff --git a/src/main/kotlin/moe/nea/firmament/util/customgui/CustomGui.kt b/src/main/kotlin/moe/nea/firmament/util/customgui/CustomGui.kt new file mode 100644 index 0000000..40c9ade --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/util/customgui/CustomGui.kt @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.util.customgui + +import me.shedaniel.math.Rectangle +import net.minecraft.client.gui.DrawContext +import net.minecraft.screen.slot.Slot +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.HandledScreenPushREIEvent + +abstract class CustomGui { + + abstract fun getBounds(): List + + open fun moveSlot(slot: Slot) { + // TODO: return a Pair maybe? worth an investigation + } + + companion object { + @Subscribe + fun onExclusionZone(event: HandledScreenPushREIEvent) { + val customGui = event.screen.customGui ?: return + event.rectangles.addAll(customGui.getBounds()) + } + } + + open fun render( + drawContext: DrawContext, + delta: Float, + mouseX: Int, + mouseY: Int + ) { + } + + open fun mouseClick(mouseX: Double, mouseY: Double, button: Int): Boolean { + return false + } + + open fun afterSlotRender(context: DrawContext, slot: Slot) {} + open fun beforeSlotRender(context: DrawContext, slot: Slot) {} + open fun mouseScrolled(mouseX: Double, mouseY: Double, horizontalAmount: Double, verticalAmount: Double): Boolean { + return false + } + + open fun isClickOutsideBounds(mouseX: Double, mouseY: Double): Boolean { + return getBounds().none { it.contains(mouseX, mouseY) } + } + + open fun isPointWithinBounds( + x: Int, + y: Int, + width: Int, + height: Int, + pointX: Double, + pointY: Double, + ): Boolean { + return getBounds().any { it.contains(pointX, pointY) } && + Rectangle(x, y, width, height).contains(pointX, pointY) + } + + open fun isPointOverSlot(slot: Slot, xOffset: Int, yOffset: Int, pointX: Double, pointY: Double): Boolean { + return isPointWithinBounds(slot.x + xOffset, slot.y + yOffset, 16, 16, pointX, pointY) + } + + open fun onInit() {} + open fun shouldDrawForeground(): Boolean { + return true + } + + open fun onVoluntaryExit(): Boolean { + return true + } +} diff --git a/src/main/kotlin/moe/nea/firmament/util/customgui/HasCustomGui.kt b/src/main/kotlin/moe/nea/firmament/util/customgui/HasCustomGui.kt new file mode 100644 index 0000000..54eb50d --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/util/customgui/HasCustomGui.kt @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.util.customgui + +import net.minecraft.client.gui.screen.ingame.HandledScreen + +@Suppress("FunctionName") +interface HasCustomGui { + fun getCustomGui_Firmament(): CustomGui? + fun setCustomGui_Firmament(gui: CustomGui?) +} + +var > T.customGui: CustomGui? + get() = (this as HasCustomGui).getCustomGui_Firmament() + set(value) { + (this as HasCustomGui).setCustomGui_Firmament(value) + } + diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/player_inventory.png b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/player_inventory.png new file mode 100644 index 0000000000000000000000000000000000000000..8dccb7fc8caf3a2cb5a520ea70096fd57a65c991 GIT binary patch literal 1019 zcmeAS@N?(olHy`uVBq!ia0vp^JAgQvgAGU?c4nJ=qZCRW5rVYG6n|b zwVp1HAr*0N=f3WJWgy^|xHZ&KI)Fo{_5%AtK8)IP^8V^7GAn@2-@70~YPd5b~-nn>-zIo zz@fl?tIA{^MizJO>ns9yN-I6}?xxu7jjXZXcQZ$9`Q?`{O^=tQ%rkO2UvusC*USGx zIOnDLK8V`(x2|4zPUR<&hr!#lKd$`wYnJzvX^W?B{KO&lP-4# z+r5tZv~cn9cfx%$@6BN_pP3)$a7Z8MK%PQhhJ&%wr#}&pVUGdwuRm3_WI4_V4II9M zg$GO-S;}MMCOH?#6yQ+0Y99OY%S+xc)hv4$TFp05ihkf_I9Pk#yrzspE{^d&gTNh1BpUkW?b}!K zXo_n1n9=3%Qr0$RkxwY=wVA-+J>N9fb-7Z*gW!p2b0axI?xuX2 zcjlv%X4(AJd+%S;nwJ<}`|`v}AsgQ#)kdE!bax+nc}_mutZ>=tx@r8*VxcMrOm`%O zcAV^CXqvq+Mah+kV_k%3l96)5gEWnq9upaww8L6woZ(Q|A?c+Sq|)%9D^yX?S?JbIZ*4CiexbF3( v8vgiMPxiU~-2LII371;)f!7= +SPDX-FileCopyrightText: This texture is a derivative of textures from Minecraft by Mojang. +SPDX-License-Identifier: CC0-1.0 diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png new file mode 100644 index 0000000000000000000000000000000000000000..10a3d83935afd3d37b536d1e5e3453307ec43125 GIT binary patch literal 4348 zcmeHKeQZ-z6n`DEL1BCeC`01o%^9Y7eV<+TURuU%3#(9=TLV)u;q|@S?zy(FYv0yx z86pEQ10x@bf|-C3P*E58Fv^#NfIwUnV>F0CB!&pdjKnVlH0XJ+UAF}jvqb*eYi{p( z=brPs=l<@wr@bB3^XC*=i>w%i75b_?HPDZTHep0Qd>_7U4`7&OBC7L+e7*;;5uh6d ziaRh%*3OcZC%}82Wf(RL`VG*=Sn@DKw+Y$=&>QwfE3ynX`&}SU&B}W~o{_aN{2A~q zg#G|D7UYT0pMs`7o0<0<$i_3m{J0ZH(vgCvRJ|Ub^< z2Sl3XMcN@UG){9O&4_ju%fnV}D8QzG&kbmnZGG{=Ywo#qrdP3#1h8MBy4}@2w;PWq zw2&G>7?$o_y53cFta{=z3u<@NJTRttT;r+F#j{@TJq?pqS5N=a`mVU?&~Ia3;qNQ8 zJ>TAN^yt=)cdb~nw`)vSJAQ50_dj0Pewipa|MJAgj;D_m-+SrXWctd?S@mCS3jJPU z@qh4XL@fDX)Z4+!lcuj6zm2kS-J4$6KDw=^XUmN@f12RzK62%BQDDvFVR=O3hABJ! zhboEu28 zw*K<-+5=B@EVl07wd7*mtZ7BcnXxzXkAxdG?!5o-_A2Y;WsRL{%g22){2Twt>oc1> zuZ|qomf!VT*OBvip$ebY{QlSD>3xNZ7pLrK?1$$pv$u>s`{EO&RdZ)-eEJWrb!Q&k zHfrUWL;IVqHr~K4)-T*V^}>a5cT=@j51c++(0b{rwjlgeMd{K+$1eqKkUxcKHBhJ5 z`R9qU7PU!=7DTpmG;VIAvmzar4KAV%lFh9p5YF3p8%=uC zYBNjBwBpW$5)x}Xv$GKJ<|68KJuXsIDwVROIGdITQ;Z-86wOjBOM(TNY>DYonv5lj z4Tube2PNf%8rM}Vh8vhtP;1g%1OelCZ+y|X-=BkzC9^C5A5>b3Q;dzKqEV{9M^g7T z14uTYfAvTPTH=VRK}oGCAtP@yis{AuArv|1A8$%T%;hLDg(4^lrb(EU88D@BEOQ

LY7FY{rPWoc@7;dk%Mib?@3uW3cl3MUN&o8R7XnF2a~!R5V#t zL{luMIhH9C7!o0dCV3$kB84(Xh~(@{PzeP?ip+%iLHS}yU5d%bfC6xv3OG`kAlU_) zBiV9?K=NF$jFifFnxuntInVICquh@AK`cn9kd;!Te^dsP0#JfLOXZw{CKXmtNM2$k zh=%3Kpx}^siDPAk3z|@hEY8*vQ3;IGn(61g5G9h3i3X-wU`=;pTSQn45GxsZ3IL`a z)*_l!R7(UjE#e|_r8zh3hvTG3y5xbP4xqVW9q29A57MkiX96(b{F!0I6Sx$ z>|x8R)TFK@T1-{uMAf3k%vK9)GeGiO@n?#2K+KQC|*ztsa!G4 za%PmB+xTSn;x^6z&_k09ir*o+hUgj;1A{Ui%B~^02F1XjjEA!8f1}IVcb!5p_!pFd z%To8|@7{x}R(^2q98c!veBFI;8Av1JRgWey%vNkPOL|8|GbjsmpWj<>xxi|%@LzD- zu7PTb?yb|^TGaSgj@@$QUbKA7U0}rcJe7g+ZkcyD{ysg+GGgRNX~}Zt)bNs$lF{=< r2e8*yJy*m&d8*p6^0k8p|E$;nF4j41H*0zx1qt(c=X<&yTGIXx=#RA3 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png.license b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png.license new file mode 100644 index 0000000..c5a7a6d --- /dev/null +++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2024 Linnea Gräf +SPDX-FileCopyrightText: This texture is a derivative of textures from Minecraft by Mojang. +SPDX-License-Identifier: CC0-1.0 diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png.mcmeta b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png.mcmeta new file mode 100644 index 0000000..94b9a1d --- /dev/null +++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 17, + "height": 18, + "border": 2 + } + } +} diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png.mcmeta.license b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png.mcmeta.license new file mode 100644 index 0000000..c5a7a6d --- /dev/null +++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png.mcmeta.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2024 Linnea Gräf +SPDX-FileCopyrightText: This texture is a derivative of textures from Minecraft by Mojang. +SPDX-License-Identifier: CC0-1.0 diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_knob.png b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_knob.png new file mode 100644 index 0000000000000000000000000000000000000000..8ced28a93d76e73c98c85053168a5d696c153647 GIT binary patch literal 4583 zcmeHLYitx%6rL^es-c>uN~tKr6qJXv^Vr$P3|$KBE_8t{O z^WAg4d+wReJXKaYWl(lOHilt?f+hZP=m$W%c|bq-9osy99){WSP<2oZ2FF-4H-g{> z%$C-(rN=kJeU_~c)(86Mpyk-Iuw3X{pyh(x)Hhp!t*_OugYj+Y@kSVrO6!>R4A>5W z{sU+%jPs!11xH=JS;WeLg%E z*HtZyFf6%h-m~74Ps;Khn^CcLZROCD_>pC6gad)V(7Ici%ZBgIep6`q@Z3!=b0bFE zpI%tk-oASKxika z*2~eA2aKt^2hZHTckHAm@db1)yJ;yO{A}yykA8c7`Eh&urpuSEykUEvjM+H#;nE`UtG-VSYuk{#yrb>p#;p!nwU3)IwD8u?y>FbO*S8d$ z;pW``wei{D-|twzYO|;Ay?!SSF8`RmZ-3PbZKZX2+TGH?T>q%r*5-onh}v(0?<`w; z_W5V#Z~J4~FXg-L>07un>-b)L%Oj_YFL-CI=bXQe3{*_Gy5#4Fm)h1m(E5Y1>Xf`w zTaC5X9G-gW;t6v0`Q_)-swKm0i@x8wy}qjA{DRMKpMBxeWuXHr7QVZ(Yng4vyPA{~_V*C0psCc*~2Eo%ciT&C)8X zjq1>JLDnO7QPCx2Pex+aJ{A=xW1?J#3|vArTGUIN+VU}hYl@esa)#(o%!g{VlDTnI zF}Ji*o?9pL3Q;^hyC^9DKm-{go{WT}2_fkvEL;J`rkEmdOU0=364j7xK0S_b&d%9s zGLY07SYmuOUKCeUq1-j|XoW(8Uzh7m_kw;eVXiMI zx~wUJH5A@VQF0KnyFq$yLsEkY~0Jm#^qcEbI zc8NTRc#b1E74f7bNeoH56pzeG9H+RLE)X-~8f2vy?&_5ZrI=7OEin?Sk~ETd@J8iH zo&#@GSy4omXA$Q~d85d}WIY}cVLP>mSc9low8mP)BwUzS7W5LVo$i#Bg+)UJ11~XM zi`FMQCn~iFsxU;8O~%dAPPc>iIBD7e5p^ofMDYYEE=?!-E%r zK5Ti3mN4{qlO;->s0uVEb(gvgYt~W1aqCbBqMQmLAvPez@)NL9Q*x~stwB&d(&^eE z*Zv_D+)f7eOI1k;c_fk(T?*-89Efx>A}y<&$KiIW-O&@eYBY**G_eMF1g;=Gt>+j% z%G#*W-L)HQk+};%7)iTHhBgU<=N#uu6GmPC5G6OO(on%5JE3-QZU;kpTpWT^!y}gF z6u0DYcX#@~hiE>*{}y5qWtPQ`O)a9X+4ht|mpTg6q-0Q^q1;oMdY|UZtgP4ZN~ha( zTmhha7wHkdy>#``)guOaq}-cby>#`6fgUOMX4n5lS9a$cBZ|U5dX4ZtIDJHrhPTOn z(zGf5)YpMOeOu +SPDX-License-Identifier: CC0-1.0 diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png new file mode 100644 index 0000000000000000000000000000000000000000..5ffc990c90941edf7656e653d2e9b23e6bd0d2a5 GIT binary patch literal 649 zcmeAS@N?(olHy`uVBq!ia0vp^i-1^&gAGXDuwcz&U|?*?baoE#baqxKD9TUE%t>Wn z(3n^|(bnUzgUr$R;H9owd~X#VO;}jAsk_VSnwH2yrxl`6r3O2{aL?MJqN&%{lzs4E z|It;=-J93(t!q;F!T9LSgC#GNRR8H~Zt3{=V2Awsced~DF*>b2WwKeHF=JP1N|2}F z^mtL`^&AQv25AD5XGYGSyu`%j_;uTwn!Vkhb>0`(_c?A&FgllX!&u6qqs`llU}UpHJjye22(Nbr%s(94?f zCw8tWy7y>JsPvj*|9^)99OEOOS)C1BvY|uOLtx>8My^#G1eS-YA9ZQ_Hd*eU;Bozl z-iqO@Ju=)Y9Gl<#IQaiT`S!HJqC>u1s*V4|Z~v2EX4>trJM;GaC}CUe0+@&otv)P-jjXv_U(P+x&P`IZe1w4(0S#~I$*T0C3(BM0BIoj>Abrh zNO2Z;L>4nJ=qZCRW5rVYGN2%PiKnkC`wMmsVN2=T*XErE3Q3l@MwB?`=jNv7l`uFL zr6!i7rYMwWmSiZnd-?{1H}Z)C6(xGQIEG~0dwa={@qhvk!@+=HM~@{Bn6xXF$SrVg zw`R1jJ$*)Y`YNq!Q41Fr-^!f1bM-4NlVctCs$aaAQ#P+b-ayHXMLEd@gK9}GKKGYZ ccCNHOkIM#ef9rykK+70BUHx3vIVCg!08Z) +SPDX-FileCopyrightText: This texture is a derivative of textures from Minecraft by Mojang. +SPDX-License-Identifier: CC0-1.0 diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png.mcmeta b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png.mcmeta new file mode 100644 index 0000000..cd2857e --- /dev/null +++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png.mcmeta @@ -0,0 +1,9 @@ +{ + "gui": { + "scaling": { + "type": "tile", + "width": 162, + "height": 18 + } + } +} diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png.mcmeta.license b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png.mcmeta.license new file mode 100644 index 0000000..c5a7a6d --- /dev/null +++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png.mcmeta.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2024 Linnea Gräf +SPDX-FileCopyrightText: This texture is a derivative of textures from Minecraft by Mojang. +SPDX-License-Identifier: CC0-1.0 diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png new file mode 100644 index 0000000000000000000000000000000000000000..8362bb662bc2e5cc2f87a3dc26944d96d649dc1b GIT binary patch literal 1396 zcmeAS@N?(olHy`uVBq!ia0vp^8-Vy82OE%_>)BNTq&N#aB8wRq^pruEv0|xx83O~W znx~6nNJZS+DHnSW8HgM>@p9R#NwQ0-r);dsReg}alw~cX(J}vdy7uF5R=4+8UpM=3 zR$8L}q1^Ob`};Npo3}PTXFF!G*>->3H~)`f$CsLISohlS{m(~oqWf8E_TSfMy}V%` z@0rgvPWq>d_8mAMyl0=pI=??+AFd|Oy1%`V>6@jyptyn8a{grvwdVqlu=ZSf*{qo` zzgVP@adFA71g;y;Wn5zpXw0>nF{*Sl1V%$(Gz6#+0-wEw&wu`7_W%9A>^JGr*S~)I z`FyFTblk?qmOGJ_b{Pu@7NNr(M}guI0`vp&~BwsU3w878h$*>mSu(tBVr%HZkh K=d#Wzp$Py_SY1Q_ literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png.license b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png.license new file mode 100644 index 0000000..c5a7a6d --- /dev/null +++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2024 Linnea Gräf +SPDX-FileCopyrightText: This texture is a derivative of textures from Minecraft by Mojang. +SPDX-License-Identifier: CC0-1.0 diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png.mcmeta b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png.mcmeta new file mode 100644 index 0000000..a29299d --- /dev/null +++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 176, + "height": 222, + "border": 10 + } + } +} diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png.mcmeta.license b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png.mcmeta.license new file mode 100644 index 0000000..c5a7a6d --- /dev/null +++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png.mcmeta.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2024 Linnea Gräf +SPDX-FileCopyrightText: This texture is a derivative of textures from Minecraft by Mojang. +SPDX-License-Identifier: CC0-1.0 diff --git a/src/main/resources/firmament.accesswidener b/src/main/resources/firmament.accesswidener index 5009a61..40e8dda 100644 --- a/src/main/resources/firmament.accesswidener +++ b/src/main/resources/firmament.accesswidener @@ -13,3 +13,7 @@ accessible field net/minecraft/entity/passive/AbstractHorseEntity SADDLED_FLAG I accessible field net/minecraft/entity/passive/AbstractHorseEntity HORSE_FLAGS Lnet/minecraft/entity/data/TrackedData; accessible method net/minecraft/resource/NamespaceResourceManager loadMetadata (Lnet/minecraft/resource/InputSupplier;)Lnet/minecraft/resource/metadata/ResourceMetadata; accessible method net/minecraft/client/gui/DrawContext drawTexturedQuad (Lnet/minecraft/util/Identifier;IIIIIFFFFFFFF)V + +mutable field net/minecraft/screen/slot/Slot x I +mutable field net/minecraft/screen/slot/Slot y I + diff --git a/symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt b/symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt index be817ce..70491ab 100644 --- a/symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt +++ b/symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt @@ -81,7 +81,7 @@ class SubscribeAnnotationProcessor( continue } val parent = element.parentDeclaration - if (parent !is KSClassDeclaration || parent.isCompanionObject || parent.classKind != ClassKind.OBJECT) { + if (parent !is KSClassDeclaration || parent.classKind != ClassKind.OBJECT) { logger.error("@Subscribe on a non-object", element) continue }