com.orion.parser.UrT42Parser.java Source code

Java tutorial

Introduction

Here is the source code for com.orion.parser.UrT42Parser.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.3
 * @copyright   Daniele Pantaleone, 12 February, 2013
 * @package     com.orion.parser
 **/

package com.orion.parser;

import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.joda.time.DateTime;
import org.joda.time.Hours;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import com.orion.console.Console;
import com.orion.control.ClientCtl;
import com.orion.control.GroupCtl;
import com.orion.domain.Callvote;
import com.orion.domain.Client;
import com.orion.event.ClientBombDefusedEvent;
import com.orion.event.ClientBombHolderEvent;
import com.orion.event.ClientBombPlantedEvent;
import com.orion.event.ClientCallvoteEvent;
import com.orion.event.ClientConnectEvent;
import com.orion.event.ClientDamageEvent;
import com.orion.event.ClientDamageSelfEvent;
import com.orion.event.ClientDamageTeamEvent;
import com.orion.event.ClientDisconnectEvent;
import com.orion.event.ClientFlagCapturedEvent;
import com.orion.event.ClientFlagDroppedEvent;
import com.orion.event.ClientFlagReturnedEvent;
import com.orion.event.ClientGearChangeEvent;
import com.orion.event.ClientItemPickupEvent;
import com.orion.event.ClientJoinEvent;
import com.orion.event.ClientJumpRunCanceledEvent;
import com.orion.event.ClientJumpRunStartedEvent;
import com.orion.event.ClientJumpRunStoppedEvent;
import com.orion.event.ClientKillEvent;
import com.orion.event.ClientKillSelfEvent;
import com.orion.event.ClientKillTeamEvent;
import com.orion.event.ClientNameChangeEvent;
import com.orion.event.ClientPositionLoadEvent;
import com.orion.event.ClientPositionSaveEvent;
import com.orion.event.ClientRadioEvent;
import com.orion.event.ClientSayEvent;
import com.orion.event.ClientSayPrivateEvent;
import com.orion.event.ClientSayTeamEvent;
import com.orion.event.ClientTeamChangeEvent;
import com.orion.event.ClientVoteEvent;
import com.orion.event.Event;
import com.orion.event.GameExitEvent;
import com.orion.event.GameRoundStartEvent;
import com.orion.event.GameStartEvent;
import com.orion.event.GameWarmupEvent;
import com.orion.event.SurvivorWinnerEvent;
import com.orion.event.TeamFlagReturnEvent;
import com.orion.event.TeamSurvivorWinnerEvent;
import com.orion.exception.AuthenticationException;
import com.orion.exception.ExpectedParameterException;
import com.orion.exception.ParserException;
import com.orion.exception.RconException;
import com.orion.urt.Cvar;
import com.orion.urt.Gametype;
import com.orion.urt.Hitlocation;
import com.orion.urt.Item;
import com.orion.urt.Mod;
import com.orion.urt.Team;

public class UrT42Parser implements Parser {

    private static final Map<String, Pattern> patterns = new LinkedHashMap<String, Pattern>();
    private static final Map<Integer, Gametype> gametypeByCode = new HashMap<Integer, Gametype>();
    private static final Map<Integer, Hitlocation> hitlocationByCode = new HashMap<Integer, Hitlocation>();
    private static final Map<Character, Item> itemByCode = new HashMap<Character, Item>();
    private static final Map<String, Item> itemByName = new HashMap<String, Item>();
    private static final Map<Integer, Mod> modByKillCode = new HashMap<Integer, Mod>();
    private static final Map<Integer, Mod> modByHitCode = new HashMap<Integer, Mod>();
    private static final Map<Integer, Team> teamByCode = new HashMap<Integer, Team>();
    private static final Map<String, Team> teamByName = new HashMap<String, Team>();
    private static final Multimap<Gametype, Team> teamsByGametype = LinkedListMultimap.create();

    private final Log log;
    private final Console console;
    private final GroupCtl groupCtl;
    private final ClientCtl clientCtl;

    private BlockingQueue<Event> eventBus;
    private Map<String, Cvar> cvarList;

