diff options
Diffstat (limited to 'src/main/kotlin/util/mc')
-rw-r--r-- | src/main/kotlin/util/mc/ArmorUtil.kt | 8 | ||||
-rw-r--r-- | src/main/kotlin/util/mc/FirmamentDataComponentTypes.kt | 24 | ||||
-rw-r--r-- | src/main/kotlin/util/mc/InitLevel.kt | 25 | ||||
-rw-r--r-- | src/main/kotlin/util/mc/IntrospectableItemModelManager.kt | 7 | ||||
-rw-r--r-- | src/main/kotlin/util/mc/MCTabListAPI.kt | 96 | ||||
-rw-r--r-- | src/main/kotlin/util/mc/NbtItemData.kt | 4 | ||||
-rw-r--r-- | src/main/kotlin/util/mc/NbtPrism.kt | 91 | ||||
-rw-r--r-- | src/main/kotlin/util/mc/NbtUtil.kt | 10 | ||||
-rw-r--r-- | src/main/kotlin/util/mc/PlayerUtil.kt | 7 | ||||
-rw-r--r-- | src/main/kotlin/util/mc/SNbtFormatter.kt | 16 | ||||
-rw-r--r-- | src/main/kotlin/util/mc/SkullItemData.kt | 3 | ||||
-rw-r--r-- | src/main/kotlin/util/mc/SlotUtils.kt | 18 | ||||
-rw-r--r-- | src/main/kotlin/util/mc/TolerantRegistriesOps.kt | 29 | ||||
-rw-r--r-- | src/main/kotlin/util/mc/asFakeServer.kt | 37 |
14 files changed, 362 insertions, 13 deletions
diff --git a/src/main/kotlin/util/mc/ArmorUtil.kt b/src/main/kotlin/util/mc/ArmorUtil.kt new file mode 100644 index 0000000..fd1867c --- /dev/null +++ b/src/main/kotlin/util/mc/ArmorUtil.kt @@ -0,0 +1,8 @@ +package moe.nea.firmament.util.mc + +import net.minecraft.entity.EquipmentSlot +import net.minecraft.entity.LivingEntity + +val LivingEntity.iterableArmorItems + get() = EquipmentSlot.entries.asSequence() + .map { it to getEquippedStack(it) } diff --git a/src/main/kotlin/util/mc/FirmamentDataComponentTypes.kt b/src/main/kotlin/util/mc/FirmamentDataComponentTypes.kt index 012f52e..0866665 100644 --- a/src/main/kotlin/util/mc/FirmamentDataComponentTypes.kt +++ b/src/main/kotlin/util/mc/FirmamentDataComponentTypes.kt @@ -1,12 +1,15 @@ package moe.nea.firmament.util.mc import com.mojang.serialization.Codec +import io.netty.buffer.ByteBuf import net.minecraft.component.ComponentType +import net.minecraft.network.codec.PacketCodec import net.minecraft.registry.Registries import net.minecraft.registry.Registry import moe.nea.firmament.Firmament import moe.nea.firmament.annotations.Subscribe import moe.nea.firmament.events.ClientInitEvent +import moe.nea.firmament.repo.MiningRepoData object FirmamentDataComponentTypes { @@ -26,11 +29,32 @@ object FirmamentDataComponentTypes { ) } + fun <T> errorCodec(message: String): PacketCodec<in ByteBuf, T> = + object : PacketCodec<ByteBuf, T> { + override fun decode(buf: ByteBuf?): T? { + error(message) + } + + override fun encode(buf: ByteBuf?, value: T?) { + error(message) + } + } + + fun <T, B : ComponentType.Builder<T>> B.neverEncode(message: String = "This element should never be encoded or decoded"): B { + packetCodec(errorCodec(message)) + codec(null) + return this + } + val IS_BROKEN = register<Boolean>( "is_broken" ) { it.codec(Codec.BOOL.fieldOf("is_broken").codec()) } + val CUSTOM_MINING_BLOCK_DATA = register<MiningRepoData.CustomMiningBlock>("custom_mining_block") { + it.neverEncode() + } + } diff --git a/src/main/kotlin/util/mc/InitLevel.kt b/src/main/kotlin/util/mc/InitLevel.kt new file mode 100644 index 0000000..2c3eedb --- /dev/null +++ b/src/main/kotlin/util/mc/InitLevel.kt @@ -0,0 +1,25 @@ +package moe.nea.firmament.util.mc + +enum class InitLevel { + STARTING, + MC_INIT, + RENDER_INIT, + RENDER, + MAIN_MENU, + ; + + companion object { + var initLevel = InitLevel.STARTING + private set + + @JvmStatic + fun isAtLeast(wantedLevel: InitLevel): Boolean = initLevel >= wantedLevel + + @JvmStatic + fun bump(nextLevel: InitLevel) { + if (nextLevel.ordinal != initLevel.ordinal + 1) + error("Cannot bump initLevel $nextLevel from $initLevel") + initLevel = nextLevel + } + } +} diff --git a/src/main/kotlin/util/mc/IntrospectableItemModelManager.kt b/src/main/kotlin/util/mc/IntrospectableItemModelManager.kt new file mode 100644 index 0000000..e546fd3 --- /dev/null +++ b/src/main/kotlin/util/mc/IntrospectableItemModelManager.kt @@ -0,0 +1,7 @@ +package moe.nea.firmament.util.mc + +import net.minecraft.util.Identifier + +interface IntrospectableItemModelManager { + fun hasModel_firmament(identifier: Identifier): Boolean +} diff --git a/src/main/kotlin/util/mc/MCTabListAPI.kt b/src/main/kotlin/util/mc/MCTabListAPI.kt new file mode 100644 index 0000000..66bdd55 --- /dev/null +++ b/src/main/kotlin/util/mc/MCTabListAPI.kt @@ -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, + ) + } +} diff --git a/src/main/kotlin/util/mc/NbtItemData.kt b/src/main/kotlin/util/mc/NbtItemData.kt index e8a908f..0c49862 100644 --- a/src/main/kotlin/util/mc/NbtItemData.kt +++ b/src/main/kotlin/util/mc/NbtItemData.kt @@ -5,8 +5,8 @@ import net.minecraft.component.type.LoreComponent import net.minecraft.item.ItemStack import net.minecraft.text.Text -var ItemStack.loreAccordingToNbt - get() = get(DataComponentTypes.LORE)?.lines ?: listOf() +var ItemStack.loreAccordingToNbt: List<Text> + get() = get(DataComponentTypes.LORE)?.lines ?: listOf() set(value) { set(DataComponentTypes.LORE, LoreComponent(value)) } diff --git a/src/main/kotlin/util/mc/NbtPrism.kt b/src/main/kotlin/util/mc/NbtPrism.kt new file mode 100644 index 0000000..f034210 --- /dev/null +++ b/src/main/kotlin/util/mc/NbtPrism.kt @@ -0,0 +1,91 @@ +package moe.nea.firmament.util.mc + +import com.google.gson.Gson +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonPrimitive +import com.mojang.brigadier.StringReader +import com.mojang.brigadier.arguments.ArgumentType +import com.mojang.brigadier.arguments.StringArgumentType +import com.mojang.brigadier.context.CommandContext +import com.mojang.brigadier.suggestion.Suggestions +import com.mojang.brigadier.suggestion.SuggestionsBuilder +import com.mojang.serialization.JsonOps +import java.util.concurrent.CompletableFuture +import kotlin.collections.indices +import kotlin.collections.map +import kotlin.jvm.optionals.getOrNull +import net.minecraft.nbt.NbtCompound +import net.minecraft.nbt.NbtElement +import net.minecraft.nbt.NbtList +import net.minecraft.nbt.NbtOps +import net.minecraft.nbt.NbtString +import moe.nea.firmament.util.Base64Util + +class NbtPrism(val path: List<String>) { + companion object { + fun fromElement(path: JsonElement): NbtPrism? { + if (path is JsonArray) { + return NbtPrism(path.map { (it as JsonPrimitive).asString }) + } else if (path is JsonPrimitive && path.isString) { + return NbtPrism(path.asString.split(".")) + } + return null + } + } + + object Argument : ArgumentType<NbtPrism> { + override fun parse(reader: StringReader): NbtPrism? { + return fromElement(JsonPrimitive(StringArgumentType.string().parse(reader))) + } + + override fun getExamples(): Collection<String?>? { + return listOf("some.nbt.path", "some.other.*", "some.path.*json.in.a.json.string") + } + } + + override fun toString(): String { + return "Prism($path)" + } + + fun access(root: NbtElement): Collection<NbtElement> { + var rootSet = mutableListOf(root) + var switch = mutableListOf<NbtElement>() + for (pathSegment in path) { + if (pathSegment == ".") continue + if (pathSegment != "*" && pathSegment.startsWith("*")) { + if (pathSegment == "*json") { + for (element in rootSet) { + val eString = element.asString().getOrNull() ?: continue + val element = Gson().fromJson(eString, JsonElement::class.java) + switch.add(JsonOps.INSTANCE.convertTo(NbtOps.INSTANCE, element)) + } + } else if (pathSegment == "*base64") { + for (element in rootSet) { + val string = element.asString().getOrNull() ?: continue + switch.add(NbtString.of(Base64Util.decodeString(string))) + } + } + } + for (element in rootSet) { + if (element is NbtList) { + if (pathSegment == "*") + switch.addAll(element) + val index = pathSegment.toIntOrNull() ?: continue + if (index !in element.indices) continue + switch.add(element[index]) + } + if (element is NbtCompound) { + if (pathSegment == "*") + element.keys.mapTo(switch) { element.get(it)!! } + switch.add(element.get(pathSegment) ?: continue) + } + } + val temp = switch + switch = rootSet + rootSet = temp + switch.clear() + } + return rootSet + } +} diff --git a/src/main/kotlin/util/mc/NbtUtil.kt b/src/main/kotlin/util/mc/NbtUtil.kt new file mode 100644 index 0000000..2cab1c7 --- /dev/null +++ b/src/main/kotlin/util/mc/NbtUtil.kt @@ -0,0 +1,10 @@ +package moe.nea.firmament.util.mc + +import net.minecraft.nbt.NbtElement +import net.minecraft.nbt.NbtList + +fun Iterable<NbtElement>.toNbtList() = NbtList().also { + for (element in this) { + it.add(element) + } +} diff --git a/src/main/kotlin/util/mc/PlayerUtil.kt b/src/main/kotlin/util/mc/PlayerUtil.kt new file mode 100644 index 0000000..53ef1f4 --- /dev/null +++ b/src/main/kotlin/util/mc/PlayerUtil.kt @@ -0,0 +1,7 @@ +package moe.nea.firmament.util.mc + +import net.minecraft.entity.EquipmentSlot +import net.minecraft.entity.player.PlayerEntity + + +val PlayerEntity.mainHandStack get() = this.getEquippedStack(EquipmentSlot.MAINHAND) diff --git a/src/main/kotlin/util/mc/SNbtFormatter.kt b/src/main/kotlin/util/mc/SNbtFormatter.kt index e773927..7617d17 100644 --- a/src/main/kotlin/util/mc/SNbtFormatter.kt +++ b/src/main/kotlin/util/mc/SNbtFormatter.kt @@ -1,5 +1,6 @@ package moe.nea.firmament.util.mc +import net.minecraft.nbt.AbstractNbtList import net.minecraft.nbt.NbtByte import net.minecraft.nbt.NbtByteArray import net.minecraft.nbt.NbtCompound @@ -38,7 +39,7 @@ class SNbtFormatter private constructor() : NbtElementVisitor { override fun visitString(element: NbtString) { - result.append(NbtString.escape(element.asString())) + result.append(NbtString.escape(element.value)) } override fun visitByte(element: NbtByte) { @@ -65,18 +66,18 @@ class SNbtFormatter private constructor() : NbtElementVisitor { result.append(element.doubleValue()).append("d") } - private fun visitArrayContents(array: List<NbtElement>) { + private fun visitArrayContents(array: AbstractNbtList) { array.forEachIndexed { index, element -> writeIndent() element.accept(this) - if (array.size != index + 1) { + if (array.size() != index + 1) { result.append(",") } result.append("\n") } } - private fun writeArray(arrayTypeTag: String, array: List<NbtElement>) { + private fun writeArray(arrayTypeTag: String, array: AbstractNbtList) { result.append("[").append(arrayTypeTag).append("\n") pushIndent() visitArrayContents(array) @@ -109,7 +110,7 @@ class SNbtFormatter private constructor() : NbtElementVisitor { keys.forEachIndexed { index, key -> writeIndent() val element = compound[key] ?: error("Key '$key' found but not present in compound: $compound") - val escapedName = if (key.matches(SIMPLE_NAME)) key else NbtString.escape(key) + val escapedName = escapeName(key) result.append(escapedName).append(": ") element.accept(this) if (keys.size != index + 1) { @@ -133,6 +134,9 @@ class SNbtFormatter private constructor() : NbtElementVisitor { fun NbtElement.toPrettyString() = prettify(this) - private val SIMPLE_NAME = "[A-Za-z0-9._+-]+".toRegex() + fun escapeName(key: String): String = + if (key.matches(SIMPLE_NAME)) key else NbtString.escape(key) + + val SIMPLE_NAME = "[A-Za-z0-9._+-]+".toRegex() } } diff --git a/src/main/kotlin/util/mc/SkullItemData.kt b/src/main/kotlin/util/mc/SkullItemData.kt index 0405b65..1b7dcba 100644 --- a/src/main/kotlin/util/mc/SkullItemData.kt +++ b/src/main/kotlin/util/mc/SkullItemData.kt @@ -10,7 +10,6 @@ import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.serialization.Serializable import kotlinx.serialization.UseSerializers -import kotlinx.serialization.encodeToString import net.minecraft.component.DataComponentTypes import net.minecraft.component.type.ProfileComponent import net.minecraft.item.ItemStack @@ -51,7 +50,7 @@ fun ItemStack.setEncodedSkullOwner(uuid: UUID, encodedData: String) { this.set(DataComponentTypes.PROFILE, ProfileComponent(gameProfile)) } -val zeroUUID = UUID.fromString("d3cb85e2-3075-48a1-b213-a9bfb62360c1") +val arbitraryUUID = UUID.fromString("d3cb85e2-3075-48a1-b213-a9bfb62360c1") fun createSkullItem(uuid: UUID, url: String) = ItemStack(Items.PLAYER_HEAD) .also { it.setSkullOwner(uuid, url) } diff --git a/src/main/kotlin/util/mc/SlotUtils.kt b/src/main/kotlin/util/mc/SlotUtils.kt index 4709dcf..9eb4918 100644 --- a/src/main/kotlin/util/mc/SlotUtils.kt +++ b/src/main/kotlin/util/mc/SlotUtils.kt @@ -1,5 +1,6 @@ package moe.nea.firmament.util.mc +import org.lwjgl.glfw.GLFW import net.minecraft.screen.ScreenHandler import net.minecraft.screen.slot.Slot import net.minecraft.screen.slot.SlotActionType @@ -10,7 +11,7 @@ object SlotUtils { MC.interactionManager?.clickSlot( handler.syncId, this.id, - 2, + GLFW.GLFW_MOUSE_BUTTON_MIDDLE, SlotActionType.CLONE, MC.player ) @@ -20,14 +21,25 @@ object SlotUtils { MC.interactionManager?.clickSlot( handler.syncId, this.id, hotbarIndex, SlotActionType.SWAP, - MC.player) + MC.player + ) } fun Slot.clickRightMouseButton(handler: ScreenHandler) { MC.interactionManager?.clickSlot( handler.syncId, this.id, - 1, + GLFW.GLFW_MOUSE_BUTTON_RIGHT, + SlotActionType.PICKUP, + MC.player + ) + } + + fun Slot.clickLeftMouseButton(handler: ScreenHandler) { + MC.interactionManager?.clickSlot( + handler.syncId, + this.id, + GLFW.GLFW_MOUSE_BUTTON_LEFT, SlotActionType.PICKUP, MC.player ) diff --git a/src/main/kotlin/util/mc/TolerantRegistriesOps.kt b/src/main/kotlin/util/mc/TolerantRegistriesOps.kt new file mode 100644 index 0000000..ce596a0 --- /dev/null +++ b/src/main/kotlin/util/mc/TolerantRegistriesOps.kt @@ -0,0 +1,29 @@ +package moe.nea.firmament.util.mc + +import com.mojang.serialization.DynamicOps +import java.util.Optional +import net.minecraft.registry.Registry +import net.minecraft.registry.RegistryKey +import net.minecraft.registry.RegistryOps +import net.minecraft.registry.RegistryWrapper +import net.minecraft.registry.entry.RegistryEntryOwner + +class TolerantRegistriesOps<T>( + delegate: DynamicOps<T>, + registryInfoGetter: RegistryInfoGetter +) : RegistryOps<T>(delegate, registryInfoGetter) { + constructor(delegate: DynamicOps<T>, registry: RegistryWrapper.WrapperLookup) : + this(delegate, CachedRegistryInfoGetter(registry)) + + class TolerantOwner<E> : RegistryEntryOwner<E> { + override fun ownerEquals(other: RegistryEntryOwner<E>?): Boolean { + return true + } + } + + override fun <E : Any?> getOwner(registryRef: RegistryKey<out Registry<out E>>?): Optional<RegistryEntryOwner<E>> { + return super.getOwner(registryRef).map { + TolerantOwner() + } + } +} diff --git a/src/main/kotlin/util/mc/asFakeServer.kt b/src/main/kotlin/util/mc/asFakeServer.kt new file mode 100644 index 0000000..d3811bd --- /dev/null +++ b/src/main/kotlin/util/mc/asFakeServer.kt @@ -0,0 +1,37 @@ +package moe.nea.firmament.util.mc + +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource +import net.minecraft.server.command.CommandOutput +import net.minecraft.server.command.ServerCommandSource +import net.minecraft.text.Text + +fun FabricClientCommandSource.asFakeServer(): ServerCommandSource { + val source = this + return ServerCommandSource( + object : CommandOutput { + override fun sendMessage(message: Text?) { + source.player.sendMessage(message, false) + } + + override fun shouldReceiveFeedback(): Boolean { + return true + } + + override fun shouldTrackOutput(): Boolean { + return true + } + + override fun shouldBroadcastConsoleToOps(): Boolean { + return true + } + }, + source.position, + source.rotation, + null, + 0, + "FakeServerCommandSource", + Text.literal("FakeServerCommandSource"), + null, + source.player + ) +} |