/** * This class controls the entire command queue for all player * objects. Whenever a player tries to execute a command, * it gets placed in a queue here and eventually (during the * player's heart_beat()), the command will (hopefully) be executed. * * <p>Since this class is a nexus of control for player commands, it * is also the natural place for a number of other utilities and * functions which also affect the execution of all commands. * Among these are things like drunk_check(), do_soul(), etc. * * @see /global/player/new_parse->add_command() * @see /global/player->heart_beat() * @see query_passed_out_message() * * @author Pinkfish * @changed 3 November 1997 -- Sin * Documented the bejeesus out of this thing. * @changed 4 Novemebr 1997 - Pinkfish * Updated the documentation and changed the interrupt system slightly. * @changed 22 Feburary 1998 - Pinkfish * Fixed up the problems with the queueing system. * @changed 28 Feburary 2002 - Sandoz * Fixed up a problem in _process_input with printf() erroring * on error messages with % in them. */ #include <player.h> inherit "/global/player/alias"; inherit "/global/player/nickname"; nosave int time_left; nosave int doing_it; nosave int last_command; nosave int bypass_queue; nosave int flush_call_id; nosave string *queued_commands; nosave string in_command; nosave mixed interrupt; nosave private function cmd = 0; /** @ignore */ void create() { time_left = ROUND_TIME; last_command = time(); queued_commands = ({ }); } /* create() */ /** * To make the next single command be executed directly rather * than being placed in the command queue, call this function. */ void bypass_queue() { bypass_queue = 1; } /** * Ensure that the player has no more time for executing commands. * This will force the next command to be queued. */ void no_time_left() { time_left = -ROUND_TIME; } /* no_time_left() */ /** * This is a setup function that is called by the player object. * It is used to register the lower_check() and drunk_check() * functions. Plus it initializes the alias object, the * nickname object, and the history object. */ protected int drunk_check( string str ); /** @ignore yes */ void soul_commands() { alias_commands(); nickname_commands(); history_commands(); } /* soul_commands() */ /** * You can use this function to see if there are any commands * queued for this player. * @return the number of queued commands */ int query_queued_commands() { return sizeof(queued_commands); } /* query_queued_commands() */ /** * The amount of time units left. A time unit is 1/40th of a second. */ int query_time_left() { return time_left; } /** * Change the amount of time a player has left. You call this after a command * has been executed to make it take more time. * @param i the amount of time units to change by * @return the amount of time left */ int adjust_time_left( int i ) { return time_left += i; } /* adjust_time_left() */ /** @ignore yes */ private void do_flush( int first ) { int i; string str; if( time_left < 1 || !sizeof( queued_commands ) || ( TO->queue_commands(queued_commands[0]) && !TO->query_creator() ) ) return; if( !first ) { str = queued_commands[0]; queued_commands = queued_commands[1..]; doing_it = 1; catch( command(str) ); doing_it = 0; // The end! if( !sizeof(queued_commands) ) { queued_commands = ({ }); doing_alias = ([ ]); } return; } for( i = 0; i < 2 && i < sizeof(queued_commands); i++ ) flush_call_id = call_out( (: do_flush(0) :), 1 ); flush_call_id = call_out( (: do_flush(1) :), 2 ); } /* do_flush() */ /** @ignore yes */ private void call_interrupt( int time_left, object interupter ) { mixed stuff; stuff = interrupt; interrupt = 0; // The previous object is the person interupting us. if( pointerp(stuff) ) catch( call_other( stuff[1], stuff[0], time_left, stuff[2], TO, interupter, in_command ) ); else if( functionp(stuff) ) catch( evaluate( stuff, time_left, TO, interupter, in_command ) ); } /* call_interrupt() */ /** * This method flushes all the queued commands. It increments the time by the * ROUND_TIME define and checks to see if any of the commands now need to be * executed. This should be called each heart beat.. * @see /global/player->heart_beat() */ protected void flush_queue() { time_left += ROUND_TIME; if( time_left > ROUND_TIME ) time_left = ROUND_TIME; remove_call_out( flush_call_id ); do_flush(1); if( !sizeof(queued_commands) ) { // Check to see if an interupt was set up. if( interrupt && time_left > 0 ) call_interrupt( 0, TO ); in_alias_command = 0; doing_alias = ([ ]); if( !sizeof(queued_commands) ) return; doing_alias = ([ ]); in_alias_command = 0; } } /* flush_queue() */ /** * Sets the function to be executed if the command is interrupted. * It is also executed if the command finished. If it is interrupted * the first argument to the called function will be the amount of time * it had left to complete. If it complets successfuly, this * argument will be 0. If the first argument is a function pointer, * this will be used instead.<p> * * Eg: set_interupt_command("frog", TO);<p> * void frog(int time_left, mixed arg) { <p> * ... <p> * } * * @param func the function to call back * @param ob the object to call the function on * @param arg the argument to pass to the function * @example * ... * void frog(int time_left, mixed arg); * ... * set_interupt_commant( (: frog :) ); * ... * void frog(int time_left, mixed arg) { * ... * } /\* frog() *\/ */ void set_interupt_command(mixed func, mixed ob, mixed arg) { if( !functionp(func) ) { interrupt = ({ func, ob, arg }); if( !stringp( func ) ) interrupt = 0; } else { interrupt = func; } } /* set_interupt_command() */ /** * This method returns the current value associated with tine interupt * command. * @return the current interupt command data */ mixed query_interupt_command() { return interrupt; } /* query_interupt_command_func() */ /** * This one only takes a function pointer as an input. * @param func the function pointer to call back with */ void set_interrupt_command( function func ) { set_interupt_command( func, 0, 0 ); } /* set_interrupt_command() */ /** * This is called by the stop command. It sets the entire queue back to * empty. It calls the interrupt functions and stuff if they need to be * called. */ void remove_queue() { queued_commands = ({ }); if( interrupt && time_left < 0 ) call_interrupt( -time_left, TO ); tell_object( TO, "Removed queue.\n"); if( lordp(TO) ) { // Just in case something really bad happens... Let lords fix it. time_left = 0; } else { // Make sure they cannot do anything for a heartbeat. time_left = -DEFAULT_TIME; } } /* remove_queue() */ /** * This method interupts the current command. * @param interupter the person interupting the command */ void interupt_command( object interupter ) { if( interupter ) call_interrupt( -time_left, interupter ); } /* interupt_command() */ /** * Use this function to set a function that is called with the players input * before the command handlers get to it, return 1 from the function if the * input needs no further parsing (ie the command is handled) * @param func function in the players environment to call. */ void command_override( function func ) { if( !functionp(func) ) error("command_override needs a function!"); cmd = func; } /* command_override() */ /** * This poorly named function was originally used to affect the * player's behavior when they are drunk, and to prevent any * player from doing anything in the event that they are passed * out. Now the function also is responsible for adding commands * to the player's command queue, for implementing the 'stop' * and 'restart' commands, and for ensuring that the player can quit * the game, even when queueing. * * <p>To see if a player is passed out, it checks the * "passed out" property. If that property is nonzero, then * the player will be prevented from doing the command unless * that player is also a creator. By default, it will print * a message that says: "You are unconscious. You can't do * anything.\n". If the function query_passed_out_message() is * defined on the player object (usually by a shadow), and * returns a string, then that string is printed instead. * * @return 0 if nothing was done, 1 if drunk_check() blocked * the command. * @param str the command being executed * * @see /global/new_parse->add_command() */ protected int drunk_check( string str ) { string *rabbit, *green, mess, comm, arg; if( cmd ) { object owner; owner = function_owner(cmd); if( owner && owner == ENV(TP) ) { int ret; if( ret = evaluate( cmd, str ) ) return ret; } else { cmd = 0; } } if( sizeof( rabbit = explode( str, " ") ) == 1 ) if( rabbit[0] == ",") return 0; if( in_command == str ) { in_command = 0; sscanf( str, "%s %*s", str ); if( is_doing_alias(str) ) notify_fail("Recursive aliases. Bad "+({"thing", "boy", "girl"})[TO->query_gender()]+".\n"); return 0; } if( bypass_queue ) { bypass_queue = 0; return 0; } last_command = time(); if( TO->query_property(PASSED_OUT) || !interactive(TO) ) { if( str == "quit") return 0; if( !stringp( mess = TO->query_passed_out_message() ) ) mess = "You are unconscious. You can't do anything.\n"; write( mess ); if( !TO->query_creator() ) return 1; write("On the other hand, you're a creator...\n"); } if( str == "stop" || str == "restart") { remove_queue(); return 0; } if( stringp(str) && str[0..4] == "stop ") return 0; /* * If: there's no time left * or: commands are to be queued (e.g. spell casting) and this is a player * or: if we have queueing commands and we are not currently executing * a command off the stack * or: we are trying to do a flush * then queue the command. */ if( time_left < 0 || ( TO->queue_commands(str) && !TO->query_creator() ) || ( !doing_it && ( sizeof( queued_commands ) || find_call_out( flush_call_id ) != -1 ) ) ) { // Only print commands which are not in upper case. rabbit = explode( str, " "); if( rabbit[0] != upper_case( rabbit[0] ) ) write("Queued command: "+str+"\n"); if( str == "quit") write("If you are trying to quit and it is queueing things, use " "\"stop\" to stop your commands, or \"restart\" to restart " "your heartbeat.\n"); // The command should always go on the end because the aliases // in the queue are expanded elsewhere... queued_commands += ({ str }); return 1; } if( interrupt ) call_interrupt( 0, TO ); interrupt = 0; in_command = str; // Get the args and stuff to run the alias. if( sscanf( str, "%s %s", comm, arg ) != 2 ) { comm = str; arg = ""; } if( rabbit = run_alias( comm, arg ) ) { // Set us as running the alias. set_doing_alias(comm); green = queued_commands; queued_commands = ({ }); foreach( comm in rabbit ) catch( command(comm) ); queued_commands += green; } else if( sizeof(str) > 1024 ) { write("Command too long.\n"); } else { if( str[0..8] != "END_ALIAS") time_left -= DEFAULT_TIME; command(str); } if( interrupt && time_left >= 0 ) call_interrupt( 0, TO ); return 1; } /* drunk_check() */ /** @ignore yes */ protected string _process_input( string str ) { object ob; if( str == "" ) return 0; ob = TP; set_this_player(TO); _notify_fail(0); if( !sizeof( explode( str, " " ) - ({"", 0 }) ) || str[0] == ',' ) { efun::tell_object( TO, "What?\n"); set_this_player(ob); return 0; } if( !drunk_check(str) && !TO->exit_command(str) && !TO->cmdAll(str) && !TO->new_parser(str) && !TO->lower_check(str) ) { efun::tell_object( TO, query_notify_fail() || "What?\n"); set_this_player(ob); return 0; } set_this_player(ob); return "bing"; } /* _process_input() */ /** * This is the command called by the driver on a player object every * time a command is executed. It expands the history comands. * @param str the string to expand * @return the expanded history string */ protected string process_input( string str ) { reset_eval_cost(); if( str[0] == '.' ) str = expand_history(str[1..]); else if( str[0] == '^' ) str = substitute_history(str[1..]); TO->add_history(str); _process_input(str); return 0; } /* process_input() */ /** * This function will get called when all other commands and actions * have refused to do anything for this input from the user. This * function adds some extra time for the user, and then returns. * @param str the user's input * @return 1 if the user's input is "stop", otherwise 0. */ int lower_check( string str ) { query_time_left(); return str == "stop"; } /* lower_check() */