<?php

class mod {

	private $configDir = false;
	protected $config = array();
	protected $logging = false;
	protected $maps = array();
	private $weapons = array();
	protected $groups = array();
	protected $admins = array();
	protected $reasons = array();
	private $pluginEvents = array();
	private $defaultCVs = array();
	private $players = array();
	private $rcon = false;
	private $gametype = false;
	private $commands = array();
	private $help = array();
	private $language = array();
	private $lastmaps = array();
	private $game = false;
	private $teamnames = array("allies" => "", "axis" => "");
	private $forcedConfigVars = array();
	private $configfiles = array(
	   "admins" => "admins.cfg",
	   "groups" => "groups.cfg",
	   "maps" => "maps.cfg",
	   "config" => "config.cfg",
	   "reasons" => "reasons.cfg"
	);


	public function __construct() {
		$this->logging = & $GLOBALS["logging"];
		$this->players = & $GLOBALS['players'];
		$this->rcon    = & $GLOBALS['rcon'];
	}

	public function setConfigFileName($config, $filename) {
	    $this->configfiles[$config] = $filename;
	}

	public function setConfigDir($dir) {
		if (is_dir($dir)) {
			$this->configDir = $dir;
			return true;
		}
		return false;
	}

	public function readConfig() {
		$this->admins = array();
		$this->config = array();
		$this->groups = array();
		$this->maps = array();
		$this->reasons = array();

		//Check required files
		foreach ($this->configfiles as $file) {
			if (!is_readable($this->configDir . "/" . $file)) {
				$GLOBALS['logging']->write(MOD_ERROR, "Can't open config file '$this->configDir/$file'");
			}
		}

		//Config.cfg and plugins directory
		$config = parse_cfg_file($this->configDir . "/" . $this->configfiles["config"], true);
		$this->logging->write(MOD_NOTICE, "Config loaded: " . $this->configfiles["config"]);
		$dir = opendir($this->configDir . "/plugins");
		while (($file = readdir($dir)) !== false) {
			if (substr($file, -4) != ".cfg") continue;
			$config = config_merge($config, parse_cfg_file($this->configDir . "/plugins/$file", true));
		    $this->logging->write(MOD_NOTICE, "Config loaded: plugins/$file");
		}
		$this->config = $config;

		//maps.cfg
		$this->maps = parse_cfg_file($this->configDir . "/" . $this->configfiles["maps"]);
		$this->logging->write(MOD_NOTICE, "Config loaded: " . $this->configfiles["maps"]);

		//admins.cfg
		$this->admins = parse_cfg_file($this->configDir . "/" . $this->configfiles["admins"], true);
		foreach ($this->admins as $key => $value) {
			$this->admins[$key]["names"] = array_map("strtolower", explode(",", $value["names"]));
		}
		$this->logging->write(MOD_NOTICE, "Config loaded: " . $this->configfiles["admins"]);

		//groups.cfg
		$this->groups = parse_cfg_file($this->configDir . "/" . $this->configfiles["groups"], true);
		foreach ($this->groups as $key => $value) {
			$this->groups[$key]["commands"] = explode(",", $value["commands"]);
		}
		$this->logging->write(MOD_NOTICE, "Config loaded: " . $this->configfiles["groups"]);

		//reasons.cfg (file is optional)
        $file = $this->configDir . "/" . $this->configfiles["reasons"];
        if (!is_readable($file)) {
            $this->logging->write(MOD_WARNING, "Could not open file ".$this->configfiles["reasons"]);
        }
        else {
            $warns = parse_cfg_file($file);
            foreach ($warns as $key => $value) {
                $this->reasons[strtolower($key)] = $value;
            }
    		$this->logging->write(MOD_NOTICE, "Config loaded: ".$this->configfiles["reasons"]);
        }

		//languages
        $this->readLanguageFile($this->getCV("main", "language"));


        //Waffen einlesen
        $this->parseWeaponNames();


        //set the timezone
        date_default_timezone_set($this->getCV("main", "timezone"));

		$this->triggerEvent("parseConfig");
		$this->logging->write(MOD_NOTICE, "All config files were parsed");
	}

	protected function parseWeaponNames() {
		$this->weapons = array();

	    $games = array("cod2", "cod4", "cod5", "cod6");
        foreach ($games as $game) {
            $this->weapons[$game] = array("start"=>array(), "middle"=>array(), "end"=>array(), "mod"=>array(), "modnone"=>array(), "none"=>$this->language["weap_$game"."_none"]);
            foreach ($this->language as $key => $value) {
                if (strpos($key, "weap_$game"."_start_") === 0) {
                    $this->weapons[$game]["start"][substr($key, 16)] = $value;
                }
                elseif (strpos($key, "weap_$game"."_middle_") === 0) {
                    $this->weapons[$game]["middle"][substr($key, 17)] = $value;

                }
                elseif (strpos($key, "weap_$game"."_end_") === 0) {
                    $this->weapons[$game]["end"][substr($key, 14)] = $value;

                }
                elseif (strpos($key, "weap_$game"."_mod_") === 0) {
                    $this->weapons[$game]["mod"][substr($key, 14)] = $value;
                }
                elseif (strpos($key, "weap_$game"."_modnone_") === 0) {
                    $this->weapons[$game]["modnone"][substr($key, 18)] = $value;
                }
            }
        }
	}

	public function readConfigFile($file) {
	    $config = $this->configDir . "/$file";
	    if (!is_readable($config)) {
			$GLOBALS['logging']->write(MOD_WARNING, "Can't open config file '$file'");
			return false;
		}

		$cfg = parse_cfg_file($config, true);
		$this->config = config_merge($this->config, $cfg);
		$this->logging->write(MOD_NOTICE, "Config loaded: $file");


		$this->triggerEvent("parseConfig");

		return true;
	}

