Package com.github.zathrus_writer.commandsex

Source Code of com.github.zathrus_writer.commandsex.CommandsEX

package com.github.zathrus_writer.commandsex;

import static com.github.zathrus_writer.commandsex.Language._;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.permissions.Permission;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.java.JavaPlugin;
import org.kitteh.vanish.staticaccess.VanishNoPacket;

import com.github.zathrus_writer.commandsex.helpers.Commands;
import com.github.zathrus_writer.commandsex.helpers.Jails;
import com.github.zathrus_writer.commandsex.helpers.LogHelper;
import com.github.zathrus_writer.commandsex.helpers.Metrics;
import com.github.zathrus_writer.commandsex.helpers.Metrics.Graph;
import com.github.zathrus_writer.commandsex.helpers.Utils;

public class CommandsEX extends JavaPlugin implements Listener {
  // our plugin :-)
  public static CommandsEX plugin;
  // regex to check if String is a number
  public final static String intRegex = "(-)?(\\d){1,10}(\\.(\\d){1,10})?";
  // plugin description file, used at least on 2 places, so it's here :-P
  public transient static PluginDescriptionFile pdfFile;
  // if true, the _() function won't try to load existing perUserLocales from DB - used in exceptions handling
  public static Boolean avoidDB = false;
  // our command listener class
  private Commands cListener;
  // if SQL is enabled for our plugin, this will be true,
  // otherwise it'll remain false... this variable removes
  // the need for unneccessary SQLManager class if we don't need it
  public static Boolean sqlEnabled = false;
  // number of seconds each player on the server played so far
  public static Map<String, Integer> playTimes = new HashMap<String, Integer>();
  // timestamp of user's server join time, used to update playtime periodically
  public static Map<String, Integer> joinTimes = new HashMap<String, Integer>();
  // IPs of all users currently on the server (or that was on the server configurable time ago)
  public static Map<String, String> playerIPs = new HashMap<String, String>();
  // this map will hold IDs of tasks patched to scheduler for delayed execution when a user joins the server
  // ... this delay is used so when a player spams login/logout, the DB is not queried all the time for his playtime
  protected Map<String, Integer> playTimeLoadTasks = new HashMap<String, Integer>();
  // minimum number of seconds from player's last logout to pass before his playtime is loaded from DB
  public static Integer minTimeFromLogout = 30;
  // number of seconds after which we flush playTimes into database
  public static Integer playTimesFlushTime = 180;
  // number of seconds a player must stay on the server before his playTime is storable
  protected Integer minTimeToSavePlayTime = 45;
  // functions contained in this list will get executed on plugin disable,
  // so we can handle things like DB or XMPP disconnects correctly
  public static List<String> onDisableFunctions = new ArrayList<String>();
  // true if Vault plugin was found in the server installation
  public static Boolean vaultPresent = false;
  // true if VanishNoPacket plugin was found in the server installation
  public static Boolean vanishNoPacketPresent = false;
  // reference our plugin timer
  private long startTime, stopTime, finalTime;
  public static File file;
  // stores all loaded commands, listeners and init's
  public static List<String> loadedClasses = new ArrayList<String>();
  // store metrics
  public static Metrics metrics;
  // stores the amount of times a command has been used
  public static HashMap<String, Integer> commandUses = new HashMap<String, Integer>();
 
  /***
   * Class constructor.
   * We need the static plugin declaration for getting config from other modules.
   */
  public CommandsEX() {
    plugin = this;
  }
 
