com.orion.plugin.AdminPlugin.java Source code

Java tutorial

Introduction

Here is the source code for com.orion.plugin.AdminPlugin.java

Source

/**
 * Copyright (c) 2012 Daniele Pantaleone, Mathias Van Malderen
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * 
 * @author      Daniele Pantaleone
 * @version     1.1
 * @copyright   Daniele Pantaleone, 17 February, 2013
 * @package     com.orion.plugin
 **/

package com.orion.plugin;

import java.net.UnknownHostException;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.joda.time.DateTime;

import com.google.common.base.Joiner;
import com.google.common.eventbus.Subscribe;
import com.orion.annotation.Dependency;
import com.orion.annotation.Usage;
import com.orion.bot.Orion;
import com.orion.bot.PenaltyType;
import com.orion.command.Command;
import com.orion.command.Prefix;
import com.orion.comparator.ClientLevelComparator;
import com.orion.comparator.ClientSlotComparator;
import com.orion.console.UrT42Console;
import com.orion.domain.Alias;
import com.orion.domain.Client;
import com.orion.domain.Group;
import com.orion.domain.IpAlias;
import com.orion.domain.Penalty;
import com.orion.event.ClientConnectEvent;
import com.orion.event.ClientNameChangeEvent;
import com.orion.event.ClientTeamChangeEvent;
import com.orion.exception.CommandRuntimeException;
import com.orion.exception.CommandSyntaxException;
import com.orion.misc.RegisteredCommand;
import com.orion.misc.TimeParser;
import com.orion.urt.Color;
import com.orion.urt.Team;
import com.orion.utility.Configuration;
import com.orion.utility.MessageFormat;

public class AdminPlugin extends Plugin {

    public Map<String, String> messages;
    public Map<String, String> keywords;

    public int adminLevel;
    public int noReasonLevel;
    public long banDuration;

    /**
     * Object constructor
     * 
     * @author Daniele Pantaleone
     * @param  config Plugin <tt>Configuration</tt> object
     * @param  orion <tt>Orion</tt> object reference
     **/
    public AdminPlugin(Configuration config, Orion orion) {
        super(config, orion);
    }

    /**
     * Load the <tt>Plugin</tt> configuration file and fill <tt>Plugin</tt> attributes
     * 
     * @author Daniele Pantaleone
     **/
    @Override
    public void onLoadConfig() {

        this.debug("Loading plugin configuration...");

        this.messages = this.config.getMap("messages");
        this.keywords = this.config.getMap("keywords");

        this.adminLevel = this.config.getInt("settings", "admin_level", 20);
        this.noReasonLevel = this.config.getInt("settings", "no_reason_level", 80);
        this.banDuration = this.config.getTime("settings", "ban_duration", 604800000);

    }

