LP injecting, fake player fixes

#96
LuckPerms
Added support for excluding plugins from certain events from code
Also might have fixed some message timeouts by relocating netty
This commit is contained in:
Norbi Peti 2019-05-25 01:48:06 +02:00
parent 2bdba0af22
commit bf538d9bd0
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
8 changed files with 1114 additions and 787 deletions

14
pom.xml
View file

@ -69,6 +69,14 @@
</excludes> <!-- http://stackoverflow.com/questions/28458058/maven-shade-plugin-exclude-a-dependency-and-all-its-transitive-dependencies --> </excludes> <!-- http://stackoverflow.com/questions/28458058/maven-shade-plugin-exclude-a-dependency-and-all-its-transitive-dependencies -->
</artifactSet> </artifactSet>
<minimizeJar>true</minimizeJar> <minimizeJar>true</minimizeJar>
<relocations>
<relocation>
<pattern>io.netty</pattern>
<shadedPattern>buttondevteam.discordplugin.io.netty</shadedPattern>
<excludes>
</excludes>
</relocation>
</relocations>
</configuration> </configuration>
</execution> </execution>
</executions> </executions>
@ -242,6 +250,12 @@
<artifactId>blockhound</artifactId> <artifactId>blockhound</artifactId>
<version>1.0.0.M3</version> <version>1.0.0.M3</version>
</dependency> --> </dependency> -->
<dependency>
<groupId>com.github.lucko</groupId>
<artifactId>LuckPerms</artifactId>
<version>v4.4</version>
<scope>provided</scope>
</dependency>
</dependencies> </dependencies>
<profiles> <profiles>

View file

@ -6,15 +6,19 @@ import buttondevteam.discordplugin.playerfaker.VanillaCommandListener;
import discord4j.core.object.entity.MessageChannel; import discord4j.core.object.entity.MessageChannel;
import discord4j.core.object.entity.User; import discord4j.core.object.entity.User;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import java.util.UUID; import java.util.UUID;
public class DiscordConnectedPlayer extends DiscordFakePlayer implements IMCPlayer<DiscordConnectedPlayer> { public class DiscordConnectedPlayer extends DiscordFakePlayer implements IMCPlayer<DiscordConnectedPlayer> {
private static int nextEntityId = 10000; private static int nextEntityId = 10000;
private @Getter VanillaCommandListener<DiscordConnectedPlayer> vanillaCmdListener; private @Getter VanillaCommandListener<DiscordConnectedPlayer> vanillaCmdListener;
@Getter
@Setter
private boolean loggedIn = false;
public DiscordConnectedPlayer(User user, MessageChannel channel, UUID uuid, String mcname, MinecraftChatModule module) { public DiscordConnectedPlayer(User user, MessageChannel channel, UUID uuid, String mcname, MinecraftChatModule module) {
super(user, channel, nextEntityId++, uuid, mcname ,module); super(user, channel, nextEntityId++, uuid, mcname, module);
vanillaCmdListener = new VanillaCommandListener<>(this); vanillaCmdListener = new VanillaCommandListener<>(this);
} }

View file

@ -3,15 +3,12 @@ package buttondevteam.discordplugin.mcchat;
import buttondevteam.core.ComponentManager; import buttondevteam.core.ComponentManager;
import buttondevteam.discordplugin.DiscordConnectedPlayer; import buttondevteam.discordplugin.DiscordConnectedPlayer;
import buttondevteam.discordplugin.DiscordPlayer; import buttondevteam.discordplugin.DiscordPlayer;
import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.lib.player.TBMCPlayer; import buttondevteam.lib.player.TBMCPlayer;
import discord4j.core.object.entity.MessageChannel; import discord4j.core.object.entity.MessageChannel;
import discord4j.core.object.entity.PrivateChannel; import discord4j.core.object.entity.PrivateChannel;
import discord4j.core.object.entity.User; import discord4j.core.object.entity.User;
import lombok.val; import lombok.val;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.event.Event;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.ArrayList; import java.util.ArrayList;
@ -32,11 +29,14 @@ public class MCChatPrivate {
val sender = new DiscordConnectedPlayer(user, channel, mcp.getUUID(), op.getName(), mcm); val sender = new DiscordConnectedPlayer(user, channel, mcp.getUUID(), op.getName(), mcm);
MCChatUtils.addSender(MCChatUtils.ConnectedSenders, user, sender); MCChatUtils.addSender(MCChatUtils.ConnectedSenders, user, sender);
if (p == null)// Player is offline - If the player is online, that takes precedence if (p == null)// Player is offline - If the player is online, that takes precedence
MCListener.callLoginEvents(sender); MCChatUtils.callLoginEvents(sender);
} else { } else {
val sender = MCChatUtils.removeSender(MCChatUtils.ConnectedSenders, channel.getId(), user); val sender = MCChatUtils.removeSender(MCChatUtils.ConnectedSenders, channel.getId(), user);
if (p == null)// Player is offline - If the player is online, that takes precedence assert sender != null;
callEventSync(new PlayerQuitEvent(sender, "")); if (p == null // Player is offline - If the player is online, that takes precedence
&& sender.isLoggedIn()) //Don't call the quit event if login failed
MCChatUtils.callLogoutEvent(sender, true);
sender.setLoggedIn(false);
} }
} // ---- PermissionsEx warning is normal on logout ---- } // ---- PermissionsEx warning is normal on logout ----
if (!start) if (!start)
@ -60,11 +60,8 @@ public class MCChatPrivate {
for (val entry : MCChatUtils.ConnectedSenders.entrySet()) for (val entry : MCChatUtils.ConnectedSenders.entrySet())
for (val valueEntry : entry.getValue().entrySet()) for (val valueEntry : entry.getValue().entrySet())
if (MCChatUtils.getSender(MCChatUtils.OnlineSenders, valueEntry.getKey(), valueEntry.getValue().getUser()) == null) //If the player is online then the fake player was already logged out if (MCChatUtils.getSender(MCChatUtils.OnlineSenders, valueEntry.getKey(), valueEntry.getValue().getUser()) == null) //If the player is online then the fake player was already logged out
MCChatUtils.callEventExcludingSome(new PlayerQuitEvent(valueEntry.getValue(), "")); //This is sync MCChatUtils.callLogoutEvent(valueEntry.getValue(), false); //This is sync
MCChatUtils.ConnectedSenders.clear(); MCChatUtils.ConnectedSenders.clear();
} }
private static void callEventSync(Event event) {
Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> MCChatUtils.callEventExcludingSome(event));
}
} }