    static {

        ////////////////////////////////////
        // BEGIN LOADING URT42 GAME TYPES //
        ////////////////////////////////////
        gametypeByCode.put(0, Gametype.FFA);
        gametypeByCode.put(1, Gametype.LMS);
        gametypeByCode.put(3, Gametype.TDM);
        gametypeByCode.put(4, Gametype.TS);
        gametypeByCode.put(5, Gametype.FTL);
        gametypeByCode.put(6, Gametype.CAH);
        gametypeByCode.put(7, Gametype.CTF);
        gametypeByCode.put(8, Gametype.BOMB);
        gametypeByCode.put(9, Gametype.JUMP);
        //////////////////////////////////
        // END LOADING URT42 GAME TYPES //
        //////////////////////////////////

        ///////////////////////////////////////
        // BEGIN LOADING URT42 HIT LOCATIONS //
        ///////////////////////////////////////
        hitlocationByCode.put(1, Hitlocation.HL_HEAD);
        hitlocationByCode.put(2, Hitlocation.HL_HELMET);
        hitlocationByCode.put(3, Hitlocation.HL_TORSO);
        hitlocationByCode.put(4, Hitlocation.HL_VEST);
        hitlocationByCode.put(5, Hitlocation.HL_ARML);
        hitlocationByCode.put(6, Hitlocation.HL_ARMR);
        hitlocationByCode.put(7, Hitlocation.HL_GROIN);
        hitlocationByCode.put(8, Hitlocation.HL_BUTT);
        hitlocationByCode.put(9, Hitlocation.HL_LEGUL);
        hitlocationByCode.put(10, Hitlocation.HL_LEGUR);
        hitlocationByCode.put(11, Hitlocation.HL_LEGLL);
        hitlocationByCode.put(12, Hitlocation.HL_LEGLR);
        hitlocationByCode.put(13, Hitlocation.HL_FOOTL);
        hitlocationByCode.put(14, Hitlocation.HL_FOOTR);
        /////////////////////////////////////
        // END LOADING URT42 HIT LOCATIONS //
        /////////////////////////////////////

        ///////////////////////////////
        // BEGIN LOADING URT42 ITEMS //
        ///////////////////////////////
        itemByCode.put('A', Item.ITEM_EMPTY);
        itemByCode.put('R', Item.ITEM_VEST);
        itemByCode.put('S', Item.ITEM_NVG);
        itemByCode.put('T', Item.ITEM_MEDKIT);
        itemByCode.put('U', Item.ITEM_SILENCER);
        itemByCode.put('V', Item.ITEM_LASER);
        itemByCode.put('W', Item.ITEM_HELMET);
        itemByCode.put('X', Item.ITEM_EXTRAMMO);
        itemByCode.put('F', Item.UT_WP_BERETTA);
        itemByCode.put('G', Item.UT_WP_DEAGLE);
        itemByCode.put('H', Item.UT_WP_SPAS12);
        itemByCode.put('I', Item.UT_WP_MP5K);
        itemByCode.put('J', Item.UT_WP_UMP45);
        itemByCode.put('K', Item.UT_WP_HK69);
        itemByCode.put('L', Item.UT_WP_LR300);
        itemByCode.put('M', Item.UT_WP_G36);
        itemByCode.put('N', Item.UT_WP_PSG1);
        itemByCode.put('O', Item.UT_WP_GRENADE_HE);
        itemByCode.put('Q', Item.UT_WP_GRENADE_SMOKE);
        itemByCode.put('Z', Item.UT_WP_SR8);
        itemByCode.put('a', Item.UT_WP_AK103);
        itemByCode.put('c', Item.UT_WP_NEGEV);
        itemByCode.put('e', Item.UT_WP_M4);
        itemByCode.put('f', Item.UT_WP_GLOCK);
        itemByCode.put('g', Item.UT_WP_COLT1911);
        itemByCode.put('h', Item.UT_WP_MAC11);

        itemByName.put("team_CTF_redflag", Item.ITEM_CTF_RED_FLAG);
        itemByName.put("team_CTF_blueflag", Item.ITEM_CTF_RED_FLAG);
        itemByName.put("team_CTF_neutralflag", Item.ITEM_CTF_NEUTRAL_FLAG);
        itemByName.put("ut_item_vest", Item.ITEM_VEST);
        itemByName.put("ut_item_nvg", Item.ITEM_NVG);
        itemByName.put("ut_item_medkit", Item.ITEM_MEDKIT);
        itemByName.put("ut_item_silencer", Item.ITEM_SILENCER);
        itemByName.put("ut_item_laser", Item.ITEM_LASER);
        itemByName.put("ut_item_helmet", Item.ITEM_HELMET);
        itemByName.put("ut_item_extraammo", Item.ITEM_EXTRAMMO);
        itemByName.put("ut_weapon_knife", Item.UT_WP_KNIFE);
        itemByName.put("ut_weapon_beretta", Item.UT_WP_BERETTA);
        itemByName.put("ut_weapon_deagle", Item.UT_WP_DEAGLE);
        itemByName.put("ut_weapon_spas12", Item.UT_WP_SPAS12);
        itemByName.put("ut_weapon_mp5k", Item.UT_WP_MP5K);
        itemByName.put("ut_weapon_ump45", Item.UT_WP_UMP45);
        itemByName.put("ut_weapon_hk69", Item.UT_WP_HK69);
        itemByName.put("ut_weapon_lr", Item.UT_WP_LR300);
        itemByName.put("ut_weapon_g36", Item.UT_WP_G36);
        itemByName.put("ut_weapon_psg1", Item.UT_WP_PSG1);
        itemByName.put("ut_weapon_sr8", Item.UT_WP_SR8);
        itemByName.put("ut_weapon_ak103", Item.UT_WP_AK103);
        itemByName.put("ut_weapon_negev", Item.UT_WP_NEGEV);
        itemByName.put("ut_weapon_m4", Item.UT_WP_M4);
        itemByName.put("ut_weapon_glock", Item.UT_WP_GLOCK);
        itemByName.put("ut_weapon_colt1911", Item.UT_WP_COLT1911);
        itemByName.put("ut_weapon_mac11", Item.UT_WP_MAC11);
        itemByName.put("ut_weapon_grenade_he", Item.UT_WP_GRENADE_HE);
        itemByName.put("ut_weapon_grenade_smoke", Item.UT_WP_GRENADE_SMOKE);
        itemByName.put("ut_weapon_bomb", Item.UT_WP_BOMB);
        /////////////////////////////
        // END LOADING URT42 ITEMS //
        /////////////////////////////

        ////////////////////////////////////////
        // BEGIN LOADING URT42 MEANS OF DEATH //
        ////////////////////////////////////////
        modByKillCode.put(0, Mod.MOD_UNKNOWN);
        modByKillCode.put(1, Mod.MOD_WATER);
        modByKillCode.put(2, Mod.MOD_SLIME);
        modByKillCode.put(3, Mod.MOD_LAVA);
        modByKillCode.put(4, Mod.MOD_CRUSH);
        modByKillCode.put(5, Mod.MOD_TELEFRAG);
        modByKillCode.put(6, Mod.MOD_FALLING);
        modByKillCode.put(7, Mod.MOD_SUICIDE);
        modByKillCode.put(8, Mod.MOD_TARGET_LASER);
        modByKillCode.put(9, Mod.MOD_TRIGGER_HURT);
        modByKillCode.put(10, Mod.MOD_CHANGE_TEAM);
        modByKillCode.put(12, Mod.UT_MOD_KNIFE);
        modByKillCode.put(13, Mod.UT_MOD_KNIFE_THROWN);
        modByKillCode.put(14, Mod.UT_MOD_BERETTA);
        modByKillCode.put(15, Mod.UT_MOD_DEAGLE);
        modByKillCode.put(16, Mod.UT_MOD_SPAS);
        modByKillCode.put(17, Mod.UT_MOD_UMP45);
        modByKillCode.put(18, Mod.UT_MOD_MP5K);
        modByKillCode.put(19, Mod.UT_MOD_LR300);
        modByKillCode.put(20, Mod.UT_MOD_G36);
        modByKillCode.put(21, Mod.UT_MOD_PSG1);
        modByKillCode.put(22, Mod.UT_MOD_HK69);
        modByKillCode.put(23, Mod.UT_MOD_BLED);
        modByKillCode.put(24, Mod.UT_MOD_KICKED);
        modByKillCode.put(25, Mod.UT_MOD_HEGRENADE);
        modByKillCode.put(28, Mod.UT_MOD_SR8);
        modByKillCode.put(30, Mod.UT_MOD_AK103);
        modByKillCode.put(31, Mod.UT_MOD_SPLODED);
        modByKillCode.put(32, Mod.UT_MOD_SLAPPED);
        modByKillCode.put(33, Mod.UT_MOD_SMITED);
        modByKillCode.put(34, Mod.UT_MOD_BOMBED);
        modByKillCode.put(35, Mod.UT_MOD_NUKED);
        modByKillCode.put(36, Mod.UT_MOD_NEGEV);
        modByKillCode.put(37, Mod.UT_MOD_HK69_HIT);
        modByKillCode.put(38, Mod.UT_MOD_M4);
        modByKillCode.put(39, Mod.UT_MOD_GLOCK);
        modByKillCode.put(40, Mod.UT_MOD_COLT1911);
        modByKillCode.put(41, Mod.UT_MOD_MAC11);
        modByKillCode.put(42, Mod.UT_MOD_FLAG);
        modByKillCode.put(43, Mod.UT_MOD_GOOMBA);

        modByHitCode.put(0, Mod.MOD_UNKNOWN);
        modByHitCode.put(1, Mod.UT_MOD_KNIFE);
        modByHitCode.put(2, Mod.UT_MOD_BERETTA);
        modByHitCode.put(3, Mod.UT_MOD_DEAGLE);
        modByHitCode.put(4, Mod.UT_MOD_SPAS);
        modByHitCode.put(5, Mod.UT_MOD_MP5K);
        modByHitCode.put(6, Mod.UT_MOD_UMP45);
        modByHitCode.put(7, Mod.UT_MOD_HK69);
        modByHitCode.put(8, Mod.UT_MOD_LR300);
        modByHitCode.put(9, Mod.UT_MOD_G36);
        modByHitCode.put(10, Mod.UT_MOD_PSG1);
        modByHitCode.put(14, Mod.UT_MOD_SR8);
        modByHitCode.put(15, Mod.UT_MOD_AK103);
        modByHitCode.put(17, Mod.UT_MOD_NEGEV);
        modByHitCode.put(19, Mod.UT_MOD_M4);
        modByHitCode.put(20, Mod.UT_MOD_GLOCK);
        modByHitCode.put(21, Mod.UT_MOD_COLT1911);
        modByHitCode.put(22, Mod.UT_MOD_MAC11);
        modByHitCode.put(24, Mod.UT_MOD_KICKED);
        modByHitCode.put(25, Mod.UT_MOD_KNIFE_THROWN);
        //////////////////////////////////////
        // END LOADING URT42 MEANS OF DEATH //
        //////////////////////////////////////

        ///////////////////////////////
        // BEGIN LOADING URT42 TEAMS //
        ///////////////////////////////     
        teamByCode.put(0, Team.FREE);
        teamByCode.put(1, Team.RED);
        teamByCode.put(2, Team.BLUE);
        teamByCode.put(3, Team.SPECTATOR);

        teamByName.put("FREE", Team.FREE);
        teamByName.put("RED", Team.RED);
        teamByName.put("BLUE", Team.BLUE);
        teamByName.put("SPECTATOR", Team.SPECTATOR);

        teamsByGametype.put(Gametype.FFA, Team.FREE);
        teamsByGametype.put(Gametype.FFA, Team.SPECTATOR);
        teamsByGametype.put(Gametype.LMS, Team.FREE);
        teamsByGametype.put(Gametype.LMS, Team.SPECTATOR);
        teamsByGametype.put(Gametype.TDM, Team.RED);
        teamsByGametype.put(Gametype.TDM, Team.BLUE);
        teamsByGametype.put(Gametype.TDM, Team.SPECTATOR);
        teamsByGametype.put(Gametype.TS, Team.RED);
        teamsByGametype.put(Gametype.TS, Team.BLUE);
        teamsByGametype.put(Gametype.TS, Team.SPECTATOR);
        teamsByGametype.put(Gametype.FTL, Team.RED);
        teamsByGametype.put(Gametype.FTL, Team.BLUE);
        teamsByGametype.put(Gametype.FTL, Team.SPECTATOR);
        teamsByGametype.put(Gametype.CAH, Team.RED);
        teamsByGametype.put(Gametype.CAH, Team.BLUE);
        teamsByGametype.put(Gametype.CAH, Team.SPECTATOR);
        teamsByGametype.put(Gametype.CTF, Team.RED);
        teamsByGametype.put(Gametype.CTF, Team.BLUE);
        teamsByGametype.put(Gametype.CTF, Team.SPECTATOR);
        teamsByGametype.put(Gametype.BOMB, Team.RED);
        teamsByGametype.put(Gametype.BOMB, Team.BLUE);
        teamsByGametype.put(Gametype.BOMB, Team.SPECTATOR);
        teamsByGametype.put(Gametype.JUMP, Team.FREE);
        teamsByGametype.put(Gametype.JUMP, Team.SPECTATOR);
        /////////////////////////////
        // END LOADING URT42 TEAMS //
        /////////////////////////////  

        //////////////////////////////////
        // BEGIN LOADING URT42 PATTERNS //
        //////////////////////////////////
        patterns.put("BombDefused", Pattern.compile("^\\s*\\d+:\\d+\\s?Bomb\\swas\\sdefused\\sby\\s(?<slot>\\d+)!$",
                Pattern.CASE_INSENSITIVE));
        patterns.put("BombHolder",
                Pattern.compile("^\\s*\\d+:\\d+\\s?Bombholder\\sis\\s+(?<slot>\\d+)$", Pattern.CASE_INSENSITIVE));
        patterns.put("BombPlanted", Pattern.compile("^\\s*\\d+:\\d+\\s?Bomb\\swas\\splanted\\sby\\s(?<slot>\\d+)!$",
                Pattern.CASE_INSENSITIVE));
        patterns.put("Callvote", Pattern.compile(
                "^\\s*\\d+:\\d+\\s?Callvote:\\s?(?<slot>\\d+)\\s?-\\s?\\\"(?<type>\\w+)\\s?(?<data>.*)\\\"$",
                Pattern.CASE_INSENSITIVE));
        patterns.put("ClientBegin",
                Pattern.compile("^\\s*\\d+:\\d+\\s?ClientBegin:\\s(?<slot>\\d+)$", Pattern.CASE_INSENSITIVE));
        patterns.put("ClientConnect",
                Pattern.compile("^\\s*\\d+:\\d+\\s?ClientConnect:\\s(?<slot>\\d+)$", Pattern.CASE_INSENSITIVE));
        patterns.put("ClientDisconnect",
                Pattern.compile("^\\s*\\d+:\\d+\\s?ClientDisconnect:\\s(?<slot>\\d+)$", Pattern.CASE_INSENSITIVE));
        patterns.put("ClientJumpRunCanceled", Pattern.compile(
                "^\\s*\\d+:\\d+\\s?ClientJumpRunCanceled:\\s(?<slot>\\d+)\\s-\\sway:\\s(?<way>\\d+)(\\s-\\sattempt:\\s(?<anum>\\d+)\\sof\\s(?<amax>\\d+))?$",
                Pattern.CASE_INSENSITIVE));
        patterns.put("ClientJumpRunStarted", Pattern.compile(
                "^\\s*\\d+:\\d+\\s?ClientJumpRunStarted:\\s(?<slot>\\d+)\\s-\\sway:\\s(?<way>\\d+)(\\s-\\sattempt:\\s(?<anum>\\d+)\\sof\\s(?<amax>\\d+))?$",
                Pattern.CASE_INSENSITIVE));
        patterns.put("ClientJumpRunStopped", Pattern.compile(
                "^\\s*\\d+:\\d+\\s?ClientJumpRunStopped:\\s(?<slot>\\d+)\\s-\\sway:\\s(?<way>\\d+)\\s-\\stime:\\s(?<wtime>\\d+)(\\s-\\sattempt:\\s(?<anum>\\d+)\\sof\\s(?<amax>\\d+))?$",
                Pattern.CASE_INSENSITIVE));
        patterns.put("ClientLoadPosition", Pattern.compile(
                "^\\s*\\d+:\\d+\\s?ClientLoadPosition:\\s(?<slot>\\d+)\\s-\\s(?<x>-?\\d+\\.\\d+)\\s-\\s(?<y>-?\\d+\\.\\d+)\\s-\\s(?<z>-?\\d+\\.\\d+)$",
                Pattern.CASE_INSENSITIVE));
        patterns.put("ClientSavePosition", Pattern.compile(
                "^\\s*\\d+:\\d+\\s?ClientSavePosition:\\s(?<slot>\\d+)\\s-\\s(?<x>-?\\d+\\.\\d+)\\s-\\s(?<y>-?\\d+\\.\\d+)\\s-\\s(?<z>-?\\d+\\.\\d+)$",
                Pattern.CASE_INSENSITIVE));
        patterns.put("ClientUserinfo",
                Pattern.compile("^\\s*\\d+:\\d+\\s?ClientUserinfo:\\s(?<slot>\\d+)\\s(?<infostring>.*)$",
                        Pattern.CASE_INSENSITIVE));
        patterns.put("ClientUserinfoChanged",
                Pattern.compile("^\\s*\\d+:\\d+\\s?ClientUserinfoChanged:\\s*(?<slot>\\d+)\\s*(?<infostring>.*)$",
                        Pattern.CASE_INSENSITIVE));
        patterns.put("Exit",
                Pattern.compile("^\\s*\\d+:\\d+\\s?Exit:\\sTimelimit hit.$", Pattern.CASE_INSENSITIVE));
        patterns.put("Flag",
                Pattern.compile("^\\s*\\d+:\\d+\\s?Flag:\\s(?<slot>\\d+)\\s(?<action>\\d+):\\s(?<text>.*)$",
                        Pattern.CASE_INSENSITIVE));
        patterns.put("FlagReturn",
                Pattern.compile("^\\s*\\d+:\\d+\\s?Flag\\sReturn:\\s(?<team>.*)$", Pattern.CASE_INSENSITIVE));
        patterns.put("Hit", Pattern.compile(
                "^\\s*\\d+:\\d+\\s?Hit:\\s(?<victim>\\d+)\\s(?<attacker>\\d+)\\s(?<hitlocation>\\d+)\\s(?<weapon>\\d+):\\s(.*)$",
                Pattern.CASE_INSENSITIVE));
        patterns.put("Item",
                Pattern.compile("^\\s*\\d+:\\d+\\sItem:\\s(?<slot>\\d+)\\s(?<item>.*)$", Pattern.CASE_INSENSITIVE));
        patterns.put("InitGame",
                Pattern.compile("^\\s*\\d+:\\d+\\s?InitGame:\\s(?<infostring>.*)$", Pattern.CASE_INSENSITIVE));
        patterns.put("InitRound",
                Pattern.compile("^\\s*\\d+:\\d+\\s?InitRound:\\s(?<infostring>.*)$", Pattern.CASE_INSENSITIVE));
        patterns.put("Kill",
                Pattern.compile(
                        "^\\s*\\d+:\\d+\\s?Kill:\\s(?<attacker>\\d+)\\s(?<victim>\\d+)\\s(?<weapon>\\d+):\\s(.*)$",
                        Pattern.CASE_INSENSITIVE));
        patterns.put("Radio", Pattern.compile(
                "^\\s*\\d+:\\d+\\s?Radio:\\s?(?<slot>\\d+)\\s?-\\s?(?<group>\\d+)\\s?-\\s?(?<id>\\d+)\\s?-\\s?\\\"(?<location>.*)\\\"\\s?-\\s?\\\"(?<message>.*)\\\"$",
                Pattern.CASE_INSENSITIVE));
        patterns.put("Say",
                Pattern.compile("^\\s*\\d+:\\d+\\s?say:\\s(?<slot>\\d+)\\s(?<name>.*):\\s?(?<message>.*)$",
                        Pattern.CASE_INSENSITIVE));
        patterns.put("SayTell", Pattern.compile(
                "^\\s*\\d+:\\d+\\s?saytell:\\s(?<slot>\\d+)\\s(?<target>\\d+)\\s(?<name>.*):\\s?(?<message>.*)$",
                Pattern.CASE_INSENSITIVE));
        patterns.put("SayTeam",
                Pattern.compile("^\\s*\\d+:\\d+\\s?sayteam:\\s(?<slot>\\d+)\\s(?<name>.*):\\s?(?<message>.*)$",
                        Pattern.CASE_INSENSITIVE));
        patterns.put("ShutdownGame", Pattern.compile("^\\s*\\d+:\\d+\\s?ShutdownGame:$", Pattern.CASE_INSENSITIVE));
        patterns.put("SurvivorWinner",
                Pattern.compile("^\\s*\\d+:\\d+\\s?SurvivorWinner:\\s(?<data>.*)$", Pattern.CASE_INSENSITIVE));
        patterns.put("Vote", Pattern.compile("^\\s*\\d+:\\d+\\s?Vote:\\s?(?<slot>\\d+)\\s?-\\s?(?<data>\\d+)$",
                Pattern.CASE_INSENSITIVE));
        patterns.put("Warmup", Pattern.compile("^\\s*\\d+:\\d+\\s?Warmup:$", Pattern.CASE_INSENSITIVE));
        ////////////////////////////////
        // END LOADING URT42 PATTERNS //
        ////////////////////////////////

    }

