/* * Copyright or © or Copr. Amaury Carrade (2014 - 2016) * * http://amaury.carrade.eu * * This software is governed by the CeCILL-B license under French law and * abiding by the rules of distribution of free software. You can use, * modify and/ or redistribute the software under the terms of the CeCILL-B * license as circulated by CEA, CNRS and INRIA at the following URL * "http://www.cecill.info". * * As a counterpart to the access to the source code and rights to copy, * modify and redistribute granted by the license, users are provided only * with a limited warranty and the software's author, the holder of the * economic rights, and the successive licensors have only limited * liability. * * In this respect, the user's attention is drawn to the risks associated * with loading, using, modifying and/or developing or reproducing the * software by the user in light of its specific status of free software, * that may mean that it is complicated to manipulate, and that also * therefore means that it is reserved for developers and experienced * professionals having in-depth computer knowledge. Users are therefore * encouraged to load and test the software's suitability as regards their * requirements in conditions enabling the security of their systems and/or * data to be ensured and, more generally, to use and operate it in the * same conditions as regards security. * * The fact that you are presently reading this means that you have had * knowledge of the CeCILL-B license and that you accept its terms. */ package eu.carrade.amaury.UHCReloaded.misc; import fr.zcraft.zlib.components.worker.Worker; import fr.zcraft.zlib.components.worker.WorkerCallback; import fr.zcraft.zlib.components.worker.WorkerRunnable; import fr.zcraft.zlib.tools.Callback; import fr.zcraft.zlib.tools.PluginLogger; import fr.zcraft.zlib.tools.mojang.UUIDFetcher; import fr.zcraft.zlib.tools.reflection.Reflection; import org.apache.commons.lang.StringUtils; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; public class OfflinePlayersLoader extends Worker { private static Map<UUID, OfflinePlayer> offlinePlayers = new HashMap<>(); /** * Returns a list of offline players, including the players who logged in the server and the explicitly * loaded players. * * @return A list of OfflinePlayers. */ public static Set<OfflinePlayer> getOfflinePlayers() { Set<OfflinePlayer> players = new HashSet<>(); players.addAll(offlinePlayers.values()); Collections.addAll(players, Bukkit.getOfflinePlayers()); return Collections.unmodifiableSet(players); } /** * Retrieves an OfflinePlayer by ID. * * Tries to load a logged-in player, then an explicitly loaded player, then * a server-wide OfflinePlayer. * * @param id The player's UUID. * @return An OfflinePlayer. The retrieved object will never be null but may not * be loaded with a name and other data if the player never came to the server and * was not explicitly loaded. */ public static OfflinePlayer getOfflinePlayer(UUID id) { OfflinePlayer player = Bukkit.getPlayer(id); if (player == null) player = offlinePlayers.get(id); if (player == null) player = Bukkit.getOfflinePlayer(id); return player; } /** * Retrieves an OfflinePlayer by name. * * Tries to load a logged-in player, then an explicitly loaded player, then * a server-wide OfflinePlayer. * * @param name The player's name. * @return An OfflinePlayer. {@code null} if no player was found with this name. */ public static OfflinePlayer getOfflinePlayer(String name) { OfflinePlayer player = null; for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { if (onlinePlayer.getName().equalsIgnoreCase(name)) { player = onlinePlayer; break; } } if (player == null) { for (OfflinePlayer offlinePlayer : Bukkit.getOfflinePlayers()) { if (offlinePlayer.getName().equalsIgnoreCase(name)) { player = offlinePlayer; break; } } } if (player == null) { for (OfflinePlayer offlinePlayer : offlinePlayers.values()) { if (offlinePlayer.getName().equalsIgnoreCase(name)) { player = offlinePlayer; break; } } } return player; } /** * Loads the given players list in the system, making it available in the players list and to be added in teams. * * @param pseudonym A pseudonym. * @param successCallback A callback called when the process ended. Called with {@code null} if no player was found. */ public static void loadPlayer(final String pseudonym, final Callback<OfflinePlayer> successCallback) { loadPlayers(Collections.singletonList(pseudonym), new Callback<Map<UUID, OfflinePlayer>>() { @Override public void call(Map<UUID, OfflinePlayer> retrieved) { if (successCallback != null) { if (retrieved.size() == 1) successCallback.call(retrieved.values().iterator().next()); else successCallback.call(null); } } }); } /** * Loads the given players list in the system, making it available in the players list and to be added in teams. * * @param pseudonyms A list of pseudonyms. * @param callbackSuccess A callback called when the process ended. */ public static void loadPlayers(final List<String> pseudonyms, final Callback<Map<UUID, OfflinePlayer>> callbackSuccess) { loadPlayers(pseudonyms, callbackSuccess, new Callback<List<String>>() { @Override public void call(List<String> errors) { PluginLogger.error("Unable to retrieve the following names: {0}", StringUtils.join(errors, ", ")); } }); } /** * Loads the given players list in the system, making it available in the players list * and to be added in teams. * * Only works in online mode. In offline mode, the already known players will be sent * to the success callback and the others to the errors one. Use {@link UUIDFetcher} * directly if needed. * * @param pseudonyms A list of pseudonyms. * @param callbackSuccess A callback called when the process ended. */ public static void loadPlayers(final List<String> pseudonyms, final Callback<Map<UUID, OfflinePlayer>> callbackSuccess, final Callback<List<String>> callbackErrors) { final List<String> toRetrieve = new ArrayList<>(pseudonyms); final Map<UUID, OfflinePlayer> alreadyKnown = new HashMap<>(); for (String pseudonym : pseudonyms) { OfflinePlayer player = getOfflinePlayer(pseudonym); if (player != null) { alreadyKnown.put(player.getUniqueId(), player); toRetrieve.remove(pseudonym); } } if (toRetrieve.size() == 0) { if (callbackSuccess != null) callbackSuccess.call(alreadyKnown); return; } // If the server is in offline mode, we don't even try to load the players, as they will // not be valid and be unusable. if (!Bukkit.getOnlineMode()) { if (callbackSuccess != null) callbackSuccess.call(alreadyKnown); if (callbackErrors != null && toRetrieve.size() > 0) callbackErrors.call(toRetrieve); return; } submitQuery(new WorkerRunnable<Map<String, UUID>>() { @Override public Map<String, UUID> run() throws Throwable { Map<String, UUID> uuids = UUIDFetcher.fetch(toRetrieve); UUIDFetcher.fetchRemaining(toRetrieve, uuids); return uuids; } }, new WorkerCallback<Map<String, UUID>>() { @Override public void finished(final Map<String, UUID> result) { final Map<UUID, OfflinePlayer> added = new HashMap<>(alreadyKnown); final Class<?> gameProfileClass; try { gameProfileClass = Class.forName("com.mojang.authlib.GameProfile"); } catch (ClassNotFoundException e) { PluginLogger.error("Cannot load GameProfile class required to load OfflinePlayers.", e); return; } for (Map.Entry<String, UUID> playerProfile : result.entrySet()) { String name = playerProfile.getKey(); UUID uuid = playerProfile.getValue(); if (uuid == null) { PluginLogger.error("Unable to load the player {0}, skipping.", name); continue; } try { Object profile = Reflection.instantiate(gameProfileClass, uuid, name); OfflinePlayer player = (OfflinePlayer) Reflection.call(Bukkit.getServer(), "getOfflinePlayer", profile); offlinePlayers.put(uuid, player); added.put(uuid, player); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) { PluginLogger.error("Unable to load the player {0}, skipping.", e, playerProfile.getKey()); } } if (callbackSuccess != null) callbackSuccess.call(added); if (callbackErrors != null) { List<String> notRetrieved = new ArrayList<>(); for (String pseudonym : toRetrieve) { if (!result.keySet().contains(pseudonym)) notRetrieved.add(pseudonym); } if (notRetrieved.size() > 0) callbackErrors.call(notRetrieved); } } @Override public void errored(Throwable exception) { PluginLogger.error("Unable to load players", exception); } }); } }