Bulk commit

This commit is contained in:
nea
2023-07-11 21:01:58 +02:00
parent 4444fcca44
commit 4d93f475aa
39 changed files with 951 additions and 40 deletions

View File

@@ -1,7 +1,10 @@
Prio 0 (Bugs):
- WorldReadyEvent buggy? -> out of date locraw
Priority 1:
- recipes - recipes
- more recipe categories - more recipe categories
- dfu cache - dfu cache
- replace REI with custom renderer (if needed)
- Storage Overlay - Storage Overlay
- PV - PV
- NEU buttons - NEU buttons

View File

@@ -4,8 +4,9 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
java java
`maven-publish` `maven-publish`
kotlin("jvm") version "1.8.10" kotlin("jvm") version "1.8.20"
kotlin("plugin.serialization") version "1.8.10" 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("dev.architectury.loom") version "1.1.336"
id("com.github.johnrengelman.shadow") version "7.1.2" id("com.github.johnrengelman.shadow") version "7.1.2"
id("moe.nea.licenseextractificator") id("moe.nea.licenseextractificator")

View File

@@ -16,6 +16,7 @@ qolify = "1.3.0-1.20"
citresewn = "1.1.3+1.20" citresewn = "1.1.3+1.20"
hotswap_agent = "1.4.2-SNAPSHOT" hotswap_agent = "1.4.2-SNAPSHOT"
sodium = "mc1.20-0.4.10" sodium = "mc1.20-0.4.10"
freecammod = "1.2.0-mc1.20"
ncr = "Fabric-1.20-v2.2.0" ncr = "Fabric-1.20-v2.2.0"
mixinextras = "0.2.0-beta.9" 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" } citresewn = { module = "maven.modrinth:cit-resewn", version.ref = "citresewn" }
ncr = { module = "maven.modrinth:no-chat-reports", version.ref = "ncr" } ncr = { module = "maven.modrinth:no-chat-reports", version.ref = "ncr" }
sodium = { module = "maven.modrinth:sodium", version.ref = "sodium" } sodium = { module = "maven.modrinth:sodium", version.ref = "sodium" }
freecammod = { module = "maven.modrinth:freecam", version.ref = "freecammod" }
[bundles] [bundles]
dbus = ["dbus_java_core", "dbus_java_unixsocket"] dbus = ["dbus_java_core", "dbus_java_unixsocket"]
runtime_required = ["architectury_fabric", "rei_fabric"] runtime_required = ["architectury_fabric", "rei_fabric"]
runtime_optional = [ runtime_optional = [
"devauth", "devauth",
"freecammod",
"sodium", "sodium",
"qolify", "qolify",
"citresewn", "citresewn",

View File

@@ -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();
}
}

View File

@@ -19,25 +19,29 @@
package moe.nea.firmament.mixins; package moe.nea.firmament.mixins;
import moe.nea.firmament.events.WorldRenderLastEvent; import moe.nea.firmament.events.WorldRenderLastEvent;
import net.minecraft.client.render.Camera; import net.minecraft.client.render.*;
import net.minecraft.client.render.GameRenderer;
import net.minecraft.client.render.LightmapTextureManager;
import net.minecraft.client.render.WorldRenderer;
import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.util.math.MatrixStack;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; 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.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(WorldRenderer.class) @Mixin(WorldRenderer.class)
public class MixinWorldRenderer { 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)) @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) { 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( var event = new WorldRenderLastEvent(
matrices, tickDelta, renderBlockOutline, matrices, tickDelta, renderBlockOutline,
camera, gameRenderer, lightmapTextureManager, camera, gameRenderer, lightmapTextureManager,
positionMatrix positionMatrix,
this.bufferBuilders.getEntityVertexConsumers()
); );
WorldRenderLastEvent.Companion.publish(event); WorldRenderLastEvent.Companion.publish(event);
} }

View File