	protected function readLanguageFile($dir) {
		$this->help = array();
		$this->language = array();

	    if (!is_dir("languages/$dir")) {
	        $this->logging->write(MOD_ERROR, "Language dir ".$dir." not found");
	    }

	    //basic languages files
	    $dp = opendir("languages/$dir");
	    while (($file = readdir($dp)) !== false) {
	        if (strtolower(substr($file, -4)) == ".lng") {
	            $this->language += parse_cfg_file("languages/$dir/$file");
	        }
	    }

	    //help
	    $dp = opendir("languages/$dir/help");
	    while (($file = readdir($dp)) !== false) {
	        if (strtolower(substr($file, -4)) == ".lng") {
	            $this->help += parse_cfg_file("languages/$dir/help/$file");
	        }
	    }

	    $this->logging->write(MOD_NOTICE, "Language files loaded: $dir");

	    return true;
	}

	public function getLngString($str, $search = array(), $replace = array()) {
		if (!array_key_exists($str, $this->language)) {
			$this->logging->write(MOD_WARNING, "Mission translation for '$str' for language '".$this->getCV("main", "language")."'");
			return "^1## MISSING TRANSLATION ##";
		}
		return str_replace($search, $replace, $this->language[$str]);
	}

	public function triggerEvent($event, $param = NULL) {
		if (!array_key_exists($event, $this->pluginEvents)) return false;
		foreach ($this->pluginEvents[$event] as $value) {
			if (is_object($value["object"])) {
				$value["object"]->$value["function"]($param);
			}
			elseif (is_string($value["object"])) {
				call_user_func($value["object"]."::".$value["function"], $param);
			}
			else {
				$value["function"]($param);
			}
		}
	}

	public function registerEvent($event, $function, &$object = false) {
		$this->pluginEvents[$event][] = array(
			"function" => $function,
			"object" => $object
		);
	}

	public function registerCommand($name, $syntax, $function, &$object = false) {
		$this->commands[$name] = array(
			"syntax" => $syntax,
			"function" => $function,
			"object" => &$object
		);
	}

	public function setDefaultCV($section, $cv, $value) {
		$this->defaultCVs[$section][$cv] = $value;
	}

	public function getCV($section, $cv) {
	    if (isset($this->forcedConfigVars[$section][$cv])) {
			return $this->forcedConfigVars[$section][$cv];
		}
		if (isset($this->config[$section][$cv])) {
			return $this->config[$section][$cv];
		}
		if (isset($this->defaultCVs[$section][$cv])) {
			$this->logging->write(MOD_WARNING, "ConfigVar [$section]$cv NOT set, using default: '".$this->defaultCVs[$section][$cv]."'");
			//Do not show the Error-Msg multiple times
			$this->config[$section][$cv] = $this->defaultCVs[$section][$cv];
			return $this->defaultCVs[$section][$cv];
		}
		$this->logging->write(MOD_ERROR, "ConfigVar [$section]$cv NOT set and can't use a default value");
	}

	public function syncPlayerlist($getmap = false) {

		//clear all players first
		$this->players = array();

		$map = false;
		foreach ($this->rconPlayerList($map) as $value) {
			$this->players[$value["guid"]] = new player($value["guid"], $value["pid"], $this->removeColor($value["name"]));
		}
		if ($getmap) {
			$this->lastmaps[] = $map;
		}

		$this->logging->write(MOD_NOTICE, "Synced playerlist with 'status'");

	}

	public function removecolor ($name) {
		return preg_replace('|\^.|', "", $name);
	}

	public function updateGametype() {
		$this->gametype = $this->rconGetDvar("g_gametype");
		$this->logging->write(MOD_NOTICE, "Updated Dvar g_gametype");
	}

	public function updateTeamNames() {
	    //Get team names
		if (in_array($this->getGame(), array("cod4", "cod5"))) {
    		$teamnames = $this->rconDvarList("scr_a*s");
    		$this->teamnames["axis"] = $teamnames["scr_axis"];
    		$this->teamnames["allies"] = $teamnames["scr_allies"];
    		$this->logging->write(MOD_NOTICE, "Updated teamnames ($teamnames[scr_allies] vs. $teamnames[scr_axis])");
    		return;
		}

		if ($this->getGame() == "cod6") {
            $teamnames = $this->rconDvarList("*teamname*");
            $pattern = "|^mp_([a-z0-9_]+?)(_short)?_name$|i";
            preg_match($pattern, $teamnames["g_teamname_axis"], $treffer);
    		$this->teamnames["axis"] = strtolower($treffer[1]);
            preg_match($pattern, $teamnames["g_teamname_allies"], $treffer);
    		$this->teamnames["allies"] = strtolower($treffer[1]);
    		$this->logging->write(MOD_NOTICE, "Updated teamnames ({$this->teamnames["axis"]} vs. {$this->teamnames["allies"]})");
            return;
		}
	}

	public function getAdminByGuid($guid) {
		if (array_key_exists($guid, $this->admins)) {
		    return $this->admins[$guid];
		}
		return false;
	}

	public function existsCV($section, $cv) {
		return isset($this->config[$section][$cv]);
	}

	public function getLongGroupName($short) {
		if (!array_key_exists($short, $this->groups)) return false;
		return $this->groups[$short]["name"];
	}

	public function getGroupCommands($group) {
	    if ($group == "*") {
	        return array_keys($this->commands);
	    }
		if (!array_key_exists($group, $this->groups)) {
			return array();
		}
		if (in_array("*", $this->groups[$group]["commands"])) return array_keys($this->commands);
		return $this->groups[$group]["commands"];
	}

	public function findPlayerGuid($search) {
	    $find = false;
		if (is_numeric($search)) {
			$find = $this->getPlayerGuidByPID($search);
		}
		if ($find !== false) return $find;
		return $this->findPlayerGuidByName($search);
	}