    /**
     * Object constructor
     * 
     * @author Daniele Pantaleone 
     * @param  log Main logger object reference
     * @param  console Main console object reference
     * @param  groupCtl The <tt>Group</tt> controller object reference
     * @param  clientCtl The <tt>Client</tt> controller object reference
     * @param  eventBus A <tt>BlockingQueue</tt> where to push generated events
     * @param  cvarList A <tt>Map</tt> of <tt>Cvar</tt> objects shared 
     *                  by the Parser and the Console
     **/
    public UrT42Parser(Log log, Console console, GroupCtl groupCtl, ClientCtl clientCtl,
            BlockingQueue<Event> eventBus, Map<String, Cvar> cvarList) {

        this.log = log;
        this.console = console;
        this.groupCtl = groupCtl;
        this.clientCtl = clientCtl;
        this.eventBus = eventBus;
        this.cvarList = cvarList;

        this.log.debug("Urban Terror 4.2 parser initialized");

    }

    /**
     * Return the <tt>Gametype</tt> matching the given code
     * 
     * @author Daniele Pantaleone
     * @param  code The <tt>Gametype</tt> code
     * @throws IndexOutOfBoundsException If the <tt>Gametype</tt> code is not mapped
     * @return The <tt>Gametype</tt> matching the given code
     **/
    public Gametype getGametypeByCode(Integer code) throws IndexOutOfBoundsException {

        if (!gametypeByCode.containsKey(code))
            throw new IndexOutOfBoundsException("could not retrieve a Gametype using the given code: " + code);

        return gametypeByCode.get(code);
    }

    /**
     * Return the <tt>Hitlocation</tt> matching the given code
     * 
     * @author Daniele Pantaleone
     * @param  code The <tt>Hitlocation</tt> code
     * @throws IndexOutOfBoundsException If the <tt>Hitlocation</tt> code is not mapped
     * @return The <tt>Hitlocation</tt> matching the given code
     **/
    public Hitlocation getHitlocationByCode(Integer code) throws IndexOutOfBoundsException {

        if (!hitlocationByCode.containsKey(code))
            throw new IndexOutOfBoundsException("could not retrieve a Hitlocation using the given code: " + code);

        return hitlocationByCode.get(code);
    }

