From e0aca1a6eb31495d003eac0f98f27e2cd089c76f Mon Sep 17 00:00:00 2001 From: asofold Date: Fri, 9 Nov 2012 14:29:51 +0100 Subject: [PATCH] Add pinpoint notification and getting players by permission support (cached). This adds the ability to keep track of players with a certain permission with world-specific resolution (world changes, join, quit, kick). Also adding a static access method to NoCheatPlus to notify all with administration.notify permission. --- .../neatmonster/nocheatplus/NoCheatPlus.java | 208 +++++++++++++----- .../components/NameSetPermState.java | 86 ++++++++ .../components/PermStateHolder.java | 26 +++ .../components/PermStateReceiver.java | 16 ++ 4 files changed, 276 insertions(+), 60 deletions(-) create mode 100644 src/fr/neatmonster/nocheatplus/components/NameSetPermState.java create mode 100644 src/fr/neatmonster/nocheatplus/components/PermStateHolder.java create mode 100644 src/fr/neatmonster/nocheatplus/components/PermStateReceiver.java diff --git a/src/fr/neatmonster/nocheatplus/NoCheatPlus.java b/src/fr/neatmonster/nocheatplus/NoCheatPlus.java index d9660247..0623c4f7 100644 --- a/src/fr/neatmonster/nocheatplus/NoCheatPlus.java +++ b/src/fr/neatmonster/nocheatplus/NoCheatPlus.java @@ -8,17 +8,22 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import org.bukkit.Bukkit; import org.bukkit.ChatColor; +import org.bukkit.Server; import org.bukkit.command.PluginCommand; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerKickEvent; import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerLoginEvent.Result; +import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.java.JavaPlugin; @@ -35,7 +40,9 @@ import fr.neatmonster.nocheatplus.command.CommandHandler; import fr.neatmonster.nocheatplus.command.INotifyReload; import fr.neatmonster.nocheatplus.components.ComponentWithName; import fr.neatmonster.nocheatplus.components.INeedConfig; +import fr.neatmonster.nocheatplus.components.NameSetPermState; import fr.neatmonster.nocheatplus.components.NoCheatPlusAPI; +import fr.neatmonster.nocheatplus.components.PermStateReceiver; import fr.neatmonster.nocheatplus.config.ConfPaths; import fr.neatmonster.nocheatplus.config.ConfigFile; import fr.neatmonster.nocheatplus.config.ConfigManager; @@ -69,11 +76,14 @@ import fr.neatmonster.nocheatplus.utilities.Updates; /** * This is the main class of NoCheatPlus. The commands, events listeners and tasks are registered here. */ -public class NoCheatPlus extends JavaPlugin implements Listener, NoCheatPlusAPI { +public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI { /** Lower case player name to milliseconds point of time of release */ private static final Map denyLoginNames = Collections.synchronizedMap(new HashMap()); + /** Names of players with a certain permission. */ + protected static final NameSetPermState nameSetPerms = new NameSetPermState(Permissions.ADMINISTRATION_NOTIFY); + /** * Remove expired entries. */ @@ -155,6 +165,30 @@ public class NoCheatPlus extends JavaPlugin implements Listener, NoCheatPlusAPI public static NoCheatPlusAPI getAPI() { return (NoCheatPlusAPI) Bukkit.getPluginManager().getPlugin("NoCheatPlus"); } + + /** + * Send all players with the nocheatplus.admin.notify permission a message. + * This is likely to be more efficient than iterating over all players and + * checking their permissions. However this only updates permissions with + * login and world changes currently. + * + * @param message + * @return Number of players messaged. + */ + public static int sendAdminNotifyMessage(final String message){ + final Set names = nameSetPerms.getPlayers(Permissions.ADMINISTRATION_NOTIFY); + if (names == null) return 0; + int done = 0; + final Server server = Bukkit.getServer(); + for (final String name : names){ + final Player player = server.getPlayerExact(name); + if (player != null){ + player.sendMessage(message); + done ++; + } + } + return done; + } /** The event listeners. */ private final List listeners = new ArrayList(); @@ -182,6 +216,8 @@ public class NoCheatPlus extends JavaPlugin implements Listener, NoCheatPlusAPI private final ListenerManager listenerManager = new ListenerManager(this, false); private boolean manageListeners = true; + + protected final List permStateReceivers = new ArrayList(); /** * Interfaces checked for managed listeners: IHaveMethodOrder (method), ComponentWithName (tag)
@@ -197,6 +233,10 @@ public class NoCheatPlus extends JavaPlugin implements Listener, NoCheatPlusAPI ((INeedConfig) obj).onReload(); } } + if (obj instanceof PermStateReceiver){ + // No immediate update done. + permStateReceivers.add((PermStateReceiver) obj); + } dataMan.addComponent(obj); } @@ -204,7 +244,7 @@ public class NoCheatPlus extends JavaPlugin implements Listener, NoCheatPlusAPI * Interfaces checked for managed listeners: IHaveMethodOrder (method), ComponentWithName (tag)
* @param listener */ - private void addListener(final Listener listener) { + protected void addListener(final Listener listener) { if (manageListeners){ String tag = "NoCheatPlus"; if (listener instanceof ComponentWithName){ @@ -237,7 +277,12 @@ public class NoCheatPlus extends JavaPlugin implements Listener, NoCheatPlusAPI listeners.remove(obj); listenerManager.remove((Listener) obj); } - notifyReload.remove(obj); + if (obj instanceof PermStateReceiver){ + permStateReceivers.remove((PermStateReceiver) obj); + } + if (obj instanceof INotifyReload) { + notifyReload.remove(obj); + } dataMan.removeComponent(obj); } @@ -269,16 +314,18 @@ public class NoCheatPlus extends JavaPlugin implements Listener, NoCheatPlusAPI // Remove config listeners. notifyReload.clear(); - - // More cleanup. - dataMan.onDisable(); - - // Cleanup the configuration manager. - ConfigManager.cleanup(); + + permStateReceivers.clear(); + + // More cleanup. + dataMan.onDisable(); // Restore changed commands. undoCommandChanges(); + // Cleanup the configuration manager. + ConfigManager.cleanup(); + // Remove listener references. listenerManager.setRegisterDirectly(false); listenerManager.clear(); @@ -300,7 +347,7 @@ public class NoCheatPlus extends JavaPlugin implements Listener, NoCheatPlusAPI } } - private void setupCommandProtection() { + protected void setupCommandProtection() { final List changedCommands = PermissionUtil.protectCommands( Arrays.asList("plugins", "version", "icanhasbukkit"), "nocheatplus.feature.command", false); if (this.changedCommands == null) this.changedCommands = changedCommands; @@ -338,7 +385,8 @@ public class NoCheatPlus extends JavaPlugin implements Listener, NoCheatPlusAPI listenerManager.setRegisterDirectly(false); listenerManager.clear(); } - addListener(this); + addComponent(nameSetPerms); + addListener(getCoreListener()); for (final Object obj : new Object[]{ NCPExemptionManager.getListener(), dataMan, @@ -347,7 +395,7 @@ public class NoCheatPlus extends JavaPlugin implements Listener, NoCheatPlusAPI new BlockPlaceListener(), new ChatListener(), new CombinedListener(), - // Do ming registration order: Combined must come before Fight. + // Do mind registration order: Combined must come before Fight. new FightListener(), new InventoryListener(), new MovingListener(), @@ -463,43 +511,12 @@ public class NoCheatPlus extends JavaPlugin implements Listener, NoCheatPlusAPI // configFile.getBoolean(ConfPaths.MISCELLANEOUS_NOMOVEDTOOQUICKLY_USEPROXY, false)); // } - /** - * This event handler is used to send all the disabling messages to the client. - * - * @param event - * the event handled - */ - @EventHandler( - priority = EventPriority.MONITOR) - public void onPlayerJoinMonitor(final PlayerJoinEvent event) { - /* - * ____ _ _ _ - * | _ \| | __ _ _ _ ___ _ __ | | ___ (_)_ __ - * | |_) | |/ _` | | | |/ _ \ '__| _ | |/ _ \| | '_ \ - * | __/| | (_| | |_| | __/ | | |_| | (_) | | | | | - * |_| |_|\__,_|\__, |\___|_| \___/ \___/|_|_| |_| - * |___/ - */ - final Player player = event.getPlayer(); - - // Send a message to the player if a new update is available. - if (updateAvailable && player.hasPermission(Permissions.ADMINISTRATION_NOTIFY)) - player.sendMessage(ChatColor.RED + "NCP: " + ChatColor.WHITE - + "A new update of NoCheatPlus is available.\n" + "Download it at http://nocheatplus.org/update"); - - // Send a message to the player if the configuration is outdated. - if (configOutdated && player.hasPermission(Permissions.ADMINISTRATION_NOTIFY)) - player.sendMessage(ChatColor.RED + "NCP: " + ChatColor.WHITE + "Your configuration might be outdated.\n" - + "Some settings could have changed, you should regenerate it!"); - - checkModsMessage(player); - } /** * Send block codes to the player according to allowed or disallowed client-mods or client-mod features. * @param player */ - private void checkModsMessage(Player player) { + protected void checkModsMessage(Player player) { String message = ""; // Check if we allow all the client mods. @@ -584,20 +601,91 @@ public class NoCheatPlus extends JavaPlugin implements Listener, NoCheatPlusAPI player.sendMessage(message); } - @EventHandler(priority=EventPriority.HIGHEST) - public void onPlayerLogin(final PlayerLoginEvent event){ - // (HGHEST to give other plugins the possibility to add permissions or allow the player). - if (event.getResult() != Result.ALLOWED) return; - final Player player = event.getPlayer(); - // Check if login is denied: - checkDenyLoginsNames(); - if (player.hasPermission(Permissions.BYPASS_DENY_LOGIN)) return; - if (isLoginDenied(player.getName())){ - // TODO: display time for which the player is banned. - event.setResult(Result.KICK_OTHER); - // TODO: Make message configurable. - event.setKickMessage("You are temporarily denied to join this server."); - } - } - + /** + * Quick solution to hide the listener methods, expect refactoring. + * @return + */ + private Listener getCoreListener() { + return new Listener() { + @SuppressWarnings("unused") + @EventHandler(priority = EventPriority.HIGHEST) + public void onPlayerLogin(final PlayerLoginEvent event) { + // (HGHEST to give other plugins the possibility to add + // permissions or allow the player). + if (event.getResult() != Result.ALLOWED) return; + final Player player = event.getPlayer(); + // Check if login is denied: + checkDenyLoginsNames(); + if (player.hasPermission(Permissions.BYPASS_DENY_LOGIN)) return; + if (isLoginDenied(player.getName())) { + // TODO: display time for which the player is banned. + event.setResult(Result.KICK_OTHER); + // TODO: Make message configurable. + event.setKickMessage("You are temporarily denied to join this server."); + } + } + + @SuppressWarnings("unused") + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerJoin(final PlayerJoinEvent event) { + final Player player = event.getPlayer(); + + updatePermStateReceivers(player); + + if (nameSetPerms.hasPermission(player.getName(), Permissions.ADMINISTRATION_NOTIFY)){ + // Login notifications... + + // Update available. + if (updateAvailable) player.sendMessage(ChatColor.RED + "NCP: " + ChatColor.WHITE + "A new update of NoCheatPlus is available.\n" + "Download it at http://nocheatplus.org/update"); + + // Outdated config. + if (configOutdated) player.sendMessage(ChatColor.RED + "NCP: " + ChatColor.WHITE + "Your configuration might be outdated.\n" + "Some settings could have changed, you should regenerate it!"); + + } + checkModsMessage(player); + } + + @SuppressWarnings("unused") + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerchangedWorld(final PlayerChangedWorldEvent event) + { + final Player player = event.getPlayer(); + updatePermStateReceivers(player); + } + + @SuppressWarnings("unused") + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerKick(final PlayerKickEvent event) { + onLeave(event.getPlayer()); + } + + @SuppressWarnings("unused") + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerQuitMonitor(final PlayerQuitEvent event) { + onLeave(event.getPlayer()); + } + }; + } + + protected void onLeave(final Player player) { + for (final PermStateReceiver pr : permStateReceivers) { + pr.removePlayer(player.getName()); + } + } + + protected void updatePermStateReceivers(final Player player) { + final Map checked = new HashMap(20); + final String name = player.getName(); + for (final PermStateReceiver pr : permStateReceivers) { + for (final String permission : pr.getDefaultPermissions()) { + Boolean state = checked.get(permission); + if (state == null) { + state = player.hasPermission(permission); + checked.put(permission, state); + } + pr.setPermission(name, permission, state); + } + } + } + } diff --git a/src/fr/neatmonster/nocheatplus/components/NameSetPermState.java b/src/fr/neatmonster/nocheatplus/components/NameSetPermState.java new file mode 100644 index 00000000..6b6d8682 --- /dev/null +++ b/src/fr/neatmonster/nocheatplus/components/NameSetPermState.java @@ -0,0 +1,86 @@ +package fr.neatmonster.nocheatplus.components; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Map.Entry; +import java.util.Set; + +/** + * Store set of player names by defaultPermissions they have to get players with a certain permission.
+ * TODO: Might later detach to an interface (+SimpleNameSetPermStateHolder). + * @author mc_dev + * + */ +public class NameSetPermState implements PermStateReceiver{ + + /** Map permission to player names (all exact case). */ + protected final HashMap> playerSets = new HashMap>(); + + protected String[] defaultPermissions; + + public NameSetPermState(String... permissions){ + this.defaultPermissions = permissions; + } + + @Override + public String[] getDefaultPermissions() { + return defaultPermissions; + } + + @Override + public boolean hasPermission(final String player, final String permission) { + final Set names = playerSets.get(permission); + if (names == null) return false; + return (names.contains(player)); + } + + @Override + public void setPermission(final String player, final String permission, boolean state) { + Set names = playerSets.get(permission); + if (names == null){ + if (!state) return; + names = new LinkedHashSet(20); + playerSets.put(permission, names); + } + if (state) names.add(player); + else names.remove(player); + } + + + @Override + public void removePlayer(final String player) { + // TODO: Something more efficient ? [mostly used with few defaultPermissions, though]. + final Iterator>> it = playerSets.entrySet().iterator(); + while (it.hasNext()){ + final Entry> entry = it.next(); + final Set set = entry.getValue(); + set.remove(player); + if (set.isEmpty()) it.remove(); + } + } + + /** + * Get a set with the players that hold the permission. + * @param permission + * @return + */ + public Set getPlayers(final String permission){ + return playerSets.get(permission); + } + + public void addDefaultPermissions(Collection permissions){ + Collection newDefaults = new HashSet(); + newDefaults.addAll(Arrays.asList(this.defaultPermissions)); + newDefaults.addAll(permissions); + } + + public void setDefaultPermissions(Collection permissions){ + defaultPermissions = new String[permissions.size()]; + permissions.toArray(defaultPermissions); + } + +} diff --git a/src/fr/neatmonster/nocheatplus/components/PermStateHolder.java b/src/fr/neatmonster/nocheatplus/components/PermStateHolder.java new file mode 100644 index 00000000..a904d423 --- /dev/null +++ b/src/fr/neatmonster/nocheatplus/components/PermStateHolder.java @@ -0,0 +1,26 @@ +package fr.neatmonster.nocheatplus.components; + + +/** + * Permission cache. Allow to query defaultPermissions (a defined set of defaultPermissions), to be registered and automatically be updated, according to registry.
+ * The defaultPermissions are not updated in real time but on certain events, to be specified by the registry. + * + * @author mc_dev + * + */ +public interface PermStateHolder { + + /** + * Get the defaultPermissions that are guaranteed to be held here. + * @return + */ + public String[] getDefaultPermissions(); + + /** + * Test a permission. If not available the result will be false, no updating of defaultPermissions is expected on calling this. + * @param player + * @param permission + * @return + */ + public boolean hasPermission(String player, String permission); +} diff --git a/src/fr/neatmonster/nocheatplus/components/PermStateReceiver.java b/src/fr/neatmonster/nocheatplus/components/PermStateReceiver.java new file mode 100644 index 00000000..b96c50a6 --- /dev/null +++ b/src/fr/neatmonster/nocheatplus/components/PermStateReceiver.java @@ -0,0 +1,16 @@ +package fr.neatmonster.nocheatplus.components; + + + +/** + * Receive permission changes for certain defaultPermissions. + * @author mc_dev + * + */ +public interface PermStateReceiver extends PermStateHolder{ + + public void setPermission(String player, String permission, boolean state); + + public void removePlayer(String player); + +}