/** * This is the standard inheritable to enable players to script npcs. * <p> * All you need to do is inherit this file, make sure you call the ::init, * and make sure you have a few functions defined in your context. * <p> * If you are not using a player shop inherit then you must ensure that * is_allowed( name ) returns 1 if the player is allowed to edit scripts * in this object. * <p> * You will need to write your own find_npc() function, since this is * extremely sensitive to the implementation. For reference check out * the one which will be written for player controlled shops. * <p> * It is assumed that a save object will be inherited, if not * then event_save() will need to be defined. * * * @example * inherit "/std/room/scripting"; * inherit "/std/room"; * * void init(){ * scripting::init(); * .. * } * * void setup(){ * .. * add_help_file( DOCS +"scripting" ); * } * @see trigger_thingy(), return_script() * * @author Nofear */ #include <expressions.h> #include "nroff.h"; //edit //#define PATH "/w/nofear/scripting/" inherit "/std/basic/expressions"; #define SCRIPTING_WAIT_COMMAND 1 #define SCRIPTING_COMMAND 2 #define SCRIPTING_EXPRESSION 3 #define SCRIPTING_GOTO 4 #define SCRIPTING_TRIGGER_EVENT 5 /** * This class contains each element of the script itself. * @param type if it is a simple string or a complex expression * @param actor the person doing stuff * @param action the action to preform * @param expr the expression * @param number a number */ class script_data { int type; string actor; string str; class parse_node* expr; int number; } class script { string* valid_actors; class script_data* data; int priority; } class running_script { class script script; int position; object who; string who_name; string who_short; int run_priority; } private mapping _script_data; private nosave int _callout_tag; // Stack them. We will only do one at a time, but we could potentially // do another after the first one has finished. private nosave class running_script* _running_scripts; // Predefines. object find_npc( string what ); int is_allowed( string name ); void event_save( object ob ); /* public functions */ /** * This returns the script associated with the event. * @param name The name of the script. * @returns *string The content of the script, or an empty array. */ class script query_script( string name ){ return _script_data[name]; } /* return_script() */ private void add_running_script(class running_script script) { if (!sizeof(_running_scripts)) { _running_scripts += ({ script }); return ; } } /** * This function is called to activate a script in this object. * ( Can be left blank, best to use file_name( this_player() ) ). * @param event the name of the event to be triggered, * @param who a reference to the object who triggered the event * @param override the override for the priority */ void trigger_script( string event, object who, int override ){ int i; class script this_script; class running_script running; this_script = query_script( event ); if (!this_script || !objectp(who)) { return; } running = new(class running_script); running->script = this_script; running->who = who; running->position = 0; running->who_name = who->query_name(); running->who_short = who->the_short(); if (undefinedp(override)) { running->run_priority = this_script->priority; } else { running->run_priority = override; } add_running_script(running); } /* trigger_thingy() */ /** * This function is called by trigger_thingy and by itself. * <p> * You could mask this if you need to restrict what the npcs are allowed * to say. * * @param this_script the script contents, * @param num the line number we are up to * @param who the person we are looking at */ void repeat_loop() { object npc; class script_data data; string str; class parse_node node; if (!sizeof( _running_scripts ) ) { return; } if (_running_scripts[0]->position >= sizeof(_running_scripts[0]->script)) { _running_scripts = _running_scripts[1..]; if (!sizeof( _running_scripts ) ) { return; } } data = _running_scripts[0]->script->data[_running_scripts[0]->position]; _running_scripts[0]->position++; _callout_tag = call_out( "repeat_loop", 2); switch ( data->type) { case SCRIPTING_WAIT_COMMAND : remove_call_out(_callout_tag); _callout_tag = call_out( "repeat_loop", data->number); return; case SCRIPTING_COMMAND : npc = find_npc( data->actor ); if( !npc ){ repeat_loop(); return; } npc->do_command( data->str ); break; case SCRIPTING_EXPRESSION : node = evaluate_expresion(data->expr); if (node->type == EXPRESSION_TYPE_STRING) { npc = find_npc( data->actor ); if (!npc) { repeat_loop(); return ; } npc->do_command( data-> str ); } break; case SCRIPTING_GOTO : _running_script[0]->position = data->number; break; case SCRIPTING_TRIGGER_EVENT : // Make this one happen first. trigger_script(data->name, _running_script[0]->npc, _running_script[0]->run_priority | PRIORITY_FRONT); break; } call_out("repeat_loop"); } /* repeat_loop() */ /** * This method sets the script to the new value. * @param name the event to override * @param script the new script */ void set_script(string name, class script script) { _script_data[name] = script; event_save(this_object()); } /* set_script() */ /** * This method sets the script data. * @param name the event to set the data for * @param data the script data */ void set_script_data(string name, class script_data* data) { if (_script_data[name]) { _script_data[name]->data = data; event_save(this_object()); } } /* set_script_data() */ /** * This method sets the actors associated with the script. * @param name the name of the event to set the data for * @param actors the actors for the script */ void set_script_actors(string name, string* actors) { if (_script_data[name]) { _script_data[name]->actors = actors; event_save(this_object()); } } /* set_script_actors() */ /** * This method replaces the script data at the specified location in * the script with a new item. * @param name the script name * @param pos the position to replace * @param data the data to replace it with * @return 1 on success, 0 on failure */ int set_script_data_location(string name, int pos, class script_data data) { if (_script_data[name] && pos >= 0 && pos < sizeof(_script_data[name]->data)) { _script_data[name]->data[pos] = data; event_save(this_object()); return 1; } return 0; } /* set_script_data_location() */ /** * This method deletes the script data at the specified location. * @param name the script name * @param pos the position to replace * @return 1 on success, 0 on failure */ int delete_script_data_location(string name, int pos) { if (_script_data[name] && pos >= 0 && pos < sizeof(_script_data[name]->data)) { _script_data[name]->data = _script_data[name]->data[0..pos-1] + _script_data[name]->data[pos+1..]; event_save(this_object()); return 1; } return 0; } /* delete_script_data_location() */ /** * This method inserts a data element before the specified position in the * script. If the position is 0 then the element is inserted at the start, * if the position is beyond the end of the array, then the item is inserter * at the end. * @param name the script name * @param pos the position to insert before * @param data the data to replace it with */ int insert_script_data_before(string name, int pos, class script_data data) { if (_script_data[name]) { _script_data[name]->data = _script_data[name]->data[0..pos-1] + ({ data }) + _script_data[name]->data[pos..]; event_save(this_object()); return 1; } return 0; } /* insert_script_data_before() */ /* Internal workings - not much use to anything else. */ /** @ignore yes * This occurs during script creation. */ void menu_prompt(string name, class* script) { tell_object( this_player(), "Enter the name of the person who should " "perform an action ( or another command such as \"wait\" ).\n" ); print_commands(); input_to( "menu_input", 0, name, script ); } /* name_prompt() */ private int query_pos_from_string(string str) { if (bits[2] == "begining") { return 0; } else if (bits[2] == "end") { return sizeof(script->data); } else { pos = to_int(bits[2]); } if (pos < 0 || pos > sizeof(script->data)) { return -1; } return pos; } /* query_pos_from_string() */ /** @ignore yes * This occurs during script creation. */ void menu_input( string input, string name, class script script ){ string* bits; bits = explode(lower_case(input), " "); switch (lower_case(bits[0])) { case "h" : case "help" : show_help(); break; case "s" : case "save" : set_script(name, script); break; case "q" : case "quit" : write("Are you sure you want to quit and " "lose the current script?\n" ); input_to( "check_quit", 0, name, script ); return ; case "l" : case "list" : write(query_script_string(script)); break; case "a" : case "add" : // add <person> <pos|end|begining> if (sizeof(bits) != 3) { write("Syntax: add <person> <pos|end|begining>\n"); } else if (lower_case(bits[1]) != "wait" && member_array(bits[1], script->actors) == -1) { write("The actor must be one of " + query_multiple_short(script->actors) + " not " + bits[1] + ".\n"); } else { pos = query_pos_from_string(bits[2]); if (pos != -1) { write("Which command do you want to execute for " + bits[1] + "?\n: "); input_to("enter_command", 0, name, script, bits[1], pos); } else { write("The position must be in the range 0.." + sizeof(script->data) + " or 'begining' or 'end'.\n"); } } break; case "d" : case "delete" : if (sizeof(bits) != 2) { write("Syntax: delete <pos|end|begining>\n"); } else { pos = query_pos_from_string(bits[2]); if (pos != -1) { } else { write("The position must be in the range 0.." + sizeof(script->data) + " or 'begining' or 'end'.\n"); } } break; default : write("Invalid command " + bits[1] + ".\n"); } menu_prompt(name, script); } /* input_name() */ /** @ignore yes * This occurs during script creation. */ protected void enter_command( string input, string name, class script script ) { if( _temp_script->script[sizeof( _temp_script->script ) - 1] == "wait" ){ if( !to_int(input) ){ tell_object( this_player(), "%^BOLD%^For the wait command you " "must enter a whole value for the number of seconds " "to wait%^RESET%^.\n" ); prompt_command(); return; } } _temp_script->script = _temp_script->script + ({input}); tell_object( this_player(), "Enter the name of the person who should " "perform an action ( or another command such as \"wait\" ).\n" ); print_commands(); input_to( "input_name" ); } /* input_command() */ /** @ignore yes * This occurs during script creation. */ void print_commands(){ tell_object( this_player(), "Command [ h for help ]:\n" ); } /* print_commands() */ /** @ignore yes * This occurs during script creation. */ void show_help(){ string str; //edit change path or filename of nroff file. str = unguarded((: NROFF_HAND->cat_file("/doc/room/scripting_nroff", 1) :)); if(!str){ unguarded((: NROFF_HAND->create_nroff(PATH +"scripting_online_help", PATH +"scripting_nroff") :)); str = unguarded((:NROFF_HAND->cat_file(PATH +"scripting_nroff", 0):)); } write(str); } /* show_help() */ /** @ignore yes * This occurs during script creation if player enters "q". */ void query_quit( string input, string func ){ if( (input == "y") || (input == "yes") || (input == "Y") ){ write("Okay, quitting.\n" ); }else{ write("%^BOLD%^You did not enter \"y\" " "or \"yes\" so you are not being quitted.%^RESET%^\n" ); call_other( this_object(), func ); } } /* query_quit() */ /** * This sets the specific script to the new value. */ void set_script(string name, class scripting* script) { write("Saving current script and exiting.\n" ); _scripts[name] = script; event_save( this_object() ); } /* save_script() */ /** * This returns a printable string of the script. * @param script the script to turn into a string * @return the string version of the script */ string query_script_string( class scripting* script ){ int i; string str; if( _temp_script->tag == "" ) { return; } str = "\nScript name \""+ _temp_script->tag + ":\n\n"; for( i = 0; i < sizeof(_temp_script->script); i++ ){ str += _temp_script->script[i]->name +" : "; str += _temp_script->script[i]->action +"\n"; } str += "\n"; return str; } /* view_script() */ /** @ignore yes * This is called by delete_script_command. * Num is the index of script to delete. */ void query_delete( string input, int num ){ if( (input == "y") || (input == "yes") || (input == "Y") ){ tell_object( this_player(), "Okay, removing script.\n" ); if( sizeof(_script_data) == 1 ){ _script_data = ({}); }else if( sizeof(_script_data) == 2 ){ _script_data = ({_script_data[!num]}); }else{ if( !num ){ _script_data = _script_data[1..]; }else{ _script_data = _script_data[0..(num - 1)] + _script_data[(num + 1)..]; } } event_save( this_object() ); }else{ tell_object( this_player(), "%^BOLD%^You did not enter \"y\" or " "\"yes\" so the script will not be deleted.%^RESET%^\n" ); } _temp_script->tag = ""; _temp_script->script = ({}); } /* query_delete() */ /** @ignore yes * This function returns the index of the script "input", or -1. */ int get_index( mixed input ){ int i; if( to_int( input ) ){ i = to_int( input ); if( i > sizeof(_script_data) ){ tell_object( this_player(), "Invalid option, quitting.\n" ); return -1; }else{ return i - 1; } }else{ for( i = 0; i < sizeof(_script_data); i++ ){ if( _script_data[i]->tag == input ) return i; } tell_object( this_player(), "Invalid option, quitting.\n" ); return -1; } } /* get_index() */ /** @ignore yes * Ugly function alert! * This recalls itself until the player is done editing. */ void edit_script( string input, int which ){ string *comm, command, text; int line, i; if( input == "" ){ input_to( "edit_script", 0, which ); tell_object( this_player(), "Command [ R(eplace), D(elete), " "I(nsert), V(iew), S(ave), Q(uit) ]:\nEg \"R 13 rabbit\".\n" ); return; } command = ""; line = -1; text = ""; comm = explode( input, " " ); if( sizeof(comm) ) command = comm[0]; if( sizeof(comm) > 1) line = to_int(comm[1]) - 1; if( sizeof(comm) > 2){ comm = comm[2..]; text = implode(comm, " "); } switch (command){ case "s" : case "S" : tell_object( this_player(), "Done editing script.\n" ); _script_data[which]->script = _temp_script->script; return; case "q" : case "Q" : tell_object( this_player(), "Quitting without saving changes.\n"); return; case "v" : case "V" : for( i = 0; i < sizeof(_temp_script->script); i++ ) tell_object(this_player(),(i+1) +" "+ _temp_script->script[i] +"\n"); input_to( "edit_script", 0, which ); tell_object( this_player(), "Command [ R(eplace), D(elete), " "I(nsert), V(iew), S(ave), Q(uit) ]:\nEg \"R 13 rabbit\".\n" ); return; case "d" : case "D" : if( (line < 0) || ((line +1) > sizeof(_temp_script->script)) ){ tell_object( this_player(), "For this command you need to " "enter a valid line number.\n" ); input_to( "edit_script", 0, which ); tell_object( this_player(), "Command [ R(eplace), D(elete), " "I(nsert), V(iew), S(ave), Q(uit) ]:\nEg \"R 13 rabbit\".\n"); return; } if( sizeof(_temp_script->script) == 1 ){ _temp_script->script = ({}); }else if( sizeof(_temp_script->script) == 2 ){ _temp_script->script = ({_temp_script->script[!line]}); }else{ _temp_script->script = _temp_script->script[0..(line-1)] + _temp_script->script[line+1..]; } tell_object( this_player(), "Line "+ (line+1) +" deleted. " "Please note that the line numbers for the rest of the " "script may have been effected by this change.\n" ); input_to( "edit_script", 0, which ); tell_object( this_player(), "Command [ R(eplace), D(elete), " "I(nsert), V(iew), S(ave), Q(uit) ]:\nEg \"R 13 rabbit\".\n" ); return; case "r" : case "R" : if( (line < 0) || ((line +1) > sizeof(_temp_script->script)) ){ tell_object( this_player(), "For this command you need to " "enter a valid line number.\n" ); input_to( "edit_script", 0, which ); tell_object( this_player(), "Command [ R(eplace), D(elete), " "I(nsert), V(iew), S(ave), Q(uit) ]:\nEg \"R 13 rabbit\".\n" ); return; } tell_object( this_player(), "Replacing line "+ (line+1) +" with \""+ text +"\".\n" ); _temp_script->script[line] = text; input_to( "edit_script", 0, which ); tell_object( this_player(), "Command [ R(eplace), D(elete), " "I(nsert), V(iew), S(ave), Q(uit) ]:\nEg \"R 13 rabbit\".\n" ); return; case "i" : case "I" : if( (line < 0) || ((line +1) > sizeof(_temp_script->script)) ){ tell_object( this_player(), "For this command you need to " "enter a valid line number.\n" ); input_to( "edit_script", 0, which ); tell_object( this_player(), "Command [ R(eplace), D(elete), " "I(nsert), V(iew), S(ave), Q(uit) ]:\nEg \"R 13 rabbit\".\n" ); return; } tell_object( this_player(), "Inserting \""+ text +"\" at line "+ (line+1) +". Please note that the line numbers for the rest " "of the script may have been effected by this change.\n" ); if( !line ){ _temp_script->script = ({text}) + _temp_script->script; }else{ _temp_script->script = _temp_script->script[0..(line-1)] + ({text}) + _temp_script->script[line..]; } input_to( "edit_script", 0, which ); tell_object( this_player(), "Command [ R(eplace), D(elete), " "I(nsert), V(iew), S(ave), Q(uit) ]:\nEg \"R 13 rabbit\".\n" ); return; default : tell_object( this_player(),"%^BOLD%^Unknown command.\n%^RESET%^"); input_to( "edit_script", 0, which ); tell_object( this_player(), "Command [ R(eplace), D(elete), " "I(nsert), V(iew), S(ave), Q(uit) ]:\nEg \"R 13 rabbit\".\n" ); return; } /* switch */ } /* edit_script() */ /* Command functions */ /** @ignore yes * Command response */ int do_add_script( string input ){ if( input == "" ){ tell_object( this_player(), "You must enter a name for the script.\n" ); add_succeeded_mess( "" ); return 1; } if( return_script( input ) != ({}) ){ tell_object( this_player(), "%^BOLD%^There is already a script for \""+ input +"\", if you wish to keep the existing version then quit " "now and start over, if you continue the new version will " "replace the old one.%^RESET%^\n" ); }else{ tell_object( this_player(), "This script will be known as \""+ input +"\".\n" ); } _temp_script = new(class fancy_hat, tag : input, script : ({}) ); tell_object( this_player(), "Enter the name of the person who should " "perform an action, or another command such as \"wait\". eg\n\n" " \"mike\"\n" " \"thug1\"\n" " \"wait\"\n" ); print_commands(); input_to( "input_name" ); add_succeeded_mess( "$N begin$s to write a new script.\n" ); return 1; } /* do_add_script() */ /** @ignore yes * Command response */ int do_list_scripts(){ int i; if( !sizeof(_script_data) ){ this_player()->add_failed_mess( this_object(), "No scripts found.\n" ); return 0; } tell_object(this_player(), sizeof(_script_data) +" scripts found:\n\n"); for( i = 0; i < sizeof(_script_data); i++ ) tell_object( this_player(), (i +1) +" "+ _script_data[i]->tag +"\n"); add_succeeded_mess( "" ); return 1; } /* do_list_scripts() */ /** @ignore yes * Command response */ int do_view_script( mixed input ){ int num; num = get_index( input ); if( num == -1 ){ add_succeeded_mess( "" ); return 1; } if( undefinedp( _temp_script ) ){ _temp_script = new( class fancy_hat, tag : _script_data[num]->tag, script : _script_data[num]->script ); }else{ _temp_script->tag = _script_data[num]->tag; _temp_script->script = _script_data[num]->script; } view_script(1); add_succeeded_mess( "" ); return 1; } /* do_view_script() */ /** @ignore yes * Command response */ int do_test_script( mixed input ){ int num; num = get_index( input ); if( num == -1 ){ add_succeeded_mess( "" ); return 1; } tell_object( this_player(), "Triggering event \""+ _script_data[num]->tag +"\".\n" ); trigger_thingy( _script_data[num]->tag, file_name( this_player()) ); add_succeeded_mess( "" ); return 1; } /* do_test_script() */ /** @ignore yes * Command response */ int do_delete_script( mixed input ){ int num; num = get_index( input ); if( num == -1 ){ add_succeeded_mess( "" ); return 1; } if( undefinedp( _temp_script ) ){ _temp_script = new( class fancy_hat, tag : _script_data[num]->tag, script : _script_data[num]->script ); }else{ _temp_script->tag = _script_data[num]->tag; _temp_script->script = _script_data[num]->script; } tell_object( this_player(), "Are you sure that you want to " "permanently remove the script \""+ _temp_script->tag +"\"?\n" ); input_to( "query_delete", 0, num ); add_succeeded_mess( "" ); return 1; } /* do_delete_script() */ /** @ignore yes * Command response */ int do_edit_script( mixed input ){ int num, i; num = get_index( input ); if( num == -1 ){ add_succeeded_mess( "" ); return 1; } if( undefinedp( _temp_script ) ){ _temp_script = new( class fancy_hat, tag : _script_data[num]->tag, script : _script_data[num]->script ); }else{ _temp_script->tag = _script_data[num]->tag; _temp_script->script = _script_data[num]->script; } tell_object( this_player(), "Editing script \""+ _temp_script->tag + "\".\n\nThis is a complete listing of the script:\n" ); for( i = 0; i < sizeof(_temp_script->script); i++ ){ tell_object(this_player(),(i+1) +" "+ _temp_script->script[i] +"\n"); } tell_object( this_player(), "Command [ R(eplace), D(elete), I(nsert), " "V(iew), S(ave), Q(uit) ]:\nEg \"R 13 rabbit\".\n" ); input_to( "edit_script", 0, num ); add_succeeded_mess( "" ); return 1; } /* do_edit_script() */ /** @ignore yes */ void init(){ if( !is_allowed( this_player()->query_name() ) ) { return; } add_command( "add", "script <string'name'>", (: do_add_script( $4[0] ) :) ); add_command( "list", "scripts", (: do_list_scripts() :) ); add_command( "view", "script <string'name'>", (: do_view_script( $4[0] ) :) ); add_command( "view", "script <number>", (: do_view_script( $4[0] ) :) ); add_command( "test", "script <string'name'>", (: do_test_script( $4[0] ) :) ); add_command( "test", this_object(), "script <number>", (: do_test_script( $4[0] ) :) ); add_command( "delete", "script <string'name'>", (: do_delete_script( $4[0] ) :) ); add_command( "delete", "script <number>", (: do_delete_script( $4[0] ) :) ); add_command( "edit", "script <string'name'>", (: do_edit_script( $4[0] ) :) ); add_command( "edit", "script <number>", (: do_edit_script( $4[0] ) :) ); } /* init() */