Progress on require linking, add folia support, achievements/advancements

This commit is contained in:
Vankka 2023-04-23 16:53:58 +03:00
parent 42ec392bcb
commit ec02e5f88a
No known key found for this signature in database
GPG Key ID: 6E50CB7A29B96AD0
37 changed files with 985 additions and 273 deletions

View File

@ -36,6 +36,7 @@ allprojects {
filter { filter {
includeGroup 'com.destroystokyo.paper' includeGroup 'com.destroystokyo.paper'
includeGroup 'io.papermc.paper' includeGroup 'io.papermc.paper'
includeGroup 'dev.folia'
includeGroup 'org.spigotmc' includeGroup 'org.spigotmc'
includeGroup 'net.md-5' includeGroup 'net.md-5'
} }
@ -55,21 +56,24 @@ allprojects {
} }
} }
} }
dependencies {
// Platform
compileOnly(libs.paperapi.old)
}
} }
dependencies { dependencies {
// API // API
annotationProcessor project(':api') annotationProcessor project(':api')
// Platform
compileOnly(libs.spigotapi)
// Common // Common
compileOnly project(':common') compileOnly project(':common')
implementation project(path: ':common', configuration: 'runtimeElements') implementation project(path: ':common', configuration: 'runtimeElements')
// Folia, modern bukkit
api project(':bukkit:bukkit-folia')
api project(':bukkit:bukkit-paper')
api project(':bukkit:bukkit-bukkit1_12')
// DependencyDownload // DependencyDownload
implementation(libs.mcdependencydownload.bukkit.bootstrap) implementation(libs.mcdependencydownload.bukkit.bootstrap)

View File

@ -0,0 +1,10 @@
dependencies {
// Platform
compileOnly(libs.spigotapi.onetwelve)
// Adventure (runtime downloaded by :bukkit)
compileOnly(libs.adventure.platform.bukkit)
// Common
compileOnly project(':common')
}

View File

@ -0,0 +1,38 @@
package com.discordsrv.bukkit.listener.award;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.logging.NamedLogger;
import com.github.benmanes.caffeine.cache.Cache;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class AbstractBukkitAwardListener implements Listener {
private final Cache<UUID, AtomicInteger> advancementCount;
protected final Logger logger;
protected final IBukkitAwardForwarder forwarder;
public AbstractBukkitAwardListener(DiscordSRV discordSRV, IBukkitAwardForwarder forwarder) {
this.advancementCount = discordSRV.caffeineBuilder()
.expireAfterWrite(5, TimeUnit.SECONDS)
.build();
this.logger = new NamedLogger(discordSRV, "AWARD_LISTENER");
this.forwarder = forwarder;
}
@SuppressWarnings("DataFlowIssue") // Not possible
public boolean checkIfShouldSkip(Player player) {
int count = advancementCount.get(player.getUniqueId(), key -> new AtomicInteger(0)).incrementAndGet();
boolean skip = count >= 5;
if (skip && (count % 5 == 0)) {
logger.debug("Skipping advancement/achievement processing for player: " + player.getName()
+ " currently at " + advancementCount + " advancements/achievements per 5 seconds");
}
return skip;
}
}

View File

@ -0,0 +1,22 @@
package com.discordsrv.bukkit.listener.award;
import com.discordsrv.common.DiscordSRV;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.player.PlayerAdvancementDoneEvent;
public class BukkitAdvancementListener extends AbstractBukkitAwardListener {
public BukkitAdvancementListener(DiscordSRV discordSRV, IBukkitAwardForwarder forwarder) {
super(discordSRV, forwarder);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerAdvancementDone(PlayerAdvancementDoneEvent event) {
// TODO:
// - NMS: check if the advancement should be broadcasted to chat, returning if not
// - run checkIfShouldSkip from parent
// - NMS: get advancement and/or 'advancement award message'
// - run forwarder.publishEvent
}
}

View File

@ -0,0 +1,8 @@
package com.discordsrv.bukkit.listener.award;
import com.discordsrv.api.component.MinecraftComponent;
import org.bukkit.entity.Player;
public interface IBukkitAwardForwarder {
void publishEvent(Player player, MinecraftComponent message, MinecraftComponent name, boolean cancelled);
}

View File

@ -0,0 +1,9 @@
disableAutoTargetJvm() // Requires Java 17, we target 8
dependencies {
// Platform
compileOnly(libs.folia)
// Common
compileOnly project(':common')
}

View File

@ -0,0 +1,36 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2023 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.bukkit.scheduler;
import com.discordsrv.common.scheduler.ServerScheduler;
import org.bukkit.Server;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import java.util.function.BiConsumer;
public interface IBukkitScheduler extends ServerScheduler {
void runWithArgs(BiConsumer<Server, Plugin> runNormal);
default void runOnMainThread(CommandSender sender, Runnable task) {
runOnMainThread(task);
}
}

View File

@ -0,0 +1,67 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2023 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.bukkit.scheduler;
import com.discordsrv.common.scheduler.ServerScheduler;
import org.bukkit.command.BlockCommandSender;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ProxiedCommandSender;
import org.bukkit.entity.Entity;
public interface IFoliaScheduler extends ServerScheduler, IBukkitScheduler {
@Override
default void runOnMainThread(CommandSender sender, Runnable task) {
if (sender instanceof ProxiedCommandSender) {
runOnMainThread(((ProxiedCommandSender) sender).getCallee(), task);
return;
}
if (sender instanceof Entity) {
runWithArgs((server, plugin) -> ((Entity) sender).getScheduler().run(
plugin,
r -> task.run(),
null
));
} else if (sender instanceof BlockCommandSender) {
runWithArgs((server, plugin) -> server.getRegionScheduler().run(
plugin,
((BlockCommandSender) sender).getBlock().getLocation(),
r -> task.run()
));
} else {
runOnMainThread(task);
}
}
@Override
default void runOnMainThread(Runnable task) {
runWithArgs((server, plugin) -> server.getGlobalRegionScheduler().execute(plugin, task));
}
@Override
default void runOnMainThreadLaterInTicks(Runnable task, int ticks) {
runWithArgs((server, plugin) -> server.getGlobalRegionScheduler().runDelayed(plugin, r -> task.run(), ticks));
}
@Override
default void runOnMainThreadAtFixedRateInTicks(Runnable task, int initialTicks, int rateTicks) {
runWithArgs((server, plugin) -> server.getGlobalRegionScheduler().execute(plugin, task));
}
}

View File

@ -6,6 +6,9 @@ dependencies {
// API // API
implementation project(':common:common-api') implementation project(':common:common-api')
// Platform
compileOnly(libs.spigotapi)
// DependencyDownload // DependencyDownload
implementation(libs.mcdependencydownload.bukkit.loader) implementation(libs.mcdependencydownload.bukkit.loader)
} }

View File

@ -4,8 +4,14 @@ version: @VERSION@
description: "" description: ""
authors: [Scarsz, Vankka] authors: [Scarsz, Vankka]
load: STARTUP load: STARTUP
# Marks the plugin non-legacy in versions 1.13 and above
# This does not mean that the plugin only supports 1.13
api-version: 1.13 api-version: 1.13
# Marks the plugin as supporting Folia
folia-supported: true
softdepend: [ softdepend: [
# Permission + group providers # Permission + group providers
Vault, LuckPerms, Vault, LuckPerms,

13
bukkit/paper/build.gradle Normal file
View File

@ -0,0 +1,13 @@
disableAutoTargetJvm() // Requires Java 17, we target 8
dependencies {
// Platform
compileOnly(libs.paperapi)
// Adventure (runtime downloaded by :bukkit)
compileOnly(libs.adventure.platform.bukkit)
// Common
compileOnly project(':bukkit:bukkit-bukkit1_12')
compileOnly project(':common')
}

View File

@ -64,7 +64,7 @@ public class PaperComponentHandle<T> {
if (handle != null) { if (handle != null) {
Object unrelocated = null; Object unrelocated = null;
try { try {
unrelocated = handle.invoke(target); unrelocated = handle.invokeExact(target);
} catch (Throwable ignored) {} } catch (Throwable ignored) {}
if (unrelocated != null) { if (unrelocated != null) {
MinecraftComponent component = discordSRV.componentFactory().empty(); MinecraftComponent component = discordSRV.componentFactory().empty();

View File

@ -23,7 +23,6 @@ import dev.vankka.dynamicproxy.processor.Proxy;
import net.kyori.adventure.platform.bukkit.BukkitComponentSerializer; import net.kyori.adventure.platform.bukkit.BukkitComponentSerializer;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import net.md_5.bungee.api.chat.BaseComponent;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -31,6 +30,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.UUID; import java.util.UUID;
import java.util.function.Consumer; import java.util.function.Consumer;
@SuppressWarnings("deprecation") // Paper
@Proxy(value = CommandSender.class, className = "BukkitCommandExecutorProxy") @Proxy(value = CommandSender.class, className = "BukkitCommandExecutorProxy")
public abstract class BukkitCommandExecutorProxyTemplate implements CommandSender { public abstract class BukkitCommandExecutorProxyTemplate implements CommandSender {
@ -81,7 +81,6 @@ public abstract class BukkitCommandExecutorProxyTemplate implements CommandSende
return spigot; return spigot;
} }
@SuppressWarnings("deprecation")
public class Spigot extends CommandSender.Spigot { public class Spigot extends CommandSender.Spigot {
private final CommandSender.Spigot spigot; private final CommandSender.Spigot spigot;
@ -91,30 +90,30 @@ public abstract class BukkitCommandExecutorProxyTemplate implements CommandSende
} }
@Override @Override
public void sendMessage(@Nullable UUID sender, @NotNull BaseComponent component) { public void sendMessage(@Nullable UUID sender, @NotNull net.md_5.bungee.api.chat.BaseComponent component) {
spigot.sendMessage(sender, component); spigot.sendMessage(sender, component);
forwardBungee(new BaseComponent[] {component}); forwardBungee(new net.md_5.bungee.api.chat.BaseComponent[] {component});
} }
@Override @Override
public void sendMessage(@NotNull BaseComponent component) { public void sendMessage(@NotNull net.md_5.bungee.api.chat.BaseComponent component) {
spigot.sendMessage(component); spigot.sendMessage(component);
forwardBungee(new BaseComponent[] {component}); forwardBungee(new net.md_5.bungee.api.chat.BaseComponent[] {component});
} }
@Override @Override
public void sendMessage(@Nullable UUID sender, @NotNull BaseComponent... components) { public void sendMessage(@Nullable UUID sender, @NotNull net.md_5.bungee.api.chat.BaseComponent... components) {
spigot.sendMessage(components); spigot.sendMessage(components);
forwardBungee(components); forwardBungee(components);
} }
@Override @Override
public void sendMessage(@NotNull BaseComponent... components) { public void sendMessage(@NotNull net.md_5.bungee.api.chat.BaseComponent... components) {
spigot.sendMessage(components); spigot.sendMessage(components);
forwardBungee(components); forwardBungee(components);
} }
private void forwardBungee(BaseComponent[] components) { private void forwardBungee(net.md_5.bungee.api.chat.BaseComponent[] components) {
componentConsumer.accept(BungeeComponentSerializer.get().deserialize(components)); componentConsumer.accept(BungeeComponentSerializer.get().deserialize(components));
} }
} }

View File

@ -0,0 +1,55 @@
package com.discordsrv.bukkit.listener.award;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.bukkit.component.PaperComponentHandle;
import com.discordsrv.common.DiscordSRV;
import io.papermc.paper.advancement.AdvancementDisplay;
import org.bukkit.advancement.Advancement;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.player.PlayerAdvancementDoneEvent;
public class PaperModernAdvancementListener extends AbstractBukkitAwardListener {
private static final PaperComponentHandle<PlayerAdvancementDoneEvent> MESSAGE_HANDLE;
private static final PaperComponentHandle<Advancement> DISPLAY_NAME_HANDLE;
static {
MESSAGE_HANDLE = new PaperComponentHandle<>(
PlayerAdvancementDoneEvent.class,
"message",
null
);
DISPLAY_NAME_HANDLE = new PaperComponentHandle<>(
Advancement.class,
"displayName",
null
);
}
private final DiscordSRV discordSRV;
public PaperModernAdvancementListener(DiscordSRV discordSRV, IBukkitAwardForwarder forwarder) {
super(discordSRV, forwarder);
this.discordSRV = discordSRV;
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerAdvancementDone(PlayerAdvancementDoneEvent event) {
Advancement advancement = event.getAdvancement();
AdvancementDisplay display = advancement.getDisplay();
if (display == null || !display.doesAnnounceToChat()) {
logger.trace("Skipping advancement display of \"" + advancement.getKey().getKey() + "\" for "
+ event.getPlayer() + ": advancement display == null or does not broadcast to chat");
return;
}
if (checkIfShouldSkip(event.getPlayer())) {
return;
}
MinecraftComponent message = MESSAGE_HANDLE.getComponent(discordSRV, event);
MinecraftComponent displayName = DISPLAY_NAME_HANDLE.getComponent(discordSRV, advancement);
forwarder.publishEvent(event.getPlayer(), message, displayName, false);
}
}

View File

@ -0,0 +1,9 @@
package com.discordsrv.bukkit.listener.chat;
import com.discordsrv.api.component.MinecraftComponent;
import org.bukkit.entity.Player;
public interface IBukkitChatForwarder {
void publishEvent(Player player, MinecraftComponent component, boolean cancelled);
}

View File

@ -0,0 +1,54 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2023 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.bukkit.listener.chat;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.bukkit.component.PaperComponentHandle;
import com.discordsrv.common.DiscordSRV;
import io.papermc.paper.event.player.AsyncChatEvent;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
public class PaperChatListener implements Listener {
private static final PaperComponentHandle<AsyncChatEvent> COMPONENT_HANDLE;
static {
COMPONENT_HANDLE = new PaperComponentHandle<>(
AsyncChatEvent.class,
"message",
null
);
}
private final DiscordSRV discordSRV;
private final IBukkitChatForwarder listener;
public PaperChatListener(DiscordSRV discordSRV, IBukkitChatForwarder listener) {
this.discordSRV = discordSRV;
this.listener = listener;
}
@EventHandler(priority = EventPriority.MONITOR)
public void onAsyncChat(AsyncChatEvent event) {
MinecraftComponent component = COMPONENT_HANDLE.getComponent(discordSRV, event);
listener.publishEvent(event.getPlayer(), component, event.isCancelled());
}
}

View File

@ -25,14 +25,18 @@ import com.discordsrv.bukkit.config.main.BukkitConfig;
import com.discordsrv.bukkit.config.manager.BukkitConfigManager; import com.discordsrv.bukkit.config.manager.BukkitConfigManager;
import com.discordsrv.bukkit.config.manager.BukkitConnectionConfigManager; import com.discordsrv.bukkit.config.manager.BukkitConnectionConfigManager;
import com.discordsrv.bukkit.console.BukkitConsole; import com.discordsrv.bukkit.console.BukkitConsole;
import com.discordsrv.bukkit.listener.BukkitChatListener;
import com.discordsrv.bukkit.listener.BukkitConnectionListener; import com.discordsrv.bukkit.listener.BukkitConnectionListener;
import com.discordsrv.bukkit.listener.BukkitDeathListener; import com.discordsrv.bukkit.listener.BukkitDeathListener;
import com.discordsrv.bukkit.listener.BukkitRequiredLinkingListener;
import com.discordsrv.bukkit.listener.BukkitStatusMessageListener; import com.discordsrv.bukkit.listener.BukkitStatusMessageListener;
import com.discordsrv.bukkit.listener.award.BukkitAwardForwarder;
import com.discordsrv.bukkit.listener.chat.BukkitChatForwarder;
import com.discordsrv.bukkit.player.BukkitPlayerProvider; import com.discordsrv.bukkit.player.BukkitPlayerProvider;
import com.discordsrv.bukkit.plugin.BukkitPluginManager; import com.discordsrv.bukkit.plugin.BukkitPluginManager;
import com.discordsrv.bukkit.requiredlinking.BukkitRequiredLinkingModule; import com.discordsrv.bukkit.requiredlinking.BukkitRequiredLinkingModule;
import com.discordsrv.bukkit.scheduler.BukkitScheduler; import com.discordsrv.bukkit.scheduler.BukkitScheduler;
import com.discordsrv.bukkit.scheduler.FoliaScheduler;
import com.discordsrv.bukkit.scheduler.IBukkitScheduler;
import com.discordsrv.common.ServerDiscordSRV; import com.discordsrv.common.ServerDiscordSRV;
import com.discordsrv.common.command.game.handler.ICommandHandler; import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.component.translation.Translation; import com.discordsrv.common.component.translation.Translation;
@ -60,19 +64,29 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<DiscordSRVBukkitBootstrap
private BukkitAudiences audiences; private BukkitAudiences audiences;
private final BukkitScheduler scheduler; private final IBukkitScheduler scheduler;
private final BukkitConsole console; private final BukkitConsole console;
private final BukkitPlayerProvider playerProvider; private final BukkitPlayerProvider playerProvider;
private final BukkitPluginManager pluginManager; private final BukkitPluginManager pluginManager;
private AbstractBukkitCommandHandler commandHandler; private AbstractBukkitCommandHandler commandHandler;
private final BukkitRequiredLinkingListener requiredLinkingListener;
private final BukkitConnectionConfigManager connectionConfigManager; private final BukkitConnectionConfigManager connectionConfigManager;
private final BukkitConfigManager configManager; private final BukkitConfigManager configManager;
private static IBukkitScheduler createScheduler(BukkitDiscordSRV discordSRV) {
try {
Class.forName("io.papermc.paper.threadedregions.scheduler.GlobalRegionScheduler");
return new FoliaScheduler(discordSRV);
} catch (ClassNotFoundException ignored) {
return new BukkitScheduler(discordSRV);
}
}
public BukkitDiscordSRV(DiscordSRVBukkitBootstrap bootstrap) { public BukkitDiscordSRV(DiscordSRVBukkitBootstrap bootstrap) {
super(bootstrap); super(bootstrap);
this.scheduler = new BukkitScheduler(this); this.scheduler = createScheduler(this);
this.console = new BukkitConsole(this); this.console = new BukkitConsole(this);
this.playerProvider = new BukkitPlayerProvider(this); this.playerProvider = new BukkitPlayerProvider(this);
this.pluginManager = new BukkitPluginManager(this); this.pluginManager = new BukkitPluginManager(this);
@ -82,6 +96,8 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<DiscordSRVBukkitBootstrap
this.configManager = new BukkitConfigManager(this); this.configManager = new BukkitConfigManager(this);
load(); load();
this.requiredLinkingListener = new BukkitRequiredLinkingListener(this);
} }
public JavaPlugin plugin() { public JavaPlugin plugin() {
@ -97,7 +113,7 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<DiscordSRVBukkitBootstrap
} }
@Override @Override
public BukkitScheduler scheduler() { public IBukkitScheduler scheduler() {
return scheduler; return scheduler;
} }
@ -217,7 +233,8 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<DiscordSRVBukkitBootstrap
commandHandler = AbstractBukkitCommandHandler.get(this); commandHandler = AbstractBukkitCommandHandler.get(this);
// Register listeners // Register listeners
server().getPluginManager().registerEvents(BukkitChatListener.get(this), plugin()); server().getPluginManager().registerEvents(BukkitAwardForwarder.get(this), plugin());
server().getPluginManager().registerEvents(BukkitChatForwarder.get(this), plugin());
server().getPluginManager().registerEvents(new BukkitDeathListener(this), plugin()); server().getPluginManager().registerEvents(new BukkitDeathListener(this), plugin());
server().getPluginManager().registerEvents(new BukkitStatusMessageListener(this), plugin()); server().getPluginManager().registerEvents(new BukkitStatusMessageListener(this), plugin());
@ -241,4 +258,10 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<DiscordSRVBukkitBootstrap
server().getPluginManager().registerEvents(new BukkitConnectionListener(this), plugin()); server().getPluginManager().registerEvents(new BukkitConnectionListener(this), plugin());
} }
@Override
protected void disable() {
super.disable();
requiredLinkingListener.disable();
}
} }

View File

@ -25,17 +25,36 @@ import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.command.game.sender.ICommandSender; import com.discordsrv.common.command.game.sender.ICommandSender;
import com.discordsrv.common.logging.Logger; import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.logging.NamedLogger; import com.discordsrv.common.logging.NamedLogger;
import org.bukkit.Server;
import org.bukkit.command.CommandMap;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender; import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.PluginCommand; import org.bukkit.command.PluginCommand;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.util.Locale; import java.util.Locale;
public abstract class AbstractBukkitCommandHandler implements ICommandHandler { public abstract class AbstractBukkitCommandHandler implements ICommandHandler {
private static final MethodHandle COMMAND_MAP_HANDLE;
static {
MethodHandle handle = null;
try {
handle = MethodHandles.lookup().findVirtual(
Server.class,
"getCommandMap",
MethodType.methodType(CommandMap.class)
);
} catch (ReflectiveOperationException ignored) {}
COMMAND_MAP_HANDLE = handle;
}
public static AbstractBukkitCommandHandler get(BukkitDiscordSRV discordSRV) { public static AbstractBukkitCommandHandler get(BukkitDiscordSRV discordSRV) {
try { try {
Class.forName("me.lucko.commodore.Commodore"); Class.forName("me.lucko.commodore.Commodore");
@ -76,13 +95,20 @@ public abstract class AbstractBukkitCommandHandler implements ICommandHandler {
return pluginCommand; return pluginCommand;
} }
if (COMMAND_MAP_HANDLE == null) {
// CommandMap unusable, can't get the command from it
return null;
}
PluginCommand command = null; PluginCommand command = null;
try { try {
Constructor<?> constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class); Constructor<?> constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class);
constructor.setAccessible(true); constructor.setAccessible(true);
command = (PluginCommand) constructor.newInstance(label, discordSRV.plugin()); command = (PluginCommand) constructor.newInstance(label, discordSRV.plugin());
discordSRV.server().getCommandMap().register(label, discordSRV.plugin().getName().toLowerCase(Locale.ROOT), command);
} catch (ReflectiveOperationException ignored) {} CommandMap commandMap = (CommandMap) COMMAND_MAP_HANDLE.invokeExact(discordSRV.server());
commandMap.register(label, discordSRV.plugin().getName().toLowerCase(Locale.ROOT), command);
} catch (Throwable ignored) {}
return command; return command;
} }

View File

@ -45,7 +45,7 @@ public class BukkitCommandSender implements ICommandSender {
@Override @Override
public void runCommand(String command) { public void runCommand(String command) {
discordSRV.scheduler().runOnMainThread(() -> discordSRV.server().dispatchCommand(commandSender, command)); discordSRV.scheduler().runOnMainThread(commandSender, () -> discordSRV.server().dispatchCommand(commandSender, command));
} }
@Override @Override

View File

@ -34,6 +34,6 @@ public class CommandSenderExecutor implements CommandExecutor {
@Override @Override
public void runCommand(String command) { public void runCommand(String command) {
discordSRV.scheduler().runOnMainThread(() -> discordSRV.server().dispatchCommand(commandSender, command)); discordSRV.scheduler().runOnMainThread(commandSender, () -> discordSRV.server().dispatchCommand(commandSender, command));
} }
} }

View File

@ -1,99 +0,0 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2023 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.bukkit.listener;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.event.events.message.receive.game.GameChatMessageReceiveEvent;
import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.bukkit.component.PaperComponentHandle;
import com.discordsrv.common.channel.GlobalChannel;
import com.discordsrv.common.component.util.ComponentUtil;
import io.papermc.paper.event.player.AsyncChatEvent;
import net.kyori.adventure.platform.bukkit.BukkitComponentSerializer;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
public abstract class BukkitChatListener implements Listener {
public static BukkitChatListener get(BukkitDiscordSRV discordSRV) {
// TODO: config option
//noinspection ConstantConditions,PointlessBooleanExpression
if (1 == 2 && PaperComponentHandle.IS_PAPER_ADVENTURE) {
return new Paper(discordSRV);
}
return new Bukkit(discordSRV);
}
protected final BukkitDiscordSRV discordSRV;
public BukkitChatListener(BukkitDiscordSRV discordSRV) {
this.discordSRV = discordSRV;
}
protected void publishEvent(Player player, MinecraftComponent component, boolean cancelled) {
discordSRV.scheduler().run(() -> discordSRV.eventBus().publish(
new GameChatMessageReceiveEvent(
discordSRV.playerProvider().player(player),
new GlobalChannel(discordSRV),
component,
cancelled
)
));
}
static class Bukkit extends BukkitChatListener {
public Bukkit(BukkitDiscordSRV discordSRV) {
super(discordSRV);
}
@SuppressWarnings("deprecation") // Paper
@EventHandler(priority = EventPriority.MONITOR)
public void onAsyncPlayerChat(org.bukkit.event.player.AsyncPlayerChatEvent event) {
MinecraftComponent component = ComponentUtil.toAPI(
BukkitComponentSerializer.legacy().deserialize(event.getMessage()));
publishEvent(event.getPlayer(), component, event.isCancelled());
}
}
static class Paper extends BukkitChatListener {
private final PaperComponentHandle<AsyncChatEvent> componentHandle;
public Paper(BukkitDiscordSRV discordSRV) {
super(discordSRV);
this.componentHandle = new PaperComponentHandle<>(
AsyncChatEvent.class,
"message",
null
);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onAsyncChat(AsyncChatEvent event) {
MinecraftComponent component = componentHandle.getComponent(discordSRV, event);
publishEvent(event.getPlayer(), component, event.isCancelled());
}
}
}

View File

@ -23,32 +23,59 @@ import com.discordsrv.api.event.events.message.receive.game.DeathMessageReceiveE
import com.discordsrv.api.player.DiscordSRVPlayer; import com.discordsrv.api.player.DiscordSRVPlayer;
import com.discordsrv.bukkit.BukkitDiscordSRV; import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.bukkit.component.PaperComponentHandle; import com.discordsrv.bukkit.component.PaperComponentHandle;
import org.bukkit.event.Cancellable;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.entity.PlayerDeathEvent;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public class BukkitDeathListener implements Listener { public class BukkitDeathListener implements Listener {
private final BukkitDiscordSRV discordSRV; private static final PaperComponentHandle<PlayerDeathEvent> COMPONENT_HANDLE;
private final PaperComponentHandle<PlayerDeathEvent> componentHandle; private static final MethodHandle CANCELLED_HANDLE;
@SuppressWarnings("deprecation") // Paper static {
public BukkitDeathListener(BukkitDiscordSRV discordSRV) { COMPONENT_HANDLE = new PaperComponentHandle<>(
this.discordSRV = discordSRV;
this.componentHandle = new PaperComponentHandle<>(
PlayerDeathEvent.class, PlayerDeathEvent.class,
"deathMessage", "deathMessage",
PlayerDeathEvent::getDeathMessage PlayerDeathEvent::getDeathMessage
); );
MethodHandle handle = null;
try {
handle = MethodHandles.lookup().findVirtual(
Cancellable.class,
"isCancelled",
MethodType.methodType(boolean.class)
);
} catch (ReflectiveOperationException ignored) {}
CANCELLED_HANDLE = handle;
}
private final BukkitDiscordSRV discordSRV;
public BukkitDeathListener(BukkitDiscordSRV discordSRV) {
this.discordSRV = discordSRV;
} }
@EventHandler(priority = EventPriority.MONITOR) @EventHandler(priority = EventPriority.MONITOR)
public void onPlayerDeath(PlayerDeathEvent event) { public void onPlayerDeath(PlayerDeathEvent event) {
DiscordSRVPlayer player = discordSRV.playerProvider().player(event.getEntity()); DiscordSRVPlayer player = discordSRV.playerProvider().player(event.getEntity());
MinecraftComponent component = componentHandle.getComponent(discordSRV, event); MinecraftComponent component = COMPONENT_HANDLE.getComponent(discordSRV, event);
boolean cancelled = false;
if (CANCELLED_HANDLE != null) {
try {
cancelled = (boolean) CANCELLED_HANDLE.invokeExact(event);
} catch (Throwable ignored) {}
}
boolean wasCancelled = cancelled;
discordSRV.scheduler().run(() -> discordSRV.eventBus().publish( discordSRV.scheduler().run(() -> discordSRV.eventBus().publish(
new DeathMessageReceiveEvent(player, null, component, event.isCancelled()))); new DeathMessageReceiveEvent(player, null, component, wasCancelled)));
} }
} }

View File

@ -0,0 +1,284 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2023 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.bukkit.listener;
import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.bukkit.config.main.BukkitRequiredLinkingConfig;
import com.discordsrv.bukkit.requiredlinking.BukkitRequiredLinkingModule;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.player.IPlayer;
import net.kyori.adventure.platform.bukkit.BukkitComponentSerializer;
import net.kyori.adventure.text.Component;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.*;
import org.bukkit.event.player.*;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class BukkitRequiredLinkingListener implements Listener {
private final BukkitDiscordSRV discordSRV;
public BukkitRequiredLinkingListener(BukkitDiscordSRV discordSRV) {
this.discordSRV = discordSRV;
register(PlayerLoginEvent.class, this::handle);
register(AsyncPlayerPreLoginEvent.class, this::handle);
discordSRV.server().getPluginManager().registerEvents(this, discordSRV.plugin());
}
@SuppressWarnings("unchecked")
private <T extends Event> void register(Class<T> eventType, BiConsumer<T, EventPriority> eventConsumer) {
for (EventPriority priority : EventPriority.values()) {
if (priority == EventPriority.MONITOR) {
continue;
}
discordSRV.server().getPluginManager().registerEvent(
eventType,
this,
priority,
(listener, event) -> eventConsumer.accept((T) event, priority),
discordSRV.plugin(),
true
);
}
}
public void disable() {
HandlerList.unregisterAll(this);
}
private BukkitRequiredLinkingModule getModule() {
int tries = 0;
BukkitRequiredLinkingModule module;
do {
module = discordSRV.getModule(BukkitRequiredLinkingModule.class);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
tries++;
} while (module == null || tries >= 50);
return module;
}
private CompletableFuture<Component> getBlockReason(UUID playerUUID) {
BukkitRequiredLinkingModule module = getModule();
if (module == null) {
return CompletableFuture.completedFuture(Component.text("Discord unavailable, please try again later"));
}
return module.getBlockReason(playerUUID);
}
//
// Kick
//
private void handle(AsyncPlayerPreLoginEvent event, EventPriority priority) {
handle(
"AsyncPlayerPreLoginEvent",
priority,
event.getUniqueId(),
() -> event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED ? event.getLoginResult().name() : null,
text -> event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, text)
);
}
private void handle(PlayerLoginEvent event, EventPriority priority) {
handle(
"PlayerLoginEvent",
priority,
event.getPlayer().getUniqueId(),
() -> event.getResult() != PlayerLoginEvent.Result.ALLOWED ? event.getResult().name() : null,
text -> event.disallow(PlayerLoginEvent.Result.KICK_OTHER, text)
);
}
private void handle(
String eventType,
EventPriority priority,
UUID playerUUID,
Supplier<String> alreadyBlocked,
Consumer<String> disallow
) {
BukkitRequiredLinkingConfig config = discordSRV.config().requiredLinking;
if (!config.enabled || !config.action.equalsIgnoreCase("KICK")
|| !eventType.equals(config.kick.event) || !priority.name().equals(config.kick.priority)) {
return;
}
String blockType = alreadyBlocked.get();
if (blockType != null) {
discordSRV.logger().debug(playerUUID + " is already blocked for " + eventType + "/" + priority + " (" + blockType + ")");
return;
}
Component kickReason = getBlockReason(playerUUID).join();
if (kickReason != null) {
disallow.accept(BukkitComponentSerializer.legacy().serialize(kickReason));
}
}
//
// Freeze
//
private final Map<UUID, Component> frozen = new ConcurrentHashMap<>();
private boolean isFrozen(Player player) {
return frozen.containsKey(player.getUniqueId());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPreLogin(AsyncPlayerPreLoginEvent event) {
if (event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) {
return;
}
if (discordSRV.isShutdown()) {
return;
} else if (!discordSRV.isReady()) {
try {
discordSRV.waitForStatus(DiscordSRV.Status.CONNECTED);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
BukkitRequiredLinkingConfig config = discordSRV.config().requiredLinking;
if (!config.enabled || !config.action.equalsIgnoreCase("FREEZE")) {
return;
}
Component blockReason = getBlockReason(event.getUniqueId()).join();
if (blockReason != null) {
frozen.put(event.getUniqueId(), blockReason);
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void onLogin(PlayerLoginEvent event) {
if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) {
frozen.remove(event.getPlayer().getUniqueId());
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void onJoin(PlayerJoinEvent event) {
UUID uuid = event.getPlayer().getUniqueId();
Component blockReason = frozen.get(uuid);
if (blockReason == null) {
return;
}
IPlayer player = discordSRV.playerProvider().player(uuid);
if (player == null) {
throw new IllegalStateException("Player not available: " + uuid);
}
player.sendMessage(blockReason);
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onPlayerMove(PlayerMoveEvent event) {
Component freezeReason = frozen.get(event.getPlayer().getUniqueId());
if (freezeReason == null) {
return;
}
Location from = event.getFrom(), to = event.getTo();
if (from.getWorld().getName().equals(to.getWorld().getName())
&& from.getBlockX() == to.getBlockX()
&& from.getBlockZ() == to.getBlockZ()
&& from.getBlockY() >= to.getBlockY()) {
return;
}
event.setTo(
new Location(
from.getWorld(),
from.getBlockX() + 0.5,
from.getBlockY(),
from.getBlockZ() + 0.5,
from.getYaw(),
from.getPitch()
)
);
IPlayer player = discordSRV.playerProvider().player(event.getPlayer());
player.sendMessage(freezeReason);
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onPlayerChat(AsyncPlayerChatEvent event) {
Component freezeReason = frozen.get(event.getPlayer().getUniqueId());
if (freezeReason == null) {
event.getRecipients().removeIf(this::isFrozen);
return;
}
event.setCancelled(true);
IPlayer player = discordSRV.playerProvider().player(event.getPlayer());
player.sendMessage(freezeReason);
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onPlayerCommand(PlayerCommandPreprocessEvent event) {
if (!isFrozen(event.getPlayer())) {
return;
}
event.setCancelled(true);
String message = event.getMessage();
if (message.startsWith("/")) message = message.substring(1);
if (message.equals("linked")) {
IPlayer player = discordSRV.playerProvider().player(event.getPlayer());
player.sendMessage(Component.text("Checking..."));
UUID uuid = player.uniqueId();
getBlockReason(uuid).whenComplete((reason, t) -> {
if (t != null) {
return;
}
if (reason == null) {
frozen.remove(uuid);
} else {
frozen.put(uuid, reason);
player.sendMessage(reason);
}
});
}
}
}

View File

@ -32,29 +32,32 @@ import org.bukkit.event.player.PlayerQuitEvent;
public class BukkitStatusMessageListener implements Listener { public class BukkitStatusMessageListener implements Listener {
private final BukkitDiscordSRV discordSRV; private static final PaperComponentHandle<PlayerJoinEvent> JOIN_HANDLE;
private final PaperComponentHandle<PlayerJoinEvent> joinHandle; private static final PaperComponentHandle<PlayerQuitEvent> QUIT_HANDLE;
private final PaperComponentHandle<PlayerQuitEvent> quitHandle;
@SuppressWarnings("deprecation") // Paper static {
public BukkitStatusMessageListener(BukkitDiscordSRV discordSRV) { JOIN_HANDLE = new PaperComponentHandle<>(
this.discordSRV = discordSRV;
this.joinHandle = new PaperComponentHandle<>(
PlayerJoinEvent.class, PlayerJoinEvent.class,
"joinMessage", "joinMessage",
PlayerJoinEvent::getJoinMessage PlayerJoinEvent::getJoinMessage
); );
this.quitHandle = new PaperComponentHandle<>( QUIT_HANDLE = new PaperComponentHandle<>(
PlayerQuitEvent.class, PlayerQuitEvent.class,
"quitMessage", "quitMessage",
PlayerQuitEvent::getQuitMessage PlayerQuitEvent::getQuitMessage
); );
} }
private final BukkitDiscordSRV discordSRV;
public BukkitStatusMessageListener(BukkitDiscordSRV discordSRV) {
this.discordSRV = discordSRV;
}
@EventHandler(priority = EventPriority.MONITOR) @EventHandler(priority = EventPriority.MONITOR)
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerJoin(PlayerJoinEvent event) {
DiscordSRVPlayer player = discordSRV.playerProvider().player(event.getPlayer()); DiscordSRVPlayer player = discordSRV.playerProvider().player(event.getPlayer());
MinecraftComponent component = joinHandle.getComponent(discordSRV, event); MinecraftComponent component = JOIN_HANDLE.getComponent(discordSRV, event);
boolean firstJoin = !event.getPlayer().hasPlayedBefore(); boolean firstJoin = !event.getPlayer().hasPlayedBefore();
discordSRV.scheduler().run(() -> discordSRV.eventBus().publish( discordSRV.scheduler().run(() -> discordSRV.eventBus().publish(
@ -65,7 +68,7 @@ public class BukkitStatusMessageListener implements Listener {
@EventHandler(priority = EventPriority.HIGH) @EventHandler(priority = EventPriority.HIGH)
public void onPlayerQuit(PlayerQuitEvent event) { public void onPlayerQuit(PlayerQuitEvent event) {
DiscordSRVPlayer player = discordSRV.playerProvider().player(event.getPlayer()); DiscordSRVPlayer player = discordSRV.playerProvider().player(event.getPlayer());
MinecraftComponent component = quitHandle.getComponent(discordSRV, event); MinecraftComponent component = QUIT_HANDLE.getComponent(discordSRV, event);
discordSRV.scheduler().run(() -> discordSRV.eventBus().publish( discordSRV.scheduler().run(() -> discordSRV.eventBus().publish(
new LeaveMessageReceiveEvent(player, null, component, false) new LeaveMessageReceiveEvent(player, null, component, false)

View File

@ -0,0 +1,26 @@
package com.discordsrv.bukkit.listener.award;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.component.util.ComponentUtil;
import net.kyori.adventure.platform.bukkit.BukkitComponentSerializer;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.player.PlayerAchievementAwardedEvent;
public class BukkitAchievementListener extends AbstractBukkitAwardListener {
public BukkitAchievementListener(DiscordSRV discordSRV, IBukkitAwardForwarder forwarder) {
super(discordSRV, forwarder);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerAchievementAwarded(PlayerAchievementAwardedEvent event) {
if (checkIfShouldSkip(event.getPlayer())) {
return;
}
MinecraftComponent name = ComponentUtil.toAPI(BukkitComponentSerializer.legacy().deserialize(event.getAchievement().name()));
forwarder.publishEvent(event.getPlayer(), null, name, event.isCancelled());
}
}

View File

@ -0,0 +1,34 @@
package com.discordsrv.bukkit.listener.award;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.bukkit.BukkitDiscordSRV;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
public class BukkitAwardForwarder implements IBukkitAwardForwarder {
public static Listener get(BukkitDiscordSRV discordSRV) {
try {
Class.forName("org.bukkit.event.player.PlayerAdvancementDoneEvent");
try {
Class.forName("io.papermc.paper.advancement.AdvancementDisplay");
return new PaperModernAdvancementListener(discordSRV, new BukkitAwardForwarder(discordSRV));
} catch (ClassNotFoundException ignored) {
return new BukkitAdvancementListener(discordSRV, new BukkitAwardForwarder(discordSRV));
}
} catch (ClassNotFoundException ignored) {
return new BukkitAchievementListener(discordSRV, new BukkitAwardForwarder(discordSRV));
}
}
private final BukkitDiscordSRV discordSRV;
protected BukkitAwardForwarder(BukkitDiscordSRV discordSRV) {
this.discordSRV = discordSRV;
}
public void publishEvent(Player player, MinecraftComponent message, MinecraftComponent advancementName, boolean cancelled) {
// TODO
}
}

View File

@ -0,0 +1,58 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2023 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.bukkit.listener.chat;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.event.events.message.receive.game.GameChatMessageReceiveEvent;
import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.bukkit.component.PaperComponentHandle;
import com.discordsrv.common.channel.GlobalChannel;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
public class BukkitChatForwarder implements IBukkitChatForwarder {
public static Listener get(BukkitDiscordSRV discordSRV) {
// TODO: config option
//noinspection ConstantConditions,PointlessBooleanExpression
if (1 == 2 && PaperComponentHandle.IS_PAPER_ADVENTURE) {
return new PaperChatListener(discordSRV, new BukkitChatForwarder(discordSRV));
}
return new BukkitChatListener(new BukkitChatForwarder(discordSRV));
}
private final BukkitDiscordSRV discordSRV;
protected BukkitChatForwarder(BukkitDiscordSRV discordSRV) {
this.discordSRV = discordSRV;
}
@Override
public void publishEvent(Player player, MinecraftComponent component, boolean cancelled) {
discordSRV.scheduler().run(() -> discordSRV.eventBus().publish(
new GameChatMessageReceiveEvent(
discordSRV.playerProvider().player(player),
new GlobalChannel(discordSRV),
component,
cancelled
)
));
}
}

View File

@ -0,0 +1,25 @@
package com.discordsrv.bukkit.listener.chat;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.common.component.util.ComponentUtil;
import net.kyori.adventure.platform.bukkit.BukkitComponentSerializer;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
public class BukkitChatListener implements Listener {
private final IBukkitChatForwarder forwarder;
public BukkitChatListener(IBukkitChatForwarder forwarder) {
this.forwarder = forwarder;
}
@EventHandler(priority = EventPriority.MONITOR)
public void onAsyncPlayerChat(org.bukkit.event.player.AsyncPlayerChatEvent event) {
MinecraftComponent component = ComponentUtil.toAPI(
BukkitComponentSerializer.legacy().deserialize(event.getMessage()));
forwarder.publishEvent(event.getPlayer(), component, event.isCancelled());
}
}

View File

@ -19,24 +19,10 @@
package com.discordsrv.bukkit.requiredlinking; package com.discordsrv.bukkit.requiredlinking;
import com.discordsrv.bukkit.BukkitDiscordSRV; import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.bukkit.config.main.BukkitRequiredLinkingConfig;
import com.discordsrv.common.config.main.linking.RequirementsConfig; import com.discordsrv.common.config.main.linking.RequirementsConfig;
import com.discordsrv.common.linking.requirelinking.ServerRequireLinkingModule; import com.discordsrv.common.linking.requirelinking.ServerRequireLinkingModule;
import net.kyori.adventure.platform.bukkit.BukkitComponentSerializer;
import net.kyori.adventure.text.Component;
import org.bukkit.event.Event;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
// TODO: implement freeze
public class BukkitRequiredLinkingModule extends ServerRequireLinkingModule<BukkitDiscordSRV> implements Listener { public class BukkitRequiredLinkingModule extends ServerRequireLinkingModule<BukkitDiscordSRV> implements Listener {
public BukkitRequiredLinkingModule(BukkitDiscordSRV discordSRV) { public BukkitRequiredLinkingModule(BukkitDiscordSRV discordSRV) {
@ -47,84 +33,4 @@ public class BukkitRequiredLinkingModule extends ServerRequireLinkingModule<Bukk
public RequirementsConfig config() { public RequirementsConfig config() {
return discordSRV.config().requiredLinking.requirements; return discordSRV.config().requiredLinking.requirements;
} }
@Override
public void enable() {
super.enable();
register(PlayerLoginEvent.class, this::handle);
register(AsyncPlayerPreLoginEvent.class, this::handle);
}
@SuppressWarnings("unchecked")
private <T extends Event> void register(Class<T> eventType, BiConsumer<T, EventPriority> eventConsumer) {
for (EventPriority priority : EventPriority.values()) {
if (priority == EventPriority.MONITOR) {
continue;
}
discordSRV.server().getPluginManager().registerEvent(
eventType,
this,
priority,
(listener, event) -> eventConsumer.accept((T) event, priority),
discordSRV.plugin(),
true
);
}
}
@SuppressWarnings("deprecation") // Component is relocated so using it here is inconvenient
private void handle(AsyncPlayerPreLoginEvent event, EventPriority priority) {
handle(
"AsyncPlayerPreLoginEvent",
priority,
event.getUniqueId(),
() -> event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED ? event.getLoginResult().name() : null,
text -> event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, text)
);
}
@SuppressWarnings("deprecation") // Component is relocated so using it here is inconvenient
private void handle(PlayerLoginEvent event, EventPriority priority) {
handle(
"PlayerLoginEvent",
priority,
event.getPlayer().getUniqueId(),
() -> event.getResult() != PlayerLoginEvent.Result.ALLOWED ? event.getResult().name() : null,
text -> event.disallow(PlayerLoginEvent.Result.KICK_OTHER, text)
);
}
private void handle(
String eventType,
EventPriority priority,
UUID playerUUID,
Supplier<String> alreadyBlocked,
Consumer<String> disallow
) {
BukkitRequiredLinkingConfig config = discordSRV.config().requiredLinking;
if (!config.enabled || !config.action.equalsIgnoreCase("KICK")
|| !eventType.equals(config.kick.event) || !priority.name().equals(config.kick.priority)) {
return;
}
String blockType = alreadyBlocked.get();
if (blockType != null) {
discordSRV.logger().debug(playerUUID + " is already blocked for " + eventType + "/" + priority + " (" + blockType + ")");
return;
}
Component kickReason = getKickReason(playerUUID).join();
if (kickReason != null) {
disallow.accept(BukkitComponentSerializer.legacy().serialize(kickReason));
}
}
@Override
public void disable() {
super.disable();
HandlerList.unregisterAll(this);
}
} }

View File

@ -0,0 +1,42 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2023 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.bukkit.scheduler;
import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.common.scheduler.ServerScheduler;
import com.discordsrv.common.scheduler.StandardScheduler;
import org.bukkit.Server;
import org.bukkit.plugin.Plugin;
import java.util.function.BiConsumer;
public abstract class AbstractBukkitScheduler extends StandardScheduler implements ServerScheduler, IBukkitScheduler {
protected final BukkitDiscordSRV discordSRV;
public AbstractBukkitScheduler(BukkitDiscordSRV discordSRV) {
super(discordSRV);
this.discordSRV = discordSRV;
}
@Override
public void runWithArgs(BiConsumer<Server, Plugin> runNormal) {
runNormal.accept(discordSRV.server(), discordSRV.plugin());
}
}

View File

@ -21,43 +21,39 @@ package com.discordsrv.bukkit.scheduler;
import com.discordsrv.bukkit.BukkitDiscordSRV; import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.bukkit.DiscordSRVBukkitBootstrap; import com.discordsrv.bukkit.DiscordSRVBukkitBootstrap;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.scheduler.ServerScheduler; import org.bukkit.Server;
import com.discordsrv.common.scheduler.StandardScheduler;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
public class BukkitScheduler extends StandardScheduler implements ServerScheduler { public class BukkitScheduler extends AbstractBukkitScheduler {
private final BukkitDiscordSRV discordSRV;
public BukkitScheduler(BukkitDiscordSRV discordSRV) { public BukkitScheduler(BukkitDiscordSRV discordSRV) {
super(discordSRV); super(discordSRV);
this.discordSRV = discordSRV;
} }
private void checkDisable(Runnable task, BiConsumer<org.bukkit.scheduler.BukkitScheduler, Plugin> runNormal) { protected void checkDisable(Runnable task, BiConsumer<Server, Plugin> runNormal) {
// Can't run tasks when disabling, so we'll push those to the bootstrap to run after disable // Can't run tasks when disabling, so we'll push those to the bootstrap to run after disable
if (!discordSRV.plugin().isEnabled() && discordSRV.status() == DiscordSRV.Status.SHUTTING_DOWN) { if (!discordSRV.plugin().isEnabled() && discordSRV.status() == DiscordSRV.Status.SHUTTING_DOWN) {
((DiscordSRVBukkitBootstrap) discordSRV.bootstrap()).mainThreadTasksForDisable().add(task); ((DiscordSRVBukkitBootstrap) discordSRV.bootstrap()).mainThreadTasksForDisable().add(task);
return; return;
} }
runNormal.accept(discordSRV.server().getScheduler(), discordSRV.plugin()); runWithArgs(runNormal);
} }
@Override @Override
public void runOnMainThread(Runnable task) { public void runOnMainThread(Runnable task) {
checkDisable(task, (scheduler, plugin) -> scheduler.runTask(plugin, task)); checkDisable(task, (server, plugin) -> server.getScheduler().runTask(plugin, task));
} }
@Override @Override
public void runOnMainThreadLaterInTicks(Runnable task, int ticks) { public void runOnMainThreadLaterInTicks(Runnable task, int ticks) {
checkDisable(task, (scheduler, plugin) -> scheduler.runTaskLater(plugin, task, ticks)); checkDisable(task, (server, plugin) -> server.getScheduler().runTaskLater(plugin, task, ticks));
} }
@Override @Override
public void runOnMainThreadAtFixedRateInTicks(Runnable task, int initialTicks, int rateTicks) { public void runOnMainThreadAtFixedRateInTicks(Runnable task, int initialTicks, int rateTicks) {
checkDisable(task, (scheduler, plugin) -> scheduler.runTaskTimer(plugin, task, initialTicks, rateTicks)); checkDisable(task, (server, plugin) -> server.getScheduler().runTaskTimer(plugin, task, initialTicks, rateTicks));
} }
} }

View File

@ -0,0 +1,28 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2023 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.bukkit.scheduler;
import com.discordsrv.bukkit.BukkitDiscordSRV;
public class FoliaScheduler extends AbstractBukkitScheduler implements IFoliaScheduler {
public FoliaScheduler(BukkitDiscordSRV discordSRV) {
super(discordSRV);
}
}

View File

@ -25,10 +25,10 @@ import com.discordsrv.common.debug.file.TextDebugFile;
import com.discordsrv.common.paste.Paste; import com.discordsrv.common.paste.Paste;
import com.discordsrv.common.paste.PasteService; import com.discordsrv.common.paste.PasteService;
import com.discordsrv.common.plugin.Plugin; import com.discordsrv.common.plugin.Plugin;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDA;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import java.io.IOException; import java.io.IOException;
@ -113,7 +113,9 @@ public class DebugReport {
} }
public void addFile(DebugFile file) { public void addFile(DebugFile file) {
files.add(file); if (file != null) {
files.add(file);
}
} }
private DebugFile environment() { private DebugFile environment() {
@ -167,24 +169,12 @@ public class DebugReport {
.sorted(Comparator.comparing(plugin -> plugin.name().toLowerCase(Locale.ROOT))) .sorted(Comparator.comparing(plugin -> plugin.name().toLowerCase(Locale.ROOT)))
.collect(Collectors.toList()); .collect(Collectors.toList());
int longestName = 0; try {
int longestVersion = 0; String json = discordSRV.json().writeValueAsString(plugins);
for (Plugin plugin : plugins) { return new TextDebugFile(5, "plugins.json", json);
longestName = Math.max(longestName, plugin.name().length()); } catch (JsonProcessingException e) {
longestVersion = Math.max(longestVersion, plugin.version().length()); return null;
} }
longestName++;
longestVersion++;
StringBuilder builder = new StringBuilder("Plugins (" + plugins.size() + "):\n");
for (Plugin plugin : plugins) {
builder.append('\n')
.append(StringUtils.rightPad(plugin.name(), longestName))
.append(" v").append(StringUtils.rightPad(plugin.version(), longestVersion))
.append(" ").append(plugin.authors());
}
return new TextDebugFile(5, "plugins.txt", builder.toString());
} }
private DebugFile readFile(int order, Path file) { private DebugFile readFile(int order, Path file) {

View File

@ -50,7 +50,7 @@ public abstract class ServerRequireLinkingModule<T extends DiscordSRV> extends R
} }
} }
public CompletableFuture<Component> getKickReason(UUID playerUUID) { public CompletableFuture<Component> getBlockReason(UUID playerUUID) {
RequirementsConfig config = config(); RequirementsConfig config = config();
if (config.bypassUUIDs.contains(playerUUID.toString())) { if (config.bypassUUIDs.contains(playerUUID.toString())) {
// Bypasses: let them through // Bypasses: let them through
@ -63,7 +63,7 @@ public abstract class ServerRequireLinkingModule<T extends DiscordSRV> extends R
return CompletableFuture.completedFuture(Component.text("Unable to check linking status at this time")); return CompletableFuture.completedFuture(Component.text("Unable to check linking status at this time"));
} }
return linkProvider.getUserId(playerUUID) return linkProvider.queryUserId(playerUUID)
.thenCompose(opt -> { .thenCompose(opt -> {
if (!opt.isPresent()) { if (!opt.isPresent()) {
// User is not linked // User is not linked

View File

@ -38,7 +38,7 @@ public class BytebinPasteService implements PasteService {
public Paste uploadFile(byte[] fileContent) throws Throwable { public Paste uploadFile(byte[] fileContent) throws Throwable {
Request request = new Request.Builder() Request request = new Request.Builder()
.url(bytebinUrl + "/post") .url(bytebinUrl + "/post")
.header("Content-Encoding", "gzip") //.header("Content-Encoding", "gzip")
.post(RequestBody.create(MediaType.get("application/octet-stream"), fileContent)) .post(RequestBody.create(MediaType.get("application/octet-stream"), fileContent))
.build(); .build();

View File

@ -18,13 +18,19 @@
package com.discordsrv.common.plugin; package com.discordsrv.common.plugin;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List; import java.util.List;
public class Plugin { public class Plugin {
@JsonProperty("identifier")
private final String identifier; private final String identifier;
@JsonProperty("name")
private final String name; private final String name;
@JsonProperty("version")
private final String version; private final String version;
@JsonProperty("authors")
private final List<String> authors; private final List<String> authors;
public Plugin(String name, String version, List<String> authors) { public Plugin(String name, String version, List<String> authors) {

View File

@ -16,10 +16,14 @@ dependencyResolutionManagement {
plugin('indra-git', 'net.kyori.indra.git').version('2.1.1') plugin('indra-git', 'net.kyori.indra.git').version('2.1.1')
// Bukkit // Bukkit
version('bukkit', '1.16.5-R0.1-SNAPSHOT') version('bukkit_minimum', '1.8.8-R0.1-SNAPSHOT')
library('paperapi', 'io.papermc.paper', 'paper-api').versionRef('bukkit') version('bukkit1_12', '1.12.2-R0.1-SNAPSHOT')
library('paperapi-old', 'com.destroystokyo.paper', 'paper-api').versionRef('bukkit') version('bukkit_latest', '1.19.4-R0.1-SNAPSHOT')
library('spigotapi', 'org.spigotmc', 'spigot-api').versionRef('bukkit') version('folia', '1.19.4-R0.1-SNAPSHOT')
library('paperapi', 'io.papermc.paper', 'paper-api').versionRef('bukkit_latest')
library('spigotapi', 'org.spigotmc', 'spigot-api').versionRef('bukkit_minimum')
library('spigotapi-onetwelve', 'org.spigotmc', 'spigot-api').versionRef('bukkit1_12')
library('folia', 'dev.folia', 'folia-api').versionRef('folia')
// Bungee // Bungee
library('bungee', 'net.md-5', 'bungeecord-api').version('1.17-R0.1-SNAPSHOT') library('bungee', 'net.md-5', 'bungeecord-api').version('1.17-R0.1-SNAPSHOT')
@ -141,7 +145,7 @@ rootProject.name = 'DiscordSRV2'
'common', 'common:api', 'common', 'common:api',
'i18n', 'i18n',
'api', 'api',
'bukkit', 'bukkit:loader', 'bukkit', 'bukkit:loader', 'bukkit:folia', 'bukkit:paper', 'bukkit:bukkit1_12',
'bungee', 'bungee:loader', 'bungee', 'bungee:loader',
'sponge', 'sponge:loader', 'sponge', 'sponge:loader',
'velocity' 'velocity'