/** * This is the main NPC inheritable. * @author Pinkfish * @change Modified to properly work on Divided Sky and * fixed skills/stats - Sandoz, 20th July 2001. * @see /std/npc/npc_base */ #include <player.h> #include <route.h> #include <skills.h> #include <top_ten_tables.h> inherit "/std/living/living"; inherit "/global/player/new_parse"; inherit "/std/living/response_mon"; inherit "/global/player/guild-race"; class chat_data { int chance; int total_w; int *weight; mixed chats; int last_chat; } nosave class chat_data chats, a_chats; nosave int added_lang, follow_speed, chats_off, cannot_change_position, always_return_to_default_position, *move_after, aggressive, death_xp, death_xp_set, join_fight_type, _level, chase_id; nosave string *move_zones, *following_route, *enter_commands, _home, join_fight_mess, *_greets, cur_lang; nosave object *to_greet; nosave mixed queued_commands, doing_story, combat_actions; string find_rel( string word, int from ); void do_move_after( int running_away ); void do_route_move(); void add_language( string lang ); int query_chats_off(); int check_env(); /** @ignore yes */ int query_cols() { return 79; } void create() { do_setup++; living::create(); new_parse::create(); response_mon::create(); do_setup--; reset_get(); follow_speed = 3; // Do they won't get silly commands like "rearrange". known_commands = ({ }); living_commands(); parser_commands(); add_property("npc", 1 ); add_property("no ambiguous", 1 ); set_con(12); set_dex(12); set_int(12); set_str(12); set_wis(12); set_max_hp(10000); set_hp(10000); set_max_gp(1000); set_gp(1000); // Default to 5 seconds. always_return_to_default_position = 5; cur_lang = "common"; // This is done only if the NPC doesn't have a language set after setup. // Check their env here as well. call_out( function() { if( !added_lang ) { add_language("common"); if( clonep() && !ENV(TO) ) call_out( function() { if( !ENV(TO) ) move("/room/rubbish"); }, 60 ); } }, 2); if( !do_setup ) { TO->setup(); TO->reset(); } } /* create() */ /** * This method sets the unique propety on the NPC. * Unique NPCs give no xp when killed and will generate * informs when dying amongst other things. */ void set_unique() { add_property("unique", 1 ); } /** * This method will fetch an item from the armoury and move it into the NPC. * @param the item to get * @param the condition of the item * @return the object of the new item */ object get_item( string item, int cond ) { object ob; if( !sizeof(item) || cond < 1 ) error("No item name specified or condition less than 1.\n"); if( !ob = ARMOURY_H->request_item( item, cond ) ) error( sprintf("Failed to clone %s for %s (%s).\n", item, short(), file_name(TO) ) ); ob->move(TO); return ob; } /* get_item() */ /** * This method adds a language to the NPC. * @param lang the language to add */ void add_language( string lang ) { int lvl; string skill; if( !LANGUAGE_H->test_language( lang ) ) return ; if( LANGUAGE_H->query_language_spoken( lang ) ) { skill = LANGUAGE_H->query_language_spoken_skill( lang ); lvl = TO->query_skill( skill ); TO->add_skill_level( skill, 100 - lvl ); } if( LANGUAGE_H->query_language_written( lang ) || LANGUAGE_H->query_language_magic( lang ) ) { skill = LANGUAGE_H->query_language_written_skill( lang ); lvl = TO->query_skill( skill ); TO->add_skill_level( skill, 100 - lvl ); } cur_lang = lang; added_lang = 1; } /* add_language() */ /** * This method sets the current language of the NPC. * @param lang the language to set as current */ int set_language( string lang ) { if( !LANGUAGE_H->test_language( lang ) ) return 0; cur_lang = lang; return 1; } /* set_language() */ /** * This method returns the current language of the NPC. * @return the current language */ string query_current_language() { return cur_lang; } /** * This method allows you to control the NPC and get it to do * actions. This can be used for NPC control and intelligence. * @param words the action to preform * @see init_equip() * @see init_command() * @example * ob = clone_object(NICE_HAIRY_APE); * ob->do_command("'I am a hairy ape!"); * ob->do_command("emote apes around the room."); */ int do_command( string words ) { if( TO->query_property(PASSED_OUT) ) return -1; if( stringp( words ) ) return command( words ); printf("Invalid parameter to do_command: %O for NPC %O in %O.\n", words, TO, ENV(TO) ); return -1; } /* do_command() */ /** * This method allows you submit delayed commands to the NPC via a call_out. * @param str the action to perform * @param tim the time delay after which to perform the action * @see do_command() * @see queue_command() * @see delay_command() */ varargs void init_command( string str, int tim ) { call_out("do_command", tim, str ); } /* init_command() */ /** * @ignore yes * No need to print any messages to an NPC. * Less functions will be called if we don't :) */ void add_message( string message, mixed things ); /** @ignore yes */ varargs string fix_string(string str, int width, int indent, mixed *args ...) { return "/global/player/events"->fix_string( str, width, indent, args ...); } /* fix_string() */ /** @ignore yes */ string evaluate_message( mixed stuff ) { return "/global/player/events"->evaluate_message( stuff ); } /* evaluate_message() */ /** @ignore yes */ string fit_message( string message ) { return "/global/player/events"->fit_message( message ); } /* fit_message() */ /** @ignore yes */ string convert_message( string message ) { return "/global/player/events"->convert_message( message ); } /* convert_message() */ /** @ignore yes */ void comm_event( mixed thing, string type, string start, string rest, string lang ) { if( !objectp( thing ) && !pointerp( thing ) ) return; event( thing, type, start, rest, lang ); call_other( INV(TO), "event_"+type, TO, start, rest, lang ); } /* comm_event() */ /** * @ignore yes * We need to be able to whisper as it generates event_whisper(). */ void do_whisper( object ob, string event_type, string start, string type, string words, object *others, string lang, object me ) { event( ob, event_type, start, type, words, others, lang, me ); } /* do_whisper() */ /** * This method is used to expand the message strings used in the * npc messages. It is used for chat strings and such things like * that. The strings it expands are of the form:<br> * $lname$, $mname$, $aname$, $itheshort$ ...<br> * The first letter determines the type of object being referenced. * They are: * <dl> * <dt>m * <dd>Me! The NPC itself. * <dt>l * <dt>A random living object in the NPC's environment. * <dt>i * <dt>A random interactive object (player) in the NPC's environment. * <dt>a * <dd>Chooses a random attacker from those attacking the NPC. * <dt>o * <dd>Choose a random object in the inventory of the NPC. * </dl> * After the first letter is a type of information being request. * <dl> * <dt>name * <dd>The name of the selected object. * <dt>cname * <dd>The capitalised name of the selected object. * <dt>gender * <dd>The gender string of the selected object (male, female, neuter). * <dt>poss * <dd>The possessive string of the selected object. * <dt>obj * <dd>The objective string of the selected object. * <dt>pronoun * <dd>The pronoun string of the selected object. * <dt>gtitle * <dd>The guild title of the selected object (only useful on livings). * <dt>ashort * <dd>The a_short() call. * <dt>possshort * <dd>The poss_short() call. * <dt>theshort * <dd>The the_short() call. * <dt>oneshort * <dd>The one_short() call. * </dl> * @see set_chat_string() * @see expand_mon_string() * @param in_str the input string * @param on the object to use for the 'o' matching */ string expand_string( string in_str, object on ) { string *str, ret; int i, add_dollar; object liv, *obs, ob; in_str = convert_message( in_str ); str = explode( in_str, "$" ); ret = ""; for( i = 0; i < sizeof( str ); i++ ) { if( i % 2 == 0 ) { if( add_dollar ) ret += "$"; ret += str[i]; add_dollar = 1; ob = 0; } else switch (str[i][0]) { case 'm' : ob = TO; case 'l' : if( !ob ) { if( !liv ) { obs = INV( ENV(TO) ) - ({ TO }); if( sizeof( obs = filter( obs, (: living($1) :) ) ) ) liv = choice( obs ); } if( !liv ) break; ob = liv; } case 'i' : if( !ob ) { if( !liv ) { obs = INV( ENV(TO) ) - ({ TO }); if( sizeof( obs = filter( obs, (: interactive($1) :) ) ) ) liv = choice( obs ); } if( !liv ) break; ob = liv; } case 'a' : if( !ob ) { if( !sizeof( obs = (object *)TO->query_attacker_list() ) ) break; ob = choice( obs ); } case 'o' : if( !ob ) { if( !on ) { obs = INV( ENV(TO) ); if( sizeof( obs = filter( obs, (: !living( $1 ) :) ) ) ) on = choice( obs ); } ob = on; } switch (str[ i ][ 1 .. ]) { case "theshort" : ret += (string)ob->the_short(); add_dollar = 0; break; case "ashort" : ret += (string)ob->a_short(); add_dollar = 0; break; case "oneshort": ret += (string)ob->one_short(); add_dollar = 0; break; case "possshort" : ret += (string)ob->poss_short(); add_dollar = 0; break; case "name" : ret += (string)ob->query_name(); add_dollar = 0; break; case "cname" : ret += (string)ob->query_cap_name(); add_dollar = 0; break; case "gender" : ret += (string)ob->query_gender_string(); add_dollar = 0; break; case "poss" : ret += (string)ob->query_possessive(); add_dollar = 0; break; case "obj" : ret += (string)ob->query_objective(); add_dollar = 0; break; case "gtitle" : ret += (string)ob->query_gender_title(); add_dollar = 0; break; case "pronoun" : ret += (string)ob->query_pronoun(); add_dollar = 0; break; default : if( add_dollar ) ret += "$"; ret += str[i]; add_dollar = 1; break; } ob = 0; break; default : if( add_dollar ) ret += "$"; ret += str[i]; add_dollar = 1; ob = 0; break; } } if( strlen( ret ) && ret[ strlen( ret ) - 1 ] == '$' ) return ret[0..strlen(ret)-2]; return ret; } /* expand_string() */ /** * This method executes the string passed in. It handles all the * stuff which is needed from the chat_string stuff. * * If the input is a function pointer then it is evaluated with one * parameter, being the NPC. * * If the input is a string then the first letter determines what will * be done with it. All these are passed through expand_string * so that exciting things can be done. * <ul> * <li># - A call_other will be generated. The parameters are separated by * ':'s, so "#bing:fred:chicken" would call<pre> * TO->bing("fred", "chicken"); * </pre>. * <ul>', ", : - These will generate a 'say', 'lsay' or 'emote'. * <ul>@ - This will run the passed in command. Eg: "@frog" would cause the * soul command frog to be used. * <ul>Anything else will be used as a message to be sent to everyone in the * room. * </ul> * @param str the thing to execute * @see expand_string() */ void expand_mon_string( mixed str ) { string *args; if( functionp( str ) ) evaluate( str, TO ); else { if( !stringp( str ) && ENV( TO ) ) { tell_room( ENV(TO), "%^RED%^"+ TO->the_short()+" says: please " "bugreport me, I have a bad load_chat.%^RESET%^\n"); } switch( str[ 0 ] ) { case '#' : args = explode( str[ 1..], ":" ); switch( sizeof( args ) ) { case 1 : call_other( TO, args[0] ); break; case 2 : call_other( TO, args[0], args[1] ); break; case 3 : call_other( TO, args[0], args[1], args[2] ); break; case 4 : call_other( TO, args[0], args[1], args[2], args[3] ); break; default : call_other( TO, args[0], args[1], args[2], args[3], args[4] ); break; } break; case ':' : case '\'' : case '"' : init_command( expand_string( str, 0 ), 1 ); break; case '@' : init_command( expand_string( str[ 1 .. ], 0 ), 1 ); break; default : tell_room( ENV(TO), expand_string( str, 0 ) +"\n" ); } } } /* expand_mon_string() */ /** @ignore yes */ void set_name( string name ) { if( query_name() && query_name() != "object" ) return; ::set_name( name ); add_plural( pluralize( name ) ); set_short( name ); set_long("This is a half-fabricate NPC.\n"); set_living_name( name ); } /* set_name() */ /** @ignore yes */ int query_sp() { return 50; } /** @ignore yes */ int adjust_sp( int number ) { return 50; } /** @ignore yes */ void event_whisper( object thing, string start, string mess, object *obs, string lang, object me) { response_mon::event_whisper( thing, mess, obs, lang, me); } /* event_whisper() */ /** * This method sets up the basic abilities and race of the critter. * @see query_race() * @see query_level() * @see query_gender() * @param race this is the race of the character. It should be * a race that's understood by the /std/race.c * @param level this is the level of the NPC. The number is used * by the race object to set ability scores. * @param gender this is the gender of the NPC. It can be either a * number ( 0 - neuter, 1 - male or 2 - female ) or a string ("neuter", * "male", "female") * @example * basic_setup("human", 200, "male"); */ void basic_setup( string race, int level, mixed gender ) { RACE_OBJ->set_level( level, race ); _level = level; set_gender( gender ); } /* basic_setup() */ /** * This method returns the level of the NPC. * @return the level of the NPC * @see basic_setup() */ int query_level() { return _level; } /** * This method sets the message to use when joining into fights. * @param str the message to print when joining a fight * @see query_join_fights() * @see set_join_fight_type() */ void set_join_fights(string str) { join_fight_mess = str; } /** * This method returns the message to use when joining into fights. * @return the message to print when joining a fight * @see set_join_fights() * @see set_join_fight_type() */ string query_join_fights() { return join_fight_mess; } /** * This method sets the flag which allows the NPC to join into fights. * If this is set to a non-zero value then the NPC will join into * fights in progress using the fight joining message. * @param i 1 if the npc is to join fights, 0 if not * @see set_join_fights() * @see query_join_fight_type() */ void set_join_fight_type( int i ) { join_fight_type = i; } /** * This method returns the flag which allows the NPC to join into fights. * @return 1 if the NPC is to join fights, 0 if not * @see set_join_fights() * @see query_join_fight_type() */ int query_fight_type() { return join_fight_type; } /** * This method check to see if the NPC should start attacking someone * when they enter the NPC's environment. It is called from inside * init(). The NPC will only attack if the agressive is set and the * person is visible to be attacked. The property * <pre>"no attack"</pre> can be set on the npc (or player) to * stop them being attacked. * @param who the person to potentially start attacking * @see set_aggressive() */ void start_attack( object who ) { if( !who || !aggressive ) return; if( !who->query_visible(TO) || who->query_auto_loading() || file_name(who) == DEATH || who->query_property("guest") || ( userp(who) && !interactive(who) ) || who->query_property("no attack") ) { return; } // Use a call_other() just in case there are shadows. if( aggressive > 1 || interactive(who) ) TO->attack_ob( who ); } /* start_attack() */ /** * This method returns the current aggressive level of the NPC. * If the aggressive is set to 1, then the NPC will attack all players * that enter its environment. If the aggressive is set to 2 then * the NPC will attack everything (including other NPCs). * <p> * See the function start_attack() for information about things you * can do to stop an aggressive NPC from attacking things. * @return the aggressive level of the NPC * @see set_aggressive() * @see start_attack() */ int query_aggressive() { return aggressive; } /** * This method sets the current aggressive level of the NPC. * If the aggressive is set to 1, then the NPC will attack all players * that enter its environment. If the aggressive is set to 2 then * the NPC will attack everything (including other NPCs). * <p> * See the function start_attack() for information about things you * can do to stop an aggressive NPC from attacking things. * @see query_aggressive() * @see start_attack() * @see set_join_fights() * @see set_throw_out() * @param level the new aggressive level */ void set_aggressive( int level ) { aggressive = level; // this added to make aggressive npcs join in fights if they aren't // already set that way. This is needed coz otherwise you can sneak // into a room and fight the aggressive NPCs one at a time // - Ceres 10/97 if( level && !join_fight_mess ) join_fight_mess = TO->one_short()+" joins in the fight!"; } /* set_aggressive() */ /** @ignore yes */ void init() { set_heart_beat( 1 ); if( !ENV(TO) || file_name( ENV(TO) )[1..4] == "room" ) return; if( aggressive ) call_out("start_attack", 1, TP ); if( _greets && TP && !TP->query_property("dead") && TP->query_visible(TO) && !TO->is_fighting(TP) ) { if( pointerp(to_greet) ) { if( member_array( TP, to_greet ) == -1 ) to_greet += ({ TP }); } else to_greet = ({ TP }); } } /* init() */ /** * This method makes the NPC to equip whatever they have. * @see do_command() */ void init_equip() { command("equip"); } /** @ignore yes */ string long( string str, int dark ) { string s; if( dark < -1 ) s = "You can only make out a rough shape in the gloom.\n"; else if( dark > 1 ) s = "You can only make out a rough shape in the glare.\n"; else s = query_long(); if( !dark ) { s += CAP(HE)+" "+health_string(TO,0)+".\n"; s += CAP(HE)+" is " + query_position_short() + ".\n"; s += ( query_property( PASSED_OUT ) ? CAP(HE)+" appears to be passed out.\n" : "" ); s += calc_extra_look(); s += query_living_contents(0); } return s; } /* long() */ /** * This method returns 1 if it is ok to turn of the NPC's heart beat. * This can be overridden for times when the heart beat needs to be * kept on for some reason. * @return 1 if the heart beat should go off, 0 if it should stay on */ int query_ok_turn_off_heart_beat() { return 1; } /* query_ok_turn_off_heart_beat() */ /** @ignore yes */ varargs int adjust_hp( int num, object attacker, object wep, string attack ) { set_heart_beat( 1 ); return ::adjust_hp( num, attacker, wep, attack ); } /* adjust_hp() */ /** @ignore yes */ int adjust_gp( int number ) { set_heart_beat( 1 ); return ::adjust_gp( number ); } /* adjust_gp() */ /** * This method can be temporarily overwritten in NPCs to stop them * wandering under certain circumstances. For example if someone is * fighting in the room with the NPC, and we want the NPC to watch. * Alternatively to overwriting the function, you can set a "no wander" * property on the NPC. * @return should return 1 if we don't want to wander */ int do_not_wander() { return query_property("no wander"); } /** * This method adds a move zone onto the NPC. The move zones control * which areas the NPC will wander into, a move zone is set on the * room and the NPC will only enter rooms which have a matching * move zone. If there is no move zone, then the NPC will enter * any room. * <p> * If the parameter is an array each of the elements of the array * will be added as a move zone. * @param zone the zone(s) to add * @see remove_move_zone() * @see query_move_zones() * @see set_move_after() * @see do_not_wander() */ void add_move_zone( mixed zone ) { if( !sizeof(zone) ) return; if( !pointerp(move_zones) ) move_zones = ({ }); if( pointerp( zone ) ) { foreach( zone in zone ) add_move_zone( zone ); } else if( stringp(zone) && member_array( zone, move_zones ) == -1 ) move_zones += ({ zone }); } /* add_move_zone() */ /** * This method removes a move zone from the NPC. * @param zone the zone(s) to remove * @see add_move_zone() * @see query_move_zones() * @see set_move_after() */ void remove_move_zone( mixed zone ) { if( !sizeof( move_zones ) ) return; if( pointerp( zone ) ) { move_zones -= zone; return; } move_zones -= ({ zone }); } /* remove_move_zone() */ /** * This method returns the current list of move zones on the NPC * @return the current list of move zones * @see add_move_zone() * @see remove_move_zones() * @see set_move_after() */ string *query_move_zones() { return move_zones || ({ }); } /** * This method sets the speed at which the NPC will randomly * wander around. The NPC will wander at the speed:<pre> * speed = after + random(rand) * </pre>This is called every time the NPC sets up for its next move. * <p> * The move zones control which areas the NPC will wander into, * a move zone is set on the room and the NPC will only enter * rooms which have a matching move zone. If there is no move * zone, then the NPC will enter any room. * @param after the fixed amount of time * @param rand the random amount of time * @see remove_move_zone() * @see query_move_zones() * @see add_move_zone() * @see do_not_wander() */ void set_move_after( int after, int rand ) { move_after = ({ after, rand }); if( after != 0 && rand != 0 ) do_move_after(0); } /* set_move_after() */ /** * This method returns the current move after values. * It returns an array of the form:<pre> * ({ * after, * rand, * }) * </pre> * @return the move after values * @see set_move_after() */ int *query_move_after() { return move_after; } /** * This is called when the NPC decides it must continue down * a certain route. This will be called by the wander handler * and can be used to force the NPC to wander along a route * faster. * @param running_away this is 1 if the npc is running away * @see set_move_after() * @see do_not_wander() */ void do_move_after( int running_away ) { if( sizeof( following_route ) ) do_route_move(); else WANDER_H->move_after( running_away ); } /* do_move_after() */ /** @ignore yes */ void dest_me() { WANDER_H->delete_move_after(TO); ::dest_me(); } /* dest_me() */ /** @ignore yes */ string expand_nickname(string str) { return str; } /** * This method throws away any queued commands. * It doesn't remove the call_out, however if no * new commands are added there will be no effect. * @see init_equip() * @see init_command() * @see delay_command() * @see do_command() * @see queue_command() */ void delete_queued_commands() { queued_commands = 0; } /** * This method returns the queued command list. * @see init_equip() * @see init_command() * @see delay_command() * @see do_command() * @see queue_command() */ mixed query_queued_commands() { return queued_commands || ({ }); } /** @ignore yes */ private void next_queued_command() { mixed next; if( !sizeof( queued_commands ) ) return; // The interval of the next command on queue. next = queued_commands[0]; if( intp( next ) ) { queued_commands = queued_commands[1..]; if( !sizeof( queued_commands ) ) return; next = queued_commands[0]; } while( stringp( next ) ) { if( TO->queue_commands() ) { call_out( (: next_queued_command :), 2 ); return; } do_command( next ); queued_commands = queued_commands[1..]; if( !sizeof( queued_commands ) ) return; next = queued_commands[0]; } call_out( (: next_queued_command :), next ); } /* next_queued_command */ /** * This method allows you to control the NPC and get it to do * actions where they are queued as for players. The command * is always delayed by delay even if there are no commands pending * unlike queue_command(). This function * is 100% compatible with queue_command() and init_command(). * @param words the action to perform * @param interval to wait before the command. * @see queue_command() * @see query_queued_commands() * @see init_command() * @see do_command() * @example * ob = clone_object(NICE_HAIRY_APE); * ob->delay_command("'I am a hairy ape!", 10 ); * ob->delay_command("emote apes around the room.", 2 ); * ob->queue_command("emote get banana.", 3 ); * ob->queue_command("emote get apple."); * After 10 seconds it says "I am a hariy ape", * 2 seconds after that it apes around the room, * immediately following that it gets a banana * and 3 seconds after that it gets an apple. */ int delay_command( string words, int interval ) { if( query_property( PASSED_OUT ) ) return -1; if( stringp( words ) ) { // If there are no queued commands, start the unqueuing process. if( !sizeof( queued_commands ) ) call_out( (: next_queued_command :), interval ); queued_commands = query_queued_commands() + ({ interval, words }); return 1; } printf( "Invalid parameter to delay_command: %O for monster %O in %O.\n", words, TO, ENV(TO) ); return -1; } /* delay_command() */ /** * This method allows you to control the NPC and get it to do * actions where they are queued as for players. If there are no * commands pending the command is executed immediately. This function * is 100% compatible with delay_command() and init_command(). * @param words the action to perform * @param interval the time to wait before processing another command * If omitted defaults to 2 seconds as per players * @see delay_command() * @see query_queued_commands() * @see init_command() * @see do_command() * @example * ob = clone_object(NICE_HAIRY_APE); * ob->queue_command("'I am a hairy ape!"); * ob->queue_command("emote apes around the room.", 5 ); * ob->queue_command("get banana", 10 ); * ob->delay_command("emote get apple.", 3 ); * Right away it says "I am a hairy ape", * 2 seconds later it apes around the room, * 5 seconds after that it gets a banana * and 13 seconds (10+3) after that it gets an apple. */ varargs int queue_command( string words, int interval ) { if( query_property( PASSED_OUT ) ) return -1; if( stringp( words ) ) { if( undefinedp( interval ) ) interval = 2; /* if there are no queued commands, start the unqueuing process */ if( !sizeof( queued_commands ) ) { queued_commands = ({ words, interval }); next_queued_command(); return 1; } /* there are queued commands, so just add them to the existing ones */ queued_commands = queued_commands + ({ words, interval }); return 1; } printf( "Invalid parameter to queue_command: %O for monster %O in %O.\n", words, TO, ENV(TO) ); return -1; } /* queue_command() */ /** @ignore yes */ varargs int move( mixed dest, string messin, string messout ) { int result; object before = ENV(TO); result = living::move( dest, messin, messout ); if( !result ) me_moveing( before ); return result; } /* move() */ /** @ignore yes */ void room_look() { ::room_look(); if( !sizeof( enter_commands ) ) return; foreach( string cmd in enter_commands ) { if( functionp( cmd ) ) { call_out( cmd, 1 + random( sizeof( enter_commands ) + 1 ), TO ); continue; } if( cmd[ 0 .. 0 ] == "#" ) call_out( cmd[1..], 1 + random( sizeof( enter_commands ) + 1 ) ); else init_command( cmd, 1 + random( sizeof( enter_commands ) + 1 ) ); } } /* room_look() */ /** * This method is used to make the NPC run away. This will be * called by the combat code for wimpy when the NPC is bellow the * number of points used to trigger the wimpy action. * @return 1 if successfuly ran away */ int run_away() { if( query_property( "run away" ) == -1 ) return 0; /* just to make npcs a bit trickier... */ do_command("lose all"); become_flummoxed(); if( sizeof( following_route ) ) { do_route_move(); return 1; } if( query_property( "run away" ) ) { do_move_after( 1 ); return 1; } return ::run_away(); } /* run_away() */ /** @ignore yes */ int rand_num( int min, int max ) { return min + random( max - min + 1 ); } /* rand_num() */ /** * This method sets the stats for the NPC to some exciting random * values. * @param min the minimum value of stats * @param max the maximum value of stats */ void set_random_stats( int min, int max ) { set_str( rand_num( min, max ) ); set_dex( rand_num( min, max ) ); set_int( rand_num( min, max ) ); set_con( rand_num( min, max ) ); set_wis( rand_num( min, max ) ); } /* set_random_stats() */ /** * This method adds a command to be called whenever the npc enters * a room. If the command is a string, then it will be executed * as if they had typed it. If it is a function then the function * will be evaluated and one argument (the NPC itself) will be passed * in. * @param str the enter commands to add * @see reset_enter_commands() * @see query_enter_commands() */ void add_enter_commands( mixed str ) { if( !enter_commands ) enter_commands = ({ }); if( stringp( str ) || functionp( str ) ) enter_commands += ({ str }); else if( pointerp( str ) ) foreach( str in str ) add_enter_commands( str ); } /* add_enter_commands() */ /** * This method returns the current array of enter commands. * @return the current array of enter commands * @see reset_enter_commands() * @see add_enter_commands() */ string *query_enter_commands() { return enter_commands || ({ }); } /** * This method resets the array of enter commands back to nothing. * @see add_enter_commands() * @see query_enter_commands() */ void reset_enter_commands() { enter_commands = 0; } /** @ignore yes */ void event_enter( object dest, string mess, object from ) { // Stop massive spam in /room/rubbish and /room/void. if( ENV(TO) && file_name( ENV(TO) )[1..4] == "room" ) return; living::event_enter( dest, mess, from ); } /* event_enter() */ /** * This method is used to make the NPC follow after attackers * when they leave the room. * @param who the person we are chasing * @param dir the direction to follow them in */ void do_follow_move( object who, string dir ) { chase_id = 0; // Only chase if we are not following a route, // and are not fighting anyone here. if( !sizeof(following_route) && !TO->query_fighting() ) do_command(dir); } /* do_follow_move() */ /** @ignore yes */ void event_exit( object who, string mess, object to ) { string fname; mixed exits; int i; new_parse::event_exit( who, mess, to ); living::event_exit( who, mess, to ); // Don't move if move_after isn't set, or if following a route. if( !to || !move_after || sizeof( following_route ) ) return; // Chase the sucker, if they're on our attacker list. if( member_array( who, TO->query_attacker_list() ) == -1 ) return; exits = ENV(TO)->query_dest_dir(); fname = file_name(to); // No exit to destination. if( ( i = member_array( fname, exits ) ) == -1 ) return; // Don't follow if we are only supposed to wander in move_zones and // don't have unrestricted follow set. if( !TO->query_property("unrestricted follow") && !sizeof( to->query_zones() & query_move_zones() ) ) return; if( chase_id ) remove_call_out( chase_id ); chase_id = call_out( (: do_follow_move :), 3 + random(follow_speed), who, exits[i-1] ); } /* event_exit() */ /** * This method will set up a 'home' location for the NPC. * This is the file name of the room the NPC * should return to whenever it is moved. * It can be used to make unique NPCs returns * to their default location etc. * @param loc the filename of the room to set as home */ void set_home_location( string loc ) { _home = loc; } /* set_home_location() */ /** * This method will return the NPC's home location. * @return the home location */ string query_home_location() { return _home; } /** @ignore yes */ void event_move_object( mixed from, mixed to ) { ::event_move_object( from, to ); if( chase_id ) remove_call_out( chase_id ); if( _home && find_call_out("check_home_location") == -1 ) call_out("check_home_location", 4 + random( 4 ) ); } /* event_move_object() */ /** @ignore yes */ void check_home_location() { string where; if( ENV(TO) && ( where = base_name(ENV(TO)) ) && where[1..4] != "room" && where != _home && !sizeof( TO->query_following_route() ) ) TO->move_me_to( _home ); } /* check_home_location() */ /** @ignore yes */ int query_time_left() { return 1; } /** * This function makes the more intelligent NPCs pick up their * weapons after being disarmed. * It can be overwritten if you don't want the default disarm * response to work for your NPC. * @param disarmer the person who disarmed us * @param weapon the weapon that was disarmed */ void event_disarm( object disarmer, object weapon ) { int i, time; if( !objectp(weapon) ) return; i = (int)TO->query_int(); if( ( time = 10 - sqrt( i ) ) < 4 ) time = 4; if( i >= 20 || i > random( 60 ) ) { if( ENV(weapon) == ENV(TO) ) init_command("get "+file_name(weapon), time++ ); init_command("hold "+file_name(weapon), time ); } } /* event_disarm() */ /** * This method queries the greetings of the NPC, * if there are any set. * @return the greetings array */ string *query_greetings() { return _greets; } /** * This method sets up the greeting strings to use when * someone enters the room the NPC is in. * This is useful for shopkeepers and more unique NPCs. * $ob$ in greeting strings will be replaced with * the people that are being greeted. * * To use a greeting in form of a soul command, please * use an "@" in front of the greeting string. Soul greetings * will be called on each person individually, and it is * possible to set up random souls by separating different * soul commands by a ":" in the greeting string (see example). * * It is also possible to use a function instead of a string * greeting. The function can either be a function * pointer or a string of the following format - * "#file_name:function", and will then be called with * the following arguments - the thing (object) doing * the greeting, and the array of objects to greet. * * NOTE : The NPC will only greet those livings he can see, * and that are not ghosts or otherwise dead. * * @param str the greeting or array of greetings to use * @example set_greeting( ({"Greetings, $ob$!", (: do_greet :) }) ); * @example set_greeting( ({"'Hello $ob$!", "#"+PUB+"pub:do_greet"}) ); * @example set_greeting("@salute $ob$:bow $ob$:bing $ob$"); */ void set_greeting( mixed str ) { string *tmp; if( !_greets ) _greets = ({ }); if( stringp(str) && sizeof(str) ) { if( str[0] == "#" ) { str = str[1..]; if( sizeof( tmp = explode( str, ":" ) ) < 2 ) { write("Greeting function missing a function name in "+ file_name(TO)+".\n"); return; } if( !find_object(tmp[0]) && !file_exists( tmp[0]+".c" ) ) { write("Greeting function pointing to a non-existent " "file ("+tmp[0]+".c) in "+file_name(TO)+".\n"); return; } } _greets += ({ str }); } else if( pointerp(str) ) _greets += str; else if( functionp(str) ) _greets += ({ str }); } /* set_greeting() */ /** @ignore yes */ object *query_to_greet() { return to_greet; } /** * The main heart beat function. This is called by the driver * every 2 seconds. Does all the maintenance stuff like fixing * up hps, greeting people and stuff like that. */ void heart_beat() { int i, j, d; mixed using; string *tmp; living::heart_beat(); if( hp < 0 || query_property("dead") ) d = 1; i = check_env(); if( !d && i && _greets && sizeof( to_greet ) && sizeof( to_greet = filter( to_greet, (: $1 && ENV($1) == ENV(TO) :) ) ) ) { if( functionp( using = choice(_greets) ) ) { evaluate( using, TO, to_greet ); } else if( sizeof( to_greet = filter( to_greet, (: !TO->is_fighting($1) :) ) ) ) { switch( using[0] ) { case '#' : tmp = explode( using[1..], ":"); call_other( tmp[0], tmp[1], TO, to_greet ); break; case '@' : tmp = explode( using[1..], ":"); foreach( object ob in to_greet ) do_command( replace( choice(tmp), "$ob$", ob->query_name() ) ); break; default : do_command( replace( using, "$ob$", query_multiple_short( map( to_greet, (: $1->short() :) ) ) ) ); } } } to_greet = 0; if( hp == max_hp && gp == max_gp && TO->query_ok_turn_off_heart_beat() ) { if( !i || ( !chats && !a_chats ) ) { set_heart_beat( 0 ); return; } } if( !d && !query_chats_off() && i ) { if( TO->query_fighting() ) { if( sizeof(doing_story) && sizeof( doing_story[ 1 ] ) ) { expand_mon_string( doing_story[ 1 ][ 0 ] ); doing_story[ 1 ] = doing_story[ 1 ][ 1 .. ]; } else if( a_chats && random( 1000 ) < a_chats->chance && sizeof( a_chats->weight ) ) { i = random( a_chats->total_w + 1 ); while( ( i -= a_chats->weight[ j ] ) > 0 ) j++; if( j == a_chats->last_chat ) j = ( j + 1 ) % sizeof( a_chats->weight ); a_chats->last_chat = j; if( pointerp( a_chats->chats[ j ] ) ) { expand_mon_string( a_chats->chats[ j ][ 0 ] ); if( !doing_story ) doing_story = allocate( 2, ({ }) ); doing_story[ 1 ] = a_chats->chats[ j ][ 1 .. ]; } else expand_mon_string( a_chats->chats[ j ] ); } } else if( chats ) { if( sizeof(doing_story) && sizeof( doing_story[ 0 ] ) ) { expand_mon_string( doing_story[ 0 ][ 0 ] ); doing_story[ 0 ] = doing_story[ 0 ][ 1 .. ]; } else if( chats && random( 1000 ) < chats->chance && sizeof( chats->weight ) ) { i = random( chats->total_w + 1 ); while( ( i -= chats->weight[ j ] ) > 0 ) j++; if( j == chats->last_chat ) j = ( j + 1 ) % sizeof( chats->weight ); chats->last_chat = j; if( pointerp( chats->chats[ j ] ) ) { expand_mon_string( chats->chats[ j ][ 0 ] ); if( !doing_story ) doing_story = allocate( 2, ({ }) ); doing_story[ 0 ] = chats->chats[ j ][ 1 .. ]; } else expand_mon_string( chats->chats[ j ] ); } } } } /* heart_beat() */ /** * This method queries the speed at which the NPC will follow * after a player, when they leave combat. * @return the current follow speed * @see set_follow_speed() */ int query_follow_speed() { return follow_speed; } /** * This method sets the speed at which the NPC will follow * after a player, when they leave combat. * @param speed the new follow speed * @see query_follow_speed() */ void set_follow_speed( int speed ) { follow_speed = speed; } /** * This method checks to see if there are any players in the environment * of the NPC. This should be used to determine if chats should * be turned off and other things which should only work in the * presence of players. * @return 1 if there is a player in the room, 0 if not */ int check_env() { object thing; if( !ENV(TO) || file_name( ENV(TO) )[1..4] == "room") return 0; if( ENV(TO)->query_linked() ) return 1; foreach( thing in INV( ENV(TO) ) ) if( interactive( thing ) || thing->query_slave() ) return 1; return 0; } /* check_env() */ /** * This method causes the NPC to move in the given direction. * @param move the direction to move in */ void do_move( string move ) { command( move ); } /* do_move() */ /** * This method gets the next direction to go in the route which * is currently being followed and will remove this direction * from the array. * @return the next direction to go to * @see query_following_route() * @see do_route_move() */ string get_next_route_direction() { string direc; if( !sizeof( following_route ) ) return 0; direc = following_route[0]; following_route = following_route[1..]; return direc; } /* get_next_route_direction() */ /** * This method returns the current array of directions we are following * as a route. * @see get_next_route_direction() * @see do_route_move() */ string *query_following_route() { return following_route || ({ }); } /** * This method moves the NPC one more location along the route it * is following. * @see query_following_route() * @see get_next_route_direction() */ void do_route_move() { if( !sizeof( following_route ) ) return; do_command( get_next_route_direction() ); } /* do_route_move() */ /** * This method is called by the move_me_to function after the * route handler has successfuly discovered the route to follow. * @param route the route to follow * @param delay the delay to follow it with * @param dest route destination * @see move_me_to() */ protected void got_the_route( string *route, int delay, string dest ) { following_route = route; if( sizeof(route) ) { WANDER_H->move_me_please( delay, dest ); do_route_move(); } else TO->stopped_route(); } /* got_the_route() */ /** * This method will move the NPC to the specified destination. The * NPC will walk from where they currently are to the destination using * the time delay specified between the movements. * <p> * If the location is reached then the function "stopped_route" will * be called on the npc. The first argument to the function will * be 0 if the npc did not reach its destination and 1 if it did. * @param dest the destination to go to * @param delay the time delay between each move * @example * inherit "/std/npc"; * * void go_home() { * move_me_to(HOME_LOCATION, 2); * } /\* go_home() *\/ * * void stopped_route(int success) { * if (success) { * do_command("emote jumps for joy."); * } else { * do_command("emote looks sad and lost."); * } * } /\* stopped_route() *\/ * @see get_next_route_direction() * @see got_the_route() * @see query_following_route() * @see do_route_move() */ varargs void move_me_to( string dest, int delay ) { string *dest_dir, *start_dir; if( !ENV(TO) ) return ; // Very likely to have no route. if( !MAP_H->static_query_short( dest ) || !MAP_H->static_query_short( file_name( ENV(TO) ) ) ) { // One of the rooms has no exits. if( !sizeof( dest_dir = dest->query_dest_dir() ) || !sizeof( start_dir = ENV(TO)->query_dest_dir() ) ) { call_out("stopped_route", 1 ); return; } call_out("stopped_route", 1 ); return; } ROUTE_HANDLER->get_route( dest, file_name( ENV(TO) ), (: got_the_route( $1, $(delay), $(dest) ) :) ); } /* move_me_to() */ /** @ignore yes */ int clean_up( int parent ) { if( parent || query_property("unique") || query_hp() < query_max_hp() || check_env() ) return 1; move("/room/rubbish"); return 1; } /* clean_up() */ /** * This method sets the value of the unable to change position flag. * This flag will be checked by the soul, and by anything else which * deliberatly changes someones position. * @param flag the unable to change position flag * @see /std/living/living->set_default_position() */ void set_cannot_change_position( int flag ) { cannot_change_position = flag; } /* set_cannot_change_position() */ /** * This method returns the current value of the unable to change * position flag. * @return the unable to change position flag * @see /std/living/living->set_default_position() */ int query_cannot_change_position() { return cannot_change_position; } /* query_cannot_change_position() */ /** * This method overrides the position code, so that if the position * is changed we are changed back to the default position. * @param new_pos the new position * @ignore yes */ void set_position( string new_pos ) { if( always_return_to_default_position && !query_property(PASSED_OUT) && new_pos != query_position() && TP != TO ) call_out( (: return_to_default_position(0) :), always_return_to_default_position ); ::set_position( new_pos ); } /* set_position() */ /** * This method sets the status of the flag that makes the NPC return * to the default position if its position is changed. The flag * specifies the length of time to wait before causing the * default position to be restored. * @param tim the time to wait before the position is restored * @see /std/living/living->return_to_default_position() * @see query_always_return_to_default_position() */ void set_always_return_to_default_position( int tim ) { always_return_to_default_position = tim; } /* set_always_return_to_default_position() */ /** * This method returns the status of the flag that makes the NPC return * to the default position if its position is changed. The flag * specifies the length of time to wait before causing the * default position to be restored. * @return the time to wait before the position is restored * @see /std/living/living->return_to_default_position() * @see set_always_return_to_default_position() */ int query_always_return_to_default_position() { return always_return_to_default_position; } /* query_always_return_to_default_position() */ /** * This method can be used to turn the chats of the NPC on/off. * @param i 1 to turn the chats off, 0 to turn them on */ void set_chats_off( int i ) { chats_off = i; } /** * This method queries whether or not the chats of the NPC * have been turned off. * @return 1 if the chats are off, 0 if chats are on */ int query_chats_off() { return chats_off; } /** * This method loads up the chat strings for the NPC. This will be * an array containing pairs of elements, the first pair is the * weighting of the chat and the second is the chat to use. * <p> * All the weights in the array are added up and then a random * number is chosen in the weighting. Then that element is looked * up in the array. This way you can control a chat and make it * rare. * <p> * If the chat string is an array then this a story, the story will be * executed one after another and no other chats will be executed * in between. The chat chance is still used to determine when the * chats will occur. Special strings can be used which will replace with * object names, see expand_mon_string() for further information. * <p> * The chat chance is a chance (in 1000) of the chat occuring. You * will need to play with this yourself to see which frequency of * chatting you wish for your NPC. * @example * load_chat(60, * ({ * 1, "'I am a chicken!", * // Make this one more likely to occur. * 2, ":clucks like a chicken." * 1, ":pecks at $lpossshort$ foot." * })); * @example * load_chat(100, * ({ * 1, "'I am a simple farmer." * 1, ":waves $mposs$ pitchfork around." * // A story, they will always occur in this order * 1, ({ * "'Once upon a time there was a rabbit.", * "'It was a nice rabbit and hung around in bars.", * "'It sung little songs about fruit.", * }), * })); * @see expand_mon_string() * @param chance the chance in 1000 of a chat working every 2 seconds * @param data the chats and chat weights to use * @see load_a_chat() * @see query_chat_chance() * @see query_chat_string() */ void load_chat( int chance, mixed data ) { int i; if( chance < 1 ) error("Invalid chat chance in load_chat().\n"); if( !i = sizeof(data) / 2 ) error("Invalid chats in load_chat().\n"); chats = new( class chat_data, chance : chance, chats : allocate( i ), weight : allocate( i ) ); while( i-- ) { chats->total_w += data[i*2]; chats->weight[i] = data[i*2]; chats->chats[i] = data[i*2+1]; } } /* load_chat() */ /** * This method sets the current chat chance for the NPC. * @param i the chat chance * @see load_chat() * @see query_chat_chance() */ void set_chat_chance( int i ) { if( !chats ) error("Trying to specify a chat chance with no chats.\n"); chats->chance = i; } /* set_chat_chance() */ /** * This method returns the current chat chance for the NPC. * @return the current chat chance * @see set_chat_chance() * @see load_chat() */ int query_chat_chance() { if( chats ) return chats->chance; return 0; } /* query_chat_chance() */ /** * This method queries the current chat strings for the NPC. * The return value is an array of three elements where the * first member is the sum of the weights of all chats, the * second member contains the weights of chats, and the third * member contains the chat strings. * chat string is formatted. * @return the current chat strings * @see load_chat() * @see query_chat_string() */ mixed query_chat_string() { if( chats ) return ({ chats->total_w, chats->weight, chats->chats }); return 0; } /* query_chat_string() */ /** * This method adds an array of chats or a single chat string into * the current list of chat strings. See load_chat() for a longer * description of the chat string. * @param weight the weight of the chat, or an array of weights and chats * @param chat the new chat string * @see load_chat() * @see remove_chat_string() */ varargs void add_chat_string( mixed weight, mixed chat ) { int i; if( !chats ) error("Cannot use add_chat_string() on an NPC with no chats.\n"); if( pointerp( weight ) ) { i = sizeof( weight ) / 2; while( i-- ) add_chat_string( weight[i*2], weight[i*2+1] ); } else if( member_array( chat, chats->chats ) == -1 ) { chats->total_w += weight; chats->weight += ({ weight }); chats->chats += ({ chat }); } } /* add_chat_string() */ /** * This method attempts to remove the given chat string from the * current list of chat strings. The chat message is checked * to see if it exists in the array, the weighting of the * string is ignored. * @param chat the chat string to remove * @see add_chat_string() * @see load_chat() */ void remove_chat_string( mixed chat ) { int i; if( !chats ) return; if( pointerp( chat ) ) { map( chat, (: remove_chat_string($1) :) ); } else if( ( i = member_array( chat, chats->chats ) ) != -1 ) { chats->total_w -= chats->weight[i]; chats->weight = delete( chats->weight, i, 1 ); chats->chats = delete( chats->chats, i, 1 ); } } /* remove_chat_string() */ /** * This method loads up the set of chat strings to use while in combat. * @param chance the chance of the chat occuring * @param data the chat strings and weights to use * @see load_chat() * @see query_achat_chance() * @see query_achat_string() */ void load_a_chat( int chance, mixed data ) { int i; if( chance < 1 ) error("Invalid chat chance in load_a_chat().\n"); if( !i = sizeof(data) / 2 ) error("Invalid chats in load_a_chat().\n"); a_chats = new( class chat_data, chance : chance, chats : allocate( i ), weight : allocate( i ) ); while( i-- ) { a_chats->total_w += data[i*2]; a_chats->weight[i] = data[i*2]; a_chats->chats[i] = data[i*2+1]; } } /* load_a_chat() */ /** * This method sets the current aggressive chat chance the NPC. * @param i the attack message chat chance * @see load_chat() * @see load_achat() * @see query_achat_chance() */ void set_achat_chance( int i ) { if( !a_chats ) error("Trying to specify an achat chance with no achats.\n"); a_chats->chance = i; } /* set_achat_chance() */ /** * This method returns the current attack chat chance for the NPC. * @return the current chat chance * @see set_achat_chance() * @see load_chat() * @see load_a_chat() */ int query_achat_chance() { if( a_chats ) return a_chats->chance; return 0; } /* query_achat_chance() */ /** * This method queries the current aggressive chat strings for the NPC. * The return value is an array of three elements where the * first member is the sum of the weights of all chats, the * second member contains the weights of chats, and the third * member contains the chat strings. * chat string is formatted. * @return the current chat strings * @see load_chat() * @see query_chat_string() */ mixed query_achat_string() { if( a_chats ) return ({ a_chats->total_w, a_chats->weight, a_chats->chats }); return 0; } /* query_achat_string() */ /** * This method adds an array of attack chats or a single attack chat * string into the current list of achat strings. * See load_chat() for a longer description of the chat string. * @param weight the weight of the chat, or an array of weights and chats * @param chat the new chat string * @see load_chat() * @see load_a_chat() * @see remove_achat_string() */ varargs void add_achat_string( mixed weight, mixed chat ) { int i; if( !a_chats ) error("Cannot use add_achat_string() on an NPC with no achats.\n"); if( pointerp( weight ) ) { i = sizeof( weight ) / 2; while( i-- ) add_achat_string( weight[i*2], weight[i*2+1] ); } else if( member_array( chat, a_chats->chats ) == -1 ) { a_chats->total_w += weight; a_chats->weight += ({ weight }); a_chats->chats += ({ chat }); } } /* add_achat_string() */ /** * This method attempts to remove the given chat string from the * current list of attack message chat strings. The chat message * is checked to see if it exists in the array, the weighting of * the string is ignored. * @param chat the chat string to remove * @see add_achat_string() * @see load_chat() * @see load_achat() */ void remove_achat_string( mixed chat ) { int i; if( !a_chats ) return; if( pointerp( chat ) ) { map( chat, (: remove_achat_string($1) :) ); } else if( ( i = member_array( chat, a_chats->chats ) ) != -1 ) { a_chats->total_w -= a_chats->weight[i]; a_chats->weight = delete( a_chats->weight, i, 1 ); a_chats->chats = delete( a_chats->chats, i, 1 ); } } /* remove_achat_string() */ /** * This method adds a combat action to the NPC. This is an action which * has a chance of occuring during combat. The name is an identifier * which can be used to remove the action with later. The action * itself can be a string, then that command will be executed. If * the action is a function pointer then it will be evaluated with * two arguments, the first being the attacker, the second being the * target. * <p> * If the action is an array, if it is one element then the function * specified will be called on the attacked with the same arguments * as above. If the size of the array is two then the function will * be called on the specified object with the arguments as above. * @see remove_combat_action() * @see query_combat_actions() * @see /std/effects/fighting/combat.c */ void add_combat_action( int chance, string name, mixed action ) { int i; if( !combat_actions ) combat_actions = ({ 0 }); if( ( i = member_array( name, combat_actions ) ) == -1 ) { combat_actions += ({ chance, name, action }); combat_actions[ 0 ] += chance; } else { combat_actions[ 0 ] += chance - combat_actions[ i - 1 ]; combat_actions[ i - 1 ] = chance; combat_actions[ i + 1 ] = action; } } /* add_combat_action() */ /** * This method will remove the combat action with the specified name. * @return 1 if it is found and removed, 0 if not * @see add_combat_action() * @see query_combat_actions() * @see /std/effects/fighting/combat.c */ int remove_combat_action( string name ) { int i; if( !combat_actions ) return 0; if( ( i = member_array( name, combat_actions ) ) == -1 ) return 0; combat_actions[ 0 ] -= combat_actions[ i - 1 ]; combat_actions = delete( combat_actions, i - 1, 3 ); return 1; } /* remove_combat_action() */ /** * This method returns the current array of combat actions on the * NPC. * <p> * The array will have the format of: * <pre> * ({ * total_chance, * action1_chance, * action1_name, * action1_action, * ... * }) * </pre> * @return the combat action array * @see add_combat_action() * @see remove_combat_actions() * @see /std/effects/fighting/combat.c */ mixed query_combat_actions() { return copy( combat_actions ) || ({ 0 }); } /** * This method returns the amount of death experiecne that would be * gained by killing the NPC. * @return the amount of death experience for the npc */ int query_death_xp() { int amount; if( TO->query_property("dead") || TO->query_property("unique") ) return 0; if( death_xp_set ) return death_xp; amount = (int)TOP_TEN_HANDLER->calculate_rating( TO ); amount += (int)TO->query_max_hp(); return amount / 4; } /* query_death_xp() */ /** * This method sets the amount of death experience that would be * gained by killing the NPC. */ int set_death_xp( int amount ) { death_xp = amount; death_xp_set = 1; } /* set_death_xp() */ /** * This event is called when a fight is in progress. It will * be used for things like joining into currently running * fights and initiating combat with spell casters. * @param me the person initiating the attack * @param him the person being attacked */ void event_fight_in_progress( object me, object him ) { if( !me || ! him ) return; if( him == TO && TO->query_property("see_caster") && !userp(me) && random( him->query_property("see_caster") ) < him->query_int() ) TO->attack_ob( me->query_owner() ); if( !join_fight_mess || him == TO ) return; // Only attack NPCs if join_fight_type is not 0. if( !join_fight_type && !interactive( him ) ) return; if( member_array( him, (object *)TO->query_attacker_list() ) == -1 && !him->query_property("no attack") ) { if( join_fight_mess ) expand_mon_string( join_fight_mess ); TO->attack_ob( him ); } } /* event_fight_in_progress() */ /** @ignore yes */ object *query_ignoring( object *people ) { return ({ }); } /** @ignore yes */ object *query_ignored_by( object *people ) { return ({ }); } /** @ignore yes */ mixed stats() { mixed zones; int i; zones = allocate( i = sizeof(zones) ); while( i-- ) zones[i] = ({"move zone "+i, move_zones[i] }); if( !query_move_after() ) { return living::stats() + ({ ({ "death xp", query_death_xp() }), ({ "aggressive", query_aggressive() }), ({ "join fights", query_join_fights() }), ({ "race", query_race() }), ({ "race ob", query_race_ob() }), ({ "level", query_level() }), ({ "follow speed", query_follow_speed() }), ({ "home", query_home_location() }), ({ "chats off", query_chats_off() }), ({ "chat chance", query_chat_chance() }), ({ "achat chance", query_achat_chance() }), ({ "route", ( sizeof( following_route ) ? implode( following_route, ", ") : "not going anywhere" ) }), }) + zones; } return living::stats() + ({ ({ "death xp", query_death_xp() }), ({ "aggressive", query_aggressive() }), ({ "join fights", query_join_fights() }), ({ "race", query_race() }), ({ "race ob", query_race_ob() }), ({ "level", query_level() }), ({ "follow speed", query_follow_speed() }), ({ "home", query_home_location() }), ({ "chats off", query_chats_off() }), ({ "chat chance", query_chat_chance() }), ({ "achat chance", query_achat_chance() }), ({ "move after-fix", query_move_after()[0] }), ({ "move after-rand", query_move_after()[1] }), ({ "route", ( sizeof( following_route ) ? implode( following_route, ", " ) : "not following anyone") }), }) + zones; } /* stats() */ #if !efun_defined(add_action) /** @ignore yes */ protected string _process_input( string str ) { object ob; if( !str || str == "" ) return 0; ob = TP; set_this_player(TO); _notify_fail(0); if( !sizeof( explode( str, " " ) - ({ "", 0 }) ) || str[0] == ',' ) { set_this_player(ob); return 0; } if( !TO->drunk_check(str) && !TO->exit_command(str) && !TO->cmdAll(str) && !TO->new_parser(str) && !TO->lower_check(str) ) { set_this_player(ob); return 0; } set_this_player(ob); return "bing"; } /* _process_input() */ #endif