diff options
Diffstat (limited to 'src/main/kotlin/util/mc')
21 files changed, 652 insertions, 208 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..3bb1768 --- /dev/null +++ b/src/main/kotlin/util/mc/ArmorUtil.kt @@ -0,0 +1,8 @@ +package moe.nea.firmament.util.mc + +import net.minecraft.world.entity.EquipmentSlot +import net.minecraft.world.entity.LivingEntity + +val LivingEntity.iterableArmorItems + get() = EquipmentSlot.entries.asSequence() + .map { it to getItemBySlot(it) } diff --git a/src/main/kotlin/util/mc/CustomRenderPassHelper.kt b/src/main/kotlin/util/mc/CustomRenderPassHelper.kt new file mode 100644 index 0000000..93cd7c1 --- /dev/null +++ b/src/main/kotlin/util/mc/CustomRenderPassHelper.kt @@ -0,0 +1,161 @@ +package moe.nea.firmament.util.mc + +import com.mojang.blaze3d.buffers.GpuBuffer +import com.mojang.blaze3d.buffers.GpuBufferSlice +import com.mojang.blaze3d.buffers.Std140Builder +import com.mojang.blaze3d.pipeline.RenderPipeline +import com.mojang.blaze3d.systems.RenderPass +import com.mojang.blaze3d.systems.RenderSystem +import com.mojang.blaze3d.vertex.VertexFormat +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.util.OptionalDouble +import java.util.OptionalInt +import org.joml.Vector3f +import org.joml.Vector4f +import com.mojang.blaze3d.pipeline.RenderTarget +import com.mojang.blaze3d.vertex.BufferBuilder +import com.mojang.blaze3d.vertex.MeshData +import net.minecraft.client.renderer.texture.AbstractTexture +import com.mojang.blaze3d.vertex.ByteBufferBuilder +import net.minecraft.resources.ResourceLocation +import net.minecraft.util.Mth +import moe.nea.firmament.util.ErrorUtil +import moe.nea.firmament.util.MC + + +class CustomRenderPassHelper( + val labelSupplier: () -> String, + val drawMode: VertexFormat.Mode, + val vertexFormat: VertexFormat, + val frameBuffer: RenderTarget, + val hasDepth: Boolean, +) : AutoCloseable { + private val scope = mutableListOf<AutoCloseable>() + private val preparations = mutableListOf<(RenderPass) -> Unit>() + val device = RenderSystem.getDevice() + private var hasPipelineAction = false + private var hasSetDefaultUniforms = false + val commandEncoder = device.createCommandEncoder() + fun setPipeline(pipeline: RenderPipeline) { + ErrorUtil.softCheck("Already has a pipeline", !hasPipelineAction) + hasPipelineAction = true + queueAction { + it.setPipeline(pipeline) + } + } + + fun bindSampler(name: String, texture: ResourceLocation) { + bindSampler(name, MC.textureManager.getTexture(texture)) + } + + fun bindSampler(name: String, texture: AbstractTexture) { + queueAction { it.bindSampler(name, texture.textureView) } + } + + + fun dontSetDefaultUniforms() { + hasSetDefaultUniforms = true + } + + fun setAllDefaultUniforms() { + hasSetDefaultUniforms = true + queueAction { + RenderSystem.bindDefaultUniforms(it) + } + setUniform( + "DynamicTransforms", RenderSystem.getDynamicUniforms() + .writeTransform( + RenderSystem.getModelViewMatrix(), + Vector4f(1.0F, 1.0F, 1.0F, 1.0F), + Vector3f(), // TODO: 1.21.10 + RenderSystem.getTextureMatrix(), + RenderSystem.getShaderLineWidth() + ) + ) + } + + fun setUniform(name: String, slice: GpuBufferSlice) = queueAction { it.setUniform(name, slice) } + fun setUniform(name: String, slice: GpuBuffer) = queueAction { it.setUniform(name, slice) } + + fun setUniform(name: String, size: Int, labelSupplier: () -> String = { name }, init: (Std140Builder) -> Unit) { + val buffer = createUniformBuffer(labelSupplier, allocateByteBuf(size, init)) + setUniform(name, buffer) + } + + var vertices: MeshData? = null + + fun uploadVertices(size: Int, init: (BufferBuilder) -> Unit) { + uploadVertices( + BufferBuilder(queueClose(ByteBufferBuilder(size)), drawMode, vertexFormat) + .also(init) + .buildOrThrow() + ) + } + + fun uploadVertices(buffer: MeshData) { + queueClose(buffer) + ErrorUtil.softCheck("Vertices have already been uploaded", vertices == null) + vertices = buffer + val vertexBuffer = vertexFormat.uploadImmediateVertexBuffer(buffer.vertexBuffer()) + val indexBufferConstructor = RenderSystem.getSequentialBuffer(drawMode) + val indexBuffer = indexBufferConstructor.getBuffer(buffer.drawState().indexCount) + queueAction { + it.setIndexBuffer(indexBuffer, indexBufferConstructor.type()) + it.setVertexBuffer(0, vertexBuffer) + } + } + + fun createUniformBuffer(labelSupplier: () -> String, buffer: ByteBuffer): GpuBuffer { + return queueClose( + device.createBuffer( + labelSupplier::invoke, + GpuBuffer.USAGE_UNIFORM or GpuBuffer.USAGE_MAP_READ, + buffer + ) + ) + } + + fun allocateByteBuf(size: Int, init: (Std140Builder) -> Unit): ByteBuffer { + return Std140Builder.intoBuffer( // TODO: i really dont know about this 16 align? but it seems to be generally correct. + ByteBuffer + .allocateDirect(Mth.roundToward(size, 16)) + .order(ByteOrder.nativeOrder()) + ).also(init).get() + } + + fun queueAction(action: (RenderPass) -> Unit) { + preparations.add(action) + } + + fun <T : AutoCloseable> queueClose(t: T): T = t.also { scope.add(it) } + override fun close() { + scope.reversed().forEach { it.close() } + } + + object DrawToken + + fun draw(): DrawToken { + val vertexData = (ErrorUtil.notNullOr(vertices, "No vertex data uploaded") { return DrawToken }) + ErrorUtil.softCheck("Missing default uniforms", hasSetDefaultUniforms) + ErrorUtil.softCheck("Missing a pipeline", hasPipelineAction) + val renderPass = queueClose( + commandEncoder.createRenderPass( + labelSupplier::invoke, + RenderSystem.outputColorTextureOverride ?: frameBuffer.colorTextureView!!, + OptionalInt.empty(), + (RenderSystem.outputDepthTextureOverride + ?: frameBuffer.depthTextureView).takeIf { frameBuffer.useDepth && hasDepth }, + OptionalDouble.empty() + ) + ) + preparations.forEach { it(renderPass) } + renderPass.drawIndexed( + 0, + 0, + vertexData.drawState().indexCount, + 1 + ) + return DrawToken + } +} diff --git a/src/main/kotlin/util/mc/FakeInventory.kt b/src/main/kotlin/util/mc/FakeInventory.kt index 26c04bc..198ec68 100644 --- a/src/main/kotlin/util/mc/FakeInventory.kt +++ b/src/main/kotlin/util/mc/FakeInventory.kt @@ -1,14 +1,14 @@ package util.mc -import net.minecraft.entity.player.PlayerEntity -import net.minecraft.inventory.Inventory -import net.minecraft.item.ItemStack +import net.minecraft.world.entity.player.Player +import net.minecraft.world.Container +import net.minecraft.world.item.ItemStack -class FakeInventory(val stack: ItemStack) : Inventory { - override fun clear() { +class FakeInventory(val stack: ItemStack) : Container { + override fun clearContent() { } - override fun size(): Int { + override fun getContainerSize(): Int { return 1 } @@ -16,26 +16,26 @@ class FakeInventory(val stack: ItemStack) : Inventory { return stack.isEmpty } - override fun getStack(slot: Int): ItemStack { + override fun getItem(slot: Int): ItemStack { require(slot == 0) return stack } - override fun removeStack(slot: Int, amount: Int): ItemStack { + override fun removeItem(slot: Int, amount: Int): ItemStack { return ItemStack.EMPTY } - override fun removeStack(slot: Int): ItemStack { + override fun removeItemNoUpdate(slot: Int): ItemStack { return ItemStack.EMPTY } - override fun setStack(slot: Int, stack: ItemStack?) { + override fun setItem(slot: Int, stack: ItemStack?) { } - override fun markDirty() { + override fun setChanged() { } - override fun canPlayerUse(player: PlayerEntity?): Boolean { + override fun stillValid(player: Player?): Boolean { return true } } diff --git a/src/main/kotlin/util/mc/FakeSlot.kt b/src/main/kotlin/util/mc/FakeSlot.kt index a9be484..9793fdf 100644 --- a/src/main/kotlin/util/mc/FakeSlot.kt +++ b/src/main/kotlin/util/mc/FakeSlot.kt @@ -1,15 +1,15 @@ package moe.nea.firmament.util.mc import util.mc.FakeInventory -import net.minecraft.item.ItemStack -import net.minecraft.screen.slot.Slot +import net.minecraft.world.item.ItemStack +import net.minecraft.world.inventory.Slot class FakeSlot( - stack: ItemStack, - x: Int, - y: Int + stack: ItemStack, + x: Int, + y: Int ) : Slot(FakeInventory(stack), 0, x, y) { init { - id = 0 + index = 0 } } diff --git a/src/main/kotlin/util/mc/FirmamentDataComponentTypes.kt b/src/main/kotlin/util/mc/FirmamentDataComponentTypes.kt index 0866665..79536e5 100644 --- a/src/main/kotlin/util/mc/FirmamentDataComponentTypes.kt +++ b/src/main/kotlin/util/mc/FirmamentDataComponentTypes.kt @@ -2,10 +2,10 @@ 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 net.minecraft.core.component.DataComponentType +import net.minecraft.network.codec.StreamCodec +import net.minecraft.core.registries.BuiltInRegistries +import net.minecraft.core.Registry import moe.nea.firmament.Firmament import moe.nea.firmament.annotations.Subscribe import moe.nea.firmament.events.ClientInitEvent @@ -19,18 +19,18 @@ object FirmamentDataComponentTypes { private fun <T> register( id: String, - builderOperator: (ComponentType.Builder<T>) -> Unit - ): ComponentType<T> { + builderOperator: (DataComponentType.Builder<T>) -> Unit + ): DataComponentType<T> { return Registry.register( - Registries.DATA_COMPONENT_TYPE, + BuiltInRegistries.DATA_COMPONENT_TYPE, Firmament.identifier(id), - ComponentType.builder<T>().also(builderOperator) + DataComponentType.builder<T>().also(builderOperator) .build() ) } - fun <T> errorCodec(message: String): PacketCodec<in ByteBuf, T> = - object : PacketCodec<ByteBuf, T> { + fun <T> errorCodec(message: String): StreamCodec<in ByteBuf, T> = + object : StreamCodec<ByteBuf, T> { override fun decode(buf: ByteBuf?): T? { error(message) } @@ -40,16 +40,16 @@ object FirmamentDataComponentTypes { } } - fun <T, B : ComponentType.Builder<T>> B.neverEncode(message: String = "This element should never be encoded or decoded"): B { - packetCodec(errorCodec(message)) - codec(null) + fun <T, B : DataComponentType.Builder<T>> B.neverEncode(message: String = "This element should never be encoded or decoded"): B { + networkSynchronized(errorCodec(message)) + persistent(null) return this } val IS_BROKEN = register<Boolean>( "is_broken" ) { - it.codec(Codec.BOOL.fieldOf("is_broken").codec()) + it.persistent(Codec.BOOL.fieldOf("is_broken").codec()) } val CUSTOM_MINING_BLOCK_DATA = register<MiningRepoData.CustomMiningBlock>("custom_mining_block") { 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 index e546fd3..537ca5b 100644 --- a/src/main/kotlin/util/mc/IntrospectableItemModelManager.kt +++ b/src/main/kotlin/util/mc/IntrospectableItemModelManager.kt @@ -1,7 +1,7 @@ package moe.nea.firmament.util.mc -import net.minecraft.util.Identifier +import net.minecraft.resources.ResourceLocation interface IntrospectableItemModelManager { - fun hasModel_firmament(identifier: Identifier): Boolean + fun hasModel_firmament(identifier: ResourceLocation): Boolean } diff --git a/src/main/kotlin/util/mc/InventoryUtil.kt b/src/main/kotlin/util/mc/InventoryUtil.kt index 74f7b9f..0509138 100644 --- a/src/main/kotlin/util/mc/InventoryUtil.kt +++ b/src/main/kotlin/util/mc/InventoryUtil.kt @@ -2,26 +2,26 @@ package moe.nea.firmament.util.mc import java.util.Spliterator import java.util.Spliterators -import net.minecraft.inventory.Inventory -import net.minecraft.item.ItemStack +import net.minecraft.world.Container +import net.minecraft.world.item.ItemStack -val Inventory.indices get() = 0 until size() -val Inventory.iterableView +val Container.indices get() = 0 until containerSize +val Container.iterableView get() = object : Iterable<ItemStack> { override fun spliterator(): Spliterator<ItemStack> { - return Spliterators.spliterator(iterator(), size().toLong(), 0) + return Spliterators.spliterator(iterator(), containerSize.toLong(), 0) } override fun iterator(): Iterator<ItemStack> { return object : Iterator<ItemStack> { var i = 0 override fun hasNext(): Boolean { - return i < size() + return i < containerSize } override fun next(): ItemStack { if (!hasNext()) throw NoSuchElementException() - return getStack(i++) + return getItem(i++) } } } diff --git a/src/main/kotlin/util/mc/ItemUtil.kt b/src/main/kotlin/util/mc/ItemUtil.kt index 13519cf..91b6409 100644 --- a/src/main/kotlin/util/mc/ItemUtil.kt +++ b/src/main/kotlin/util/mc/ItemUtil.kt @@ -1,20 +1,30 @@ package moe.nea.firmament.util.mc -import net.minecraft.item.ItemStack -import net.minecraft.text.Text +import kotlin.jvm.optionals.getOrNull +import net.minecraft.world.item.ItemStack +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.NbtOps +import net.minecraft.resources.RegistryOps +import net.minecraft.core.HolderLookup +import net.minecraft.network.chat.Component +import moe.nea.firmament.util.MC -fun ItemStack.appendLore(args: List<Text>) { - if (args.isEmpty()) return - modifyLore { - val loreList = loreAccordingToNbt.toMutableList() - for (arg in args) { - loreList.add(arg) - } - loreList - } +fun ItemStack.appendLore(args: List<Component>) { + if (args.isEmpty()) return + modifyLore { + val loreList = loreAccordingToNbt.toMutableList() + for (arg in args) { + loreList.add(arg) + } + loreList + } } -fun ItemStack.modifyLore(update: (List<Text>) -> List<Text>) { - val loreList = loreAccordingToNbt - loreAccordingToNbt = update(loreList) +fun ItemStack.modifyLore(update: (List<Component>) -> List<Component>) { + val loreList = loreAccordingToNbt + loreAccordingToNbt = update(loreList) +} + +fun loadItemFromNbt(nbt: CompoundTag, registries: HolderLookup.Provider = MC.defaultRegistries): ItemStack? { + return ItemStack.CODEC.decode(RegistryOps.create(NbtOps.INSTANCE, registries), nbt).result().getOrNull()?.first } diff --git a/src/main/kotlin/util/mc/MCTabListAPI.kt b/src/main/kotlin/util/mc/MCTabListAPI.kt new file mode 100644 index 0000000..56933d9 --- /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.components.PlayerTabOverlay +import net.minecraft.nbt.NbtOps +import net.minecraft.world.scores.PlayerTeam +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.ComponentSerialization +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 PlayerTabOverlay.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(Component.literal(ComponentSerialization.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<Component>, + val footer: Optional<Component>, + val body: List<Component>, + ) { + companion object { + val CODEC: Codec<CurrentTabList> = RecordCodecBuilder.create { + it.group( + ComponentSerialization.CODEC.optionalFieldOf("header").forGetter(CurrentTabList::header), + ComponentSerialization.CODEC.optionalFieldOf("footer").forGetter(CurrentTabList::footer), + ComponentSerialization.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.tabList.cast() + val entries = hud.collectPlayerEntries_firmament() + .map { + it.tabListDisplayName ?: run { + val team = it.team + val name = it.profile.name + PlayerTeam.formatNameForTeam(team, Component.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 0c49862..55bfac3 100644 --- a/src/main/kotlin/util/mc/NbtItemData.kt +++ b/src/main/kotlin/util/mc/NbtItemData.kt @@ -1,22 +1,22 @@ package moe.nea.firmament.util.mc -import net.minecraft.component.DataComponentTypes -import net.minecraft.component.type.LoreComponent -import net.minecraft.item.ItemStack -import net.minecraft.text.Text +import net.minecraft.core.component.DataComponents +import net.minecraft.world.item.component.ItemLore +import net.minecraft.world.item.ItemStack +import net.minecraft.network.chat.Component -var ItemStack.loreAccordingToNbt: List<Text> - get() = get(DataComponentTypes.LORE)?.lines ?: listOf() +var ItemStack.loreAccordingToNbt: List<Component> + get() = get(DataComponents.LORE)?.lines ?: listOf() set(value) { - set(DataComponentTypes.LORE, LoreComponent(value)) + set(DataComponents.LORE, ItemLore(value)) } -var ItemStack.displayNameAccordingToNbt: Text - get() = get(DataComponentTypes.CUSTOM_NAME) ?: get(DataComponentTypes.ITEM_NAME) ?: item.name +var ItemStack.displayNameAccordingToNbt: Component + get() = get(DataComponents.CUSTOM_NAME) ?: get(DataComponents.ITEM_NAME) ?: item.name set(value) { - set(DataComponentTypes.CUSTOM_NAME, value) + set(DataComponents.CUSTOM_NAME, value) } -fun ItemStack.setCustomName(text: Text) { - set(DataComponentTypes.CUSTOM_NAME, text) +fun ItemStack.setCustomName(text: Component) { + set(DataComponents.CUSTOM_NAME, text) } diff --git a/src/main/kotlin/util/mc/NbtPrism.kt b/src/main/kotlin/util/mc/NbtPrism.kt new file mode 100644 index 0000000..6ac7cb2 --- /dev/null +++ b/src/main/kotlin/util/mc/NbtPrism.kt @@ -0,0 +1,85 @@ +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.serialization.JsonOps +import kotlin.jvm.optionals.getOrNull +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.Tag +import net.minecraft.nbt.ListTag +import net.minecraft.nbt.NbtOps +import net.minecraft.nbt.StringTag +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: Tag): Collection<Tag> { + var rootSet = mutableListOf(root) + var switch = mutableListOf<Tag>() + 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(StringTag.valueOf(Base64Util.decodeString(string))) + } + } + } + for (element in rootSet) { + if (element is ListTag) { + if (pathSegment == "*") + switch.addAll(element) + val index = pathSegment.toIntOrNull() ?: continue + if (index !in element.indices) continue + switch.add(element[index]) + } + if (element is CompoundTag) { + if (pathSegment == "*") + element.keySet().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..cfd4184 --- /dev/null +++ b/src/main/kotlin/util/mc/NbtUtil.kt @@ -0,0 +1,15 @@ +package moe.nea.firmament.util.mc + +import net.minecraft.world.item.component.CustomData +import net.minecraft.nbt.Tag +import net.minecraft.nbt.ListTag +import moe.nea.firmament.mixins.accessor.AccessorNbtComponent + +fun Iterable<Tag>.toNbtList() = ListTag().also { + for (element in this) { + it.add(element) + } +} + +@Suppress("CAST_NEVER_SUCCEEDS") +val CustomData.unsafeNbt get() = (this as AccessorNbtComponent).unsafeNbt_firmament diff --git a/src/main/kotlin/util/mc/PlayerUtil.kt b/src/main/kotlin/util/mc/PlayerUtil.kt new file mode 100644 index 0000000..7c21987 --- /dev/null +++ b/src/main/kotlin/util/mc/PlayerUtil.kt @@ -0,0 +1,7 @@ +package moe.nea.firmament.util.mc + +import net.minecraft.world.entity.EquipmentSlot +import net.minecraft.world.entity.player.Player + + +val Player.mainHandStack get() = this.getItemBySlot(EquipmentSlot.MAINHAND) diff --git a/src/main/kotlin/util/mc/Rectangle.kt b/src/main/kotlin/util/mc/Rectangle.kt new file mode 100644 index 0000000..6495c29 --- /dev/null +++ b/src/main/kotlin/util/mc/Rectangle.kt @@ -0,0 +1,11 @@ +package moe.nea.firmament.util.mc + +import me.shedaniel.math.Rectangle +import net.minecraft.client.gui.navigation.ScreenAxis +import net.minecraft.client.gui.navigation.ScreenRectangle + +fun Rectangle.asScreenRectangle() = + ScreenRectangle.of( + ScreenAxis.HORIZONTAL, + x, y, width, height + ) diff --git a/src/main/kotlin/util/mc/SNbtFormatter.kt b/src/main/kotlin/util/mc/SNbtFormatter.kt index e773927..0e630eb 100644 --- a/src/main/kotlin/util/mc/SNbtFormatter.kt +++ b/src/main/kotlin/util/mc/SNbtFormatter.kt @@ -1,22 +1,23 @@ package moe.nea.firmament.util.mc -import net.minecraft.nbt.NbtByte -import net.minecraft.nbt.NbtByteArray -import net.minecraft.nbt.NbtCompound -import net.minecraft.nbt.NbtDouble -import net.minecraft.nbt.NbtElement -import net.minecraft.nbt.NbtEnd -import net.minecraft.nbt.NbtFloat -import net.minecraft.nbt.NbtInt -import net.minecraft.nbt.NbtIntArray -import net.minecraft.nbt.NbtList -import net.minecraft.nbt.NbtLong -import net.minecraft.nbt.NbtLongArray -import net.minecraft.nbt.NbtShort -import net.minecraft.nbt.NbtString -import net.minecraft.nbt.visitor.NbtElementVisitor - -class SNbtFormatter private constructor() : NbtElementVisitor { +import net.minecraft.nbt.CollectionTag +import net.minecraft.nbt.ByteTag +import net.minecraft.nbt.ByteArrayTag +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.DoubleTag +import net.minecraft.nbt.Tag +import net.minecraft.nbt.EndTag +import net.minecraft.nbt.FloatTag +import net.minecraft.nbt.IntTag +import net.minecraft.nbt.IntArrayTag +import net.minecraft.nbt.ListTag +import net.minecraft.nbt.LongTag +import net.minecraft.nbt.LongArrayTag +import net.minecraft.nbt.ShortTag +import net.minecraft.nbt.StringTag +import net.minecraft.nbt.TagVisitor + +class SNbtFormatter private constructor() : TagVisitor { private val result = StringBuilder() private var indent = 0 private fun writeIndent() { @@ -31,52 +32,52 @@ class SNbtFormatter private constructor() : NbtElementVisitor { indent-- } - fun apply(element: NbtElement): StringBuilder { + fun apply(element: Tag): StringBuilder { element.accept(this) return result } - override fun visitString(element: NbtString) { - result.append(NbtString.escape(element.asString())) + override fun visitString(element: StringTag) { + result.append(StringTag.quoteAndEscape(element.value)) } - override fun visitByte(element: NbtByte) { - result.append(element.numberValue()).append("b") + override fun visitByte(element: ByteTag) { + result.append(element.box()).append("b") } - override fun visitShort(element: NbtShort) { + override fun visitShort(element: ShortTag) { result.append(element.shortValue()).append("s") } - override fun visitInt(element: NbtInt) { + override fun visitInt(element: IntTag) { result.append(element.intValue()) } - override fun visitLong(element: NbtLong) { + override fun visitLong(element: LongTag) { result.append(element.longValue()).append("L") } - override fun visitFloat(element: NbtFloat) { + override fun visitFloat(element: FloatTag) { result.append(element.floatValue()).append("f") } - override fun visitDouble(element: NbtDouble) { + override fun visitDouble(element: DoubleTag) { result.append(element.doubleValue()).append("d") } - private fun visitArrayContents(array: List<NbtElement>) { + private fun visitArrayContents(array: CollectionTag) { 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: CollectionTag) { result.append("[").append(arrayTypeTag).append("\n") pushIndent() visitArrayContents(array) @@ -86,30 +87,30 @@ class SNbtFormatter private constructor() : NbtElementVisitor { } - override fun visitByteArray(element: NbtByteArray) { + override fun visitByteArray(element: ByteArrayTag) { writeArray("B;", element) } - override fun visitIntArray(element: NbtIntArray) { + override fun visitIntArray(element: IntArrayTag) { writeArray("I;", element) } - override fun visitLongArray(element: NbtLongArray) { + override fun visitLongArray(element: LongArrayTag) { writeArray("L;", element) } - override fun visitList(element: NbtList) { + override fun visitList(element: ListTag) { writeArray("", element) } - override fun visitCompound(compound: NbtCompound) { + override fun visitCompound(compound: CompoundTag) { result.append("{\n") pushIndent() - val keys = compound.keys.sorted() + val keys = compound.keySet().sorted() 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) { @@ -122,17 +123,20 @@ class SNbtFormatter private constructor() : NbtElementVisitor { result.append("}") } - override fun visitEnd(element: NbtEnd) { + override fun visitEnd(element: EndTag) { result.append("END") } companion object { - fun prettify(nbt: NbtElement): String { + fun prettify(nbt: Tag): String { return SNbtFormatter().apply(nbt).toString() } - fun NbtElement.toPrettyString() = prettify(this) + fun Tag.toPrettyString() = prettify(this) - private val SIMPLE_NAME = "[A-Za-z0-9._+-]+".toRegex() + fun escapeName(key: String): String = + if (key.matches(SIMPLE_NAME)) key else StringTag.quoteAndEscape(key) + + val SIMPLE_NAME = "[A-Za-z0-9._+-]+".toRegex() } } diff --git a/src/main/kotlin/util/mc/ScreenUtil.kt b/src/main/kotlin/util/mc/ScreenUtil.kt index 36feb6b..4e3dbf1 100644 --- a/src/main/kotlin/util/mc/ScreenUtil.kt +++ b/src/main/kotlin/util/mc/ScreenUtil.kt @@ -1,9 +1,9 @@ package moe.nea.firmament.util.mc -import net.minecraft.client.gui.screen.Screen -import net.minecraft.client.gui.screen.ingame.HandledScreen -import net.minecraft.entity.player.PlayerInventory -import net.minecraft.screen.slot.Slot +import net.minecraft.client.gui.screens.Screen +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.inventory.Slot object ScreenUtil { private var lastScreen: Screen? = null @@ -12,15 +12,15 @@ object ScreenUtil { data class SlotIndex(val index: Int, val isPlayerInventory: Boolean) fun Screen.getSlotsByIndex(): Map<SlotIndex, Slot> { - if (this !is HandledScreen<*>) return mapOf() + if (this !is AbstractContainerScreen<*>) return mapOf() if (lastScreen === this) return slotsByIndex lastScreen = this - slotsByIndex = this.screenHandler.slots.associate { - SlotIndex(it.index, it.inventory is PlayerInventory) to it + slotsByIndex = this.menu.slots.associate { + SlotIndex(it.containerSlot, it.container is Inventory) to it } return slotsByIndex } - fun Screen.getSlotByIndex( index: Int, isPlayerInventory: Boolean): Slot? = + fun Screen.getSlotByIndex(index: Int, isPlayerInventory: Boolean): Slot? = getSlotsByIndex()[SlotIndex(index, isPlayerInventory)] } diff --git a/src/main/kotlin/util/mc/SkullItemData.kt b/src/main/kotlin/util/mc/SkullItemData.kt index 0405b65..80028af 100644 --- a/src/main/kotlin/util/mc/SkullItemData.kt +++ b/src/main/kotlin/util/mc/SkullItemData.kt @@ -2,19 +2,20 @@ package moe.nea.firmament.util.mc +import com.google.common.collect.Multimap +import com.google.common.collect.Multimaps import com.mojang.authlib.GameProfile import com.mojang.authlib.minecraft.MinecraftProfileTexture import com.mojang.authlib.properties.Property +import com.mojang.authlib.properties.PropertyMap +import java.time.Instant import java.util.UUID -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 -import net.minecraft.item.Items +import net.minecraft.core.component.DataComponents +import net.minecraft.world.item.component.ResolvableProfile +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items import moe.nea.firmament.Firmament import moe.nea.firmament.util.Base64Util.padToValidBase64 import moe.nea.firmament.util.assertTrueOr @@ -23,66 +24,75 @@ import moe.nea.firmament.util.json.InstantAsLongSerializer @Serializable data class MinecraftProfileTextureKt( - val url: String, - val metadata: Map<String, String> = mapOf(), + val url: String, + val metadata: Map<String, String> = mapOf(), ) @Serializable data class MinecraftTexturesPayloadKt( - val textures: Map<MinecraftProfileTexture.Type, MinecraftProfileTextureKt> = mapOf(), - val profileId: UUID? = null, - val profileName: String? = null, - val isPublic: Boolean = true, - val timestamp: Instant = Clock.System.now(), + val textures: Map<MinecraftProfileTexture.Type, MinecraftProfileTextureKt> = mapOf(), + val profileId: UUID? = null, + val profileName: String? = null, + val isPublic: Boolean = true, + val timestamp: Instant = Instant.now(), ) -fun GameProfile.setTextures(textures: MinecraftTexturesPayloadKt) { - val json = Firmament.json.encodeToString(textures) - val encoded = java.util.Base64.getEncoder().encodeToString(json.encodeToByteArray()) - properties.put(propertyTextures, Property(propertyTextures, encoded)) +fun createSkullTextures(textures: MinecraftTexturesPayloadKt): PropertyMap { + val json = Firmament.json.encodeToString(textures) + val encoded = java.util.Base64.getEncoder().encodeToString(json.encodeToByteArray()) + return PropertyMap( + Multimaps.forMap(mapOf(propertyTextures to Property(propertyTextures, encoded))) + ) } private val propertyTextures = "textures" fun ItemStack.setEncodedSkullOwner(uuid: UUID, encodedData: String) { - assert(this.item == Items.PLAYER_HEAD) - val gameProfile = GameProfile(uuid, "LameGuy123") - gameProfile.properties.put(propertyTextures, Property(propertyTextures, encodedData.padToValidBase64())) - this.set(DataComponentTypes.PROFILE, ProfileComponent(gameProfile)) + assert(this.item == Items.PLAYER_HEAD) + val gameProfile = GameProfile( + uuid, "LameGuy123", + PropertyMap( + Multimaps.forMap( + mapOf(propertyTextures to Property(propertyTextures, encodedData.padToValidBase64())) + ) + ) + ) + this.set(DataComponents.PROFILE, ResolvableProfile.createResolved(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) } + .also { it.setSkullOwner(uuid, url) } fun ItemStack.setSkullOwner(uuid: UUID, url: String) { - assert(this.item == Items.PLAYER_HEAD) - val gameProfile = GameProfile(uuid, "nea89") - gameProfile.setTextures( - MinecraftTexturesPayloadKt( - textures = mapOf(MinecraftProfileTexture.Type.SKIN to MinecraftProfileTextureKt(url)), - profileId = uuid, - profileName = "nea89", - ) - ) - this.set(DataComponentTypes.PROFILE, ProfileComponent(gameProfile)) + assert(this.item == Items.PLAYER_HEAD) + val gameProfile = GameProfile( + uuid, "nea89", createSkullTextures( + MinecraftTexturesPayloadKt( + textures = mapOf(MinecraftProfileTexture.Type.SKIN to MinecraftProfileTextureKt(url)), + profileId = uuid, + profileName = "nea89", + ) + ) + ) + this.set(DataComponents.PROFILE, ResolvableProfile.createResolved(gameProfile)) } fun decodeProfileTextureProperty(property: Property): MinecraftTexturesPayloadKt? { - assertTrueOr(property.name == propertyTextures) { return null } - return try { - var encodedF: String = property.value - while (encodedF.length % 4 != 0 && encodedF.last() == '=') { - encodedF = encodedF.substring(0, encodedF.length - 1) - } - val json = java.util.Base64.getDecoder().decode(encodedF).decodeToString() - Firmament.json.decodeFromString<MinecraftTexturesPayloadKt>(json) - } catch (e: Exception) { - // Malformed profile data - if (Firmament.DEBUG) - e.printStackTrace() - null - } + assertTrueOr(property.name == propertyTextures) { return null } + return try { + var encodedF: String = property.value + while (encodedF.length % 4 != 0 && encodedF.last() == '=') { + encodedF = encodedF.substring(0, encodedF.length - 1) + } + val json = java.util.Base64.getDecoder().decode(encodedF).decodeToString() + Firmament.json.decodeFromString<MinecraftTexturesPayloadKt>(json) + } catch (e: Exception) { + // Malformed profile data + if (Firmament.DEBUG) + e.printStackTrace() + null + } } diff --git a/src/main/kotlin/util/mc/SlotUtils.kt b/src/main/kotlin/util/mc/SlotUtils.kt index 4709dcf..2f5fd49 100644 --- a/src/main/kotlin/util/mc/SlotUtils.kt +++ b/src/main/kotlin/util/mc/SlotUtils.kt @@ -1,34 +1,46 @@ package moe.nea.firmament.util.mc -import net.minecraft.screen.ScreenHandler -import net.minecraft.screen.slot.Slot -import net.minecraft.screen.slot.SlotActionType +import org.lwjgl.glfw.GLFW +import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.world.inventory.Slot +import net.minecraft.world.inventory.ClickType import moe.nea.firmament.util.MC object SlotUtils { - fun Slot.clickMiddleMouseButton(handler: ScreenHandler) { - MC.interactionManager?.clickSlot( - handler.syncId, - this.id, - 2, - SlotActionType.CLONE, + fun Slot.clickMiddleMouseButton(handler: AbstractContainerMenu) { + MC.interactionManager?.handleInventoryMouseClick( + handler.containerId, + this.index, + GLFW.GLFW_MOUSE_BUTTON_MIDDLE, + ClickType.CLONE, MC.player ) } - fun Slot.swapWithHotBar(handler: ScreenHandler, hotbarIndex: Int) { - MC.interactionManager?.clickSlot( - handler.syncId, this.id, - hotbarIndex, SlotActionType.SWAP, - MC.player) + fun Slot.swapWithHotBar(handler: AbstractContainerMenu, hotbarIndex: Int) { + MC.interactionManager?.handleInventoryMouseClick( + handler.containerId, this.index, + hotbarIndex, ClickType.SWAP, + MC.player + ) + } + + fun Slot.clickRightMouseButton(handler: AbstractContainerMenu) { + MC.interactionManager?.handleInventoryMouseClick( + handler.containerId, + this.index, + GLFW.GLFW_MOUSE_BUTTON_RIGHT, + ClickType.PICKUP, + MC.player + ) } - fun Slot.clickRightMouseButton(handler: ScreenHandler) { - MC.interactionManager?.clickSlot( - handler.syncId, - this.id, - 1, - SlotActionType.PICKUP, + fun Slot.clickLeftMouseButton(handler: AbstractContainerMenu) { + MC.interactionManager?.handleInventoryMouseClick( + handler.containerId, + this.index, + GLFW.GLFW_MOUSE_BUTTON_LEFT, + ClickType.PICKUP, MC.player ) } diff --git a/src/main/kotlin/util/mc/TolerantRegistriesOps.kt b/src/main/kotlin/util/mc/TolerantRegistriesOps.kt index ce596a0..833aca9 100644 --- a/src/main/kotlin/util/mc/TolerantRegistriesOps.kt +++ b/src/main/kotlin/util/mc/TolerantRegistriesOps.kt @@ -2,27 +2,27 @@ 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 +import net.minecraft.core.Registry +import net.minecraft.resources.ResourceKey +import net.minecraft.resources.RegistryOps +import net.minecraft.core.HolderLookup +import net.minecraft.core.HolderOwner class TolerantRegistriesOps<T>( delegate: DynamicOps<T>, - registryInfoGetter: RegistryInfoGetter + registryInfoGetter: RegistryInfoLookup ) : RegistryOps<T>(delegate, registryInfoGetter) { - constructor(delegate: DynamicOps<T>, registry: RegistryWrapper.WrapperLookup) : - this(delegate, CachedRegistryInfoGetter(registry)) + constructor(delegate: DynamicOps<T>, registry: HolderLookup.Provider) : + this(delegate, HolderLookupAdapter(registry)) - class TolerantOwner<E> : RegistryEntryOwner<E> { - override fun ownerEquals(other: RegistryEntryOwner<E>?): Boolean { + class TolerantOwner<E> : HolderOwner<E> { + override fun canSerializeIn(other: HolderOwner<E>?): Boolean { return true } } - override fun <E : Any?> getOwner(registryRef: RegistryKey<out Registry<out E>>?): Optional<RegistryEntryOwner<E>> { - return super.getOwner(registryRef).map { + override fun <E : Any?> owner(registryRef: ResourceKey<out Registry<out E>>?): Optional<HolderOwner<E>> { + return super.owner(registryRef).map { TolerantOwner() } } diff --git a/src/main/kotlin/util/mc/asFakeServer.kt b/src/main/kotlin/util/mc/asFakeServer.kt index d3811bd..1075d62 100644 --- a/src/main/kotlin/util/mc/asFakeServer.kt +++ b/src/main/kotlin/util/mc/asFakeServer.kt @@ -1,27 +1,27 @@ 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 +import net.minecraft.commands.CommandSource +import net.minecraft.commands.CommandSourceStack +import net.minecraft.network.chat.Component -fun FabricClientCommandSource.asFakeServer(): ServerCommandSource { +fun FabricClientCommandSource.asFakeServer(): CommandSourceStack { val source = this - return ServerCommandSource( - object : CommandOutput { - override fun sendMessage(message: Text?) { - source.player.sendMessage(message, false) + return CommandSourceStack( + object : CommandSource { + override fun sendSystemMessage(message: Component?) { + source.player.displayClientMessage(message, false) } - override fun shouldReceiveFeedback(): Boolean { + override fun acceptsSuccess(): Boolean { return true } - override fun shouldTrackOutput(): Boolean { + override fun acceptsFailure(): Boolean { return true } - override fun shouldBroadcastConsoleToOps(): Boolean { + override fun shouldInformAdmins(): Boolean { return true } }, @@ -30,7 +30,7 @@ fun FabricClientCommandSource.asFakeServer(): ServerCommandSource { null, 0, "FakeServerCommandSource", - Text.literal("FakeServerCommandSource"), + Component.literal("FakeServerCommandSource"), null, source.player ) |
