Use reflection for VanillaCommandListener
Moved error handling to the wrapper Fixed commands on Discord getting executed even if the preprocess event got cancelled
This commit is contained in:
parent
1b747ab99f
commit
50cc0c8e61
5 changed files with 173 additions and 26 deletions
|
@ -67,13 +67,7 @@ public abstract class DiscordConnectedPlayer extends DiscordSenderBase implement
|
||||||
this.module = module;
|
this.module = module;
|
||||||
uniqueId = uuid;
|
uniqueId = uuid;
|
||||||
displayName = mcname;
|
displayName = mcname;
|
||||||
try {
|
|
||||||
vanillaCmdListener = new VCMDWrapper(VCMDWrapper.createListener(this));
|
vanillaCmdListener = new VCMDWrapper(VCMDWrapper.createListener(this));
|
||||||
if (vanillaCmdListener.getListener() == null)
|
|
||||||
DPUtils.getLogger().warning("Vanilla commands won't be available from Discord due to a compatibility error.");
|
|
||||||
} catch (NoClassDefFoundError e) {
|
|
||||||
DPUtils.getLogger().warning("Vanilla commands won't be available from Discord due to a compatibility error.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -17,13 +17,7 @@ public abstract class DiscordPlayerSender extends DiscordSenderBase implements I
|
||||||
public DiscordPlayerSender(User user, MessageChannel channel, Player player) {
|
public DiscordPlayerSender(User user, MessageChannel channel, Player player) {
|
||||||
super(user, channel);
|
super(user, channel);
|
||||||
this.player = player;
|
this.player = player;
|
||||||
try {
|
|
||||||
vanillaCmdListener = new VCMDWrapper(VCMDWrapper.createListener(this, player));
|
vanillaCmdListener = new VCMDWrapper(VCMDWrapper.createListener(this, player));
|
||||||
if (vanillaCmdListener.getListener() == null)
|
|
||||||
DPUtils.getLogger().warning("Vanilla commands won't be available from Discord due to a compatibility error.");
|
|
||||||
} catch (NoClassDefFoundError e) {
|
|
||||||
DPUtils.getLogger().warning("Vanilla commands won't be available from Discord due to a compatibility error.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -6,6 +6,7 @@ import buttondevteam.discordplugin.listeners.CommandListener;
|
||||||
import buttondevteam.discordplugin.listeners.CommonListeners;
|
import buttondevteam.discordplugin.listeners.CommonListeners;
|
||||||
import buttondevteam.discordplugin.playerfaker.VanillaCommandListener;
|
import buttondevteam.discordplugin.playerfaker.VanillaCommandListener;
|
||||||
import buttondevteam.discordplugin.playerfaker.VanillaCommandListener14;
|
import buttondevteam.discordplugin.playerfaker.VanillaCommandListener14;
|
||||||
|
import buttondevteam.discordplugin.playerfaker.VanillaCommandListener15;
|
||||||
import buttondevteam.discordplugin.util.Timings;
|
import buttondevteam.discordplugin.util.Timings;
|
||||||
import buttondevteam.lib.*;
|
import buttondevteam.lib.*;
|
||||||
import buttondevteam.lib.chat.ChatMessage;
|
import buttondevteam.lib.chat.ChatMessage;
|
||||||
|
@ -361,27 +362,31 @@ public class MCChatListener implements Listener {
|
||||||
+ "ou can access all of your regular commands (even offline) in private chat: DM me `mcchat`!");
|
+ "ou can access all of your regular commands (even offline) in private chat: DM me `mcchat`!");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
Bukkit.getLogger().info(dsender.getName() + " issued command from Discord: /" + cmd);
|
||||||
val channel = clmd == null ? user.channel().get() : clmd.mcchannel;
|
val channel = clmd == null ? user.channel().get() : clmd.mcchannel;
|
||||||
val ev = new TBMCCommandPreprocessEvent(dsender, channel, dmessage, clmd == null ? dsender : clmd.dcp);
|
val ev = new TBMCCommandPreprocessEvent(dsender, channel, dmessage, clmd == null ? dsender : clmd.dcp);
|
||||||
Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> Bukkit.getPluginManager().callEvent(ev));
|
|
||||||
if (ev.isCancelled())
|
|
||||||
return true;
|
|
||||||
Bukkit.getScheduler().runTask(DiscordPlugin.plugin, //Commands need to be run sync
|
Bukkit.getScheduler().runTask(DiscordPlugin.plugin, //Commands need to be run sync
|
||||||
() -> {
|
() -> {
|
||||||
|
Bukkit.getPluginManager().callEvent(ev);
|
||||||
|
if (ev.isCancelled())
|
||||||
|
return;
|
||||||
try {
|
try {
|
||||||
String mcpackage = Bukkit.getServer().getClass().getPackage().getName();
|
String mcpackage = Bukkit.getServer().getClass().getPackage().getName();
|
||||||
if (mcpackage.contains("1_12"))
|
if (mcpackage.contains("1_12"))
|
||||||
VanillaCommandListener.runBukkitOrVanillaCommand(dsender, cmd);
|
VanillaCommandListener.runBukkitOrVanillaCommand(dsender, cmd);
|
||||||
else if (mcpackage.contains("1_14") || mcpackage.contains("1_15"))
|
else if (mcpackage.contains("1_14"))
|
||||||
VanillaCommandListener14.runBukkitOrVanillaCommand(dsender, cmd);
|
VanillaCommandListener14.runBukkitOrVanillaCommand(dsender, cmd);
|
||||||
|
else if (mcpackage.contains("1_15"))
|
||||||
|
VanillaCommandListener15.runBukkitOrVanillaCommand(dsender, cmd);
|
||||||
else
|
else
|
||||||
Bukkit.dispatchCommand(dsender, cmd);
|
Bukkit.dispatchCommand(dsender, cmd);
|
||||||
} catch (NoClassDefFoundError e) {
|
} catch (NoClassDefFoundError e) {
|
||||||
TBMCCoreAPI.SendException("A class is not found when trying to run command " + cmd + "!", e);
|
TBMCCoreAPI.SendException("A class is not found when trying to run command " + cmd + "!", e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
TBMCCoreAPI.SendException("An error occurred when trying to run command " + cmd + "!", e);
|
||||||
}
|
}
|
||||||
Bukkit.getLogger().info(dsender.getName() + " issued command from Discord: /" + cmd);
|
|
||||||
});
|
});
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package buttondevteam.discordplugin.playerfaker;
|
package buttondevteam.discordplugin.playerfaker;
|
||||||
|
|
||||||
|
import buttondevteam.discordplugin.DPUtils;
|
||||||
import buttondevteam.discordplugin.DiscordSenderBase;
|
import buttondevteam.discordplugin.DiscordSenderBase;
|
||||||
import buttondevteam.discordplugin.IMCPlayer;
|
import buttondevteam.discordplugin.IMCPlayer;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
@ -28,12 +29,29 @@ public class VCMDWrapper {
|
||||||
* @param bukkitplayer The Bukkit player to send the raw message to
|
* @param bukkitplayer The Bukkit player to send the raw message to
|
||||||
*/
|
*/
|
||||||
public static <T extends DiscordSenderBase & IMCPlayer<T>> Object createListener(T player, Player bukkitplayer) {
|
public static <T extends DiscordSenderBase & IMCPlayer<T>> Object createListener(T player, Player bukkitplayer) {
|
||||||
|
try {
|
||||||
|
Object ret;
|
||||||
String mcpackage = Bukkit.getServer().getClass().getPackage().getName();
|
String mcpackage = Bukkit.getServer().getClass().getPackage().getName();
|
||||||
if (mcpackage.contains("1_12"))
|
if (mcpackage.contains("1_12"))
|
||||||
return bukkitplayer == null ? new VanillaCommandListener<>(player) : new VanillaCommandListener<>(player, bukkitplayer);
|
ret = new VanillaCommandListener<>(player, bukkitplayer);
|
||||||
else if (mcpackage.contains("1_14") || mcpackage.contains("1_15"))
|
else if (mcpackage.contains("1_14"))
|
||||||
return bukkitplayer == null ? new VanillaCommandListener14<>(player) : new VanillaCommandListener14<>(player, bukkitplayer);
|
ret = new VanillaCommandListener14<>(player, bukkitplayer);
|
||||||
|
else if (mcpackage.contains("1_15"))
|
||||||
|
ret = VanillaCommandListener15.create(player, bukkitplayer); //bukkitplayer may be null but that's fine
|
||||||
else
|
else
|
||||||
|
ret = null;
|
||||||
|
if (ret == null)
|
||||||
|
compatWarning();
|
||||||
|
return ret;
|
||||||
|
} catch (NoClassDefFoundError | Exception e) {
|
||||||
|
compatWarning();
|
||||||
|
if (!(e instanceof NoClassDefFoundError))
|
||||||
|
e.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void compatWarning() {
|
||||||
|
DPUtils.getLogger().warning("Vanilla commands won't be available from Discord due to a compatibility error.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
package buttondevteam.discordplugin.playerfaker;
|
||||||
|
|
||||||
|
import buttondevteam.discordplugin.DiscordSenderBase;
|
||||||
|
import buttondevteam.discordplugin.IMCPlayer;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.val;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.SimpleCommandMap;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.mockito.Answers;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as {@link VanillaCommandListener14} but with reflection
|
||||||
|
*/
|
||||||
|
public class VanillaCommandListener15<T extends DiscordSenderBase & IMCPlayer<T>> {
|
||||||
|
private @Getter T player;
|
||||||
|
private static Class<?> vcwcl;
|
||||||
|
private static String nms;
|
||||||
|
|
||||||
|
protected VanillaCommandListener15(T player, Player bukkitplayer) {
|
||||||
|
this.player = player;
|
||||||
|
if (bukkitplayer != null && !bukkitplayer.getClass().getSimpleName().endsWith("CraftPlayer"))
|
||||||
|
throw new ClassCastException("bukkitplayer must be a Bukkit player!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will only send raw vanilla messages to the sender in plain text.
|
||||||
|
*
|
||||||
|
* @param player The Discord sender player (the wrapper)
|
||||||
|
*/
|
||||||
|
public static <T extends DiscordSenderBase & IMCPlayer<T>> VanillaCommandListener15<T> create(T player) throws Exception {
|
||||||
|
return create(player, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will send both raw vanilla messages to the sender in plain text and forward the raw message to the provided player.
|
||||||
|
*
|
||||||
|
* @param player The Discord sender player (the wrapper)
|
||||||
|
* @param bukkitplayer The Bukkit player to send the raw message to
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <T extends DiscordSenderBase & IMCPlayer<T>> VanillaCommandListener15<T> create(T player, Player bukkitplayer) throws Exception {
|
||||||
|
if (vcwcl == null) {
|
||||||
|
String pkg = Bukkit.getServer().getClass().getPackage().getName();
|
||||||
|
vcwcl = Class.forName(pkg + ".command.VanillaCommandWrapper");
|
||||||
|
}
|
||||||
|
if (nms == null) {
|
||||||
|
var server = Bukkit.getServer();
|
||||||
|
nms = server.getClass().getMethod("getServer").invoke(server).getClass().getPackage().getName(); //org.mockito.codegen
|
||||||
|
}
|
||||||
|
var iclcl = Class.forName(nms + ".ICommandListener");
|
||||||
|
return Mockito.mock(VanillaCommandListener15.class, Mockito.withSettings().stubOnly()
|
||||||
|
.useConstructor(player, bukkitplayer).extraInterfaces(iclcl).defaultAnswer(invocation -> {
|
||||||
|
if (invocation.getMethod().getName().equals("sendMessage")) {
|
||||||
|
var icbc = invocation.getArgument(0);
|
||||||
|
player.sendMessage((String) icbc.getClass().getMethod("getString").invoke(icbc));
|
||||||
|
if (bukkitplayer != null) {
|
||||||
|
var handle = bukkitplayer.getClass().getMethod("getHandle").invoke(bukkitplayer);
|
||||||
|
handle.getClass().getMethod("sendMessage", icbc.getClass()).invoke(handle, icbc);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!Modifier.isAbstract(invocation.getMethod().getModifiers()))
|
||||||
|
return invocation.callRealMethod();
|
||||||
|
if (invocation.getMethod().getReturnType() == boolean.class)
|
||||||
|
return true; //shouldSend... shouldBroadcast...
|
||||||
|
if (invocation.getMethod().getReturnType() == CommandSender.class)
|
||||||
|
return player;
|
||||||
|
return Answers.RETURNS_DEFAULTS.answer(invocation);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean runBukkitOrVanillaCommand(DiscordSenderBase dsender, String cmdstr) throws Exception {
|
||||||
|
var server = Bukkit.getServer();
|
||||||
|
var cmap = (SimpleCommandMap) server.getClass().getMethod("getCommandMap").invoke(server);
|
||||||
|
val cmd = cmap.getCommand(cmdstr.split(" ")[0].toLowerCase());
|
||||||
|
if (!(dsender instanceof Player) || !vcwcl.isAssignableFrom(dsender.getClass()))
|
||||||
|
return Bukkit.dispatchCommand(dsender, cmdstr); // Unconnected users are treated well in vanilla cmds
|
||||||
|
|
||||||
|
if (!(dsender instanceof IMCPlayer))
|
||||||
|
throw new ClassCastException(
|
||||||
|
"dsender needs to implement IMCPlayer to use vanilla commands as it implements Player.");
|
||||||
|
|
||||||
|
IMCPlayer<?> sender = (IMCPlayer<?>) dsender; // Don't use val on recursive interfaces :P
|
||||||
|
|
||||||
|
if (!(Boolean) vcwcl.getMethod("testPermission", CommandSender.class).invoke(cmd, sender))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var cworld = Bukkit.getWorlds().get(0);
|
||||||
|
val world = cworld.getClass().getMethod("getHandle").invoke(cworld);
|
||||||
|
var icommandlistener = sender.getVanillaCmdListener().getListener();
|
||||||
|
var nms = icommandlistener.getClass().getPackage().getName();
|
||||||
|
var clwcl = Class.forName(nms + ".CommandListenerWrapper");
|
||||||
|
var v3dcl = Class.forName(nms + ".Vec3D");
|
||||||
|
var v2fcl = Class.forName(nms + ".Vec2F");
|
||||||
|
var icbcl = Class.forName(nms + ".IChatBaseComponent");
|
||||||
|
var mcscl = Class.forName(nms + ".MinecraftServer");
|
||||||
|
var ecl = Class.forName(nms + ".Entity");
|
||||||
|
var cctcl = Class.forName(nms + ".ChatComponentText");
|
||||||
|
Object wrapper = clwcl.getConstructor(icommandlistener.getClass(), v3dcl, v2fcl, world.getClass(), int.class, String.class, icbcl, mcscl, ecl)
|
||||||
|
.newInstance(icommandlistener,
|
||||||
|
v3dcl.getConstructor(double.class, double.class, double.class).newInstance(0, 0, 0),
|
||||||
|
v2fcl.getConstructor(float.class, float.class).newInstance(0, 0),
|
||||||
|
world, 0, sender.getName(), cctcl.getConstructor(String.class).newInstance(sender.getName()),
|
||||||
|
world.getClass().getMethod("getMinecraftServer").invoke(world), null);
|
||||||
|
/*val wrapper = new CommandListenerWrapper(icommandlistener, new Vec3D(0, 0, 0),
|
||||||
|
new Vec2F(0, 0), world, 0, sender.getName(),
|
||||||
|
new ChatComponentText(sender.getName()), world.getMinecraftServer(), null);*/
|
||||||
|
var pncscl = Class.forName(vcwcl.getPackage().getName() + ".ProxiedNativeCommandSender");
|
||||||
|
Object pncs = pncscl.getConstructor(clwcl, sender.getClass(), sender.getClass())
|
||||||
|
.newInstance(wrapper, sender, sender);
|
||||||
|
String[] args = cmdstr.split(" ");
|
||||||
|
args = Arrays.copyOfRange(args, 1, args.length);
|
||||||
|
try {
|
||||||
|
return cmd.execute((CommandSender) pncs, cmd.getLabel(), args);
|
||||||
|
} catch (Exception commandexception) {
|
||||||
|
if (!commandexception.getClass().getSimpleName().equals("CommandException"))
|
||||||
|
throw commandexception;
|
||||||
|
// Taken from CommandHandler
|
||||||
|
var cmcl = Class.forName(nms + ".ChatMessage");
|
||||||
|
var chatmessage = cmcl.getConstructor(String.class, Object[].class)
|
||||||
|
.newInstance(commandexception.getMessage(),
|
||||||
|
new Object[]{commandexception.getClass().getMethod("a").invoke(commandexception)});
|
||||||
|
var modifier = cmcl.getMethod("getChatModifier").invoke(chatmessage);
|
||||||
|
var ecfcl = Class.forName(nms + ".EnumChatFormat");
|
||||||
|
modifier.getClass().getMethod("setColor", ecfcl).invoke(modifier, ecfcl.getField("RED").get(null));
|
||||||
|
icommandlistener.getClass().getMethod("sendMessage", icbcl).invoke(icommandlistener, chatmessage);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue