feat(internal): Add a tab list api

This commit is contained in:
Linnea Gräf
2025-06-26 18:21:02 +02:00
parent e926550bd1
commit 1c5d0df368
13 changed files with 1426 additions and 18 deletions

View File

@@ -12,6 +12,7 @@ import moe.nea.firmament.apis.UrsaManager
import moe.nea.firmament.events.CommandEvent
import moe.nea.firmament.events.FirmamentEventBus
import moe.nea.firmament.features.debug.DebugLogger
import moe.nea.firmament.features.debug.DeveloperFeatures
import moe.nea.firmament.features.debug.PowerUserTools
import moe.nea.firmament.features.inventory.buttons.InventoryButtons
import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen
@@ -202,7 +203,7 @@ fun firmamentCommand() = literal("firmament") {
}
}
}
thenLiteral("dev") {
thenLiteral(DeveloperFeatures.DEVELOPER_SUBCOMMAND) {
thenLiteral("simulate") {
thenArgument("message", RestArgumentType) { message ->
thenExecute {

View File

@@ -62,7 +62,7 @@ object AnimatedClothingScanner {
@Subscribe
fun onSubCommand(event: CommandEvent.SubCommand) {
event.subcommand("dev") {
event.subcommand(DeveloperFeatures.DEVELOPER_SUBCOMMAND) {
thenLiteral("stealthisfit") {
thenLiteral("clear") {
thenExecute {

View File

@@ -25,6 +25,7 @@ import moe.nea.firmament.util.asm.AsmAnnotationUtil
import moe.nea.firmament.util.iterate
object DeveloperFeatures : FirmamentFeature {
val DEVELOPER_SUBCOMMAND: String = "dev"
override val identifier: String
get() = "developer"
override val config: TConfig
@@ -103,9 +104,12 @@ object DeveloperFeatures : FirmamentFeature {
MC.sendChat(Text.translatable("firmament.dev.resourcerebuild.start"))
val startTime = TimeMark.now()
process.toHandle().onExit().thenApply {
MC.sendChat(Text.stringifiedTranslatable(
"firmament.dev.resourcerebuild.done",
startTime.passedTime()))
MC.sendChat(
Text.stringifiedTranslatable(
"firmament.dev.resourcerebuild.done",
startTime.passedTime()
)
)
Unit
}
} else {

View File

@@ -20,7 +20,7 @@ object SoundVisualizer {
@Subscribe
fun onSubCommand(event: CommandEvent.SubCommand) {
event.subcommand("dev") {
event.subcommand(DeveloperFeatures.DEVELOPER_SUBCOMMAND) {
thenLiteral("sounds") {
thenExecute {
showSounds = !showSounds

View File

@@ -26,6 +26,7 @@ import moe.nea.firmament.commands.thenExecute
import moe.nea.firmament.commands.thenLiteral
import moe.nea.firmament.events.CommandEvent
import moe.nea.firmament.events.HandledScreenKeyPressedEvent
import moe.nea.firmament.features.debug.DeveloperFeatures
import moe.nea.firmament.features.debug.ExportedTestConstantMeta
import moe.nea.firmament.features.debug.PowerUserTools
import moe.nea.firmament.repo.RepoDownloadManager
@@ -97,7 +98,7 @@ object ItemExporter {
@Subscribe
fun onCommand(event: CommandEvent.SubCommand) {
event.subcommand("dev") {
event.subcommand(DeveloperFeatures.DEVELOPER_SUBCOMMAND) {
thenLiteral("reexportlore") {
thenArgument("itemid", StringArgumentType.string()) { itemid ->
suggestsList { RepoManager.neuRepo.items.items.keys }

View File

@@ -9,6 +9,8 @@ object StringUtil {
return string.replace(",", "").toInt()
}
fun String.title() = replaceFirstChar { it.titlecase() }
fun Iterable<String>.unwords() = joinToString(" ")
fun nextLexicographicStringOfSameLength(string: String): String {
val next = StringBuilder(string)

View File

@@ -0,0 +1,96 @@
package moe.nea.firmament.util.mc
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import java.util.Optional
import org.jetbrains.annotations.TestOnly
import net.minecraft.client.gui.hud.PlayerListHud
import net.minecraft.nbt.NbtOps
import net.minecraft.scoreboard.Team
import net.minecraft.text.Text
import net.minecraft.text.TextCodecs
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.commands.thenExecute
import moe.nea.firmament.commands.thenLiteral
import moe.nea.firmament.events.CommandEvent
import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.features.debug.DeveloperFeatures
import moe.nea.firmament.features.debug.ExportedTestConstantMeta
import moe.nea.firmament.mixins.accessor.AccessorPlayerListHud
import moe.nea.firmament.util.ClipboardUtils
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.intoOptional
import moe.nea.firmament.util.mc.SNbtFormatter.Companion.toPrettyString
object MCTabListAPI {
fun PlayerListHud.cast() = this as AccessorPlayerListHud
@Subscribe
fun onTick(event: TickEvent) {
_currentTabList = null
}
@Subscribe
fun devCommand(event: CommandEvent.SubCommand) {
event.subcommand(DeveloperFeatures.DEVELOPER_SUBCOMMAND) {
thenLiteral("copytablist") {
thenExecute {
currentTabList.body.forEach {
MC.sendChat(Text.literal(TextCodecs.CODEC.encodeStart(NbtOps.INSTANCE, it).orThrow.toString()))
}
var compound = CurrentTabList.CODEC.encodeStart(NbtOps.INSTANCE, currentTabList).orThrow
compound = ExportedTestConstantMeta.SOURCE_CODEC.encode(
ExportedTestConstantMeta.current,
NbtOps.INSTANCE,
compound
).orThrow
ClipboardUtils.setTextContent(
compound.toPrettyString()
)
}
}
}
}
@get:TestOnly
@set:TestOnly
var _currentTabList: CurrentTabList? = null
val currentTabList get() = _currentTabList ?: getTabListNow().also { _currentTabList = it }
data class CurrentTabList(
val header: Optional<Text>,
val footer: Optional<Text>,
val body: List<Text>,
) {
companion object {
val CODEC: Codec<CurrentTabList> = RecordCodecBuilder.create {
it.group(
TextCodecs.CODEC.optionalFieldOf("header").forGetter(CurrentTabList::header),
TextCodecs.CODEC.optionalFieldOf("footer").forGetter(CurrentTabList::footer),
TextCodecs.CODEC.listOf().fieldOf("body").forGetter(CurrentTabList::body),
).apply(it, ::CurrentTabList)
}
}
}
private fun getTabListNow(): CurrentTabList {
// This is a precondition for PlayerListHud.collectEntries to be valid
MC.networkHandler ?: return CurrentTabList(Optional.empty(), Optional.empty(), emptyList())
val hud = MC.inGameHud.playerListHud.cast()
val entries = hud.collectPlayerEntries_firmament()
.map {
it.displayName ?: run {
val team = it.scoreboardTeam
val name = it.profile.name
Team.decorateName(team, Text.literal(name))
}
}
return CurrentTabList(
header = hud.header_firmament.intoOptional(),
footer = hud.footer_firmament.intoOptional(),
body = entries,
)
}
}

View File

@@ -0,0 +1,41 @@
package moe.nea.firmament.util.skyblock
import org.intellij.lang.annotations.Language
import net.minecraft.text.Text
import moe.nea.firmament.util.StringUtil.title
import moe.nea.firmament.util.StringUtil.unwords
import moe.nea.firmament.util.mc.MCTabListAPI
import moe.nea.firmament.util.unformattedString
object TabListAPI {
fun getWidgetLines(widgetName: WidgetName, includeTitle: Boolean = false, from: MCTabListAPI.CurrentTabList = MCTabListAPI.currentTabList): List<Text> {
return from.body
.dropWhile { !widgetName.matchesTitle(it) }
.takeWhile { it.string.isNotBlank() && !it.string.startsWith(" ") }
.let { if (includeTitle) it else it.drop(1) }
}
enum class WidgetName(regex: Regex?) {
COMMISSIONS,
SKILLS("Skills:( .*)?"),
PROFILE("Profile: (.*)"),
COLLECTION,
ESSENCE,
PET
;
fun matchesTitle(it: Text): Boolean {
return regex.matches(it.unformattedString)
}
constructor() : this(null)
constructor(@Language("RegExp") regex: String) : this(Regex(regex))
val label =
name.split("_").map { it.lowercase().title() }.unwords()
val regex = regex ?: Regex.fromLiteral("$label:")
}
}