package org.mctourney.autoreferee;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.World;
import org.bukkit.command.BlockCommandSender;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.messaging.Messenger;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scoreboard.Scoreboard;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import org.mcstats.Metrics;
import org.mcstats.Metrics.Graph;
import org.mctourney.autoreferee.commands.AdminCommands;
import org.mctourney.autoreferee.commands.ConfigurationCommands;
import org.mctourney.autoreferee.commands.PlayerCommands;
import org.mctourney.autoreferee.commands.PracticeCommands;
import org.mctourney.autoreferee.commands.ScoreboardCommands;
import org.mctourney.autoreferee.commands.SpectatorCommands;
import org.mctourney.autoreferee.listeners.CombatListener;
import org.mctourney.autoreferee.listeners.ObjectiveTracker;
import org.mctourney.autoreferee.listeners.ObjectiveTracer;
import org.mctourney.autoreferee.listeners.SpectatorListener;
import org.mctourney.autoreferee.listeners.TeamListener;
import org.mctourney.autoreferee.listeners.WorldListener;
import org.mctourney.autoreferee.listeners.ZoneListener;
import org.mctourney.autoreferee.listeners.lobby.LobbyListener;
import org.mctourney.autoreferee.listeners.lobby.LobbyListener.LobbyMode;
import org.mctourney.autoreferee.util.NullChunkGenerator;
import org.mctourney.autoreferee.util.SportBukkitUtil;
import org.mctourney.autoreferee.util.commands.CommandManager;
import org.mctourney.autoreferee.util.metrics.PieChartGraph;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
/**
* Base plugin class
*
* @author authorblues
*/
public class AutoReferee extends JavaPlugin
{
// singleton instance
private static AutoReferee instance = null;
// AutoReferee internal properties
private static Properties properties = null;
/**
* Gets the singleton instance of AutoReferee
*
* @return AutoReferee instance
*/
public static AutoReferee getInstance()
{ return instance; }
public static void log(String msg, Level level)
{ getInstance().getLogger().log(level, ChatColor.stripColor(msg)); }
public static void log(String msg)
{ log(msg, Level.INFO); }
public String getCommit()
{ return properties == null ? "??" : properties.getProperty("git-sha-1"); }
// expected configuration version number
private static final int PLUGIN_CFG_VERSION = 2;
// plugin channel encoding
public static final String PLUGIN_CHANNEL_ENC = "UTF-8";
// plugin channel prefix - identifies all channels as belonging to AutoReferee
private static final String PLUGIN_CHANNEL_PREFIX = "autoref:";
// plugin channels (referee)
public static final String REFEREE_PLUGIN_CHANNEL = PLUGIN_CHANNEL_PREFIX + "referee";
private SpectatorListener refChannelListener = null;
// name of the stored map configuration file
public static final String CFG_FILENAME = "autoreferee.xml";
// prefix for temporary worlds
public static final String WORLD_PREFIX = "world-autoref-";
// messages
public static final String NO_LOGIN_MESSAGE = "You are not scheduled for a match on this server.";
public static final String COMPLETED_KICK_MESSAGE = "Thank you for playing!";
public static final String NO_WEBSTATS_MESSAGE = "An error has occured; no webstats will be generated.";
// command manager
protected CommandManager commandManager = null;
public CommandManager getCommandManager()
{ return this.commandManager; }
public static void callEvent(Event event)
{ Bukkit.getServer().getPluginManager().callEvent(event); }
private World lobby = null;
/**
* Gets the world designated as the lobby world. When the server is in automated mode, this world
* is where users will be teleported to when the a match is unloaded and cleaned up.
*
* @return lobby world
*/
public World getLobbyWorld()
{
if (lobby == null)
{
String lname = getConfig().getString("lobby.world", null);
if (lname != null) this.setLobbyWorld(Bukkit.getWorld(lname));
}
return lobby;
}
/**
* Sets the world designated as the lobby world.
*
* @param world lobby world
*/
public void setLobbyWorld(World world)
{
if (world != null && !AutoRefMatch.isCompatible(world))
{
// change the lobby world
this.lobby = world;
// reflect this change in the config file
this.getConfig().set("lobby.world", this.lobby.getName());
this.saveConfig();
}
}
private boolean consoleLog = true;
private boolean consoleLogInColor = true;
protected boolean isConsoleLoggingEnabled()
{ return consoleLog; }
protected boolean isColoredConsoleLoggingEnabled()
{ return consoleLogInColor; }
// get the match associated with the world
private Map<UUID, AutoRefMatch> matches = Maps.newHashMap();
/**
* Gets match object associated with the given world.
*
* @return match object if one exists, otherwise null
*/
public AutoRefMatch getMatch(World world)
{ return world != null ? matches.get(world.getUID()) : null; }
/**
* Gets all existing match objects for this server.
*
* @return a collection of match objects
*/
public Collection<AutoRefMatch> getMatches()
{ return matches.values(); }
/**
* Adds a given match to be tracked by the server.
*/
public void addMatch(AutoRefMatch match)
{ matches.put(match.getWorld().getUID(), match); }
/**
* Removes a match from the server.
*/
public void clearMatch(AutoRefMatch match)
{ matches.remove(match.getWorld().getUID()); }
// track owner of a piece of tnt (perhaps with propagation)
private Map<UUID, AutoRefPlayer> tntOwner = Maps.newHashMap();
/**
* Gets the player responsible for a primed TNT.
* @param entity primed tnt entity
*/
public AutoRefPlayer getTNTOwner(Entity entity)
{ return tntOwner.get(entity.getUniqueId()); }
/**
* Sets the player responsible for a primed TNT.
* @param entity primed tnt entity
* @param apl player responsible for tnt
*/
public void setTNTOwner(Entity entity, AutoRefPlayer apl)
{
if (entity.getType() == EntityType.PRIMED_TNT)
tntOwner.put(entity.getUniqueId(), apl);
}
/**
* Clears out a primed TNT from the tracked list.
* @param entity primed tnt entity
*/
public AutoRefPlayer clearTNTOwner(Entity entity)
{ return tntOwner.remove(entity.getUniqueId()); }
/**
* Gets team object associated with a player. Searches all matches for this player,
* returns team object for the team containing this player.
*
* @return team object if player is on team, otherwise null
*/
public AutoRefTeam getTeam(Player player)
{
// go through all the matches
for (AutoRefMatch match : matches.values())
{
// if the player is on a team for this match...
AutoRefTeam team = match.getPlayerTeam(player);
if (team != null) return team;
}
// this player is on no known teams
return null;
}
/**
* Gets the team the player is expected to join. Matches setup by automated match
* configurations may designate certain players for certain teams.
*
* @return player's team, null if no such team
*/
public AutoRefTeam getExpectedTeam(Player player)
{
AutoRefTeam actualTeam = getTeam(player);
if (actualTeam != null) return actualTeam;
// go through all the matches
for (AutoRefMatch match : matches.values())
{
// if the player is expected for any of these teams
for (AutoRefTeam team : match.getTeams())
if (team.getExpectedPlayers().contains(player.getName()))
return team;
}
// this player is expected on no known teams
return null;
}
public void onEnable()
{
// store the singleton instance
instance = this;
updateConfigurationFile();
// ensure we are dealing with the right type of config file
int configVersion = getConfig().getInt("config-version", PLUGIN_CFG_VERSION);
if (configVersion != PLUGIN_CFG_VERSION) getLogger().severe(String.format(
"!!! Incorrect config-version (expected %d, got %d)", PLUGIN_CFG_VERSION, configVersion));
// get properties file data
InputStream propStream = getResource("autoreferee.properties");
if (propStream != null) try
{
properties = new Properties();
properties.load(propStream);
}
catch (IOException e)
{ AutoReferee.log("Failed to load properties file.", Level.SEVERE); }
PluginManager pm = getServer().getPluginManager();
PracticeCommands practice = new PracticeCommands(this);
String lobbymode = getConfig().getString("lobby.mode", "manual");
LobbyListener lobbyListener = LobbyMode.fromConfig(lobbymode).getInstance(this);
// listener utility classes, subdivided for organization
pm.registerEvents(new TeamListener(this), this);
pm.registerEvents(new CombatListener(this), this);
pm.registerEvents(new ZoneListener(this), this);
pm.registerEvents(new WorldListener(this), this);
pm.registerEvents(new ObjectiveTracker(this), this);
pm.registerEvents(new ObjectiveTracer(this), this);
// save this reference to use for setting up the referee channel later
pm.registerEvents(refChannelListener = new SpectatorListener(this), this);
pm.registerEvents(lobbyListener, this);
pm.registerEvents(practice, this);
// user interface commands in a custom command manager
commandManager = new CommandManager();
commandManager.registerCommands(new PlayerCommands(this), this);
commandManager.registerCommands(new AdminCommands(this), this);
commandManager.registerCommands(new SpectatorCommands(this), this);
commandManager.registerCommands(new ConfigurationCommands(this), this);
commandManager.registerCommands(new ScoreboardCommands(this), this);
commandManager.registerCommands(lobbyListener, this);
commandManager.registerCommands(practice, this);
// go ahead and generate all help for the plugin
commandManager.generateHelp(this);
// global configuration object (can't be changed, so don't save onDisable)
InputStream configInputStream = getResource("defaults/config.yml");
if (configInputStream != null) getConfig().setDefaults(
YamlConfiguration.loadConfiguration(configInputStream));
getConfig().options().copyDefaults(true); saveConfig();
// setup a possible alternate map repository
String mrepo = this.getConfig().getString("map-repo", null);
if (mrepo != null) AutoRefMatch.changeMapRepo(mrepo);
// attempt to setup the plugin channels
setupPluginChannels();
// fire up the plugin metrics
try { setupPluginMetrics(); }
catch (IOException e) { getLogger().severe("Plugin Metrics not enabled."); }
// wrap up, debug to follow this message
getLogger().info(this.getName() + " (" + Bukkit.getName() + ") loaded successfully" +
(SportBukkitUtil.hasSportBukkitApi() ? " with SportBukkit API" : "") + ".");
getLogger().info(this.getName() + " Git: " + this.getCommit());
// are ties allowed? (auto-mode always allows for ties)
AutoRefMatch.setAllowTies(getConfig().getBoolean("allow-ties", false));
// log messages to console?
consoleLog = getConfig().getBoolean("console-log", true);
consoleLogInColor = getConfig().getBoolean("console-colors", true);
// setup the map library folder
AutoRefMap.getMapLibrary();
// process initial world(s), just in case
for ( World w : getServer().getWorlds() )
AutoRefMatch.setupWorld(w, false);
// update maps automatically if auto-update is enabled
if (getConfig().getBoolean("auto-update", true))
AutoRefMap.getUpdates(Bukkit.getConsoleSender(), false);
}
public void onDisable()
{
for (AutoRefMatch match : getMatches())
match.saveWorldConfiguration();
getLogger().info(this.getName() + " disabled.");
}
private void setupPluginChannels()
{
Messenger m = getServer().getMessenger();
// setup referee plugin channels
m.registerOutgoingPluginChannel(this, REFEREE_PLUGIN_CHANNEL);
m.registerIncomingPluginChannel(this, REFEREE_PLUGIN_CHANNEL, refChannelListener);
}
protected PieChartGraph playedMapsTracker = null;
private void setupPluginMetrics()
throws IOException
{
Metrics metrics = new Metrics(this);
Set<String> mapNames = Sets.newHashSet();
for (AutoRefMap map : AutoRefMap.getRemoteMaps())
mapNames.add(map.getName());
Graph gMaps = metrics.createGraph("Most Popular Maps");
playedMapsTracker = new PieChartGraph(gMaps, mapNames);
metrics.start();
}
public static WorldEditPlugin getWorldEdit()
{
Plugin x = Bukkit.getPluginManager().getPlugin("WorldEdit");
return (x != null && x instanceof WorldEditPlugin) ? (WorldEditPlugin) x : null;
}
private UUID consoleWorld = null;
/**
* Gets the world that the console user has selected.
*
* @return The world selected by the console user
*/
public World getConsoleWorld()
{
return consoleWorld == null || getServer().getWorld(consoleWorld) == null
? getServer().getWorlds().get(0) : getServer().getWorld(consoleWorld);
}
/**
* Sets the world that the console user has selected.
*/
public void setConsoleWorld(World world)
{ consoleWorld = world == null ? null : world.getUID(); }
/**
* Sets the world that the console user has selected.
*/
public void setConsoleWorld(String name)
{
World world = getServer().getWorld(name);
if (world == null)
{
Player player = getServer().getPlayer(name);
if (player != null) world = player.getWorld();
}
this.setConsoleWorld(world);
}
/**
* Gets the world associated with a command sender.
*/
public World getSenderWorld(CommandSender sender)
{
if (sender instanceof Player)
return ((Player) sender).getWorld();
if (sender instanceof BlockCommandSender)
return ((BlockCommandSender) sender).getBlock().getWorld();
return getConsoleWorld();
}
public Scoreboard getWorldScoreboard(World world)
{
AutoRefMatch match = this.getMatch(world);
if (match == null || match.getScoreboard() == null)
return Bukkit.getScoreboardManager().getMainScoreboard();
else return match.getScoreboard();
}
@Override
public ChunkGenerator getDefaultWorldGenerator(String worldname, String id)
{ return new NullChunkGenerator(); }
private SyncMessageTask messageQueue = new SyncMessageTask();
/**
* Force a message to be sent synchronously. Safe to use from an asynchronous task.
*
* @param msgs messages to be sent
*/
public void sendMessageSync(CommandSender recipient, String ...msgs)
{
if (recipient != null) for (String msg : msgs)
messageQueue.addMessage(recipient, msg);
try { messageQueue.runTask(this); }
catch (IllegalStateException ignored) { }
}
private class SyncMessageTask extends BukkitRunnable
{
private class RoutedMessage
{
public CommandSender recipient;
public String message;
public RoutedMessage(CommandSender r, String m)
{ recipient = r; message = m; }
}
private List<RoutedMessage> msgQueue = Lists.newLinkedList();
public SyncMessageTask addMessage(CommandSender recipient, String message)
{ msgQueue.add(new RoutedMessage(recipient, message)); return this; }
@Override public void run()
{
AutoReferee.this.messageQueue = new SyncMessageTask();
for (RoutedMessage msg : msgQueue) if (msg.recipient != null)
msg.recipient.sendMessage(msg.message);
msgQueue.clear();
}
}
// this is a catch-all to make general upgrades to old configuration files
private void updateConfigurationFile()
{
FileConfiguration config = getConfig();
// v2.2, 2013/05/04, remove remains of server mode
if (config.get("server-mode") != null)
config.set("server-mode", null);
// v2.3, 2013/05/17, update lobby info
if (config.getString("lobby-world") != null)
{
// set new parameter paths
config.set("lobby.world", config.getString("lobby-world"));
config.set("lobby.mode", "manual");
// remove old configuration flag
config.set("lobby-world", null);
}
// v2.3, 2013/06/03, allow for lobby mode enum (not "auto" boolean)
if (config.getString("lobby.auto") != null)
{
boolean autoMode = config.getBoolean("lobby.auto", false);
config.set("lobby.mode", autoMode ? "auto" : "manual");
config.set("lobby.auto", null);
}
}
}