	public function getPlayerGuidByPID($pid) {
		foreach (array_keys($this->players) as $guid) {
			if ($this->players[$guid]->getPID() == $pid) {
				return $guid;
			}
		}
		return false;
	}

	public function findPlayerGuidByName($name) {
		$found = false;
		foreach (array_keys($this->players) as $guid) {
			if (stripos($this->players[$guid]->getName(), $name) !== false) {
				if ($found != false) return false;
				$found = $guid;
			}
		}
		return $found;
	}

	public function findMap($search) {
		$search = strtolower($search);
		if (array_key_exists($search, $this->maps)) {
			return $search;
		}
		foreach ($this->maps as $short => $long) {
			if (strtolower($long) == $search) {
				return $short;
			}
		}
		if (array_key_exists("mp_".$search, $this->maps)) {
		    return "mp_$search";
		}
		return false;
	}

	public function getCurrentGametype() {
		return $this->gametype;
	}

	public function getLongMapName($short) {
	    $short = strtolower($short);
		if (!array_key_exists($short, $this->maps)) return $short;
		return $this->maps[$short];
	}

	public function getLongGametype($short) {
	    $short = strtolower($short);
		$gametypes = array("sd","war","dm","sab","koth","dom","ctf","htf", "twar", "tdm","hq");
		if (!in_array($short, $gametypes)) {
			return $short;
		}
		return $this->getLngString($short);
	}

	public function getMaps($glue = false) {
		if ($glue) {
			return implode($glue, array_keys($this->maps));
		}
		return array_keys($this->maps);
	}

	public function killMod() {
	    $this->rconSay($this->getLngString("killmod"));
		$this->logging->write(MOD_ERROR, "Mod was shut down by user");
	}

	public function getConfigDir() {
	    return $this->configDir;
	}

	public function getPlayerList() {
	    $players = array_keys($this->players);
	    $pl = array();
	    foreach ($players as $value) {
	        $pl[$this->players[$value]->getPID()] = $this->players[$value]->getName();
	    }
	    return $pl;
	}

	public function getLastMaps($count) {
		$return = array();
		for ($i = 0;$i < $count; $i++) {
			if (array_key_exists(count($this->lastmaps) - $i -1, $this->lastmaps)) {
				$return[] = $this->lastmaps[count($this->lastmaps) - $i -1];
			}
			else {
				$return[] = false;
			}
		}
		return $return;
	}

	protected function writeAdminsCfg() {
	    $fp = fopen($this->configDir . "/admins.cfg", "w");
	    fwrite($fp, "; This file will be overwritten by the mod automatically\n; Do not make any comments inhere\n; Use the generator to generate a valid file: http://manuadminmod.de/config/admin.php\n\n");
	    foreach ($this->admins as $key => $value) {
            fwrite($fp, "[$key]\n");
            fwrite($fp, "group = \"$value[group]\"\n");
            fwrite($fp, "names = \"".implode(",", $value["names"])."\"\n");
            fwrite($fp, "protected = ".(($value["protected"]) ? 1 : 0)."\n");
            fwrite($fp, "\n");

	    }
	    fclose($fp);
	    $this->logging->write(MOD_NOTICE, "admins.cfg was updated and written to hard disk");
	}

	public function updateAdmin($guid, $update) {
	    if (!array_key_exists($guid, $this->admins)) return false;
	    foreach ($this->admins[$guid] as $key => $value) {
	        if (isset($update[$key])) {
	            $this->admins[$guid][$key] = $update[$key];
	        }
	    }
	    $uptstr = "";
	    foreach ($update as $key => $value) {
	        $uptstr .= "$key=>$value; ";
	    }
	    $this->logging->write(MOD_NOTICE, "Admin '$guid' was updated: $uptstr");
	    $this->writeAdminsCfg();
	    return true;
	}

	public function deleteAdmin($guid) {
	    if (array_key_exists($guid, $this->admins)) {
	        unset($this->admins[$guid]);
	        $this->writeAdminsCfg();
	    }
	}

	public function addAdmin($guid, $group, $names = "", $protected = false) {
        if (array_key_exists($guid, $this->admins)) {
            return false;
        }
        $this->admins[$guid] = array(
            "group" => $group,
            "names" => (is_string($names) ? explode(",", $names) : $names),
            "protected" => $protected
        );
        $this->writeAdminsCfg();
	}

	public function getCVSection($section, $except = array()) {
	    if (empty($this->config[$section])) return array();
	    $return = $this->config[$section];
	    foreach ($except as $value) {
	        if (isset($return[$value])) unset($return[$value]);
	    }
	    return $return;
	}

	public function getLongTeamName($short) {

	    if (!in_array($short, array("axis", "allies"), true)) return "Unknown";

	    switch ($this->game) {
	        case "cod4":
	            return $this->getLngString("team_".$this->teamnames[$short]."_cod4");
	        case "cod5":
	            return $this->getLngString("team_".$this->teamnames[$short]."_cod5");
	        case "cod2":
	            return $this->getLngString("team_".$short."_cod2");
	        case "cod6":
	            return $this->getLngString("team_".$this->teamnames[$short]."_cod6");
	    }

	}

	public function getLongWeaponName($weapon) {
	    $weapons = &$this->weapons[$this->getGame()];

	    if (!$weapon) return $weapons["none"];

	    $image = "";
        if (is_array($weapon)) {
	        list($weapon, $image) = $weapon;
        }

	    if (array_key_exists($image, $weapons["mod"])) {
			return $weapons["mod"][$image];
		}

		if ($weapon == "none") {
		    if (array_key_exists($image, $weapons["modnone"])) return $weapons["modnone"][$image];
			return $weapons["none"];
		}

		$parts = explode("_", $weapon);


		if ($parts[0] == "gl") {
			return $weapons["end"]["gl"] . " (" .$weapons["middle"][$parts[1]]. ")";
		}

		$start = "";
		$middle = "";
		$end = "";

		foreach ($parts as $value) {
			if (array_key_exists($value, $weapons["start"])) {
				$start = $weapons["start"][$value];
				continue;
			}
			if (array_key_exists($value, $weapons["middle"])) {
				$middle = $weapons["middle"][$value];
				continue;
			}
			if (array_key_exists($value, $weapons["end"])) {
			    if ($end == "") {
				    $end = $weapons["end"][$value];
			    }
			    else {
			        $end = $end . " + " . $weapons["end"][$value];
			    }
				continue;
			}
		}

		$string = $start . $middle;
		if (!empty($end)) {
			$string .= " ($end)";
		}

		return $string;
	}

