<?php
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *     Name: plugin_manager.php                                        *
 *  Created: Wednesday, 8th October 2008 (08/10/2008)                  *
 * Modified: Friday, 16th July 2010 (16/07/2010)                       *
 *   Author: DedDerek (http://www.dedderek.org/)                       *
 *  Support: www.tm-forum.com/viewforum.php?f=131, geryasd@dedderek.org*
 *  Version: 0.23                                                      *
 *  Purpose: Provides a framework for further plugin creation          *
 *           without modification of the tm_gery.php file.             *
 *    Usage: Place the following line(s) in the plugin file:           *
 * $this->AddPlugin(new [classname]);                                  *
 * $this->AddEvent('svraction', 'returnfunction');                     *
 * $this->AddChatCommand('chat', 'inhelp' ,'returnfunction');          *
 * ------------------------------------------------------------------- *
 * The AddPlugin line must be above plugin events and [classname] must *
 * contain the name of the class in which the plugin will run.         *
 * Syntax for class myClass1: $this->AddPlugin(new myClass1);          *
 *                                                                     *
 * AddEvent calls add functions to the script which will be called back*
 * via the trackmania game upon actions such as endmap and playerfinish*
 * The svraction field must be one of the following trackmania calls:  *
 * - onStartup (called when gery boots up                              *
 * - PlayerConnect (called upon a player connecting to the game)       *
 * - PlayerDisconnect (called upon a player disconnecting from the game*
 * - PlayerCheckpoint (called when a player passes a checkpoint)       *
 * - PlayerFinish (called when a player finishes or resets)            *
 *   This function is invalid in Stunts,Cup and timeattack mode as     *
 *   all players will 'finish'                                         *
 * - StatusChanged (called when the server changes mode - end of round,*
 *   beginning of round etc)                                           *
 * - BeginRace (called when the race begins)                           *
 * - BeginRound (rounds mode only, called when a new round begins)     *
 * - EndRace (called upon the end of a race)                           *
 * - PlayerChat (called when a player (or server) sends a chat msg)    *
 * - PlayerInfoChanged (end of round and when players switch to/from   *
 *   spectator mode                                                    *
 * - PlayerManialinkPageAnswer (manialink answer events)               *
 * - BillUpdated (called on coppers transaction)                       *
 * Exampe: Syntax for action onStartup for function mytest1:           *
 * $this->AddEvent('onStartup', 'mytest1');                            *
 *                                                                     *
 * AddChatCommand('chat', 'inhelp', 'retfunc');                        *
 * The returnfunction variable will contain the info required for the  *
 * chat call to return to the appropriate class function.              *
 * chat = the command (without / and before spaces) the player uses    *
 * inhelp = display this command in the /help list?                    *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

//initialize the pluginmanager when included from tm_gery.php
$plugins = new plugin_manager();

//this class will manage the plugin_manager
class plugin_manager
{
	private $events; //where the callback functions are kept
	private $currentclass; //the current class being worked on by the system
	private $classes; //an array of class information mapped to the registering class name for future use
	private $chatcommands; //an array of custom chat commands & their mapped class and function information
	private $chathelpcount; //count how many help chat commands we have added so we can new line every 6
	public $plugininfo; //information about each plugin is stored here (for future use)
	public $version = '0.23'; //version of this script

	//initialize the plugin_manager by setting up some arrays
	public function initpluginmanager(){
		global $client, $messages;
		$this->events = array();
		$this->events['onStartup'] = array();
		$this->events['onMainLoop'] = array();
		$this->events['PlayerConnect'] = array();
		$this->events['PlayerDisconnect'] = array();
		$this->events['PlayerCheckpoint'] = array();
		$this->events['PlayerFinish'] = array();
		$this->events['PlayerChat'] = array();
		$this->events['PlayerInfoChanged'] = array();
		$this->events['PlayerManialinkPageAnswer'] = array();
		$this->events['StatusChanged'] = array();
		$this->events['BeginRace'] = array();
		$this->events['BeginRound'] = array();
		$this->events['EndRace'] = array();
		$this->events['BillUpdated'] = array();
		$this->chatcommands[] = array();
		$this->chathelpcount = 0;
		//setup some space for the help messages:
		$messages['help'] .= "\n\r";
		
		//tell everyone of my awesomeness :P
		$msg = 'TMGery Plugin Manager '.$this->version.' by DedDerek!';
		echo $msg."\n";
//		$client->query('ChatSendServerMessage', '$ff0>> '.$msg);
		$this->loadplugins();
	}
	
	//load the plugins file (if exists) and check the plugins listed exist then include them
	private function loadplugins()
	{
		$file = 'plugins.txt';
		if(file_exists($file))
		{
			//parse the file line by line
			$handle = fopen($file,'r');
			echo '[PLUGINMAN] Loaded plugins.txt file.'."\n";
			while (!feof($handle))
			{
				$path = fgets($handle);
				$path = trim($path);
				//check the plugin path on this line exits
				if(file_exists($path))
				{
					echo '[PLUGINMAN] Load plugin: '.$path."\n";
					//include the plugin and load it
					include $path;
				}else{
					echo '[PLUGINMAN] Plugin file: '.$path.' DOES NOT EXIST!'."\n";
				}
			}
			fclose($handle);
		}else{
			echo '[PLUGINMAN] No plugins.txt file found to load!'."\n";
		}
	}
	
	//add a plugin to the system (register the class event for the AddEvent and AddChatCommand calls)
	public function addPlugin($class)
	{
		$classname = get_class($class);
		$this->currentclass = array('name'=>$classname, 'object'=>$class);
		$this->classes[$classname] = $class;
	}

	//this function is called to create the event callbacks for this plugin
	public function AddEvent($name, $returnfunction)
	{
		if(is_array($this->events[$name]))
		{
			//check the class/function exists before adding it
			if(method_exists($this->currentclass['name'], $returnfunction)){
				$this->events[$name][] = array($this->currentclass['name'], $returnfunction);
			}else{
				$backtrace = $this->getFunctionName();
				echo '[PLUGINERR] Unknown class and/or function event: \''.$this->currentclass['name'].'::'.$returnfunction.'\'!'."\n";
				echo '> Could not Add this event as the class and/or function does not exist!'."\n";
				echo '> In file: '.$backtrace['file'].' on line '.$backtrace['line']."\n";
			}
		}else{
			$backtrace = $this->getFunctionName();
			echo '[PLUGINWARN] Unknown event: \''.$name.'\'. Event names are case sensitive!'."\n";
			echo '> In file: '.$backtrace['file'].' on line '.$backtrace['line']."\n";
		}
	}
	
	//this function is called to create a chat command in gery to be passed to this plugin
	public function AddChatCommand($name, $inhelp, $returnfunction)
	{	
		global $messages;
		$thisclass = $this->currentclass['name'];
		//check the chatcommand doesn't already exist
		if(empty($this->chatcommands[$name])){
			//it doesn't so add it as a command and add it to help (if applicable)
			if($inhelp === true){ //add to help?
				$add = '';
				if($this->chathelpcount == 6){ $messages['help'] .= "\n\r"; $this->chathelpcount=0; }elseif($this->chathelpcount > 0 && $this->chathelpcount < 6){ $add = ', '; }
					$messages['help'] .= $add.'/'.$name;
					$this->chathelpcount++;
			}
			$this->chatcommands[$name] = array($thisclass, $returnfunction);
		}else{
			//it already exists so error the user and do not re-add it
			$backtrace = $this->getFunctionName();
			echo '[PLUGINWARN] Chat Command: \''.$name.'\' is already registered with another plugin!'."\n";
			echo '> In file: '.$backtrace['file'].' on line '.$backtrace['line']."\n";
		}
	}

	//this function is called by gery on every server event.
	//this function evaluates the event then checks the $this->events[$eventname] array
	//then executes the functions specified under this array for the individual plugins
	public function CallEvent($eventname,$gamedata)
	{
		//do for every call in this category
		foreach ($this->events[$eventname] as $event)
		{
			//split into the class names and function names
			list($classname, $function) = $event;
			//get the class info from the class name
			$thisclass = $this->classes[$classname];
			//check the class & function exists
			if(method_exists($thisclass, $function))
			{
				//execute the plugin function
				$call1 = call_user_func_array(array($thisclass,$function), array($gamedata));
			}else{
				$backtrace = $this->getFunctionName();
				echo '[PLUGINERR] Unknown class/function called in CallEvent: \''.$classname.'::'.$function.'\'!'."\n";
				echo '> In file: '.$backtrace['file'].' on line '.$backtrace['line']."\n";
			}
		}
	}

	//this function is called by gery if all other chat commands fail
	//if this function finds a chat passed to it then it will return true and execute the function registered by the specified plugin
	//if this function does not find the chat command then it returns false and gery will give the standard UNKNOWN COMMAND error
	public function CallChatCommand($command,$playerinfo)
	{
		$chat = $command['params'];
		$command = $command['command'];
		//check the command exists in the plugin db
		if(isset($this->chatcommands[$command]) && is_array($this->chatcommands[$command])){
			$command = $this->chatcommands[$command];
			list($classname, $function) = $command;
			$thisclass = $this->classes[$classname];
			//check the method exists
			if(method_exists($thisclass, $function)){
				//exec the function to manage the chat command
				call_user_func_array(array($thisclass, $function), array($chat,$playerinfo));
				return true; //and tell gery that the command was valid
			}else{
				echo '[PLUGINERR] Unknown class/function called in CallChatCommand: \''.$classname.'::'.$function.'\' (/'.$name.')!'."\n";
			}
		}else{
			return false;
		}
	}

	//used to trace errors back to their originating function/line
	private function getFunctionName()
	{
		$backtrace = debug_backtrace();
		return $backtrace[1];
	}
}
?>