diff options
author | Linnea Gräf <nea@nea.moe> | 2024-10-18 00:41:25 +0200 |
---|---|---|
committer | Linnea Gräf <nea@nea.moe> | 2024-10-18 14:36:51 +0200 |
commit | c89b663acad487caeb15f7521be3dd14342dd4e7 (patch) | |
tree | ae21daabaf5de1ac5281509c6fa9446259169ae5 | |
parent | 7de0e8e7e09e3428c17ca9717c21c02469c31b76 (diff) | |
download | firmament-c89b663acad487caeb15f7521be3dd14342dd4e7.tar.gz firmament-c89b663acad487caeb15f7521be3dd14342dd4e7.tar.bz2 firmament-c89b663acad487caeb15f7521be3dd14342dd4e7.zip |
Add slot binding
-rw-r--r-- | src/main/java/moe/nea/firmament/init/HandledScreenRiser.java | 192 | ||||
-rw-r--r-- | src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java | 150 | ||||
-rw-r--r-- | src/main/kotlin/events/HandledScreenKeyPressedEvent.kt | 44 | ||||
-rw-r--r-- | src/main/kotlin/features/inventory/SlotLocking.kt | 487 | ||||
-rw-r--r-- | src/main/kotlin/features/mining/HotmPresets.kt | 21 | ||||
-rw-r--r-- | src/main/kotlin/util/mc/ScreenUtil.kt | 26 | ||||
-rw-r--r-- | src/main/kotlin/util/mc/SlotUtils.kt | 35 | ||||
-rw-r--r-- | src/main/kotlin/util/render/DrawContextExt.kt | 19 | ||||
-rw-r--r-- | src/main/resources/assets/firmament/lang/en_us.json | 2 |
9 files changed, 639 insertions, 337 deletions
diff --git a/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java b/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java index bcb754c..1fbbe45 100644 --- a/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java +++ b/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java @@ -2,8 +2,8 @@ package moe.nea.firmament.init; import me.shedaniel.mm.api.ClassTinkerers; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.screen.ingame.HandledScreen; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.ParentElement; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; @@ -17,69 +17,139 @@ import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.VarInsnNode; import java.lang.reflect.Modifier; +import java.util.function.Consumer; public class HandledScreenRiser extends RiserUtils { - @IntermediaryName(net.minecraft.client.gui.screen.Screen.class) - String Screen; - @IntermediaryName(net.minecraft.client.gui.screen.ingame.HandledScreen.class) - String HandledScreen; - 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()); + @IntermediaryName(net.minecraft.client.gui.screen.Screen.class) + String Screen; + @IntermediaryName(net.minecraft.client.gui.screen.ingame.HandledScreen.class) + String HandledScreen; + 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()); + // boolean keyReleased(int keyCode, int scanCode, int modifiers) + Type keyReleasedDesc = Type.getMethodType(Type.BOOLEAN_TYPE, Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE); + String keyReleased = remapper.mapMethodName("intermediary", Intermediary.<Element>className(), + Intermediary.methodName(Element::keyReleased), + keyReleasedDesc.getDescriptor()); - @Override - public void addTinkerers() { - ClassTinkerers.addTransformation(HandledScreen, this::handle, true); - } - 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); - } + @Override + public void addTinkerers() { + ClassTinkerers.addTransformation(HandledScreen, this::addMouseScroll, true); + ClassTinkerers.addTransformation(HandledScreen, this::addKeyReleased, true); + } - 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); - } + /** + * Insert a handler that roughly inserts the following code at the beginning of the instruction list: + * <code><pre> + * if (insertInvoke(insertLoads)) return true + * </pre></code> + * + * @param node The method node to prepend the instructions to + * @param insertLoads insert all the loads, including the {@code this} parameter + * @param insertInvoke insert the invokevirtual/invokestatic call + */ + void insertTrueHandler(MethodNode node, + Consumer<InsnList> insertLoads, + Consumer<InsnList> insertInvoke) { + + var insns = new InsnList(); + insertLoads.accept(insns); + insertInvoke.accept(insns); + // 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); + node.instructions.insert(insns); + } + + void addKeyReleased(ClassNode classNode) { + var keyReleasedNode = findMethod(classNode, keyReleased, keyReleasedDesc); + if (keyReleasedNode == null) { + keyReleasedNode = new MethodNode( + Modifier.PUBLIC, + keyReleased, + keyReleasedDesc.getDescriptor(), + null, + new String[0] + ); + var insns = keyReleasedNode.instructions; + // ALOAD 0, load this + insns.add(new VarInsnNode(Opcodes.ALOAD, 0)); + // ILOAD 1-3, load args + insns.add(new VarInsnNode(Opcodes.ILOAD, 1)); + insns.add(new VarInsnNode(Opcodes.ILOAD, 2)); + insns.add(new VarInsnNode(Opcodes.ILOAD, 3)); + // INVOKESPECIAL call super method + insns.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, getTypeForClassName(Screen).getInternalName(), + keyReleased, keyReleasedDesc.getDescriptor())); + // IRETURN return int on stack (booleans are int at runtime) + insns.add(new InsnNode(Opcodes.IRETURN)); + classNode.methods.add(keyReleasedNode); + } + insertTrueHandler(keyReleasedNode, insns -> { + // ALOAD 0, load this + insns.add(new VarInsnNode(Opcodes.ALOAD, 0)); + // ILOAD 1-3, load args + insns.add(new VarInsnNode(Opcodes.ILOAD, 1)); + insns.add(new VarInsnNode(Opcodes.ILOAD, 2)); + insns.add(new VarInsnNode(Opcodes.ILOAD, 3)); + }, insns -> { + // INVOKEVIRTUAL call custom handler + insns.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, + getTypeForClassName(HandledScreen).getInternalName(), + "keyReleased_firmament", + keyReleasedDesc.getDescriptor())); + }); + } + + void addMouseScroll(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); + } + + insertTrueHandler(mouseScrolledNode, insns -> { + // 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)); + }, insns -> { + // INVOKEVIRTUAL call custom handler + insns.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, + getTypeForClassName(HandledScreen).getInternalName(), + "mouseScrolled_firmament", + mouseScrolledDesc.getDescriptor())); + + }); + } } diff --git a/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java b/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java index 741ba4b..fd50c72 100644 --- a/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java +++ b/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java @@ -24,77 +24,81 @@ import org.spongepowered.asm.mixin.injection.callback.LocalCapture; @Mixin(HandledScreen.class) public abstract class MixinHandledScreen<T extends ScreenHandler> { - @Shadow - @Final - protected T handler; - - @Shadow - public abstract T getScreenHandler(); - - @Shadow - protected int y; - @Shadow - protected int x; - @Unique - PlayerInventory playerInventory; - - @Inject(method = "<init>", at = @At("TAIL")) - public void savePlayerInventory(ScreenHandler handler, PlayerInventory inventory, Text title, CallbackInfo ci) { - this.playerInventory = inventory; - } - - @Inject(method = "keyPressed", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;handleHotbarKeyPressed(II)Z", shift = At.Shift.BEFORE), cancellable = true) - public void onKeyPressed(int keyCode, int scanCode, int modifiers, CallbackInfoReturnable<Boolean> cir) { - if (HandledScreenKeyPressedEvent.Companion.publish(new HandledScreenKeyPressedEvent((HandledScreen<?>) (Object) this, keyCode, scanCode, modifiers)).getCancelled()) { - cir.setReturnValue(true); - } - } - - @Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true) - public void onMouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable<Boolean> cir) { - if (HandledScreenClickEvent.Companion.publish(new HandledScreenClickEvent((HandledScreen<?>) (Object) this, mouseX, mouseY, button)).getCancelled()) { - cir.setReturnValue(true); - } - } - - @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawForeground(Lnet/minecraft/client/gui/DrawContext;II)V", shift = At.Shift.AFTER)) - public void onAfterRenderForeground(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { - context.getMatrices().push(); - context.getMatrices().translate(-x, -y, 0); - HandledScreenForegroundEvent.Companion.publish(new HandledScreenForegroundEvent((HandledScreen<?>) (Object) this, context, mouseX, mouseY, delta)); - context.getMatrices().pop(); - } - - @Inject(method = "onMouseClick(Lnet/minecraft/screen/slot/Slot;IILnet/minecraft/screen/slot/SlotActionType;)V", at = @At("HEAD"), cancellable = true) - public void onMouseClickedSlot(Slot slot, int slotId, int button, SlotActionType actionType, CallbackInfo ci) { - if (slotId == -999 && getScreenHandler() != null && actionType == SlotActionType.PICKUP) { // -999 is code for "clicked outside the main window" - ItemStack cursorStack = getScreenHandler().getCursorStack(); - if (cursorStack != null && IsSlotProtectedEvent.shouldBlockInteraction(slot, SlotActionType.THROW, cursorStack)) { - ci.cancel(); - return; - } - } - if (IsSlotProtectedEvent.shouldBlockInteraction(slot, actionType)) { - ci.cancel(); - return; - } - if (actionType == SlotActionType.SWAP && 0 <= button && button < 9) { - if (IsSlotProtectedEvent.shouldBlockInteraction(new Slot(playerInventory, button, 0, 0), actionType)) { - ci.cancel(); - } - } - } - - - @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawSlot(Lnet/minecraft/client/gui/DrawContext;Lnet/minecraft/screen/slot/Slot;)V", shift = At.Shift.AFTER), locals = LocalCapture.CAPTURE_FAILHARD) - public void onAfterDrawSlot(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci, int i, int j, int k, Slot slot) { - SlotRenderEvents.After event = new SlotRenderEvents.After(context, slot, mouseX, mouseY, delta); - SlotRenderEvents.After.Companion.publish(event); - } - - @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawSlot(Lnet/minecraft/client/gui/DrawContext;Lnet/minecraft/screen/slot/Slot;)V", shift = At.Shift.BEFORE), locals = LocalCapture.CAPTURE_FAILHARD) - public void onBeforeDrawSlot(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci, int i, int j, int k, Slot slot) { - SlotRenderEvents.Before event = new SlotRenderEvents.Before(context, slot, mouseX, mouseY, delta); - SlotRenderEvents.Before.Companion.publish(event); - } + @Shadow + @Final + protected T handler; + + @Shadow + public abstract T getScreenHandler(); + + @Shadow + protected int y; + @Shadow + protected int x; + @Unique + PlayerInventory playerInventory; + + @Inject(method = "<init>", at = @At("TAIL")) + public void savePlayerInventory(ScreenHandler handler, PlayerInventory inventory, Text title, CallbackInfo ci) { + this.playerInventory = inventory; + } + + @Inject(method = "keyPressed", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;handleHotbarKeyPressed(II)Z", shift = At.Shift.BEFORE), cancellable = true) + public void onKeyPressed(int keyCode, int scanCode, int modifiers, CallbackInfoReturnable<Boolean> cir) { + if (HandledScreenKeyPressedEvent.Companion.publish(new HandledScreenKeyPressedEvent((HandledScreen<?>) (Object) this, keyCode, scanCode, modifiers)).getCancelled()) { + cir.setReturnValue(true); + } + } + + @Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true) + public void onMouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable<Boolean> cir) { + if (HandledScreenClickEvent.Companion.publish(new HandledScreenClickEvent((HandledScreen<?>) (Object) this, mouseX, mouseY, button)).getCancelled()) { + cir.setReturnValue(true); + } + } + + boolean keyReleased_firmament(int keyCode, int scanCode, int modifiers) { + return HandledScreenKeyReleasedEvent.Companion.publish(new HandledScreenKeyReleasedEvent((HandledScreen<?>) (Object) this, keyCode, scanCode, modifiers)).getCancelled(); + } + + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawForeground(Lnet/minecraft/client/gui/DrawContext;II)V", shift = At.Shift.AFTER)) + public void onAfterRenderForeground(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { + context.getMatrices().push(); + context.getMatrices().translate(-x, -y, 0); + HandledScreenForegroundEvent.Companion.publish(new HandledScreenForegroundEvent((HandledScreen<?>) (Object) this, context, mouseX, mouseY, delta)); + context.getMatrices().pop(); + } + + @Inject(method = "onMouseClick(Lnet/minecraft/screen/slot/Slot;IILnet/minecraft/screen/slot/SlotActionType;)V", at = @At("HEAD"), cancellable = true) + public void onMouseClickedSlot(Slot slot, int slotId, int button, SlotActionType actionType, CallbackInfo ci) { + if (slotId == -999 && getScreenHandler() != null && actionType == SlotActionType.PICKUP) { // -999 is code for "clicked outside the main window" + ItemStack cursorStack = getScreenHandler().getCursorStack(); + if (cursorStack != null && IsSlotProtectedEvent.shouldBlockInteraction(slot, SlotActionType.THROW, cursorStack)) { + ci.cancel(); + return; + } + } + if (IsSlotProtectedEvent.shouldBlockInteraction(slot, actionType)) { + ci.cancel(); + return; + } + if (actionType == SlotActionType.SWAP && 0 <= button && button < 9) { + if (IsSlotProtectedEvent.shouldBlockInteraction(new Slot(playerInventory, button, 0, 0), actionType)) { + ci.cancel(); + } + } + } + + + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawSlot(Lnet/minecraft/client/gui/DrawContext;Lnet/minecraft/screen/slot/Slot;)V", shift = At.Shift.AFTER), locals = LocalCapture.CAPTURE_FAILHARD) + public void onAfterDrawSlot(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci, int i, int j, int k, Slot slot) { + SlotRenderEvents.After event = new SlotRenderEvents.After(context, slot, mouseX, mouseY, delta); + SlotRenderEvents.After.Companion.publish(event); + } + + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawSlot(Lnet/minecraft/client/gui/DrawContext;Lnet/minecraft/screen/slot/Slot;)V", shift = At.Shift.BEFORE), locals = LocalCapture.CAPTURE_FAILHARD) + public void onBeforeDrawSlot(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci, int i, int j, int k, Slot slot) { + SlotRenderEvents.Before event = new SlotRenderEvents.Before(context, slot, mouseX, mouseY, delta); + SlotRenderEvents.Before.Companion.publish(event); + } } diff --git a/src/main/kotlin/events/HandledScreenKeyPressedEvent.kt b/src/main/kotlin/events/HandledScreenKeyPressedEvent.kt index 7ec2abb..183ec71 100644 --- a/src/main/kotlin/events/HandledScreenKeyPressedEvent.kt +++ b/src/main/kotlin/events/HandledScreenKeyPressedEvent.kt @@ -1,24 +1,38 @@ - - package moe.nea.firmament.events import net.minecraft.client.gui.screen.ingame.HandledScreen import net.minecraft.client.option.KeyBinding import moe.nea.firmament.keybindings.IKeyBinding -data class HandledScreenKeyPressedEvent( - val screen: HandledScreen<*>, - val keyCode: Int, - val scanCode: Int, - val modifiers: Int -) : FirmamentEvent.Cancellable() { - companion object : FirmamentEventBus<HandledScreenKeyPressedEvent>() +sealed interface HandledScreenKeyEvent { + val screen: HandledScreen<*> + val keyCode: Int + val scanCode: Int + val modifiers: Int + + fun matches(keyBinding: KeyBinding): Boolean { + return matches(IKeyBinding.minecraft(keyBinding)) + } - fun matches(keyBinding: KeyBinding): Boolean { - return matches(IKeyBinding.minecraft(keyBinding)) - } + fun matches(keyBinding: IKeyBinding): Boolean { + return keyBinding.matches(keyCode, scanCode, modifiers) + } +} + +data class HandledScreenKeyPressedEvent( + override val screen: HandledScreen<*>, + override val keyCode: Int, + override val scanCode: Int, + override val modifiers: Int +) : FirmamentEvent.Cancellable(), HandledScreenKeyEvent { + companion object : FirmamentEventBus<HandledScreenKeyPressedEvent>() +} - fun matches(keyBinding: IKeyBinding): Boolean { - return keyBinding.matches(keyCode, scanCode, modifiers) - } +data class HandledScreenKeyReleasedEvent( + override val screen: HandledScreen<*>, + override val keyCode: Int, + override val scanCode: Int, + override val modifiers: Int +) : FirmamentEvent.Cancellable(), HandledScreenKeyEvent { + companion object : FirmamentEventBus<HandledScreenKeyReleasedEvent>() } diff --git a/src/main/kotlin/features/inventory/SlotLocking.kt b/src/main/kotlin/features/inventory/SlotLocking.kt index 611b7e4..de54005 100644 --- a/src/main/kotlin/features/inventory/SlotLocking.kt +++ b/src/main/kotlin/features/inventory/SlotLocking.kt @@ -1,5 +1,3 @@ - - @file:UseSerializers(DashlessUUIDSerializer::class) package moe.nea.firmament.features.inventory @@ -13,11 +11,15 @@ import kotlinx.serialization.serializer import net.minecraft.client.gui.screen.ingame.HandledScreen import net.minecraft.entity.player.PlayerInventory import net.minecraft.screen.GenericContainerScreenHandler +import net.minecraft.screen.slot.Slot import net.minecraft.screen.slot.SlotActionType import net.minecraft.util.Identifier import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.HandledScreenForegroundEvent import moe.nea.firmament.events.HandledScreenKeyPressedEvent +import moe.nea.firmament.events.HandledScreenKeyReleasedEvent import moe.nea.firmament.events.IsSlotProtectedEvent +import moe.nea.firmament.events.ScreenChangeEvent import moe.nea.firmament.events.SlotRenderEvents import moe.nea.firmament.features.FirmamentFeature import moe.nea.firmament.gui.config.ManagedConfig @@ -28,176 +30,325 @@ import moe.nea.firmament.util.MC import moe.nea.firmament.util.SBData import moe.nea.firmament.util.SkyBlockIsland import moe.nea.firmament.util.data.ProfileSpecificDataHolder +import moe.nea.firmament.util.json.DashlessUUIDSerializer +import moe.nea.firmament.util.mc.ScreenUtil.getSlotByIndex +import moe.nea.firmament.util.mc.SlotUtils.swapWithHotBar import moe.nea.firmament.util.mc.displayNameAccordingToNbt import moe.nea.firmament.util.mc.loreAccordingToNbt -import moe.nea.firmament.util.json.DashlessUUIDSerializer +import moe.nea.firmament.util.render.drawLine import moe.nea.firmament.util.skyblockUUID import moe.nea.firmament.util.unformattedString object SlotLocking : FirmamentFeature { - override val identifier: String - get() = "slot-locking" - - @Serializable - data class Data( - val lockedSlots: MutableSet<Int> = mutableSetOf(), - val lockedSlotsRift: MutableSet<Int> = mutableSetOf(), - - val lockedUUIDs: MutableSet<UUID> = mutableSetOf(), - ) - - object TConfig : ManagedConfig(identifier, Category.INVENTORY) { - val lockSlot by keyBinding("lock") { GLFW.GLFW_KEY_L } - val lockUUID by keyBindingWithOutDefaultModifiers("lock-uuid") { - SavedKeyBinding(GLFW.GLFW_KEY_L, shift = true) - } - } - - override val config: TConfig - get() = TConfig - - object DConfig : ProfileSpecificDataHolder<Data>(serializer(), "locked-slots", ::Data) - - val lockedUUIDs get() = DConfig.data?.lockedUUIDs - - val lockedSlots - get() = when (SBData.skyblockLocation) { - SkyBlockIsland.RIFT -> DConfig.data?.lockedSlotsRift - null -> null - else -> DConfig.data?.lockedSlots - } - - fun isSalvageScreen(screen: HandledScreen<*>?): Boolean { - if (screen == null) return false - return screen.title.unformattedString.contains("Salvage Item") - } - - fun isTradeScreen(screen: HandledScreen<*>?): Boolean { - if (screen == null) return false - val handler = screen.screenHandler as? GenericContainerScreenHandler ?: return false - if (handler.inventory.size() < 9) return false - val middlePane = handler.inventory.getStack(handler.inventory.size() - 5) - if (middlePane == null) return false - return middlePane.displayNameAccordingToNbt?.unformattedString == "⇦ Your stuff" - } - - fun isNpcShop(screen: HandledScreen<*>?): Boolean { - if (screen == null) return false - val handler = screen.screenHandler as? GenericContainerScreenHandler ?: return false - if (handler.inventory.size() < 9) return false - val sellItem = handler.inventory.getStack(handler.inventory.size() - 5) - if (sellItem == null) return false - if (sellItem.displayNameAccordingToNbt?.unformattedString == "Sell Item") return true - val lore = sellItem.loreAccordingToNbt - return (lore.lastOrNull() ?: return false).unformattedString == "Click to buyback!" - } - - @Subscribe - fun onSalvageProtect(event: IsSlotProtectedEvent) { - if (event.slot == null) return - if (!event.slot.hasStack()) return - if (event.slot.stack.displayNameAccordingToNbt?.unformattedString != "Salvage Items") return - val inv = event.slot.inventory - var anyBlocked = false - for (i in 0 until event.slot.index) { - val stack = inv.getStack(i) - if (IsSlotProtectedEvent.shouldBlockInteraction(null, SlotActionType.THROW, stack)) - anyBlocked = true - } - if (anyBlocked) { - event.protectSilent() - } - } - - @Subscribe - fun onProtectUuidItems(event: IsSlotProtectedEvent) { - val doesNotDeleteItem = event.actionType == SlotActionType.SWAP - || event.actionType == SlotActionType.PICKUP - || event.actionType == SlotActionType.QUICK_MOVE - || event.actionType == SlotActionType.QUICK_CRAFT - || event.actionType == SlotActionType.CLONE - || event.actionType == SlotActionType.PICKUP_ALL - val isSellOrTradeScreen = - isNpcShop(MC.handledScreen) || isTradeScreen(MC.handledScreen) || isSalvageScreen(MC.handledScreen) - if ((!isSellOrTradeScreen || event.slot?.inventory !is PlayerInventory) - && doesNotDeleteItem - ) return - val stack = event.itemStack ?: return - val uuid = stack.skyblockUUID ?: return - if (uuid in (lockedUUIDs ?: return)) { - event.protect() - } - } - - @Subscribe - fun onProtectSlot(it: IsSlotProtectedEvent) { - if (it.slot != null && it.slot.inventory is PlayerInventory && it.slot.index in (lockedSlots ?: setOf())) { - it.protect() - } - } - - @Subscribe - fun onLockUUID(it: HandledScreenKeyPressedEvent) { - if (!it.matches(TConfig.lockUUID)) return - val inventory = MC.handledScreen ?: return - inventory as AccessorHandledScreen - - val slot = inventory.focusedSlot_Firmament ?: return - val stack = slot.stack ?: return - val uuid = stack.skyblockUUID ?: return - val lockedUUIDs = lockedUUIDs ?: return - if (uuid in lockedUUIDs) { - lockedUUIDs.remove(uuid) - } else { - lockedUUIDs.add(uuid) - } - DConfig.markDirty() - CommonSoundEffects.playSuccess() - it.cancel() - } - - @Subscribe - fun onLockSlot(it: HandledScreenKeyPressedEvent) { - if (!it.matches(TConfig.lockSlot)) return - val inventory = MC.handledScreen ?: return - inventory as AccessorHandledScreen - - val slot = inventory.focusedSlot_Firmament ?: return - val lockedSlots = lockedSlots ?: return - if (slot.inventory is PlayerInventory) { - if (slot.index in lockedSlots) { - lockedSlots.remove(slot.index) - } else { - lockedSlots.add(slot.index) - } - DConfig.markDirty() - CommonSoundEffects.playSuccess() - } - } - - @Subscribe - fun onRenderSlotOverlay(it: SlotRenderEvents.After) { - val isSlotLocked = it.slot.inventory is PlayerInventory && it.slot.index in (lockedSlots ?: setOf()) - val isUUIDLocked = (it.slot.stack?.skyblockUUID) in (lockedUUIDs ?: setOf()) - if (isSlotLocked || isUUIDLocked) { - RenderSystem.disableDepthTest() - it.context.drawSprite( - it.slot.x, it.slot.y, 0, - 16, 16, - MC.guiAtlasManager.getSprite( - when { - isSlotLocked -> - (Identifier.of("firmament:slot_locked")) - - isUUIDLocked -> - (Identifier.of("firmament:uuid_locked")) - - else -> - error("unreachable") - } - ) - ) - RenderSystem.enableDepthTest() - } - } + override val identifier: String + get() = "slot-locking" + + @Serializable + data class Data( + val lockedSlots: MutableSet<Int> = mutableSetOf(), + val lockedSlotsRift: MutableSet<Int> = mutableSetOf(), + val lockedUUIDs: MutableSet<UUID> = mutableSetOf(), + val boundSlots: MutableMap<Int, Int> = mutableMapOf() + ) + + object TConfig : ManagedConfig(identifier, Category.INVENTORY) { + val lockSlot by keyBinding("lock") { GLFW.GLFW_KEY_L } + val lockUUID by keyBindingWithOutDefaultModifiers("lock-uuid") { + SavedKeyBinding(GLFW.GLFW_KEY_L, shift = true) + } + val slotBind by keyBinding("bind") { GLFW.GLFW_KEY_L } + val slotBindRequireShift by toggle("require-quick-move") { true } + } + + override val config: TConfig + get() = TConfig + + object DConfig : ProfileSpecificDataHolder<Data>(serializer(), "locked-slots", ::Data) + + val lockedUUIDs get() = DConfig.data?.lockedUUIDs + + val lockedSlots + get() = when (SBData.skyblockLocation) { + SkyBlockIsland.RIFT -> DConfig.data?.lockedSlotsRift + null -> null + else -> DConfig.data?.lockedSlots + } + + fun isSalvageScreen(screen: HandledScreen<*>?): Boolean { + if (screen == null) return false + return screen.title.unformattedString.contains("Salvage Item") + } + + fun isTradeScreen(screen: HandledScreen<*>?): Boolean { + if (screen == null) return false + val handler = screen.screenHandler as? GenericContainerScreenHandler ?: return false + if (handler.inventory.size() < 9) return false + val middlePane = handler.inventory.getStack(handler.inventory.size() - 5) + if (middlePane == null) return false + return middlePane.displayNameAccordingToNbt?.unformattedString == "⇦ Your stuff" + } + + fun isNpcShop(screen: HandledScreen<*>?): Boolean { + if (screen == null) return false + val handler = screen.screenHandler as? GenericContainerScreenHandler ?: return false + if (handler.inventory.size() < 9) return false + val sellItem = handler.inventory.getStack(handler.inventory.size() - 5) + if (sellItem == null) return false + if (sellItem.displayNameAccordingToNbt?.unformattedString == "Sell Item") return true + val lore = sellItem.loreAccordingToNbt + return (lore.lastOrNull() ?: return false).unformattedString == "Click to buyback!" + } + + @Subscribe + fun onSalvageProtect(event: IsSlotProtectedEvent) { + if (event.slot == null) return + if (!event.slot.hasStack()) return + if (event.slot.stack.displayNameAccordingToNbt?.unformattedString != "Salvage Items") return + val inv = event.slot.inventory + var anyBlocked = false + for (i in 0 until event.slot.index) { + val stack = inv.getStack(i) + if (IsSlotProtectedEvent.shouldBlockInteraction(null, SlotActionType.THROW, stack)) + anyBlocked = true + } + if (anyBlocked) { + event.protectSilent() + } + } + + @Subscribe + fun onProtectUuidItems(event: IsSlotProtectedEvent) { + val doesNotDeleteItem = event.actionType == SlotActionType.SWAP + || event.actionType == SlotActionType.PICKUP + || event.actionType == SlotActionType.QUICK_MOVE + || event.actionType == SlotActionType.QUICK_CRAFT + || event.actionType == SlotActionType.CLONE + || event.actionType == SlotActionType.PICKUP_ALL + val isSellOrTradeScreen = + isNpcShop(MC.handledScreen) || isTradeScreen(MC.handledScreen) || isSalvageScreen(MC.handledScreen) + if ((!isSellOrTradeScreen || event.slot?.inventory !is PlayerInventory) + && doesNotDeleteItem + ) return + val stack = event.itemStack ?: return + val uuid = stack.skyblockUUID ?: return + if (uuid in (lockedUUIDs ?: return)) { + event.protect() + } + } + + @Subscribe + fun onProtectSlot(it: IsSlotProtectedEvent) { + if (it.slot != null && it.slot.inventory is PlayerInventory && it.slot.index in (lockedSlots ?: setOf())) { + it.protect() + } + } + + @Subscribe + fun onQuickMoveBoundSlot(it: IsSlotProtectedEvent) { + val boundSlots = DConfig.data?.boundSlots ?: mapOf() + val isValidAction = + it.actionType == SlotActionType.QUICK_MOVE || (it.actionType == SlotActionType.PICKUP && !TConfig.slotBindRequireShift) + if (!isValidAction) return + val handler = MC.handledScreen?.screenHandler ?: return + val slot = it.slot + if (slot != null && it.slot.inventory is PlayerInventory) { + val boundSlot = boundSlots.entries.find { + it.value == slot.index || it.key == slot.index + } ?: return + it.protectSilent() + val inventorySlot = MC.handledScreen?.getSlotByIndex(boundSlot.value, true) + inventorySlot?.swapWithHotBar(handler, boundSlot.key) + } + } + + @Subscribe + fun onLockUUID(it: HandledScreenKeyPressedEvent) { + if (!it.matches(TConfig.lockUUID)) return + val inventory = MC.handledScreen ?: return + inventory as AccessorHandledScreen + + val slot = inventory.focusedSlot_Firmament ?: return + val stack = slot.stack ?: return + val uuid = stack.skyblockUUID ?: return + val lockedUUIDs = lockedUUIDs ?: return + if (uuid in lockedUUIDs) { + lockedUUIDs.remove(uuid) + } else { + lockedUUIDs.add(uuid) + } + DConfig.markDirty() + CommonSoundEffects.playSuccess() + it.cancel() + } + + + @Subscribe + fun onLockSlotKeyRelease(it: HandledScreenKeyReleasedEvent) { + val inventory = MC.handledScreen ?: return + inventory as AccessorHandledScreen + val slot = inventory.focusedSlot_Firmament + val storedSlot = storedLockingSlot ?: return + + if (it.matches(TConfig.slotBind) && slot != storedSlot && slot != null && slot.isHotbar() != storedSlot.isHotbar()) { + storedLockingSlot = null + val hotBarSlot = if (slot.isHotbar()) slot else storedSlot + val invSlot = if (slot.isHotbar()) storedSlot else slot + val boundSlots = DConfig.data?.boundSlots ?: return + lockedSlots?.remove(hotBarSlot.index) + lockedSlots?.remove(invSlot.index) + boundSlots.entries.removeIf { + it.value == invSlot.index + } + boundSlots[hotBarSlot.index] = invSlot.index + DConfig.markDirty() + CommonSoundEffects.playSuccess() + return + } + if (it.matches(TConfig.lockSlot) && slot == storedSlot) { + storedLockingSlot = null + toggleSlotLock(slot) + return + } + if (it.matches(TConfig.slotBind)) { + storedLockingSlot = null + } + } + + @Subscribe + fun onRenderAllBoundSlots(event: HandledScreenForegroundEvent) { + val boundSlots = DConfig.data?.boundSlots ?: return + fun findByIndex(index: Int) = event.screen.getSlotByIndex(index, true) + val accScreen = event.screen as AccessorHandledScreen + val sx = accScreen.x_Firmament + val sy = accScreen.y_Firmament + boundSlots.entries.forEach { + val hotbarSlot = findByIndex(it.key) ?: return@forEach + val inventorySlot = findByIndex(it.value) ?: return@forEach + + val (hotX, hotY) = hotbarSlot.lineCenter() + val (invX, invY) = inventorySlot.lineCenter() + event.context.drawLine( + invX + sx, invY + sy, + hotX + sx, hotY + sy, + me.shedaniel.math.Color.ofOpaque(0x00FF00) + ) + event.context.drawBorder(hotbarSlot.x + sx, + hotbarSlot.y + sy, + 16, 16, 0xFF00FF00u.toInt()) + event.context.drawBorder(inventorySlot.x + sx, + inventorySlot.y + sy, + 16, 16, 0xFF00FF00u.toInt()) + } + } + + @Subscribe + fun onRenderCurrentDraggingSlot(event: HandledScreenForegroundEvent) { + val draggingSlot = storedLockingSlot ?: return + val accScreen = event.screen as AccessorHandledScreen + val hoveredSlot = accScreen.focusedSlot_Firmament + ?.takeIf { it.inventory is PlayerInventory } + ?.takeIf { it == draggingSlot || it.isHotbar() != draggingSlot.isHotbar() } + val sx = accScreen.x_Firmament + val sy = accScreen.y_Firmament + val (borderX, borderY) = draggingSlot.lineCenter() + event.context.drawBorder(draggingSlot.x + sx, draggingSlot.y + sy, 16, 16, 0xFF00FF00u.toInt()) + if (hoveredSlot == null) { + event.context.drawLine( + borderX + sx, borderY + sy, + event.mouseX, event.mouseY, + me.shedaniel.math.Color.ofOpaque(0x00FF00) + ) + } else if (hoveredSlot != draggingSlot) { + val (hovX, hovY) = hoveredSlot.lineCenter() + event.context.drawLine( + borderX + sx, borderY + sy, + hovX + sx, hovY + sy, + me.shedaniel.math.Color.ofOpaque(0x00FF00) + ) + event.context.drawBorder(hoveredSlot.x + sx, + hoveredSlot.y + sy, + 16, 16, 0xFF00FF00u.toInt()) + } + } + + fun Slot.lineCenter(): Pair<Int, Int> { + return if (isHotbar()) { + x + 9 to y + } else { + x + 9 to y + 17 + } + } + + + fun Slot.isHotbar(): Boolean { + return index < 9 + } + + @Subscribe + fun onScreenChange(event: ScreenChangeEvent) { + storedLockingSlot = null + } + + var storedLockingSlot: Slot? = null + + fun toggleSlotLock(slot: Slot) { + val lockedSlots = lockedSlots ?: return + val boundSlots = DConfig.data?.boundSlots ?: mutableMapOf() + if (slot.inventory is PlayerInventory) { + if (boundSlots.entries.removeIf { + it.value == slot.index || it.key == slot.index + }) { + // intentionally do nothing + } else if (slot.index in lockedSlots) { + lockedSlots.remove(slot.index) + } else { + lockedSlots.add(slot.index) + } + DConfig.markDirty() + CommonSoundEffects.playSuccess() + } + } + + @Subscribe + fun onLockSlot(it: HandledScreenKeyPressedEvent) { + val inventory = MC.handledScreen ?: return + inventory as AccessorHandledScreen + + val slot = inventory.focusedSlot_Firmament ?: return + if (slot.inventory !is PlayerInventory) return + if (it.matches(TConfig.slotBind)) { + storedLockingSlot = storedLockingSlot ?: slot + return + } + if (!it.matches(TConfig.lockSlot)) { + return + } + toggleSlotLock(slot) + } + + @Subscribe + fun onRenderSlotOverlay(it: SlotRenderEvents.After) { + val isSlotLocked = it.slot.inventory is PlayerInventory && it.slot.index in (lockedSlots ?: setOf()) + val isUUIDLocked = (it.slot.stack?.skyblockUUID) in (lockedUUIDs ?: setOf()) + if (isSlotLocked || isUUIDLocked) { + RenderSystem.disableDepthTest() + it.context.drawSprite( + it.slot.x, it.slot.y, 0, + 16, 16, + MC.guiAtlasManager.getSprite( + when { + isSlotLocked -> + (Identifier.of("firmament:slot_locked")) + + isUUIDLocked -> + (Identifier.of("firmament:uuid_locked")) + + else -> + error("unreachable") + } + ) + ) + RenderSystem.enableDepthTest() + } + } } diff --git a/src/main/kotlin/features/mining/HotmPresets.kt b/src/main/kotlin/features/mining/HotmPresets.kt index 329ff77..3f83f3d 100644 --- a/src/main/kotlin/features/mining/HotmPresets.kt +++ b/src/main/kotlin/features/mining/HotmPresets.kt @@ -29,6 +29,7 @@ import moe.nea.firmament.util.TimeMark import moe.nea.firmament.util.customgui.CustomGui import moe.nea.firmament.util.customgui.customGui import moe.nea.firmament.util.mc.CommonTextures +import moe.nea.firmament.util.mc.SlotUtils.clickRightMouseButton import moe.nea.firmament.util.mc.displayNameAccordingToNbt import moe.nea.firmament.util.unformattedString import moe.nea.firmament.util.useMatch @@ -103,26 +104,6 @@ object HotmPresets { var hasScrolled = false var hasAll = false - fun Slot.clickMiddleMouseButton(handler: ScreenHandler) { - MC.interactionManager?.clickSlot( - handler.syncId, - this.id, - 2, - SlotActionType.CLONE, - MC.player - ) - } - - fun Slot.clickRightMouseButton(handler: ScreenHandler) { - MC.interactionManager?.clickSlot( - handler.syncId, - this.id, - 1, - SlotActionType.PICKUP, - MC.player - ) - } - override fun mouseClick(mouseX: Double, mouseY: Double, button: Int): Boolean { if (!hasScrolled) { val slot = screen.screenHandler.getSlot(8) diff --git a/src/main/kotlin/util/mc/ScreenUtil.kt b/src/main/kotlin/util/mc/ScreenUtil.kt new file mode 100644 index 0000000..36feb6b --- /dev/null +++ b/src/main/kotlin/util/mc/ScreenUtil.kt @@ -0,0 +1,26 @@ +package moe.nea.firmament.util.mc + +import net.minecraft.client.gui.screen.Screen +import net.minecraft.client.gui.screen.ingame.HandledScreen +import net.minecraft.entity.player.PlayerInventory +import net.minecraft.screen.slot.Slot + +object ScreenUtil { + private var lastScreen: Screen? = null + private var slotsByIndex: Map<SlotIndex, Slot> = mapOf() + + data class SlotIndex(val index: Int, val isPlayerInventory: Boolean) + + fun Screen.getSlotsByIndex(): Map<SlotIndex, Slot> { + if (this !is HandledScreen<*>) return mapOf() + if (lastScreen === this) return slotsByIndex + lastScreen = this + slotsByIndex = this.screenHandler.slots.associate { + SlotIndex(it.index, it.inventory is PlayerInventory) to it + } + return slotsByIndex + } + + fun Screen.getSlotByIndex( index: Int, isPlayerInventory: Boolean): Slot? = + getSlotsByIndex()[SlotIndex(index, isPlayerInventory)] +} diff --git a/src/main/kotlin/util/mc/SlotUtils.kt b/src/main/kotlin/util/mc/SlotUtils.kt new file mode 100644 index 0000000..4709dcf --- /dev/null +++ b/src/main/kotlin/util/mc/SlotUtils.kt @@ -0,0 +1,35 @@ +package moe.nea.firmament.util.mc + +import net.minecraft.screen.ScreenHandler +import net.minecraft.screen.slot.Slot +import net.minecraft.screen.slot.SlotActionType +import moe.nea.firmament.util.MC + +object SlotUtils { + fun Slot.clickMiddleMouseButton(handler: ScreenHandler) { + MC.interactionManager?.clickSlot( + handler.syncId, + this.id, + 2, + SlotActionType.CLONE, + MC.player + ) + } + + fun Slot.swapWithHotBar(handler: ScreenHandler, hotbarIndex: Int) { + MC.interactionManager?.clickSlot( + handler.syncId, this.id, + hotbarIndex, SlotActionType.SWAP, + MC.player) + } + + fun Slot.clickRightMouseButton(handler: ScreenHandler) { + MC.interactionManager?.clickSlot( + handler.syncId, + this.id, + 1, + SlotActionType.PICKUP, + MC.player + ) + } +} diff --git a/src/main/kotlin/util/render/DrawContextExt.kt b/src/main/kotlin/util/render/DrawContextExt.kt index 48698b4..3e086b8 100644 --- a/src/main/kotlin/util/render/DrawContextExt.kt +++ b/src/main/kotlin/util/render/DrawContextExt.kt @@ -1,8 +1,27 @@ package moe.nea.firmament.util.render +import com.mojang.blaze3d.systems.RenderSystem +import me.shedaniel.math.Color import org.joml.Matrix4f import net.minecraft.client.gui.DrawContext +import moe.nea.firmament.util.MC fun DrawContext.isUntranslatedGuiDrawContext(): Boolean { return (matrices.peek().positionMatrix.properties() and Matrix4f.PROPERTY_IDENTITY.toInt()) != 0 } + +fun DrawContext.drawLine(fromX: Int, fromY: Int, toX: Int, toY: Int, color: Color) { + // TODO: push scissors + if (toY < fromY) { + drawLine(toX, toY, fromX, fromY, color) + return + } + RenderSystem.lineWidth(MC.window.scaleFactor.toFloat()) + val buf = this.vertexConsumers.getBuffer(RenderInWorldContext.RenderLayers.LINES) + buf.vertex(fromX.toFloat(), fromY.toFloat(), 0F).color(color.color) + .normal(toX - fromX.toFloat(), toY - fromY.toFloat(), 0F) + buf.vertex(toX.toFloat(), toY.toFloat(), 0F).color(color.color) + .normal(toX - fromX.toFloat(), toY - fromY.toFloat(), 0F) + this.draw() +} + diff --git a/src/main/resources/assets/firmament/lang/en_us.json b/src/main/resources/assets/firmament/lang/en_us.json index 1f2bbeb..cd4d2d4 100644 --- a/src/main/resources/assets/firmament/lang/en_us.json +++ b/src/main/resources/assets/firmament/lang/en_us.json @@ -177,6 +177,8 @@ "firmament.config.slot-locking": "Slot Locking", "firmament.config.slot-locking.lock": "Lock Slot", "firmament.config.slot-locking.lock-uuid": "Lock UUID (Lock Item)", + "firmament.config.slot-locking.bind": "Bind Slot", + "firmament.config.slot-locking.require-quick-move": "Require Shift-Click for Bind", "firmament.config.fixes.auto-sprint": "Auto Sprint", "firmament.config.fixes.auto-sprint-keybinding": "Auto Sprint KeyBinding", "firmament.config.fixes.auto-sprint-hud": "Sprint State Hud", |