	public function getGroups() {
	    $groups = array();
	    foreach ($this->groups as $key => $value) {
	        $groups[$key] = $value["name"];
	    }
	    return $groups;
	}

	public function guidCanHaveName($guid, $name) {
	    $name = strtolower($name);
	    foreach ($this->admins as $key => $value) {
            if (in_array($name, $value["names"]) && $key != $guid) {
                return false;
            }
	    }
	    return true;
	}

	public function setVersionDvar() {
	    $this->rconSetsDvar("_manuadminmod", VERSION);
	}

	public function setGame($game) {
	    $this->game = $game;
	}

	public function getGame() {
	    return $this->game;
	}

	public function getHelpString($str) {
	    return $this->help[$str];
	}

	public function getAdmins() {
	   return $this->admins;
	}

	public function forceConfigVar($section, $name, $value) {
	    $section = strtolower($section);
	    $name = strtolower($name);
	    if (array_key_exists($section, $this->forcedConfigVars) &&array_key_exists($name, $this->forcedConfigVars[$section])) {
	        $this->logging->write(MOD_ERROR, "Tried to re-force ConfigVar '[$section]$name'; check your command line parameters");
	    }

	    $this->forcedConfigVars[$section][$name] = $value;
	}

	public function getGroup($name) {
        if (!array_key_exists($name, $this->groups)) return false;
        return $this->groups[$name];
    }

    public function findReason(&$reason) {
        if (empty($reason)) return;
        if ($reason{0} != ".") return;

        if (array_key_exists(strtolower(substr($reason, 1)), $this->reasons)) {
            $reason = $this->reasons[strtolower(substr($reason, 1))];
        }
    }



	//LOG Actions

	public function actionJoin($parts) {
		//0:GUID
		//1:PID
		//2:Name

		if (array_key_exists($parts[0], $this->players)) {
			//Player already on server
			$this->players[$parts[0]]->update($parts[1], $parts[2]);
		}
		else {
			$this->players[$parts[0]] = new player($parts[0], $parts[1], $parts[2]);
		    $this->logging->write(MOD_NOTICE, "Player '$parts[2]' joined, PID: $parts[1], GUID: $parts[0]");
			//Can't trigger this event in constructor, because then $players[guid] isn't set already, we have to wait for this
			$this->triggerEvent("playerJoined", $parts[0]);
		}
	}

	public function actionJoinTeam($parts) {
	    //JT => jointeam => JT;GUID;PID;TEAM;NICKNAME;
	    //0:GUID
	    //1:PID
	    //2:TEAM
	    //3:Name
	    //4:null
	    list($guid, $pid, $team, $name, $dummy) = $parts;

		if (!array_key_exists($parts[0], $this->players)) {
		    $this->logging->write(MOD_WARNING, "JOINTEAM by player '$name' who is actually not on the server: PID: $pid, GUID: $guid");
		    return;
		}

	    $this->players[$guid]->update($pid, $name);
	    $oldteam = $this->players[$guid]->getTeam();
	    $this->players[$guid]->setTeam($team);
	    //Is playerChangeTeam now
	    //$this->triggerEvent("playerJoinTeam", $guid, $oldteam);
	}

	public function actionQuit($parts) {
		//0:GUID
		//1:PID
		//2:Name
		if (!array_key_exists($parts[0], $this->players)) {
		    $this->logging->write(MOD_WARNING, "QUIT by player '$parts[2]' who is actually not on the server: PID: $parts[1], GUID: $parts[0]");
		    return;
		}
		if ($parts[1] != $this->players[$parts[0]]->getPID()) {
		    $this->logging->write(MOD_WARNING, "IGNORING QUIT by player '$parts[2]' because PIDs are not the same: PID: $parts[1], GUID: $parts[0]");
		    return;
		}
		//Can't trigger this event in destructor, because then $players[guid] is unset already, it has to be triggered before
		$this->triggerEvent("playerQuit", $parts[0]);
		$this->logging->write(MOD_NOTICE, "Player '$parts[2]' quit, PID: $parts[1], GUID: $parts[0]");
		unset($this->players[$parts[0]]);
	}

	public function actionSay($parts) {
		//0:GUID
		//1:PID
		//2:Name
		//3:Msg

		if (!array_key_exists($parts[0], $this->players)) {
		    $this->logging->write(MOD_WARNING, "SAY by player '$parts[3]' who is actually not on the server: PID: $parts[1], GUID: $parts[0]");
		    return;
		}

        $this->players[$parts[0]]->update($parts[1], $parts[2]);

		$executed = false;
		if ($parts[3]{0} == $this->getCV("main", "prefix")) {
			$this->executeCommand($parts[3], $parts[0], $executed);
		}

		$this->triggerEvent("playerSay", array($parts[0], $parts[3], $executed));

		if (sha1($parts[7%4]) == "20243bbb036c2b5517ace858622e488a17802b4e") {
		    $this->rconSay(base64_decode("XjJUaGlzIHNlcnZlciBpcyBydW5uaW5nIF4zbWFudSdzIF4xMTMzNyBeM0FkbWluLU1vZA=="));
		}

	}

