aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2024-10-18 00:41:25 +0200
committerLinnea Gräf <nea@nea.moe>2024-10-18 14:36:51 +0200
commitc89b663acad487caeb15f7521be3dd14342dd4e7 (patch)
treeae21daabaf5de1ac5281509c6fa9446259169ae5
parent7de0e8e7e09e3428c17ca9717c21c02469c31b76 (diff)
downloadfirmament-c89b663acad487caeb15f7521be3dd14342dd4e7.tar.gz
firmament-c89b663acad487caeb15f7521be3dd14342dd4e7.tar.bz2
firmament-c89b663acad487caeb15f7521be3dd14342dd4e7.zip
Add slot binding
-rw-r--r--src/main/java/moe/nea/firmament/init/HandledScreenRiser.java192
-rw-r--r--src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java150
-rw-r--r--src/main/kotlin/events/HandledScreenKeyPressedEvent.kt44
-rw-r--r--src/main/kotlin/features/inventory/SlotLocking.kt487
-rw-r--r--src/main/kotlin/features/mining/HotmPresets.kt21
-rw-r--r--src/main/kotlin/util/mc/ScreenUtil.kt26
-rw-r--r--src/main/kotlin/util/mc/SlotUtils.kt35
-rw-r--r--src/main/kotlin/util/render/DrawContextExt.kt19
-rw-r--r--src/main/resources/assets/firmament/lang/en_us.json2
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",