    /**
     * Return the <tt>Item</tt> matching the given gear code
     * 
     * @author Daniele Pantaleone
     * @param  code The <tt>Item</tt> gear code
     * @throws IndexOutOfBoundsException If the <tt>Item</tt> gear code is not mapped
     * @return The <tt>Item</tt> matching the given gear code
     **/
    public Item getItemByCode(Character code) throws IndexOutOfBoundsException {

        if (!itemByCode.containsKey(code))
            throw new IndexOutOfBoundsException("could not retrieve an Item using the given code: " + code);

        return itemByCode.get(code);
    }

    /**
     * Return the <tt>Item</tt> matching the given name
     * 
     * @author Daniele Pantaleone
     * @param  name The <tt>Item</tt> name
     * @throws IndexOutOfBoundsException If the <tt>Item</tt> name is not mapped
     * @return The <tt>Item</tt> matching the given name
     **/
    public Item getItemByName(String name) throws IndexOutOfBoundsException {

        if (!itemByName.containsKey(name))
            throw new IndexOutOfBoundsException("could not retrieve an Item using the given name: " + name);

        return itemByName.get(name);
    }

    /**
     * Return the <tt>Mod</tt> matching the given kill code
     * 
     * @author Daniele Pantaleone
     * @param  killcode The <tt>Mod</tt> kill code
     * @throws IndexOutOfBoundsException If the <tt>Mod</tt> kill code is not mapped
     * @return The <tt>Mod</tt> matching the given kill code
     **/
    public Mod getModByKillCode(int code) throws IndexOutOfBoundsException {

        if (!modByKillCode.containsKey(code))
            throw new IndexOutOfBoundsException("could not retrieve a Mod using the given kill code: " + code);

        return modByKillCode.get(code);
    }

    /**
     * Return the <tt>Mod</tt> matching the given hit code
     * 
     * @author Daniele Pantaleone
     * @param  hitcode The <tt>Mod</tt> hit code
     * @throws IndexOutOfBoundsException If the <tt>Mod</tt> hit code code is not mapped
     * @return The <tt>Mod</tt> matching the given hit code
     **/
    public Mod getModByHitCode(int code) throws IndexOutOfBoundsException {

        if (!modByHitCode.containsKey(code))
            throw new IndexOutOfBoundsException("could not retrieve a Mod using the given hit code: " + code);

        return modByHitCode.get(code);
    }

    /**
     * Return the <tt>Team</tt> matching the given code
     * 
     * @author Daniele Pantaleone
     * @param  code The <tt>Team</tt> code
     * @throws IndexOutOfBoundsException If the <tt>Team</tt> code is not mapped
     * @return The <tt>Team</tt> matching the given code
     **/
    public Team getTeamByCode(Integer code) throws IndexOutOfBoundsException {

        if (!teamByCode.containsKey(code))
            throw new IndexOutOfBoundsException("could not retrieve a Team using the given code: " + code);

        return teamByCode.get(code);
    }

    /**
     * Return the Team by matching the given name.
     * 
     * @author Daniele Pantaleone
     * @param  name The <tt>Team</tt> name
     * @throws IndexOutOfBoundsException If the <tt>Team</tt> name is not mapped
     * @return The <tt>Team</tt> matching the given name
     **/
    public Team getTeamByName(String name) throws IndexOutOfBoundsException {

        name = name.toUpperCase();

        switch (name) {

        case "F":
            name = "FREE";
            break;
        case "R":
            name = "RED";
            break;
        case "B":
            name = "BLUE";
            break;
        case "S":
            name = "SPECTATOR";
            break;
        case "SPEC":
            name = "SPECTATOR";
            break;

        }

        if (!teamByName.containsKey(name))
            throw new IndexOutOfBoundsException("could not retrieve a Team using the given name: " + name);

        return teamByName.get(name);
    }

    /**
     * Return a <tt>List</tt> of available <tt>Team</tt>
     * objects according to the current played <tt>Gametype</tt>
     * 
     * @author Daniele Pantaleone
     * @return A <tt>List</tt> of available <tt>Team</tt> objects according to
     *         the current played <tt>Gametype</tt>
     **/
    public List<Team> getAvailableTeams() {

        Cvar cvar = this.cvarList.get("g_gametype");

        if (cvar == null)
            return new LinkedList<Team>();

        Gametype gametype = gametypeByCode.get(cvar.getInt());
        return new LinkedList<Team>(teamsByGametype.get(gametype));

    }

    //////////////////////////
    // BEGIN HELPER METHODS //
    //////////////////////////

    /**
     * Helper method for BombDefused
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onBombDefused(Matcher matcher) {

        // 0:00 Bomb was defused by 3!
        // 0:00 Bomb was defused by 11!

        try {

            int slot = Integer.parseInt(matcher.group("slot"));
            Client client = this.clientCtl.getBySlot(slot);

            // Check to have a proper client object before the event generation
            checkNotNull(client, "could not retrieve client on slot %s", slot);

            this.eventBus.put(new ClientBombDefusedEvent(client));
            this.log.trace("[EVENT] ClientBombDefusedEvent [ client : " + client.getSlot() + " ]");

        } catch (NullPointerException | InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] ClientBombDefusedEvent", e);

        }

    }

    /**
     * Helper method for BombHolder
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onBombHolder(Matcher matcher) {

        // 0:00 Bombholder is 1
        // 0:00 Bombholder is 15

        try {

            int slot = Integer.parseInt(matcher.group("slot"));
            Client client = this.clientCtl.getBySlot(slot);

            // Check to have a proper client object before the event generation
            checkNotNull(client, "could not retrieve client on slot %s", slot);

            this.eventBus.put(new ClientBombHolderEvent(client));
            this.log.trace("[EVENT] ClientBombHolderEvent [ client : " + client.getSlot() + " ]");

        } catch (NullPointerException | InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] ClientBombHolderEvent", e);

        }

    }

    /**
     * Helper method for BombPlanted
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onBombPlanted(Matcher matcher) {

        // 0:00 Bomb was planted by 3!
        // 0:00 Bomb was planted by 11!

        try {

            int slot = Integer.parseInt(matcher.group("slot"));
            Client client = this.clientCtl.getBySlot(slot);

            // Check to have a proper client object before the event generation
            checkNotNull(client, "could not retrieve client on slot %s", slot);

            this.eventBus.put(new ClientBombPlantedEvent(client));
            this.log.trace("[EVENT] ClientBombPlantedEvent [ client : " + client.getSlot() + " ]");

        } catch (NullPointerException | InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] ClientBombPlantedEvent", e);

        }

    }

    /**
     * Helper method for Callvote
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onCallvote(Matcher matcher) {

        // 0:00 Callvote: 3 - "map ut42_jupiter"
        // 0:00 Callvote: 3 - "cyclemap"

        try {

            int slot = Integer.parseInt(matcher.group("slot"));
            Client client = this.clientCtl.getBySlot(slot);

            // Check to have a proper client object before the event generation
            checkNotNull(client, "could not retrieve client on slot %s", slot);

            String type = matcher.group("type");
            String data = matcher.group("data") != null && !matcher.group("data").isEmpty() ? matcher.group("data")
                    : null;

            // Check to have at least the callvote type
            checkNotNull(type, "could not retrieve callvote type");

            Callvote callvote = new Callvote.Builder(client, type).data(data).build();

            this.eventBus.put(new ClientCallvoteEvent(client, callvote));
            this.log.trace("[EVENT] ClientCallvoteEvent [ client : " + client.getSlot() + " | type : "
                    + callvote.getType() + " | data : " + callvote.getData() + " ]");

        } catch (NullPointerException | InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] ClientCallvoteEvent", e);

        }

    }

    /**
     * Helper method for ClientBegin
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onClientBegin(Matcher matcher) {

        // 0:00 ClientBegin: 0
        // 0:00 ClientBegin: 4

        try {

            int slot = Integer.parseInt(matcher.group("slot"));
            Client client = this.clientCtl.getBySlot(slot);

            // Check to have a proper client object before the event generation
            checkNotNull(client, "could not retrieve client on slot %s", slot);

            this.eventBus.put(new ClientJoinEvent(client));
            this.log.trace("[EVENT] ClientJoinEvent [ client : " + client.getSlot() + " ]");

        } catch (NullPointerException | InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] ClientJoinEvent", e);

        }

    }

    /**
     * Helper method for Say (matching command pattern)
     * 
     * @author Daniele Pantaleone
     * @param  client The <tt>Client</tt> who performed the command
     * @param  message The command string
     **/
    public void onClientCommand(Client client, String message) {

        if (message.substring(0, 2).equals("!!")) {
            // Ugly hack to make !! an alias for !say
            // Used for b3 command syntax compatibility
            message = "!say " + (message.substring(2));
        }

        // Prefix prefix = Prefix.getByChar(message.charAt(0));
        // String data[] = message.substring(1).split(" ", 2);  
        // String handle = data[0].toLowerCase();
        // String params = data.length > 1 ? data[1].toLowerCase() : null;

        // Command command = new Command(client, prefix, handle, params);

    }

