feat: Allow dropping protected items in dungeons

This commit is contained in:
Linnea Gräf
2025-01-17 18:00:43 +01:00
parent 19bc576b67
commit 74a043e535
9 changed files with 158 additions and 86 deletions

View File

@@ -4,8 +4,11 @@ package moe.nea.firmament.mixins;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local; import moe.nea.firmament.events.HandledScreenClickEvent;
import moe.nea.firmament.events.*; import moe.nea.firmament.events.HandledScreenForegroundEvent;
import moe.nea.firmament.events.HandledScreenKeyPressedEvent;
import moe.nea.firmament.events.IsSlotProtectedEvent;
import moe.nea.firmament.events.SlotRenderEvents;
import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.DrawContext;
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;
@@ -22,9 +25,6 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import java.util.Iterator;
@Mixin(value = HandledScreen.class, priority = 990) @Mixin(value = HandledScreen.class, priority = 990)
public abstract class MixinHandledScreen<T extends ScreenHandler> { public abstract class MixinHandledScreen<T extends ScreenHandler> {
@@ -74,17 +74,17 @@ public abstract class MixinHandledScreen<T extends ScreenHandler> {
public void onMouseClickedSlot(Slot slot, int slotId, int button, SlotActionType actionType, CallbackInfo ci) { 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" if (slotId == -999 && getScreenHandler() != null && actionType == SlotActionType.PICKUP) { // -999 is code for "clicked outside the main window"
ItemStack cursorStack = getScreenHandler().getCursorStack(); ItemStack cursorStack = getScreenHandler().getCursorStack();
if (cursorStack != null && IsSlotProtectedEvent.shouldBlockInteraction(slot, SlotActionType.THROW, cursorStack)) { if (cursorStack != null && IsSlotProtectedEvent.shouldBlockInteraction(slot, SlotActionType.THROW, IsSlotProtectedEvent.MoveOrigin.INVENTORY_MOVE, cursorStack)) {
ci.cancel(); ci.cancel();
return; return;
} }
} }
if (IsSlotProtectedEvent.shouldBlockInteraction(slot, actionType)) { if (IsSlotProtectedEvent.shouldBlockInteraction(slot, actionType, IsSlotProtectedEvent.MoveOrigin.INVENTORY_MOVE)) {
ci.cancel(); ci.cancel();
return; return;
} }
if (actionType == SlotActionType.SWAP && 0 <= button && button < 9) { if (actionType == SlotActionType.SWAP && 0 <= button && button < 9) {
if (IsSlotProtectedEvent.shouldBlockInteraction(new Slot(playerInventory, button, 0, 0), actionType)) { if (IsSlotProtectedEvent.shouldBlockInteraction(new Slot(playerInventory, button, 0, 0), actionType, IsSlotProtectedEvent.MoveOrigin.INVENTORY_MOVE)) {
ci.cancel(); ci.cancel();
} }
} }

View File

@@ -14,15 +14,15 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ClientPlayerEntity.class) @Mixin(ClientPlayerEntity.class)
public abstract class PlayerDropEventPatch extends PlayerEntity { public abstract class PlayerDropEventPatch extends PlayerEntity {
public PlayerDropEventPatch() { public PlayerDropEventPatch() {
super(null, null, 0, null); super(null, null, 0, null);
} }
@Inject(method = "dropSelectedItem", at = @At("HEAD"), cancellable = true) @Inject(method = "dropSelectedItem", at = @At("HEAD"), cancellable = true)
public void onDropSelectedItem(boolean entireStack, CallbackInfoReturnable<Boolean> cir) { public void onDropSelectedItem(boolean entireStack, CallbackInfoReturnable<Boolean> cir) {
Slot fakeSlot = new Slot(getInventory(), getInventory().selectedSlot, 0, 0); Slot fakeSlot = new Slot(getInventory(), getInventory().selectedSlot, 0, 0);
if (IsSlotProtectedEvent.shouldBlockInteraction(fakeSlot, SlotActionType.THROW)) { if (IsSlotProtectedEvent.shouldBlockInteraction(fakeSlot, SlotActionType.THROW, IsSlotProtectedEvent.MoveOrigin.DROP_FROM_HOTBAR)) {
cir.setReturnValue(false); cir.setReturnValue(false);
} }
} }
} }

View File

@@ -1,5 +1,3 @@
package moe.nea.firmament.events package moe.nea.firmament.events
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
@@ -10,37 +8,49 @@ import moe.nea.firmament.util.CommonSoundEffects
import moe.nea.firmament.util.MC import moe.nea.firmament.util.MC
data class IsSlotProtectedEvent( data class IsSlotProtectedEvent(
val slot: Slot?, val slot: Slot?,
val actionType: SlotActionType, val actionType: SlotActionType,
var isProtected: Boolean, var isProtected: Boolean,
val itemStackOverride: ItemStack?, val itemStackOverride: ItemStack?,
var silent: Boolean = false, val origin: MoveOrigin,
var silent: Boolean = false,
) : FirmamentEvent() { ) : FirmamentEvent() {
val itemStack get() = itemStackOverride ?: slot!!.stack val itemStack get() = itemStackOverride ?: slot!!.stack
fun protect() { fun protect() {
isProtected = true isProtected = true
} silent = false
}
fun protectSilent() { fun protectSilent() {
if (!isProtected) { if (!isProtected) {
silent = true silent = true
} }
isProtected = true isProtected = true
} }
companion object : FirmamentEventBus<IsSlotProtectedEvent>() { enum class MoveOrigin {
@JvmStatic DROP_FROM_HOTBAR,
@JvmOverloads SALVAGE,
fun shouldBlockInteraction(slot: Slot?, action: SlotActionType, itemStackOverride: ItemStack? = null): Boolean { INVENTORY_MOVE
if (slot == null && itemStackOverride == null) return false ;
val event = IsSlotProtectedEvent(slot, action, false, itemStackOverride) }
publish(event) companion object : FirmamentEventBus<IsSlotProtectedEvent>() {
if (event.isProtected && !event.silent) { @JvmStatic
MC.sendChat(Text.translatable("firmament.protectitem").append(event.itemStack.name)) @JvmOverloads
CommonSoundEffects.playFailure() fun shouldBlockInteraction(
} slot: Slot?, action: SlotActionType,
return event.isProtected origin: MoveOrigin,
} itemStackOverride: ItemStack? = null,
} ): Boolean {
if (slot == null && itemStackOverride == null) return false
val event = IsSlotProtectedEvent(slot, action, false, itemStackOverride, origin)
publish(event)
if (event.isProtected && !event.silent) {
MC.sendChat(Text.translatable("firmament.protectitem").append(event.itemStack.name))
CommonSoundEffects.playFailure()
}
return event.isProtected
}
}
} }

View File

@@ -45,10 +45,6 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature
private var hasAutoloaded = false private var hasAutoloaded = false
init {
autoload()
}
fun autoload() { fun autoload() {
synchronized(this) { synchronized(this) {
if (hasAutoloaded) return if (hasAutoloaded) return

View File

@@ -15,6 +15,7 @@ import net.minecraft.screen.slot.SlotActionType
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.minecraft.util.StringIdentifiable import net.minecraft.util.StringIdentifiable
import moe.nea.firmament.annotations.Subscribe import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.FeaturesInitializedEvent
import moe.nea.firmament.events.HandledScreenForegroundEvent import moe.nea.firmament.events.HandledScreenForegroundEvent
import moe.nea.firmament.events.HandledScreenKeyPressedEvent import moe.nea.firmament.events.HandledScreenKeyPressedEvent
import moe.nea.firmament.events.HandledScreenKeyReleasedEvent import moe.nea.firmament.events.HandledScreenKeyReleasedEvent
@@ -37,6 +38,7 @@ import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.mc.loreAccordingToNbt import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.render.GuiRenderLayers import moe.nea.firmament.util.render.GuiRenderLayers
import moe.nea.firmament.util.render.drawLine import moe.nea.firmament.util.render.drawLine
import moe.nea.firmament.util.skyblock.DungeonUtil
import moe.nea.firmament.util.skyblockUUID import moe.nea.firmament.util.skyblockUUID
import moe.nea.firmament.util.unformattedString import moe.nea.firmament.util.unformattedString
@@ -60,6 +62,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 allowDroppingInDungeons by toggle("drop-in-dungeons") { true }
} }
enum class SlotRenderLinesMode : StringIdentifiable { enum class SlotRenderLinesMode : StringIdentifiable {
@@ -120,7 +123,11 @@ object SlotLocking : FirmamentFeature {
var anyBlocked = false var anyBlocked = false
for (i in 0 until event.slot.index) { for (i in 0 until event.slot.index) {
val stack = inv.getStack(i) val stack = inv.getStack(i)
if (IsSlotProtectedEvent.shouldBlockInteraction(null, SlotActionType.THROW, stack)) if (IsSlotProtectedEvent.shouldBlockInteraction(null,
SlotActionType.THROW,
IsSlotProtectedEvent.MoveOrigin.SALVAGE,
stack)
)
anyBlocked = true anyBlocked = true
} }
if (anyBlocked) { if (anyBlocked) {
@@ -155,6 +162,19 @@ object SlotLocking : FirmamentFeature {
} }
} }
@Subscribe
fun onEvent(event: FeaturesInitializedEvent) {
IsSlotProtectedEvent.subscribe(receivesCancelled = true, "SlotLocking:unlockInDungeons") {
if (it.isProtected
&& it.origin == IsSlotProtectedEvent.MoveOrigin.DROP_FROM_HOTBAR
&& DungeonUtil.isInActiveDungeon
&& TConfig.allowDroppingInDungeons
) {
it.isProtected = false
}
}
}
@Subscribe @Subscribe
fun onQuickMoveBoundSlot(it: IsSlotProtectedEvent) { fun onQuickMoveBoundSlot(it: IsSlotProtectedEvent) {
val boundSlots = DConfig.data?.boundSlots ?: mapOf() val boundSlots = DConfig.data?.boundSlots ?: mapOf()
@@ -245,7 +265,7 @@ object SlotLocking : FirmamentFeature {
val (hotX, hotY) = hotbarSlot.lineCenter() val (hotX, hotY) = hotbarSlot.lineCenter()
val (invX, invY) = inventorySlot.lineCenter() val (invX, invY) = inventorySlot.lineCenter()
val anyHovered = accScreen.focusedSlot_Firmament === hotbarSlot val anyHovered = accScreen.focusedSlot_Firmament === hotbarSlot
|| 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) val color = if (anyHovered)

View File

@@ -1,8 +1,6 @@
package moe.nea.firmament.util package moe.nea.firmament.util
import java.util.* import java.util.Optional
import net.minecraft.client.gui.hud.InGameHud import net.minecraft.client.gui.hud.InGameHud
import net.minecraft.scoreboard.ScoreboardDisplaySlot import net.minecraft.scoreboard.ScoreboardDisplaySlot
import net.minecraft.scoreboard.Team import net.minecraft.scoreboard.Team
@@ -10,36 +8,48 @@ import net.minecraft.text.StringVisitable
import net.minecraft.text.Style import net.minecraft.text.Style
import net.minecraft.text.Text import net.minecraft.text.Text
import net.minecraft.util.Formatting import net.minecraft.util.Formatting
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.TickEvent
fun getScoreboardLines(): List<Text> { object ScoreboardUtil {
val scoreboard = MC.player?.scoreboard ?: return listOf() var scoreboardLines: List<Text> = listOf()
val activeObjective = scoreboard.getObjectiveForSlot(ScoreboardDisplaySlot.SIDEBAR) ?: return listOf() var simplifiedScoreboardLines: List<String> = listOf()
return scoreboard.getScoreboardEntries(activeObjective)
.filter { !it.hidden() } @Subscribe
.sortedWith(InGameHud.SCOREBOARD_ENTRY_COMPARATOR) fun onTick(event: TickEvent) {
.take(15).map { scoreboardLines = getScoreboardLinesUncached()
val team = scoreboard.getScoreHolderTeam(it.owner) simplifiedScoreboardLines = scoreboardLines.map { it.unformattedString }
val text = it.name() }
Team.decorateName(team, text)
} private fun getScoreboardLinesUncached(): List<Text> {
val scoreboard = MC.player?.scoreboard ?: return listOf()
val activeObjective = scoreboard.getObjectiveForSlot(ScoreboardDisplaySlot.SIDEBAR) ?: return listOf()
return scoreboard.getScoreboardEntries(activeObjective)
.filter { !it.hidden() }
.sortedWith(InGameHud.SCOREBOARD_ENTRY_COMPARATOR)
.take(15).map {
val team = scoreboard.getScoreHolderTeam(it.owner)
val text = it.name()
Team.decorateName(team, text)
}
}
} }
fun Text.formattedString(): String { fun Text.formattedString(): String {
val sb = StringBuilder() val sb = StringBuilder()
visit(StringVisitable.StyledVisitor<Unit> { style, string -> visit(StringVisitable.StyledVisitor<Unit> { style, string ->
val c = Formatting.byName(style.color?.name) val c = Formatting.byName(style.color?.name)
if (c != null) { if (c != null) {
sb.append("§${c.code}") sb.append("§${c.code}")
} }
if (style.isUnderlined) { if (style.isUnderlined) {
sb.append("§n") sb.append("§n")
} }
if (style.isBold) { if (style.isBold) {
sb.append("§l") sb.append("§l")
} }
sb.append(string) sb.append(string)
Optional.empty() Optional.empty()
}, Style.EMPTY) }, Style.EMPTY)
return sb.toString().replace("§[^a-f0-9]".toRegex(), "") return sb.toString().replace("§[^a-f0-9]".toRegex(), "")
} }

View File

@@ -36,6 +36,7 @@ private constructor(
val RIFT = forMode("rift") val RIFT = forMode("rift")
val MINESHAFT = forMode("mineshaft") val MINESHAFT = forMode("mineshaft")
val GARDEN = forMode("garden") val GARDEN = forMode("garden")
val DUNGEON = forMode("dungeon")
} }
val userFriendlyName val userFriendlyName

View File

@@ -0,0 +1,33 @@
package moe.nea.firmament.util.skyblock
import moe.nea.firmament.util.SBData
import moe.nea.firmament.util.ScoreboardUtil
import moe.nea.firmament.util.SkyBlockIsland
import moe.nea.firmament.util.TIME_PATTERN
object DungeonUtil {
val isInDungeonIsland get() = SBData.skyblockLocation == SkyBlockIsland.DUNGEON
private val timeElapsedRegex = "Time Elapsed: $TIME_PATTERN".toRegex()
val isInActiveDungeon get() = isInDungeonIsland && ScoreboardUtil.simplifiedScoreboardLines.any { it.matches(
timeElapsedRegex) }
/*Title:
§f§lSKYBLOCK§B§L CO-OP
' Late Spring 7th'
' §75:20am'
' §7⏣ §cThe Catacombs §7(M3)'
' §7♲ §7Ironman'
' '
'Keys: §c■ §c✗ §8■ §a1x'
'Time Elapsed: §a46s'
'Cleared: §660% §8(105)'
' '
'§e[B] §b151_Dragon §e2,062§c❤'
'§e[A] §6Lennart0312 §a17,165§c'
'§e[T] §b187i §a14,581§c❤'
'§e[H] §bFlameeke §a8,998§c❤'
' '
'§ewww.hypixel.net'*/
}

View File

@@ -231,6 +231,8 @@
"firmament.config.slot-locking.bind-render.choice.only_boxes": "Only boxes", "firmament.config.slot-locking.bind-render.choice.only_boxes": "Only boxes",
"firmament.config.slot-locking.bind-render.description": "Disable rendering of the slot binding lines (or all of the slot binding rendering), unless the relevant slot is being hovered.", "firmament.config.slot-locking.bind-render.description": "Disable rendering of the slot binding lines (or all of the slot binding rendering), unless the relevant slot is being hovered.",
"firmament.config.slot-locking.bind.description": "Bind a hotbar slot to another slot. This allows quick switching between the slots by shift clicking on either slot.", "firmament.config.slot-locking.bind.description": "Bind a hotbar slot to another slot. This allows quick switching between the slots by shift clicking on either slot.",
"firmament.config.slot-locking.drop-in-dungeons": "Allow Dungeon Abilities",
"firmament.config.slot-locking.drop-in-dungeons.description": "Allow dropping items in dungeons, to use your dungeon ultimate abilities.",
"firmament.config.slot-locking.lock": "Lock Slot", "firmament.config.slot-locking.lock": "Lock Slot",
"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.",