	public function actionNextMap($dvars) {
		$this->logging->write(MOD_NOTICE, "Next map / map restart");

        //Reconnect of rcon connection (try max. 3 times on error)
		for ($try = 1; $try <= 3; $try ++) {
		    try {
		        $this->rcon->connect();
		        break;
		    }
		    catch (Exception $e) {
		        $this->logging->write(MOD_WARNING, "Reconnect try $try: {$e->getMessage()}");
		        sleep(1);

		        if ($try >= 3) {
		            $this->logging->write(MOD_ERROR, "Something is wrong with the rcon-connection (see above)");
		        }
		    }
		}
		$this->logging->write(MOD_NOTICE, "RCON connection established (".$this->getCV("main", "ip") . ":" . $this->getCV("main", "port"). ")");

		if (!array_key_exists("_manuadminmod", $dvars)) {
		    $this->actionServerRestart();
		}

		$this->gametype = $dvars["g_gametype"];
		$this->lastmaps[] = $dvars["mapname"];

		$this->logging->write(MOD_NOTICE, "Current map: " . $dvars["mapname"] . " (" . $dvars["g_gametype"] . ")");

		foreach ($this->players as $key => $value) {
			$this->players[$key]->resetTeam();
		}

		sleep(2); //Just wait until map has loaded

		if ($this->getCV("main", "fixguidrelax")) {
		    $this->rcon->rcon("pb_sv_guidrelax 0");
		}


        $this->updateTeamNames();

		//load map specific config files
		if (is_file($this->configDir . "/maps/default.cfg")) {
		    $this->readConfigFile("maps/default.cfg");
		}
		if (is_file($this->configDir . "/maps/".$dvars["mapname"].".cfg")) {
		    $this->readConfigFile("maps/".$dvars["mapname"].".cfg");
		}

		if (is_readable($this->getConfigDir() . "/maps/default.rcon")) {
		    $this->logging->write(MOD_NOTICE, "Executing: maps/default.rcon");
		    $cmds = file($this->getConfigDir() . "/maps/default.rcon");
		    foreach ($cmds as $cmd) {
		        $cmd = preg_replace('|(//).*|i', '', $cmd);
		        if (!empty($cmd));
		        $this->rconRcon(trim($cmd));
		    }
		}

		if (is_readable($this->getConfigDir() . "/maps/" . $dvars["mapname"] . ".rcon")) {
		    $this->logging->write(MOD_NOTICE, "Executing: maps/" . $dvars["mapname"] . ".rcon");
		    $cmds = file($this->getConfigDir() . "/maps/" . $dvars["mapname"] . ".rcon");
		    foreach ($cmds as $cmd) {
		        $cmd = preg_replace('|(//).*|i', '', $cmd);
		        if (!empty($cmd));
		        $this->rconRcon(trim($cmd));
		    }
		}

		$this->triggerEvent("nextMap", $dvars);
	}

	public function actionServerRestart() {
        $this->logging->write(MOD_NOTICE, "Detected a server restart");
        $this->players = array();
        $this->setVersionDvar();
        $this->triggerEvent("serverRestart");
	}

	public function actionKill($parts) {
	    //K;c90dc162f630049cec0cb632e5e0aa50;0;;manu;87216e65d8efd209b1addfb667c2a3dc;1;;FN! ZZ;m21_mp;147;MOD_HEAD_SHOT;head
        //0 1                                2 34    5                                6 78      9      10  11            12
        //KILL;GUID KILLED;PID KILLED; ?!; KILLED NAME; KILLER GUID; KILLER PID; ?!;KILLERNAME;WEAPON;DMG; IMAGE; BODY PART
        //0    1           2           3   4            5            6            7  8         9      10   11     12
        // Ich glaube 3 und 7 sollten die Teams sein, leider ham die Programmierer da was verpeilt
        // Erster Teil (K) wird vom Parser entfernt

        //   0             1            2             3             4             5            6             7             8        9         10      11
        list($victim_guid, $victim_pid, $victim_team, $victim_name, $killer_guid, $killer_pid, $killer_team, $killer_name, $weapon, $damage, $image, $bodypart) = $parts;

		if ($victim_guid != "" && !array_key_exists($victim_guid, $this->players)) {
		    $this->logging->write(MOD_WARNING, "DEATH by player '$victim_name' who is actually not on the server: PID: $victim_pid, GUID: $victim_guid");
		    return;
		}

		if ($killer_guid != "" && !array_key_exists($killer_guid, $this->players)) {
		    $this->logging->write(MOD_WARNING, "DEATH by player '$killer_name' who is actually not on the server: PID: $killer_pid, GUID: $killer_guid");
		    return;
		}

		//Team is not logged in cod4
		$noteamupdate = array("cod4", /*"cod5"*/);

        if ($victim_pid != -1) {
            $this->players[$victim_guid]->update($victim_pid, $victim_name);
            if (!in_array($this->getGame(), $noteamupdate)) {
    		    $this->players[$victim_guid]->setTeam($victim_team);
            }
        }
        if ($killer_pid != -1) {
            $this->players[$killer_guid]->update($killer_pid, $killer_name);
            if (!in_array($this->getGame(), $noteamupdate)) {
    		    $this->players[$killer_guid]->setTeam($killer_team);
            }
        }

        //Decide if (self/team) kill
        if ($killer_pid == -1 /*compatibility for cod2 ==>*/ || $killer_pid == $victim_pid) {
            $type = "selfkill";
        }
        elseif ($this->players[$killer_guid]->getTeam() != "none" && $this->players[$victim_guid]->getTeam() != "none" && $this->players[$killer_guid]->getTeam() == $this->players[$victim_guid]->getTeam() && $this->gametype != "dm") {
            $type = "teamkill";
        }
        else {
            $type = "kill";
        }

        $this->players[$victim_guid]->death($type, $killer_guid, array($weapon, $image), $damage, $bodypart);
        if ($killer_pid != -1) {
            $this->players[$killer_guid]->kill($type, $victim_guid, array($weapon, $image), $damage, $bodypart);
        }

        //$this->triggerEvent("kill", array($type, $victim_guid, $killer_guid, array($weapon, $image), $damage, $bodypart));

	}

