diff options
Diffstat (limited to 'src/main')
37 files changed, 1130 insertions, 217 deletions
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 <nea@nea.moe> + * + * 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, "<init>") && Type.getMethodType(method.desc).equals(constructorDescriptor)) { + modifyConstructor(method, superClass); + return; + } + } + var node = new MethodNode(Opcodes.ASM9, "<init>", 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(), "<init>", 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, "<init>") && Type.getMethodType(method.desc).equals(constructorDescriptor)) { - modifyConstructor(method, superClass); - return; - } - } - var node = new MethodNode(Opcodes.ASM9, "<init>", 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(), "<init>", 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 <nea@nea.moe> + * + * 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 <nea@nea.moe> + * + * 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 <nea@nea.moe> + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> * * 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<Screen> 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 <nea@nea.moe> + * + * 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 <nea@nea.moe> + * + * 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<T extends ScreenHandler> 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<Object> 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<Boolean> 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<Boolean> 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<Boolean> 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<Boolean> 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 <nea@nea.moe> + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> * * 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<ScreenChangeEvent>() } 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<T : FirmamentEvent>( - val owner: SubscriptionOwner, + val owner: Any, val invoke: (T) -> Unit, val eventBus: FirmamentEventBus<T>, ) 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<FeatureManager.Config>(serializer(), "feature } private fun <T : FirmamentEvent> subscribeSingleEvent(it: Subscription<T>) { - 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 <nea@nea.moe> + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> * * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -22,11 +23,6 @@ sealed interface StorageBackingHandle { } /** - * 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` */ data class Overview(override val handler: GenericContainerScreenHandler) : StorageBackingHandle, HasBackingScreen @@ -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 <nea@nea.moe> + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> * * 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,13 +36,15 @@ 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) { @@ -47,35 +52,25 @@ object StorageOverlay : FirmamentFeature { } @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 <nea@nea.moe> + * + * 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<Rectangle> { + 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,147 +1,310 @@ /* - * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> * * 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.empty()) { - companion object { - val emptyStorageSlotItems = listOf<Item>( - Blocks.RED_STAINED_GLASS_PANE.asItem(), - Blocks.BROWN_STAINED_GLASS_PANE.asItem(), - Items.GRAY_DYE - ) - val pageWidth get() = 19 * 9 - } +class StorageOverlayScreen : Screen(Text.literal("")) { - private var handler: StorageBackingHandle = StorageBackingHandle.None - val content = StorageOverlay.Data.data ?: StorageData() - var isClosing = false + companion object { + 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 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() + + 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( + mouseX: Double, + mouseY: Double, + horizontalAmount: Double, + verticalAmount: Double + ): Boolean { + scroll = (scroll + StorageOverlay.adjustScrollSpeed(verticalAmount)).toFloat() + .coerceAtMost(getMaxScroll()) + .coerceAtLeast(0F) + return true + } + + fun getMaxScroll() = lastRenderedInnerHeight.toFloat() - getScrollPanelInner().height + + 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() { + isExiting = true + super.close() + } 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() + 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<Int, Int> { + 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 + ) } - inline fun layoutedForEach(onEach: (data: Pair<StoragePageSlot, StorageData.StorageInventory>, 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) + 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) } - 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 - } + 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<Slot>?, + 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 + ) } - return super.mouseClicked(mouseX, mouseY, button) + context.disableScissor() } - fun getStorePageHeight(page: StorageData.StorageInventory): Int { - return page.inventory?.rows?.let { it * 19 + MC.font.fontHeight + 2 } ?: 60 + override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { + return mouseClicked(mouseX, mouseY, button, null) } - override fun mouseScrolled( - mouseX: Double, - mouseY: Double, - 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) - return true + 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 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 + 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() + } - 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()) + fun drawPage( + context: DrawContext, + x: Int, + y: Int, + page: StoragePageSlot, + inventory: StorageData.StorageInventory, + slots: List<Slot>?, + 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 { - context.fill(x, y, x + 18, y + 18, 0x40808080.toInt()) + val slot = slots[index] + slot.x = slotX - slotOffset.x + slot.y = slotY - slotOffset.y } - context.drawItem(stack, x + 1, y + 1) - context.drawItemInSlot(MC.font, stack, x + 1, y + 1) } + return inv.rows * SLOT_SIZE + 4 + textRenderer.fontHeight } - override fun close() { - discardOldHandle() - isClosing = true - super.close() + fun getBounds(): List<Rectangle> { + 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 <nea@nea.moe> + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> + * + * 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<Item>( + 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<StoragePageSlot, StorageData.StorageInventory>, 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 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 -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.rei.recipes.SBMobDropRecipe 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 <nea@nea.moe> + * + * 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 <nea@nea.moe> + * + * 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<Rectangle> + + 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 <nea@nea.moe> + * + * 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 : HandledScreen<*>> 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 Binary files differnew file mode 100644 index 0000000..8dccb7f --- /dev/null +++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/player_inventory.png diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/player_inventory.png.license b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/player_inventory.png.license new file mode 100644 index 0000000..c5a7a6d --- /dev/null +++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/player_inventory.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> +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 Binary files differnew file mode 100644 index 0000000..10a3d83 --- /dev/null +++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_background.png 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 <nea@nea.moe> +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 <nea@nea.moe> +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 Binary files differnew file mode 100644 index 0000000..8ced28a --- /dev/null +++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_knob.png diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_knob.png.license b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_knob.png.license new file mode 100644 index 0000000..41cf35d --- /dev/null +++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/scroll_bar_knob.png.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> +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 Binary files differnew file mode 100644 index 0000000..5ffc990 --- /dev/null +++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png.license b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png.license new file mode 100644 index 0000000..c5a7a6d --- /dev/null +++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/storage_row.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> +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 <nea@nea.moe> +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 Binary files differnew file mode 100644 index 0000000..8362bb6 --- /dev/null +++ b/src/main/resources/assets/firmament/textures/gui/sprites/storageoverlay/upper_background.png 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 <nea@nea.moe> +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 <nea@nea.moe> +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 + |