Add slot locking

This commit is contained in:
nea
2023-05-18 03:20:22 +02:00
parent 3ebe3e80b9
commit 1dd603ab1c
19 changed files with 297 additions and 14 deletions

View File

@@ -5,7 +5,6 @@
- Storage Overlay - Storage Overlay
- PV - PV
- NEU buttons - NEU buttons
- Slot locking
- Pet/Equipment hud in inventory - Pet/Equipment hud in inventory
- Pet Overlay - Pet Overlay
- Price Graphs - Price Graphs
@@ -27,6 +26,8 @@ Priority 2:
- biome wands - biome wands
- dirt wand - dirt wand
- block zapper - block zapper
- slot binding
- uuid locking (allow items with that uuid to be moved around but not dropped/sold)
Priority 3: Priority 3:
- Item rarity halo - Item rarity halo

View File

@@ -0,0 +1,25 @@
package moe.nea.firmament.mixins;
import moe.nea.firmament.events.IsSlotProtectedEvent;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.screen.slot.Slot;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ClientPlayerEntity.class)
public abstract class MixinClientPlayerEntity extends PlayerEntity {
public MixinClientPlayerEntity() {
super(null, null, 0, null);
}
@Inject(method = "dropSelectedItem", at = @At("HEAD"), cancellable = true)
public void onDropSelectedItem(boolean entireStack, CallbackInfoReturnable<Boolean> cir) {
Slot fakeSlot = new Slot(getInventory(), getInventory().selectedSlot, 0, 0);
if (IsSlotProtectedEvent.shouldBlockInteraction(fakeSlot)) {
cir.setReturnValue(false);
}
}
}

View File

@@ -0,0 +1,50 @@
package moe.nea.firmament.mixins;
import moe.nea.firmament.events.HandledScreenKeyPressedEvent;
import moe.nea.firmament.events.IsSlotProtectedEvent;
import moe.nea.firmament.events.SlotRenderEvents;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.screen.slot.Slot;
import net.minecraft.screen.slot.SlotActionType;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
@Mixin(HandledScreen.class)
public class MixinHandledScreen {
@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(keyCode, scanCode, modifiers)).getCancelled()) {
cir.setReturnValue(true);
}
}
@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 (IsSlotProtectedEvent.shouldBlockInteraction(slot)) {
ci.cancel();
}
}
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawSlot(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/screen/slot/Slot;)V", shift = At.Shift.AFTER), locals = LocalCapture.CAPTURE_FAILHARD)
public void onAfterDrawSlot(
MatrixStack matrices, int mouseX, int mouseY, float delta, CallbackInfo ci,
int i, int j, int k, Slot slot) {
SlotRenderEvents.After event = new SlotRenderEvents.After(matrices, 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/util/math/MatrixStack;Lnet/minecraft/screen/slot/Slot;)V", shift = At.Shift.BEFORE), locals = LocalCapture.CAPTURE_FAILHARD)
public void onBeforeDrawSlot(
MatrixStack matrices, int mouseX, int mouseY, float delta, CallbackInfo ci,
int i, int j, int k, Slot slot) {
SlotRenderEvents.Before event = new SlotRenderEvents.Before(matrices, slot, mouseX, mouseY, delta);
SlotRenderEvents.Before.Companion.publish(event);
}
}

View File

@@ -0,0 +1,11 @@
package moe.nea.firmament.mixins;
import net.minecraft.entity.player.PlayerInventory;
import org.spongepowered.asm.mixin.Mixin;
@Mixin(PlayerInventory.class)
public class MixinPlayerInventory {
}

View File

@@ -5,6 +5,7 @@ import io.ktor.client.*
import io.ktor.client.plugins.* import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.* import io.ktor.serialization.kotlinx.json.*
import java.awt.Taskbar.Feature
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import net.fabricmc.api.ClientModInitializer import net.fabricmc.api.ClientModInitializer
@@ -70,7 +71,9 @@ object Firmament : ModInitializer, ClientModInitializer {
} }
override fun onInitialize() { override fun onInitialize() {
}
override fun onInitializeClient() {
dbusConnection.requestBusName("moe.nea.firmament") dbusConnection.requestBusName("moe.nea.firmament")
dbusConnection.exportObject(FirmamentDbusObject) dbusConnection.exportObject(FirmamentDbusObject)
IDataHolder.registerEvents() IDataHolder.registerEvents()
@@ -85,8 +88,6 @@ object Firmament : ModInitializer, ClientModInitializer {
globalJob.cancel() globalJob.cancel()
} }
}) })
}
override fun onInitializeClient() {
} }
} }