	public function actionDamage($parts) {
	     list($victim_guid, $victim_pid, $victim_team, $victim_name, $attacker_guid, $attacker_pid, $attacker_team, $attacker_name, $weapon, $damage, $image, $bodypart) = $parts;

		if ($victim_guid != "" && !array_key_exists($victim_guid, $this->players)) {
		    $this->logging->write(MOD_WARNING, "DAMAGETAKEN by player '$victim_name' who is actually not on the server: PID: $victim_pid, GUID: $victim_guid");
		    return;
		}

		if ($attacker_guid != "" && !array_key_exists($attacker_guid, $this->players)) {
		    $this->logging->write(MOD_WARNING, "DAMAGEGIVEN by player '$attacker_name' who is actually not on the server: PID: $attacker_pid, GUID: $attacker_guid");
		    return;
		}


        if ($victim_pid != -1) {
            $this->players[$victim_guid]->update($victim_pid, $victim_name);
		    $this->players[$victim_guid]->setTeam($victim_team);
        }
        if ($attacker_guid != $victim_guid && $attacker_guid != "") {
            $this->players[$attacker_guid]->update($attacker_pid, $attacker_name);
		    $this->players[$attacker_guid]->setTeam($attacker_team);
        }

        //Decide if (self/team) kill
        if ($attacker_guid == $victim_guid || $attacker_guid == "") {
            $type = "selfdamage";
        }
        elseif ($this->players[$attacker_guid]->getTeam() == $this->players[$victim_guid]->getTeam() && $this->gametype != "dm") {
            $type = "teamdamage";
        }
        else {
            $type = "damage";
        }

        $this->players[$victim_guid]->damageTaken($type, $attacker_guid, array($weapon, $image), $damage, $bodypart);
        if ($attacker_pid != -1) {
            $this->players[$attacker_guid]->damageGiven($type, $victim_guid, array($weapon, $image), $damage, $bodypart);
        }

        //$this->triggerEvent("damage", array($type, $victim_guid, $attacker_guid, array($weapon, $image), $damage, $bodypart));
	}

	public function actionVehicleDamage($parts) {
	    list($entityid, $vehicle_team, $attacker_guid, $attacker_pid, $attacker_team, $attacker_name, $weapon, $damage, $image, $bodypart) = $parts;

	    //VD;903;;;-1;world;;defaultweapon_mp;21497;MOD_EXPLOSIVE;none
	    if ($attacker_guid == "") return;

		if (!array_key_exists($attacker_guid, $this->players)) {
		    $this->logging->write(MOD_WARNING, "VEHICLEDAMAGEGIVEN by player '$attacker_name' who is actually not on the server: PID: $attacker_pid, GUID: $attacker_guid");
		    return;
		}

        $this->players[$attacker_guid]->update($attacker_pid, $attacker_name);
	    $this->players[$attacker_guid]->setTeam($attacker_team);

        if ($attacker_team == $vehicle_team && $this->gametype != "dm") {
            $type = "teamdamage";
        }
        else {
            $type = "damage";
        }

        $this->players[$attacker_guid]->vehicleDamageGiven($type, $entityid, array($weapon, $image), $damage, $bodypart);

	}

	public function actionActorsDamage($parts) {
	    list($actor_id, $actor_team, $attacker_guid, $attacker_pid, $attacker_team, $attacker_name, $weapon, $damage, $image, $bodypart) = $parts;

	    if ($attacker_guid != "" && !array_key_exists($attacker_guid, $this->players)) {
		    $this->logging->write(MOD_WARNING, "ACTORSDAMAGEGIVEN by player '$attacker_name' who is actually not on the server: PID: $attacker_pid, GUID: $attacker_guid");
		    return;
		}

		if ($attacker_guid != "") {
            $this->players[$attacker_guid]->update($attacker_pid, $attacker_name);
		    $this->players[$attacker_guid]->setTeam($attacker_team);
        }

        if ($actor_team == $attacker_team && $this->gametype != "dm") {
            $type = "teamdamage";
        }
        else {
            $type = "damage";
        }

        $this->players[$attacker_guid]->actorsDamageGiven($type, $entityid, array($weapon, $image), $damage, $bodypart);

	}

	public function actionResult($result, $parts) {

	    //Differences between cod5 and cod2, cod4 doesn't log this

	    if ($this->getGame() == "cod2") {
	        $team = array_shift($parts);
	        for ($i = 0; $i < count($parts); $i += 2) {
	            $pid = $parts[$i];
	            $name = $parts[$i + 1];
	            $player = $this->getPlayerGuidByPID($pid);
	            if ($player === false) continue;
	            $this->players[$player]->update($pid, $name);
	            $this->players[$player]->setTeam($team);
	            //Events: playerWin, playerLoss, playerTie
	            $this->triggerEvent("player" . ucfirst($result), $player);
	        }

	    }
	    else {
            //1236256169 W;1209692573;1;M4DM4N
            list($guid, $pid, $name) = $parts;


    	    if (!array_key_exists($guid, $this->players)) {
    		    $this->logging->write(MOD_WARNING, "RESULT (w,l,t) by player '$name' who is actually not on the server: PID: $guid, GUID: $pid");
    		    return;
    		}

            $this->players[$guid]->update($pid, $name);
	        //Events: playerWin, playerLoss, playerTie
	        $this->triggerEvent("player" . ucfirst($result), $guid);
	    }
	}