@@ -19,13 +19,15 @@
package moe.nea.firmament package moe.nea.firmament
import com.mojang.brigadier.CommandDispatcher import com.mojang.brigadier.CommandDispatcher
import io.ktor.client.* import dev.architectury.event.events.client.ClientTickEvent
import io.ktor.client.plugins.* import io.ktor.client.HttpClient
import io.ktor.client.plugins.cache.* import io.ktor.client.plugins.UserAgent
import io.ktor.client.plugins.compression.* import io.ktor.client.plugins.cache.HttpCache
import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.plugins.compression.ContentEncoding
import io.ktor.client.plugins.logging.* import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.serialization.kotlinx.json.* 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.Files
import java.nio.file.Path import java.nio.file.Path
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback
@@ -49,6 +51,7 @@ import net.minecraft.command.CommandRegistryAccess
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import moe.nea.firmament.commands.registerFirmamentCommand import moe.nea.firmament.commands.registerFirmamentCommand
import moe.nea.firmament.dbus.FirmamentDbusObject import moe.nea.firmament.dbus.FirmamentDbusObject
import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.features.FeatureManager import moe.nea.firmament.features.FeatureManager
import moe.nea.firmament.repo.HypixelStaticData import moe.nea.firmament.repo.HypixelStaticData
import moe.nea.firmament.repo.RepoManager import moe.nea.firmament.repo.RepoManager
@@ -114,6 +117,10 @@ object Firmament {
@JvmStatic @JvmStatic
fun onClientInitialize() { fun onClientInitialize() {
dbusConnection.requestBusName("moe.nea.firmament") dbusConnection.requestBusName("moe.nea.firmament")
var tick = 0
ClientTickEvent.CLIENT_POST.register(ClientTickEvent.Client { instance ->
TickEvent.publish(TickEvent(tick++))
})
dbusConnection.exportObject(FirmamentDbusObject) dbusConnection.exportObject(FirmamentDbusObject)
IDataHolder.registerEvents() IDataHolder.registerEvents()
RepoManager.initialize() RepoManager.initialize()

View File

@@ -22,6 +22,7 @@ import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.arguments.StringArgumentType.string import com.mojang.brigadier.arguments.StringArgumentType.string
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource
import net.minecraft.text.Text import net.minecraft.text.Text
import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen
import moe.nea.firmament.features.world.FairySouls import moe.nea.firmament.features.world.FairySouls
import moe.nea.firmament.gui.config.AllConfigsGui import moe.nea.firmament.gui.config.AllConfigsGui
import moe.nea.firmament.gui.profileviewer.ProfileViewer 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.FirmFormatters
import moe.nea.firmament.util.MC import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SBData import moe.nea.firmament.util.SBData
import moe.nea.firmament.util.ScreenUtil
import moe.nea.firmament.util.SkyblockId import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.unformattedString import moe.nea.firmament.util.unformattedString
@@ -40,6 +42,12 @@ fun firmamentCommand() = literal("firmament") {
AllConfigsGui.showAllGuis() AllConfigsGui.showAllGuis()
} }
} }
thenLiteral("storage") {
thenExecute {
ScreenUtil.setScreenLater(StorageOverlayScreen())
MC.player?.networkHandler?.sendChatCommand("ec")
}
}
thenLiteral("repo") { thenLiteral("repo") {
thenLiteral("reload") { thenLiteral("reload") {
thenLiteral("fetch") { thenLiteral("fetch") {

View File

@@ -0,0 +1,5 @@
package moe.nea.firmament.events
data class TickEvent(val tickCount: Int) : FirmamentEvent() {
companion object : FirmamentEventBus<TickEvent>()
}

View File

@@ -21,10 +21,13 @@ package moe.nea.firmament.features
import kotlinx.serialization.Serializable 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.debug.DebugView
import moe.nea.firmament.features.debug.DeveloperFeatures import moe.nea.firmament.features.debug.DeveloperFeatures
import moe.nea.firmament.features.fishing.FishingWarning import moe.nea.firmament.features.fishing.FishingWarning
import moe.nea.firmament.features.inventory.CraftingOverlay 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.SlotLocking
import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlay
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
@@ -50,9 +53,13 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature
loadFeature(FairySouls) loadFeature(FairySouls)
loadFeature(FishingWarning) loadFeature(FishingWarning)
loadFeature(SlotLocking) loadFeature(SlotLocking)
loadFeature(StorageOverlay)
loadFeature(CraftingOverlay) loadFeature(CraftingOverlay)
if (Firmament.DEBUG) loadFeature(SaveCursorPosition)
if (Firmament.DEBUG) {
loadFeature(DeveloperFeatures) loadFeature(DeveloperFeatures)
loadFeature(DebugView)
}
hasAutoloaded = true hasAutoloaded = true
} }
} }

View File

@@ -21,7 +21,6 @@ package moe.nea.firmament.features
import moe.nea.firmament.gui.config.ManagedConfig import moe.nea.firmament.gui.config.ManagedConfig
interface FirmamentFeature { interface FirmamentFeature {
val name: String
val identifier: String val identifier: String
val defaultEnabled: Boolean val defaultEnabled: Boolean
get() = true get() = true

View File

@@ -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)
}
}
}
}
}

