diff --git a/bukkit/compatibility/build.gradle.kts b/bukkit/compatibility/build.gradle.kts index 05d1dc29f..8a7807cc9 100644 --- a/bukkit/compatibility/build.gradle.kts +++ b/bukkit/compatibility/build.gradle.kts @@ -32,6 +32,8 @@ dependencies { // MMOItems compileOnly("net.Indyuce:MMOItems-API:6.10-SNAPSHOT") compileOnly("io.lumine:MythicLib-dist:1.6.2-SNAPSHOT") + // LuckPerms + compileOnly("net.luckperms:api:5.4") } java { diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/permission/LuckPermsEventListeners.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/permission/LuckPermsEventListeners.java new file mode 100644 index 000000000..b56a2befe --- /dev/null +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/permission/LuckPermsEventListeners.java @@ -0,0 +1,78 @@ +package net.momirealms.craftengine.bukkit.compatibility.permission; + +import net.luckperms.api.LuckPerms; +import net.luckperms.api.event.EventBus; +import net.luckperms.api.event.EventSubscription; +import net.luckperms.api.event.group.GroupDataRecalculateEvent; +import net.luckperms.api.event.user.UserDataRecalculateEvent; +import net.luckperms.api.model.user.User; +import net.momirealms.craftengine.core.plugin.scheduler.SchedulerAdapter; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.logging.Level; + +public class LuckPermsEventListeners { + private final JavaPlugin plugin; + private final LuckPerms luckPerms; + private final BiConsumer consumer; + private final SchedulerAdapter scheduler; + private final List> subscriptions = new ArrayList<>(); + + public LuckPermsEventListeners(JavaPlugin plugin, BiConsumer consumer, SchedulerAdapter scheduler) { + this.plugin = plugin; + this.consumer = consumer; + this.scheduler = scheduler; + RegisteredServiceProvider provider = Bukkit.getServicesManager().getRegistration(LuckPerms.class); + if (provider != null) { + this.luckPerms = provider.getProvider(); + this.registerEventListeners(); + } else { + throw new IllegalStateException("Unable to hook into LuckPerms"); + } + } + + private void registerEventListeners() { + EventBus eventBus = this.luckPerms.getEventBus(); + this.subscriptions.add(eventBus.subscribe(this.plugin, UserDataRecalculateEvent.class, this::onUserPermissionChange)); + this.subscriptions.add(eventBus.subscribe(this.plugin, GroupDataRecalculateEvent.class, this::onGroupPermissionChange)); + } + + public void unregisterListeners() { + this.subscriptions.forEach(subscription -> { + try { + subscription.close(); + } catch (Exception e) { + this.plugin.getLogger().log(Level.WARNING, "Failed to close event subscription", e); + } + }); + this.subscriptions.clear(); + } + + private void onUserPermissionChange(UserDataRecalculateEvent event) { + this.consumer.accept(event.getUser().getUniqueId(), true); + } + + private void onGroupPermissionChange(GroupDataRecalculateEvent event) { + this.scheduler.asyncLater(() -> { + String groupName = event.getGroup().getName(); + Bukkit.getOnlinePlayers().forEach(player -> { + UUID uuid = player.getUniqueId(); + User user = luckPerms.getUserManager().getUser(uuid); + if (user == null) return; + boolean inGroup = user.getInheritedGroups(user.getQueryOptions()).stream() + .anyMatch(g -> g.getName().equals(groupName)); + if (inGroup) { + this.consumer.accept(uuid, false); + } + }); + }, 1L, TimeUnit.SECONDS); + } +} diff --git a/bukkit/loader/build.gradle.kts b/bukkit/loader/build.gradle.kts index ab4e6c6c1..7f2a8a975 100644 --- a/bukkit/loader/build.gradle.kts +++ b/bukkit/loader/build.gradle.kts @@ -48,6 +48,7 @@ bukkit { name = "CraftEngine" apiVersion = "1.20" authors = listOf("XiaoMoMi") + contributors = listOf("jhqwqmc", "iqtesterrr") softDepend = listOf("PlaceholderAPI", "WorldEdit", "FastAsyncWorldEdit") foliaSupported = true } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/font/BukkitFontManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/font/BukkitFontManager.java index 91eb5f378..58856a6b0 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/font/BukkitFontManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/font/BukkitFontManager.java @@ -5,7 +5,7 @@ import com.google.gson.JsonObject; import io.papermc.paper.event.player.AsyncChatCommandDecorateEvent; import io.papermc.paper.event.player.AsyncChatDecorateEvent; import net.kyori.adventure.text.Component; -import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.compatibility.permission.LuckPermsEventListeners; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.util.ComponentUtils; import net.momirealms.craftengine.bukkit.util.LegacyInventoryUtils; @@ -14,7 +14,6 @@ import net.momirealms.craftengine.core.font.*; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.AdventureHelper; -import net.momirealms.craftengine.core.util.CharacterUtils; import net.momirealms.craftengine.core.util.VersionHelper; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -32,10 +31,13 @@ import org.bukkit.inventory.meta.BookMeta; import org.bukkit.inventory.view.AnvilView; import java.lang.reflect.InvocationTargetException; -import java.util.*; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; public class BukkitFontManager extends AbstractFontManager implements Listener { private final BukkitCraftEngine plugin; + private LuckPermsEventListeners luckPermsEventListeners; public BukkitFontManager(BukkitCraftEngine plugin) { super(plugin); @@ -44,6 +46,11 @@ public class BukkitFontManager extends AbstractFontManager implements Listener { @Override public void delayedInit() { + if (this.plugin.isPluginEnabled("LuckPerms")) { + luckPermsEventListeners = new LuckPermsEventListeners( + plugin.bootstrap(), this::refreshEmojiSuggestions, plugin.scheduler() + ); + } Bukkit.getPluginManager().registerEvents(this, plugin.bootstrap()); } @@ -51,6 +58,9 @@ public class BukkitFontManager extends AbstractFontManager implements Listener { public void disable() { super.disable(); HandlerList.unregisterAll(this); + if (luckPermsEventListeners != null && this.plugin.isPluginEnabled("LuckPerms")) { + luckPermsEventListeners.unregisterListeners(); + } } @Override @@ -64,23 +74,39 @@ public class BukkitFontManager extends AbstractFontManager implements Listener { }); } - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + @EventHandler(priority = EventPriority.LOWEST) public void onPlayerJoin(PlayerJoinEvent event) { - Player player = event.getPlayer(); - this.addEmojiSuggestions(player); + plugin.scheduler().async().execute(() -> { + Player player = event.getPlayer(); + this.addEmojiSuggestions(player); + }); + } + + public void refreshEmojiSuggestions(UUID playerUUID, boolean isAsync) { + if (isAsync) { + plugin.scheduler().async().execute(() -> { + Player player = Bukkit.getPlayer(playerUUID); + if (player == null) return; + player.removeCustomChatCompletions(oldCachedEmojiSuggestions); + this.addEmojiSuggestions(player); + }); + } else { + Player player = Bukkit.getPlayer(playerUUID); + if (player == null) return; + player.removeCustomChatCompletions(oldCachedEmojiSuggestions); + this.addEmojiSuggestions(player); + } } private void addEmojiSuggestions(Player player) { - List hasPermissions = new ArrayList<>(); - List cachedEmojiSuggestions = this.cachedEmojiSuggestions(); - for (String keyword : cachedEmojiSuggestions) { - Emoji emoji = super.emojiMapper.get(keyword); - if (emoji == null) continue; - if (emoji.permission() != null && !player.hasPermission(Objects.requireNonNull(emoji.permission()))) { - continue; - } - hasPermissions.add(keyword); - } + List hasPermissions = cachedEmojiSuggestions().stream() + .filter(keyword -> { + Emoji emoji = super.emojiMapper.get(keyword); + if (emoji == null) return false; + String permission = emoji.permission(); + return permission == null || player.hasPermission(permission); + }) + .collect(Collectors.toList()); player.addCustomChatCompletions(hasPermissions); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index 77b5bce77..16954643e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -443,7 +443,11 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes NMSPacketEvent event = new NMSPacketEvent(packet); onNMSPacketSend(player, event, packet); if (event.isCancelled()) return; - super.write(context, packet, channelPromise); + if (!event.hasNewPacket()) { + super.write(context, packet, channelPromise); + } else { + super.write(context, event.getNewPacket(), channelPromise); + } channelPromise.addListener((p) -> { for (Runnable task : event.getDelayedTasks()) { task.run(); @@ -460,7 +464,11 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes NMSPacketEvent event = new NMSPacketEvent(packet); onNMSPacketReceive(player, event, packet); if (event.isCancelled()) return; - super.channelRead(context, packet); + if (!event.hasNewPacket()) { + super.channelRead(context, packet); + } else { + super.channelRead(context, event.getNewPacket()); + } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/NMSPacketEvent.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/NMSPacketEvent.java index bce61806f..3ac92406f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/NMSPacketEvent.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/NMSPacketEvent.java @@ -2,6 +2,7 @@ package net.momirealms.craftengine.bukkit.plugin.network; import net.momirealms.craftengine.core.util.Cancellable; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -9,8 +10,10 @@ import java.util.Optional; public class NMSPacketEvent implements Cancellable { private boolean cancelled; + private boolean hasNewPacket; private List delayedTasks = null; private final Object packet; + private Object newPacket; public NMSPacketEvent(Object packet) { this.packet = packet; @@ -20,6 +23,20 @@ public class NMSPacketEvent implements Cancellable { return packet; } + public void setNewPacket(Object newPacket) { + hasNewPacket = true; + this.newPacket = newPacket; + } + + public boolean hasNewPacket() { + return hasNewPacket; + } + + @Nullable + public Object getNewPacket() { + return newPacket; + } + public void addDelayedTask(Runnable task) { if (delayedTasks == null) { delayedTasks = new ArrayList<>(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index cd158fe39..abd371a32 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -339,8 +339,8 @@ public class PacketConsumers { return; } + boolean isChanged = false; List newEntries = new MarkedArrayList<>(); - Reflections.field$ClientboundPlayerInfoUpdatePacket$entries.set(packet, newEntries); for (Object entry : entries) { Object mcComponent = FastNMS.INSTANCE.field$ClientboundPlayerInfoUpdatePacket$Entry$displayName(entry); if (mcComponent == null) { @@ -359,6 +359,10 @@ public class PacketConsumers { } Object newEntry = FastNMS.INSTANCE.constructor$ClientboundPlayerInfoUpdatePacket$Entry(entry, ComponentUtils.adventureToMinecraft(component)); newEntries.add(newEntry); + isChanged = true; + } + if (isChanged) { + event.setNewPacket(FastNMS.INSTANCE.constructor$ClientboundPlayerInfoUpdatePacket(enums, newEntries)); } } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundPlayerInfoUpdatePacket", e); @@ -1769,18 +1773,12 @@ public class PacketConsumers { } if (changed) { - if (VersionHelper.isVersionNewerThan1_20_5()) { - event.setCancelled(true); - Object newPacket = Reflections.constructor$ServerboundEditBookPacket.newInstance( - Reflections.field$ServerboundEditBookPacket$slot.get(packet), - newPages, - newTitle - ); - user.receivePacket(newPacket); - } else { - Reflections.field$ServerboundEditBookPacket$pages.set(packet, newPages); - Reflections.field$ServerboundEditBookPacket$title.set(packet, newTitle); - } + Object newPacket = Reflections.constructor$ServerboundEditBookPacket.newInstance( + Reflections.field$ServerboundEditBookPacket$slot.get(packet), + newPages, + newTitle + ); + event.setNewPacket(newPacket); } } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ServerboundEditBookPacket", e);