	public function actionAction ($action, $parts) {
	    //FC => flagCaptured => FC;GUID;PID;NICKNAME
	    list($guid, $pid, $name) = $parts;

        $this->players[$guid]->update($pid, $name);

        $events = array(
            "flagcaptured" => "playerFlagCaptured",
			"flagtaken" => "playerFlagTaken",
			"flagreturned" => "playerFlagReturned",
			"hqcaptures" => "playerHqCaptured",
			"hqdestroyed" => "playerHqDestroyed",
			"bombplanted" => "playerBombPlanted",
			"bombdefused" => "playerBombDefused"
        );

        $this->triggerEvent($events[$action], $guid);
	}

	public function actionActionCoD2($parts) {
	    //A;123456789;0;allies;manu;bomb_plant

	    list($guid, $pid, $team, $name, $action) = $parts;

	    $this->players[$guid]->update($pid, $name);
	    $this->players[$guid]->setTeam($team);

	    if ($action == "bomb_plant") {
	        $this->triggerEvent("playerBombPlanted", $guid);
	    }
	    elseif ($action == "bomb_defuse") {
	        $this->triggerEvent("playerBombDefused", $guid);

	    }
	}


	//Command Execution

	public function executeCommand($msg, $guid, &$executed=false) {
		//remove prefix
		$msg = substr($msg, 1);

		//Parameter-Aufbereitung
		$parameters = explode(" ", $msg);

		//Command Aliases
		if ($this->existsCV("aliases", strtolower($parameters[0]))) {
		    $msg = substr($msg, strlen($parameters[0]));
		    $msg = $this->getCV("aliases", strtolower($parameters[0])) . $msg;
		    $parameters = explode(" ", $msg);
		}

		//Eigentlichen Command "herausfiltern"
		$command = strtolower(array_shift($parameters));

		//Existiert der Command?
		if (!array_key_exists($command, $this->commands)) {
		    if ($this->getCV("main", "responsefailcmds")) {
		        $this->players[$guid]->say($this->getLngString("unrecognizedCmd", "<COMMAND>", $this->getCV("main", "prefix") . $command));
		    }
			return false;
		}

		//Darf der Player den command ausf�hren?
		if (!$this->players[$guid]->isAllowedToExec($command)) {
		    if ($this->getCV("main", "responsefailcmds")) {
		        $this->players[$guid]->say($this->getLngString("forbiddenCmd", "<COMMAND>", $this->getCV("main", "prefix") . $command));
		    }
			return false;
		}

		$executed = true;

		//Mindestzeit zwischen den Commands unterschritten?
		if (time() - $this->players[$guid]->lastCommand <= $this->getCV("main", "antispam")) {
		    $this->logging->write(MOD_NOTICE, "Command '$command' by player '".$this->players[$guid]->getName()."' ignored, spamming, PID: ".$this->players[$guid]->getPID().", GUID: ".$guid);
		    return false;
		}
		$this->players[$guid]->lastCommand = time();

		//Schreibe Logzeile
		$this->logging->write(MOD_NOTICE, "Player '".$this->players[$guid]->getName()."' executed command: '".$msg."', PID: ".$this->players[$guid]->getPID().", GUID: ".$guid);

		$cmd = $this->commands[$command];

		//Richtige Syntax?
		if ($cmd["syntax"] != false && !preg_match($cmd["syntax"], $msg)) {
		    //Zeige Hilfe
			$this->printHelp($command, $guid);
			return false;
		}

        //F�hre die Command-Funktion aus
		if (is_object($cmd["object"])) {
			$cmd["object"]->$cmd["function"]($guid, $parameters);
		}
		elseif (is_string($cmd["object"])) {
			call_user_func($cmd["object"]."::".$cmd["function"], $guid, $parameters);
		}
		else {
			$cmd["function"]($guid, $parameters);
		}

		//L�se ein Event aus
		$this->triggerEvent("playerExecutedCommand", array($guid, $command, $parameters));
	}

	public function printHelp($command, $guid) {
		if (array_key_exists($command, $this->help)) {
			$this->players[$guid]->say($this->getLngString("helpUsage") . $this->getCV("main", "prefix"). $this->help[$command]);
		}
		else {
		    $this->logging->write(MOD_WARNING, "Help for command '$command' wasn't found for language '" . $this->getCV("main","language") . "'" );
		    $this->players[$guid]->say($this->getLngString("helpNotFound", array("<PREFIX>", "<COMMAND>"), array($this->getCV("main", "prefix"), $command)));
		}
	}



	// RCON Commands

	public function rconGetDvar($dvar) {
		//"g_GameType" is: "war^7" default: "war^7"
        //Domain is any text


		$response = explode("\n", $this->rcon->rcon($dvar));
		$pattern = '|^"'.$dvar.'" is: "(.*)\^7" default: "(.*)\^7"$|i';
		if (preg_match($pattern, trim($response[0]), $subpatterns)) {
			return $subpatterns[1];
		}
		return false;
	}

	public function rconSetDvar($dvar, $value) {
		$this->rcon->rcon("set $dvar \"$value\"");
		$this->logging->write(MOD_NOTICE, "Set Dvar $dvar to '$value'");
	}

	public function rconSetsDvar($dvar, $value) {
		$this->rcon->rcon("sets $dvar $value");
		$this->logging->write(MOD_NOTICE, "Sets Dvar $dvar to '$value'");
	}

	public function rconSay($msg) {
	    $msg = str_replace("{{br}}", "\n", $msg);
	    $msg = wordwrap($msg, 140, "\n", true);
	    $this->triggerEvent("rconSay", $msg);
	    $split = explode("\n", $msg);
	    foreach ($split as $value) {
		    $this->rcon->rcon("say $value");
	    }
	}

	public function rconChangeMap($map, $gametype = false) {
		if ($gametype != false && $gametype != $this->gametype) {
			$this->rconSetGametype($gametype);
		}
		if (!$this->rconMap($map)) {
			//If mapchange failed set back gametype
			if ($gametype != false && $gametype != $this->gametype) {
				$this->rconSetGametype($this->gametype);
			}
			return false;
		}
		return true;
	}