    /**
     * Helper method for ClientConnect
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onClientConnect(Matcher matcher) {

        // 0:00 ClientConnect: 0
        // 0:00 ClientConnect: 4

        int slot = Integer.parseInt(matcher.group("slot"));
        this.log.debug("Client connecting on slot " + slot + ". Ready to parse ClientUserinfo line...");

    }

    /**
     * Helper method for ClientDisconnect
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onClientDisconnect(Matcher matcher) {

        // 0:00 ClientDisconnect: 0
        // 0:00 ClientDisconnect: 4

        try {

            int slot = Integer.parseInt(matcher.group("slot"));
            Client client = this.clientCtl.removeBySlot(slot);

            // Check to have a proper client object before the event generation
            checkNotNull(client, "could not retrieve client on slot %s", slot);

            this.eventBus.put(new ClientDisconnectEvent(client));
            this.log.trace("[EVENT] ClientDisconnectEvent [ client : " + client.getSlot() + " ]");

        } catch (NullPointerException | InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] ClientDisconnectEvent", e);

        }

    }

    /**
     * Helper method for ClientJumpRunCanceled
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onClientJumpRunCanceled(Matcher matcher) {

        // 0:00 ClientJumpRunCanceled: 0 - way: 2
        // 0:00 ClientJumpRunCanceled: 0 - way: 2 - attempt: 1 of 5

        try {

            int slot = Integer.parseInt(matcher.group("slot"));
            Client client = this.clientCtl.getBySlot(slot);

            // Check to have a proper client object before the event generation
            checkNotNull(client, "could not retrieve client on slot %s", slot);

            int way = Integer.parseInt(matcher.group("way"));
            Integer anum = Integer.valueOf(matcher.group("anum"));
            Integer amax = Integer.valueOf(matcher.group("amax"));

            if (anum != null && amax != null) {
                this.eventBus.put(new ClientJumpRunCanceledEvent(client, way, anum, amax));
                this.log.trace("[EVENT] ClientJumpRunCanceledEvent [ client : " + client.getSlot() + " | way : "
                        + way + " | attempt_num : " + anum + " | attempt_max : " + amax + " ]");
            } else {
                this.eventBus.put(new ClientJumpRunCanceledEvent(client, way));
                this.log.trace("[EVENT] ClientJumpRunCanceledEvent [ client : " + client.getSlot() + " | way : "
                        + way + " ]");
            }

        } catch (NullPointerException | InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] ClientJumpRunCanceledEvent", e);

        }

    }

    /**
     * Helper method for ClientJumpRunStarted
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onClientJumpRunStarted(Matcher matcher) {

        // 0:00 ClientJumpRunStarted: 0 - way: 2
        // 0:00 ClientJumpRunStarted: 0 - way: 2 - attempt: 1 of 5

        try {

            int slot = Integer.parseInt(matcher.group("slot"));
            Client client = this.clientCtl.getBySlot(slot);

            // Check to have a proper client object before the event generation
            checkNotNull(client, "could not retrieve client on slot %s", slot);

            int way = Integer.parseInt(matcher.group("way"));
            Integer anum = Integer.valueOf(matcher.group("anum"));
            Integer amax = Integer.valueOf(matcher.group("amax"));

            if (anum != null && amax != null) {
                this.eventBus.put(new ClientJumpRunStartedEvent(client, way, anum, amax));
                this.log.trace("[EVENT] ClientJumpRunStartedEvent [ client : " + client.getSlot() + " | way : "
                        + way + " | attempt_num : " + anum + " | attempt_max : " + amax + " ]");
            } else {
                this.eventBus.put(new ClientJumpRunStartedEvent(client, way));
                this.log.trace("[EVENT] ClientJumpRunStartedEvent [ client : " + client.getSlot() + " | way : "
                        + way + " ]");
            }

        } catch (NullPointerException | InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] ClientJumpRunStartedEvent", e);

        }

    }

    /**
     * Helper method for ClientJumpRunStopped
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onClientJumpRunStopped(Matcher matcher) {

        // 0:00 ClientJumpRunStopped: 0 - way: 2 - time: 4850
        // 0:00 ClientJumpRunStopped: 0 - way: 2 - time: 4850 - attempt: 1 of 5

        try {

            int slot = Integer.parseInt(matcher.group("slot"));
            Client client = this.clientCtl.getBySlot(slot);

            // Check to have a proper client object before the event generation
            checkNotNull(client, "could not retrieve client on slot %s", slot);

            int way = Integer.parseInt(matcher.group("way"));
            int wtime = Integer.parseInt(matcher.group("wtime"));
            Integer anum = Integer.valueOf(matcher.group("anum"));
            Integer amax = Integer.valueOf(matcher.group("amax"));

            if (anum != null && amax != null) {
                this.eventBus.put(new ClientJumpRunStoppedEvent(client, way, wtime, anum, amax));
                this.log.trace("[EVENT] ClientJumpRunStoppedEvent [ client : " + client.getSlot() + " | way : "
                        + way + " | way_time : " + wtime + " | attempt_num : " + anum + " | attempt_max : " + amax
                        + " ]");
            } else {
                this.eventBus.put(new ClientJumpRunStoppedEvent(client, way, wtime));
                this.log.trace("[EVENT] ClientJumpRunStoppedEvent [ client : " + client.getSlot() + " | way : "
                        + way + " | way_time : " + wtime + " ]");
            }

        } catch (NullPointerException | InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] ClientJumpRunStoppedEvent", e);

        }

    }

    /**
     * Helper method for ClientLoadPosition
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onClientLoadPosition(Matcher matcher) {

        // 0:00 ClientLoadPosition: 0 - -7558.291015 - -79.125061 - 160.125000
        // 0:00 ClientLoadPosition: 0 - 758.278015 - 12.125061 - 10.405003

        try {

            int slot = Integer.parseInt(matcher.group("slot"));
            Client client = this.clientCtl.getBySlot(slot);

            // Check to have a proper client object before the event generation
            checkNotNull(client, "could not retrieve client on slot %s", slot);

            float x = Float.parseFloat(matcher.group("x"));
            float y = Float.parseFloat(matcher.group("y"));
            float z = Float.parseFloat(matcher.group("z"));

            this.eventBus.put(new ClientPositionLoadEvent(client, x, y, z));
            this.log.trace("[EVENT] ClientPositionLoadEvent [ client : " + client.getSlot() + " | x : " + x
                    + " | y : " + y + " | z : " + z + " ]");

        } catch (NullPointerException | InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] ClientPositionLoadEvent", e);

        }

    }

    /**
     * Helper method for ClientSavePosition
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onClientSavePosition(Matcher matcher) {

        // 0:00 ClientSavePosition: 0 - -7558.291015 - -79.125061 - 160.125000
        // 0:00 ClientSavePosition: 0 - 758.278015 - 12.125061 - 10.405003

        try {

            int slot = Integer.parseInt(matcher.group("slot"));
            Client client = this.clientCtl.getBySlot(slot);

            // Check to have a proper client object before the event generation
            checkNotNull(client, "could not retrieve client on slot %s", slot);

            float x = Float.parseFloat(matcher.group("x"));
            float y = Float.parseFloat(matcher.group("y"));
            float z = Float.parseFloat(matcher.group("z"));

            this.eventBus.put(new ClientPositionSaveEvent(client, x, y, z));
            this.log.trace("[EVENT] ClientPositionSaveEvent [ client : " + client.getSlot() + " | x : " + x
                    + " | y : " + y + " | z : " + z + " ]");

        } catch (NullPointerException | InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] ClientPositionSaveEvent", e);

        }

    }

    /**
     * Helper method for ClientUserinfo
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onClientUserinfo(Matcher matcher) {

        // 0:00 ClientUserinfo: 9 \ip\93.84.143.218:27960\name\DsP**Unhitman....
        // 0:00 ClientUserinfo: 8 \ip\87.166.159.201:27960\name\SickHippster!nc....

        int slot = Integer.parseInt(matcher.group("slot"));
        Map<String, String> userinfo = this.parseInfoString(matcher.group("infostring"));

        try {

            // check if this is a new client connection
            Client client = checkNotNull(this.clientCtl.getBySlot(slot));

            // checking for gear change
            if (userinfo.containsKey("gear")) {

                if (client.getGear() == null || !client.getGear().equals(userinfo.get("gear"))) {

                    try {

                        client.setGear(userinfo.get("gear"));
                        this.eventBus.put(new ClientGearChangeEvent(client));
                        this.log.trace("[parser] ClientGearChangeEvent [ client : " + client.getSlot() + " ]");

                    } catch (InterruptedException e) {

                        // 
                        this.log.error("Could not generate 'ClientGearChangeEvent'", e);

                    }

                }

            }

        } catch (NullPointerException e1) {

            // we couldn't retrieve a client object so we will
            // assume that this is a new client connection to the server

            Client client = null;

            if (!(userinfo.containsKey("cl_guid")) && (userinfo.containsKey("skill"))) {

                try {

                    // a bot is connecting to the server
                    // we'll handle this in a different way

                    client = new Client.Builder(InetAddress.getByName("0.0.0.0"), "BOT_" + slot).bot(true).build();

                    this.log.debug("Client connecting on slot " + slot + " has been detected as a BOT");

                } catch (UnknownHostException e2) {

                    this.log.error("Could not generate 'ClientConnectEvent'", e2);
                    return;

                }

            } else {

                // normal client connecting
                // we'll try to auth the client using FSA at first
                client = this.authByFsa(slot, userinfo);

                if (client == null) {

                    // no match for this client matching FSA
                    // trying to backup using the old good GUID
                    client = this.authByGuid(slot, userinfo);

                    if (client == null) {

                        // no match also searching by GUID
                        // handle this as a new client on this server
                        client = new Client.Builder(InetAddress.getByName(userinfo.get("ip").split(":", 2)[0]),
                                userinfo.get("cl_guid")).auth(
                                        userinfo.containsKey("auth_account") ? userinfo.get("auth_account") : null)
                                        .group(this.groupCtl.getByKeyword("guest")).build();

                    }

                }

            }

            try {

                client.setSlot(slot);

                if (userinfo.containsKey("name"))
                    client.setName(userinfo.get("name"));

                if (userinfo.containsKey("gear"))
                    client.setGear(userinfo.get("gear"));

                if (userinfo.containsKey("team"))
                    client.setTeam(getTeamByName(userinfo.get("team")));

                // update the number of connections just if it's a new client or he disconnected more than 1 hour ago
                if (client.getTimeEdit() == null
                        || Hours.hoursBetween(client.getTimeEdit(), new DateTime()).getHours() > 1)
                    client.setConnections(client.getConnections() + 1);

                this.clientCtl.add(client);
                this.clientCtl.save(client);

                this.eventBus.put(new ClientConnectEvent(client));
                this.log.trace("[EVENT] ClientConnectEvent [ client : " + slot + " ]");

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

                // Logging the Exception
                this.log.error("[EVENT] ClientConnectEvent", e);

            }

        }

    }

    /**
     * Helper method for ClientUserinfoChanged
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onClientUserinfoChanged(Matcher matcher) {

        //0:00 ClientUserinfoChanged: 0 n\{B.D}LEGIONARIA\t\0\r\1\tl\0\a0\255\a1\0\a2\255
        //0:00 ClientUserinfoChanged: 1 n\ExcessivePlayer\t\0\r\2\tl\0\a0\0\a1\0\a2\0

        try {

            int slot = Integer.parseInt(matcher.group("slot"));
            Client client = this.clientCtl.getBySlot(slot);

            // Check to have a proper client object before the event generation
            checkNotNull(client, "could not retrieve client on slot %s", slot);

            // Parsing userinfo string so we can generate our custom events
            Map<String, String> userinfo = this.parseInfoString(matcher.group("infostring"));

            // Checking name change
            if (userinfo.containsKey("n")) {

                String name = userinfo.get("n").replaceAll("\\^[0-9]{1}", "");
                if (!client.getName().toLowerCase().equals(name.toLowerCase())) {
                    client.setName(name);
                    this.eventBus.put(new ClientNameChangeEvent(client));
                    this.log.trace("[EVENT] ClientNameChangeEvent [ client : " + client.getSlot() + " ]");
                }
            }

            // Checking team change
            if (userinfo.containsKey("t")) {

                try {

                    Team team = this.getTeamByCode(Integer.parseInt(userinfo.get("t")));

                    if (client.getTeam() != team) {
                        client.setTeam(team);
                        this.eventBus.put(new ClientTeamChangeEvent(client));
                        this.log.trace("[EVENT] ClientTeamChangeEvent [ client : " + client.getSlot() + " ]");
                    }

                } catch (IndexOutOfBoundsException e) {

                    // Logging the Exception
                    this.log.error("[EVENT] ClientTeamChangeEvent", e);

                }

            }

        } catch (NullPointerException | InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] ClientNameChangeEvent | ClientTeamChangeEvent", e);

        }

    }

    /**
     * Helper method for Flag
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onFlag(Matcher matcher) {

        // 0:00 Flag: 0 2: team_CTF_redflag
        // 0:00 Flag: 0 0: team_CTF_blueflag

        try {

            int slot = Integer.parseInt(matcher.group("slot"));
            int action = Integer.parseInt(matcher.group("action"));

            Client client = this.clientCtl.getBySlot(slot);

            switch (action) {

            case 0:
                checkNotNull(client, "[EVENT] ClientFlagDroppedEvent: could not retrieve client on slot %s", slot);
                this.eventBus.put(new ClientFlagDroppedEvent(client));
                this.log.trace("[EVENT] ClientFlagDroppedEvent [ client : " + client.getSlot() + " ]");
                break;
            case 1:
                checkNotNull(client, "[EVENT] ClientFlagReturnedEvent: could not retrieve client on slot %s", slot);
                this.eventBus.put(new ClientFlagReturnedEvent(client));
                this.log.trace("[EVENT] ClientFlagReturnedEvent [ client : " + client.getSlot() + " ]");
                break;
            case 2:
                checkNotNull(client, "[EVENT] ClientFlagCapturedEvent: could not retrieve client on slot %s", slot);
                this.eventBus.put(new ClientFlagCapturedEvent(client));
                this.log.trace("[EVENT] ClientFlagCapturedEvent [ client : " + client.getSlot() + " ]");
                break;

            }

        } catch (NullPointerException | InterruptedException e) {

            // Logging the Exception
            this.log.error(e.getMessage());

        }

    }

    /**
     * Helper method for Flag Return
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onFlagReturn(Matcher matcher) {

        // 0:00 Flag Return: BLUE
        // 0:00 Flag Return: RED

        try {

            Team team = getTeamByName(matcher.group("team"));
            this.eventBus.put(new TeamFlagReturnEvent(team));
            this.log.trace("[EVENT] TeamFlagReturnEvent [ team : " + team.name() + " ]");

        } catch (IndexOutOfBoundsException | InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] TeamFlagReturnEvent", e);

        }
    }

    /**
     * Helper method for Hit
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onHit(Matcher matcher) {

        // 0:00 Hit: 12 7 1 19: [Gore]Pinhead hit Fapking in the Helmet
        // 0:00 Hit: 13 10 0 8: WizardOfGore hit Fenix in the Head

        try {

            int vslot = Integer.parseInt(matcher.group("victim"));
            int aslot = Integer.parseInt(matcher.group("attacker"));
            int hcode = Integer.parseInt(matcher.group("hitlocation"));
            int wcode = Integer.parseInt(matcher.group("weapon"));

            Client vclient = this.clientCtl.getBySlot(vslot);
            Client aclient = this.clientCtl.getBySlot(aslot);
            Hitlocation hitloc = this.getHitlocationByCode(hcode);
            Mod mod = this.getModByHitCode(wcode);

            if (aclient == vclient) {

                checkNotNull(vclient, "[EVENT] ClientDamageSelfEvent: could not retrieve victim client on slot %s",
                        vslot);
                this.eventBus.put(new ClientDamageSelfEvent(vclient, mod, hitloc));
                this.log.trace("[EVENT] ClientDamageSelfEvent [ client : " + vclient.getSlot() + " | mod : "
                        + mod.name() + " | hitlocation : " + hitloc.name() + " ]");

            } else if ((aclient.getTeam() == vclient.getTeam()) && (aclient.getTeam() != Team.SPECTATOR)
                    && (aclient.getTeam() != Team.FREE)) {

                checkNotNull(vclient, "[EVENT] ClientDamageTeamEvent: could not retrieve victim client on slot %s",
                        vslot);
                checkNotNull(aclient,
                        "[EVENT] ClientDamageTeamEvent: could not retrieve attacker client on slot %s", vslot);
                this.eventBus.put(new ClientDamageTeamEvent(aclient, vclient, mod, hitloc));
                this.log.trace("[EVENT] ClientDamageTeamEvent [ attacker : " + aclient.getSlot() + " | victim : "
                        + vclient.getSlot() + " | mod : " + mod.name() + " | hitlocation : " + hitloc.name()
                        + " ]");

            } else {

                checkNotNull(vclient, "[EVENT] ClientDamageEvent: could not retrieve victim client on slot %s",
                        vslot);
                checkNotNull(aclient, "[EVENT] ClientDamageEvent: could not retrieve attacker client on slot %s",
                        vslot);
                this.eventBus.put(new ClientDamageEvent(aclient, vclient, mod, hitloc));
                this.log.trace("[EVENT] ClientDamageEvent [ attacker : " + aclient.getSlot() + " | victim : "
                        + vclient.getSlot() + " | mod : " + mod.name() + " | hitlocation : " + hitloc.name()
                        + " ]");

            }

        } catch (NullPointerException | IndexOutOfBoundsException | InterruptedException e) {

            // Logging the Exception
            this.log.error(e.getMessage());

        }

    }

    /**
     * Helper method for onInitGame
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onInitGame(Matcher matcher) {

        // 0:00 InitGame: \sv_allowdownload\0\g_matchmode\1\g_gametype\4\sv_maxclients\20\sv_floodprotect\0...
        // 0:00 InitGame: \sv_allowdownload\0\g_matchmode\1\g_gametype\4\sv_maxclients\20\sv_floodprotect\0...

        try {

            String infostring = matcher.group("infostring");
            this.eventBus.put(new GameStartEvent());
            this.log.trace("[EVENT] GameStartEvent [ data : " + infostring + " ]");

        } catch (InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] GameStartEvent", e);

        }

    }

    /**
     * Helper method for InitRound
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onInitRound(Matcher matcher) {

        // 0:00 InitRound: \sv_allowdownload\0\g_matchmode\1\g_gametype\4\sv_maxclients\20\sv_floodprotect\0...
        // 0:00 InitRound: \sv_allowdownload\0\g_matchmode\1\g_gametype\4\sv_maxclients\20\sv_floodprotect\0...

        try {

            String infostring = matcher.group("infostring");
            this.eventBus.put(new GameRoundStartEvent());
            this.log.trace("[EVENT] GameRoundStartEvent [ data : " + infostring + " ]");

        } catch (InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] GameRoundStartEvent", e);

        }

    }

    /**
     * Helper method for Item
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onItem(Matcher matcher) {

        // 0:00 Item: 0 ut_item_deagle
        // 0:00 Item: 1 ut_item_medkit

        try {

            int slot = Integer.parseInt(matcher.group("slot"));
            Client client = this.clientCtl.removeBySlot(slot);

            // Check to have a proper client object before the event generation
            checkNotNull(client, "could not retrieve client on slot %s", slot);

            Item item = getItemByName(matcher.group("item"));
            this.eventBus.put(new ClientItemPickupEvent(client, item));
            this.log.trace("[EVENT] ClientItemPickupEvent [ client : " + client.getSlot() + " | item : "
                    + item.name() + " ]");

        } catch (NullPointerException | IndexOutOfBoundsException | InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] ClientItemPickupEvent", e);

        }

    }

    /**
     * Helper method for Kill
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onKill(Matcher matcher) {

        // 0:00 Kill: 0 1 16: Fenix killed WizardOfGore by UT_MOD_SPAS
        // 0:00 Kill: 14 4 21: Fenix killed Fapking by UT_MOD_PSG1

        try {

            int vslot = Integer.parseInt(matcher.group("victim"));
            int aslot = Integer.parseInt(matcher.group("attacker"));
            int wcode = Integer.parseInt(matcher.group("weapon"));

            Client vclient = this.clientCtl.getBySlot(vslot);
            Client aclient = this.clientCtl.getBySlot(aslot);

            checkNotNull(aclient, "could not retrieve attacker client on slot %s", aslot);
            checkNotNull(vclient, "could not retrieve victim client on slot %s", vslot);

            Mod mod = this.getModByKillCode(wcode);

            switch (mod) {

            case MOD_CHANGE_TEAM:
                // Team change is detected as a client kill
                // However since it's not a proper player death
                // we'll handle it somewhere else and exit here
                return;

            case MOD_WATER:
            case MOD_SLIME:
            case MOD_LAVA:
            case MOD_CRUSH:
            case MOD_FALLING:
            case MOD_SUICIDE:
            case MOD_TRIGGER_HURT:
            case UT_MOD_SPLODED:

                this.eventBus.put(new ClientKillSelfEvent(vclient, mod));
                this.log.trace("[EVENT] ClientKillSelfEvent [ victim : " + vclient.getSlot() + " | mod : "
                        + mod.name() + " ]");

                break;

            default:

                if ((aclient == vclient) && (aclient.getTeam() != Team.SPECTATOR)) {

                    this.eventBus.put(new ClientKillSelfEvent(vclient, mod));
                    this.log.trace("[EVENT] ClientKillSelfEvent [ victim : " + vclient.getSlot() + " | mod : "
                            + mod.name() + " ]");

                } else if ((aclient.getTeam() == vclient.getTeam()) && (aclient.getTeam() != Team.SPECTATOR)
                        && (aclient.getTeam() != Team.FREE)) {

                    this.eventBus.put(new ClientKillTeamEvent(aclient, vclient, mod));
                    this.log.trace("[EVENT] ClientKillTeamEvent [ victim : " + vclient.getSlot() + " | attacker : "
                            + aclient.getSlot() + " | mod : " + mod.name() + " ]");

                } else {

                    this.eventBus.put(new ClientKillEvent(aclient, vclient, mod));
                    this.log.trace("[EVENT] ClientKillEvent [ victim : " + vclient.getSlot() + " | attacker : "
                            + aclient.getSlot() + " | mod : " + mod.name() + " ]");

                }

                break;

            }

        } catch (NullPointerException | IndexOutOfBoundsException | InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] ClientKillSelfEvent | ClientKillTeamEvent | ClientKillEvent", e);

        }

    }

    /**
     * Helper method for Radio
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onRadio(Matcher matcher) {

        // 0:00 Radio: 3 - 9 - 7 - "1. Off With their Head!" - "Oh, you idiot"
        // 0:00 Radio: 6 - 5 - 1 - "Challenge: No Save" - "Enemy spotted at Challenge: No Save"

        try {

            int slot = Integer.parseInt(matcher.group("slot"));
            Client client = this.clientCtl.removeBySlot(slot);

            // Check to have a proper client object before the event generation
            checkNotNull(client, "could not retrieve client on slot %s", slot);

            int msg_group = Integer.parseInt(matcher.group("group"));
            int msg_id = Integer.parseInt(matcher.group("id"));
            String location = matcher.group("location");
            String message = matcher.group("message");

            this.eventBus.put(new ClientRadioEvent(client, msg_group, msg_id, location, message));
            this.log.trace("[EVENT] ClientRadioEvent [ client : " + client.getSlot() + " | msg_group : " + msg_group
                    + " | msg_id : " + msg_id + " | location : " + location + " | message : " + message + " ]");

        } catch (NullPointerException | InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] ClientRadioEvent", e);

        }

    }

    /**
     * Helper method for Say
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onSay(Matcher matcher) {

        // 0:00 say: 8 denzel: lol
        // 0:00 say: 9 .:MS-T:.BstPL: this name is quite a challenge

        try {

            int slot = Integer.parseInt(matcher.group("slot"));
            Client client = this.clientCtl.removeBySlot(slot);

            if ((client == null) || (!client.getName().equals(matcher.group("name")))) {

                // Well known UrT bug          
                List<Client> collection = this.clientCtl.getByName(matcher.group("name"));

                if (collection.isEmpty() || collection.size() > 1)
                    throw new NullPointerException("could not retrieve client on slot " + slot);

                // Get the client
                client = collection.get(0);

            }

            // Get the said sentence
            String message = matcher.group("message").trim();

            if (message == null || message.isEmpty())
                throw new NullPointerException("could not retrieve message");

            // Check if an Orion command has been issued
            if ((message.startsWith("!")) || (message.startsWith("@")) || (message.startsWith("&"))) {

                // Say event matches command pattern
                this.onClientCommand(client, message);

            } else {

                // Normal client say event
                this.eventBus.put(new ClientSayEvent(client, message));
                this.log.trace(
                        "[EVENT] ClientSayEvent [ client : " + client.getSlot() + " | message : " + message + " ]");

            }

        } catch (NullPointerException | ExpectedParameterException | InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] ClientSayEvent", e);

        }

    }

    /**
     * Helper method for SayTell
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onSayTell(Matcher matcher) {

        // 0:00 saytell: 15 16 repelSteeltje: nno
        // 0:00 saytell: 4 7 Fenix: ahahahh it's cool isn't it?

        try {

            int cslot = Integer.parseInt(matcher.group("slot"));
            int tslot = Integer.parseInt(matcher.group("target"));
            Client client = this.clientCtl.removeBySlot(cslot);
            Client target = this.clientCtl.removeBySlot(tslot);

            if ((client == null) || (!client.getName().equals(matcher.group("name")))) {

                // Well known UrT bug          
                List<Client> collection = this.clientCtl.getByName(matcher.group("name"));

                if (collection.isEmpty() || collection.size() > 1)
                    throw new NullPointerException("could not retrieve client on slot " + cslot);

                // Get the client
                client = collection.get(0);

            }

            // Check to have a proper client object before the event generation
            checkNotNull(client, "could not retrieve target client on slot %s", tslot);

            // Get the said sentence
            String message = matcher.group("message").trim();

            if (message == null || message.isEmpty())
                throw new NullPointerException("could not retrieve message");

            // Check if an Orion command has been issued
            if ((message.startsWith("!")) || (message.startsWith("@")) || (message.startsWith("&"))) {

                // Say event matches command pattern
                this.onClientCommand(client, message);

            } else {

                // Normal client say private event
                this.eventBus.put(new ClientSayPrivateEvent(client, target, message));
                this.log.trace("[EVENT] ClientSayPrivateEvent [ client : " + client.getSlot() + " | target : "
                        + target.getSlot() + " | message : " + message + " ]");

            }

        } catch (NullPointerException | ExpectedParameterException | InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] ClientSayPrivateEvent", e);

        }

    }

    /**
     * Helper method for SayTeam
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onSayTeam(Matcher matcher) {

        // 0:00 sayteam: 8 denzel: lol
        // 0:00 sayteam: 9 .:MS-T:.BstPL: this name is quite a challenge

        try {

            int slot = Integer.parseInt(matcher.group("slot"));
            Client client = this.clientCtl.removeBySlot(slot);

            if ((client == null) || (!client.getName().equals(matcher.group("name")))) {

                // Well known UrT bug          
                List<Client> collection = this.clientCtl.getByName(matcher.group("name"));

                if (collection.isEmpty() || collection.size() > 1)
                    throw new NullPointerException("could not retrieve client on slot " + slot);

                // Get the client
                client = collection.get(0);

            }

            // Get the said sentence
            String message = matcher.group("message").trim();

            if (message == null || message.isEmpty())
                throw new NullPointerException("could not retrieve message");

            // Check if an Orion command has been issued
            if ((message.startsWith("!")) || (message.startsWith("@")) || (message.startsWith("&"))) {

                // Say event matches command pattern
                this.onClientCommand(client, message);

            } else {

                // Normal client say team event
                this.eventBus.put(new ClientSayTeamEvent(client, message));
                this.log.trace("[EVENT] ClientSayTeamEvent [ client : " + client.getSlot() + " | message : "
                        + message + " ]");

            }

        } catch (NullPointerException | ExpectedParameterException | InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] ClientSayTeamEvent", e);

        }

    }

    /**
     * Helper method for ShutdownGame
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onShutdownGame(Matcher matcher) {

        // 0:00 ShutdownGame:
        // 0:00 ShutdownGame:

        try {

            this.eventBus.put(new GameExitEvent());
            this.log.trace("[EVENT] GameExitEvent");

        } catch (InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] GameExitEvent", e);

        }

    }

    /**
     * Helper method for SurvivorWinner
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onSurvivorWinner(Matcher matcher) {

        // 0:00 SurvivorWinner: Red
        // 0:00 SurvivorWinner: 1

        if (matcher.group("data").matches("\\d+")) {

            try {

                int slot = Integer.parseInt(matcher.group("data"));
                Client client = this.clientCtl.getBySlot(slot);

                // Check to have a proper client object before the event generation
                checkNotNull(client, "could not retrieve client on slot %s", slot);

                this.eventBus.put(new SurvivorWinnerEvent(client));
                this.log.trace("[EVENT] SurvivorWinnerEvent [ client : " + client.getSlot() + " ]");

            } catch (NullPointerException | InterruptedException e) {

                // Logging the Exception
                this.log.error("[EVENT] SurvivorWinnerEvent", e);

            }

        } else {

            try {

                Team team = this.getTeamByName(matcher.group("data"));
                this.eventBus.put(new TeamSurvivorWinnerEvent(team));
                this.log.trace("[EVENT] TeamSurvivorWinnerEvent [ team : " + team.name() + " ]");

            } catch (IndexOutOfBoundsException | InterruptedException e) {

                // Logging the Exception
                this.log.error("[EVENT] TeamSurvivorWinnerEvent", e);

            }

        }

    }

    /**
     * Helper method for Vote
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onVote(Matcher matcher) {

        // 0:00 Vote: 4 - 1
        // 0:00 Vote: 3 - 2

        try {

            int slot = Integer.parseInt(matcher.group("data"));
            int data = Integer.parseInt(matcher.group("data"));
            Client client = this.clientCtl.getBySlot(slot);

            // Check to have a proper client object before the event generation
            checkNotNull(client, "could not retrieve client on slot %s", slot);

            this.eventBus.put(new ClientVoteEvent(client, data));
            this.log.trace("[EVENT] ClientVoteEvent [ client : " + client.getSlot() + " | data : " + data + " ]");

        } catch (NullPointerException | InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] ClientVoteEvent", e);

        }

    }

    /**
     * Helper method for Warmup
     * 
     * @author Daniele Pantaleone
     * @param  matcher A <tt>Matcher</tt> object matching the log line
     **/
    public void onWarmup(Matcher matcher) {

        // 0:00 Warmup:
        // 0:00 Warmup:

        try {

            this.eventBus.put(new GameWarmupEvent());
            this.log.trace("[EVENT] GameWarmupEvent");

        } catch (InterruptedException e) {

            // Logging the Exception
            this.log.error("[EVENT] GameWarmupEvent", e);

        }

    }

