package me.asofold.bpl.simplyvanish; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import me.asofold.bpl.simplyvanish.api.events.SimplyVanishStateEvent; import me.asofold.bpl.simplyvanish.api.hooks.impl.DisguiseCraftHook; import me.asofold.bpl.simplyvanish.api.hooks.impl.DynmapHook; import me.asofold.bpl.simplyvanish.api.hooks.impl.Essentials2Hook; import me.asofold.bpl.simplyvanish.config.Settings; import me.asofold.bpl.simplyvanish.config.VanishConfig; import me.asofold.bpl.simplyvanish.util.HookUtil; import me.asofold.bpl.simplyvanish.util.Panic; import me.asofold.bpl.simplyvanish.util.Utils; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Server; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitScheduler; /** * Core methods for vanish/reappear. * @author mc_dev * */ public class SimplyVanishCore{ /** * Vanished players. */ private final Map<String, VanishConfig> vanishConfigs = Collections.synchronizedMap(new HashMap<String, VanishConfig>(20, 0.5f)); private final HookUtil hookUtil = new HookUtil(); /** * Flag for if the plugin is enabled. */ private boolean enabled = false; private Settings settings = new Settings(); private long tsSave = 0; private int saveTaskId = -1; /* * File to save vanished players to. */ private File vanishedFile = null; private SimplyVanish plugin; /** * Only has relevance for static access by Plugin. * @param enabled */ public void setEnabled(boolean enabled) { this.enabled = enabled; } /** * Only for static access by plugin. * @return */ public boolean isEnabled(){ return enabled; } public void setSettings(Settings settings){ this.settings = settings; } public SimplyVanish getPlugin() { return plugin; } public void setPlugin(SimplyVanish plugin) { this.plugin = plugin; } // /** // * TODO: Unused ? // * @return // */ // public boolean shouldSave(){ // synchronized(vanishConfigs){ // for (VanishConfig cfg : vanishConfigs.values()){ // if (cfg.changed) return true; // } // } // return false; // } /** * Might save vanished names to file, checks timestamp, does NOT update states (!). */ public void onSaveVanished(){ if (settings.saveVanishedDelay >= 0){ if (System.currentTimeMillis() - tsSave > settings.saveVanishedDelay){ // Delay has expired. if (saveTaskId != -1) saveTaskId = -1; // Do not cancel anything, presumably that is done. doSaveVanished(); } else{ // Within delay time frame, schedule new task (unless already done): BukkitScheduler sched = Bukkit.getServer().getScheduler(); if (saveTaskId != -1 && sched.isQueued(saveTaskId)) return; saveTaskId = sched.scheduleSyncDelayedTask(plugin, new Runnable(){ @Override public void run() { onSaveVanished(); // Check if save is necessary. } }, 1+(settings.saveVanishedDelay/50)); if (saveTaskId == -1) doSaveVanished(); // force save if scheduling failed. } } else doSaveVanished(); // Delay is not used. } /** * Force save vanished names to file, does NOT update states (!). */ public void doSaveVanished(){ long ns = System.nanoTime(); tsSave = System.currentTimeMillis(); File file = getVanishedFile(); if ( file==null){ Utils.warn("Can not save vanished players: File is not set."); return; } if (!createFile(file, "vanished players")) return; BufferedWriter writer = null; try { writer = new BufferedWriter( new FileWriter(file)); writer.write("\n"); // to write something at least. Map<String, VanishConfig> copyMap = new HashMap<String,VanishConfig>(vanishConfigs.size()); synchronized(vanishConfigs){ for (Entry<String, VanishConfig> entry : vanishConfigs.entrySet()){ copyMap.put(entry.getKey(), entry.getValue().clone()); } } for (Entry<String, VanishConfig> entry : copyMap.entrySet()){ String n = entry.getKey(); VanishConfig cfg = entry.getValue(); if (cfg.needsSave()){ writer.write(n); writer.write(cfg.toLine()); writer.write("\n"); } cfg.changed = false; } } catch (IOException e) { Utils.warn("Can not save vanished players: "+e.getMessage()); } finally{ if ( writer != null) try { writer.close(); } catch (IOException e) { } } SimplyVanish.stats.addStats(SimplyVanish.statsSave, System.nanoTime()-ns); } /** * Load vanished names from file.<br> * This does not update vanished states!<br> * Assumes each involved VanishConfig to be changed by loading. */ public void loadVanished(){ synchronized(vanishConfigs){ doLoadVanished(); } } private void doLoadVanished() { File file = getVanishedFile(); if ( file == null){ Utils.warn("Can not load vanished players: File is not set."); return; } if (!createFile(file, "vanished players")) return; BufferedReader reader = null; try { reader = new BufferedReader( new FileReader(file)); String line = ""; while ( line != null){ String n = line.trim().toLowerCase(); if (!n.isEmpty()){ if (n.startsWith("nosee:") && n.length()>6){ // kept for compatibility: n = n.substring(7).trim(); if (n.isEmpty()) continue; VanishConfig cfg = getVanishConfig(n, true); cfg.set(cfg.see, false); } else{ String[] split = n.split(" "); n = split[0].trim().toLowerCase(); if (n.isEmpty()) continue; VanishConfig cfg = getVanishConfig(n, true); cfg.readFromArray(split, 1, true); } } line = reader.readLine(); } } catch (IOException e) { Utils.warn("Can not load vanished players: "+e.getMessage()); } finally{ if ( reader != null) try { reader.close(); } catch (IOException e) { } } } /** * Create if not exists. * @param file * @param tag * @return if exists now. */ public boolean createFile(File file, String tag){ if ( !file.exists() ){ try { if ( file.createNewFile()) return true; else{ Utils.warn("Could not create "+tag+" file."); } } catch (IOException e) { Utils.warn("Could not create "+tag+" file: "+e.getMessage()); return false; } } return true; } public File getVanishedFile(){ return vanishedFile; } public void setVanishedFile(File file) { vanishedFile = file; } /** * Adjust state of player to vanished, message player. * @param player */ public void doVanish(Player player) { doVanish(player, true); } /** * Adjust state of player to vanished. * @param player * @param message If to message players. */ public void doVanish(final Player player, final boolean message) { final long ns = System.nanoTime(); final String name = player.getName(); boolean was = !addVanishedName(name); String fakeQuit = null; final VanishConfig vcfg = getVanishConfig(name, true); if (settings.sendFakeMessages && !settings.fakeQuitMessage.isEmpty() && !vcfg.online.state) { fakeQuit = settings.fakeQuitMessage.replaceAll("%name", name); fakeQuit = fakeQuit.replaceAll("%displayname", player.getDisplayName()); } final String msgNotify = SimplyVanish.msgLabel+ChatColor.GREEN+name+ChatColor.GRAY+" vanished."; for (final Player other : Bukkit.getServer().getOnlinePlayers()){ if (other.getName().equals(name)) continue; final boolean shouldSee = shouldSeeVanished(other); final boolean notify = settings.notifyState && hasPermission(other, settings.notifyStatePerm); if (other.canSee(player)){ if (!shouldSee) { hidePlayer(player, other); } if (notify){ if (!was){ if (message) { other.sendMessage(msgNotify); } } } else if (!shouldSee){ if (fakeQuit != null) { other.sendMessage(fakeQuit); } } } else{ if (shouldSee) { showPlayer(player, other); // added as consistency check } if (!was && notify){ if (message) { other.sendMessage(msgNotify); } } } } if (message && vcfg.notify.state) { player.sendMessage(was?SimplyVanish.msgStillInvisible:SimplyVanish.msgNowInvisible); } SimplyVanish.stats.addStats(SimplyVanish.statsVanish, System.nanoTime()-ns); } /** * Adjust state of player to not vanished. * @param player * @param message If to send messages. */ public void doReappear(final Player player, final boolean message) { final long ns = System.nanoTime(); final String name = player.getName(); final boolean was = removeVanishedName(name); String fakeJoin = null; final VanishConfig vcfg = getVanishConfig(name, true); if (settings.sendFakeMessages && !settings.fakeJoinMessage.isEmpty() &&!vcfg.online.state) { fakeJoin = settings.fakeJoinMessage.replaceAll("%name", name); fakeJoin = fakeJoin.replaceAll("%displayname", player.getDisplayName()); } final String msgNotify = SimplyVanish.msgLabel+ChatColor.RED+name+ChatColor.GRAY+" reappeared."; for (final Player other : Bukkit.getServer().getOnlinePlayers()){ if (other.getName().equals(name)) { continue; } boolean notify = settings.notifyState && hasPermission(other, settings.notifyStatePerm); if (!other.canSee(player)){ showPlayer(player, other); if (notify){ if (message) { other.sendMessage(msgNotify); } } else if (!shouldSeeVanished(other)){ if (fakeJoin != null) { other.sendMessage(fakeJoin); } } } else{ // No need to adjust visibility. if (was && notify){ if (message) { other.sendMessage(msgNotify); } } } } if (message && vcfg.notify.state) { player.sendMessage(SimplyVanish.msgLabel+ChatColor.GRAY+"You are "+(was?"now":"still")+" "+ChatColor.RED+"visible"+ChatColor.GRAY+" to everyone!"); } SimplyVanish.stats.addStats(SimplyVanish.statsReappear, System.nanoTime()-ns); } /** * Public access method for vanish/reappear.<br> * This will call hooks.<br> * This can also be used to force a state update, though updateVanishState should be sufficient. * * @param playerName * @param vanished * @return */ public boolean setVanished(String playerName, boolean vanished) { playerName = playerName.toLowerCase(); boolean was = isVanished(playerName); // call event: SimplyVanishStateEvent svEvent = new SimplyVanishStateEvent(playerName, was, vanished); Bukkit.getServer().getPluginManager().callEvent(svEvent); if (svEvent.isCancelled()){ // no state update ! return false; } vanished = svEvent.getVisibleAfter(); // TODO // call hooks if (vanished) hookUtil.callBeforeVanish(playerName); else hookUtil.callBeforeReappear(playerName); // Do vanish or reappear: Player player = Bukkit.getServer().getPlayerExact(playerName); if (player != null){ // The simple but costly part. if ( vanished) doVanish(player, true); else doReappear(player, true); } else{ // Now very simple (lower-case names). if (vanished) addVanishedName(playerName); else removeVanishedName(playerName); } // call further hooks: if (vanished) hookUtil.callAfterVanish(playerName); else hookUtil.callAfterReappear(playerName); return true; } /** * Heavy update for who can see this player and whom this player can see.<br> * This is for internal calls (hookId 0).<br> * This will send notification messages. * @param player */ public boolean updateVanishState(Player player){ return updateVanishState(player, true); } /** * Heavy update for who can see this player and whom this player can see and other way round. * This is for internal calls (hookId 0).<br> * @param player * @param message If to message the player. */ public boolean updateVanishState(final Player player, final boolean message){ return updateVanishState(player, message, 0); } /** * Heavy update for who can see this player and whom this player can see and other way round. * @param player * @param message If to message the player. * @param hookId Id of the caller (0 = SimplyVanish, >0 = some other registered hook or API call) */ public boolean updateVanishState(final Player player, final boolean message, int hookId){ final long ns = System.nanoTime(); final String playerName = player.getName(); if (!hookUtil.allowUpdateVanishState(player, hookId)){ // TODO: either just return or still do messaging ? if (isVanished(playerName)) addVanishedName(playerName); else removeVanishedName(playerName); SimplyVanish.stats.addStats(SimplyVanish.statsUpdateVanishState, System.nanoTime()-ns); return false; } final Server server = Bukkit.getServer(); final Player[] players = server.getOnlinePlayers(); final boolean shouldSee = shouldSeeVanished(player); final boolean was = isVanished(playerName); // Show or hide other players to player: for (final Player other : players){ if (shouldSee||!isVanished(other.getName())){ if (!player.canSee(other)) showPlayer(other, player); } else if (player.canSee(other)) hidePlayer(other, player); if (!was && !other.canSee(player)) showPlayer(player, other); } if (was) doVanish(player, message); // remove: a) do not save 2x b) people will get notified. else removeVanishedName(playerName); SimplyVanish.stats.addStats(SimplyVanish.statsUpdateVanishState, System.nanoTime()-ns); return true; } /** * Only set the flags, no save. * TODO: probably needs a basic mix-in permission to avoid abuse (though that would need command spam). * @param playerName * @param args * @param startIndex Start parsing flags from that index on. * @param sender For performing permission checks. * @param hasBypass Has some bypass permission (results in no checks) * @param other If is sender is other than name * @param save If to save state. */ public void setFlags(String playerName, String[] args, int startIndex, CommandSender sender, boolean hasBypass, boolean other, boolean save) { long ns = System.nanoTime(); playerName = playerName.trim().toLowerCase(); if (playerName.isEmpty()) return; final String permBase = "simplyvanish.flags.set."+(other?"other":"self"); // bypass permission if (!hasBypass) hasBypass = hasPermission(sender, permBase); VanishConfig cfg = getVanishConfig(playerName, false); boolean hasSomePerm = hasBypass; // indicates that the player has any permission at all. if (cfg == null) cfg = new VanishConfig(); boolean hasClearFlag = false; List<String[]> applySets = new LinkedList<String[]>(); for ( int i = startIndex; i<args.length; i++){ final String arg = args[i].trim().toLowerCase(); if (arg.isEmpty()) continue; if (arg.charAt(0) == '*'){ String name = VanishConfig.getMappedFlagName(arg); if ( name.equals("clear")){ hasClearFlag = true; } else if (settings.flagSets.containsKey(name)){ String[] set = settings.flagSets.get(name); applySets.add(set); for ( String x : set){ if (VanishConfig.getMappedFlagName(x).equals("clear")) hasClearFlag = true; } } } } VanishConfig newCfg; if (hasClearFlag){ newCfg = new VanishConfig(); newCfg.set("vanished", cfg.get("vanished")); } else newCfg = cfg.clone(); // flag sets: for (String[] temp : applySets){ newCfg.readFromArray(temp, 0, false); } newCfg.readFromArray(args, startIndex, false); List<String> changes = cfg.getChanges(newCfg); // Determine permissions and apply valid changes: Set<String> missing = new HashSet<String>(); Set<String> ok = new HashSet<String>(); for ( String fn : changes){ String name = fn.substring(1); if (!hasBypass && !hasPermission(sender, permBase+"."+name)) missing.add(name); else{ hasSomePerm = true; ok.add(name); } } if (!missing.isEmpty()) Utils.send(sender, SimplyVanish.msgLabel+ChatColor.RED+"Missing permission for flags: "+Utils.join(missing, ", ")); if (!hasSomePerm){ // Difficult: might be a player without ANY permission. // TODO: maybe check permissions for all flags Utils.send(sender, SimplyVanish.msgLabel+ChatColor.DARK_RED+"You can not set these flags."); SimplyVanish.stats.addStats(SimplyVanish.statsSetFlags, System.nanoTime()-ns); return; } // if pass: putVanishConfig(playerName, cfg); // just to ensure it is there. // TODO: setflags event ! hookUtil.callBeforeSetFlags(playerName, cfg.clone(), newCfg.clone()); // Now actually apply changes to he vcfg. for (String name : ok){ cfg.set(name, newCfg.get(name)); } if ( save && cfg.changed && settings.saveVanishedAlways) onSaveVanished(); Player player = Bukkit.getServer().getPlayerExact(playerName); if (player != null){ updateVanishState(player, false); // TODO: what if returns false } if (!cfg.needsSave()) removeVanishedName(playerName); hookUtil.callAfterSetFlags(playerName); SimplyVanish.stats.addStats(SimplyVanish.statsSetFlags, System.nanoTime()-ns); } /** * Show flags for name to sender, or use the senders name, if name is null. * @param sender * @param name */ public void onShowFlags(CommandSender sender, String name) { if ( name == null) name = sender.getName(); name = name.toLowerCase(); VanishConfig cfg = getVanishConfig(name, false); if (cfg != null){ if (!cfg.needsSave()) { sender.sendMessage(SimplyVanish.msgDefaultFlags); } else { sender.sendMessage(SimplyVanish.msgLabel+ChatColor.GRAY+"Flags("+name+"): " + cfg.toLine()); } } else { sender.sendMessage(SimplyVanish.msgDefaultFlags); } } /** * Show player to canSee. * Delegating method, for the case of other things to be checked. * @param player The player to show. * @param canSee */ void showPlayer(Player player, Player canSee){ if (!Panic.checkInvolved(player, canSee, "showPlayer", settings.noAbort)) return; if (!hookUtil.allowShow(player, canSee)) return; try{ canSee.showPlayer(player); } catch(Throwable t){ Utils.severe("showPlayer failed (show "+player.getName()+" to "+canSee.getName()+"): "+t.getMessage()); t.printStackTrace(); Panic.onPanic(settings, new Player[]{player, canSee}); } } /** * Hide player from canNotSee. * Delegating method, for the case of other things to be checked. * @param player The player to hide. * @param canNotSee */ void hidePlayer(Player player, Player canNotSee){ if (!Panic.checkInvolved(player, canNotSee, "hidePlayer", settings.noAbort)) return; if (!hookUtil.allowHide(player, canNotSee)) return; try{ canNotSee.hidePlayer(player); } catch ( Throwable t){ Utils.severe("hidePlayer failed (hide "+player.getName()+" from "+canNotSee.getName()+"): "+t.getMessage()); t.printStackTrace(); Panic.onPanic(settings, new Player[]{player, canNotSee}); } } public boolean addVanishedName(String name) { VanishConfig cfg = getVanishConfig(name, true); boolean res = false; if (!cfg.vanished.state){ cfg.set(cfg.vanished, true); res = true; } if (cfg.changed && settings.saveVanishedAlways) onSaveVanished(); return res; } /** * * @param name * @return If the player was vanished. */ public boolean removeVanishedName(String name) { VanishConfig cfg = getVanishConfig(name, false); if (cfg==null) return false; boolean res = false; if (cfg.vanished.state){ cfg.set(cfg.vanished, false); if (!cfg.needsSave()) removeVanishConfig(name); res = true; } if (cfg.changed && settings.saveVanishedAlways) onSaveVanished(); return res; } /** * Central access point for checking if player has permission and wants to see vanished players. * @param player * @return */ public final boolean shouldSeeVanished(final Player player) { final VanishConfig cfg = getVanishConfig(player.getName(), false); if(cfg!=null){ if (!cfg.see.state) return false; } return hasPermission(player, "simplyvanish.see-all"); } public final boolean isVanished(final String playerName) { final VanishConfig cfg = getVanishConfig(playerName, false); if (cfg == null) return false; else return cfg.vanished.state; } /** * Lower case names.<br> * Currently iterates over all VanishConfig entries. * @return */ public Set<String> getVanishedPlayers() { Set<String> out = new HashSet<String>(); synchronized(vanishConfigs){ for (Entry<String, VanishConfig> entry : vanishConfigs.entrySet()){ if (entry.getValue().vanished.state) out.add(entry.getKey()); } } return out; } public String getVanishedMessage() { List<String> sorted = getSortedVanished(); StringBuilder builder = new StringBuilder(); builder.append(ChatColor.GOLD+"[VANISHED]"); Server server = Bukkit.getServer(); boolean found = false; for ( String n : sorted){ Player player = server.getPlayerExact(n); VanishConfig cfg = vanishConfigs.get(n); if (!cfg.vanished.state) continue; found = true; boolean isNosee = !cfg.see.state; // is lower case if ( player == null ){ builder.append(" "+ChatColor.GRAY+"("+n+")"); if (isNosee) builder.append(ChatColor.DARK_RED+"[NOSEE]"); } else{ builder.append(" "+ChatColor.GREEN+player.getName()); if (!hasPermission(player, "simplyvanish.see-all")) builder.append(ChatColor.DARK_RED+"[CANTSEE]"); else if (isNosee) builder.append(ChatColor.RED+"[NOSEE]"); } } if (!found) builder.append(" "+ChatColor.DARK_GRAY+"<none>"); return builder.toString(); } /** * Unlikely that sorted is needed, but anyway. * @return */ public List<String> getSortedVanished(){ Collection<String> vanished = getVanishedPlayers(); List<String> sorted = new ArrayList<String>(vanished.size()); sorted.addAll(vanished); Collections.sort(sorted); return sorted; } public void onNotifyPing() { if (!settings.pingEnabled) return; Set<String> keys = new HashSet<String>(); synchronized (vanishConfigs) { keys.addAll(vanishConfigs.keySet()); } for (final String name : keys){ final VanishConfig cfg = vanishConfigs.get(name); if (cfg == null) continue; final Player player = Bukkit.getPlayerExact(name); if (player == null) continue; if (!cfg.vanished.state) continue; if (!cfg.ping.state || !cfg.notify.state) continue; player.sendMessage(SimplyVanish.msgNotifyPing); } } /** * Get a VanishConfig, create it if necessary.<br> * (Might be from vanished, parked, or new thus put to parked). * @param lcName * @return */ public final VanishConfig getVanishConfig(final String name, final boolean create){ final String lcName = name.toLowerCase(); final VanishConfig cfg = vanishConfigs.get(lcName); if (cfg != null) return cfg; else if (!create) return null; else{ final VanishConfig newCfg = new VanishConfig(); vanishConfigs.put(lcName, newCfg); return newCfg; } } private final void putVanishConfig(final String name, final VanishConfig cfg) { vanishConfigs.put(name.toLowerCase(), cfg); } private final void removeVanishConfig(final String name) { vanishConfigs.remove(name.toLowerCase()); } /** * API method. * @param playerName * @param cfg * @param update * @param message */ public void setVanishConfig(String playerName, VanishConfig cfg, boolean update, boolean message) { VanishConfig newCfg = new VanishConfig(); newCfg.setAll(cfg); putVanishConfig(playerName, newCfg); if (update){ Player player = Bukkit.getServer().getPlayerExact(playerName); if (player != null){ updateVanishState(player, message); // TODO: what if returns false ? } } if (settings.saveVanishedAlways) onSaveVanished(); } void addStandardHooks(){ hookUtil.registerOnLoadHooks(); try{ hookUtil.addHook(new DisguiseCraftHook()); } catch(Throwable t){ } try{ hookUtil.addHook(new DynmapHook()); } catch(Throwable t){ } try{ hookUtil.addHook(new Essentials2Hook()); } catch(Throwable t){} } public final boolean hasPermission(final CommandSender sender, final String perm) { if (!(sender instanceof Player)) return sender.isOp(); if (settings.allowOps && sender.isOp()) return true; else if (settings.superperms){ if (sender.hasPermission(perm)) return true; else if (sender.hasPermission("simplyvanish.all")) return true; } final Set<String> perms = settings.fakePermissions.get(((Player)sender).getName().toLowerCase()); if (perms == null) return false; else if (perms.contains("simplyvanish.all")) return true; else return perms.contains(perm.toLowerCase()); } public Settings getSettings() { return settings; } public HookUtil getHookUtil() { return hookUtil; } public int getNewHookId() { return hookUtil.getNewHookId(); } public void setGod(String name, boolean god, CommandSender notify) { VanishConfig cfg = getVanishConfig(name, true); if (god == cfg.god.state){ if (notify != null) Utils.send(notify, SimplyVanish.msgLabel + ChatColor.GRAY + (name.equalsIgnoreCase(notify.getName())?" You were ":(name + " was "))+(god?"already":"not")+" in "+(god?ChatColor.GREEN:ChatColor.RED)+"god-mode."); } else{ cfg.set("god", god); if (settings.saveVanishedAlways) onSaveVanished(); Utils.tryMessage(name, SimplyVanish.msgLabel + ChatColor.GRAY + "You are "+(god?"now":"no longer")+" in "+(god?ChatColor.GREEN:ChatColor.RED)+"god-mode."); if (notify != null && !name.equalsIgnoreCase(notify.getName())) Utils.send(notify, SimplyVanish.msgLabel + ChatColor.GRAY + name + " is "+(god?"now":"no longer")+" in "+(god?ChatColor.GREEN:ChatColor.RED)+"god-mode."); } if (!cfg.needsSave()) removeVanishConfig(name); } }