feat: Allow multi slot binding

This commit is contained in:
Linnea Gräf
2025-02-27 23:06:26 +01:00
parent 5791c0cf38
commit 145fff6a59
2 changed files with 97 additions and 28 deletions

View File

@@ -4,8 +4,17 @@ package moe.nea.firmament.features.inventory
import java.util.UUID import java.util.UUID
import org.lwjgl.glfw.GLFW import org.lwjgl.glfw.GLFW
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonDecoder
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.int
import kotlinx.serialization.serializer import kotlinx.serialization.serializer
import net.minecraft.client.gui.screen.ingame.HandledScreen import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.entity.player.PlayerInventory import net.minecraft.entity.player.PlayerInventory
@@ -51,9 +60,66 @@ object SlotLocking : FirmamentFeature {
val lockedSlots: MutableSet<Int> = mutableSetOf(), val lockedSlots: MutableSet<Int> = mutableSetOf(),
val lockedSlotsRift: MutableSet<Int> = mutableSetOf(), val lockedSlotsRift: MutableSet<Int> = mutableSetOf(),
val lockedUUIDs: MutableSet<UUID> = mutableSetOf(), val lockedUUIDs: MutableSet<UUID> = mutableSetOf(),
val boundSlots: MutableMap<Int, Int> = mutableMapOf() val boundSlots: BoundSlots = BoundSlots()
) )
@Serializable
data class BoundSlot(
val hotbar: Int,
val inventory: Int,
)
@Serializable(with = BoundSlots.Serializer::class)
data class BoundSlots(
val pairs: MutableSet<BoundSlot> = mutableSetOf()
) {
fun findMatchingSlots(index: Int): List<BoundSlot> {
return pairs.filter { it.hotbar == index || it.inventory == index }
}
fun removeDuplicateForInventory(index: Int) {
pairs.removeIf { it.inventory == index }
}
fun removeAllInvolving(index: Int): Boolean {
return pairs.removeIf { it.inventory == index || it.hotbar == index }
}
fun insert(hotbar: Int, inventory: Int) {
if (!TConfig.allowMultiBinding) {
removeAllInvolving(hotbar)
removeAllInvolving(inventory)
}
pairs.add(BoundSlot(hotbar, inventory))
}
object Serializer : KSerializer<BoundSlots> {
override val descriptor: SerialDescriptor
get() = serializer<JsonElement>().descriptor
override fun serialize(
encoder: Encoder,
value: BoundSlots
) {
serializer<MutableSet<BoundSlot>>()
.serialize(encoder, value.pairs)
}
override fun deserialize(decoder: Decoder): BoundSlots {
decoder as JsonDecoder
val json = decoder.decodeJsonElement()
if (json is JsonObject) {
return BoundSlots(json.entries.map {
BoundSlot(it.key.toInt(), (it.value as JsonPrimitive).int)
}.toMutableSet())
}
return BoundSlots(decoder.json.decodeFromJsonElement(serializer<MutableSet<BoundSlot>>(), json))
}
}
}
object TConfig : ManagedConfig(identifier, Category.INVENTORY) { object TConfig : ManagedConfig(identifier, Category.INVENTORY) {
val lockSlot by keyBinding("lock") { GLFW.GLFW_KEY_L } val lockSlot by keyBinding("lock") { GLFW.GLFW_KEY_L }
val lockUUID by keyBindingWithOutDefaultModifiers("lock-uuid") { val lockUUID by keyBindingWithOutDefaultModifiers("lock-uuid") {
@@ -62,6 +128,7 @@ object SlotLocking : FirmamentFeature {
val slotBind by keyBinding("bind") { GLFW.GLFW_KEY_L } val slotBind by keyBinding("bind") { GLFW.GLFW_KEY_L }
val slotBindRequireShift by toggle("require-quick-move") { true } val slotBindRequireShift by toggle("require-quick-move") { true }
val slotRenderLines by choice("bind-render") { SlotRenderLinesMode.ONLY_BOXES } val slotRenderLines by choice("bind-render") { SlotRenderLinesMode.ONLY_BOXES }
val allowMultiBinding by toggle("multi-bind") { true } // TODO: filter based on this option
val allowDroppingInDungeons by toggle("drop-in-dungeons") { true } val allowDroppingInDungeons by toggle("drop-in-dungeons") { true }
} }
@@ -177,19 +244,19 @@ object SlotLocking : FirmamentFeature {
@Subscribe @Subscribe
fun onQuickMoveBoundSlot(it: IsSlotProtectedEvent) { fun onQuickMoveBoundSlot(it: IsSlotProtectedEvent) {
val boundSlots = DConfig.data?.boundSlots ?: mapOf() val boundSlots = DConfig.data?.boundSlots ?: BoundSlots()
val isValidAction = val isValidAction =
it.actionType == SlotActionType.QUICK_MOVE || (it.actionType == SlotActionType.PICKUP && !TConfig.slotBindRequireShift) it.actionType == SlotActionType.QUICK_MOVE || (it.actionType == SlotActionType.PICKUP && !TConfig.slotBindRequireShift)
if (!isValidAction) return if (!isValidAction) return
val handler = MC.handledScreen?.screenHandler ?: return val handler = MC.handledScreen?.screenHandler ?: return
val slot = it.slot val slot = it.slot
if (slot != null && it.slot.inventory is PlayerInventory) { if (slot != null && it.slot.inventory is PlayerInventory) {
val boundSlot = boundSlots.entries.find { val matchingSlots = boundSlots.findMatchingSlots(slot.index)
it.value == slot.index || it.key == slot.index if (matchingSlots.isEmpty()) return
} ?: return
it.protectSilent() it.protectSilent()
val inventorySlot = MC.handledScreen?.getSlotByIndex(boundSlot.value, true) val boundSlot = matchingSlots.singleOrNull() ?: return
inventorySlot?.swapWithHotBar(handler, boundSlot.key) val inventorySlot = MC.handledScreen?.getSlotByIndex(boundSlot.inventory, true)
inventorySlot?.swapWithHotBar(handler, boundSlot.hotbar)
} }
} }
@@ -228,10 +295,8 @@ object SlotLocking : FirmamentFeature {
val boundSlots = DConfig.data?.boundSlots ?: return val boundSlots = DConfig.data?.boundSlots ?: return
lockedSlots?.remove(hotBarSlot.index) lockedSlots?.remove(hotBarSlot.index)
lockedSlots?.remove(invSlot.index) lockedSlots?.remove(invSlot.index)
boundSlots.entries.removeIf { boundSlots.removeDuplicateForInventory(invSlot.index)
it.value == invSlot.index boundSlots.insert(hotBarSlot.index, invSlot.index)
}
boundSlots[hotBarSlot.index] = invSlot.index
DConfig.markDirty() DConfig.markDirty()
CommonSoundEffects.playSuccess() CommonSoundEffects.playSuccess()
return return
@@ -245,9 +310,7 @@ object SlotLocking : FirmamentFeature {
storedLockingSlot = null storedLockingSlot = null
val boundSlots = DConfig.data?.boundSlots ?: return val boundSlots = DConfig.data?.boundSlots ?: return
if (slot != null) if (slot != null)
boundSlots.entries.removeIf { boundSlots.removeAllInvolving(slot.index)
it.value == slot.index || it.key == slot.index
}
} }
} }
@@ -258,9 +321,10 @@ object SlotLocking : FirmamentFeature {
val accScreen = event.screen as AccessorHandledScreen val accScreen = event.screen as AccessorHandledScreen
val sx = accScreen.x_Firmament val sx = accScreen.x_Firmament
val sy = accScreen.y_Firmament val sy = accScreen.y_Firmament
for (it in boundSlots.entries) { val highlitSlots = mutableSetOf<Slot>()
val hotbarSlot = findByIndex(it.key) ?: continue for (it in boundSlots.pairs) {
val inventorySlot = findByIndex(it.value) ?: continue val hotbarSlot = findByIndex(it.hotbar) ?: continue
val inventorySlot = findByIndex(it.inventory) ?: continue
val (hotX, hotY) = hotbarSlot.lineCenter() val (hotX, hotY) = hotbarSlot.lineCenter()
val (invX, invY) = inventorySlot.lineCenter() val (invX, invY) = inventorySlot.lineCenter()
@@ -268,22 +332,27 @@ object SlotLocking : FirmamentFeature {
|| accScreen.focusedSlot_Firmament === inventorySlot || accScreen.focusedSlot_Firmament === inventorySlot
if (!anyHovered && TConfig.slotRenderLines == SlotRenderLinesMode.NOTHING) if (!anyHovered && TConfig.slotRenderLines == SlotRenderLinesMode.NOTHING)
continue continue
val color = if (anyHovered) if (anyHovered) {
me.shedaniel.math.Color.ofOpaque(0x00FF00) highlitSlots.add(hotbarSlot)
else highlitSlots.add(inventorySlot)
me.shedaniel.math.Color.ofTransparent(0xc0a0f000.toInt()) }
fun color(highlit: Boolean) =
if (highlit)
me.shedaniel.math.Color.ofOpaque(0x00FF00)
else
me.shedaniel.math.Color.ofTransparent(0xc0a0f000.toInt())
if (TConfig.slotRenderLines == SlotRenderLinesMode.EVERYTHING || anyHovered) if (TConfig.slotRenderLines == SlotRenderLinesMode.EVERYTHING || anyHovered)
event.context.drawLine( event.context.drawLine(
invX + sx, invY + sy, invX + sx, invY + sy,
hotX + sx, hotY + sy, hotX + sx, hotY + sy,
color color(anyHovered)
) )
event.context.drawBorder(hotbarSlot.x + sx, event.context.drawBorder(hotbarSlot.x + sx,
hotbarSlot.y + sy, hotbarSlot.y + sy,
16, 16, color.color) 16, 16, color(hotbarSlot in highlitSlots).color)
event.context.drawBorder(inventorySlot.x + sx, event.context.drawBorder(inventorySlot.x + sx,
inventorySlot.y + sy, inventorySlot.y + sy,
16, 16, color.color) 16, 16, color(inventorySlot in highlitSlots).color)
} }
} }
@@ -339,11 +408,9 @@ object SlotLocking : FirmamentFeature {
fun toggleSlotLock(slot: Slot) { fun toggleSlotLock(slot: Slot) {
val lockedSlots = lockedSlots ?: return val lockedSlots = lockedSlots ?: return
val boundSlots = DConfig.data?.boundSlots ?: mutableMapOf() val boundSlots = DConfig.data?.boundSlots ?: BoundSlots()
if (slot.inventory is PlayerInventory) { if (slot.inventory is PlayerInventory) {
if (boundSlots.entries.removeIf { if (boundSlots.removeAllInvolving(slot.index)) {
it.value == slot.index || it.key == slot.index
}) {
// intentionally do nothing // intentionally do nothing
} else if (slot.index in lockedSlots) { } else if (slot.index in lockedSlots) {
lockedSlots.remove(slot.index) lockedSlots.remove(slot.index)

View File

@@ -242,6 +242,8 @@
"firmament.config.slot-locking.lock-uuid": "Lock UUID (Lock Item)", "firmament.config.slot-locking.lock-uuid": "Lock UUID (Lock Item)",
"firmament.config.slot-locking.lock-uuid.description": "Lock a SkyBlock item by it's UUID. This blocks a specific item from being dropped/sold, but still allows moving it around.", "firmament.config.slot-locking.lock-uuid.description": "Lock a SkyBlock item by it's UUID. This blocks a specific item from being dropped/sold, but still allows moving it around.",
"firmament.config.slot-locking.lock.description": "Lock a slot, preventing any item from being moved from, dropped from, or placed into this slot.", "firmament.config.slot-locking.lock.description": "Lock a slot, preventing any item from being moved from, dropped from, or placed into this slot.",
"firmament.config.slot-locking.multi-bind": "Allow Multi Binding",
"firmament.config.slot-locking.multi-bind.description": "Allow binding the same hotbar slot to multiple inventory slots.",
"firmament.config.slot-locking.require-quick-move": "Require Shift-Click for Bound Slots", "firmament.config.slot-locking.require-quick-move": "Require Shift-Click for Bound Slots",
"firmament.config.slot-locking.require-quick-move.description": "If turned off, allows to switch between bound slots without holding shift.", "firmament.config.slot-locking.require-quick-move.description": "If turned off, allows to switch between bound slots without holding shift.",
"firmament.config.storage-overlay": "Storage Overlay", "firmament.config.storage-overlay": "Storage Overlay",