Updated to Discord4J v3, permission injection, improvements #99
8 changed files with 1114 additions and 787 deletions
14
pom.xml
14
pom.xml
|
@ -69,6 +69,14 @@
|
|||
</excludes> <!-- http://stackoverflow.com/questions/28458058/maven-shade-plugin-exclude-a-dependency-and-all-its-transitive-dependencies -->
|
||||
</artifactSet>
|
||||
<minimizeJar>true</minimizeJar>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>io.netty</pattern>
|
||||
<shadedPattern>buttondevteam.discordplugin.io.netty</shadedPattern>
|
||||
<excludes>
|
||||
</excludes>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
|
@ -242,6 +250,12 @@
|
|||
<artifactId>blockhound</artifactId>
|
||||
<version>1.0.0.M3</version>
|
||||
</dependency> -->
|
||||
<dependency>
|
||||
<groupId>com.github.lucko</groupId>
|
||||
<artifactId>LuckPerms</artifactId>
|
||||
<version>v4.4</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
|
|
|
@ -6,12 +6,16 @@ import buttondevteam.discordplugin.playerfaker.VanillaCommandListener;
|
|||
import discord4j.core.object.entity.MessageChannel;
|
||||
import discord4j.core.object.entity.User;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class DiscordConnectedPlayer extends DiscordFakePlayer implements IMCPlayer<DiscordConnectedPlayer> {
|
||||
private static int nextEntityId = 10000;
|
||||
private @Getter VanillaCommandListener<DiscordConnectedPlayer> 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);
|
||||
|
|
|
@ -3,15 +3,12 @@ package buttondevteam.discordplugin.mcchat;
|
|||
import buttondevteam.core.ComponentManager;
|
||||
import buttondevteam.discordplugin.DiscordConnectedPlayer;
|
||||
import buttondevteam.discordplugin.DiscordPlayer;
|
||||
import buttondevteam.discordplugin.DiscordPlugin;
|
||||
import buttondevteam.lib.player.TBMCPlayer;
|
||||
import discord4j.core.object.entity.MessageChannel;
|
||||
import discord4j.core.object.entity.PrivateChannel;
|
||||
import discord4j.core.object.entity.User;
|
||||
import lombok.val;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
@ -32,11 +29,14 @@ public class MCChatPrivate {
|
|||
val sender = new DiscordConnectedPlayer(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
|
||||
MCListener.callLoginEvents(sender);
|
||||
MCChatUtils.callLoginEvents(sender);
|
||||
} else {
|
||||
val sender = MCChatUtils.removeSender(MCChatUtils.ConnectedSenders, channel.getId(), user);
|
||||
if (p == null)// Player is offline - If the player is online, that takes precedence
|
||||
callEventSync(new PlayerQuitEvent(sender, ""));
|
||||
assert sender != null;
|
||||
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 ----
|
||||
if (!start)
|
||||
|
@ -60,11 +60,8 @@ public class MCChatPrivate {
|
|||
for (val entry : MCChatUtils.ConnectedSenders.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
|
||||
MCChatUtils.callEventExcludingSome(new PlayerQuitEvent(valueEntry.getValue(), "")); //This is sync
|
||||
MCChatUtils.callLogoutEvent(valueEntry.getValue(), false); //This is sync
|
||||
MCChatUtils.ConnectedSenders.clear();
|
||||
}
|
||||
|
||||
private static void callEventSync(Event event) {
|
||||
Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> MCChatUtils.callEventExcludingSome(event));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import buttondevteam.discordplugin.*;
|
|||
import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule;
|
||||
import buttondevteam.lib.TBMCCoreAPI;
|
||||
import buttondevteam.lib.TBMCSystemChatEvent;
|
||||
import com.google.common.collect.Sets;
|
||||
import discord4j.core.object.entity.*;
|
||||
import discord4j.core.object.util.Snowflake;
|
||||
import io.netty.util.collection.LongObjectHashMap;
|
||||
|
@ -15,14 +16,20 @@ import org.bukkit.Bukkit;
|
|||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.event.Event;
|
||||
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.Plugin;
|
||||
import org.bukkit.plugin.RegisteredListener;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
@ -43,6 +50,7 @@ public class MCChatUtils {
|
|||
static @Nullable LastMsgData lastmsgdata;
|
||||
static LongObjectHashMap<Message> lastmsgfromd = new LongObjectHashMap<>(); // Last message sent by a Discord user, used for clearing checkmarks
|
||||
private static MinecraftChatModule module;
|
||||
private static HashMap<Class<? extends Event>, HashSet<String>> staticExcludedPlugins = new HashMap<>();
|
||||
|
||||
public static void updatePlayerList() {
|
||||
if (notEnabled()) return;
|
||||
|
@ -227,9 +235,23 @@ public class MCChatUtils {
|
|||
//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) {
|
||||
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
|
||||
public static class LastMsgData {
|
||||
public Message message;
|
||||
|
|
|
@ -18,12 +18,12 @@ import org.bukkit.event.EventHandler;
|
|||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
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.server.BroadcastMessageEvent;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Objects;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
|
@ -38,7 +38,7 @@ class MCListener implements Listener {
|
|||
return;
|
||||
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()
|
||||
.ifPresent(dcp -> MCChatUtils.callEventExcludingSome(new PlayerQuitEvent(dcp, "")));
|
||||
.ifPresent(dcp -> MCChatUtils.callLogoutEvent(dcp, false));
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
|
@ -70,30 +70,13 @@ class MCListener implements Listener {
|
|||
Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin,
|
||||
() -> MCChatUtils.ConnectedSenders.values().stream().flatMap(v -> v.values().stream())
|
||||
.filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny()
|
||||
.ifPresent(MCListener::callLoginEvents));
|
||||
.ifPresent(MCChatUtils::callLoginEvents));
|
||||
Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin,
|
||||
ChromaBot.getInstance()::updatePlayerList, 5);
|
||||
final String message = e.GetPlayer().PlayerName().get() + " left the game";
|
||||
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)
|
||||
public void onPlayerKick(PlayerKickEvent e) {
|
||||
/*if (!DiscordPlugin.hooked && !e.getReason().equals("The server is restarting")
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package buttondevteam.discordplugin.mcchat;
|
||||
|
||||
import buttondevteam.core.MainPlugin;
|
||||
import buttondevteam.core.component.channel.Channel;
|
||||
import buttondevteam.discordplugin.DPUtils;
|
||||
import buttondevteam.discordplugin.DiscordConnectedPlayer;
|
||||
import buttondevteam.discordplugin.DiscordPlugin;
|
||||
import buttondevteam.discordplugin.playerfaker.perm.LPInjector;
|
||||
import buttondevteam.lib.TBMCCoreAPI;
|
||||
import buttondevteam.lib.TBMCSystemChatEvent;
|
||||
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
|
||||
|
|
|
@ -5,6 +5,7 @@ import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
|
|||
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.advancement.Advancement;
|
||||
|
@ -17,6 +18,7 @@ import org.bukkit.entity.Player;
|
|||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||
import org.bukkit.map.MapView;
|
||||
import org.bukkit.permissions.PermissibleBase;
|
||||
import org.bukkit.permissions.ServerOperator;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scoreboard.Scoreboard;
|
||||
|
||||
|
@ -27,15 +29,28 @@ import java.util.*;
|
|||
public class DiscordFakePlayer extends DiscordHumanEntity implements Player {
|
||||
protected DiscordFakePlayer(User user, MessageChannel channel, int entityId, UUID uuid, String mcname, MinecraftChatModule module) {
|
||||
super(user, channel, entityId, uuid, module);
|
||||
perm = new PermissibleBase(Bukkit.getOfflinePlayer(uuid));
|
||||
origPerm = perm = new PermissibleBase(basePlayer = Bukkit.getOfflinePlayer(uuid));
|
||||
name = mcname;
|
||||
}
|
||||
|
||||
@Delegate
|
||||
private PermissibleBase perm;
|
||||
@Delegate(excludes = ServerOperator.class)
|
||||
private PermissibleBase origPerm;
|
||||
|
||||
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
|
||||
public EntityType getType() {
|
||||
return EntityType.PLAYER;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue