diff --git a/gradle.properties b/gradle.properties index 1a170ea02..c1f28a830 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,6 +7,6 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled javaVersion=25 mcVersion=1.21.11 group=dev.slne.surf -version=1.21.11-2.65.0 +version=1.21.11-2.66.0 relocationPrefix=dev.slne.surf.surfapi.libs snapshot=false diff --git a/surf-api-bukkit/surf-api-bukkit-api/api/surf-api-bukkit-api.api b/surf-api-bukkit/surf-api-bukkit-api/api/surf-api-bukkit-api.api index ac48d12bc..a48e023a9 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/api/surf-api-bukkit-api.api +++ b/surf-api-bukkit/surf-api-bukkit-api/api/surf-api-bukkit-api.api @@ -2155,8 +2155,10 @@ public abstract interface class dev/slne/surf/surfapi/bukkit/api/nms/listener/pa public abstract interface class dev/slne/surf/surfapi/bukkit/api/packet/SurfBukkitPacketApi { public static final field Companion Ldev/slne/surf/surfapi/bukkit/api/packet/SurfBukkitPacketApi$Companion; public static fun getInstance ()Ldev/slne/surf/surfapi/bukkit/api/packet/SurfBukkitPacketApi; - public abstract fun registerPacketLoreListener (Lorg/bukkit/NamespacedKey;Ldev/slne/surf/surfapi/bukkit/api/packet/lore/SurfBukkitPacketLoreHandler;)V + public fun registerPacketLoreListener (Lorg/bukkit/NamespacedKey;Ldev/slne/surf/surfapi/bukkit/api/packet/lore/SurfBukkitPacketLoreHandler;)V public fun registerPacketLoreListener (Lorg/bukkit/NamespacedKey;Ldev/slne/surf/surfapi/bukkit/api/packet/lore/SurfBukkitPacketLoreHandlerSimple;)V + public abstract fun registerPacketLoreListener (Lorg/bukkit/plugin/Plugin;Lorg/bukkit/NamespacedKey;Ldev/slne/surf/surfapi/bukkit/api/packet/lore/SurfBukkitPacketLoreHandler;)V + public fun registerPacketLoreListener (Lorg/bukkit/plugin/Plugin;Lorg/bukkit/NamespacedKey;Ldev/slne/surf/surfapi/bukkit/api/packet/lore/SurfBukkitPacketLoreHandlerSimple;)V public abstract fun registerPacketLoreListenerGlobal (Lorg/bukkit/plugin/Plugin;Ldev/slne/surf/surfapi/bukkit/api/packet/lore/SurfBukkitPacketLoreHandler;)V public fun registerPacketLoreListenerGlobal (Lorg/bukkit/plugin/Plugin;Ldev/slne/surf/surfapi/bukkit/api/packet/lore/SurfBukkitPacketLoreHandlerSimple;)V public abstract fun unregisterPacketLoreListener (Lorg/bukkit/NamespacedKey;)V @@ -2168,7 +2170,9 @@ public final class dev/slne/surf/surfapi/bukkit/api/packet/SurfBukkitPacketApi$C } public final class dev/slne/surf/surfapi/bukkit/api/packet/SurfBukkitPacketApi$DefaultImpls { + public static fun registerPacketLoreListener (Ldev/slne/surf/surfapi/bukkit/api/packet/SurfBukkitPacketApi;Lorg/bukkit/NamespacedKey;Ldev/slne/surf/surfapi/bukkit/api/packet/lore/SurfBukkitPacketLoreHandler;)V public static fun registerPacketLoreListener (Ldev/slne/surf/surfapi/bukkit/api/packet/SurfBukkitPacketApi;Lorg/bukkit/NamespacedKey;Ldev/slne/surf/surfapi/bukkit/api/packet/lore/SurfBukkitPacketLoreHandlerSimple;)V + public static fun registerPacketLoreListener (Ldev/slne/surf/surfapi/bukkit/api/packet/SurfBukkitPacketApi;Lorg/bukkit/plugin/Plugin;Lorg/bukkit/NamespacedKey;Ldev/slne/surf/surfapi/bukkit/api/packet/lore/SurfBukkitPacketLoreHandlerSimple;)V public static fun registerPacketLoreListenerGlobal (Ldev/slne/surf/surfapi/bukkit/api/packet/SurfBukkitPacketApi;Lorg/bukkit/plugin/Plugin;Ldev/slne/surf/surfapi/bukkit/api/packet/lore/SurfBukkitPacketLoreHandlerSimple;)V } diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/packet/SurfBukkitPacketApi.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/packet/SurfBukkitPacketApi.kt index d142341ab..549a8ec6a 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/packet/SurfBukkitPacketApi.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/packet/SurfBukkitPacketApi.kt @@ -2,6 +2,7 @@ package dev.slne.surf.surfapi.bukkit.api.packet import dev.slne.surf.surfapi.bukkit.api.packet.lore.SurfBukkitPacketLoreHandler import dev.slne.surf.surfapi.bukkit.api.packet.lore.SurfBukkitPacketLoreHandlerSimple +import dev.slne.surf.surfapi.bukkit.api.util.getCallingPlugin import dev.slne.surf.surfapi.core.api.util.requiredService import org.bukkit.NamespacedKey import org.bukkit.plugin.Plugin @@ -9,61 +10,105 @@ import org.bukkit.plugin.Plugin /** * The SurfBukkitPacketApi interface extends packet handling capabilities for Bukkit environments. * - * It provides methods for registering and unregistering lore listeners, enabling dynamic modification - * of item stack lore based on custom logic. It also includes global registration methods for listening - * to all items and utilities for managing these listeners efficiently. + * Provides methods for registering and unregistering lore listeners, enabling dynamic modification + * of item stack lore based on custom logic. Includes global registration for all items and utilities + * for managing these listeners efficiently. * - * This API allows developers to enhance item lore dynamically, providing a flexible system for plugins - * that need to alter or interact with lore without directly modifying the underlying item stack data. + * Prefer the overloads that accept an explicit [Plugin] parameter over the deprecated ones, + * as automatic caller-plugin detection via `getCallingPlugin` is unreliable across different + * call-site configurations. */ interface SurfBukkitPacketApi { /** - * Registers a listener for modifying the lore of a specific item stack identified by the given key. - * - * @param identifier A unique key representing the item to listen for. Must not be null. - * @param listener The listener that modifies the lore of the item stack. Must not be null. - * - * Example Usage: - * ``` - * val key = NamespacedKey("myplugin", "custom_item") - * surfBukkitPacketApi.registerPacketLoreListener(key, SurfBukkitPacketLoreHandler { lore, _, _ -> - * lore.add(Component.text("Special Lore!")) - * }) - * ``` + * Registers a listener for modifying the lore of a specific item stack identified by [identifier]. + * + * @param identifier A unique key representing the item to listen for. + * @param listener The listener that modifies the lore of the item stack. + * + * @deprecated Automatic plugin detection via `getCallingPlugin` is unreliable. Use + * [registerPacketLoreListener(Plugin, NamespacedKey, SurfBukkitPacketLoreHandler)] instead + * and pass your plugin instance explicitly. + */ + @Deprecated( + message = "Automatic plugin detection is unreliable. Pass your plugin instance explicitly.", + replaceWith = ReplaceWith("registerPacketLoreListener(plugin, identifier, listener)") + ) + fun registerPacketLoreListener( + identifier: NamespacedKey, + listener: SurfBukkitPacketLoreHandler + ) { + registerPacketLoreListener(getCallingPlugin(2), identifier, listener) + } + + /** + * Registers a listener for modifying the lore of a specific item stack identified by [identifier]. + * + * This is the preferred overload. The [plugin] reference is used to properly manage the + * listener lifecycle — all listeners registered under a plugin are automatically cleaned up + * when [unregisterPacketLoreListener(Plugin)] is called. + * + * @param plugin The plugin registering the listener. Used for lifecycle management. + * @param identifier A unique key representing the item to listen for. + * @param listener The listener that modifies the lore of the item stack. */ fun registerPacketLoreListener( + plugin: Plugin, identifier: NamespacedKey, listener: SurfBukkitPacketLoreHandler ) /** - * Registers a simplified packet lore listener for a specific item. + * Registers a simplified listener for modifying the lore of a specific item stack identified by [identifier]. * - * @param identifier A unique key representing the item to listen for. Must not be null. - * @param listener The simplified packet lore listener that focuses solely on modifying the lore list. + * Delegates to [registerPacketLoreListener(Plugin, NamespacedKey, SurfBukkitPacketLoreHandler)]. * - * This method delegates to the standard [registerPacketLoreListener] implementation. + * @param identifier A unique key representing the item to listen for. + * @param listener The simplified lore listener that focuses solely on modifying the lore list. + * + * @deprecated Automatic plugin detection is unreliable. Use + * [registerPacketLoreListener(Plugin, NamespacedKey, SurfBukkitPacketLoreHandlerSimple)] instead + * and pass your plugin instance explicitly. */ + @Deprecated( + message = "Automatic plugin detection is unreliable. Pass your plugin instance explicitly.", + replaceWith = ReplaceWith("registerPacketLoreListener(plugin, identifier, listener)") + ) fun registerPacketLoreListener( identifier: NamespacedKey, listener: SurfBukkitPacketLoreHandlerSimple ) { - registerPacketLoreListener(identifier, listener as SurfBukkitPacketLoreHandler) + registerPacketLoreListener(getCallingPlugin(2), identifier, listener) } /** - * Registers a packet lore listener globally to handle lore modifications for all items. + * Registers a simplified listener for modifying the lore of a specific item stack identified by [identifier]. * - * @param plugin The plugin registering the listener. Used to manage lifecycle and cleanup. - * @param listener The lore listener to handle lore modifications globally. + * This is the preferred overload. Delegates to + * [registerPacketLoreListener(Plugin, NamespacedKey, SurfBukkitPacketLoreHandler)]. + * The [plugin] reference is used to properly manage the listener lifecycle. * - * Example Usage: - * ``` - * surfBukkitPacketApi.registerPacketLoreListenerGlobal(myPlugin, SurfBukkitPacketLoreHandler { lore, _, _ -> - * lore.add(Component.text("Global Lore Modification")) - * }) - * ``` + * @param plugin The plugin registering the listener. Used for lifecycle management. + * @param identifier A unique key representing the item to listen for. + * @param listener The simplified lore listener that focuses solely on modifying the lore list. + */ + fun registerPacketLoreListener( + plugin: Plugin, + identifier: NamespacedKey, + listener: SurfBukkitPacketLoreHandlerSimple + ) { + registerPacketLoreListener(plugin, identifier, listener as SurfBukkitPacketLoreHandler) + } + + /** + * Registers a lore listener globally to handle lore modifications for all items. + * + * Unlike the key-based overloads, this listener fires for every item stack regardless of + * its identifier. Use this only when you genuinely need to intercept all items, as it has + * a broader performance impact. + * + * @param plugin The plugin registering the listener. Used for lifecycle management. + * @param listener The lore listener to handle lore modifications globally. */ fun registerPacketLoreListenerGlobal( plugin: Plugin, @@ -71,12 +116,12 @@ interface SurfBukkitPacketApi { ) /** - * Registers a simplified packet lore listener globally for all items. + * Registers a simplified lore listener globally for all items. * - * @param plugin The plugin registering the listener. Used for proper cleanup during plugin shutdown. - * @param listener The simplified lore listener that focuses solely on the lore list. + * Delegates to [registerPacketLoreListenerGlobal(Plugin, SurfBukkitPacketLoreHandler)]. * - * This method delegates to the standard [registerPacketLoreListenerGlobal] implementation. + * @param plugin The plugin registering the listener. Used for lifecycle management. + * @param listener The simplified lore listener that focuses solely on the lore list. */ fun registerPacketLoreListenerGlobal( plugin: Plugin, @@ -86,26 +131,19 @@ interface SurfBukkitPacketApi { } /** - * Unregisters a previously registered packet lore listener identified by the given key. + * Unregisters the packet lore listener associated with the given [identifier]. * * @param identifier The key identifying the listener to unregister. - * - * Example Usage: - * ``` - * surfBukkitPacketApi.unregisterPacketLoreListener(NamespacedKey("myplugin", "custom_item")) - * ``` */ fun unregisterPacketLoreListener(identifier: NamespacedKey) /** - * Unregisters all packet lore listeners associated with the given plugin. + * Unregisters all packet lore listeners associated with the given [plugin]. * - * @param plugin The plugin whose listeners should be unregistered. + * Call this during plugin shutdown to ensure all listeners registered under this plugin + * are properly cleaned up. * - * Example Usage: - * ``` - * surfBukkitPacketApi.unregisterPacketLoreListener(myPlugin) - * ``` + * @param plugin The plugin whose listeners should be unregistered. */ fun unregisterPacketLoreListener(plugin: Plugin) @@ -114,5 +152,3 @@ interface SurfBukkitPacketApi { val instance = requiredService() } } - -val surfBukkitPacketApi get() = SurfBukkitPacketApi.instance diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/packet/SurfBukkitPacketApiImpl.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/packet/SurfBukkitPacketApiImpl.kt index d631412f5..861985f1f 100644 --- a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/packet/SurfBukkitPacketApiImpl.kt +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/packet/SurfBukkitPacketApiImpl.kt @@ -15,10 +15,11 @@ class SurfBukkitPacketApiImpl : SurfBukkitPacketApi { } override fun registerPacketLoreListener( + plugin: Plugin, identifier: NamespacedKey, listener: SurfBukkitPacketLoreHandler ) { - PacketLoreListener.register(identifier, listener) + PacketLoreListener.register(plugin, identifier, listener) } override fun registerPacketLoreListenerGlobal( diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/packet/PacketApiLoader.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/packet/PacketApiLoader.kt index 1e5ddc673..171a4e472 100644 --- a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/packet/PacketApiLoader.kt +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/packet/PacketApiLoader.kt @@ -1,11 +1,14 @@ package dev.slne.surf.surfapi.bukkit.server.packet import com.github.retrooper.packetevents.PacketEvents +import dev.slne.surf.surfapi.bukkit.api.event.register +import dev.slne.surf.surfapi.bukkit.api.event.unregister import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution import dev.slne.surf.surfapi.bukkit.api.packet.listener.packetListenerApi import dev.slne.surf.surfapi.bukkit.server.impl.glow.GlowingPacketListener import dev.slne.surf.surfapi.bukkit.server.packet.listener.PlayerChannelInjector import dev.slne.surf.surfapi.bukkit.server.packet.lore.PacketLoreListener +import dev.slne.surf.surfapi.bukkit.server.packet.lore.PluginDisablePacketLoreListener import dev.slne.surf.surfapi.bukkit.server.plugin import dev.slne.surf.surfapi.core.api.extensions.packetEvents import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder @@ -23,12 +26,14 @@ object PacketApiLoader { packetListenerApi.registerListeners(GlowingPacketListener) PlayerChannelInjector.register() + PluginDisablePacketLoreListener.register() } @OptIn(NmsUseWithCaution::class) fun onDisable() { packetEvents.terminate() packetListenerApi.unregisterListeners(PacketLoreListener) + PluginDisablePacketLoreListener.unregister() } private fun setupPacketEvents() { diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/packet/lore/PacketLoreListener.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/packet/lore/PacketLoreListener.kt index 13be6c4a9..758543ea7 100644 --- a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/packet/lore/PacketLoreListener.kt +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/packet/lore/PacketLoreListener.kt @@ -8,7 +8,10 @@ import dev.slne.surf.surfapi.bukkit.api.packet.lore.SurfBukkitPacketLoreHandler import dev.slne.surf.surfapi.bukkit.api.util.key import dev.slne.surf.surfapi.bukkit.server.nms.toBukkit import dev.slne.surf.surfapi.bukkit.server.nms.toNms -import dev.slne.surf.surfapi.core.api.util.mutableObjectListOf +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap +import it.unimi.dsi.fastutil.objects.ObjectArrayList +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet +import it.unimi.dsi.fastutil.objects.ObjectLists import net.kyori.adventure.text.format.TextDecoration import net.minecraft.core.component.DataComponents import net.minecraft.network.protocol.game.* @@ -17,7 +20,7 @@ import net.minecraft.world.item.component.CustomData import net.minecraft.world.item.component.ItemLore import org.bukkit.NamespacedKey import org.bukkit.plugin.Plugin -import java.util.concurrent.ConcurrentHashMap +import net.minecraft.network.chat.Component as MinecraftComponent /** * PacketLoreListener is a class that implements PacketListenerAbstract and is responsible for @@ -25,10 +28,22 @@ import java.util.concurrent.ConcurrentHashMap */ @OptIn(NmsUseWithCaution::class) object PacketLoreListener : PacketListener { - private val loreHandlers = ConcurrentHashMap() - private val loreHandlersGlobal = ConcurrentHashMap>() + private val globalHandlersByPlugin = + Object2ObjectLinkedOpenHashMap>() + + private val keyedHandlersByPlugin = + Object2ObjectLinkedOpenHashMap>() + + @Volatile + private var keyedHandlersSnapshot: Map = emptyMap() + + @Volatile + private var globalHandlersSnapshot: Array = emptyArray() private val ORIGINAL_LORE_KEY = key("original_lore") + private val ORIGINAL_LORE_KEY_STRING = ORIGINAL_LORE_KEY.asString() + + private fun hasAnyHandlers(): Boolean = keyedHandlersSnapshot.isNotEmpty() || globalHandlersSnapshot.isNotEmpty() @ServerboundListener fun onPacketReceive(event: ServerboundSetCreativeModeSlotPacket) { @@ -37,75 +52,169 @@ object PacketLoreListener : PacketListener { @ClientboundListener fun onWindowItem(event: ClientboundContainerSetContentPacket): ClientboundContainerSetContentPacket { + if (!hasAnyHandlers()) return event + + val sourceItems = event.items + val updatedItems = ObjectArrayList(sourceItems.size) + + var changed = false + + for (i in sourceItems.indices) { + val original = sourceItems[i] + val updated = makeUpdatedItemStack(original) + + if (updated !== original) { + changed = true + } + + updatedItems.add(updated) + } + + val originalCarried = event.carriedItem() + val updatedCarried = makeUpdatedItemStack(originalCarried) + + if (updatedCarried !== originalCarried) { + changed = true + } + + if (!changed) { + return event + } + return ClientboundContainerSetContentPacket( event.containerId(), event.stateId(), - event.items.map { makeUpdatedItemStack(it.copy()) }, - makeUpdatedItemStack(event.carriedItem().copy()) + updatedItems, + updatedCarried ) } @ClientboundListener fun onSetSlotPacket(event: ClientboundContainerSetSlotPacket): ClientboundContainerSetSlotPacket { + val original = event.item + val updated = makeUpdatedItemStack(original) + + if (updated === original) { + return event + } + return ClientboundContainerSetSlotPacket( event.containerId, event.stateId, event.slot, - makeUpdatedItemStack(event.item.copy()) + updated ) } @ClientboundListener fun onSetPlayerInventoryPacket(event: ClientboundSetPlayerInventoryPacket): ClientboundSetPlayerInventoryPacket { + val original = event.contents + val updated = makeUpdatedItemStack(original) + + if (updated === original) { + return event + } + return ClientboundSetPlayerInventoryPacket( event.slot(), - makeUpdatedItemStack(event.contents.copy()) + updated ) } @ClientboundListener fun onSetCursorItemPacket(event: ClientboundSetCursorItemPacket): ClientboundSetCursorItemPacket { - return ClientboundSetCursorItemPacket(makeUpdatedItemStack(event.contents.copy())) + val original = event.contents + val updated = makeUpdatedItemStack(original) + + if (updated === original) { + return event + } + + return ClientboundSetCursorItemPacket(updated) } + /** + * Returns the original item if: + * - item is empty + * - no handlers exist + * - no keyed handler matches and no global handler exists + * - handlers run but lore result is identical + * + * Only copies the stack if at least one handler will actually run. + */ private fun makeUpdatedItemStack( - item: ItemStack, + original: ItemStack, ): ItemStack { - if (item.isEmpty) return item - if (loreHandlers.isEmpty() && loreHandlersGlobal.isEmpty()) return item + if (original.isEmpty) return original + + // One volatile read + val keyedSnapshot = keyedHandlersSnapshot + val globalSnapshot = globalHandlersSnapshot + + if (keyedSnapshot.isEmpty() && globalSnapshot.isEmpty()) { + return original + } + + /* + * We need Bukkit PDC for the current handler API, but only when there + * are keyed handlers to consider. The original mirror is used to cheaply + * determine whether any keyed handlers actually match. + */ + val matchingKeyedHandlers = if (keyedSnapshot.isNotEmpty()) { + val originalBukkitStack = original.asBukkitMirror() + val originalPdc = originalBukkitStack.persistentDataContainer + resolveMatchingKeyedHandlers( + originalPdc.keys, + keyedSnapshot + ) + } else { + ObjectLists.emptyList() + } + if (matchingKeyedHandlers.isEmpty() && globalSnapshot.isEmpty()) { + return original + } + + /* + * From here on, we know that at least one handler will run. + * Only now create a copy. + */ + val item = original.copy() val bukkitStack = item.asBukkitMirror() val pdc = bukkitStack.persistentDataContainer - val nmsLore = item.getOrDefault(DataComponents.LORE, ItemLore.EMPTY) - val lines = nmsLore.lines - val mutableLore = lines.mapTo(mutableObjectListOf(lines.size)) { it.toBukkit() } - loreHandlers.forEach { (identifier, handler) -> - if (pdc.has(identifier)) { - handler.handleLore(mutableLore, pdc, bukkitStack) - } + val originalLore = item.getOrDefault(DataComponents.LORE, ItemLore.EMPTY) + val originalLines = originalLore.lines + + val mutableLore = originalLines.mapTo( + ObjectArrayList(originalLines.size) + ) { it.toBukkit() } + + for (i in matchingKeyedHandlers.indices) { + matchingKeyedHandlers[i].handleLore(mutableLore, pdc, bukkitStack) } - loreHandlersGlobal.forEach { (plugin, handlers) -> - if (plugin.isEnabled) { - handlers.forEach { it.handleLore(mutableLore, pdc, bukkitStack) } - } + for (i in globalSnapshot.indices) { + globalSnapshot[i].handleLore(mutableLore, pdc, bukkitStack) } - val updatedNmsLore = ItemLore( - mutableLore.asSequence() - .map { it.decorationIfAbsent(TextDecoration.ITALIC, TextDecoration.State.FALSE) } - .map { it.toNms() } - .toList() - ) + val updatedLines = ObjectArrayList(mutableLore.size) + for (i in mutableLore.indices) { + val line = mutableLore[i].decorationIfAbsent(TextDecoration.ITALIC, TextDecoration.State.FALSE) + updatedLines.add(line.toNms()) + } + + val updatedLore = ItemLore(updatedLines) - if (updatedNmsLore == nmsLore) { - return item + if (updatedLore == originalLore) { + return original } - item.set(DataComponents.LORE, updatedNmsLore) + item.set(DataComponents.LORE, updatedLore) CustomData.update(DataComponents.CUSTOM_DATA, item) { tag -> - tag.store(ORIGINAL_LORE_KEY.asString(), ItemLore.CODEC, nmsLore) + if (!tag.contains(ORIGINAL_LORE_KEY_STRING)) { + tag.store(ORIGINAL_LORE_KEY_STRING, ItemLore.CODEC, originalLore) + } } return item @@ -114,30 +223,132 @@ object PacketLoreListener : PacketListener { private fun makeCleanItemStack( stack: ItemStack, ): ItemStack { + if (stack.isEmpty) { + return stack + } + + val customData = stack.get(DataComponents.CUSTOM_DATA) ?: return stack + if (!customData.contains(ORIGINAL_LORE_KEY_STRING)) { + return stack + } + CustomData.update(DataComponents.CUSTOM_DATA, stack) { tag -> - val originalLore = tag.read(ORIGINAL_LORE_KEY.asString(), ItemLore.CODEC) + val originalLore = tag.read(ORIGINAL_LORE_KEY_STRING, ItemLore.CODEC) originalLore.ifPresent { lore -> stack.set(DataComponents.LORE, lore) - tag.remove(ORIGINAL_LORE_KEY.asString()) + tag.remove(ORIGINAL_LORE_KEY_STRING) } } return stack } - fun register(identifier: NamespacedKey, listener: SurfBukkitPacketLoreHandler) { - loreHandlers[identifier] = listener + private fun resolveMatchingKeyedHandlers( + itemKeys: Set, + keyedSnapshot: Map, + ): List { + if (itemKeys.isEmpty() || keyedSnapshot.isEmpty()) { + return emptyList() + } + + var result: ObjectArrayList? = null + for (key in itemKeys) { + val handler = keyedSnapshot[key] ?: continue + + if (result == null) { + result = ObjectArrayList(2) + } + + result.add(handler) + } + + return result ?: emptyList() + } + + fun register(plugin: Plugin, identifier: NamespacedKey, listener: SurfBukkitPacketLoreHandler) { + synchronized(this) { + check(!keyedHandlersSnapshot.containsKey(identifier)) { + "A PacketLore handler for $identifier is already registered!" + } + + val handlers = keyedHandlersByPlugin.computeIfAbsent(plugin) { Object2ObjectLinkedOpenHashMap() } + + val previous = handlers.putIfAbsent(identifier, listener) + check(previous == null) { + "A PacketLore handler for $identifier is already registered for plugin ${plugin.name}!" + } + + val newSnapshot = Object2ObjectLinkedOpenHashMap(keyedHandlersSnapshot) + newSnapshot[identifier] = listener + keyedHandlersSnapshot = newSnapshot + } } fun register(plugin: Plugin, listener: SurfBukkitPacketLoreHandler) { - loreHandlersGlobal.computeIfAbsent(plugin) { ConcurrentHashMap.newKeySet() }.add(listener) + synchronized(this) { + val handlers = globalHandlersByPlugin.computeIfAbsent(plugin) { ObjectLinkedOpenHashSet() } + if (handlers.add(listener)) { + rebuildGlobalHandlersSnapshot() + } else { + error("A PacketLore handler identical to the provided one (${listener.javaClass.name}) is already registered for plugin ${plugin.name}!") + } + } } fun unregister(identifier: NamespacedKey) { - loreHandlers.remove(identifier) + synchronized(this) { + if (!keyedHandlersSnapshot.containsKey(identifier)) { + return + } + + var emptyPlugin: Plugin? = null + + for ((plugin, handlers) in keyedHandlersByPlugin) { + if (handlers.remove(identifier) != null) { + if (handlers.isEmpty()) { + emptyPlugin = plugin + } + break + } + } + + if (emptyPlugin != null) { + keyedHandlersByPlugin.remove(emptyPlugin) + } + + val newSnapshot = Object2ObjectLinkedOpenHashMap(keyedHandlersSnapshot) + newSnapshot.remove(identifier) + keyedHandlersSnapshot = newSnapshot + } } fun unregister(plugin: Plugin) { - loreHandlersGlobal.remove(plugin) + synchronized(this) { + val removedGlobal = globalHandlersByPlugin.remove(plugin) != null + if (removedGlobal) { + rebuildGlobalHandlersSnapshot() + } + + val removedKeyed = keyedHandlersByPlugin.remove(plugin) + if (removedKeyed != null) { + val newSnapshot = Object2ObjectLinkedOpenHashMap(keyedHandlersSnapshot) + for (identifier in removedKeyed.keys) { + newSnapshot.remove(identifier) + } + keyedHandlersSnapshot = newSnapshot + } + } + } + + private fun rebuildGlobalHandlersSnapshot() { + val snapshot = ObjectArrayList() + + globalHandlersByPlugin.object2ObjectEntrySet().fastForEach { (plugin, handlers) -> + if (plugin.isEnabled) { + snapshot.addAll(handlers) + } + } + + globalHandlersSnapshot = snapshot.toTypedArray() } } diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/packet/lore/PluginDisablePacketLoreListener.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/packet/lore/PluginDisablePacketLoreListener.kt new file mode 100644 index 000000000..5955a63c2 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/packet/lore/PluginDisablePacketLoreListener.kt @@ -0,0 +1,13 @@ +package dev.slne.surf.surfapi.bukkit.server.packet.lore + +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener +import org.bukkit.event.server.PluginDisableEvent + +object PluginDisablePacketLoreListener : Listener { + + @EventHandler + fun onPluginDisable(event: PluginDisableEvent) { + PacketLoreListener.unregister(event.plugin) + } +} \ No newline at end of file