View File

@@ -0,0 +1,10 @@
package moe.nea.firmament.events
import net.minecraft.client.option.KeyBinding
data class HandledScreenKeyPressedEvent(val keyCode: Int, val scanCode: Int, val modifiers: Int) : FirmamentEvent.Cancellable() {
companion object : FirmamentEventBus<HandledScreenKeyPressedEvent>()
fun matches(keyBinding: KeyBinding): Boolean {
return keyBinding.matchesKey(keyCode, scanCode)
}
}

View File

@@ -0,0 +1,26 @@
package moe.nea.firmament.events
import net.minecraft.screen.slot.Slot
import net.minecraft.text.Text
import moe.nea.firmament.util.CommonSoundEffects
import moe.nea.firmament.util.MC
data class IsSlotProtectedEvent(
val slot: Slot, var isProtected: Boolean = false
) : FirmamentEvent() {
companion object : FirmamentEventBus<IsSlotProtectedEvent>() {
@JvmStatic
fun shouldBlockInteraction(slot: Slot): Boolean {
return publish(IsSlotProtectedEvent(slot)).isProtected.also {
if (it) {
MC.player?.sendMessage(Text.translatable("firmament.protectitem").append(slot.stack.name))
CommonSoundEffects.playFailure()
}
}
}
}
fun protect() {
isProtected = true
}
}

View File

@@ -0,0 +1,32 @@
package moe.nea.firmament.events
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.screen.slot.Slot
interface SlotRenderEvents {
val matrices: MatrixStack
val slot: Slot
val mouseX: Int
val mouseY: Int
val delta: Float
data class Before(
override val matrices: MatrixStack, override val slot: Slot,
override val mouseX: Int,
override val mouseY: Int,
override val delta: Float
) : FirmamentEvent(),
SlotRenderEvents {
companion object : FirmamentEventBus<Before>()
}
data class After(
override val matrices: MatrixStack, override val slot: Slot,
override val mouseX: Int,
override val mouseY: Int,
override val delta: Float
) : FirmamentEvent(),
SlotRenderEvents {
companion object : FirmamentEventBus<After>()
}
}

View File

