/** * 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; /* Time left for this round. */ nosave int tl_updated; /* Last time the time_left was updated */ nosave int doing_it; nosave int last_command; nosave int bypass_queue; nosave int flush_call_id; /* Id of teh call_out */ nosave string *queued_commands; nosave string in_command; nosave mixed interrupt; nosave private function cmd = 0; /** @ignore */ void create() { time_left = ROUND_TIME; tl_updated = 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() */ /** * Some of the soul commands force the target to do something. * An example is the 'tickle' soul, which forces the target to * 'giggle'. Those soul-forces call this function. But only * the soul object can use this function: any other object which calls * this function will be ignored. This prevents this function * from being used to bypass the security checking on the * 'force' command. * * @param str the command being forced * @return 0 if the command was ignored, otherwise 1. */ int soul_com_force(string str) { if( file_name(PO) != SOUL_H ) return 0; command(str); return 1; } /* soul_com_force() */ /** * All soul commands eventually call this function to output their * messages. This is nothing more than a wrapper for say(), but * it provides a convenient name by which a shadow on the * player object can replace any soul behavior. * * @param str the string being printed * @prarm bing the avoid array */ void do_soul( string str, mixed bing ) { say( str, bing ); } /* do_soul() */ /** * The amount of time units left. A time unit is 1/40th of a second. */ int query_time_left() { time_left += ( ( time() - tl_updated ) / 2 ) * ROUND_TIME; if( time_left > ROUND_TIME ) time_left = ROUND_TIME; tl_updated = time(); return time_left; } /* query_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 < 0 || !sizeof( queued_commands ) || ( TO->queue_commands(queued_commands[0]) && !creatorp(TO) ) ) return; if( !first ) { str = queued_commands[0]; queued_commands = queued_commands[1..]; doing_it = 1; catch( command(str) ); doing_it = 0; if( !sizeof(queued_commands) ) { /* The end! */ 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() { query_time_left(); remove_call_out( flush_call_id ); do_flush(1); if( !sizeof(queued_commands) ) { /* Ok... 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 teh command finished. If it is interrupted * the first arguement 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 = function_owner(cmd); if( owner && owner == ENV(TP) ) { int res = evaluate( cmd, str ); if( res ) return res; } else cmd = 0; } rabbit = explode( str, " "); if( sizeof(rabbit) == 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"})[(int)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" || str == "quit_old" ) return 0; mess = TO->query_passed_out_message(); if( !stringp( mess ) ) mess = "You are unconscious. You can't do anything.\n"; write( mess ); if( !creatorp(TO) ) 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) && !creatorp(TO) ) || ( !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, and or \"restart\" to start " "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 = ""; } rabbit = run_alias( comm, arg ); if( rabbit ) { /* 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 { 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. * * @parms 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() */