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 org.lwjgl.glfw.GLFW
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
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 net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.entity.player.PlayerInventory
@@ -51,9 +60,66 @@ object SlotLocking : FirmamentFeature {
val lockedSlots: MutableSet<Int> = mutableSetOf(),
val lockedSlotsRift: MutableSet<Int> = 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) {
val lockSlot by keyBinding("lock") { GLFW.GLFW_KEY_L }
val lockUUID by keyBindingWithOutDefaultModifiers("lock-uuid") {
@@ -62,6 +128,7 @@ object SlotLocking : FirmamentFeature {
val slotBind by keyBinding("bind") { GLFW.GLFW_KEY_L }
val slotBindRequireShift by toggle("require-quick-move") { true }
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 }
}
@@ -177,19 +244,19 @@ object SlotLocking : FirmamentFeature {
@Subscribe
fun onQuickMoveBoundSlot(it: IsSlotProtectedEvent) {
val boundSlots = DConfig.data?.boundSlots ?: mapOf()
val boundSlots = DConfig.data?.boundSlots ?: BoundSlots()
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
val matchingSlots = boundSlots.findMatchingSlots(slot.index)
if (matchingSlots.isEmpty()) return
it.protectSilent()
val inventorySlot = MC.handledScreen?.getSlotByIndex(boundSlot.value, true)
inventorySlot?.swapWithHotBar(handler, boundSlot.key)
val boundSlot = matchingSlots.singleOrNull() ?: return
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
lockedSlots?.remove(hotBarSlot.index)
lockedSlots?.remove(invSlot.index)
boundSlots.entries.removeIf {
it.value == invSlot.index
}
boundSlots[hotBarSlot.index] = invSlot.index
boundSlots.removeDuplicateForInventory(invSlot.index)
boundSlots.insert(hotBarSlot.index, invSlot.index)
DConfig.markDirty()
CommonSoundEffects.playSuccess()
return
@@ -245,9 +310,7 @@ object SlotLocking : FirmamentFeature {
storedLockingSlot = null
val boundSlots = DConfig.data?.boundSlots ?: return
if (slot != null)
boundSlots.entries.removeIf {
it.value == slot.index || it.key == slot.index
}
boundSlots.removeAllInvolving(slot.index)
}
}
@@ -258,9 +321,10 @@ object SlotLocking : FirmamentFeature {
val accScreen = event.screen as AccessorHandledScreen
val sx = accScreen.x_Firmament
val sy = accScreen.y_Firmament
for (it in boundSlots.entries) {
val hotbarSlot = findByIndex(it.key) ?: continue
val inventorySlot = findByIndex(it.value) ?: continue
val highlitSlots = mutableSetOf<Slot>()
for (it in boundSlots.pairs) {
val hotbarSlot = findByIndex(it.hotbar) ?: continue
val inventorySlot = findByIndex(it.inventory) ?: continue
val (hotX, hotY) = hotbarSlot.lineCenter()
val (invX, invY) = inventorySlot.lineCenter()
@@ -268,7 +332,12 @@ object SlotLocking : FirmamentFeature {
|| accScreen.focusedSlot_Firmament === inventorySlot
if (!anyHovered && TConfig.slotRenderLines == SlotRenderLinesMode.NOTHING)
continue
val color = if (anyHovered)
if (anyHovered) {
highlitSlots.add(hotbarSlot)
highlitSlots.add(inventorySlot)
}
fun color(highlit: Boolean) =
if (highlit)
me.shedaniel.math.Color.ofOpaque(0x00FF00)
else
me.shedaniel.math.Color.ofTransparent(0xc0a0f000.toInt())
@@ -276,14 +345,14 @@ object SlotLocking : FirmamentFeature {
event.context.drawLine(
invX + sx, invY + sy,
hotX + sx, hotY + sy,
color
color(anyHovered)
)
event.context.drawBorder(hotbarSlot.x + sx,
hotbarSlot.y + sy,
16, 16, color.color)
16, 16, color(hotbarSlot in highlitSlots).color)
event.context.drawBorder(inventorySlot.x + sx,
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) {
val lockedSlots = lockedSlots ?: return
val boundSlots = DConfig.data?.boundSlots ?: mutableMapOf()
val boundSlots = DConfig.data?.boundSlots ?: BoundSlots()
if (slot.inventory is PlayerInventory) {
if (boundSlots.entries.removeIf {
it.value == slot.index || it.key == slot.index
}) {
if (boundSlots.removeAllInvolving(slot.index)) {
// intentionally do nothing
} else if (slot.index in lockedSlots) {
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.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.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.description": "If turned off, allows to switch between bound slots without holding shift.",
"firmament.config.storage-overlay": "Storage Overlay",