View file

@ -5,6 +5,7 @@ import buttondevteam.discordplugin.*;
import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule; import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule;
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.TBMCSystemChatEvent; import buttondevteam.lib.TBMCSystemChatEvent;
import com.google.common.collect.Sets;
import discord4j.core.object.entity.*; import discord4j.core.object.entity.*;
import discord4j.core.object.util.Snowflake; import discord4j.core.object.util.Snowflake;
import io.netty.util.collection.LongObjectHashMap; import io.netty.util.collection.LongObjectHashMap;
@ -15,14 +16,20 @@ import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.event.Event; import org.bukkit.event.Event;
import org.bukkit.event.HandlerList; import org.bukkit.event.HandlerList;
import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.AuthorNagException; import org.bukkit.plugin.AuthorNagException;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredListener; import org.bukkit.plugin.RegisteredListener;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.net.InetAddress;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -43,6 +50,7 @@ public class MCChatUtils {
static @Nullable LastMsgData lastmsgdata; static @Nullable LastMsgData lastmsgdata;
static LongObjectHashMap<Message> lastmsgfromd = new LongObjectHashMap<>(); // Last message sent by a Discord user, used for clearing checkmarks static LongObjectHashMap<Message> lastmsgfromd = new LongObjectHashMap<>(); // Last message sent by a Discord user, used for clearing checkmarks
private static MinecraftChatModule module; private static MinecraftChatModule module;
private static HashMap<Class<? extends Event>, HashSet<String>> staticExcludedPlugins = new HashMap<>();
public static void updatePlayerList() { public static void updatePlayerList() {
if (notEnabled()) return; if (notEnabled()) return;
@ -227,9 +235,23 @@ public class MCChatUtils {
//If it gets here, it's sending a message to a non-chat channel //If it gets here, it's sending a message to a non-chat channel
} }
public static void addStaticExcludedPlugin(Class<? extends Event> event, String plugin) {
staticExcludedPlugins.compute(event, (e, hs) -> hs == null
? Sets.newHashSet(plugin)
: (hs.add(plugin) ? hs : hs));
}
public static void callEventExcludingSome(Event event) { public static void callEventExcludingSome(Event event) {
if (notEnabled()) return; if (notEnabled()) return;
callEventExcluding(event, false, module.excludedPlugins().get()); val second = staticExcludedPlugins.get(event.getClass());
String[] first = module.excludedPlugins().get();
String[] both = second == null ? first
: Arrays.copyOf(first, first.length + second.size());
int i = first.length;
if (second != null)
for (String plugin : second)
both[i++] = plugin;
callEventExcluding(event, false, both);
} }
/** /**
@ -290,6 +312,50 @@ public class MCChatUtils {
} }
} }
/**
* Call it from an async thread.
*/
public static void callLoginEvents(DiscordConnectedPlayer dcp) {
Consumer<Supplier<String>> loginFail = kickMsg -> {
dcp.sendMessage("Minecraft chat disabled, as the login failed: " + kickMsg.get());
MCChatPrivate.privateMCChat(dcp.getChannel(), false, dcp.getUser(), dcp.getChromaUser());
}; //Probably also happens if the user is banned or so
val event = new AsyncPlayerPreLoginEvent(dcp.getName(), InetAddress.getLoopbackAddress(), dcp.getUniqueId());
callEventExcludingSome(event);
if (event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) {
loginFail.accept(event::getKickMessage);
return;
}
Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> {
val ev = new PlayerLoginEvent(dcp, "localhost", InetAddress.getLoopbackAddress());
callEventExcludingSome(ev);
if (ev.getResult() != PlayerLoginEvent.Result.ALLOWED) {
loginFail.accept(ev::getKickMessage);
return;
}
callEventExcludingSome(new PlayerJoinEvent(dcp, ""));
dcp.setLoggedIn(true);
});
}
/**
* Only calls the events if the player is actually logged in
*
* @param dcp The player
* @param needsSync Whether we're in an async thread
*/
public static void callLogoutEvent(DiscordConnectedPlayer dcp, boolean needsSync) {
if (!dcp.isLoggedIn()) return;
val event = new PlayerQuitEvent(dcp, "");
if (needsSync) callEventSync(event);
else callEventExcludingSome(event);
dcp.setLoggedIn(false);
}
static void callEventSync(Event event) {
Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> callEventExcludingSome(event));
}
@RequiredArgsConstructor @RequiredArgsConstructor
public static class LastMsgData { public static class LastMsgData {
public Message message; public Message message;

View file

@ -18,12 +18,12 @@ 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 org.bukkit.event.player.*; import org.bukkit.event.player.PlayerKickEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerLoginEvent.Result; import org.bukkit.event.player.PlayerLoginEvent.Result;
import org.bukkit.event.server.BroadcastMessageEvent; import org.bukkit.event.server.BroadcastMessageEvent;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import java.net.InetAddress;
import java.util.Objects; import java.util.Objects;
@RequiredArgsConstructor @RequiredArgsConstructor
@ -38,7 +38,7 @@ class MCListener implements Listener {
return; return;
MCChatUtils.ConnectedSenders.values().stream().flatMap(v -> v.values().stream()) //Only private mcchat should be in ConnectedSenders MCChatUtils.ConnectedSenders.values().stream().flatMap(v -> v.values().stream()) //Only private mcchat should be in ConnectedSenders
.filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny() .filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny()
.ifPresent(dcp -> MCChatUtils.callEventExcludingSome(new PlayerQuitEvent(dcp, ""))); .ifPresent(dcp -> MCChatUtils.callLogoutEvent(dcp, false));
} }
@EventHandler(priority = EventPriority.LOWEST) @EventHandler(priority = EventPriority.LOWEST)
@ -70,30 +70,13 @@ class MCListener implements Listener {
Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin,
() -> MCChatUtils.ConnectedSenders.values().stream().flatMap(v -> v.values().stream()) () -> MCChatUtils.ConnectedSenders.values().stream().flatMap(v -> v.values().stream())
.filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny() .filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny()
.ifPresent(MCListener::callLoginEvents)); .ifPresent(MCChatUtils::callLoginEvents));
Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin, Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin,
ChromaBot.getInstance()::updatePlayerList, 5); ChromaBot.getInstance()::updatePlayerList, 5);
final String message = e.GetPlayer().PlayerName().get() + " left the game"; final String message = e.GetPlayer().PlayerName().get() + " left the game";
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true); MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true);
} }
/**
* Call it from an async thread.
*/
public static void callLoginEvents(DiscordConnectedPlayer dcp) {
val event = new AsyncPlayerPreLoginEvent(dcp.getName(), InetAddress.getLoopbackAddress(), dcp.getUniqueId());
MCChatUtils.callEventExcludingSome(event);
if (event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED)
return;
Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> {
val ev = new PlayerLoginEvent(dcp, "localhost", InetAddress.getLoopbackAddress());
MCChatUtils.callEventExcludingSome(ev);
if (ev.getResult() != Result.ALLOWED)
return;
MCChatUtils.callEventExcludingSome(new PlayerJoinEvent(dcp, ""));
});
}
@EventHandler(priority = EventPriority.HIGHEST) @EventHandler(priority = EventPriority.HIGHEST)
public void onPlayerKick(PlayerKickEvent e) { public void onPlayerKick(PlayerKickEvent e) {
/*if (!DiscordPlugin.hooked && !e.getReason().equals("The server is restarting") /*if (!DiscordPlugin.hooked && !e.getReason().equals("The server is restarting")

View file

@ -1,9 +1,11 @@
package buttondevteam.discordplugin.mcchat; package buttondevteam.discordplugin.mcchat;
import buttondevteam.core.MainPlugin;
import buttondevteam.core.component.channel.Channel; import buttondevteam.core.component.channel.Channel;
import buttondevteam.discordplugin.DPUtils; import buttondevteam.discordplugin.DPUtils;
import buttondevteam.discordplugin.DiscordConnectedPlayer; import buttondevteam.discordplugin.DiscordConnectedPlayer;
import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.discordplugin.playerfaker.perm.LPInjector;
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.TBMCSystemChatEvent; import buttondevteam.lib.TBMCSystemChatEvent;
import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.Component;
@ -106,6 +108,12 @@ public class MinecraftChatModule extends Component<DiscordPlugin> {
}); });
} }
} }
try {
new LPInjector(MainPlugin.Instance);
} catch (Exception e) {
TBMCCoreAPI.SendException("Failed to init LuckPerms injector", e);
}
} }
@Override @Override

View file

@ -5,6 +5,7 @@ import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
import discord4j.core.object.entity.MessageChannel; import discord4j.core.object.entity.MessageChannel;
import discord4j.core.object.entity.User; import discord4j.core.object.entity.User;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Delegate; import lombok.experimental.Delegate;
import org.bukkit.*; import org.bukkit.*;
import org.bukkit.advancement.Advancement; import org.bukkit.advancement.Advancement;
@ -17,6 +18,7 @@ import org.bukkit.entity.Player;
import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.map.MapView; import org.bukkit.map.MapView;
import org.bukkit.permissions.PermissibleBase; import org.bukkit.permissions.PermissibleBase;
import org.bukkit.permissions.ServerOperator;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.scoreboard.Scoreboard; import org.bukkit.scoreboard.Scoreboard;
@ -27,15 +29,28 @@ import java.util.*;
public class DiscordFakePlayer extends DiscordHumanEntity implements Player { public class DiscordFakePlayer extends DiscordHumanEntity implements Player {
protected DiscordFakePlayer(User user, MessageChannel channel, int entityId, UUID uuid, String mcname, MinecraftChatModule module) { protected DiscordFakePlayer(User user, MessageChannel channel, int entityId, UUID uuid, String mcname, MinecraftChatModule module) {
super(user, channel, entityId, uuid, module); super(user, channel, entityId, uuid, module);
perm = new PermissibleBase(Bukkit.getOfflinePlayer(uuid)); origPerm = perm = new PermissibleBase(basePlayer = Bukkit.getOfflinePlayer(uuid));
name = mcname; name = mcname;
} }
@Delegate @Delegate(excludes = ServerOperator.class)
private PermissibleBase perm; private PermissibleBase origPerm;
private @Getter String name; private @Getter String name;
private @Getter OfflinePlayer basePlayer;
@Getter
@Setter
private PermissibleBase perm;
public void setOp(boolean value) { //CraftPlayer-compatible implementation
this.origPerm.setOp(value);
this.perm.recalculatePermissions();
}
public boolean isOp() { return this.origPerm.isOp(); }
@Override @Override
public EntityType getType() { public EntityType getType() {
return EntityType.PLAYER; return EntityType.PLAYER;

View file

@ -0,0 +1,240 @@
package buttondevteam.discordplugin.playerfaker.perm;
import buttondevteam.core.MainPlugin;
import buttondevteam.discordplugin.mcchat.MCChatUtils;
import buttondevteam.discordplugin.playerfaker.DiscordFakePlayer;
import buttondevteam.lib.TBMCCoreAPI;
import me.lucko.luckperms.bukkit.LPBukkitBootstrap;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.bukkit.inject.dummy.DummyPermissibleBase;
import me.lucko.luckperms.bukkit.inject.permissible.LPPermissible;
import me.lucko.luckperms.bukkit.listeners.BukkitConnectionListener;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.locale.message.Message;
import me.lucko.luckperms.common.model.User;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.permissions.PermissibleBase;
import org.bukkit.permissions.PermissionAttachment;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
public final class LPInjector implements Listener { //Disable login event for LuckPerms
private LPBukkitPlugin plugin;
private BukkitConnectionListener connectionListener;
private Set<UUID> deniedLogin;
private Field detectedCraftBukkitOfflineMode;
private Method printCraftBukkitOfflineModeError;
private Field PERMISSIBLE_BASE_ATTACHMENTS_FIELD;
private Method convertAndAddAttachments;
private Method getActive;
private Method setOldPermissible;
private Method getOldPermissible;
public LPInjector(MainPlugin mp) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException {
LPBukkitBootstrap bs = (LPBukkitBootstrap) Bukkit.getPluginManager().getPlugin("LuckPerms");
Field field = LPBukkitBootstrap.class.getDeclaredField("plugin");
field.setAccessible(true);
plugin = (LPBukkitPlugin) field.get(bs);
MCChatUtils.addStaticExcludedPlugin(PlayerLoginEvent.class, "LuckPerms");
MCChatUtils.addStaticExcludedPlugin(PlayerQuitEvent.class, "LuckPerms");
field = LPBukkitPlugin.class.getDeclaredField("connectionListener");
field.setAccessible(true);
connectionListener = (BukkitConnectionListener) field.get(plugin);
field = connectionListener.getClass().getDeclaredField("deniedLogin");
field.setAccessible(true);
//noinspection unchecked
deniedLogin = (Set<UUID>) field.get(connectionListener);
field = connectionListener.getClass().getDeclaredField("detectedCraftBukkitOfflineMode");
field.setAccessible(true);
detectedCraftBukkitOfflineMode = field;
printCraftBukkitOfflineModeError = connectionListener.getClass().getDeclaredMethod("printCraftBukkitOfflineModeError");
printCraftBukkitOfflineModeError.setAccessible(true);
//PERMISSIBLE_FIELD = DiscordFakePlayer.class.getDeclaredField("perm");
//PERMISSIBLE_FIELD.setAccessible(true); //Hacking my own plugin, while we're at it
PERMISSIBLE_BASE_ATTACHMENTS_FIELD = PermissibleBase.class.getDeclaredField("attachments");
PERMISSIBLE_BASE_ATTACHMENTS_FIELD.setAccessible(true);
convertAndAddAttachments = LPPermissible.class.getDeclaredMethod("convertAndAddAttachments", Collection.class);
convertAndAddAttachments.setAccessible(true);
getActive = LPPermissible.class.getDeclaredMethod("getActive");
getActive.setAccessible(true);
setOldPermissible = LPPermissible.class.getDeclaredMethod("setOldPermissible", PermissibleBase.class);
setOldPermissible.setAccessible(true);
getOldPermissible = LPPermissible.class.getDeclaredMethod("getOldPermissible");
getOldPermissible.setAccessible(true);
TBMCCoreAPI.RegisterEventsForExceptions(this, mp);
}
//Code copied from LuckPerms - me.lucko.luckperms.bukkit.listeners.BukkitConnectionListener
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerLogin(PlayerLoginEvent e) {
/* Called when the player starts logging into the server.
At this point, the users data should be present and loaded. */
if (!(e.getPlayer() instanceof DiscordFakePlayer))
return; //Normal players must be handled by the plugin
final DiscordFakePlayer player = (DiscordFakePlayer) e.getPlayer();
if (plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) {
plugin.getLogger().info("Processing login for " + player.getUniqueId() + " - " + player.getName());
}
final User user = plugin.getUserManager().getIfLoaded(player.getUniqueId());
/* User instance is null for whatever reason. Could be that it was unloaded between asyncpre and now. */
if (user == null) {
deniedLogin.add(player.getUniqueId());
if (!connectionListener.getUniqueConnections().contains(player.getUniqueId())) {
plugin.getLogger().warn("User " + player.getUniqueId() + " - " + player.getName() +
" doesn't have data pre-loaded, they have never been processed during pre-login in this session." +
" - denying login.");
try {
if ((Boolean) detectedCraftBukkitOfflineMode.get(connectionListener)) {
printCraftBukkitOfflineModeError.invoke(connectionListener);
e.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.LOADING_STATE_ERROR_CB_OFFLINE_MODE.asString(plugin.getLocaleManager()));
return;
}
} catch (IllegalAccessException | InvocationTargetException ex) {
ex.printStackTrace();
}
} else {
plugin.getLogger().warn("User " + player.getUniqueId() + " - " + player.getName() +
" doesn't currently have data pre-loaded, but they have been processed before in this session." +
" - denying login.");
}
e.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.LOADING_STATE_ERROR.asString(plugin.getLocaleManager()));
return;
}
// User instance is there, now we can inject our custom Permissible into the player.
// Care should be taken at this stage to ensure that async tasks which manipulate bukkit data check that the player is still online.
try {
// get the existing PermissibleBase held by the player
PermissibleBase oldPermissible = player.getPerm();
// Make a new permissible for the user
LPPermissible lpPermissible = new LPPermissible(player, user, plugin);
// Inject into the player
inject(player, lpPermissible, oldPermissible);
} catch (Throwable t) {
plugin.getLogger().warn("Exception thrown when setting up permissions for " +
player.getUniqueId() + " - " + player.getName() + " - denying login.");
t.printStackTrace();
e.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.LOADING_SETUP_ERROR.asString(plugin.getLocaleManager()));
return;
}
plugin.refreshAutoOp(player, true);
}
// Wait until the last priority to unload, so plugins can still perform permission checks on this event
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerQuit(PlayerQuitEvent e) {
if (!(e.getPlayer() instanceof DiscordFakePlayer))
return;
final DiscordFakePlayer player = (DiscordFakePlayer) e.getPlayer();
connectionListener.handleDisconnect(player.getUniqueId());
// perform unhooking from bukkit objects 1 tick later.
// this allows plugins listening after us on MONITOR to still have intact permissions data
this.plugin.getBootstrap().getServer().getScheduler().runTaskLaterAsynchronously(this.plugin.getBootstrap(), () -> {
// Remove the custom permissible
try {
uninject(player, true);
} catch (Exception ex) {
ex.printStackTrace();
}
// Handle auto op
if (this.plugin.getConfiguration().get(ConfigKeys.AUTO_OP)) {
player.setOp(false);
}
// remove their contexts cache
this.plugin.getContextManager().onPlayerQuit(player);
}, 1L);
}
//me.lucko.luckperms.bukkit.inject.permissible.PermissibleInjector
private void inject(DiscordFakePlayer player, LPPermissible newPermissible, PermissibleBase oldPermissible) throws IllegalAccessException, InvocationTargetException {
// seems we have already injected into this player.
if (oldPermissible instanceof LPPermissible) {
throw new IllegalStateException("LPPermissible already injected into player " + player.toString());
}
// Move attachments over from the old permissible
//noinspection unchecked
List<PermissionAttachment> attachments = (List<PermissionAttachment>) PERMISSIBLE_BASE_ATTACHMENTS_FIELD.get(oldPermissible);
convertAndAddAttachments.invoke(newPermissible, attachments);
attachments.clear();
oldPermissible.clearPermissions();
// Setup the new permissible
((AtomicBoolean) getActive.invoke(newPermissible)).set(true);
setOldPermissible.invoke(newPermissible, oldPermissible);
// inject the new instance
player.setPerm(newPermissible);
}
private void uninject(DiscordFakePlayer player, boolean dummy) throws Exception {
// gets the players current permissible.
PermissibleBase permissible = player.getPerm();
// only uninject if the permissible was a luckperms one.
if (permissible instanceof LPPermissible) {
LPPermissible lpPermissible = ((LPPermissible) permissible);
// clear all permissions
lpPermissible.clearPermissions();
// set to inactive
((AtomicBoolean) getActive.invoke(lpPermissible)).set(false);
// handle the replacement permissible.
if (dummy) {
// just inject a dummy class. this is used when we know the player is about to quit the server.
player.setPerm(DummyPermissibleBase.INSTANCE);
} else {
PermissibleBase newPb = (PermissibleBase) getOldPermissible.invoke(lpPermissible);
if (newPb == null) {
newPb = new PermissibleBase(player);
}
player.setPerm(newPb);
}
}
}
}