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
- more recipe categories
- dfu cache
- replace REI with custom renderer (if needed)
- Storage Overlay
- PV
- NEU buttons

View File

@@ -4,8 +4,9 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
java
`maven-publish`
kotlin("jvm") version "1.8.10"
kotlin("plugin.serialization") version "1.8.10"
kotlin("jvm") version "1.8.20"
kotlin("plugin.serialization") version "1.8.20"
id("com.bnorm.power.kotlin-power-assert") version "0.13.0"
id("dev.architectury.loom") version "1.1.336"
id("com.github.johnrengelman.shadow") version "7.1.2"
id("moe.nea.licenseextractificator")

View File

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

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;
import moe.nea.firmament.events.WorldRenderLastEvent;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.GameRenderer;
import net.minecraft.client.render.LightmapTextureManager;
import net.minecraft.client.render.WorldRenderer;
import net.minecraft.client.render.*;
import net.minecraft.client.util.math.MatrixStack;
import org.joml.Matrix4f;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(WorldRenderer.class)
public class MixinWorldRenderer {
@Shadow
@Final
private BufferBuilderStorage bufferBuilders;
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/WorldRenderer;renderChunkDebugInfo(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;Lnet/minecraft/client/render/Camera;)V", shift = At.Shift.BEFORE))
public void onWorldRenderLast(MatrixStack matrices, float tickDelta, long limitTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, LightmapTextureManager lightmapTextureManager, Matrix4f positionMatrix, CallbackInfo ci) {
var event = new WorldRenderLastEvent(
matrices, tickDelta, renderBlockOutline,
camera, gameRenderer, lightmapTextureManager,
positionMatrix
positionMatrix,
this.bufferBuilders.getEntityVertexConsumers()
);
WorldRenderLastEvent.Companion.publish(event);
}

View File

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

View File

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

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

View File

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

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
object DeveloperFeatures : FirmamentFeature {
override val name: String
get() = "developer"
override val identifier: String
get() = "developer"
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
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)

View File

@@ -26,8 +26,6 @@ object CraftingOverlay : FirmamentFeature {
this.recipe = recipe
}
override val name: String
get() = "Crafting Overlay"
override val identifier: String
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
object SlotLocking : FirmamentFeature {
override val name: String
get() = "Slot Locking"
override val identifier: String
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 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)

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
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? {

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

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

View File

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

View File

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

View File

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

View File

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

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) {
assertTrueOr(texts.isNotEmpty()) { return@text }
matrixStack.push()

View File

@@ -57,5 +57,14 @@
"firmament.pv.skills.total": "Total Exp: %s",
"firmament.pv.lookingup": "Looking up %s",
"firmament.pv.noprofile": "%s has no SkyBlock profiles",
"firmament.pv.noplayer": "%s is not a Minecraft player"
"firmament.pv.noplayer": "%s is not a Minecraft player",
"firmament.config.save-cursor-position.enable": "Enable",
"firmament.config.save-cursor-position.tolerance": "Tolerance",
"firmament.config.save-cursor-position": "Save Cursor Position",
"firmament.config.storage-overlay": "Storage Overlay",
"firmament.config.storage-overlay.rows": "Rows",
"firmament.config.storage-overlay.padding": "Padding",
"firmament.config.storage-overlay.scroll-speed": "Scroll Speed",
"firmament.config.storage-overlay.inverse-scroll": "Invert Scroll",
"firmament.config.storage-overlay.margin": "Margin"
}