diff --git a/buildSrc/src/main/kotlin/core-convention.gradle.kts b/buildSrc/src/main/kotlin/core-convention.gradle.kts index 3c8730c3..72838850 100644 --- a/buildSrc/src/main/kotlin/core-convention.gradle.kts +++ b/buildSrc/src/main/kotlin/core-convention.gradle.kts @@ -61,14 +61,15 @@ configurations { } } -tasks.withType { - duplicatesStrategy = DuplicatesStrategy.EXCLUDE +tasks.withType().configureEach { + if (this !is com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } } tasks { shadowJar { mergeServiceFiles() - duplicatesStrategy = DuplicatesStrategy.EXCLUDE val relocationPrefix: String by project relocate("net.kyori.adventure.nbt", "$relocationPrefix.kyori.nbt") { @@ -78,8 +79,10 @@ tasks { } javadoc { + isFailOnError = false val options = options as StandardJavadocDocletOptions options.use() options.tags("apiNote:a:API Note:") + options.addStringOption("Xdoclint:none", "-quiet") } } diff --git a/gradle.properties b/gradle.properties index 7323dd41..45d9a9d4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,6 +7,6 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled javaVersion=25 mcVersion=26.2 group=dev.slne.surf.api -version=3.29.0 +version=3.30.0 relocationPrefix=dev.slne.surf.api.libs snapshot=false diff --git a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/bridges/V1_21_11SurfPaperNmsEntityBridgeImpl.kt b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/bridges/V1_21_11SurfPaperNmsEntityBridgeImpl.kt index 48e5c5eb..ccc7cfa2 100644 --- a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/bridges/V1_21_11SurfPaperNmsEntityBridgeImpl.kt +++ b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/bridges/V1_21_11SurfPaperNmsEntityBridgeImpl.kt @@ -1,5 +1,6 @@ package dev.slne.surf.api.paper.server.nms.v1_21_11.bridges +import ca.spottedleaf.moonrise.common.PlatformHooks import ca.spottedleaf.moonrise.common.util.TickThread import com.mojang.brigadier.exceptions.CommandSyntaxException import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException @@ -12,11 +13,30 @@ import dev.slne.surf.api.paper.util.chunkX import dev.slne.surf.api.paper.util.chunkZ import io.papermc.paper.math.FinePosition import net.kyori.adventure.nbt.CompoundBinaryTag +import net.kyori.adventure.text.logger.slf4j.ComponentLogger +import net.minecraft.SharedConstants +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.NbtAccounter +import net.minecraft.nbt.NbtIo +import net.minecraft.nbt.NbtUtils import net.minecraft.server.MinecraftServer import net.minecraft.server.commands.SummonCommand +import net.minecraft.util.ProblemReporter +import net.minecraft.util.datafix.fixes.References +import net.minecraft.world.entity.EntitySpawnReason +import net.minecraft.world.entity.Pose.CODEC +import net.minecraft.world.level.storage.TagValueInput +import net.minecraft.world.level.storage.TagValueOutput import org.bukkit.World import org.bukkit.entity.Entity import org.bukkit.entity.EntityType +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.util.* +import kotlin.jvm.optionals.getOrNull +import net.minecraft.world.entity.Entity as NmsEntity +import net.minecraft.world.entity.EntityType as NmsEntityType @NmsUseWithCaution class V1_21_11SurfPaperNmsEntityBridgeImpl : SurfPaperNmsEntityBridge { @@ -59,4 +79,126 @@ class V1_21_11SurfPaperNmsEntityBridgeImpl : SurfPaperNmsEntityBridge { override fun getById(world: World, id: Int): Entity? { return world.toNms().getEntity(id)?.bukkitEntity } + + override fun captureVehicleNbt(rootVehicle: Entity): ByteArray { + val nmsEntity = rootVehicle.toNms() + TickThread.ensureTickThread(nmsEntity, "Cannot capture vehicle NBT asynchronously") + + return ProblemReporter.ScopedCollector(VEHICLE_LOGGER).use { reporter -> + val output = TagValueOutput.createWithContext(reporter, nmsEntity.registryAccess()) + nmsEntity.save(output) + + val additions = output.child("surf-api-addtions") + nmsEntity.passengersAndSelf + .filter { it.type.canSerialize() } + .forEach { entity -> + val tag = additions.child(entity.uuid.toString()) + + tag.store("Pose", CODEC, entity.pose) + + val living = entity.asLivingEntity() + if (living != null) { + tag.putInt("ArrowCount", living.arrowCount) + tag.putInt("Stingers", living.stingerCount) + } + } + + serializeNbtToBytes(output.buildResult()) + } + } + + override fun restoreVehicle( + world: World, + nbt: ByteArray, + x: Double, + y: Double, + z: Double, + yaw: Float, + pitch: Float + ): Entity? { + val level = world.toNms() + TickThread.ensureTickThread(level, x, z, "Cannot restore vehicle asynchronously") + + var tag = deserializeNbtFromBytes(nbt) + val dataVersion = NbtUtils.getDataVersion(tag, 0) + tag = PlatformHooks.get().convertNBT( + References.ENTITY, + MinecraftServer.getServer().fixerUpper, + tag, + dataVersion, + SharedConstants.getCurrentVersion().dataVersion().version + ) + + val entity = ProblemReporter.ScopedCollector(VEHICLE_LOGGER).use { reporter -> + val input = TagValueInput.create(reporter, level.registryAccess(), tag) + val additions = input.child("surf-api-addtions").map { additionsInput -> + require(additionsInput is TagValueInput) + additionsInput.input.keySet() + .mapNotNull(fun(uuidStr: String): Pair Unit>? { + val uuid = runCatching { UUID.fromString(uuidStr) }.getOrNull() ?: return null + val addition = additionsInput.child(uuidStr).getOrNull() ?: return null + + val pose = addition.read("Pose", CODEC).getOrNull() + val arrowCount = addition.getInt("ArrowCount").getOrNull() + val stingerCount = addition.getInt("Stingers").getOrNull() + + return uuid to { entity -> + if (pose != null) entity.pose = pose + if (arrowCount != null) entity.asLivingEntity()?.arrowCount = arrowCount + if (stingerCount != null) entity.asLivingEntity()?.stingerCount = stingerCount + } + }) + .toMap() + }.getOrNull().orEmpty() + + NmsEntityType.loadEntityRecursive( + input, + level, + EntitySpawnReason.LOAD + ) { entity -> + additions[entity.uuid]?.invoke(entity) + entity + } + } ?: return null + + entity.snapTo(x, y, z, yaw, pitch) + + if (!level.tryAddFreshEntityWithPassengers(entity)) { + return null + } + + return entity.bukkitEntity + } + + private fun deserializeNbtFromBytes(data: ByteArray): CompoundTag { + val compound: CompoundTag + try { + compound = NbtIo.readCompressed(ByteArrayInputStream(data), NbtAccounter.unlimitedHeap()) + } catch (ex: IOException) { + throw RuntimeException(ex) + } + + return compound + } + + private fun serializeNbtToBytes(compound: CompoundTag): ByteArray { + val baos = ByteArrayOutputStream() + try { + NbtIo.writeCompressed(compound, baos) + } catch (ex: IOException) { + throw RuntimeException(ex) + } + + return baos.toByteArray() + } + + companion object { + private val VEHICLE_LOGGER = ComponentLogger.logger("SurfPaperNmsEntityBridge Vehicle") + } + + private data class VehicleTreeNbt( + val tag: CompoundTag, + val rootUuid: UUID, + val entityUuids: Set, + ) } diff --git a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/bridges/V1_21_11SurfPaperNmsPlayerBridgeImpl.kt b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/bridges/V1_21_11SurfPaperNmsPlayerBridgeImpl.kt index b453a647..b03a5c9a 100644 --- a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/bridges/V1_21_11SurfPaperNmsPlayerBridgeImpl.kt +++ b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v1-21-11/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v1_21_11/bridges/V1_21_11SurfPaperNmsPlayerBridgeImpl.kt @@ -14,6 +14,7 @@ import dev.slne.surf.api.paper.nms.common.dummy.DummyEntityEquipment import dev.slne.surf.api.paper.server.nms.v1_21_11.extensions.toNms import dev.slne.surf.api.paper.server.nms.v1_21_11.reflection.V1_21_11NmsReflections import io.papermc.paper.adventure.PaperAdventure +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -27,6 +28,7 @@ import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.NbtIo import net.minecraft.network.chat.* import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket +import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket import net.minecraft.server.MinecraftServer import net.minecraft.server.players.NameAndId import net.minecraft.util.ProblemReporter @@ -90,6 +92,53 @@ class V1_21_11SurfPaperNmsPlayerBridgeImpl : SurfPaperNmsPlayerBridge { } } + @Suppress("USELESS_ELVIS") + override fun resyncVehicleState(player: Player, swallowExceptions: Boolean): Int { + val nmsPlayer = player.toNms() + val connection = nmsPlayer.connection ?: return 0 + val chunkMap = nmsPlayer.level().chunkSource.chunkMap + + val root = nmsPlayer.rootVehicle + if (root === nmsPlayer && nmsPlayer.passengers.isEmpty()) { + return 0 + } + + val chain = ObjectLinkedOpenHashSet(root.passengersAndSelf.iterator()) + chain.addFirst(root) + + var resynced = 0 + for (entity in chain) { + if (entity === nmsPlayer) continue + val tracker = chunkMap.entityMap.get(entity.id) ?: continue + try { + tracker.seenBy.add(connection) + tracker.serverEntity.removePairing(nmsPlayer) + tracker.serverEntity.addPairing(nmsPlayer) + resynced++ + } catch (e: Throwable) { + if (!swallowExceptions) throw e + } + } + + for (entity in chain) { + if (entity.passengers.isEmpty()) continue + try { + connection.send(ClientboundSetPassengersPacket(entity)) + } catch (e: Throwable) { + if (!swallowExceptions) throw e + } + } + + return resynced + } + + @Suppress("USELESS_ELVIS") + override fun resyncPlayerState(player: Player) { + val nmsPlayer = player.toNms() + nmsPlayer.connection ?: return + nmsPlayer.onUpdateAbilities() + } + override fun getRemoteChatSessionData(player: Player): RemoteChatSessionData? { val session = player.toNms().chatSession?.asData() ?: return null val profilePublicKey = session.profilePublicKey() @@ -432,4 +481,4 @@ class V1_21_11SurfPaperNmsPlayerBridgeImpl : SurfPaperNmsPlayerBridge { private val OFFLINE_INVENTORY_EDIT_LOGGER = ComponentLogger.logger("OfflinePlayer Inventory Edit") private val CHAT_LOGGER = ComponentLogger.logger("SurfPaperNmsPlayerBridge Chat") } -} \ No newline at end of file +} diff --git a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/bridges/V26_1SurfPaperNmsEntityBridgeImpl.kt b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/bridges/V26_1SurfPaperNmsEntityBridgeImpl.kt index 53265d83..0d747717 100644 --- a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/bridges/V26_1SurfPaperNmsEntityBridgeImpl.kt +++ b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/bridges/V26_1SurfPaperNmsEntityBridgeImpl.kt @@ -1,5 +1,6 @@ package dev.slne.surf.api.paper.server.nms.v26_1.bridges +import ca.spottedleaf.moonrise.common.PlatformHooks import ca.spottedleaf.moonrise.common.util.TickThread import com.mojang.brigadier.exceptions.CommandSyntaxException import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException @@ -12,11 +13,30 @@ import dev.slne.surf.api.paper.util.chunkX import dev.slne.surf.api.paper.util.chunkZ import io.papermc.paper.math.FinePosition import net.kyori.adventure.nbt.CompoundBinaryTag +import net.kyori.adventure.text.logger.slf4j.ComponentLogger +import net.minecraft.SharedConstants +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.NbtAccounter +import net.minecraft.nbt.NbtIo +import net.minecraft.nbt.NbtUtils import net.minecraft.server.MinecraftServer import net.minecraft.server.commands.SummonCommand +import net.minecraft.util.ProblemReporter +import net.minecraft.util.datafix.fixes.References +import net.minecraft.world.entity.EntitySpawnReason +import net.minecraft.world.entity.Pose.CODEC +import net.minecraft.world.level.storage.TagValueInput +import net.minecraft.world.level.storage.TagValueOutput import org.bukkit.World import org.bukkit.entity.Entity import org.bukkit.entity.EntityType +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.util.* +import kotlin.jvm.optionals.getOrNull +import net.minecraft.world.entity.Entity as NmsEntity +import net.minecraft.world.entity.EntityType as NmsEntityType @NmsUseWithCaution @Suppress("ClassName") @@ -60,4 +80,116 @@ class V26_1SurfPaperNmsEntityBridgeImpl : SurfPaperNmsEntityBridge { override fun getById(world: World, id: Int): Entity? { return world.toNms().getEntity(id)?.bukkitEntity } + + override fun captureVehicleNbt(rootVehicle: Entity): ByteArray { + val nmsEntity = rootVehicle.toNms() + TickThread.ensureTickThread(nmsEntity, "Cannot capture vehicle NBT asynchronously") + + return ProblemReporter.ScopedCollector(VEHICLE_LOGGER).use { reporter -> + val output = TagValueOutput.createWithContext(reporter, nmsEntity.registryAccess()) + nmsEntity.save(output) + + val additions = output.child("surf-api-addtions") + nmsEntity.passengersAndSelf + .filter { it.type.canSerialize() } + .forEach { entity -> + val tag = additions.child(entity.uuid.toString()) + + tag.store("Pose", CODEC, entity.pose) + + val living = entity.asLivingEntity() + if (living != null) { + tag.putInt("ArrowCount", living.arrowCount) + tag.putInt("Stingers", living.stingerCount) + } + } + + serializeTagToBytes(output.buildResult()) + } + } + + override fun restoreVehicle( + world: World, + nbt: ByteArray, + x: Double, + y: Double, + z: Double, + yaw: Float, + pitch: Float + ): Entity? { + val level = world.toNms() + TickThread.ensureTickThread(level, x, z, "Cannot restore vehicle asynchronously") + + var tag = deserializeTagFromBytes(nbt) + val dataVersion = NbtUtils.getDataVersion(tag, 0) + tag = PlatformHooks.get().convertNBT( + References.ENTITY, + MinecraftServer.getServer().fixerUpper, + tag, + dataVersion, + SharedConstants.getCurrentVersion().dataVersion().version + ) + + val entity = ProblemReporter.ScopedCollector(VEHICLE_LOGGER).use { reporter -> + val input = TagValueInput.create(reporter, level.registryAccess(), tag) + val additions = input.child("surf-api-addtions").map { additionsInput -> + require(additionsInput is TagValueInput) + additionsInput.input.keySet() + .mapNotNull(fun(uuidStr: String): Pair Unit>? { + val uuid = runCatching { UUID.fromString(uuidStr) }.getOrNull() ?: return null + val addition = additionsInput.child(uuidStr).getOrNull() ?: return null + + val pose = addition.read("Pose", CODEC).getOrNull() + val arrowCount = addition.getInt("ArrowCount").getOrNull() + val stingerCount = addition.getInt("Stingers").getOrNull() + + return uuid to { entity -> + if (pose != null) entity.pose = pose + if (arrowCount != null) entity.asLivingEntity()?.arrowCount = arrowCount + if (stingerCount != null) entity.asLivingEntity()?.stingerCount = stingerCount + } + }) + .toMap() + }.getOrNull().orEmpty() + + NmsEntityType.loadEntityRecursive( + input, + level, + EntitySpawnReason.LOAD + ) { entity -> + additions[entity.uuid]?.invoke(entity) + entity + } + } ?: return null + + entity.snapTo(x, y, z, yaw, pitch) + + if (!level.tryAddFreshEntityWithPassengers(entity)) { + return null + } + + return entity.bukkitEntity + } + + private fun deserializeTagFromBytes(data: ByteArray): CompoundTag { + try { + return NbtIo.readCompressed(ByteArrayInputStream(data), NbtAccounter.unlimitedHeap()) + } catch (ex: IOException) { + throw RuntimeException(ex) + } + } + + private fun serializeTagToBytes(compound: CompoundTag): ByteArray { + try { + val baos = ByteArrayOutputStream() + NbtIo.writeCompressed(compound, baos) + return baos.toByteArray() + } catch (ex: IOException) { + throw RuntimeException(ex) + } + } + + companion object { + private val VEHICLE_LOGGER = ComponentLogger.logger("V26_1SurfPaperNmsEntityBridgeImpl Vehicle") + } } diff --git a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/bridges/V26_1SurfPaperNmsPlayerBridgeImpl.kt b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/bridges/V26_1SurfPaperNmsPlayerBridgeImpl.kt index 07a80832..7cea44eb 100644 --- a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/bridges/V26_1SurfPaperNmsPlayerBridgeImpl.kt +++ b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-1/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_1/bridges/V26_1SurfPaperNmsPlayerBridgeImpl.kt @@ -12,6 +12,7 @@ import dev.slne.surf.api.paper.nms.common.dummy.DummyEntityEquipment import dev.slne.surf.api.paper.server.nms.v26_1.extensions.toNms import dev.slne.surf.api.paper.server.nms.v26_1.reflection.V26_1NmsReflections import io.papermc.paper.adventure.PaperAdventure +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -26,6 +27,7 @@ import net.minecraft.nbt.NbtIo import net.minecraft.network.chat.* import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket +import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket import net.minecraft.server.MinecraftServer import net.minecraft.server.players.NameAndId import net.minecraft.util.ProblemReporter @@ -90,6 +92,53 @@ class V26_1SurfPaperNmsPlayerBridgeImpl : SurfPaperNmsPlayerBridge { } } + @Suppress("USELESS_ELVIS") + override fun resyncVehicleState(player: Player, swallowExceptions: Boolean): Int { + val nmsPlayer = player.toNms() + val connection = nmsPlayer.connection ?: return 0 + val chunkMap = nmsPlayer.level().chunkSource.chunkMap + + val root = nmsPlayer.rootVehicle + if (root === nmsPlayer && nmsPlayer.passengers.isEmpty()) { + return 0 + } + + val chain = ObjectLinkedOpenHashSet(root.passengersAndSelf.iterator()) + chain.addFirst(root) + + var resynced = 0 + for (entity in chain) { + if (entity === nmsPlayer) continue + val tracker = chunkMap.entityMap.get(entity.id) ?: continue + try { + tracker.seenBy.add(connection) + tracker.serverEntity.removePairing(nmsPlayer) + tracker.serverEntity.addPairing(nmsPlayer) + resynced++ + } catch (e: Throwable) { + if (!swallowExceptions) throw e + } + } + + for (entity in chain) { + if (entity.passengers.isEmpty()) continue + try { + connection.send(ClientboundSetPassengersPacket(entity)) + } catch (e: Throwable) { + if (!swallowExceptions) throw e + } + } + + return resynced + } + + @Suppress("USELESS_ELVIS") + override fun resyncPlayerState(player: Player) { + val nmsPlayer = player.toNms() + nmsPlayer.connection ?: return + nmsPlayer.onUpdateAbilities() + } + @Suppress("USELESS_ELVIS") override fun getRemoteChatSessionData(player: Player): RemoteChatSessionData? { val connection = player.toNms().connection ?: return null @@ -608,4 +657,4 @@ class V26_1SurfPaperNmsPlayerBridgeImpl : SurfPaperNmsPlayerBridge { private val OFFLINE_INVENTORY_EDIT_LOGGER = ComponentLogger.logger("OfflinePlayer Inventory Edit") private val CHAT_LOGGER = ComponentLogger.logger("SurfPaperNmsPlayerBridge Chat") } -} \ No newline at end of file +} diff --git a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-2/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_2/bridges/V26_2SurfPaperNmsEntityBridgeImpl.kt b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-2/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_2/bridges/V26_2SurfPaperNmsEntityBridgeImpl.kt index 6df0edc8..e5d12970 100644 --- a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-2/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_2/bridges/V26_2SurfPaperNmsEntityBridgeImpl.kt +++ b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-2/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_2/bridges/V26_2SurfPaperNmsEntityBridgeImpl.kt @@ -1,5 +1,6 @@ package dev.slne.surf.api.paper.server.nms.v26_2.bridges +import ca.spottedleaf.moonrise.common.PlatformHooks import ca.spottedleaf.moonrise.common.util.TickThread import com.mojang.brigadier.exceptions.CommandSyntaxException import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException @@ -11,12 +12,27 @@ import dev.slne.surf.api.paper.server.nms.v26_2.extensions.toNmsHolder import dev.slne.surf.api.paper.util.chunkX import dev.slne.surf.api.paper.util.chunkZ import io.papermc.paper.math.FinePosition +import io.papermc.paper.util.MCUtil import net.kyori.adventure.nbt.CompoundBinaryTag +import net.kyori.adventure.text.logger.slf4j.ComponentLogger +import net.minecraft.SharedConstants +import net.minecraft.nbt.NbtUtils import net.minecraft.server.MinecraftServer import net.minecraft.server.commands.SummonCommand +import net.minecraft.util.ProblemReporter +import net.minecraft.util.datafix.fixes.References +import net.minecraft.world.entity.EntitySpawnReason +import net.minecraft.world.entity.EntitySpawnRequest +import net.minecraft.world.entity.Pose.CODEC +import net.minecraft.world.level.storage.TagValueInput +import net.minecraft.world.level.storage.TagValueOutput import org.bukkit.World import org.bukkit.entity.Entity import org.bukkit.entity.EntityType +import java.util.* +import kotlin.jvm.optionals.getOrNull +import net.minecraft.world.entity.Entity as NmsEntity +import net.minecraft.world.entity.EntityType as NmsEntityType @NmsUseWithCaution @Suppress("ClassName") @@ -60,4 +76,98 @@ class V26_2SurfPaperNmsEntityBridgeImpl : SurfPaperNmsEntityBridge { override fun getById(world: World, id: Int): Entity? { return world.toNms().getEntity(id)?.bukkitEntity } + + override fun captureVehicleNbt(rootVehicle: Entity): ByteArray { + val nmsEntity = rootVehicle.toNms() + TickThread.ensureTickThread(nmsEntity, "Cannot capture vehicle NBT asynchronously") + + return ProblemReporter.ScopedCollector(VEHICLE_LOGGER).use { reporter -> + val output = TagValueOutput.createWithContext(reporter, nmsEntity.registryAccess()) + nmsEntity.save(output) + + val additions = output.child("surf-api-addtions") + nmsEntity.passengersAndSelf + .filter { it.type.canSerialize() } + .forEach { entity -> + val tag = additions.child(entity.uuid.toString()) + + tag.store("Pose", CODEC, entity.pose) + + val living = entity.asLivingEntity() + if (living != null) { + tag.putInt("ArrowCount", living.arrowCount) + tag.putInt("Stingers", living.stingerCount) + } + } + + MCUtil.serializeTagToBytes(output.buildResult()) + } + } + + override fun restoreVehicle( + world: World, + nbt: ByteArray, + x: Double, + y: Double, + z: Double, + yaw: Float, + pitch: Float + ): Entity? { + val level = world.toNms() + TickThread.ensureTickThread(level, x, z, "Cannot restore vehicle asynchronously") + + var tag = MCUtil.deserializeTagFromBytes(nbt) + val dataVersion = NbtUtils.getDataVersion(tag, 0) + tag = PlatformHooks.get().convertNBT( + References.ENTITY, + MinecraftServer.getServer().fixerUpper, + tag, + dataVersion, + SharedConstants.getCurrentVersion().dataVersion().version + ) + + val entity = ProblemReporter.ScopedCollector(VEHICLE_LOGGER).use { reporter -> + val input = TagValueInput.create(reporter, level.registryAccess(), tag) + val additions = input.child("surf-api-addtions").map { additionsInput -> + require(additionsInput is TagValueInput) + additionsInput.input.keySet() + .mapNotNull(fun(uuidStr: String): Pair Unit>? { + val uuid = runCatching { UUID.fromString(uuidStr) }.getOrNull() ?: return null + val addition = additionsInput.child(uuidStr).getOrNull() ?: return null + + val pose = addition.read("Pose", CODEC).getOrNull() + val arrowCount = addition.getInt("ArrowCount").getOrNull() + val stingerCount = addition.getInt("Stingers").getOrNull() + + return uuid to { entity -> + if (pose != null) entity.pose = pose + if (arrowCount != null) entity.asLivingEntity()?.arrowCount = arrowCount + if (stingerCount != null) entity.asLivingEntity()?.stingerCount = stingerCount + } + }) + .toMap() + }.getOrNull().orEmpty() + + NmsEntityType.loadEntityRecursive( + input, + level, + EntitySpawnRequest(EntitySpawnReason.LOAD, false) + ) { entity -> + additions[entity.uuid]?.invoke(entity) + entity + } + } ?: return null + + entity.snapTo(x, y, z, yaw, pitch) + + if (!level.tryAddFreshEntityWithPassengers(entity)) { + return null + } + + return entity.bukkitEntity + } + + companion object { + private val VEHICLE_LOGGER = ComponentLogger.logger("V26_2SurfPaperNmsEntityBridgeImpl Vehicle") + } } diff --git a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-2/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_2/bridges/V26_2SurfPaperNmsPlayerBridgeImpl.kt b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-2/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_2/bridges/V26_2SurfPaperNmsPlayerBridgeImpl.kt index 3f6d94e5..80a3731d 100644 --- a/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-2/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_2/bridges/V26_2SurfPaperNmsPlayerBridgeImpl.kt +++ b/surf-api-paper/surf-api-paper-nms/surf-api-paper-nms-v26-2/src/main/kotlin/dev/slne/surf/api/paper/server/nms/v26_2/bridges/V26_2SurfPaperNmsPlayerBridgeImpl.kt @@ -12,6 +12,7 @@ import dev.slne.surf.api.paper.nms.common.dummy.DummyEntityEquipment import dev.slne.surf.api.paper.server.nms.v26_2.extensions.toNms import dev.slne.surf.api.paper.server.nms.v26_2.reflection.V26_2NmsReflections import io.papermc.paper.adventure.PaperAdventure +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -26,6 +27,7 @@ import net.minecraft.nbt.NbtIo import net.minecraft.network.chat.* import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket +import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket import net.minecraft.server.MinecraftServer import net.minecraft.server.players.NameAndId import net.minecraft.util.ProblemReporter @@ -90,6 +92,53 @@ class V26_2SurfPaperNmsPlayerBridgeImpl : SurfPaperNmsPlayerBridge { } } + @Suppress("USELESS_ELVIS") + override fun resyncVehicleState(player: Player, swallowExceptions: Boolean): Int { + val nmsPlayer = player.toNms() + val connection = nmsPlayer.connection ?: return 0 + val chunkMap = nmsPlayer.level().chunkSource.chunkMap + + val root = nmsPlayer.rootVehicle + if (root === nmsPlayer && nmsPlayer.passengers.isEmpty()) { + return 0 + } + + val chain = ObjectLinkedOpenHashSet(root.passengersAndSelf.iterator()) + chain.addFirst(root) + + var resynced = 0 + for (entity in chain) { + if (entity === nmsPlayer) continue + val tracker = chunkMap.entityMap.get(entity.id) ?: continue + try { + tracker.seenBy.add(connection) + tracker.serverEntity.removePairing(nmsPlayer) + tracker.serverEntity.addPairing(nmsPlayer) + resynced++ + } catch (e: Throwable) { + if (!swallowExceptions) throw e + } + } + + for (entity in chain) { + if (entity.passengers.isEmpty()) continue + try { + connection.send(ClientboundSetPassengersPacket(entity)) + } catch (e: Throwable) { + if (!swallowExceptions) throw e + } + } + + return resynced + } + + @Suppress("USELESS_ELVIS") + override fun resyncPlayerState(player: Player) { + val nmsPlayer = player.toNms() + nmsPlayer.connection ?: return + nmsPlayer.onUpdateAbilities() + } + @Suppress("USELESS_ELVIS") override fun getRemoteChatSessionData(player: Player): RemoteChatSessionData? { val connection = player.toNms().connection ?: return null @@ -608,4 +657,4 @@ class V26_2SurfPaperNmsPlayerBridgeImpl : SurfPaperNmsPlayerBridge { private val OFFLINE_INVENTORY_EDIT_LOGGER = ComponentLogger.logger("OfflinePlayer Inventory Edit") private val CHAT_LOGGER = ComponentLogger.logger("SurfPaperNmsPlayerBridge Chat") } -} \ No newline at end of file +} diff --git a/surf-api-paper/surf-api-paper/api/surf-api-paper.api b/surf-api-paper/surf-api-paper/api/surf-api-paper.api index ab6040f3..b4ee7f2e 100644 --- a/surf-api-paper/surf-api-paper/api/surf-api-paper.api +++ b/surf-api-paper/surf-api-paper/api/surf-api-paper.api @@ -1671,15 +1671,19 @@ public final class dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsCommonBridge$ public abstract interface class dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsEntityBridge { public static final field Companion Ldev/slne/surf/api/paper/nms/bridges/SurfPaperNmsEntityBridge$Companion; + public abstract fun captureVehicleNbt (Lorg/bukkit/entity/Entity;)[B public abstract fun createEntityByNbt (Lorg/bukkit/World;Lorg/bukkit/entity/EntityType;Lio/papermc/paper/math/FinePosition;Lnet/kyori/adventure/nbt/CompoundBinaryTag;)V public abstract fun getById (Lorg/bukkit/World;I)Lorg/bukkit/entity/Entity; + public abstract fun restoreVehicle (Lorg/bukkit/World;[BDDDFF)Lorg/bukkit/entity/Entity; public abstract fun setId (Lorg/bukkit/entity/Entity;I)V } public final class dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsEntityBridge$Companion : dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsEntityBridge { + public fun captureVehicleNbt (Lorg/bukkit/entity/Entity;)[B public fun createEntityByNbt (Lorg/bukkit/World;Lorg/bukkit/entity/EntityType;Lio/papermc/paper/math/FinePosition;Lnet/kyori/adventure/nbt/CompoundBinaryTag;)V public fun getById (Lorg/bukkit/World;I)Lorg/bukkit/entity/Entity; public final fun getINSTANCE ()Ldev/slne/surf/api/paper/nms/bridges/SurfPaperNmsEntityBridge; + public fun restoreVehicle (Lorg/bukkit/World;[BDDDFF)Lorg/bukkit/entity/Entity; public fun setId (Lorg/bukkit/entity/Entity;I)V } @@ -1747,6 +1751,9 @@ public abstract interface class dev/slne/surf/api/paper/nms/bridges/SurfPaperNms public abstract fun removeAllTrackedPlayers (Lorg/bukkit/entity/Player;Z)V public static synthetic fun removeAllTrackedPlayers$default (Ldev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge;Lorg/bukkit/entity/Player;ZILjava/lang/Object;)V public abstract fun resetPlayerChatState (Lorg/bukkit/entity/Player;Ldev/slne/surf/api/paper/nms/bridges/data/chat/RemoteChatSessionData;)V + public abstract fun resyncPlayerState (Lorg/bukkit/entity/Player;)V + public abstract fun resyncVehicleState (Lorg/bukkit/entity/Player;Z)I + public static synthetic fun resyncVehicleState$default (Ldev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge;Lorg/bukkit/entity/Player;ZILjava/lang/Object;)I public abstract fun runOnChatMessageChain (Lorg/bukkit/entity/Player;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function1;)V public abstract fun sendPlayerChatMessage (Lorg/bukkit/entity/Player;Lnet/kyori/adventure/chat/SignedMessage;Lnet/kyori/adventure/chat/ChatType$Bound;)V public abstract fun sendSignedMessageWithChangedContent (Lorg/bukkit/entity/Player;Lnet/kyori/adventure/chat/SignedMessage;Lnet/kyori/adventure/chat/ChatType$Bound;Lnet/kyori/adventure/text/Component;)V @@ -1768,6 +1775,8 @@ public final class dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge$ public fun removeAllTrackedEntities (Lorg/bukkit/entity/Player;Z)V public fun removeAllTrackedPlayers (Lorg/bukkit/entity/Player;Z)V public fun resetPlayerChatState (Lorg/bukkit/entity/Player;Ldev/slne/surf/api/paper/nms/bridges/data/chat/RemoteChatSessionData;)V + public fun resyncPlayerState (Lorg/bukkit/entity/Player;)V + public fun resyncVehicleState (Lorg/bukkit/entity/Player;Z)I public fun runOnChatMessageChain (Lorg/bukkit/entity/Player;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function1;)V public fun sendPlayerChatMessage (Lorg/bukkit/entity/Player;Lnet/kyori/adventure/chat/SignedMessage;Lnet/kyori/adventure/chat/ChatType$Bound;)V public fun sendSignedMessageWithChangedContent (Lorg/bukkit/entity/Player;Lnet/kyori/adventure/chat/SignedMessage;Lnet/kyori/adventure/chat/ChatType$Bound;Lnet/kyori/adventure/text/Component;)V @@ -1778,6 +1787,7 @@ public final class dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge$ public static synthetic fun createPlayerChatMessageMirrorFromAdventure$default (Ldev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge;Lnet/kyori/adventure/chat/SignedMessage;Lnet/kyori/adventure/text/Component;ILjava/lang/Object;)Ldev/slne/surf/api/paper/nms/bridges/data/chat/PlayerChatMessageMirror; public static synthetic fun removeAllTrackedEntities$default (Ldev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge;Lorg/bukkit/entity/Player;ZILjava/lang/Object;)V public static synthetic fun removeAllTrackedPlayers$default (Ldev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge;Lorg/bukkit/entity/Player;ZILjava/lang/Object;)V + public static synthetic fun resyncVehicleState$default (Ldev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge;Lorg/bukkit/entity/Player;ZILjava/lang/Object;)I } public final class dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge$PlayerInventoryEdit { diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsEntityBridge.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsEntityBridge.kt index 03552949..5f98092e 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsEntityBridge.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsEntityBridge.kt @@ -21,10 +21,22 @@ interface SurfPaperNmsEntityBridge { fun getById(world: World, id: Int): Entity? + fun captureVehicleNbt(rootVehicle: Entity): ByteArray + + fun restoreVehicle( + world: World, + nbt: ByteArray, + x: Double, + y: Double, + z: Double, + yaw: Float, + pitch: Float, + ): Entity? + companion object : SurfPaperNmsEntityBridge by bridge { val INSTANCE get() = bridge } } @OptIn(NmsUseWithCaution::class) -private val bridge = requiredService() \ No newline at end of file +private val bridge = requiredService() diff --git a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge.kt b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge.kt index 7c92ade4..321f5429 100644 --- a/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge.kt +++ b/surf-api-paper/surf-api-paper/src/main/kotlin/dev/slne/surf/api/paper/nms/bridges/SurfPaperNmsPlayerBridge.kt @@ -25,6 +25,9 @@ interface SurfPaperNmsPlayerBridge { fun removeAllTrackedEntities(player: Player, swallowExceptions: Boolean = true) fun removeAllTrackedPlayers(player: Player, swallowExceptions: Boolean = true) + fun resyncVehicleState(player: Player, swallowExceptions: Boolean = true): Int + fun resyncPlayerState(player: Player) + fun getRemoteChatSessionData(player: Player): RemoteChatSessionData? fun createChatSessionSnapshot(player: Player): PlayerChatSessionSnapshot? @@ -109,4 +112,4 @@ interface SurfPaperNmsPlayerBridge { } @NmsUseWithCaution -private val playerBridge = requiredService() \ No newline at end of file +private val playerBridge = requiredService()