  /***
   * OnEnable
   */
  @Override
  public void onEnable() {
    startTimer();
    file = getFile();
   
    // save default config if not saved yet
    getConfig().options().copyDefaults(true);
    saveConfig();
   
    // check for Vault plugin presence
    try {
      new Vault();
      vaultPresent = true;
    } catch (Throwable e) {}
   
    // check for VanishNoPacket plugin presence
    try {
      new VanishNoPacket();
      vanishNoPacketPresent = true;
    } catch (Throwable e){}

        // Enable CombatTag support
        new CombatTag();

    // set up commands listener
    cListener = new Commands(this);

    // initialize translations
    Language.init(this);

    // get description file and display initial startup OK info
    pdfFile = this.getDescription();
    LogHelper.logInfo("[" + pdfFile.getName() + "] " + _("startupMessage", "") + " " + Language.defaultLocale);
    LogHelper.logInfo("[" + pdfFile.getName() + "] " + _("version", "") + " " + pdfFile.getVersion() + " " + _("enableMsg", ""));

    // initialize database, if we have it included in our build
    Class<?>[] proto = new Class[] {this.getClass()};
    Object[] params = new Object[] {this};
    if (getConf().getBoolean("enableDatabase")) {
      try {
        Class<?> c = Class.forName("com.github.zathrus_writer.commandsex.SQLManager");
        Method method = c.getDeclaredMethod("init", proto);
        method.invoke(null, params);
      } catch (ClassNotFoundException e) {
        // this is OK, since we won't neccessarily have this class in each build
      } catch (Throwable e) {
        LogHelper.logSevere(_("dbError", ""));
        LogHelper.logDebug("Message: " + e.getMessage() + ", cause: " + e.getCause());
      }
    }
   
    // add all commands to the loadedClasses List
    for (String cmd : CommandsEX.pdfFile.getCommands().keySet()){
      loadedClasses.add("Command_" + cmd);
    }
   
    // enable existing classes that are listening to events - determine names from permissions
    // ... also call init() function for each helper class that requires initialization (has Init prefix in permissions)
    List<Permission> perms = CommandsEX.pdfFile.getPermissions();
    for(int i = 0; i <= perms.size() - 1; i++) {
      // call initialization function for each of the event handling functions
      String pName = perms.get(i).getName();
      if (pName.startsWith("Listener")) {
        String[] s = pName.split("\\.");
        if (s.length == 0) continue;
        try {
          Class.forName("com.github.zathrus_writer.commandsex.handlers.Handler_" + s[1]).newInstance();
          // add class to loadedClasses if successfull
          loadedClasses.add("Handler_" + s[1]);
        } catch (ClassNotFoundException e) {
          // this is OK, since we won't neccessarily have this class in each build
        } catch (Throwable e) {
          LogHelper.logSevere(_("loadTimeError", ""));
          LogHelper.logDebug("Message: " + e.getMessage() + ", cause: " + e.getCause());
          e.printStackTrace();
        }
      } else if (pName.startsWith("Init")) {
        String[] s = pName.split("\\.");
        if (s.length == 0) continue;
        try {
          Class<?> c = Class.forName("com.github.zathrus_writer.commandsex.helpers." + s[1]);
          Method method = c.getDeclaredMethod("init", proto);
          method.invoke(null, params);
          // add class to loadedClasses is successful
          loadedClasses.add("Init_" + s[1]);
        } catch (ClassNotFoundException e) {
          // this is OK, since we won't neccessarily have this class in each build
        } catch (Throwable e) {
          LogHelper.logSevere(_("loadTimeError", ""));
          LogHelper.logDebug("Message: " + e.getMessage() + ", cause: " + e.getCause());
          e.printStackTrace();
        }
      }
    }
   
    // setup a recurring task that will periodically save players' play times into DB
    if (sqlEnabled) {
      getServer().getScheduler().runTaskTimerAsynchronously(this, new Runnable() {
        @Override
          public void run() {
              // flush play times only for players that have their playtime loaded initially
          Integer stamp = Utils.getUnixTimestamp(0L);
          Iterator<Entry<String, Integer>> it = CommandsEX.playTimes.entrySet().iterator();
          List<Object> insertParts = new ArrayList<Object>();
          List<Object> insertValues = new ArrayList<Object>();
          while (it.hasNext()) {
            Map.Entry<String, Integer> pairs = (Map.Entry<String, Integer>)it.next();
           
            // only update data for players that don't have -1 set as their playTime
            if (pairs.getValue() <= -1) continue;
           
            String pName = pairs.getKey();
            // update play time and join time
            Integer played = (pairs.getValue() + (stamp - CommandsEX.joinTimes.get(pName)));
            CommandsEX.playTimes.put(pName, played);
            CommandsEX.joinTimes.put(pName, Utils.getUnixTimestamp(0L));
           
            // prepare DB query parts
            insertParts.add("SELECT ? AS 'player_name', ? AS 'seconds_played'");
            insertValues.add(pName);
            insertValues.add(played);
            //it.remove(); // avoids a ConcurrentModificationException - not needed in our case and will clear out HashMap!
          }
         
          if (insertParts.size() > 0) {
            // update the database
            SQLManager.query("INSERT "+ (SQLManager.sqlType.equals("mysql") ? "" : "OR REPLACE ") +"INTO " + SQLManager.prefix + "playtime "+ Utils.implode(insertParts, " UNION ") + (SQLManager.sqlType.equals("mysql") ? " ON DUPLICATE KEY UPDATE seconds_played = VALUES(seconds_played)" : ""), insertValues);
          }
          }
      }, (20 * playTimesFlushTime), (20 * playTimesFlushTime));
     
      // tell Bukkit we have some event handling to do in this class :-)
      this.getServer().getPluginManager().registerEvents(this, this);
    }

    // don't start metrics if the user has disabled it
    if (getConf().getBoolean("pluginMetrics")){
      try {
          metrics = new Metrics(plugin);
         
          Graph featureGraph = metrics.createGraph("Feature Statistics");
          if (loadedClasses.contains("Init_Home")){
            featureGraph.addPlotter(new Metrics.Plotter("Homes Set") {
            @Override
              public int getValue() {
              int count = 0;
              try {
                ResultSet rs = SQLManager.query_res("SELECT player_name FROM " + SQLManager.prefix + "homes");
                while (rs.next()){
                  count++;
                }
                rs.close();
              } catch (SQLException e){
                e.printStackTrace();
              }
             
              return count;
            }
          });
          }
         
          if (loadedClasses.contains("Init_Warps")){
            featureGraph.addPlotter(new Metrics.Plotter("Warps Set") {
            @Override
            public int getValue() {
              int count = 0;
              try {
                ResultSet rs = SQLManager.query_res("SELECT owner_name FROM " + SQLManager.prefix + "warps");
                while (rs.next()){
                  count++;
                }
                rs.close();
              } catch (SQLException e){
                e.printStackTrace();
              }
             
              return count;
            }
          });
          }
         
          if (loadedClasses.contains("Init_Nicknames")){
            featureGraph.addPlotter(new Metrics.Plotter("Nicknames Set") {
            @Override
            public int getValue() {
              int count = 0;
              try {
                ResultSet rs = SQLManager.query_res("SELECT player_name FROM " + SQLManager.prefix + "nicknames");
                while (rs.next()){
                  count++;
                }
                rs.close();
              } catch (SQLException e){
                e.printStackTrace();
              }
             
              return count;
            }
          });
          }
         
          if (loadedClasses.contains("Init_Nametags")){
            featureGraph.addPlotter(new Metrics.Plotter("Nametags Set") {
            @Override
            public int getValue() {
              int count = 0;
              try {
                ResultSet rs = SQLManager.query_res("SELECT player_name FROM " + SQLManager.prefix + "nametags");
                while (rs.next()){
                  count++;
                }
                rs.close();
              } catch (SQLException e){
                e.printStackTrace();
              }
             
              return count;
            }
          });
          }
         
          if (loadedClasses.contains("Init_Kits")){
            featureGraph.addPlotter(new Metrics.Plotter("Kits Set") {
            @Override
            public int getValue() {
              int count = 0;
              FileConfiguration f = CommandsEX.getConf();
              ConfigurationSection configGroups = f.getConfigurationSection("kits");
              if (configGroups != null){
                Set<String> kitGroups = configGroups.getKeys(false);

                for (String group : kitGroups) {
                  ConfigurationSection kits = f.getConfigurationSection("kits." + group);
                  Set<String> kitNames = kits.getKeys(false);
                  count = count + kitNames.size();
                }
              }
             
              return count;
            }
            });
          }

          // Add commands to Command Uses graph, these items must be present
          // when the server starts up otherwise the graph cannot be sent
          Graph commandUsesGraph = metrics.createGraph("Command Uses");
          for (final String s : loadedClasses){
            if (s.startsWith("Command_cex_")){
              String key = s.replaceAll("Command_cex_", "/");
             
              commandUsesGraph.addPlotter(new Metrics.Plotter(key) {
                @Override
                public int getValue() {
                  return (commandUses.containsKey(s) ? commandUses.get(s) : 0);
                }
              });
            }
          }
         
          metrics.start();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
   
    stopTimer();
  }
 
  // Timer Methods
  public void startTimer(){
    startTime = System.currentTimeMillis();
  }
 
  public void stopTimer(){
    stopTime = System.currentTimeMillis();
    finalTime = stopTime - startTime;
    if (getConf().getBoolean("startupTimer")){
      LogHelper.logInfo("[CommandsEx] " + _("startupTime", "") + finalTime + "ms");
    }
  }

  /***
   * Bukkit API requires us to bind each existing command individually
   * to a separate class if we want to utilize such a class.
   * Here, we'll just pass each command to the class' onCommand function,
   * saving us the trouble of writing a list of all possible commands.
   */
  public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) {
    return cListener.onCommand(sender, cmd, commandLabel, args);
  }
 
  /***
   * OnDisable
   */
  @Override
  public void onDisable() {
    // if we don't have per-player language loaded from DB, do not try to load it now :-)
    avoidDB = true;
   
    // execute everything that should be executed on disable
    if (onDisableFunctions.size() > 0) {
      Class<?>[] proto = new Class[] {this.getClass()};
      Object[] params = new Object[] {this};
     
      for (String s : onDisableFunctions) {
        try {
          String[] ss = s.split("#####");
          Class<?> c = Class.forName(ss[0]);
          Method method = c.getDeclaredMethod(ss[1], proto);
          method.invoke(null, params);
        } catch (Throwable e) {
          LogHelper.logSevere("[CommandsEX] " + _("errorFunctionOnDisableExecute", "") + s);
          LogHelper.logDebug("Message: " + e.getMessage() + ", cause: " + e.getCause());
        }
      }
    }
   
    // close all database connections
    if (getConf().getBoolean("enableDatabase")) {
      Class<?>[] proto = new Class[] {this.getClass()};
      Object[] params = new Object[] {this};
      try {
        Class<?> c = Class.forName("com.github.zathrus_writer.commandsex.SQLManager");
        Method method = c.getDeclaredMethod("onDisable", proto);
        method.invoke(null, params);
      } catch (ClassNotFoundException e) {
        // this is OK, since we won't neccessarily have this class in each build
      } catch (Throwable e) {
        LogHelper.logSevere(_("dbError", ""));
        LogHelper.logDebug("Message: " + e.getMessage() + ", cause: " + e.getCause());
      }
    }
   
    LogHelper.logInfo("[" + this.getDescription().getName() + "] " + _("disableMsg", ""));
  }

  /***
   * Returns current config.
   * @return
   */
  public static FileConfiguration getConf() {
    return plugin.getConfig();
  }
   
  /***
   * When a player joins the server, their join timestamp is saved, helping us to determine
   * how long he's been online. Also, a new delayed task will be created that will load up player's
   * full playtime from database, should the player stay on the server for more than minTimeFromLogout seconds.
   *
   * Additionally, when a player joins the server, their IP is stored internally to allow for IP-banning when
   * the player leaves as soon as they burst-grief.
   * @param e
   */
  @EventHandler(priority = EventPriority.NORMAL)
  public void pJoin(PlayerJoinEvent e) {
    String pName = e.getPlayer().getName();
    playerIPs.put(pName.toLowerCase(), e.getPlayer().getAddress().getAddress().getHostAddress());

    // check if player is not jailed
    try {
      if (Jails.jailedPlayers.containsKey(pName)) {
        LogHelper.showInfo("jailsStillJailed", e.getPlayer());
      }
    } catch (Throwable ex) {}
   
    if (sqlEnabled) {
      // add -1 to playtimes, so our Runnable function will know to look the player up after the delay has passed
      playTimes.put(pName, -1);
      joinTimes.put(pName, Utils.getUnixTimestamp(0L));

      // cancel out any previously delayed task created by this user
      if (this.playTimeLoadTasks.containsKey(pName)) {
        this.getServer().getScheduler().cancelTask(this.playTimeLoadTasks.get(pName));
      }
     
      // add new delayed task to load user's playtime from DB
      this.playTimeLoadTasks.put(pName, this.getServer().getScheduler().scheduleSyncDelayedTask(this, new Runnable() {
        @Override
        public void run() {
          // load playtimes for all players with time set to -1 and valid time on the server (i.e. +(minTimeFromLogout - 5) seconds)
          // ... and -5 seconds to allow for server lag and similar things to happen :-)
          Iterator<Entry<String, Integer>> it = CommandsEX.playTimes.entrySet().iterator();
          List<String> playerNames = new ArrayList<String>();
          Integer stamp = Utils.getUnixTimestamp(0L);
          while (it.hasNext()) {
            Map.Entry<String, Integer> pairs = (Map.Entry<String, Integer>)it.next();

            // only check players with -1 as playtime
            if (pairs.getValue() > -1) continue;

            // check if the player's last logout time was not within last minTimeFromLogout seconds, in which case we don't bother
            // checking up on him and his playtime will be loaded as needed on Quit event
            String pName = (String)pairs.getKey();
            OfflinePlayer o = CommandsEX.plugin.getServer().getOfflinePlayer(pName);
           
            if ((o != null) && (o.getLastPlayed() > 0)) {
              // convert miliseconds of player last visit time to seconds
              Integer lastPlay = Utils.getUnixTimestamp(o.getLastPlayed());
              // check if we should be adding the player, based on last quitting time
              if ((stamp - lastPlay) >= (CommandsEX.minTimeFromLogout - 5)) {
                playerNames.add(pName);
              }
            } else {
              // this is a new player, set his playtime to 0
              CommandsEX.playTimes.put(pName, 0);
            }
                //it.remove(); // avoids a ConcurrentModificationException - not needed in our case and will clear out HashMap!
            }
         
          // load playtimes from DB
          Integer pSize = playerNames.size();
          if (pSize > 0) {
            try {
              String[] qMarks = new String[pSize];
              Arrays.fill(qMarks, "?");
              ResultSet res = SQLManager.query_res("SELECT * FROM " + SQLManager.prefix + "playtime WHERE player_name IN ("+ Utils.implode(qMarks, ", ") +")", playerNames);
              while (res.next()) {
                CommandsEX.playTimes.put(res.getString("player_name"), res.getInt("seconds_played"));
              }
              res.close();
            } catch (Throwable e) {
              // unable to load players' playtimes, show up on console
              LogHelper.logSevere("[CommandsEX] " + _("dbTotalPlayTimeGetError", ""));
              LogHelper.logDebug("Message: " + e.getMessage() + ", cause: " + e.getCause());
              return;
            }
          }
        }
      }, (20 * minTimeFromLogout)));
    }
  }
 
  /***
   * Single-purpose class that handles removal of old Players' IPs after a certain amount of time.
   */
  public static class DelayedIpRemoval implements Runnable {
      private String pName;
     
      public DelayedIpRemoval(String pName) {
        this.pName = pName;
      }
     
      public void run() {
        // only remove offline player - in case of the player re-joining server
        if (Bukkit.getServer().getPlayer(this.pName) == null) {
          CommandsEX.playerIPs.remove(this.pName.toLowerCase());
        }
      }
    }
 
  /***
   * When a player leaves the server, their join timestamp and playtime is removed to free up memory.
   * Playtime will get stored into database if the time since his joining is more than 45 seconds
   * to prevent database overloading when player tries to spam logins/logouts.
   * @param e
   */
  @EventHandler(priority = EventPriority.NORMAL)
  public void pQuit(PlayerQuitEvent e) {
    String pName = e.getPlayer().getName();
    Integer stamp = Utils.getUnixTimestamp(0L);

    // schedule player's IP removal
    CommandsEX.plugin.getServer().getScheduler().scheduleSyncDelayedTask(CommandsEX.plugin, new DelayedIpRemoval(pName), (20 * getConf().getInt("maxIPholdTime")));
   
    // save player's playtime
    if (sqlEnabled && joinTimes.containsKey(pName)) {
      Integer played = (stamp - joinTimes.get(pName));
      if (played >= minTimeToSavePlayTime) {
        // player was online for more than minTimeToSavePlayTime seconds, count this visit
        if (playTimes.containsKey(pName) && (playTimes.get(pName) > -1)) {
          // update playtime directly if we have previous time loaded
          playTimes.put(pName, (playTimes.get(pName) + played));
        } else {
          // get total playtime from database, since we don't have it loaded yet
          try {
            // first, reset the time, so we don't add to -1 later
            Integer pTime = 0;
            ResultSet res = SQLManager.query_res("SELECT seconds_played FROM " + SQLManager.prefix + "playtime WHERE player_name = ?", pName);
            while (res.next()) {
              pTime = res.getInt("seconds_played");
            }
            res.close();
            playTimes.put(pName, (pTime + played));
          } catch (Throwable ex) {
            // something went wrong...
            LogHelper.logSevere("[CommandsEX] " + _("dbTotalPlayTimeGetError", ""));
            LogHelper.logDebug("Message: " + ex.getMessage() + ", cause: " + ex.getCause());
          }
        }
        // update DB with new value
        played = playTimes.get(pName);
        SQLManager.query("INSERT "+ (SQLManager.sqlType.equals("mysql") ? "" : "OR REPLACE ") +"INTO " + SQLManager.prefix + "playtime VALUES (?, ?)"+ (SQLManager.sqlType.equals("mysql") ? " ON DUPLICATE KEY UPDATE seconds_played = VALUES(seconds_played)" : ""), pName, played);
      }
      joinTimes.remove(pName);
      playTimes.remove(pName);
    }
  }
}
TOP

Related Classes of com.github.zathrus_writer.commandsex.CommandsEX

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.