aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO.txt5
-rw-r--r--build.gradle.kts5
-rw-r--r--gradle/libs.versions.toml3
-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
39 files changed, 951 insertions, 40 deletions
diff --git a/TODO.txt b/TODO.txt
index e037d1b..f3c524b 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -1,7 +1,10 @@
+Prio 0 (Bugs):
+- WorldReadyEvent buggy? -> out of date locraw
+
+Priority 1:
- recipes
- more recipe categories
- dfu cache
-- replace REI with custom renderer (if needed)
- Storage Overlay
- PV
- NEU buttons
diff --git a/build.gradle.kts b/build.gradle.kts
index f944d2a..3b0b945 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -4,8 +4,9 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
java
`maven-publish`
- kotlin("jvm") version "1.8.10"
- kotlin("plugin.serialization") version "1.8.10"
+ kotlin("jvm") version "1.8.20"
+ kotlin("plugin.serialization") version "1.8.20"
+ id("com.bnorm.power.kotlin-power-assert") version "0.13.0"
id("dev.architectury.loom") version "1.1.336"
id("com.github.johnrengelman.shadow") version "7.1.2"
id("moe.nea.licenseextractificator")
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index aa5c2b2..8d64cb3 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -16,6 +16,7 @@ qolify = "1.3.0-1.20"
citresewn = "1.1.3+1.20"
hotswap_agent = "1.4.2-SNAPSHOT"
sodium = "mc1.20-0.4.10"
+freecammod = "1.2.0-mc1.20"
ncr = "Fabric-1.20-v2.2.0"
mixinextras = "0.2.0-beta.9"
@@ -43,12 +44,14 @@ qolify = { module = "maven.modrinth:qolify", version.ref = "qolify" }
citresewn = { module = "maven.modrinth:cit-resewn", version.ref = "citresewn" }
ncr = { module = "maven.modrinth:no-chat-reports", version.ref = "ncr" }
sodium = { module = "maven.modrinth:sodium", version.ref = "sodium" }
+freecammod = { module = "maven.modrinth:freecam", version.ref = "freecammod" }
[bundles]
dbus = ["dbus_java_core", "dbus_java_unixsocket"]
runtime_required = ["architectury_fabric", "rei_fabric"]
runtime_optional = [
"devauth",
+ "freecammod",
"sodium",
"qolify",
"citresewn",
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 Data : ProfileSpecificDataHolder<StorageData>(serializer(), "storage-data", ::StorageData)
+
+ override val identifier: String
+ get() = "storage-overlay"
+
+ object TConfig : ManagedConfig(identifier) {
+ val rows by integer("rows", 1, 5) { 3 }
+ val scrollSpeed by integer("scroll-speed", 1, 50) { 10 }
+ val inverseScroll by toggle("inverse-scroll") { false }
+ val padding by integer("padding", 1, 20) { 5 }
+ val margin by integer("margin", 1, 60) { 20 }
+ }
+
+ override val config: TConfig
+ get() = TConfig
+
+ var currentHandler: StorageBackingHandle? = StorageBackingHandle.None
+
+ override fun onLoad() {
+ ScreenOpenEvent.subscribe { event ->
+ currentHandler = StorageBackingHandle.fromScreen(event.new)
+ if (event.old is StorageOverlayScreen && !event.old.isClosing) {
+ event.old.setHandler(currentHandler)
+ if (currentHandler != null)
+ // TODO: Consider instead only replacing rendering? might make a lot of stack handling easier
+ event.cancel()
+ }
+ }
+ TickEvent.subscribe {
+ rememberContent(currentHandler ?: return@subscribe)
+ }
+ }
+
+ private fun rememberContent(handler: StorageBackingHandle) {
+ // TODO: Make all of these functions work on deltas / updates instead of the entire contents
+ val data = Data.data?.storageInventories ?: return
+ when (handler) {
+ StorageBackingHandle.None -> {}
+ is StorageBackingHandle.Overview -> rememberStorageOverview(handler, data)
+ is StorageBackingHandle.Page -> rememberPage(handler, data)
+ }
+ }
+
+ private fun rememberStorageOverview(
+ handler: StorageBackingHandle.Overview,
+ data: SortedMap<StoragePageSlot, StorageData.StorageInventory>
+ ) {
+ for ((index, stack) in handler.handler.stacks.withIndex()) {
+ // Ignore unloaded item stacks
+ if (stack.isEmpty) continue
+ val slot = StoragePageSlot.fromOverviewSlotIndex(index) ?: continue
+ val isEmpty = stack.item in StorageOverlayScreen.emptyStorageSlotItems
+ if (slot in data) {
+ if (isEmpty)
+ data.remove(slot)
+ continue
+ }
+ if (!isEmpty) {
+ data[slot] = StorageData.StorageInventory(slot.defaultName(), slot, null)
+ }
+ }
+ }
+
+ private fun rememberPage(
+ handler: StorageBackingHandle.Page,
+ data: SortedMap<StoragePageSlot, StorageData.StorageInventory>
+ ) {
+ // TODO: FIXME: FIXME NOW: Definitely don't copy all of this every tick into persistence
+ val newStacks =
+ VirtualInventory(handler.handler.stacks.take(handler.handler.rows * 9).drop(9).map { it.copy() })
+ data.compute(handler.storagePageSlot) { slot, existingInventory ->
+ (existingInventory ?: StorageData.StorageInventory(
+ slot.defaultName(),
+ slot,
+ null
+ )).also {
+ it.inventory = newStacks
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayScreen.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayScreen.kt
new file mode 100644
index 0000000..1c896db
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayScreen.kt
@@ -0,0 +1,135 @@
+package moe.nea.firmament.features.inventory.storageoverlay
+
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.assertNotNullOr
+import moe.nea.firmament.util.toShedaniel
+import net.minecraft.block.Blocks
+import net.minecraft.client.gui.DrawContext
+import net.minecraft.client.gui.screen.Screen
+import net.minecraft.item.Item
+import net.minecraft.item.Items
+import net.minecraft.network.packet.c2s.play.CloseHandledScreenC2SPacket
+import net.minecraft.text.Text
+import net.minecraft.util.DyeColor
+import kotlin.math.max
+
+class StorageOverlayScreen() : Screen(Text.empty()) {
+ companion object {
+ val emptyStorageSlotItems = listOf<Item>(
+ Blocks.RED_STAINED_GLASS_PANE.asItem(),
+ Blocks.BROWN_STAINED_GLASS_PANE.asItem(),
+ Items.GRAY_DYE
+ )
+ val pageWidth get() = 19 * 9
+ }
+
+ private var handler: StorageBackingHandle = StorageBackingHandle.None
+ val content = StorageOverlay.Data.data ?: StorageData()
+ var isClosing = false
+
+ private fun discardOldHandle() {
+ val player = assertNotNullOr(MC.player) { return }
+ val handle = this.handler
+ if (handle is StorageBackingHandle.HasBackingScreen) {
+ player.networkHandler.sendPacket(CloseHandledScreenC2SPacket(handle.handler.syncId))
+ if (player.currentScreenHandler === handle.handler) {
+ player.currentScreenHandler = player.playerScreenHandler
+ }
+ }
+ }
+
+ fun setHandler(handler: StorageBackingHandle?) {
+ discardOldHandle()
+ if (handler != null)
+ this.handler = handler
+ }
+
+ var scroll = 0
+ var lastRenderedHeight = 0
+
+ override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
+ super.render(context, mouseX, mouseY, delta)
+ context.fill(0, 0, width, height, 0x90000000.toInt())
+ layoutedForEach { (key, value), offsetX, offsetY ->
+ context.matrices.push()
+ context.matrices.translate(offsetX.toFloat(), offsetY.toFloat(), 0F)
+ renderStoragePage(context, value, mouseX - offsetX, mouseY - offsetY)
+ context.matrices.pop()
+ }
+ }
+
+ inline fun layoutedForEach(onEach: (data: Pair<StoragePageSlot, StorageData.StorageInventory>, offsetX: Int, offsetY: Int) -> Unit) {
+ var offsetY = 0
+ var currentMaxHeight = StorageOverlay.config.margin - StorageOverlay.config.padding - scroll
+ var totalHeight = -currentMaxHeight
+ content.storageInventories.onEachIndexed { index, (key, value) ->
+ val pageX = (index % StorageOverlay.config.rows)
+ if (pageX == 0) {
+ currentMaxHeight += StorageOverlay.config.padding
+ offsetY += currentMaxHeight
+ totalHeight += currentMaxHeight
+ currentMaxHeight = 0
+ }
+ val xPosition =
+ width / 2 - (StorageOverlay.config.rows * (pageWidth + StorageOverlay.config.padding) - StorageOverlay.config.padding) / 2 + pageX * (pageWidth + StorageOverlay.config.padding)
+ onEach(Pair(key, value), xPosition, offsetY)
+ val height = getStorePageHeight(value)
+ currentMaxHeight = max(currentMaxHeight, height)
+ }
+ lastRenderedHeight = totalHeight + currentMaxHeight
+ }
+
+ override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
+ layoutedForEach { (k, p), x, y ->
+ val rx = mouseX - x
+ val ry = mouseY - y
+ if (rx in (0.0..pageWidth.toDouble()) && ry in (0.0..getStorePageHeight(p).toDouble())) {
+ close()
+ k.navigateTo()
+ return true
+ }
+ }
+ return super.mouseClicked(mouseX, mouseY, button)
+ }
+
+ fun getStorePageHeight(page: StorageData.StorageInventory): Int {
+ return page.inventory?.rows?.let { it * 19 + MC.font.fontHeight + 2 } ?: 60
+ }
+
+ override fun mouseScrolled(mouseX: Double, mouseY: Double, amount: Double): Boolean {
+ scroll =
+ (scroll + amount * StorageOverlay.config.scrollSpeed *
+ (if (StorageOverlay.config.inverseScroll) 1 else -1)).toInt()
+ .coerceAtMost(lastRenderedHeight - height + 2 * StorageOverlay.config.margin).coerceAtLeast(0)
+ return true
+ }
+
+ private fun renderStoragePage(context: DrawContext, page: StorageData.StorageInventory, mouseX: Int, mouseY: Int) {
+ context.drawText(MC.font, page.title, 2, 2, -1, true)
+ val inventory = page.inventory
+ if (inventory == null) {
+ // TODO: Missing texture
+ context.fill(0, 0, pageWidth, 60, DyeColor.RED.toShedaniel().darker(4.0).color)
+ context.drawCenteredTextWithShadow(MC.font, Text.literal("Not loaded yet"), pageWidth / 2, 30, -1)
+ return
+ }
+
+ for ((index, stack) in inventory.stacks.withIndex()) {
+ val x = (index % 9) * 19
+ val y = (index / 9) * 19 + MC.font.fontHeight + 2
+ if (((mouseX - x) in 0 until 18) && ((mouseY - y) in 0 until 18)) {
+ context.fill(x, y, x + 18, y + 18, 0x80808080.toInt())
+ } else {
+ context.fill(x, y, x + 18, y + 18, 0x40808080.toInt())
+ }
+ context.drawItem(stack, x + 1, y + 1)
+ context.drawItemInSlot(MC.font, stack, x + 1, y + 1)
+ }
+ }
+
+ override fun close() {
+ discardOldHandle()
+ isClosing = true
+ super.close()
+ }
+}
diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StoragePageSlot.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StoragePageSlot.kt
new file mode 100644
index 0000000..19afeef
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StoragePageSlot.kt
@@ -0,0 +1,64 @@
+package moe.nea.firmament.features.inventory.storageoverlay
+
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import moe.nea.firmament.util.MC
+
+@Serializable(with = StoragePageSlot.Serializer::class)
+data class StoragePageSlot(val index: Int) : Comparable<StoragePageSlot> {
+ object Serializer : KSerializer<StoragePageSlot> {
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("StoragePageSlot", PrimitiveKind.INT)
+
+ override fun deserialize(decoder: Decoder): StoragePageSlot {
+ return StoragePageSlot(decoder.decodeInt())
+ }
+
+ override fun serialize(encoder: Encoder, value: StoragePageSlot) {
+ encoder.encodeInt(value.index)
+ }
+ }
+
+ init {
+ assert(index in 0 until (3 * 9))
+ }
+
+ val isEnderChest get() = index < 9
+ val isBackPack get() = !isEnderChest
+ val slotIndexInOverviewPage get() = if (isEnderChest) index + 9 else index + 18
+ fun defaultName(): String = if (isEnderChest) "Ender Chest #${index + 1}" else "Backpack #${index - 9 + 1}"
+
+ fun navigateTo() {
+ if (isBackPack) {
+ MC.sendCommand("backpack ${index - 9 + 1}")
+ } else {
+ MC.sendCommand("enderchest ${index + 1}")
+ }
+ }
+
+ companion object {
+ fun fromOverviewSlotIndex(slot: Int): StoragePageSlot? {
+ if (slot in 9 until 18) return StoragePageSlot(slot - 9)
+ if (slot in 27 until 45) return StoragePageSlot(slot - 27 + 9)
+ return null
+ }
+
+ fun ofEnderChestPage(slot: Int): StoragePageSlot {
+ assert(slot in 1..9)
+ return StoragePageSlot(slot - 1)
+ }
+
+ fun ofBackPackPage(slot: Int): StoragePageSlot {
+ assert(slot in 1..18)
+ return StoragePageSlot(slot - 1 + 9)
+ }
+ }
+
+ override fun compareTo(other: StoragePageSlot): Int {
+ return this.index - other.index
+ }
+}
diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/VirtualInventory.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/VirtualInventory.kt
new file mode 100644
index 0000000..28280f9
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/VirtualInventory.kt
@@ -0,0 +1,53 @@
+package moe.nea.firmament.features.inventory.storageoverlay
+
+import io.ktor.util.decodeBase64Bytes
+import io.ktor.util.encodeBase64
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import net.minecraft.item.ItemStack
+import net.minecraft.nbt.NbtCompound
+import net.minecraft.nbt.NbtIo
+import net.minecraft.nbt.NbtList
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+
+@Serializable(with = VirtualInventory.Serializer::class)
+data class VirtualInventory(
+ val stacks: List<ItemStack>
+) {
+ val rows = stacks.size / 9
+
+ init {
+ assert(stacks.size % 9 == 0)
+ assert(stacks.size / 9 in 1..5)
+ }
+
+
+ object Serializer : KSerializer<VirtualInventory> {
+ const val INVENTORY = "INVENTORY"
+ override val descriptor: SerialDescriptor
+ get() = PrimitiveSerialDescriptor("VirtualInventory", PrimitiveKind.STRING)
+
+ override fun deserialize(decoder: Decoder): VirtualInventory {
+ val s = decoder.decodeString()
+ val n = NbtIo.readCompressed(ByteArrayInputStream(s.decodeBase64Bytes()))
+ val items = n.getList(INVENTORY, NbtCompound.COMPOUND_TYPE.toInt())
+ return VirtualInventory(items.map { ItemStack.fromNbt(it as NbtCompound) })
+ }
+
+ override fun serialize(encoder: Encoder, value: VirtualInventory) {
+ val list = NbtList()
+ value.stacks.forEach {
+ list.add(NbtCompound().also(it::writeNbt))
+ }
+ val baos = ByteArrayOutputStream()
+ NbtIo.writeCompressed(NbtCompound().also { it.put(INVENTORY, list) }, baos)
+ encoder.encodeString(baos.toByteArray().encodeBase64())
+ }
+ }
+}
diff --git a/src/main/kotlin/moe/nea/firmament/features/world/FairySouls.kt b/src/main/kotlin/moe/nea/firmament/features/world/FairySouls.kt
index d9d7f3d..91821f6 100644
--- a/src/main/kotlin/moe/nea/firmament/features/world/FairySouls.kt
+++ b/src/main/kotlin/moe/nea/firmament/features/world/FairySouls.kt
@@ -21,7 +21,14 @@ package moe.nea.firmament.features.world
import io.github.moulberry.repo.data.Coordinate
import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer
-import net.minecraft.util.math.Direction
+import net.minecraft.client.render.RenderLayer
+import net.minecraft.client.render.RenderLayer.ALWAYS_DEPTH_TEST
+import net.minecraft.client.render.RenderLayer.MultiPhaseParameters
+import net.minecraft.client.render.RenderPhase
+import net.minecraft.client.render.VertexFormat
+import net.minecraft.client.render.VertexFormats
+import net.minecraft.text.Text
+import net.minecraft.util.math.Vec3d
import moe.nea.firmament.events.ServerChatLineReceivedEvent
import moe.nea.firmament.events.SkyblockServerUpdateEvent
import moe.nea.firmament.events.WorldRenderLastEvent
@@ -32,6 +39,7 @@ import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SBData
import moe.nea.firmament.util.blockPos
import moe.nea.firmament.util.data.ProfileSpecificDataHolder
+import moe.nea.firmament.util.render.RenderInWorldContext
import moe.nea.firmament.util.render.RenderInWorldContext.Companion.renderInWorld
import moe.nea.firmament.util.unformattedString
@@ -59,7 +67,6 @@ object FairySouls : FirmamentFeature {
}
- override val name: String get() = "Fairy Souls"
override val identifier: String get() = "fairy-souls"
val playerReach = 5
@@ -107,6 +114,22 @@ object FairySouls : FirmamentFeature {
updateMissingSouls()
}
+ val NODEPTH: RenderLayer = RenderLayer.of(
+ "firmamentnodepth",
+ VertexFormats.POSITION_COLOR_TEXTURE,
+ VertexFormat.DrawMode.QUADS,
+ 256,
+ true,
+ true,
+ MultiPhaseParameters.builder()
+ .program(RenderPhase.COLOR_PROGRAM)
+ .writeMaskState(RenderPhase.COLOR_MASK)
+ .depthTest(ALWAYS_DEPTH_TEST)
+ .cull(RenderPhase.DISABLE_CULLING)
+ .layering(RenderLayer.VIEW_OFFSET_Z_LAYERING)
+ .target(RenderPhase.MAIN_TARGET)
+ .build(true)
+ )
override fun onLoad() {
SkyblockServerUpdateEvent.subscribe {
@@ -127,7 +150,8 @@ object FairySouls : FirmamentFeature {
}
WorldRenderLastEvent.subscribe {
if (!TConfig.displaySouls) return@subscribe
- renderInWorld(it.matrices, it.camera) {
+ renderInWorld(it) {
+ text(Vec3d(0.0, 0.0, 0.0), Text.literal("Test String") , Text.literal("Short"), Text.literal("just lik"), verticalAlign = RenderInWorldContext.VerticalAlign.BOTTOM)
color(1F, 1F, 0F, 0.8F)
currentMissingSouls.forEach {
block(it.blockPos)
diff --git a/src/main/kotlin/moe/nea/firmament/gui/WSpacer.kt b/src/main/kotlin/moe/nea/firmament/gui/WSpacer.kt
new file mode 100644
index 0000000..42d08fe
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/gui/WSpacer.kt
@@ -0,0 +1,19 @@
+package moe.nea.firmament.gui
+
+import io.github.cottonmc.cotton.gui.widget.WPanel
+import io.github.cottonmc.cotton.gui.widget.WWidget
+
+class WSpacer(val child: WWidget, val spaceLeft: Int) : WPanel() {
+ init {
+ children.add(child)
+ child.setLocation(spaceLeft, 0)
+ }
+
+ override fun getWidth(): Int {
+ return child.width + spaceLeft
+ }
+
+ override fun getHeight(): Int {
+ return child.height
+ }
+}
diff --git a/src/main/kotlin/moe/nea/firmament/gui/config/BooleanHandler.kt b/src/main/kotlin/moe/nea/firmament/gui/config/BooleanHandler.kt
index 7808192..df30f6d 100644
--- a/src/main/kotlin/moe/nea/firmament/gui/config/BooleanHandler.kt
+++ b/src/main/kotlin/moe/nea/firmament/gui/config/BooleanHandler.kt
@@ -18,14 +18,11 @@
package moe.nea.firmament.gui.config
-import io.github.cottonmc.cotton.gui.widget.WLabel
import io.github.cottonmc.cotton.gui.widget.WToggleButton
-import io.github.cottonmc.cotton.gui.widget.data.VerticalAlignment
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.jsonPrimitive
-import net.minecraft.text.Text
class BooleanHandler(val config: ManagedConfig) : ManagedConfig.OptionHandler<Boolean> {
override fun toJson(element: Boolean): JsonElement? {
diff --git a/src/main/kotlin/moe/nea/firmament/gui/config/DurationHandler.kt b/src/main/kotlin/moe/nea/firmament/gui/config/DurationHandler.kt
new file mode 100644
index 0000000..bf25691
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/gui/config/DurationHandler.kt
@@ -0,0 +1,57 @@
+package moe.nea.firmament.gui.config
+
+import io.github.cottonmc.cotton.gui.widget.WLabel
+import io.github.cottonmc.cotton.gui.widget.WSlider
+import io.github.cottonmc.cotton.gui.widget.data.Axis
+import io.github.cottonmc.cotton.gui.widget.data.VerticalAlignment
+import java.util.function.IntConsumer
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.json.jsonPrimitive
+import kotlinx.serialization.json.long
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.DurationUnit
+import kotlin.time.toDuration
+import net.minecraft.text.Text
+import moe.nea.firmament.util.FirmFormatters
+
+class DurationHandler(val config: ManagedConfig, val min: Duration, val max: Duration) :
+ ManagedConfig.OptionHandler<Duration> {
+ override fun toJson(element: Duration): JsonElement? {
+ return JsonPrimitive(element.inWholeMilliseconds)
+ }
+
+ override fun fromJson(element: JsonElement): Duration {
+ return element.jsonPrimitive.long.toDuration(DurationUnit.MILLISECONDS)
+ }
+
+ override fun emitGuiElements(opt: ManagedConfig.Option<Duration>, guiAppender: GuiAppender) {
+ val lw = guiAppender.width / 2
+ guiAppender.set(
+ 0, 0, lw, 1,
+ WLabel(opt.labelText).setVerticalAlignment(VerticalAlignment.CENTER)
+ )
+ val label =
+ WLabel(Text.literal(FirmFormatters.formatTimespan(opt.value))).setVerticalAlignment(VerticalAlignment.CENTER)
+ guiAppender.set(lw, 0, 2, 1, label)
+ guiAppender.set(
+ lw + 2,
+ 0,
+ lw - 2,
+ 1,
+ WSlider(min.inWholeMilliseconds.toInt(), max.inWholeMilliseconds.toInt(), Axis.HORIZONTAL).apply {
+ valueChangeListener = IntConsumer {
+ opt.value = it.milliseconds
+ label.text = Text.literal(FirmFormatters.formatTimespan(opt.value))
+ config.save()
+ }
+ guiAppender.onReload {
+ value = opt.value.inWholeMilliseconds.toInt()
+ label.text = Text.literal(FirmFormatters.formatTimespan(opt.value))
+ }
+ })
+ guiAppender.skipRows(1)
+ }
+
+}
diff --git a/src/main/kotlin/moe/nea/firmament/gui/config/GuiAppender.kt b/src/main/kotlin/moe/nea/firmament/gui/config/GuiAppender.kt
index d9d32f0..f298c76 100644
--- a/src/main/kotlin/moe/nea/firmament/gui/config/GuiAppender.kt
+++ b/src/main/kotlin/moe/nea/firmament/gui/config/GuiAppender.kt
@@ -29,7 +29,7 @@ class GuiAppender(val width: Int) {
internal val panel = WGridPanel().also { it.setGaps(4, 4) }
internal val reloadables = mutableListOf<(() -> Unit)>()
fun set(x: Int, y: Int, w: Int, h: Int, widget: WWidget) {
- panel.add(widget, x, y, w, h)
+ panel.add(widget, x, y + row, w, h)
}
@@ -50,13 +50,13 @@ class GuiAppender(val width: Int) {
fun appendSplitRow(left: WWidget, right: WWidget) {
val lw = width / 2
- set(0, row, lw, 1, left)
- set(lw, row, width - lw, 1, right)
+ set(0, 0, lw, 1, left)
+ set(lw, 0, width - lw, 1, right)
skipRows(1)
}
fun appendFullRow(widget: WWidget) {
- set(0, row, width, 1, widget)
+ set(0, 0, width, 1, widget)
skipRows(1)
}
}
diff --git a/src/main/kotlin/moe/nea/firmament/gui/config/IntegerHandler.kt b/src/main/kotlin/moe/nea/firmament/gui/config/IntegerHandler.kt
new file mode 100644
index 0000000..c705f7f
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/gui/config/IntegerHandler.kt
@@ -0,0 +1,51 @@
+package moe.nea.firmament.gui.config
+
+import io.github.cottonmc.cotton.gui.widget.WLabel
+import io.github.cottonmc.cotton.gui.widget.WSlider
+import io.github.cottonmc.cotton.gui.widget.data.Axis
+import io.github.cottonmc.cotton.gui.widget.data.VerticalAlignment
+import java.util.function.IntConsumer
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.json.int
+import kotlinx.serialization.json.jsonPrimitive
+import net.minecraft.text.Text
+
+class IntegerHandler(val config: ManagedConfig, val min: Int, val max: Int) : ManagedConfig.OptionHandler<Int> {
+ override fun toJson(element: Int): JsonElement? {
+ return JsonPrimitive(element)
+ }
+
+ override fun fromJson(element: JsonElement): Int {
+ return element.jsonPrimitive.int
+ }
+
+ override fun emitGuiElements(opt: ManagedConfig.Option<Int>, guiAppender: GuiAppender) {
+ val lw = guiAppender.width / 2
+ guiAppender.set(
+ 0, 0, lw, 1,
+ WLabel(opt.labelText).setVerticalAlignment(VerticalAlignment.CENTER)
+ )
+ val label =
+ WLabel(Text.literal(opt.value.toString())).setVerticalAlignment(VerticalAlignment.CENTER)
+ guiAppender.set(lw, 0, 2, 1, label)
+ guiAppender.set(
+ lw + 2,
+ 0,
+ lw - 2,
+ 1,
+ WSlider(min, max, Axis.HORIZONTAL).apply {
+ valueChangeListener = IntConsumer {
+ opt.value = it
+ label.text = Text.literal(opt.value.toString())
+ config.save()
+ }
+ guiAppender.onReload {
+ value = opt.value
+ label.text = Text.literal(opt.value.toString())
+ }
+ })
+ guiAppender.skipRows(1)
+ }
+
+}
diff --git a/src/main/kotlin/moe/nea/firmament/gui/config/ManagedConfig.kt b/src/main/kotlin/moe/nea/firmament/gui/config/ManagedConfig.kt
index 417a470..2115fb0 100644
--- a/src/main/kotlin/moe/nea/firmament/gui/config/ManagedConfig.kt
+++ b/src/main/kotlin/moe/nea/firmament/gui/config/ManagedConfig.kt
@@ -30,6 +30,7 @@ import kotlin.io.path.readText
import kotlin.io.path.writeText
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
+import kotlin.time.Duration
import net.minecraft.client.gui.screen.Screen
import net.minecraft.text.Text
import moe.nea.firmament.Firmament
@@ -139,6 +140,25 @@ abstract class ManagedConfig(val name: String) {
return option(propertyName, default, BooleanHandler(this))
}
+ protected fun duration(
+ propertyName: String,
+ min: Duration,
+ max: Duration,
+ default: () -> Duration,
+ ): Option<Duration> {
+ return option(propertyName, default, DurationHandler(this, min, max))
+ }
+
+
+ protected fun integer(
+ propertyName: String,
+ min: Int,
+ max: Int,
+ default: () -> Int,
+ ): Option<Int> {
+ return option(propertyName, default, IntegerHandler(this, min, max))
+ }
+
protected fun button(propertyName: String, runnable: () -> Unit): Option<Unit> {
return option(propertyName, { }, ClickHandler(this, runnable))
}
diff --git a/src/main/kotlin/moe/nea/firmament/gui/config/StringHandler.kt b/src/main/kotlin/moe/nea/firmament/gui/config/StringHandler.kt
index 84d24b5..a9c7ebf 100644
--- a/src/main/kotlin/moe/nea/firmament/gui/config/StringHandler.kt
+++ b/src/main/kotlin/moe/nea/firmament/gui/config/StringHandler.kt
@@ -38,6 +38,7 @@ class StringHandler(val config: ManagedConfig) : ManagedConfig.OptionHandler<Str
guiAppender.appendLabeledRow(
opt.labelText,
WTextField(opt.labelText).apply {
+ maxLength = 1000
suggestion = Text.translatableWithFallback(opt.rawLabelText + ".hint", "")
guiAppender.onReload { text = opt.value }
setChangedListener {
diff --git a/src/main/kotlin/moe/nea/firmament/repo/RepoManager.kt b/src/main/kotlin/moe/nea/firmament/repo/RepoManager.kt
index 1880617..0574907 100644
--- a/src/main/kotlin/moe/nea/firmament/repo/RepoManager.kt
+++ b/src/main/kotlin/moe/nea/firmament/repo/RepoManager.kt
@@ -35,6 +35,7 @@ import moe.nea.firmament.Firmament.logger
import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.hud.ProgressBar
import moe.nea.firmament.rei.PetData
+import moe.nea.firmament.util.MinecraftDispatcher
import moe.nea.firmament.util.SkyblockId
object RepoManager {
@@ -63,9 +64,11 @@ object RepoManager {
registerReloadListener(ItemCache)
registerReloadListener(ExpLadders)
registerReloadListener {
- if (!trySendClientboundUpdateRecipesPacket()) {
- logger.warn("Failed to issue a ClientboundUpdateRecipesPacket (to reload REI). This may lead to an outdated item list.")
- recentlyFailedToUpdateItemList = true
+ Firmament.coroutineScope.launch(MinecraftDispatcher) {
+ if (!trySendClientboundUpdateRecipesPacket()) {
+ logger.warn("Failed to issue a ClientboundUpdateRecipesPacket (to reload REI). This may lead to an outdated item list.")
+ recentlyFailedToUpdateItemList = true
+ }
}
}
}
diff --git a/src/main/kotlin/moe/nea/firmament/util/FirmFormatters.kt b/src/main/kotlin/moe/nea/firmament/util/FirmFormatters.kt
index ba77b04..a3e93ae 100644
--- a/src/main/kotlin/moe/nea/firmament/util/FirmFormatters.kt
+++ b/src/main/kotlin/moe/nea/firmament/util/FirmFormatters.kt
@@ -2,6 +2,7 @@ package moe.nea.firmament.util
import com.google.common.math.IntMath.pow
import kotlin.math.absoluteValue
+import kotlin.time.Duration
object FirmFormatters {
fun toString(float: Float, fractionalDigits: Int): String = toString(float.toDouble(), fractionalDigits)
@@ -13,4 +14,8 @@ object FirmFormatters {
return long.toString() + (if (digits.isEmpty()) "" else ".$digits")
}
+ fun formatTimespan(duration: Duration): String {
+ return duration.toString()
+ }
+
}
diff --git a/src/main/kotlin/moe/nea/firmament/util/MC.kt b/src/main/kotlin/moe/nea/firmament/util/MC.kt
index e9ab6c9..d595b61 100644
--- a/src/main/kotlin/moe/nea/firmament/util/MC.kt
+++ b/src/main/kotlin/moe/nea/firmament/util/MC.kt
@@ -24,6 +24,10 @@ import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.util.math.BlockPos
object MC {
+ fun sendCommand(command: String) {
+ player?.networkHandler?.sendCommand(command)
+ }
+
inline val font get() = MinecraftClient.getInstance().textRenderer
inline val soundManager get() = MinecraftClient.getInstance().soundManager
inline val player get() = MinecraftClient.getInstance().player
diff --git a/src/main/kotlin/moe/nea/firmament/util/ScoreboardUtil.kt b/src/main/kotlin/moe/nea/firmament/util/ScoreboardUtil.kt
new file mode 100644
index 0000000..d1bd057
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/util/ScoreboardUtil.kt
@@ -0,0 +1,38 @@
+package moe.nea.firmament.util
+
+import java.util.Optional
+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
+
+fun getScoreboardLines(): List<Text> {
+ val scoreboard = MC.player?.scoreboard ?: return listOf()
+ val activeObjective = scoreboard.getObjectiveForSlot(Scoreboard.SIDEBAR_DISPLAY_SLOT_ID) ?: return listOf()
+ return scoreboard.getAllPlayerScores(activeObjective).reversed().take(15).map {
+ val team = scoreboard.getPlayerTeam(it.playerName)
+ Team.decorateName(team, Text.literal(it.playerName))
+ }
+}
+
+
+fun Text.formattedString(): String {
+ val sb = StringBuilder()
+ visit(StringVisitable.StyledVisitor<Unit> { style, string ->
+ val c = Formatting.byName(style.color?.name)
+ if (c != null) {
+ sb.append("§${c.code}")
+ }
+ if (style.isUnderlined) {
+ sb.append("§n")
+ }
+ if (style.isBold) {
+ sb.append("§l")
+ }
+ sb.append(string)
+ Optional.empty()
+ }, Style.EMPTY)
+ return sb.toString().replace("§[^a-f0-9]".toRegex(), "")
+}
diff --git a/src/main/kotlin/moe/nea/firmament/util/item/NbtItemData.kt b/src/main/kotlin/moe/nea/firmament/util/item/NbtItemData.kt
new file mode 100644
index 0000000..2e721d8
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/util/item/NbtItemData.kt
@@ -0,0 +1,18 @@
+package moe.nea.firmament.util.item
+
+import net.minecraft.item.ItemStack
+import net.minecraft.nbt.NbtElement
+import net.minecraft.nbt.NbtString
+import net.minecraft.text.Text
+
+val ItemStack.loreAccordingToNbt
+ get() = getOrCreateSubNbt(ItemStack.DISPLAY_KEY).getList(ItemStack.LORE_KEY, NbtElement.STRING_TYPE.toInt())
+ .map { Text.Serializer.fromJson((it as NbtString).asString()) }
+
+val ItemStack.displayNameAccordingToNbt
+ get() = getOrCreateSubNbt(ItemStack.DISPLAY_KEY).let {
+ if (it.contains(ItemStack.NAME_KEY, NbtElement.STRING_TYPE.toInt()))
+ Text.Serializer.fromJson(it.getString(ItemStack.NAME_KEY))
+ else
+ null
+ }
diff --git a/src/main/kotlin/moe/nea/firmament/util/regex.kt b/src/main/kotlin/moe/nea/firmament/util/regex.kt
new file mode 100644
index 0000000..6317b59
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/util/regex.kt
@@ -0,0 +1,4 @@
+package moe.nea.firmament.util
+
+inline fun <T> String.ifMatches(regex: Regex, block: (MatchResult) -> T): T? =
+ regex.matchEntire(this)?.let(block)
diff --git a/src/main/kotlin/moe/nea/firmament/util/render/RenderInWorldContext.kt b/src/main/kotlin/moe/nea/firmament/util/render/RenderInWorldContext.kt
index 1603b6b..1d51f13 100644
--- a/src/main/kotlin/moe/nea/firmament/util/render/RenderInWorldContext.kt
+++ b/src/main/kotlin/moe/nea/firmament/util/render/RenderInWorldContext.kt
@@ -76,6 +76,10 @@ class RenderInWorldContext private constructor(
}
}
+ fun waypoint(position: BlockPos, label: Text) {
+ text(position.toCenterPos(), label, Text.literal("§e${MC.player?.pos?.distanceTo(position.toCenterPos())}m"))
+ }
+
fun text(position: Vec3d, vararg texts: Text, verticalAlign: VerticalAlign = VerticalAlign.CENTER) {
assertTrueOr(texts.isNotEmpty()) { return@text }
matrixStack.push()
diff --git a/src/main/resources/assets/firmament/lang/en_us.json b/src/main/resources/assets/firmament/lang/en_us.json
index 79b1503..f75fea0 100644
--- a/src/main/resources/assets/firmament/lang/en_us.json
+++ b/src/main/resources/assets/firmament/lang/en_us.json
@@ -57,5 +57,14 @@
"firmament.pv.skills.total": "Total Exp: %s",
"firmament.pv.lookingup": "Looking up %s",
"firmament.pv.noprofile": "%s has no SkyBlock profiles",
- "firmament.pv.noplayer": "%s is not a Minecraft player"
+ "firmament.pv.noplayer": "%s is not a Minecraft player",
+ "firmament.config.save-cursor-position.enable": "Enable",
+ "firmament.config.save-cursor-position.tolerance": "Tolerance",
+ "firmament.config.save-cursor-position": "Save Cursor Position",
+ "firmament.config.storage-overlay": "Storage Overlay",
+ "firmament.config.storage-overlay.rows": "Rows",
+ "firmament.config.storage-overlay.padding": "Padding",
+ "firmament.config.storage-overlay.scroll-speed": "Scroll Speed",
+ "firmament.config.storage-overlay.inverse-scroll": "Invert Scroll",
+ "firmament.config.storage-overlay.margin": "Margin"
}