View File

@@ -14,8 +14,6 @@ import moe.nea.firmament.util.TimeMark
import moe.nea.firmament.util.iterate import moe.nea.firmament.util.iterate
object DeveloperFeatures : FirmamentFeature { object DeveloperFeatures : FirmamentFeature {
override val name: String
get() = "developer"
override val identifier: String override val identifier: String
get() = "developer" get() = "developer"
override val config: TConfig override val config: TConfig

View File

@@ -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()
}
}

View File

@@ -38,8 +38,6 @@ import moe.nea.firmament.util.TimeMark
import moe.nea.firmament.util.render.RenderInWorldContext.Companion.renderInWorld import moe.nea.firmament.util.render.RenderInWorldContext.Companion.renderInWorld
object FishingWarning : FirmamentFeature { object FishingWarning : FirmamentFeature {
override val name: String
get() = "Fishing Warning"
override val identifier: String override val identifier: String
get() = "fishing-warning" get() = "fishing-warning"
@@ -137,7 +135,7 @@ object FishingWarning : FirmamentFeature {
WorldRenderLastEvent.subscribe { WorldRenderLastEvent.subscribe {
recentParticles.removeIf { it.second.passedTime() > 5.seconds } recentParticles.removeIf { it.second.passedTime() > 5.seconds }
recentCandidates.removeIf { it.timeMark.passedTime() > 5.seconds } recentCandidates.removeIf { it.timeMark.passedTime() > 5.seconds }
renderInWorld(it.matrices, it.camera) { renderInWorld(it) {
color(0f, 0f, 1f, 1f) color(0f, 0f, 1f, 1f)
recentParticles.forEach { recentParticles.forEach {
tinyBlock(it.first, 0.1F) tinyBlock(it.first, 0.1F)

View File

@@ -26,8 +26,6 @@ object CraftingOverlay : FirmamentFeature {
this.recipe = recipe this.recipe = recipe
} }
override val name: String
get() = "Crafting Overlay"
override val identifier: String override val identifier: String
get() = "crafting-overlay" get() = "crafting-overlay"

View File

@@ -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)
}
}

View File

@@ -33,8 +33,6 @@ import moe.nea.firmament.util.MC
import moe.nea.firmament.util.data.ProfileSpecificDataHolder import moe.nea.firmament.util.data.ProfileSpecificDataHolder
object SlotLocking : FirmamentFeature { object SlotLocking : FirmamentFeature {
override val name: String
get() = "Slot Locking"
override val identifier: String override val identifier: String
get() = "slot-locking" get() = "slot-locking"

View File

@@ -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()))
}
}
}
}

View File

@@ -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?,
)
}