@@ -4,6 +4,7 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer import kotlinx.serialization.serializer
import moe.nea.firmament.Firmament import moe.nea.firmament.Firmament
import moe.nea.firmament.features.fishing.FishingWarning import moe.nea.firmament.features.fishing.FishingWarning
import moe.nea.firmament.features.inventory.SlotLocking
import moe.nea.firmament.features.world.FairySouls import moe.nea.firmament.features.world.FairySouls
import moe.nea.firmament.util.data.DataHolder import moe.nea.firmament.util.data.DataHolder
@@ -13,7 +14,7 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature
val enabledFeatures: MutableMap<String, Boolean> = mutableMapOf() val enabledFeatures: MutableMap<String, Boolean> = mutableMapOf()
) )
private val features = mutableMapOf<String, NEUFeature>() private val features = mutableMapOf<String, FirmamentFeature>()
private var hasAutoloaded = false private var hasAutoloaded = false
@@ -26,11 +27,12 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature
if (hasAutoloaded) return if (hasAutoloaded) return
loadFeature(FairySouls) loadFeature(FairySouls)
loadFeature(FishingWarning) loadFeature(FishingWarning)
loadFeature(SlotLocking)
hasAutoloaded = true hasAutoloaded = true
} }
} }
fun loadFeature(feature: NEUFeature) { fun loadFeature(feature: FirmamentFeature) {
synchronized(features) { synchronized(features) {
if (feature.identifier in features) { if (feature.identifier in features) {
Firmament.logger.error("Double registering feature ${feature.identifier}. Ignoring second instance $feature") Firmament.logger.error("Double registering feature ${feature.identifier}. Ignoring second instance $feature")

View File

@@ -2,7 +2,7 @@ package moe.nea.firmament.features
import moe.nea.firmament.util.config.ManagedConfig import moe.nea.firmament.util.config.ManagedConfig
interface NEUFeature { interface FirmamentFeature {
val name: String val name: String
val identifier: String val identifier: String
val defaultEnabled: Boolean val defaultEnabled: Boolean

View File

@@ -13,13 +13,13 @@ import net.minecraft.util.math.Vec3d
import moe.nea.firmament.events.ParticleSpawnEvent import moe.nea.firmament.events.ParticleSpawnEvent
import moe.nea.firmament.events.WorldReadyEvent import moe.nea.firmament.events.WorldReadyEvent
import moe.nea.firmament.events.WorldRenderLastEvent import moe.nea.firmament.events.WorldRenderLastEvent
import moe.nea.firmament.features.NEUFeature import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.util.MC import moe.nea.firmament.util.MC
import moe.nea.firmament.util.TimeMark import moe.nea.firmament.util.TimeMark
import moe.nea.firmament.util.config.ManagedConfig import moe.nea.firmament.util.config.ManagedConfig
import moe.nea.firmament.util.render.RenderBlockContext.Companion.renderBlocks import moe.nea.firmament.util.render.RenderBlockContext.Companion.renderBlocks
object FishingWarning : NEUFeature { object FishingWarning : FirmamentFeature {
override val name: String override val name: String
get() = "Fishing Warning" get() = "Fishing Warning"
override val identifier: String override val identifier: String

View File

@@ -0,0 +1,68 @@
package moe.nea.firmament.features.inventory
import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer
import net.minecraft.client.gui.DrawableHelper
import net.minecraft.entity.player.PlayerInventory
import moe.nea.firmament.events.HandledScreenKeyPressedEvent
import moe.nea.firmament.events.IsSlotProtectedEvent
import moe.nea.firmament.events.SlotRenderEvents
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.keybindings.FirmamentKeyBindings
import moe.nea.firmament.mixins.accessor.AccessorHandledScreen
import moe.nea.firmament.util.CommonSoundEffects
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.data.ProfileSpecificDataHolder
object SlotLocking : FirmamentFeature {
override val name: String
get() = "Slot Locking"
override val identifier: String
get() = "slot-locking"
@Serializable
data class Data(
val lockedSlots: MutableSet<Int> = mutableSetOf(),
)
object DConfig : ProfileSpecificDataHolder<Data>(serializer(), "locked-slots", ::Data)
val keyBinding by FirmamentKeyBindings::SLOT_LOCKING
val lockedSlots get() = DConfig.data?.lockedSlots
override fun onLoad() {
HandledScreenKeyPressedEvent.subscribe {
if (!it.matches(keyBinding)) return@subscribe
val inventory = MC.handledScreen ?: return@subscribe
inventory as AccessorHandledScreen
val slot = inventory.focusedSlot_NEU ?: return@subscribe
val lockedSlots = lockedSlots ?: return@subscribe
if (slot.inventory is PlayerInventory) {
if (slot.index in lockedSlots) {
lockedSlots.remove(slot.index)
} else {
lockedSlots.add(slot.index)
}
DConfig.markDirty()
CommonSoundEffects.playSuccess()
}
}
IsSlotProtectedEvent.subscribe {
if (it.slot.inventory is PlayerInventory && it.slot.index in (lockedSlots ?: setOf())) {
it.protect()
}
}
SlotRenderEvents.Before.subscribe {
if (it.slot.inventory is PlayerInventory && it.slot.index in (lockedSlots ?: setOf())) {
DrawableHelper.fill(
it.matrices,
it.slot.x,
it.slot.y,
it.slot.x + 16,
it.slot.y + 16,
0xFFFF0000.toInt()
)
}
}
}
}

View File

@@ -6,7 +6,7 @@ import kotlinx.serialization.serializer
import moe.nea.firmament.events.ServerChatLineReceivedEvent import moe.nea.firmament.events.ServerChatLineReceivedEvent
import moe.nea.firmament.events.SkyblockServerUpdateEvent import moe.nea.firmament.events.SkyblockServerUpdateEvent
import moe.nea.firmament.events.WorldRenderLastEvent import moe.nea.firmament.events.WorldRenderLastEvent
import moe.nea.firmament.features.NEUFeature import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.repo.RepoManager import moe.nea.firmament.repo.RepoManager
import moe.nea.firmament.util.MC import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SBData import moe.nea.firmament.util.SBData
@@ -17,7 +17,7 @@ import moe.nea.firmament.util.render.RenderBlockContext.Companion.renderBlocks
import moe.nea.firmament.util.unformattedString import moe.nea.firmament.util.unformattedString
object FairySouls : NEUFeature { object FairySouls : FirmamentFeature {
@Serializable @Serializable

View File

@@ -0,0 +1,17 @@
package moe.nea.firmament.keybindings
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper
import org.lwjgl.glfw.GLFW
import net.minecraft.client.option.KeyBinding
import net.minecraft.client.util.InputUtil
object FirmamentKeyBindings {
val SLOT_LOCKING = KeyBindingHelper.registerKeyBinding(
KeyBinding(
"firmament.key.slotlocking",
InputUtil.Type.KEYSYM,
GLFW.GLFW_KEY_L,
"firmament.key.category"
)
)
}

View File

@@ -0,0 +1,28 @@
package moe.nea.firmament.util
import net.minecraft.client.sound.AbstractSoundInstance
import net.minecraft.client.sound.PositionedSoundInstance
import net.minecraft.client.sound.SoundInstance
import net.minecraft.sound.SoundCategory
import net.minecraft.sound.SoundEvent
import net.minecraft.util.Identifier
// TODO: Replace these with custom sound events that just re use the vanilla ogg s
object CommonSoundEffects {
fun playSound(identifier: Identifier) {
MC.soundManager.play(PositionedSoundInstance.master(SoundEvent.of(identifier), 1F))
}
fun playFailure() {
playSound(Identifier("minecraft", "block.anvil.place"))
}
fun playSuccess() {
playDing()
}
fun playDing() {
playSound(Identifier("minecraft", "entity.arrow.hit_player"))
}
}

View File

@@ -2,11 +2,16 @@ package moe.nea.firmament.util
import io.github.moulberry.repo.data.Coordinate import io.github.moulberry.repo.data.Coordinate
import net.minecraft.client.MinecraftClient import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import moe.nea.firmament.mixins.accessor.AccessorHandledScreen
object MC { object MC {
inline val soundManager get() = MinecraftClient.getInstance().soundManager
inline val player get() = MinecraftClient.getInstance().player inline val player get() = MinecraftClient.getInstance().player
inline val world get() = MinecraftClient.getInstance().world inline val world get() = MinecraftClient.getInstance().world
inline val screen get() = MinecraftClient.getInstance().currentScreen
inline val handledScreen: HandledScreen<*>? get() = MinecraftClient.getInstance().currentScreen as? HandledScreen<*>
} }
val Coordinate.blockPos: BlockPos val Coordinate.blockPos: BlockPos

View File

@@ -22,5 +22,8 @@
"firmament.config.fairy-souls.show": "Show Fairy Soul Waypoints", "firmament.config.fairy-souls.show": "Show Fairy Soul Waypoints",
"firmament.config.fairy-souls.reset": "Reset Collected Fairy Souls", "firmament.config.fairy-souls.reset": "Reset Collected Fairy Souls",
"firmament.config.fishing-warning.display-warning": "Display a warning when you are about to hook a fish", "firmament.config.fishing-warning.display-warning": "Display a warning when you are about to hook a fish",
"firmament.config.fishing-warning.highlight-wake-chain": "Highlight fishing particles" "firmament.config.fishing-warning.highlight-wake-chain": "Highlight fishing particles",
"firmament.key.slotlocking": "Lock Slot / Slot Binding",
"firmament.key.category": "Firmament",
"firmament.protectitem": "Firmament protected your item: "
} }

View File

@@ -40,8 +40,9 @@
"firmament.mixins.json" "firmament.mixins.json"
], ],
"depends": { "depends": {
"fabric": "*", "fabric": "*",
"fabric-language-kotlin": ">=${fabric_kotlin_version}", "fabric-language-kotlin": ">=${fabric_kotlin_version}",
"minecraft": ">=${minecraft_version}" "minecraft": ">=${minecraft_version}",
"fabric-key-binding-api-v1": "*"
} }
} }

View File

@@ -13,6 +13,9 @@
], ],
"mixins": [ "mixins": [
"MixinClientPacketHandler", "MixinClientPacketHandler",
"MixinClientPlayerEntity",
"MixinHandledScreen",
"MixinPlayerInventory",
"devenv.DisableInvalidFishingHook", "devenv.DisableInvalidFishingHook",
"devenv.MixinScoreboard" "devenv.MixinScoreboard"
], ],