diff --git a/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java b/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java old mode 100755 new mode 100644 index 843484c..da5ccd4 --- a/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java +++ b/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java @@ -1,29 +1,155 @@ package buttondevteam.discordplugin; import buttondevteam.discordplugin.mcchat.MinecraftChatModule; -import buttondevteam.discordplugin.playerfaker.DiscordFakePlayer; +import buttondevteam.discordplugin.playerfaker.VCMDWrapper; import buttondevteam.discordplugin.playerfaker.VanillaCommandListener; import discord4j.core.object.entity.MessageChannel; import discord4j.core.object.entity.User; import lombok.Getter; import lombok.Setter; +import lombok.experimental.Delegate; +import org.bukkit.*; +import org.bukkit.entity.Entity; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.permissions.PermissibleBase; +import org.bukkit.permissions.ServerOperator; +import org.mockito.Answers; +import org.mockito.Mockito; +import java.util.HashSet; import java.util.UUID; -public class DiscordConnectedPlayer extends DiscordFakePlayer implements IMCPlayer { - private static int nextEntityId = 10000; - private @Getter VanillaCommandListener vanillaCmdListener; +public abstract class DiscordConnectedPlayer extends DiscordSenderBase implements IMCPlayer { + private @Getter VCMDWrapper vanillaCmdListener; @Getter @Setter private boolean loggedIn = false; - public DiscordConnectedPlayer(User user, MessageChannel channel, UUID uuid, String mcname, MinecraftChatModule module) { - super(user, channel, nextEntityId++, uuid, mcname, module); + @Delegate(excludes = ServerOperator.class) + private PermissibleBase origPerm; + + private @Getter String name; + + private @Getter OfflinePlayer basePlayer; + + @Getter + @Setter + private PermissibleBase perm; + + private Location location = Bukkit.getWorlds().get(0).getSpawnLocation(); + + private final MinecraftChatModule module; + + @Getter + private final UUID uniqueId; + + /** + * The parameters must match with {@link #create(User, MessageChannel, UUID, String, MinecraftChatModule)} + */ + protected DiscordConnectedPlayer(User user, MessageChannel channel, UUID uuid, String mcname, + MinecraftChatModule module) { + super(user, channel); + origPerm = perm = new PermissibleBase(basePlayer = Bukkit.getOfflinePlayer(uuid)); + name = mcname; + this.module = module; + uniqueId = uuid; + displayName = mcname; try { - vanillaCmdListener = new VanillaCommandListener<>(this); + vanillaCmdListener = new VCMDWrapper<>(new VanillaCommandListener<>(this)); } catch (NoClassDefFoundError e) { DPUtils.getLogger().warning("Vanilla commands won't be available from Discord due to a compatibility error."); } } + public void setOp(boolean value) { //CraftPlayer-compatible implementation + this.origPerm.setOp(value); + this.perm.recalculatePermissions(); + } + + public boolean isOp() { return this.origPerm.isOp(); } + + @Override + public boolean teleport(Location location) { + if (module.allowFakePlayerTeleports().get()) + this.location = location; + return true; + } + + @Override + public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) { + if (module.allowFakePlayerTeleports().get()) + this.location = location; + return true; + } + + @Override + public boolean teleport(Entity destination) { + if (module.allowFakePlayerTeleports().get()) + this.location = destination.getLocation(); + return true; + } + + @Override + public boolean teleport(Entity destination, PlayerTeleportEvent.TeleportCause cause) { + if (module.allowFakePlayerTeleports().get()) + this.location = destination.getLocation(); + return true; + } + + @Override + public Location getLocation(Location loc) { + if (loc != null) { + loc.setWorld(getWorld()); + loc.setX(location.getX()); + loc.setY(location.getY()); + loc.setZ(location.getZ()); + loc.setYaw(location.getYaw()); + loc.setPitch(location.getPitch()); + } + + return loc; + } + + @Override + public Server getServer() { + return Bukkit.getServer(); + } + + @Override + public void sendRawMessage(String message) { + sendMessage(message); + } + + @Override + public void chat(String msg) { + Bukkit.getPluginManager() + .callEvent(new AsyncPlayerChatEvent(true, this, msg, new HashSet<>(Bukkit.getOnlinePlayers()))); + } + + @Override + public World getWorld() { + return Bukkit.getWorlds().get(0); + } + + @Override + public boolean isOnline() { + return true; + } + + @Override + public Location getLocation() { + return new Location(getWorld(), location.getX(), location.getY(), location.getZ(), + location.getYaw(), location.getPitch()); + } + + @Getter + @Setter + private String displayName; + + public static DiscordConnectedPlayer create(User user, MessageChannel channel, UUID uuid, String mcname, + MinecraftChatModule module) { + return Mockito.mock(DiscordConnectedPlayer.class, Mockito.withSettings() + .defaultAnswer(Answers.CALLS_REAL_METHODS).useConstructor(user, channel, uuid, mcname, module)); + } } diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java b/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java index 680e886..62949c5 100755 --- a/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java +++ b/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java @@ -1,5 +1,6 @@ package buttondevteam.discordplugin; +import buttondevteam.discordplugin.playerfaker.VCMDWrapper; import buttondevteam.discordplugin.playerfaker.VanillaCommandListener; import discord4j.core.object.entity.MessageChannel; import discord4j.core.object.entity.User; @@ -36,13 +37,13 @@ import java.util.*; public class DiscordPlayerSender extends DiscordSenderBase implements IMCPlayer { protected Player player; - private @Getter VanillaCommandListener vanillaCmdListener; + private @Getter VCMDWrapper vanillaCmdListener; public DiscordPlayerSender(User user, MessageChannel channel, Player player) { super(user, channel); this.player = player; try { - vanillaCmdListener = new VanillaCommandListener(this); + vanillaCmdListener = new VCMDWrapper<>(new VanillaCommandListener(this, player)); } catch (NoClassDefFoundError e) { DPUtils.getLogger().warning("Vanilla commands won't be available from Discord due to a compatibility error."); } diff --git a/src/main/java/buttondevteam/discordplugin/IMCPlayer.java b/src/main/java/buttondevteam/discordplugin/IMCPlayer.java index 854db2b..7943f21 100755 --- a/src/main/java/buttondevteam/discordplugin/IMCPlayer.java +++ b/src/main/java/buttondevteam/discordplugin/IMCPlayer.java @@ -1,8 +1,8 @@ package buttondevteam.discordplugin; -import buttondevteam.discordplugin.playerfaker.VanillaCommandListener; +import buttondevteam.discordplugin.playerfaker.VCMDWrapper; import org.bukkit.entity.Player; public interface IMCPlayer> extends Player { - VanillaCommandListener getVanillaCmdListener(); + VCMDWrapper getVanillaCmdListener(); } diff --git a/src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.java b/src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.java index 7aafd27..804e79e 100644 --- a/src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.java +++ b/src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.java @@ -15,9 +15,12 @@ public class GeneralEventBroadcasterModule extends Component { PlayerListWatcher.hookUp(); DPUtils.getLogger().info("Finished hooking into the player list"); hooked = true; - } catch (Exception | NoClassDefFoundError e) { + } catch (Exception e) { TBMCCoreAPI.SendException("Error while hacking the player list! Disable this module if you're on an incompatible version.", e); + } catch (NoClassDefFoundError e) { + DPUtils.getLogger().warning("Error while hacking the player list! Disable this module if you're on an incompatible version."); } + } @Override @@ -29,8 +32,9 @@ public class GeneralEventBroadcasterModule extends Component { else DPUtils.getLogger().info("Didn't have the player list hooked."); hooked = false; - } catch (Exception | NoClassDefFoundError e) { + } catch (Exception e) { TBMCCoreAPI.SendException("Error while hacking the player list!", e); + } catch (NoClassDefFoundError ignored) { } } } diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.java b/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.java index 41599b7..bc1cf20 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.java @@ -107,7 +107,7 @@ public class ChannelconCommand extends ICommand2DC { return true; } val channel = message.getChannel().block(); - DiscordConnectedPlayer dcp = new DiscordConnectedPlayer(message.getAuthor().get(), channel, chp.getUUID(), Bukkit.getOfflinePlayer(chp.getUUID()).getName(), module); + DiscordConnectedPlayer dcp = DiscordConnectedPlayer.create(message.getAuthor().get(), channel, chp.getUUID(), Bukkit.getOfflinePlayer(chp.getUUID()).getName(), module); //Using a fake player with no login/logout, should be fine for this event String groupid = chan.get().getGroupID(dcp); if (groupid == null && !(chan.get() instanceof ChatRoom)) { //ChatRooms don't allow it unless the user joins, which happens later diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.java index 344ecf5..06c3ce1 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.java @@ -26,7 +26,7 @@ public class MCChatPrivate { val op = Bukkit.getOfflinePlayer(mcp.getUUID()); val mcm = ComponentManager.getIfEnabled(MinecraftChatModule.class); if (start) { - val sender = new DiscordConnectedPlayer(user, channel, mcp.getUUID(), op.getName(), mcm); + val sender = DiscordConnectedPlayer.create(user, channel, mcp.getUUID(), op.getName(), mcm); MCChatUtils.addSender(MCChatUtils.ConnectedSenders, user, sender); if (p == null)// Player is offline - If the player is online, that takes precedence MCChatUtils.callLoginEvents(sender); diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java index 92af87d..5f709d2 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java @@ -180,7 +180,10 @@ public class MCChatUtils { } public static Consumer> send(String message) { - return ch -> ch.flatMap(mc -> mc.createMessage(DPUtils.sanitizeString(message))).subscribe(); + return ch -> ch.flatMap(mc -> { + resetLastMessage(mc); + return mc.createMessage(DPUtils.sanitizeString(message)); + }).subscribe(); } public static void forAllowedMCChat(Consumer> action, TBMCSystemChatEvent event) { diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java b/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java index 28e1e72..1534ee9 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java @@ -104,7 +104,7 @@ public class MinecraftChatModule extends Component { if (!mcch.isPresent() || ch == null || user == null || groupid == null) continue; Bukkit.getScheduler().runTask(getPlugin(), () -> { //<-- Needed because of occasional ConcurrentModificationExceptions when creating the player (PermissibleBase) - val dcp = new DiscordConnectedPlayer(user, (MessageChannel) ch, UUID.fromString(chcon.getString("mcuid")), chcon.getString("mcname"), this); + val dcp = DiscordConnectedPlayer.create(user, (MessageChannel) ch, UUID.fromString(chcon.getString("mcuid")), chcon.getString("mcname"), this); MCChatCustom.addCustomChat((MessageChannel) ch, groupid, mcch.get(), user, dcp, toggles, brtoggles.stream().map(TBMCSystemChatEvent.BroadcastTarget::get).filter(Objects::nonNull).collect(Collectors.toSet())); }); } diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.java b/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.java new file mode 100644 index 0000000..95a7811 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.java @@ -0,0 +1,12 @@ +package buttondevteam.discordplugin.playerfaker; + +import buttondevteam.discordplugin.DiscordSenderBase; +import buttondevteam.discordplugin.IMCPlayer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class VCMDWrapper> { + @Getter //Needed to mock the player + private final VanillaCommandListener listener; +} diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.java b/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.java index 29f3a13..9f2db22 100755 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.java +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.java @@ -87,7 +87,7 @@ public class VanillaCommandListener> if (!vcmd.testPermission(sender)) return true; - ICommandListener icommandlistener = sender.getVanillaCmdListener(); + ICommandListener icommandlistener = sender.getVanillaCmdListener().getListener(); String[] args = cmdstr.split(" "); args = Arrays.copyOfRange(args, 1, args.length); try {