diff options
Diffstat (limited to 'src/main/kotlin')
33 files changed, 884 insertions, 31 deletions
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() |