    ////////////////////////
    // END HELPER METHODS //
    ////////////////////////

    /**
     * Authenticate a client by matching his FSA
     * 
     * @author Daniele Pantaleone
     * @param  slot The connecting <tt>Client</tt> slot
     * @param  userinfo The parsed <tt>Client</tt> userinfo
     * @return An initialized <tt>Client</tt> object
     **/
    public Client authByFsa(int slot, Map<String, String> userinfo) {

        try {

            Cvar authEnable = this.cvarList.get("auth_enable");
            Cvar authOwners = this.cvarList.get("auth_owners");

            if (authEnable == null || !authEnable.getBoolean() || authOwners == null)
                throw new AuthenticationException("auth system is not configured properly");

            try {

                // retrieving auth informations using the game console
                String result = this.console.write("auth-whois " + slot, true);

                Pattern pattern = Pattern.compile(
                        "^auth:\\s*id:\\s*(?<slot>\\d+)\\s*-\\s*name:\\s*(?<name>\\w+)\\s*-\\s*login:\\s*(?<account>\\w*)\\s*-\\s*notoriety:\\s*(?<notoriety>.*)\\s*-\\s*level:\\s*(?<level>\\d+)\\s*-\\s*(?<rank>.*)$",
                        Pattern.CASE_INSENSITIVE);
                Matcher matcher = pattern.matcher(result);

                if (!matcher.matches())
                    throw new ParserException("could not parse auth server response");

                if (matcher.group("account").isEmpty())
                    throw new AuthenticationException("client is not authed using the FrozenSand auth system");

                userinfo.put("auth_slot", matcher.group("slot"));
                userinfo.put("auth_name", matcher.group("name"));
                userinfo.put("auth_account", matcher.group("account"));
                userinfo.put("auth_notoriety", matcher.group("notoriety"));
                userinfo.put("auth_level", matcher.group("level"));
                userinfo.put("auth_rank", matcher.group("rank"));

                // lookup the client in the database using the FSA account login
                Client client = this.clientCtl.getByAuth(userinfo.get("auth_account"));
                checkNotNull(client, "no match found in the storage for FSA: %s", userinfo.get("auth_account"));

                this.log.debug(
                        "Client connecting on slot " + slot + "authenticated using FSA: " + client.getAuth());
                this.log.trace("Welcome back " + client.getName() + " (@" + client.getId() + ") - level: "
                        + client.getGroup().getName());

                // double check auth/guid consistency
                if (!client.getGuid().equals(userinfo.get("cl_guid"))) {
                    this.log.debug("Detected GUID mismatch for client @" + client.getId() + ": " + client.getGuid()
                            + " != " + userinfo.get("cl_guid"));
                    this.log.debug("Updating client @" + client.getId() + " GUID with the most recent one: "
                            + userinfo.get("cl_guid"));
                    client.setGuid(userinfo.get("cl_guid"));
                }

                return client;

            } catch (RconException | ParserException | NullPointerException | ClassNotFoundException
                    | UnknownHostException | SQLException e) {

                // throw our custom exception
                throw new AuthenticationException(e);

            }

        } catch (AuthenticationException e) {

            this.log.debug("Could not authenticate client connecting on slot " + slot + " using FSA", e);
            return null;

        }

    }