    /**
     * Perform operations on <tt>Plugin</tt> startup
     * Method usage:
     * 
     * <ul>
     *     <li>Register plugin commands</li>
     *     <li>Register plugin events</li>
     *     <li>Synchronize the plugin with the current game status</li>
     * </ul>
     * 
     * @author Daniele Pantaleone
     **/
    @Override
    public void onStartup() {

        this.debug("Starting plugin...");

        // Getting the command section from configuration file
        Map<String, String> commands = this.config.getMap("commands");

        // Iterating through all the commands so we can register them
        for (Map.Entry<String, String> entry : commands.entrySet()) {

            String alias = null;
            String name = entry.getKey();
            String group = entry.getValue();

            if (name.contains("-")) {
                String handler[] = name.split("-", 2);
                name = handler[0];
                alias = handler[1];
            }

            // Registering the command
            this.registerCommand(name, alias, group);

        }

        try {

            // Check if we need to enable !iamgod
            List<Client> collection = this.clients.getByGroupFull("superadmin");

            if (collection.size() == 0) {
                // Enabling the !iamgod command
                this.log.trace("Enabling !iamgod command. There are is no Super Admin on this server yet");
                this.registerCommand("iamgod", null, "guest");
            }

        } catch (ClassNotFoundException | UnknownHostException | SQLException e) {

            // Logging the Exception
            this.error("Unable to lookup for Super Admin. !iamgod command is not going to be enabled", e);

        }

        // Listen for produced events
        this.eventBus.register(this);

    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////// EVENTS SECTION //////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * Performs operation on <tt>EVT_CLIENT_CONNECT</tt>
     * 
     * @author Daniele Pantaleone
     * @param  event The generated <tt>Event</tt>
     **/
    @Subscribe
    public void onClientConnect(ClientConnectEvent event) {

        // Copying the client reference
        Client client = event.getClient();

        ///////////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////// PENALIES //////////////////////////////////////
        ///////////////////////////////////////////////////////////////////////////////////

        try {

            // Checking if the connecting Client has an active ban
            List<Penalty> penalties = this.penalties.getByClient(client, PenaltyType.BAN);

            if (penalties.size() != 0) {

                Penalty penalty = penalties.get(0);

                // Found at least one active BAN for the connecting client
                // We'll kick him again for the same reason he was banned
                this.debug("Connecting client " + client.getName() + "[@" + client.getId() + "] has an active BAN "
                        + penalty.toString());
                this.debug("Kicking client " + client.getName() + "[@" + client.getId()
                        + "] and stopping to process " + event.getType().name());
                this.console.kick(client, penalty.getReason());

            }

        } catch (ClassNotFoundException | SQLException | UnknownHostException | IndexOutOfBoundsException e) {

            // Logging the Exception. Using a more verbose log message so we can identify
            // in a better way the function where such Exception as been raised and catched
            this.error("Exception generated on " + event.getType().name(), e);

        }

        ///////////////////////////////////////////////////////////////////////////////////
        //////////////////////////////////// ALIASES //////////////////////////////////////
        ///////////////////////////////////////////////////////////////////////////////////

        try {

            // Checking if we already got an alias matching the current
            // name for the given client. If not we are going to add
            // a new entry in our database to keep tracking it
            Alias alias = this.aliases.getByClientName(client);

            if (alias != null) {
                // Upgrade the alias usage
                alias.setNumUsed(alias.getNumUsed() + 1);
            } else {
                // Create a new alias for this client
                alias = new Alias(client, client.getName());
            }

            // Saving the Alias object in the storage
            this.aliases.save(alias);

        } catch (ClassNotFoundException | SQLException e) {

            // Logging the Exception. Using a more verbose log message so we can identify
            // in a better way the function where such Exception as been raised and catched
            this.error("Exception generated on " + event.getType().name(), e);

        }

        ///////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////// IP ALIASES /////////////////////////////////////
        ///////////////////////////////////////////////////////////////////////////////////

        try {

            // Checking if the connecting client already used
            // the current IP in the past. If not we are going to
            // add a new entry in our database to keep tracking it
            IpAlias ipalias = this.ipaliases.getByClientIp(client);

            if (ipalias != null) {
                // Upgrade the ipalias usage
                ipalias.setNumUsed(ipalias.getNumUsed() + 1);
            } else {
                // Create a new alias for this client
                ipalias = new IpAlias(client, client.getIp());
            }

            // Saving the IpAlias object in the storage
            this.ipaliases.save(ipalias);

        } catch (ClassNotFoundException | SQLException | UnknownHostException e) {

            // Logging the Exception. Using a more verbose log message so we can identify
            // in a better way the function where such Exception as been raised and catched
            this.error("Exception generated on " + event.getType().name(), e);

        }

    }

    /**
     * Performs operation on <tt>EVT_CLIENT_NAME_CHANGE</tt>
     * 
     * @author Daniele Pantaleone
     * @param  event The generated <tt>Event</tt>
     **/
    @Subscribe
    public void onClientNameChange(ClientNameChangeEvent event) {

        // Copying the client reference
        Client client = event.getClient();

        try {

            // Checking if we already got an alias matching the current
            // name for the given client. If not we are going to add
            // a new entry in our database to keep tracking it
            Alias alias = this.aliases.getByClientName(client);

            if (alias != null) {
                // Upgrade the alias usage
                alias.setNumUsed(alias.getNumUsed() + 1);
            } else {
                // Create a new alias for this client
                alias = new Alias(client, client.getName());
            }

            // Saving data
            this.aliases.save(alias);
            this.clients.save(client);

        } catch (ClassNotFoundException | SQLException e) {

            // Logging the Exception. Using a more verbose log message so we can identify
            // in a better way the function where such Exception as been raised and catched
            this.error("Exception generated on " + event.getType().name(), e);

        }

    }

    /**
     * Performs operation on <tt>EVT_CLIENT_TEAM_CHANGE</tt>
     * 
     * @author Daniele Pantaleone
     * @param  event The generated <tt>Event</tt>
     **/
    @Subscribe
    public void onClientTeamChange(ClientTeamChangeEvent event) {

        // Copying the client reference
        Client client = event.getClient();

        // Checking if the player is locked
        if (client.isVar("locked_team")) {

            Team team = (Team) client.getVar("locked_team");

            // If this client is locked to a team
            // don't let him move into another one
            if ((team != client.getTeam())) {
                this.console.forceteam(client, team);
                this.console.tell(client, "You are locked to: " + Color.getByTeam(team) + team.name());
            }

        }

    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////// COMMANDS SECTION /////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * Display the list of online admins
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     **/
    @Usage(message = "Display the list of online admins", syntax = "!admins")
    public void CmdAdmins(Command command) throws CommandSyntaxException {

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 0)
            throw new CommandSyntaxException("Invalid syntax");

        // Getting the list of online admins
        List<Client> admins = this.clients.getByMinGroup(this.adminLevel);

        if (admins.size() == 0) {
            // Informing the player that there are no admins online
            this.console.sayLoudOrPm(command, this.messages.get("no_admins"));
            return;
        }

        // Sorting the collection
        Comparator<Client> comparator = new ClientLevelComparator();
        Collections.sort(admins, comparator);

        // Building the message to be displayed
        List<String> collection = new LinkedList<String>();

        for (Client admin : admins) {
            // Appending all the admin details to our collection
            // using the following format: nickname [level]
            collection.add(admin.getName() + " [" + Color.GREEN + admin.getGroup().getLevel() + Color.WHITE + "]");
        }

        // Printing the admin list in the game chat
        this.console.sayLoudOrPm(command,
                MessageFormat.format(this.messages.get("admin_list"), Joiner.on(", ").join(collection)));

    }

    /**
     * Ban a player from the server
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     * @throws ClassNotFoundException If it's not possible to load the storage interface library
     * @throws SQLException If the connection with the database has been lost and cannot
     *                      be established anymore, or if the SQL query fails somehow
     **/
    @Usage(message = "Ban a player from the server", syntax = "!ban <client> [<reason>]")
    public void CmdBan(Command command)
            throws CommandSyntaxException, CommandRuntimeException, ClassNotFoundException, SQLException {

        String reason = null;
        Client client = command.client;

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() < 1)
            throw new CommandSyntaxException("Invalid syntax");

        // Retrieving the correct client
        Client sclient = this.clients.getByMagic(command.client, command.getParamString(0));
        if (sclient == null)
            return;

        if (client == sclient) {
            // The user is trying to ban himself
            throw new CommandRuntimeException(this.messages.get("ban_self"));
        }

        if ((client.getGroup().getLevel() <= sclient.getGroup().getLevel()) && (client != sclient)) {
            // The user is trying to kick an higher/equal level client
            throw new CommandRuntimeException(
                    MessageFormat.format(this.messages.get("ban_denied"), sclient.getName()));
        }

        // Getting the ban reason if any
        reason = command.getParamStringConcat(1);

        if (reason == null) {

            if (client.getGroup().getLevel() < this.noReasonLevel) {
                // Informing the player that he MUST supply a reason for the ban
                throw new CommandSyntaxException("You must supply a reason");
            }

        } else {

            // Checking if a predefined keyword has been used as kick reason
            if (this.keywords.containsKey(reason.toLowerCase())) {
                // Replacing the reason string with the associated keyword value
                reason = this.keywords.get(reason.toLowerCase());
            }

        }

        ////////////////////////
        // PERFORMING THE BAN //
        ////////////////////////

        // Check if the Auth System is enabled and correctly configured
        // This will automatically exclude Urban Terror 4.1 console since the 
        // auth boolean flag will remain false upon console initialization
        if ((this.game.isAuthEnabled()) && (this.game.getAuthOwners() != null)) {

            // Check if the client is authed
            if (sclient.getAuth() != null) {

                int days = 0, hours = 0;
                int minutes = (int) (this.banDuration / 60000);
                while (minutes >= 60) {
                    hours += 1;
                    minutes -= 60;
                }
                while (hours >= 24) {
                    days += 1;
                    hours -= 24;
                }

                // Sending the BAN through the auth server. We don't need
                // to kick the player from the server. The Auth System
                // will drop the player automatically after receiving the ban
                this.console.authban(sclient, days, hours, minutes);

            } else {

                // Client is not authed
                this.console.kick(sclient, reason);

            }

        } else {

            // Auth System not available
            this.console.kick(sclient, reason);
        }

        /////////////////////////
        // BAN IN-GAME DISPLAY //
        /////////////////////////        

        if (reason == null) {

            this.console.say(MessageFormat.format(this.messages.get("tempban"), sclient.getName(), client.getName(),
                    TimeParser.getHumanReadableTime(this.banDuration)));
        } else {

            this.console.say(MessageFormat.format(this.messages.get("tempban_reason"), sclient.getName(),
                    client.getName(), TimeParser.getHumanReadableTime(this.banDuration), reason));
        }

        /////////////////////////
        // STORING THE PENALTY //
        /////////////////////////

        this.penalties.insert(new Penalty.Builder(sclient, PenaltyType.BAN).admin(client).reason(reason)
                .timeExpire(new DateTime(this.orion.timezone).plus(this.banDuration)).build());

    }