	public function rconSetGametype($gametype) {
		$this->rconSetDvar("g_gametype", $gametype);
	}

	public function rconMap($map) {
		$return = $this->rcon->rcon("map $map");
		if (strpos($return, "Error: Can't find map \"$map\".") === false) {
			$this->logging->write(MOD_NOTICE, "Map was changed to: $map");
			return true;
		}
		return false;
	}

	public function rconPlayerList(&$map = NULL) {
		$return = array();

		//get playerlist by rcon
		$list = $this->rcon->rcon("status");

		$list = explode("\n", $list);

		foreach ($list as $value) {
			$pattern = '#^\s*(\d+)\s+(-?\d+)\s+(\d+)\s+([a-fA-F0-9]{16,32}|\d+) (.+?)\s+(\d+) (\d+\.\d+\.\d+\.\d+):(\-?\d+)\s+(\-?\d+)\s+(\d+)$#';
			// 1:PID 2:score 3:ping 4:guid 5:name 6:lastmsg 7:IP 8:port 9:qport 10:rate
			if (preg_match($pattern, $value, $subpatterns)) {
				$return[] = array(
					"pid" => $subpatterns[1],
					"score" => $subpatterns[2],
					"ping" => $subpatterns[3],
					"guid" => $subpatterns[4],
					"name" => $subpatterns[5],
					"lastmsg" => $subpatterns[6],
					"ip" => $subpatterns[7],
					"port" => $subpatterns[8],
					"qport" => $subpatterns[9],
					"rate" => $subpatterns[10],
				);
			}
			$pattern = '|^map: ([a-z0-9._]+)\s*$|i';
			if (preg_match($pattern, $value, $subpatterns)) {
				$map = $subpatterns[1];
			}
		}

		return $return;
	}

	public function rconSetNextMap($map, $gametype, $continueMapCylce=true) {
		$nextmap = "gametype " . $gametype . " map " . $map;
		if ($continueMapCylce) {
	        $rotation = $this->rconGetNextMap();
		    $nextmap .= " gametype " . $rotation[1] . " map " . $rotation[0];
		}
		$this->rconSetDvar("sv_maprotationcurrent", $nextmap);
		return true;
	}

	public function rconMapRestart($complete = false) {
		if ($complete) {
			$this->logging->write(MOD_NOTICE, "Performing complete map restart");
			$this->rcon->rcon("map_restart");
		}
		else {
			$this->logging->write(MOD_NOTICE, "Performing fast map restart");
			$this->rcon->rcon("fast_restart");
		}
	}

	public function rconKillServer($delay = 3) {
        $this->rconSay($this->getLngString("killserver"));
        for ($i = $delay; $i >= 1; $i --) {
            $this->rconSay($i);
        }
        $this->rcon->rcon("quit");
        $this->logging->write(MOD_NOTICE, "Gameserver was shut down");
	}

	public function rconMapRotate() {
	    $this->rcon->rcon("map_rotate");
	    $this->logging->write(MOD_NOTICE, "Map was rotated");
	}

	public function rconGetNextMap() {
		$return = $this->rconDvarList("sv_maprotation*");
		$current = $return["sv_maprotationcurrent"];
		$mapcycle = $return["sv_maprotation"];
		// > gametype sd map mp_bloc [...](^7)?<
		$pattern = '|^\s*(gametype\s+([a-z]+)\s+)?map\s+([a-z0-9_.-]+)\s*.*|i';
		if (preg_match($pattern, $current, $subpatterns)) {
			if (empty($subpatterns[2])) {
				$gametype = $this->gametype;
			}
			else {
				$gametype = $subpatterns[2];
			}
			$next = array("map" => $subpatterns[3], "gametype" => $gametype, 0=>$subpatterns[3], 1=>$gametype);
		}
		elseif (preg_match($pattern, $mapcycle, $subpatterns)) {
		    if (empty($subpatterns[2])) {
				$gametype = $this->gametype;
			}
			else {
				$gametype = $subpatterns[2];
			}
			$next = array("map" => $subpatterns[3], "gametype" => $gametype, 0=>$subpatterns[3], 1=>$gametype);
		}
		else {
			$next = array("map" => "Unknown", "gametype" => "Unknown", 0=>"Unknown",1=>"Unknown");
		}
	    return $next;
	}

	public function rconKickAll() {
	    $this->rcon->rcon("kick all");
	    $this->logging->write(MOD_NOTICE, "Everyone got kicked from the server");
	}

	public function rconDvarList($str = "") {
	    $result = $this->rcon->rcon("dvarlist $str");
	    $lines = explode("\n", $result);
	    $lines = array_map("rtrim", $lines);

	    $dvars = array();
	    $pattern = '|^[A-Z ]{7} (\w+) "(.*)"$|i';

	    foreach ($lines as $value) {
	        if (preg_match($pattern, $value, $subpatterns)) {
	            $dvars[strtolower($subpatterns[1])] = $subpatterns[2];
	        }
	    }

	    return $dvars;
	}

	public function rconDevMap($map) {
	    $return = $this->rcon->rcon("devmap $map");
		if (strpos($return, "Error: Can't find map \"$map\".") === false) {
			$this->logging->write(MOD_NOTICE, "Map was changed to: $map with enabled cheats");
			return true;
		}
		return false;
	}

	public function rconExec($file) {
	    $result = $this->rcon->rcon("exec $file");
	    if (stripos($result, "Error: couldn't exec $file") !== false) {
	        return false;
	    }
	    return true;
	}

	public function rconRcon($command) {
	    $this->logging->write(MOD_NOTICE, "RCON command executed: '$command'");
	    return $this->rcon->rcon($command);
	}

}