    /**
     * Authenticate a client by matching his GUID
     * 
     * @author Daniele Pantaleone
     * @param  slot The connecting <tt>Client</tt> slot
     * @param  userinfo The parsed <tt>Client</tt> userinfo
     * @return An initialized <tt>Client</tt> object
     **/
    public Client authByGuid(int slot, Map<String, String> userinfo) {

        try {

            try {

                Client client = this.clientCtl.getByGuid(userinfo.get("cl_guid"));
                checkNotNull(client, "no match found in the storage for GUID: %s", userinfo.get("cl_guid"));

                this.log.debug(
                        "Client connecting on slot " + slot + "authenticated using GUID: " + client.getGuid());
                this.log.trace("Welcome back " + client.getName() + " (@" + client.getId() + ") - level: "
                        + client.getGroup().getName());

                if (userinfo.containsKey("auth_account")) {
                    this.log.debug("Updating client @" + client.getId() + " AUTH login using FSA: "
                            + userinfo.get("auth_account"));
                    client.setAuth(userinfo.get("auth_account"));
                }

                return client;

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

                // throw our custom exception
                throw new AuthenticationException(e);

            }

        } catch (AuthenticationException e) {

            this.log.debug("Could not authenticate client connecting on slot " + slot + " using GUID", e);
            return null;

        }

    }