    /**
     * Cycle the server current map
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     **/
    @Usage(message = "Cycle the server current map", syntax = "!cyclemap")
    public void CmdCyclemap(Command command) throws CommandSyntaxException, CommandRuntimeException {

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 0)
            throw new CommandSyntaxException("Invalid syntax");

        // Getting the nextmap name
        String nextmap = this.console.getNextMap();

        if (nextmap == null) {

            // We were not able to get the nextmap name
            // for a proper in-game chat display. Cycle anyway...
            this.console.sayLoudOrPm(command, "Cycling current map...");

            try {
                Thread.sleep(1200);
            } catch (InterruptedException e) {
                // Don't do anything since this sleep is not really necessary
                // It's just so players knows which map we are going to play
            }

            // Cycle current map
            this.console.cyclemap();

        }

        // Printing the nextmap name in the game chat so players will notice the cycle
        this.console.sayLoudOrPm(command, MessageFormat.format(this.messages.get("map_cycle"), nextmap));

        try {
            Thread.sleep(1200);
        } catch (InterruptedException e) {
        }

        // Cycle current map
        this.console.cyclemap();

    }

    /**
     * Disable a plugin
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     **/
    @Usage(message = "Disable a plugin", syntax = "!disable <plugin>")
    public void CmdDisable(Command command) throws CommandSyntaxException, CommandRuntimeException {

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 1)
            throw new CommandSyntaxException("Invalid syntax");

        // Retrieving the plugin to be disabled
        String name = command.getParamString(0).toLowerCase();
        Plugin plugin = this.orion.plugins.get(name);

        if (name.equals("admin")) {
            // The user is trying to disable the Admin plugin. This one can't be disabled
            // If we allow so, we do not have the possibility to enable it again
            throw new CommandRuntimeException("Can't disable plugin: " + Color.RED + name);
        }

        if (plugin == null) {
            // The specified plugin doesn't exists or is not loaded
            throw new CommandRuntimeException("Unable to find plugin: " + Color.RED + name);
        }

        if (!plugin.isEnabled()) {
            // The specified plugin is already disabled
            throw new CommandRuntimeException("Plugin already disabled: " + Color.YELLOW + name);
        }

        // Disabling the plugin
        plugin.setEnabled(false);
        this.console.sayLoudOrPm(command, "Plugin disabled: " + Color.RED + name);

    }

    /**
     * Enable a plugin
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     **/
    @Usage(message = "Enable a plugin", syntax = "!enable <plugin>")
    public void CmdEnable(Command command) throws CommandSyntaxException, CommandRuntimeException {

        // Copying client reference
        Client client = command.client;

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 1)
            throw new CommandSyntaxException("Invalid syntax");

        // Retrieving the plugin to be disabled
        String name = command.getParamString(0).toLowerCase();
        Plugin plugin = this.orion.plugins.get(name);

        if (plugin == null) {
            // The specified plugin doesn't exists or is not loaded
            throw new CommandRuntimeException("Unable to find plugin: " + Color.RED + name);
        }

        if (plugin.isEnabled()) {
            // The specified plugin is already disabled
            throw new CommandRuntimeException("Plugin already enabled: " + Color.YELLOW + name);
        }

        // Enabling the plugin
        plugin.setEnabled(true);
        this.console.tell(client, "Plugin enabled: " + Color.GREEN + name);

    }

    /**
     * Display the slot of a player
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     **/
    @Usage(message = "Display the slot of a player", syntax = "!find <client>")
    public void CmdFind(Command command) throws CommandSyntaxException {

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 1)
            throw new CommandSyntaxException("Invalid syntax");

        // Retrieving the correct client
        String search = command.getParamString(0);
        Client sclient = this.clients.getByMagic(command.client, search);
        if (sclient == null)
            return;

        // Printing the slot informations in the game chat
        this.console.sayLoudOrPm(command, "Found player matching " + Color.YELLOW + search + Color.WHITE + ": "
                + sclient.getName() + " [" + Color.GREEN + sclient.getSlot() + Color.WHITE + "]");

    }

    /**
     * Force a player in the specified team
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     **/
    @Usage(message = "Force a player in the specified team", syntax = "!force <client> <team>")
    public void CmdForce(Command command) throws CommandSyntaxException, CommandRuntimeException {

        Team team = null;
        Client sclient = null;
        Client client = command.client;

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 2)
            throw new CommandSyntaxException("Invalid syntax");

        // Retrieving the correct client
        sclient = this.clients.getByMagic(command.client, command.getParamString(0));
        if (sclient == null)
            return;

        try {

            // Retrieving the correct team
            team = this.parser.getTeamByName(command.getParamString(1));

        } catch (IndexOutOfBoundsException e) {

            // Invalid team name specified. Display a short list
            List<Team> collection = this.parser.getAvailableTeams();
            throw new CommandRuntimeException("Invalid team specified. Try using: " + Color.YELLOW
                    + Joiner.on(Color.WHITE + "|" + Color.YELLOW).join(collection));

        }

        // Check if the specified Team is available
        List<Team> collection = this.parser.getAvailableTeams();

        if (!collection.contains(team)) {
            // The specified Team is not available in the current played gametype
            // Showing a list of available teams so the user can try again
            throw new CommandRuntimeException("Invalid team specified. Try using: " + Color.YELLOW
                    + Joiner.on(Color.WHITE + "|" + Color.YELLOW).join(collection));
        }

        // Checking if the player is locked
        if (sclient.isVar("locked_team")) {

            Team lockTeam = (Team) sclient.getVar("locked_team");
            if (lockTeam != team) {

                // Inform that the player cannot be forced since he's locked to another team
                this.console.tell(client,
                        String.format("%s currently locked to: %s%s",
                                ((client != sclient) ? sclient.getName() + " is" : "You are"),
                                Color.getByTeam(lockTeam), lockTeam.name()));
                return;

            }

        }

        // Forcing the player
        this.console.forceteam(sclient, team);

        // Informing the player that he has been moved by an admin into another team
        this.console.tell(sclient, "You have been forced to: " + Color.getByTeam(team) + team.name());
        if (sclient != client)
            this.console.tell(client,
                    sclient.getName() + " has been forced to: " + Color.getByTeam(team) + team.name());

    }

    /**
     * Display a CVAR value
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     **/
    @Usage(message = "Display a CVAR value", syntax = "!get <cvar>")
    public void CmdGet(Command command) throws CommandSyntaxException, CommandRuntimeException {

        // Copying client reference
        Client client = command.client;

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 1)
            throw new CommandSyntaxException("Invalid syntax");

        // Getting the CVAR value
        String cvar = command.getParamString(0);
        String value = this.console.getCvar(cvar);

        if (value == null) {
            // Not able to retrieve the CVAR value
            throw new CommandRuntimeException("Unable to retrieve " + Color.RED + cvar);
        }

        // Printing the CVAR value in the game chat
        this.console.tell(client, cvar + " is set to: " + Color.YELLOW + value);

    }

    /**
     * Display the list of available commands
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     **/
    @Usage(message = "Display the list of available commands", syntax = "!help [<command>]")
    public void CmdHelp(Command command) throws CommandSyntaxException, CommandRuntimeException {

        // Copying client reference
        Client client = command.client;

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() > 1)
            throw new CommandSyntaxException("Invalid syntax");

        // Displaying help text for the given command
        if (command.getParamNum() != 0) {

            String name = command.getParamString(0).toLowerCase();
            if (!this.regcommands.containsKey(name)) {
                // Informing the client that the command is not mapped
                throw new CommandRuntimeException(
                        MessageFormat.format(this.messages.get("help_not_found"), command.prefix, command.handle));
            }

            // Getting the RegisteredCommand object
            RegisteredCommand regcommand = this.regcommands.get(name);

            // Checking correct client minLevel
            if (client.getGroup().getLevel() < regcommand.minGroup.getLevel()) {
                // Informing the client that he has not sufficient access for this command
                throw new CommandRuntimeException(MessageFormat.format(this.messages.get("help_no_access"),
                        command.prefix.name, command.handle));
            }

            // Displaying the help text
            Usage usage = regcommand.method.getAnnotation(Usage.class);
            this.console.tell(command.client, usage.message());
            this.console.tell(command.client, "Usage: " + Color.YELLOW + usage.syntax());

        } else {

            // Displaying a list of all the available commands
            List<String> collection = new LinkedList<String>();
            Map<String, RegisteredCommand> commands = this.regcommands.getMap1();

            for (Map.Entry<String, RegisteredCommand> entry : commands.entrySet()) {

                // No access for this command. Skip it!
                if (client.getGroup().getLevel() < entry.getValue().minGroup.getLevel())
                    continue;

                // Adding the command to the collection
                collection.add(entry.getKey());

            }

            Collections.sort(collection);

            // Printing the command list in the game chat
            this.console.sayLoudOrPm(command, MessageFormat.format(this.messages.get("help_list"),
                    Color.YELLOW + Joiner.on(Color.WHITE + ", " + Color.YELLOW).join(collection)));

        }

    }

    /**
     * Register yourself as Super Admin
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     * @throws UnknownHostException If we can't generate an <tt>InetAddress</tt> object using the <tt>Client</tt> IP address
     * @throws ClassNotFoundException If it's not possible to load the storage interface library
     * @throws SQLException If the connection with the database has been lost and cannot
     *                      be established anymore, or if the SQL query fails somehow
     **/
    @Usage(message = "Register yourself as Super Admin", syntax = "!iamgod")
    public void CmdIamgod(Command command) throws CommandSyntaxException, CommandRuntimeException,
            ClassNotFoundException, UnknownHostException, SQLException {

        // Copying client reference
        Client client = command.client;

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 0)
            throw new CommandSyntaxException("Invalid syntax");

        // Double check the Super Admin list
        List<Client> collection = this.clients.getByGroupFull("superadmin");

        if (collection.size() > 0) {
            // Found at least one Super Admin -> this command should not be enabled
            this.log.warn("Command !iamgod found enabled but there are already " + collection.size()
                    + " Super Admin registered");
            this.log.debug("Unregistering !iamgod command");
            this.regcommands.remove("iamgod");
            return;
        }

        // Changing the Client group level and removing the command
        client.setGroup(this.groups.getByKeyword("superadmin"));
        this.trace(client.getName() + " [@" + client.getId() + "] has been registered as "
                + client.getGroup().getName() + " [" + client.getGroup().getLevel() + "]");
        this.debug("Unregistering !iamgod command");
        this.regcommands.remove("iamgod");

        // Printing the command result in the game chat
        this.console.tell(client, "You are now a " + Color.GREEN + client.getGroup().getName());

        // Saving the changes
        this.clients.save(client);

    }

    /**
     * Kick a player from the server
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     * @throws ClassNotFoundException If it's not possible to load the storage interface library
     * @throws SQLException If the connection with the database has been lost and cannot
     *                      be established anymore, or if the SQL query fails somehow
     **/
    @Usage(message = "Kick a player from the server", syntax = "!kick <client> [<reason>]")
    public void CmdKick(Command command)
            throws CommandSyntaxException, CommandRuntimeException, ClassNotFoundException, SQLException {

        String reason = null;
        Client client = command.client;

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() < 1)
            throw new CommandSyntaxException("Invalid syntax");

        // Retrieving the correct client
        Client sclient = this.clients.getByMagic(command.client, command.getParamString(0));
        if (sclient == null)
            return;

        if (client == sclient) {
            // The user is trying to kick himself
            throw new CommandRuntimeException(this.messages.get("kick_self"));
        }

        if ((client.getGroup().getLevel() <= sclient.getGroup().getLevel()) && (client != sclient)) {
            // The user is trying to kick an higher/equal level client
            throw new CommandRuntimeException(
                    MessageFormat.format(this.messages.get("kick_denied"), sclient.getName()));
        }

        // Getting the kick reason if any
        reason = command.getParamStringConcat(1);

        if (reason == null) {

            if (client.getGroup().getLevel() < this.noReasonLevel) {
                // Informing the player that he MUST supply a reason for the kick
                throw new CommandSyntaxException("You must supply a reason");
            }

            // Kicking the player
            this.console.kick(sclient);
            this.console.say(MessageFormat.format(this.messages.get("kick"), sclient.getName(), client.getName()));

        } else {

            // Checking if a predefined keyword has been used as kick reason
            if (this.keywords.containsKey(reason.toLowerCase())) {
                // Replacing the reason string with the associated keyword value
                reason = this.keywords.get(reason.toLowerCase());
            }

            // Kicking the client
            this.console.kick(sclient, reason);
            this.console.say(MessageFormat.format(this.messages.get("kick_reason"), sclient.getName(),
                    client.getName(), reason));

        }

        // Storing the penalty in the storage
        this.penalties.insert(new Penalty.Builder(sclient, PenaltyType.KICK).admin(client).reason(reason).build());

    }

    /**
     * Kill a player
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     **/
    @Usage(message = "Kill a player", syntax = "!kill [<client>]")
    @Dependency(console = UrT42Console.class)
    public void CmdKill(Command command) throws CommandSyntaxException, CommandRuntimeException {

        // Copying client reference
        Client client = command.client;
        Client sclient = null;

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() > 1)
            throw new CommandSyntaxException("Invalid syntax");

        // Retrieving the correct client
        if (command.getParamNum() != 1)
            sclient = client;
        else
            sclient = this.clients.getByMagic(command.client, command.getParamString(0));

        if ((client.getGroup().getLevel() <= sclient.getGroup().getLevel()) && (client != sclient)) {
            // The user is trying to kill an higher/equal level client
            throw new CommandRuntimeException(
                    MessageFormat.format(this.messages.get("kill_denied"), sclient.getName()));
        }

        // Performing the kill
        this.console.kill(sclient);

    }

    /**
     * Display the level of a player
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     * @throws UnknownHostException If we can't generate an <tt>InetAddress</tt> object using the <tt>Client</tt> IP address
     * @throws ClassNotFoundException If it's not possible to load the storage interface library
     * @throws SQLException If the connection with the database has been lost and cannot
     *                      be established anymore, or if the SQL query fails somehow
     **/
    @Usage(message = "Display the level of a player", syntax = "!leveltest [<client>]")
    public void CmdLeveltest(Command command) throws CommandSyntaxException, CommandRuntimeException,
            UnknownHostException, ClassNotFoundException, SQLException {

        // Copying client reference
        Client client = command.client;
        Client sclient = null;

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() > 1)
            throw new CommandSyntaxException("Invalid syntax");

        // Retrieving the correct client
        if (command.getParamNum() != 1)
            sclient = client;
        else
            sclient = this.clients.getByMagicFull(command.client, command.getParamString(0));

        // Exit if we don't have a proper client
        // on which to perform the leveltest
        if (sclient == null)
            return;

        this.console.sayLoudOrPm(command, MessageFormat.format(this.messages.get("level_test"), sclient.getName(),
                sclient.getId(), sclient.getGroup().getName(), sclient.getGroup().getLevel()));

    }

    /**
     * Display the list of online clients
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     **/
    @Usage(message = "Display the list of online client", syntax = "!list")
    public void CmdList(Command command) throws CommandSyntaxException {

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 0)
            throw new CommandSyntaxException("Invalid syntax");

        // Getting the list of online clients
        List<Client> clientlist = this.clients.getList();
        Comparator<Client> comparator = new ClientSlotComparator();
        Collections.sort(clientlist, comparator);

        // Building the message to be displayed
        List<String> collection = new LinkedList<String>();

        for (Client sclient : clientlist) {
            // Appending all the client details to our collection
            // using the following format: [slot] nickname
            collection.add("[" + Color.YELLOW + sclient.getSlot() + Color.WHITE + "] " + sclient.getName());
        }

        // Printing the client list in the game chat
        this.console.sayLoudOrPm(command,
                MessageFormat.format(this.messages.get("client_list"), Joiner.on(", ").join(collection)));

    }

    /**
     * Lock a player into the specified team
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     **/
    @Usage(message = "Lock a player into the specified team", syntax = "!lock <client> <team>")
    public void CmdLock(Command command) throws CommandSyntaxException, CommandRuntimeException {

        Team team = null;
        Client sclient = null;
        Client client = command.client;

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 2)
            throw new CommandSyntaxException("Invalid syntax");

        // Retrieving the correct client
        sclient = this.clients.getByMagic(command.client, command.getParamString(0));
        if (sclient == null)
            return;

        if ((client.getGroup().getLevel() <= sclient.getGroup().getLevel()) && (client != sclient)) {
            // The user is trying to lock an higher/equal level client
            throw new CommandRuntimeException(
                    MessageFormat.format(this.messages.get("lock_denied"), sclient.getName()));
        }

        try {

            // Retrieving the correct team
            team = this.parser.getTeamByName(command.getParamString(1));

        } catch (IndexOutOfBoundsException e) {

            // Invalid team name specified. Display a short list
            List<Team> collection = this.parser.getAvailableTeams();
            throw new CommandRuntimeException("Invalid team specified. Try using: " + Color.YELLOW
                    + Joiner.on(Color.WHITE + "|" + Color.YELLOW).join(collection));

        }

        // Check if the specified Team is available
        List<Team> collection = this.parser.getAvailableTeams();

        if (!collection.contains(team)) {
            // The specified Team is not available in the current played gametype
            // Showing a list of available teams so the user can try again
            throw new CommandRuntimeException("Invalid team specified. Try using: " + Color.YELLOW
                    + Joiner.on(Color.WHITE + "|" + Color.YELLOW).join(collection));
        }

        // Storing the locked team in the client object
        // This will overwrite a previously set value
        sclient.setVar("locked_team", team);

        // Forcing the player
        this.console.forceteam(sclient, team);

        // Informing the player that he has been moved by an admin into another team
        this.console.tell(sclient, "You have been locked to: " + Color.getByTeam(team) + team.name());
        if (sclient != client)
            this.console.tell(client,
                    sclient.getName() + " has been locked to: " + Color.getByTeam(team) + team.name());

    }

    /**
     * Lookup a client in the storage
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws UnknownHostException If we can't generate an <tt>InetAddress</tt> object using the <tt>Client</tt> IP address
     * @throws ClassNotFoundException If it's not possible to load the storage interface library
     * @throws SQLException If the connection with the database has been lost and cannot
     *                      be established anymore, or if the SQL query fails somehow
     **/
    @Usage(message = "Lookup a client in the storage", syntax = "!lookup <client>")
    public void CmdLookup(Command command)
            throws CommandSyntaxException, UnknownHostException, ClassNotFoundException, SQLException {

        // Copying client reference
        Client client = command.client;

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 1)
            throw new CommandSyntaxException("Invalid syntax");

        // Retrieving the correct client
        Client sclient = this.clients.getByMagicFull(command.client, command.getParamString(0));
        if (sclient == null)
            return;

        // Printing lookup info in the game chat
        this.console.sayLoudOrPm(command, client.getName() + " [" + Color.YELLOW + "@" + client.getId()
                + Color.WHITE + "] - Level: " + Color.YELLOW + client.getGroup().getName());
        this.console.sayLoudOrPm(command, "Joined on: " + Color.YELLOW
                + client.getTimeAdd().toString(this.orion.timeformat, this.orion.locale));
        this.console.sayLoudOrPm(command, "Last seen: " + Color.YELLOW
                + client.getTimeEdit().toString(this.orion.timeformat, this.orion.locale));
        this.console.sayLoudOrPm(command, "Last known host: " + Color.YELLOW + client.getIp().getHostAddress());

    }

    /**
     * Change the server current map
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     **/
    @Usage(message = "Change the server current map", syntax = "!map <mapname>")
    public void CmdMap(Command command) throws CommandSyntaxException, CommandRuntimeException {

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 1)
            throw new CommandSyntaxException("Invalid syntax");

        // Get all the maps matching the given pattern
        String pattern = command.getParamString(0);
        List<String> collection = this.console.getMapSoundingLike(pattern);

        if (collection == null) {
            // Inform the player that something went wrong
            throw new CommandRuntimeException("Unable to retrieve map list");
        } else if (collection.isEmpty()) {
            // No map found matching the given pattern
            throw new CommandRuntimeException("No map found matching: " + Color.RED + pattern);
        } else if (collection.size() > 1) {
            // Too many maps found matching the given pattern
            throw new CommandRuntimeException("Do you mean: " + Color.YELLOW
                    + Joiner.on(Color.WHITE + ", " + Color.YELLOW).join(collection) + Color.WHITE + "?");
        }

        // Found just our map
        String mapname = collection.get(0);

        // Printing the map name in the game chat so players will notice the change
        this.console.sayLoudOrPm(command, MessageFormat.format(this.messages.get("map_change"), mapname));

        try {
            Thread.sleep(1200);
        } catch (InterruptedException e) {
            // Don't do anything since this sleep is not really necessary
            // It's just so players knows which map we are going to play
        }

        // Changing the map
        this.console.map(mapname);

    }

    /**
     * List the available maps
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     **/
    @Usage(message = "List the available maps", syntax = "!maplist")
    public void CmdMaps(Command command) throws CommandSyntaxException, CommandRuntimeException {

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 0)
            throw new CommandSyntaxException("Invalid syntax");

        // Getting the map list
        if ((this.game.getMapList() == null) || (this.game.getMapList().size() == 0))
            this.game.setMapList(this.console.getMapList());

        if ((this.game.getMapList() == null) || (this.game.getMapList().size() == 0)) {
            // Inform the player that something went wrong
            throw new CommandRuntimeException("Unable to retrieve map list");
        }

        // Printing the map list in the game chat
        this.console.sayLoudOrPm(command, MessageFormat.format(this.messages.get("map_list"),
                Color.YELLOW + Joiner.on(Color.WHITE + ", " + Color.YELLOW).join(this.game.getMapList())));

    }

    /**
     * Display the nextmap name
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     **/
    @Usage(message = "Display the nextmap name", syntax = "!nextmap")
    public void CmdNextmap(Command command) throws CommandSyntaxException, CommandRuntimeException {

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 0)
            throw new CommandSyntaxException("Invalid syntax");

        // Getting the nextmap name
        String nextmap = this.console.getNextMap();

        if (nextmap == null) {
            // Inform the player that something went wrong
            throw new CommandRuntimeException("Unable to retrieve nextmap");
        }

        // Printing nextmap name in the game chat
        this.console.sayLoudOrPm(command, MessageFormat.format(this.messages.get("nextmap"), nextmap));

    }

    /**
     * Nuke a player
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     **/
    @Usage(message = "Nuke a player", syntax = "!nuke <client>")
    public void CmdNuke(Command command) throws CommandSyntaxException {

        // Copying client reference
        Client client = command.client;

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 1)
            throw new CommandSyntaxException("Invalid syntax");

        // Retrieving the correct client
        Client sclient = this.clients.getByMagic(command.client, command.getParamString(0));
        if (sclient == null)
            return;

        if ((client.getGroup().getLevel() <= sclient.getGroup().getLevel()) && (client != sclient)) {
            // The user is trying to nuke an higher/equal level client
            throw new CommandRuntimeException(
                    MessageFormat.format(this.messages.get("nuke_denied"), sclient.getName()));
        }

        // Performing the nuke
        this.console.nuke(sclient);

    }

    /**
     * Display bot info
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     **/
    @Usage(message = "Display bot info", syntax = "!orion")
    public void CmdOrion(Command command) throws CommandSyntaxException {

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 0)
            throw new CommandSyntaxException("Invalid syntax");

        // Printing bot info in the game chat. Format: BOTNAME vX.x [CODENAME] - WEBSITE
        this.console.sayLoudOrPm(command, Orion.BOTNAME + " " + Color.YELLOW + Orion.VERSION + Color.WHITE + " ["
                + Color.YELLOW + Orion.CODENAME + Color.WHITE + "] - " + Color.BLACK + Orion.WEBSITE);

    }

    /**
     * Ban a player from the server permanently
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     * @throws ClassNotFoundException If it's not possible to load the storage interface library
     * @throws SQLException If the connection with the database has been lost and cannot
     *                      be established anymore, or if the SQL query fails somehow
     **/
    @Usage(message = "Ban a player from the server permanently", syntax = "!ban <client> [<reason>]")
    public void CmdPermban(Command command)
            throws CommandSyntaxException, CommandRuntimeException, ClassNotFoundException, SQLException {

        String reason = null;
        Client client = command.client;

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() < 1)
            throw new CommandSyntaxException("Invalid syntax");

        // Retrieving the correct client
        Client sclient = this.clients.getByMagic(command.client, command.getParamString(0));
        if (sclient == null)
            return;

        if (client == sclient) {
            // The user is trying to ban himself
            throw new CommandRuntimeException(this.messages.get("ban_self"));
        }

        if ((client.getGroup().getLevel() <= sclient.getGroup().getLevel()) && (client != sclient)) {
            // The user is trying to kick an higher/equal level client
            throw new CommandRuntimeException(
                    MessageFormat.format(this.messages.get("ban_denied"), sclient.getName()));
        }

        // Getting the kick reason if any
        reason = command.getParamStringConcat(1);

        if (reason == null) {

            if (client.getGroup().getLevel() < this.noReasonLevel) {
                // Informing the player that he MUST supply a reason for the ban
                throw new CommandSyntaxException("You must supply a reason");
            }

        } else {

            // Checking if a predefined keyword has been used as kick reason
            if (this.keywords.containsKey(reason.toLowerCase())) {
                // Replacing the reason string with the associated keyword value
                reason = this.keywords.get(reason.toLowerCase());
            }

        }

        ////////////////////////
        // PERFORMING THE BAN //
        ////////////////////////

        // Add the IP to banlist
        this.console.ban(sclient);

        // Check if the Auth System is enabled and correctly configured
        // This will automatically exclude Urban Terror 4.1 console since the 
        // auth boolean flag will remain false upon console initialization
        if ((this.game.isAuthEnabled()) && (this.game.getAuthOwners() != null)) {

            // Check if the client is authed
            if (sclient.getAuth() != null) {

                // Sending the BAN through the auth server. We don't need
                // to kick the player from the server. The Auth System
                // will drop the player automatically after receiving the ban
                this.console.authban(sclient, 0, 0, 0);

            } else {

                // Client is not authed
                this.console.kick(sclient, reason);

            }

        } else {

            // Auth System not available
            this.console.kick(sclient, reason);
        }

        /////////////////////////
        // BAN IN-GAME DISPLAY //
        /////////////////////////        

        if (reason == null) {

            this.console
                    .say(MessageFormat.format(this.messages.get("permban"), sclient.getName(), client.getName()));

        } else {

            this.console.say(MessageFormat.format(this.messages.get("permban_reason"), sclient.getName(),
                    client.getName(), reason));
        }

        /////////////////////////
        // STORING THE PENALTY //
        /////////////////////////

        this.penalties.insert(new Penalty.Builder(sclient, PenaltyType.BAN).admin(client).reason(reason).build());

    }

    /**
     * Display the list of loaded plugins
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     **/
    @Usage(message = "Display the list of loaded plugins", syntax = "!plugins")
    public void CmdPlugins(Command command) throws CommandSyntaxException, CommandRuntimeException {

        // Copying client reference
        Client client = command.client;

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 0)
            throw new CommandSyntaxException("Invalid syntax");

        List<String> collection = new LinkedList<String>();

        for (Map.Entry<String, Plugin> entry : this.plugins.entrySet()) {
            // Adding to in the collection the list of all the loaded plugins
            // We'll denote with GREEN an enabled plugin, and with RED a disabled one
            collection.add((entry.getValue().isEnabled() ? Color.GREEN : Color.RED) + entry.getKey());
        }

        // Printing the plugin list in the game chat
        this.console.tell(client, MessageFormat.format(this.messages.get("plugin_list"),
                Joiner.on(Color.WHITE + ", ").join(collection)));

    }

    /**
     * Change a user group
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     * @throws UnknownHostException If we can't generate an <tt>InetAddress</tt> object using the <tt>Client</tt> IP address
     * @throws ClassNotFoundException If it's not possible to load the storage interface library
     * @throws SQLException If the connection with the database has been lost and cannot
     *                      be established anymore, or if the SQL query fails somehow
     **/
    @Usage(message = "Change a user group", syntax = "!putgroup <client> <group>")
    public void CmdPutgroup(Command command) throws CommandSyntaxException, CommandRuntimeException,
            UnknownHostException, ClassNotFoundException, SQLException {

        // Copying client reference
        Client client = command.client;

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 2)
            throw new CommandRuntimeException("Invalid command syntax");

        // Retrieving the correct client
        Client sclient = this.clients.getByMagicFull(command.client, command.getParamString(0));
        if (sclient == null)
            return;

        // Checking if the target is a higher level client
        if ((client.getGroup().getLevel() <= sclient.getGroup().getLevel()) && (client != sclient)) {
            // The user is trying to change the group of a higher/equal level client
            throw new CommandRuntimeException(
                    MessageFormat.format(this.messages.get("putgroup_denied"), sclient.getName()));
        }

        // Getting the group where to move the client
        Group newgroup = this.groups.getByKeyword(command.getParamString(1));
        Group oldgroup = null;

        if (newgroup == null) {
            // Not able to find a group. Must be a wrong keyword specified by the client
            throw new CommandRuntimeException("Invalid group specified: " + Color.RED + command.getParamString(1));
        }

        if ((newgroup.getLevel() >= client.getGroup().getLevel()) && (client.getGroup().getLevel() < 100)) {
            // The user is trying to change a client level, to a level beyond his reach
            throw new CommandRuntimeException(
                    "Group " + Color.RED + newgroup.getName() + Color.WHITE + " is beyond your reach");
        }

        if (sclient.inGroup(newgroup)) {
            // Target client is already in the specified group
            throw new CommandRuntimeException(MessageFormat.format(this.messages.get("putgroup_already"),
                    sclient.getName(), newgroup.getName()));
        }

        // Changing the group
        oldgroup = sclient.getGroup();
        sclient.setGroup(newgroup);
        this.log.trace(sclient.getName() + "[@" + sclient.getId() + "] group has been changed by "
                + client.getName() + "[@" + client.getId() + "]: " + oldgroup.getName() + " [" + oldgroup.getLevel()
                + "] -> " + sclient.getGroup().getName() + " [" + sclient.getGroup().getLevel() + "]");

        // Informing both players on what happened
        this.console.tell(client,
                sclient.getName() + " has been added in group: " + Color.GREEN + sclient.getGroup().getName());
        this.console.tell(sclient, "You have been added in group: " + Color.GREEN + sclient.getGroup().getName());

        // Saving the changes 
        this.clients.save(sclient);

    }

    /**
     * Register yourself
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     * @throws ClassNotFoundException If it's not possible to load the storage interface library
     * @throws SQLException If the connection with the database has been lost and cannot
     *                      be established anymore, or if the SQL query fails somehow
     **/
    @Usage(message = "Register yourself", syntax = "!register")
    public void CmdRegister(Command command)
            throws CommandSyntaxException, CommandRuntimeException, ClassNotFoundException, SQLException {

        // Copying client reference
        Client client = command.client;

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 0)
            throw new CommandSyntaxException("Invalid syntax");

        // Getting the guest group so we can check if 
        // the client is already registered in the database
        Group guest = this.groups.getByKeyword("guest");

        if (client.getGroup().getLevel() >= guest.getLevel()) {
            // The user is already registered
            throw new CommandRuntimeException("You are already registered");
        }

        // Registering the user
        client.setGroup(this.groups.getByKeyword("user"));
        this.console.tell(client,
                MessageFormat.format(this.messages.get("client_registered"), client.getGroup().getName()));

        // Saving the new client group
        // permanently in the storage layer
        this.clients.save(client);

    }

    /**
     * Run a command as another <tt>Client</tt>
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     **/
    @Usage(message = "Run a command as another client", syntax = "!runas <client> <command>")
    public void CmdRunas(Command command) throws CommandSyntaxException, CommandRuntimeException {

        // Copying client reference
        String runcommand = null;
        Client client = command.client;

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() < 2)
            throw new CommandSyntaxException("Invalid syntax");

        // Retrieving the correct client
        Client sclient = this.clients.getByMagic(command.client, command.getParamString(0));
        if (sclient == null)
            return;

        if (client == sclient) {
            // Running the command as himself....meh!
            throw new CommandRuntimeException(this.messages.get("run_self"));
        }

        if ((client.getGroup().getLevel() < sclient.getGroup().getLevel()) && (client != sclient)) {
            // The user is trying to run a command as an higher level player
            throw new CommandRuntimeException(
                    MessageFormat.format(this.messages.get("runas_denied"), sclient.getName()));
        }

        // Getting the command to be run as the target client
        runcommand = command.getParamStringConcat(1);

        if ((!runcommand.startsWith("!")) && (!runcommand.startsWith("@")) && (!runcommand.startsWith("&"))) {
            // Not a valid command to be run
            throw new CommandRuntimeException("You specified an invalid command syntax");
        }

        // Building the Command object to be stored in the queue
        String data[] = runcommand.substring(1).split(" ", 2);
        Command newcommand = new Command(client, Prefix.getByChar(runcommand.charAt(0)), data[0].toLowerCase(),
                data.length > 1 ? data[1].toLowerCase() : null, true);

        // Checking if the issued command actually exists
        if (!this.regcommands.containsKey(newcommand.handle)) {
            // The command to be issued as the target user is not registered
            // Inform the client so he can eventually try again
            new CommandRuntimeException("Unable to find command: " + Color.YELLOW + command.prefix.name + Color.RED
                    + newcommand.handle);
        }

        // Pushing the command to the command
        this.orion.commandqueue.add(newcommand);

    }

    /**
     * Print a message in the game chat
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     **/
    @Usage(message = "Print a message in the game chat", syntax = "!say <message>")
    public void CmdSay(Command command) throws CommandSyntaxException {

        // Copying client reference
        Client client = command.client;

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() < 1)
            throw new CommandSyntaxException("Invalid syntax");

        // Getting the message to be displayed
        String message = command.getParamStringConcat(0);

        // Printing the message in the game chat
        this.console.say(MessageFormat.format(this.messages.get("say"), client.getName(), message));

    }

    /**
     * Set a CVAR value
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     **/
    @Usage(message = "Set a CVAR value", syntax = "!set <cvar> <value>")
    public void CmdSet(Command command) throws CommandSyntaxException {

        // Copying client reference
        Client client = command.client;

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() < 2)
            throw new CommandSyntaxException("Invalid syntax");

        // Getting command parameters
        String cvar = command.getParamString(0);
        String value = command.getParamStringConcat(1);

        // Setting the CVAR value
        this.console.setCvar(cvar, value);
        this.console.tell(client, cvar + " changed to: " + Color.YELLOW + value);

    }

    /**
     * Change the server next map
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     **/
    @Usage(message = "Change the server current map", syntax = "!setnextmap <mapname>")
    public void CmdSetnextmap(Command command) throws CommandSyntaxException, CommandRuntimeException {

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 1)
            throw new CommandSyntaxException("Invalid syntax");

        // Get all the maps matching the given pattern
        String pattern = command.getParamString(0);
        List<String> collection = this.console.getMapSoundingLike(pattern);

        if (collection == null) {
            // Inform the player that something went wrong
            throw new CommandRuntimeException("Unable to retrieve map list");
        } else if (collection.isEmpty()) {
            // No map found matching the given pattern
            throw new CommandRuntimeException("No map found matching: " + Color.RED + pattern);
        } else if (collection.size() > 1) {
            // Too many maps found matching the given pattern
            throw new CommandRuntimeException("Do you mean: " + Color.YELLOW
                    + Joiner.on(Color.WHITE + ", " + Color.YELLOW).join(collection) + Color.WHITE + "?");
        }

        // Found just our map
        String mapname = collection.get(0);

        // Setting the nextmap CVAR
        this.console.setCvar("g_nextmap", mapname);

        // Printing the map name in the game chat so players will notice the change
        this.console.sayLoudOrPm(command, MessageFormat.format(this.messages.get("nextmap"), mapname));

    }

    /**
     * Slap a player
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     **/
    @Usage(message = "Slap a player", syntax = "!slap <client> [<amount>]")
    public void CmdSlap(Command command) throws CommandSyntaxException, CommandRuntimeException {

        // Copying client reference
        Client client = command.client;

        int num = command.getParamNum();
        if (num < 1)
            throw new CommandSyntaxException("Invalid syntax");
        if (num > 2)
            throw new CommandSyntaxException("Too many parameters specified");

        // Retrieving the correct client
        Client sclient = this.clients.getByMagic(command.client, command.getParamString(0));
        if (sclient == null)
            return;

        if ((client.getGroup().getLevel() <= sclient.getGroup().getLevel()) && (client != sclient)) {
            // The user is trying to slap an higher/equal level client
            throw new CommandRuntimeException(
                    MessageFormat.format(this.messages.get("slap_denied"), sclient.getName()));
        }

        if (num == 2) {

            try {

                int count = command.getParamInt(1);

                // Looping ensuring not to overcome 25 slaps
                // The cycle is slowed down by the RCON delay
                // so there is no need to add a sleep here
                for (int i = 0; i < count && count < 25; i++) {
                    // Sending the command.
                    this.console.slap(sclient);
                }

            } catch (NumberFormatException e) {
                // Wrong user input. Rethrow our custom exception with help info so the client will notice
                throw new CommandSyntaxException(
                        "Invalid " + Color.YELLOW + "<amount>" + Color.WHITE + " specified");
            }

            return;

        }

        // Performing single slap
        this.console.slap(sclient);

    }

    /**
     * Shutdown the bot
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     **/
    @Usage(message = "Shutdown the bot", syntax = "!shutdown")
    public void CmdShutdown(Command command) throws CommandSyntaxException {

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 0)
            throw new CommandSyntaxException("Invalid syntax");

        this.orion.reader.interrupt();
        this.orion.commandproc.interrupt();
        this.console.sayLoudOrPm(command, "Shutting down...");

    }

    /**
     * Display the bot status
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     **/
    @Usage(message = "Display the bot status", syntax = "!status")
    public void CmdStatus(Command command) throws CommandSyntaxException {

        // Copying client reference
        Client client = command.client;

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 0)
            throw new CommandSyntaxException("Invalid syntax");

        // Print the bot status in the game chat
        this.console.tell(client, "Uptime [" + Color.RED + TimeParser.getHumanReadableTime(this.orion.uptime())
                + Color.WHITE + "] - " + "Storage ["
                + (this.orion.storage.isConnection() ? Color.GREEN + "connected" : Color.RED + "disconnected")
                + Color.WHITE + "]");

    }

    /**
     * Display the current datetime
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     **/
    @Usage(message = "Display the current datetime", syntax = "!time")
    public void CmdTime(Command command) throws CommandSyntaxException {

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 0)
            throw new CommandSyntaxException("Invalid syntax");

        // Formatting the datetime string
        DateTime datetime = new DateTime(this.orion.timezone);

        // Printing datetime informations in the game chat
        this.console.sayLoudOrPm(command, MessageFormat.format(this.messages.get("time"),
                datetime.toString(this.orion.timeformat, this.orion.locale)));

    }

    /**
     * Remove a team lock from a player
     * 
     * @author Daniele Pantaleone
     * @param  command The <tt>Command</tt> object
     * @throws CommandSyntaxException Used to signal a non-recoverable (fatal) syntax exception
     * @throws CommandRuntimeException If there is <tt>Runtime</tt> error while processing the command
     **/
    @Usage(message = "Remove a team lock from a player", syntax = "!unlock <client>")
    public void CmdUnlock(Command command) throws CommandSyntaxException, CommandRuntimeException {

        Client sclient = null;
        Client client = command.client;

        // Checking if we got the correct number of parameters for the command execution
        if (command.getParamNum() != 1)
            throw new CommandSyntaxException("Invalid syntax");

        // Retrieving the correct client
        sclient = this.clients.getByMagic(command.client, command.getParamString(0));
        if (sclient == null)
            return;

        if (!sclient.isVar("locked_team")) {
            // There is no team lock on the specified client
            this.console.tell(client, "There is no team lock on " + Color.YELLOW + sclient.getName());
            return;
        }

        // Removing the team lock
        sclient.delVar("locked_team");

        // Informing the player that he has been unlocked
        this.console.tell(sclient, "Your have been unlocked");
        if (sclient != client)
            this.console.tell(client, sclient.getName() + " has been unlocked");

    }

}