aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authornea <nea@nea.moe>2023-07-11 21:01:58 +0200
committernea <nea@nea.moe>2023-07-11 21:01:58 +0200
commit4d93f475aadc42c4bf83c3a0749af41659235c71 (patch)
treec8e01710defe66e06c50fa962c72fdac66a35a1f /src/main
parent4444fcca44d9a53c8162d69e0e9f19fd214c2f54 (diff)
downloadFirmament-4d93f475aadc42c4bf83c3a0749af41659235c71.tar.gz
Firmament-4d93f475aadc42c4bf83c3a0749af41659235c71.tar.bz2
Firmament-4d93f475aadc42c4bf83c3a0749af41659235c71.zip
Bulk commit
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/moe/nea/firmament/mixins/MixinMouse.java38
-rw-r--r--src/main/java/moe/nea/firmament/mixins/MixinWorldRenderer.java14
-rw-r--r--src/main/kotlin/moe/nea/firmament/Firmament.kt21
-rw-r--r--src/main/kotlin/moe/nea/firmament/commands/rome.kt8
-rw-r--r--src/main/kotlin/moe/nea/firmament/events/TickEvent.kt5
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt9
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/FirmamentFeature.kt1
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/debug/DebugView.kt53
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/debug/DeveloperFeatures.kt2
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/debug/ObjectRenderer.kt52
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/fishing/FishingWarning.kt4
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/inventory/CraftingOverlay.kt2
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/inventory/SaveCursorPosition.kt67
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/inventory/SlotLocking.kt2
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageBackingHandle.kt56
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageData.kt16
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlay.kt94
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayScreen.kt135
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StoragePageSlot.kt64
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/VirtualInventory.kt53
-rw-r--r--src/main/kotlin/moe/nea/firmament/features/world/FairySouls.kt30
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/WSpacer.kt19
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/config/BooleanHandler.kt3
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/config/DurationHandler.kt57
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/config/GuiAppender.kt8
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/config/IntegerHandler.kt51
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/config/ManagedConfig.kt20
-rw-r--r--src/main/kotlin/moe/nea/firmament/gui/config/StringHandler.kt1
-rw-r--r--src/main/kotlin/moe/nea/firmament/repo/RepoManager.kt9
-rw-r--r--src/main/kotlin/moe/nea/firmament/util/FirmFormatters.kt5
-rw-r--r--src/main/kotlin/moe/nea/firmament/util/MC.kt4
-rw-r--r--src/main/kotlin/moe/nea/firmament/util/ScoreboardUtil.kt38
-rw-r--r--src/main/kotlin/moe/nea/firmament/util/item/NbtItemData.kt18
-rw-r--r--src/main/kotlin/moe/nea/firmament/util/regex.kt4
-rw-r--r--src/main/kotlin/moe/nea/firmament/util/render/RenderInWorldContext.kt4
-rw-r--r--src/main/resources/assets/firmament/lang/en_us.json11
36 files changed, 941 insertions, 37 deletions
diff --git a/src/main/java/moe/nea/firmament/mixins/MixinMouse.java b/src/main/java/moe/nea/firmament/mixins/MixinMouse.java
new file mode 100644
index 0000000..19cdcd2
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/MixinMouse.java
@@ -0,0 +1,38 @@
+package moe.nea.firmament.mixins;
+
+import kotlin.Pair;
+import moe.nea.firmament.features.inventory.SaveCursorPosition;
+import net.minecraft.client.Mouse;
+import org.objectweb.asm.Opcodes;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(Mouse.class)
+public class MixinMouse {
+ @Shadow
+ private double x;
+
+ @Shadow
+ private double y;
+
+ @Inject(method = "lockCursor", at = @At(value = "FIELD", opcode = Opcodes.PUTFIELD, target = "Lnet/minecraft/client/Mouse;cursorLocked:Z"))
+ public void onLockCursor(CallbackInfo ci) {
+ SaveCursorPosition.saveCursorOriginal(x, y);
+ }
+
+ @Inject(method = "lockCursor", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/Window;getHandle()J"))
+ public void onLockCursorAfter(CallbackInfo ci) {
+ SaveCursorPosition.saveCursorMiddle(x, y);
+ }
+
+ @Inject(method = "unlockCursor", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/Window;getHandle()J"))
+ public void onUnlockCursor(CallbackInfo ci) {
+ Pair<Double, Double> cursorPosition = SaveCursorPosition.loadCursor(this.x, this.y);
+ if (cursorPosition == null) return;
+ this.x = cursorPosition.getFirst();
+ this.y = cursorPosition.getSecond();
+ }
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/MixinWorldRenderer.java b/src/main/java/moe/nea/firmament/mixins/MixinWorldRenderer.java
index 2aebd8e..94bbef4 100644
--- a/src/main/java/moe/nea/firmament/mixins/MixinWorldRenderer.java
+++ b/src/main/java/moe/nea/firmament/mixins/MixinWorldRenderer.java
@@ -19,25 +19,29 @@
package moe.nea.firmament.mixins;
import moe.nea.firmament.events.WorldRenderLastEvent;
-import net.minecraft.client.render.Camera;
-import net.minecraft.client.render.GameRenderer;
-import net.minecraft.client.render.LightmapTextureManager;
-import net.minecraft.client.render.WorldRenderer;
+import net.minecraft.client.render.*;
import net.minecraft.client.util.math.MatrixStack;
import org.joml.Matrix4f;
+import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(WorldRenderer.class)
public class MixinWorldRenderer {
+ @Shadow
+ @Final
+ private BufferBuilderStorage bufferBuilders;
+
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/WorldRenderer;renderChunkDebugInfo(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;Lnet/minecraft/client/render/Camera;)V", shift = At.Shift.BEFORE))
public void onWorldRenderLast(MatrixStack matrices, float tickDelta, long limitTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, LightmapTextureManager lightmapTextureManager, Matrix4f positionMatrix, CallbackInfo ci) {
var event = new WorldRenderLastEvent(
matrices, tickDelta, renderBlockOutline,
camera, gameRenderer, lightmapTextureManager,
- positionMatrix
+ positionMatrix,
+ this.bufferBuilders.getEntityVertexConsumers()
);
WorldRenderLastEvent.Companion.publish(event);
}
diff --git a/src/main/kotlin/moe/nea/firmament/Firmament.kt b/src/main/kotlin/moe/nea/firmament/Firmament.kt
index 2e1cd05..46329f1 100644
--- a/src/main/kotlin/moe/nea/firmament/Firmament.kt
+++ b/src/main/kotlin/moe/nea/firmament/Firmament.kt
@@ -19,13 +19,15 @@
package moe.nea.firmament
import com.mojang.brigadier.CommandDispatcher
-import io.ktor.client.*
-import io.ktor.client.plugins.*
-import io.ktor.client.plugins.cache.*
-import io.ktor.client.plugins.compression.*
-import io.ktor.client.plugins.contentnegotiation.*
-import io.ktor.client.plugins.logging.*
-import io.ktor.serialization.kotlinx.json.*
+import dev.architectury.event.events.client.ClientTickEvent
+import io.ktor.client.HttpClient
+import io.ktor.client.plugins.UserAgent
+import io.ktor.client.plugins.cache.HttpCache
+import io.ktor.client.plugins.compression.ContentEncoding
+import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
+import io.ktor.client.plugins.logging.LogLevel
+import io.ktor.client.plugins.logging.Logging
+import io.ktor.serialization.kotlinx.json.json
import java.nio.file.Files
import java.nio.file.Path
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback
@@ -49,6 +51,7 @@ import net.minecraft.command.CommandRegistryAccess
import net.minecraft.util.Identifier
import moe.nea.firmament.commands.registerFirmamentCommand
import moe.nea.firmament.dbus.FirmamentDbusObject
+import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.features.FeatureManager
import moe.nea.firmament.repo.HypixelStaticData
import moe.nea.firmament.repo.RepoManager
@@ -114,6 +117,10 @@ object Firmament {
@JvmStatic
fun onClientInitialize() {
dbusConnection.requestBusName("moe.nea.firmament")
+ var tick = 0
+ ClientTickEvent.CLIENT_POST.register(ClientTickEvent.Client { instance ->
+ TickEvent.publish(TickEvent(tick++))
+ })
dbusConnection.exportObject(FirmamentDbusObject)
IDataHolder.registerEvents()
RepoManager.initialize()
diff --git a/src/main/kotlin/moe/nea/firmament/commands/rome.kt b/src/main/kotlin/moe/nea/firmament/commands/rome.kt
index 2237abd..11aaf17 100644
--- a/src/main/kotlin/moe/nea/firmament/commands/rome.kt
+++ b/src/main/kotlin/moe/nea/firmament/commands/rome.kt
@@ -22,6 +22,7 @@ import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.arguments.StringArgumentType.string
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource
import net.minecraft.text.Text
+import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen
import moe.nea.firmament.features.world.FairySouls
import moe.nea.firmament.gui.config.AllConfigsGui
import moe.nea.firmament.gui.profileviewer.ProfileViewer
@@ -30,6 +31,7 @@ import moe.nea.firmament.repo.RepoManager
import moe.nea.firmament.util.FirmFormatters
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SBData
+import moe.nea.firmament.util.ScreenUtil
import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.unformattedString
@@ -40,6 +42,12 @@ fun firmamentCommand() = literal("firmament") {
AllConfigsGui.showAllGuis()
}
}
+ thenLiteral("storage") {
+ thenExecute {
+ ScreenUtil.setScreenLater(StorageOverlayScreen())
+ MC.player?.networkHandler?.sendChatCommand("ec")
+ }
+ }
thenLiteral("repo") {
thenLiteral("reload") {
thenLiteral("fetch") {
diff --git a/src/main/kotlin/moe/nea/firmament/events/TickEvent.kt b/src/main/kotlin/moe/nea/firmament/events/TickEvent.kt
new file mode 100644
index 0000000..5d9b0ce
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/events/TickEvent.kt
@@ -0,0 +1,5 @@
+package moe.nea.firmament.events
+
+data class TickEvent(val tickCount: Int) : FirmamentEvent() {
+ companion object : FirmamentEventBus<TickEvent>()
+}
diff --git a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt b/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt
index e465349..0f0a166 100644
--- a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt
+++ b/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt
@@ -21,10 +21,13 @@ package moe.nea.firmament.features
import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer
import moe.nea.firmament.Firmament
+import moe.nea.firmament.features.debug.DebugView
import moe.nea.firmament.features.debug.DeveloperFeatures
import moe.nea.firmament.features.fishing.FishingWarning
import moe.nea.firmament.features.inventory.CraftingOverlay
+import moe.nea.firmament.features.inventory.SaveCursorPosition
import moe.nea.firmament.features.inventory.SlotLocking
+import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlay
import moe.nea.firmament.features.world.FairySouls
import moe.nea.firmament.util.data.DataHolder
@@ -50,9 +53,13 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature
loadFeature(FairySouls)
loadFeature(FishingWarning)
loadFeature(SlotLocking)
+ loadFeature(StorageOverlay)
loadFeature(CraftingOverlay)
- if (Firmament.DEBUG)
+ loadFeature(SaveCursorPosition)
+ if (Firmament.DEBUG) {
loadFeature(DeveloperFeatures)
+ loadFeature(DebugView)
+ }
hasAutoloaded = true
}
}
diff --git a/src/main/kotlin/moe/nea/firmament/features/FirmamentFeature.kt b/src/main/kotlin/moe/nea/firmament/features/FirmamentFeature.kt
index e394fb7..94b69f1 100644
--- a/src/main/kotlin/moe/nea/firmament/features/FirmamentFeature.kt
+++ b/src/main/kotlin/moe/nea/firmament/features/FirmamentFeature.kt
@@ -21,7 +21,6 @@ package moe.nea.firmament.features
import moe.nea.firmament.gui.config.ManagedConfig
interface FirmamentFeature {
- val name: String
val identifier: String
val defaultEnabled: Boolean
get() = true
diff --git a/src/main/kotlin/moe/nea/firmament/features/debug/DebugView.kt b/src/main/kotlin/moe/nea/firmament/features/debug/DebugView.kt
new file mode 100644
index 0000000..e8c85d8
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/features/debug/DebugView.kt
@@ -0,0 +1,53 @@
+package moe.nea.firmament.features.debug
+
+import io.github.cottonmc.cotton.gui.client.CottonHud
+import io.github.cottonmc.cotton.gui.widget.WBox
+import io.github.cottonmc.cotton.gui.widget.data.Axis
+import java.util.Optional
+import kotlin.time.Duration.Companion.seconds
+import net.minecraft.scoreboard.Scoreboard
+import net.minecraft.scoreboard.Team
+import net.minecraft.text.StringVisitable
+import net.minecraft.text.Style
+import net.minecraft.text.Text
+import net.minecraft.util.Formatting
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.events.TickEvent
+import moe.nea.firmament.features.FirmamentFeature
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.TimeMark
+
+object DebugView : FirmamentFeature {
+ private data class StoredVariable<T>(
+ val obj: T,
+ val timer: TimeMark,
+ )
+
+ private val storedVariables: MutableMap<String, StoredVariable<*>> = mutableMapOf()
+ override val identifier: String
+ get() = "debug-view"
+ override val defaultEnabled: Boolean
+ get() = Firmament.DEBUG
+
+ fun <T : Any?> showVariable(label: String, obj: T) {
+ synchronized(this) {
+ storedVariables[label] = StoredVariable(obj, TimeMark.now())
+ }
+ }
+
+ val debugWidget = WBox(Axis.VERTICAL)
+
+
+ override fun onLoad() {
+ TickEvent.subscribe {
+ synchronized(this) {
+ storedVariables.entries.removeIf { it.value.timer.passedTime() > 1.seconds }
+ if (storedVariables.isEmpty()) {
+ CottonHud.add(debugWidget, 20, 20)
+ } else {
+ CottonHud.remove(debugWidget)
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/moe/nea/firmament/features/debug/DeveloperFeatures.kt b/src/main/kotlin/moe/nea/firmament/features/debug/DeveloperFeatures.kt
index e89ee6f..253bc0d 100644
--- a/src/main/kotlin/moe/nea/firmament/features/debug/DeveloperFeatures.kt
+++ b/src/main/kotlin/moe/nea/firmament/features/debug/DeveloperFeatures.kt
@@ -14,8 +14,6 @@ import moe.nea.firmament.util.TimeMark
import moe.nea.firmament.util.iterate
object DeveloperFeatures : FirmamentFeature {
- override val name: String
- get() = "developer"
override val identifier: String
get() = "developer"
override val config: TConfig
diff --git a/src/main/kotlin/moe/nea/firmament/features/debug/ObjectRenderer.kt b/src/main/kotlin/moe/nea/firmament/features/debug/ObjectRenderer.kt
new file mode 100644
index 0000000..f5185cd
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/features/debug/ObjectRenderer.kt
@@ -0,0 +1,52 @@
+package moe.nea.firmament.features.debug
+
+import io.github.cottonmc.cotton.gui.widget.WBox
+import io.github.cottonmc.cotton.gui.widget.WLabel
+import io.github.cottonmc.cotton.gui.widget.WWidget
+import io.github.cottonmc.cotton.gui.widget.data.Axis
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import kotlin.reflect.KProperty1
+import net.minecraft.text.Text
+import moe.nea.firmament.gui.WSpacer
+
+class ObjectRenderer(val box: WBox) {
+ var indent = 0
+
+ fun beginObject() {
+ indent++
+ }
+
+ fun endObject() {
+ indent--
+ }
+
+ fun emit(label: String, widget: WWidget) {
+ WSpacer(WBox(Axis.VERTICAL).also {
+ it.add(WWidget())
+ it.add(widget)
+ }, indent * 18)
+ }
+
+ fun <T : Any?> getDebuggingView(label: String, obj: T) {
+ if (obj == null) {
+ emit(label, WLabel(Text.literal("§cnull")))
+ return
+ }
+ if (obj is String) {
+ emit(label, WLabel(Text.literal(Json.encodeToString(obj))))
+ }
+ getObject(label, obj)
+ }
+
+ fun <T : Any> getObject(label: String, obj: T) {
+ emit(label, WLabel(Text.literal(obj::class.simpleName ?: "<unknown>")))
+ beginObject()
+ for (prop in obj::class.members.filterIsInstance<KProperty1<T, *>>()) {
+ val child = prop.get(obj)
+ getDebuggingView(prop.name, child)
+ }
+ endObject()
+ }
+
+}
diff --git a/src/main/kotlin/moe/nea/firmament/features/fishing/FishingWarning.kt b/src/main/kotlin/moe/nea/firmament/features/fishing/FishingWarning.kt
index fa8d779..4c20450 100644
--- a/src/main/kotlin/moe/nea/firmament/features/fishing/FishingWarning.kt
+++ b/src/main/kotlin/moe/nea/firmament/features/fishing/FishingWarning.kt
@@ -38,8 +38,6 @@ import moe.nea.firmament.util.TimeMark
import moe.nea.firmament.util.render.RenderInWorldContext.Companion.renderInWorld
object FishingWarning : FirmamentFeature {
- override val name: String
- get() = "Fishing Warning"
override val identifier: String
get() = "fishing-warning"
@@ -137,7 +135,7 @@ object FishingWarning : FirmamentFeature {
WorldRenderLastEvent.subscribe {
recentParticles.removeIf { it.second.passedTime() > 5.seconds }
recentCandidates.removeIf { it.timeMark.passedTime() > 5.seconds }
- renderInWorld(it.matrices, it.camera) {
+ renderInWorld(it) {
color(0f, 0f, 1f, 1f)
recentParticles.forEach {
tinyBlock(it.first, 0.1F)
diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/CraftingOverlay.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/CraftingOverlay.kt
index 31d2b23..bb73513 100644
--- a/src/main/kotlin/moe/nea/firmament/features/inventory/CraftingOverlay.kt
+++ b/src/main/kotlin/moe/nea/firmament/features/inventory/CraftingOverlay.kt
@@ -26,8 +26,6 @@ object CraftingOverlay : FirmamentFeature {
this.recipe = recipe
}
- override val name: String
- get() = "Crafting Overlay"
override val identifier: String
get() = "crafting-overlay"
diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/SaveCursorPosition.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/SaveCursorPosition.kt
new file mode 100644
index 0000000..577f19b
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/features/inventory/SaveCursorPosition.kt
@@ -0,0 +1,67 @@
+package moe.nea.firmament.features.inventory
+
+import kotlin.math.absoluteValue
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
+import net.minecraft.client.util.InputUtil
+import moe.nea.firmament.features.FirmamentFeature
+import moe.nea.firmament.gui.config.ManagedConfig
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.TimeMark
+import moe.nea.firmament.util.assertNotNullOr
+
+object SaveCursorPosition : FirmamentFeature {
+ override val identifier: String
+ get() = "save-cursor-position"
+
+ object TConfig : ManagedConfig(identifier) {
+ val enable by toggle("enable") { true }
+ val tolerance by duration("tolerance", 10.milliseconds, 5000.milliseconds) { 500.milliseconds }
+ }
+
+ override val config: TConfig
+ get() = TConfig
+
+ override fun onLoad() {
+
+ }
+
+ var savedPositionedP1: Pair<Double, Double>? = null
+ var savedPosition: SavedPosition? = null
+
+ data class SavedPosition(
+ val middle: Pair<Double, Double>,
+ val cursor: Pair<Double, Double>,
+ val savedAt: TimeMark = TimeMark.now()
+ )
+
+ @JvmStatic
+ fun saveCursorOriginal(positionedX: Double, positionedY: Double) {
+ savedPositionedP1 = Pair(positionedX, positionedY)
+ }
+
+ @JvmStatic
+ fun loadCursor(middleX: Double, middleY: Double): Pair<Double, Double>? {
+ val lastPosition = savedPosition?.takeIf { it.savedAt.passedTime() < 1.seconds }
+ savedPosition = null
+ if (lastPosition != null &&
+ (lastPosition.middle.first - middleX).absoluteValue < 1 &&
+ (lastPosition.middle.second - middleY).absoluteValue < 1
+ ) {
+ InputUtil.setCursorParameters(
+ MC.window.handle,
+ InputUtil.GLFW_CURSOR_NORMAL,
+ lastPosition.cursor.first,
+ lastPosition.cursor.second
+ )
+ return lastPosition.cursor
+ }
+ return null
+ }
+
+ @JvmStatic
+ fun saveCursorMiddle(middleX: Double, middleY: Double) {
+ val cursorPos = assertNotNullOr(savedPositionedP1) { return }
+ savedPosition = SavedPosition(Pair(middleX, middleY), cursorPos)
+ }
+}
diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/SlotLocking.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/SlotLocking.kt
index eab0da0..a601489 100644
--- a/src/main/kotlin/moe/nea/firmament/features/inventory/SlotLocking.kt
+++ b/src/main/kotlin/moe/nea/firmament/features/inventory/SlotLocking.kt
@@ -33,8 +33,6 @@ 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"
diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageBackingHandle.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageBackingHandle.kt
new file mode 100644
index 0000000..5802cb7
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageBackingHandle.kt
@@ -0,0 +1,56 @@
+package moe.nea.firmament.features.inventory.storageoverlay
+
+import net.minecraft.client.gui.screen.Screen
+import net.minecraft.client.gui.screen.ingame.GenericContainerScreen
+import net.minecraft.screen.GenericContainerScreenHandler
+import moe.nea.firmament.util.ifMatches
+import moe.nea.firmament.util.unformattedString
+
+/**
+ * A handle representing the state of the "server side" screens.
+ */
+sealed interface StorageBackingHandle {
+
+ sealed interface HasBackingScreen {
+ val handler: GenericContainerScreenHandler
+ }
+
+ /**
+ * No open "server side" screen.
+ */
+ object None : StorageBackingHandle
+
+ /**
+ * The main storage overview is open. Clicking on a slot will open that page. This page is accessible via `/storage`
+ */
+ data class Overview(override val handler: GenericContainerScreenHandler) : StorageBackingHandle, HasBackingScreen
+
+ /**
+ * An individual storage page is open. This may be a backpack or an enderchest page. This page is accessible via
+ * the [Overview] or via `/ec <index + 1>` for enderchest pages.
+ */
+ data class Page(override val handler: GenericContainerScreenHandler, val storagePageSlot: StoragePageSlot) :
+ StorageBackingHandle, HasBackingScreen
+
+ companion object {
+ private val enderChestName = "^Ender Chest \\(([1-9])/[1-9]\\)$".toRegex()
+ private val backPackName = "^.+Backpack \\(Slot #([0-9]+)\\)$".toRegex()
+
+ /**
+ * Parse a screen into a [StorageBackingHandle]. If this returns null it means that the screen is not
+ * representable as a [StorageBackingHandle], meaning another screen is open, for example the enderchest icon
+ * selection screen.
+ */
+ fun fromScreen(screen: Screen?): StorageBackingHandle? {
+ if (screen == null) return None
+ if (screen !is GenericContainerScreen) return null
+ val title = screen.title.unformattedString
+ if (title == "Storage") return Overview(screen.screenHandler)
+ return title.ifMatches(enderChestName) {
+ Page(screen.screenHandler, StoragePageSlot.ofEnderChestPage(it.groupValues[1].toInt()))
+ } ?: title.ifMatches(backPackName) {
+ Page(screen.screenHandler, StoragePageSlot.ofBackPackPage(it.groupValues[1].toInt()))
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageData.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageData.kt
new file mode 100644
index 0000000..1c2ac08
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageData.kt
@@ -0,0 +1,16 @@
+package moe.nea.firmament.features.inventory.storageoverlay
+
+import java.util.SortedMap
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class StorageData(
+ val storageInventories: SortedMap<StoragePageSlot, StorageInventory> = sortedMapOf()
+) {
+ @Serializable
+ data class StorageInventory(
+ var title: String,
+ val slot: StoragePageSlot,
+ var inventory: VirtualInventory?,
+ )
+}
diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlay.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlay.kt
new file mode 100644
index 0000000..fae3867
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlay.kt
@@ -0,0 +1,94 @@
+package moe.nea.firmament.features.inventory.storageoverlay
+
+import java.util.*
+import kotlinx.serialization.serializer
+import moe.nea.firmament.events.ScreenOpenEvent
+import moe.nea.firmament.events.TickEvent
+import moe.nea.firmament.features.FirmamentFeature
+import moe.nea.firmament.gui.config.ManagedConfig
+import moe.nea.firmament.util.data.ProfileSpecificDataHolder
+
+object StorageOverlay : FirmamentFeature {
+
+
+ object