    /**
     * Return a <tt>Map</tt> containing the infostring (stored as <tt>key|value</tt>)<br>
     * InfoString format: \ip\110.143.73.144:27960\challenge\1052098110\qport\51418\protocol\68...
     * 
     * @author Daniele Pantaleone
     * @param  info The infostring to be parsed 
     * @return A <tt>Map</tt> containing the infostring (stored as <tt>key|value</tt>)
     **/
    public Map<String, String> parseInfoString(String info) {

        Map<String, String> userinfo = new HashMap<String, String>();

        if (info.charAt(0) == '\\')
            info = info.substring(1);

        String lines[] = info.split("\\\\");

        for (int i = 0; i < lines.length; i += 2)
            userinfo.put(lines[i].toLowerCase(), lines[i + 1]);

        return userinfo;

    }

    /**
     * Parse a log line.Will generate an <tt>Event</tt> 
     * if necessary and push it in the <tt>Event</tt> bus
     * 
     * @author Daniele Pantaleone
     * @param  line A log line
     **/
    public void parseLine(String line) {

        Matcher matcher = null;

        // iterating through all the patters trying to find a match
        for (Map.Entry<String, Pattern> entry : patterns.entrySet()) {

            // getting a matcher for the current line
            matcher = entry.getValue().matcher(line);
            if (!matcher.matches())
                continue;

            try {

                this.getClass().getMethod("on" + entry.getKey(), Matcher.class).invoke(this, matcher);

            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
                    | SecurityException | NoSuchMethodException e) {

                this.log.error(e.getCause());

            }

            break;

        }

    }

}