View File

@@ -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
}
}
}
}

View File

@@ -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()
}
}

View File

@@ -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
}
}

View File

@@ -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())
}
}
}

View File

@@ -21,7 +21,14 @@ package moe.nea.firmament.features.world
import io.github.moulberry.repo.data.Coordinate import io.github.moulberry.repo.data.Coordinate
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer 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.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
@@ -32,6 +39,7 @@ import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SBData import moe.nea.firmament.util.SBData
import moe.nea.firmament.util.blockPos import moe.nea.firmament.util.blockPos
import moe.nea.firmament.util.data.ProfileSpecificDataHolder 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.render.RenderInWorldContext.Companion.renderInWorld
import moe.nea.firmament.util.unformattedString 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" override val identifier: String get() = "fairy-souls"
val playerReach = 5 val playerReach = 5
@@ -107,6 +114,22 @@ object FairySouls : FirmamentFeature {
updateMissingSouls() 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() { override fun onLoad() {
SkyblockServerUpdateEvent.subscribe { SkyblockServerUpdateEvent.subscribe {
@@ -127,7 +150,8 @@ object FairySouls : FirmamentFeature {
} }
WorldRenderLastEvent.subscribe { WorldRenderLastEvent.subscribe {
if (!TConfig.displaySouls) return@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) color(1F, 1F, 0F, 0.8F)
currentMissingSouls.forEach { currentMissingSouls.forEach {
block(it.blockPos) block(it.blockPos)

View File

@@ -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
}
}

View File

@@ -18,14 +18,11 @@
package moe.nea.firmament.gui.config 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.WToggleButton
import io.github.cottonmc.cotton.gui.widget.data.VerticalAlignment
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.boolean import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.jsonPrimitive
import net.minecraft.text.Text
class BooleanHandler(val config: ManagedConfig) : ManagedConfig.OptionHandler<Boolean> { class BooleanHandler(val config: ManagedConfig) : ManagedConfig.OptionHandler<Boolean> {
override fun toJson(element: Boolean): JsonElement? { override fun toJson(element: Boolean): JsonElement? {

View File

@@ -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)
}
}

View File

@@ -29,7 +29,7 @@ class GuiAppender(val width: Int) {
internal val panel = WGridPanel().also { it.setGaps(4, 4) } internal val panel = WGridPanel().also { it.setGaps(4, 4) }
internal val reloadables = mutableListOf<(() -> Unit)>() internal val reloadables = mutableListOf<(() -> Unit)>()
fun set(x: Int, y: Int, w: Int, h: Int, widget: WWidget) { 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) { fun appendSplitRow(left: WWidget, right: WWidget) {
val lw = width / 2 val lw = width / 2
set(0, row, lw, 1, left) set(0, 0, lw, 1, left)
set(lw, row, width - lw, 1, right) set(lw, 0, width - lw, 1, right)
skipRows(1) skipRows(1)
} }
fun appendFullRow(widget: WWidget) { fun appendFullRow(widget: WWidget) {
set(0, row, width, 1, widget) set(0, 0, width, 1, widget)
skipRows(1) skipRows(1)
} }
} }

View File

@@ -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)
}
}

View File

@@ -30,6 +30,7 @@ import kotlin.io.path.readText
import kotlin.io.path.writeText import kotlin.io.path.writeText
import kotlin.properties.ReadWriteProperty import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
import kotlin.time.Duration
import net.minecraft.client.gui.screen.Screen import net.minecraft.client.gui.screen.Screen
import net.minecraft.text.Text import net.minecraft.text.Text
import moe.nea.firmament.Firmament import moe.nea.firmament.Firmament
@@ -139,6 +140,25 @@ abstract class ManagedConfig(val name: String) {
return option(propertyName, default, BooleanHandler(this)) 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> { protected fun button(propertyName: String, runnable: () -> Unit): Option<Unit> {
return option(propertyName, { }, ClickHandler(this, runnable)) return option(propertyName, { }, ClickHandler(this, runnable))
} }

View File

@@ -38,6 +38,7 @@ class StringHandler(val config: ManagedConfig) : ManagedConfig.OptionHandler<Str
guiAppender.appendLabeledRow( guiAppender.appendLabeledRow(
opt.labelText, opt.labelText,
WTextField(opt.labelText).apply { WTextField(opt.labelText).apply {
maxLength = 1000
suggestion = Text.translatableWithFallback(opt.rawLabelText + ".hint", "") suggestion = Text.translatableWithFallback(opt.rawLabelText + ".hint", "")
guiAppender.onReload { text = opt.value } guiAppender.onReload { text = opt.value }
setChangedListener { setChangedListener {

View File

@@ -35,6 +35,7 @@ import moe.nea.firmament.Firmament.logger
import moe.nea.firmament.gui.config.ManagedConfig import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.hud.ProgressBar import moe.nea.firmament.hud.ProgressBar
import moe.nea.firmament.rei.PetData import moe.nea.firmament.rei.PetData
import moe.nea.firmament.util.MinecraftDispatcher
import moe.nea.firmament.util.SkyblockId import moe.nea.firmament.util.SkyblockId
object RepoManager { object RepoManager {
@@ -63,9 +64,11 @@ object RepoManager {
registerReloadListener(ItemCache) registerReloadListener(ItemCache)
registerReloadListener(ExpLadders) registerReloadListener(ExpLadders)
registerReloadListener { registerReloadListener {
if (!trySendClientboundUpdateRecipesPacket()) { Firmament.coroutineScope.launch(MinecraftDispatcher) {
logger.warn("Failed to issue a ClientboundUpdateRecipesPacket (to reload REI). This may lead to an outdated item list.") if (!trySendClientboundUpdateRecipesPacket()) {
recentlyFailedToUpdateItemList = true logger.warn("Failed to issue a ClientboundUpdateRecipesPacket (to reload REI). This may lead to an outdated item list.")
recentlyFailedToUpdateItemList = true
}
} }
} }
} }

View File

@@ -2,6 +2,7 @@ package moe.nea.firmament.util
import com.google.common.math.IntMath.pow import com.google.common.math.IntMath.pow
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.time.Duration
object FirmFormatters { object FirmFormatters {
fun toString(float: Float, fractionalDigits: Int): String = toString(float.toDouble(), fractionalDigits) 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") return long.toString() + (if (digits.isEmpty()) "" else ".$digits")
} }
fun formatTimespan(duration: Duration): String {
return duration.toString()
}
} }

View File

@@ -24,6 +24,10 @@ import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
object MC { object MC {
fun sendCommand(command: String) {
player?.networkHandler?.sendCommand(command)
}
inline val font get() = MinecraftClient.getInstance().textRenderer inline val font get() = MinecraftClient.getInstance().textRenderer
inline val soundManager get() = MinecraftClient.getInstance().soundManager inline val soundManager get() = MinecraftClient.getInstance().soundManager
inline val player get() = MinecraftClient.getInstance().player inline val player get() = MinecraftClient.getInstance().player

View File

@@ -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(), "")
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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) { fun text(position: Vec3d, vararg texts: Text, verticalAlign: VerticalAlign = VerticalAlign.CENTER) {
assertTrueOr(texts.isNotEmpty()) { return@text } assertTrueOr(texts.isNotEmpty()) { return@text }
matrixStack.push() matrixStack.push()

View File

@@ -57,5 +57,14 @@
"firmament.pv.skills.total": "Total Exp: %s", "firmament.pv.skills.total": "Total Exp: %s",
"firmament.pv.lookingup": "Looking up %s", "firmament.pv.lookingup": "Looking up %s",
"firmament.pv.noprofile": "%s has no SkyBlock profiles", "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"
} }