/* -*- LPC -*- */ /* * $Locker: pinkfish $ * $Id: monster.c,v 1.85 2003/07/28 21:05:41 ceres Exp pinkfish $ */ /** * This file contains all the information relevant to creating an * npc (or monster). * @author Pinkfish */ #include <config.h> #include <living.h> #include <map.h> #include <monster.h> #include <move_failures.h> #include <player.h> #include <skills.h> #include <wander.h> #include <route.h> #include <combat.h> #define SPELL_INCLUDE_CLASS #include <top_ten_tables.h> #define SOUL_OBJECT "/obj/handlers/soul" inherit "/std/living/mon_actions"; inherit "/std/living/living"; inherit "/std/living/response_mon"; inherit "/global/events"; inherit "/global/guild-race"; nosave string cap_name; nosave mixed *chat_string; nosave mixed *achat_string; private nosave mixed *_combat_actions; nosave mixed _move_after; nosave mixed *throw_out; nosave int chat_chance; nosave int achat_chance; nosave int aggressive; nosave int join_fight_type; nosave int follow_speed; nosave int virtual_move; nosave int moves; nosave string race; nosave string guild; nosave string join_fight_mess; nosave string true_location; nosave mixed *enter_commands; nosave string *move_zones; private nosave mixed *_queued_commands; private nosave mixed *doing_story; nosave object last_attacked; private nosave string *following_route; private nosave int added_language; private nosave function cmd_func = 0; // This is used to make sure players cannot change an npc's positon with // souls. private nosave int cannot_change_position; private nosave int always_return_to_default_position; private nosave mapping _spell_actions; void do_move_after(int running_away); void do_route_move(); private void _next_queued_command(); void start_attack( object who ); #if !efun_defined(add_action) protected mixed _process_input(string); protected mixed command(string); void command_override(function func); int drunk_check(string str); #endif void create() { do_setup++; events::create(); living::create(); mon_actions::create(); response_mon::create(); //p command::create(); do_setup--; reset_get(); follow_speed = 3; doing_story = ({ ({ }), ({ }) }); chat_string = ({ 0, ({ }) }); achat_string = ({ 0, ({ }) }); move_zones = ({ }); _spell_actions = ([ ]); _combat_actions = ({ }); enter_commands = ({ }); known_commands = ({ }); _queued_commands = ({ }); following_route = ({ }); enable_commands(); living_commands(); parser_commands(); communicate_commands(); command_commands(); add_property("npc",1); set_rows( 24 ); set_cols( 200 ); // Default to 5 seconds. always_return_to_default_position = 5; #ifdef OLD_SOUL add_action("*", "soul_commqz", -2); #endif /* add_action("init_race", "init_race"); race_guild_commands(); */ set_con( 13 ); set_dex( 13 ); set_int( 13 ); set_str( 13 ); set_wis( 13 ); set_max_hp( 10000 ); set_hp( 100000 ); set_max_gp( 10000 ); set_gp( 10000 ); if ( !do_setup ) this_object()->setup(); if(clonep()) call_out(function() { if(!environment()) { move(find_object("/room/rubbish")); } }, 60); } /* create() */ /** * This method sets up a nationality and sets up a region in the * nationality for the npc. * @param nationality the nationality to set * @param region the region in the nationality */ void setup_nationality(string nationality, string region) { set_nationality(nationality); set_nationality_region(region); if (!load_object(nationality)) { debug_printf("Bad nationality, %O\n", nationality); } else { if (!nationality->query_region_description(region)) { debug_printf("Bad region %O in nationality, %O\n", region, nationality); } add_language(nationality->query_language()); set_language(nationality->query_language()); set_default_language(nationality->query_language()); } } /** @ignore yes */ void dest_me() { living::dest_me(); } /* dest_me() */ /** @ignore yes */ int soul_commqz(string str) { string verb, bit; if (sscanf(str, "%s %s", verb, bit) == 2) return (int)SOUL_OBJECT->soul_command(verb, bit); return (int)SOUL_OBJECT->soul_command(str, ""); } /* soul_commqz() */ /** @ignore yes */ int query_sp() { return 50; } /** @ignore yes */ int adjust_sp( int number ) { return 50; } /** * This method returns the current capitalized name of the npc. * @return the current capitalized name * @see set_cap_name() */ string query_cap_name() { return cap_name; } /* query_cap_name() */ /** * This method set the current capitalized name of the npc. * @param s the capitalized name of the npc * @see query_cap_name() */ void set_cap_name(string s) { cap_name = s; } /** @ignore yes */ int soul_com_force(string str) { // string str1,str2; if (file_name(previous_object()) != SOUL_OBJECT) return 0; command(str); return 1; } /* soul_com_force() */ /** @ignore yes */ void set_name(string n) { if (query_name() && query_name() != "object") return; ::set_name(n); cap_name = capitalize(query_name()); add_plural(pluralize(query_name())); set_short( query_name() ); set_long("You see nothing special.\n"); set_living_name(n); } /* set_name() */ /** @ignore yes */ string long(string str, int dark) { string s; if (dark < -1) { s = "You can only make out a rough shape in the glare.\n"; } else if (dark > 1) { s = "You can only make out a rough shape in the gloom.\n"; } else { s = query_long(); } if(!dark) { s += capitalize(query_pronoun())+" "+health_string()+".\n"; s += capitalize(query_pronoun()) + " is " + this_object()->query_position_short() + ".\n"; s += calc_extra_look(); s += query_living_contents(0); } return s; } /* long() */ /** * This function is deprecated. Use basic_setup() instead. * * This method sets the race of the npc. The race should be one of * the races listed in the /std/race.c object. * This is used in conjuction with the * guild when set_level is called to setup the default * attributes for the npc. This should only be * called *before* set_level() is called. * @param str the race to set * @return always returns 1 * @example * ob = clone_object("/obj/monster"); * ob->set_race("fish"); * @example * inherit "/obj/monster"; * * void setup() { * ... * set_race("fish"); * ... * set_level(12); * } /\* setup() *\/ * @see query_race() * @see query_guild() * @see set_guild() * @see set_level() * @see basic_setup() */ int set_race(string str) { race = str; return 1; } /* set_race() */ /** * This method returns the current race of the npc. * @return the current race of the object * @see set_race() * @see query_guild() * @see set_level() */ string query_race() { return race; } /** * This method returns the current guild of the npc. * @return this current guild of the npc * @see query_race() * @see set_guild() * @see set_level() */ string query_guild() { return guild; } /** * @ignore yes * This method should not be used. It is 'depreciated'. */ string query_class() { return guild; } /** * @ignore yes * This method should not be used. It is 'depreciated'. */ string query_profession() { return guild; } /** * @ignore yes * This method should not be used. It is 'depreciated'. */ int set_class(string str) { guild = str; } /* set_guild() */ /** * This method is deprecated. Use basic_setup() instead. * * This method sets the current guild of the npc to the * passed in value. The guild should be one of * the guilds listed in the /std/race.c object. * This is used in conjuction with the * race when set_level is called to setup the default * attributes for the npc. This should only be * called *before* set_level() is called. * @param str the new guild for the npc * @example * ob = clone_object("/obj/monster"); * ob->set_guild("fighter"); * @example * inherit "/obj/monster"; * * void setup() { * ... * set_race("fighter"); * ... * set_level(12); * } /\* setup() *\/ * @see query_race() * @see query_guild() * @see set_guild() * @see set_level() */ int set_guild(string str) { guild = str; } /* set_guild() */ /** * @ignore yes * This method should not be used. It is 'depreciated'. */ int set_profession(string str) { guild = str; } /* set_profession() */ /** * This method makes the npc initialise all their equipment, like hold * it and stuff. * @see do_command() */ void init_equip() { command("equip"); } /** * This method allows you to control the npc and get it to do * actions. This can be used for npc control and inteligence. * * Be very careful with this command! This does not go through any * command queue like players have and so NPCs can end up doing * tons of commands in very short order. * * If you're trying to make your NPC act like a player use * eue_command() instead * * @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 ( this_object()->query_property( PASSED_OUT_PROP ) ) { return -1; } if ( stringp( words ) ) { return command( words ); } printf( "Invalid parameter to do_command: %O for monster %O in %O.\n", words, this_object(), environment() ); return -1; } /* do_command() */ /** * 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 = ({ }); } /** * 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; // No actions while casting. if(this_object()->query_casting_spell()){ call_out( (: _next_queued_command :), 2 ); return; } 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 (this_object()->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 ( this_object()->query_property( PASSED_OUT_PROP ) ) { return -1; } if ( stringp( words ) ) { if (!sizeof(_queued_commands)) { call_out( (: _next_queued_command :), interval ); } _queued_commands = _queued_commands + ({ interval, words }); return 1; } printf( "Invalid parameter to delay_command: %O for monster %O in %O.\n", words, this_object(), environment() ); 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 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 ( this_object()->query_property( PASSED_OUT_PROP ) ) { return -1; } if ( stringp( words ) ) { if (undefinedp(interval)) interval=2; if (!sizeof(_queued_commands)) { _queued_commands = ({ words, interval }); _next_queued_command(); return 1; } _queued_commands = _queued_commands + ({ words, interval }); return 1; } printf( "Invalid parameter to queue_command: %O for monster %O in %O.\n", words, this_object(), environment() ); return -1; } /* queue_command() */ /** * This method allows you submit delayed commands to the npc * via a call_out. * @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 */ void init() { set_heart_beat( 1 ); if (environment() && aggressive && this_player() && file_name( environment() )[1..4] != "room" && !environment()->no_attack() && this_player()->query_visible(this_object())) start_attack(this_player()); } /** * This method check to see if the npc should start attacking someone * when they enter the npcs environment. It is called from inside * init(). It 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( this_object() ) || who->query_auto_loading() || file_name(who) == DEATH || who->query_property( "guest" ) || ( userp( who ) && !interactive( who ) ) || who->query_property( "no attack" ) ) { return; } /* * This uses a call_other() just in case there are shadows. */ if(((aggressive > 1) || interactive(who)) && (!interactive(who) || !who->query_auto_loading())) this_object()->attack_ob( who ); } /** @ignore yes */ varargs int adjust_hp( int number, object attacker, object weapon, string attack ) { set_heart_beat( 1 ); return ::adjust_hp( number, attacker, weapon, attack ); } /* adjust_hp() */ /** @ignore yes */ int adjust_gp( int number ) { set_heart_beat( 1 ); return ::adjust_gp( number ); } /* adjust_gp() */ /** * This method checks to see if there are any players in the environment * of the npc. This should be used to determine when 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 otherwise */ int check_anyone_here() { object thing; if ( !environment() ) { return 0; } if ( file_name( environment() )[1..4] == "room" ) { return 0; } if ( environment()->query_linked() ) { return 1; } foreach( thing in all_inventory( environment() ) ) { if ( interactive( thing ) || thing->query_slave() ) { return 1; } } return 0; } /* check_anyone_here() */ /** * This method is used to determine when to throw people out of a * room. This is what detritus uses to throw people out of the * mended drum when it gets a bit rowdy. * <p> * The hps is the level of hps at which the npc will start throwing * people out with the chance of it occuring (chance is a percentage). * <p> * People will be thrown into a random room, if the property * <pre>"no throw out"</pre> is specified on the room then they * will not be thrown into that room. * @param hps the number of hps at which to start throwing people out * @param chance the percentage chance of being thrown out * @param their_mess the message to show them * @param everyone_mess the message to show everyone else * @see set_aggressive() * @see query_throw_out() * @see set_join_fights() * @see expand_string() */ void set_throw_out( int hps, int chance, string their_mess, string everyone_mess ) { throw_out = ({ hps, chance, their_mess, everyone_mess }); } /* set_throw_out() */ /** * This method returns the current throw out array. * The array consists of * <pre>({ * hps, * chance, * their_mess, * everyone_mess * }) * </pre>The parameters are the same as used in the set_throw_out * function. * @see set_throw_out() * @return the throw out array */ mixed *query_throw_out() { return throw_out; } /** * 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" ) ) { this_object()->do_move_after( 1 ); return 1; } return ::run_away(); } /* run_away() */ /** * 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$, ...<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 living object, choose a random living object in the npcs * 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 = "/global/events"->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 = this_object(); case 'l' : if(!ob) { if(!liv) { /* Changed to not loop badly up when no environment */ obs = all_inventory(environment()) - ({ this_object() }); obs = filter_array(obs, (: living($1) && $1->query_visible(this_object()) :)); if (sizeof(obs)) liv = obs[random(sizeof(obs))]; } if (!liv) { /* No one here to see us blow up anyway ;) */ break; } ob = liv; } case 'a' : if ( !ob ) { obs = (object *)this_object()->query_attacker_list(); if ( !sizeof( obs ) ) break; ob = obs[ random( sizeof( obs ) ) ]; } case 'o' : if (!ob) { if (!on) { obs = all_inventory(environment()); obs = filter_array( obs, (: !living( $1 ) :) ); if (sizeof(obs)) { on = obs[random(sizeof(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 "#frog:bing:fred:chicken" would call<pre> * this_object()->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, this_object() ); } else { if( !stringp( str ) && environment( this_object() ) ) { tell_room(environment(this_object()), "%^RED%^"+ this_object()->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( this_object(), args[0] ); break; case 2 : call_other( this_object(), args[0], args[1] ); break; case 3 : call_other( this_object(), args[0], args[1], args[2] ); break; case 4 : call_other( this_object(), args[0], args[1], args[2], args[3] ); break; default : call_other( this_object(), 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( environment(), expand_string( str, 0 ) +"\n" ); } } } /* expand_mon_string() */ /** * 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 */ void heart_beat() { int i, j; if(base_name(environment()) == "/room/rubbish") { set_heart_beat(0); return; } ::heart_beat(); RACE_OB->monster_heart_beat( race, guild, race_ob, guild_ob ); if ( ( hp == max_hp ) && ( gp == max_gp ) ) { if (query_ok_turn_off_heart_beat()) { if ( !check_anyone_here() || ( !sizeof( achat_string ) && !sizeof( chat_string ) ) ) { set_heart_beat( 0 ); return; } } } /* These may be obsolete. */ remove_property( "done follow" ); this_object()->do_spell_effects( 0 ); if ( check_anyone_here() ) { if ( this_object()->query_fighting() ) { if ( sizeof( doing_story[ 1 ] ) ) { if( !intp(doing_story[ 1 ][ 0 ] ) ) { expand_mon_string( doing_story[ 1 ][ 0 ] ); doing_story[ 1 ] = doing_story[ 1 ][ 1 .. ]; } else if( random( 1000 ) < doing_story[ 1 ] [ 0 ]) { expand_mon_string( doing_story[ 1 ][ 1 ] ); if ( sizeof( doing_story[ 1 ] ) == 2 ) { doing_story[ 1 ] = ({}); } else { doing_story[ 1 ] = ({ doing_story[ 1 ] [ 0 ] }) + doing_story[ 1 ][ 2 .. ]; } } } else if ( ( random( 1000 ) < achat_chance ) && sizeof( achat_string[ 1 ] ) ) { i = random( achat_string[ 0 ] + 1 ); while ( ( i -= achat_string[ 1 ][ j ] ) > 0 ) j += 2; if ( pointerp( achat_string[ 1 ][ j + 1 ] ) ) { if( intp(achat_string[ 1 ][ j + 1 ][ 0 ]) ) { if( random(1000) < achat_string[ 1 ][ j + 1 ][ 0 ] ) { expand_mon_string( achat_string[ 1 ][ j + 1 ][ 1 ] ); doing_story[ 1 ] = ({ achat_string[ 1 ][ j + 1 ][ 0 ] })+ achat_string[ 1 ][ j + 1 ][ 2 .. ]; } else { doing_story[ 1 ] = achat_string[ 1 ][ j + 1 ]; } } else { expand_mon_string( achat_string[ 1 ][ j + 1 ][ 0 ] ); doing_story[ 1 ] = achat_string[ 1 ][ j + 1 ][ 1 .. ]; } } else expand_mon_string( achat_string[ 1 ][ j + 1 ] ); } } else { if ( sizeof( doing_story[ 0 ] ) ) { if( !intp( doing_story[ 0 ][ 0 ] ) ) { expand_mon_string( doing_story[ 0 ][ 0 ] ); doing_story[ 0 ] = doing_story[ 0 ][ 1 .. ]; } else if( random( 1000 ) < doing_story[ 0 ][ 0 ]) { expand_mon_string( doing_story[ 0 ][ 1 ] ); if ( sizeof( doing_story[ 0 ] ) == 2 ) { doing_story[ 0 ] = ({}); } else { doing_story[ 0 ] = ({ doing_story[ 0 ] [ 0 ] }) + doing_story[ 0 ][ 2 .. ]; } } } else if ( ( random( 1000 ) < chat_chance ) && sizeof( chat_string[ 1 ] ) ) { i = random( chat_string[ 0 ] + 1 ); while ( ( i -= chat_string[ 1 ][ j ] ) > 0 ) j += 2; if ( pointerp( chat_string[ 1 ][ j + 1 ] ) ) { if( intp( chat_string[ 1 ][ j + 1 ][ 0 ]) ) { if(random( 1000) < chat_string[ 1 ][ j + 1 ][ 0 ]) { expand_mon_string( chat_string[ 1 ][ j + 1 ][ 1 ] ); doing_story[ 0 ] = ({ chat_string[ 1 ][ j + 1 ][ 0 ] }) + chat_string[ 1 ][ j + 1 ][ 2 .. ]; } else { doing_story[ 0 ] = chat_string[ 1 ][ j + 1 ]; } } else { expand_mon_string( chat_string[ 1 ][ j + 1 ][ 0 ] ); doing_story[ 0 ] = chat_string[ 1 ][ j + 1 ][ 1 .. ]; } } else expand_mon_string( chat_string[ 1 ][ j + 1 ] ); } } } } /* heart_beat() */ /** @ignore yes */ int clean_up( int parent ) { if(query_property("unique") || check_anyone_here()) return 1; if(parent) { log_file("CLEANUP", "%s %s cleaned up.\n", ctime(time()), file_name(this_object())); } move( "/room/rubbish" ); return 1; } /** * This method generates a random number. It used to setup the random * stats. * @param no the number of times to roll the dice * @param type the size of the dice * @see set_random_stats() * @return the randomly generate number */ int rand_num(int no, int type) { int i, val; for (i=0;i<no;i++) val = random(type)+1; return val; } /* rand_num() */ /** * This method sets the stats for the npc to some exciting random * values. * @param no the number of times to roll the dice * @param type the size of the dice * @see rand_num() */ void set_random_stats(int no, int type) { set_str(rand_num(no, type)); set_dex(rand_num(no, type)); set_int(rand_num(no, type)); set_con(rand_num(no, type)); set_wis(rand_num(no, type)); } /* set_random_stats() */ /** * This method sets up the basic abilities and race of the critter. It * is equivalent to calling set_race(), set_guild(), and then set_level() * with the same parameters. But those latter three functions are * deprecated and shouldn't be used. * @see set_race() * @see set_guild() * @see set_level() * @param race this is the race of the character. It should be a * race that's understood by the /std/race.c * @param guild this is the guild, class, or profession of the NPC. * @param level this is the base skill level of the NPC. The * number is used by the race object to set ability scores, and * base skills. */ void basic_setup(string race, string guild, int level) { this_object()->set_race(race); this_object()->set_guild(guild); this_object()->set_level(level); } /** * This function is deprecated. Use basic_setup() instead. * * This method sets the level of the npc. This should only be called * *after* the race and guild are set. If this is called before that * the results will be unexpected. * @see set_race() * @see set_guild() * @see basic_setup() * @param i the level to set the npc to */ void set_level( int i ) { RACE_OB->set_level( i, race, guild ); } /** * This method is used to give some startup money to the npc. * @param base the base amount of money to give (fixed) * @param rand the random amount of money to give * @param type the type of money to give (default: "copper") * @example * ob = clone_object(CLUCKY_CHICKEN); * // This will give the chicken 10 + random(10) royals. * // It is a royal chicken * ob->give_money(10, 10, "royal"); * @return the return value of adjust_money() * @see /std/living/money.c */ int give_money(int base, int rand, string type) { if (!type) type = "copper"; return adjust_money(base+random(rand), type); } /* give_money() */ /** * 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. If the first parameter of the story array is a number it * will be used as a 1/1000 chance of the next story line being displayed. * 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 npcs. * * @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, ({ * 500, * "'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 c_s the chat string to use * @see load_a_chat() * @see query_chat_chance() * @see query_chat_string() */ void load_chat(int chance, mixed *c_s) { int i; chat_string = ({ 0, ({ }) }); for (i=0;i<sizeof(c_s);i+=2) { chat_string[1] += ({ c_s[i], c_s[i+1] }); chat_string[0] += c_s[i]; } chat_chance = chance; } /* load_chat() */ /** * This method sets the current chat chance for messages on the * npc. * @param i the chat chance * @see load_chat() * @see query_chat_chance() */ void set_chat_chance(int i) { chat_chance = i; } /** * This method returns the current chat chance for messages on * the npc * @return the current chat chance * @see set_chat_chance() * @see load_chat() */ int query_chat_chance() { return chat_chance; } /** * This method sets the current chat string for messages on the * npc. See load_chat() for a longer description of how the * chat string is formatted. * @param chat the new chat strings. * @see load_chat() * @see query_chat_string() */ void set_chat_string(string *chat) { chat_string = chat; } /** * This method queries the current chat string for messages on the * npc. See load_chat() for a longer description of how the * chat string is formatted. * @return the current chat string * @see load_chat() * @see query_chat_string() */ string *query_chat_string() { return chat_string; } /** * This method adds 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 * @param chat the new chat string * @see load_chat() * @see remove_chat_string() */ void add_chat_string(mixed weight, mixed chat) { int i; if (pointerp(weight)) { for (i=0;i<sizeof(weight);i+=2) { add_chat_string(weight[i], weight[i+1]); } } else { if (member_array(chat, chat_string[1]) == -1) { chat_string[1] += ({ weight, chat }); chat_string[0] += weight; } } } /* 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 (pointerp(chat)) { for (i=0;i<sizeof(chat);i++) { remove_chat_string(chat[i]); } } else { if ((i = member_array(chat, chat_string[1])) != -1) { chat_string[0] -= chat_string[1][i-1]; chat_string[1] = delete(chat_string[1], i-1, 2); } } } /* 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 c_s the chat string to use * @see load_chat() * @see query_achat_chance() * @see query_achat_string() */ void load_a_chat(int chance, mixed *c_s) { int i; achat_string = ({ 0, ({ }) }); for (i=0;i<sizeof(c_s);i+=2) { achat_string[1] += ({ c_s[i], c_s[i+1] }); achat_string[0] += c_s[i]; } achat_chance = chance; } /* load_a_chat() */ /** * This method sets the current chat chance for attack messages on 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) { achat_chance = i; } /** * This method returns the current chat chance for attack messages on * the npc. * @return the current attack message chat chance * @see set_achat_chance() * @see load_chat() * @see load_achat() */ int query_achat_chance() { return achat_chance; } /** * This method sets the current chat string for attack messages on the * npc. See load_chat() for a longer description of how the * chat string is formatted. * @param chat the new chat attack message strings. * @see load_chat() * @see load_achat() * @see query_achat_string() */ void set_achat_string(string *chat) { achat_string = chat; } /** * This method queries the current chat string for attack messages on the * npc. See load_chat() for a longer description of how the * chat string is formatted. * @return the current attack message chat string * @see load_chat() * @see load_achat() * @see set_chat_string() */ string *query_achat_string() { return achat_string; } /** * This method adds a single chat string into the current list of * attack message chat strings. See load_chat() for a longer description of * the chat string. * @param weight the weight of the chat * @param chat the new chat string * @see load_chat() * @see load_achat() * @see remove_achat_string() */ void add_achat_string(mixed weight, mixed chat) { int i; if (pointerp(weight)) for (i=0;i<sizeof(weight);i+=2) add_achat_string(weight[i], weight[i+1]); else if (member_array(chat, achat_string[1]) == -1) { achat_string[1] += ({ chat }); achat_string[0] += weight; } } /* 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 (pointerp(chat)) for (i=0;i<sizeof(chat);i++) remove_achat_string(chat[i]); else if ((i = member_array(chat, achat_string[1])) != -1) { achat_string[0] -= achat_string[1][i-1]; achat_string[1] = delete(achat_string[1], i-1, 1); } } /* remove_achat_string() */ /** * This method adds a move zone onto the npc. The move zones control * which areas the npcs will wander into, a move zone is set on the * room and the npcs 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() */ void add_move_zone(mixed zone) { int i; if (pointerp(zone)) for (i=0;i<sizeof(zone);i++) add_move_zone(zone[i]); else if (member_array(zone, move_zones) != -1) return; else move_zones += ({ zone }); } /* add_move_zone() */ /** @ignore why is this here? */ void set_move_zones(string *zones) { int i; for (i=0;i<sizeof(zones);i++) add_move_zone(zones[i]); } /* set_move_zones() */ /** * This method removes a move zone from the npc. * @param zone the zone to remove * @see add_move_zone() * @see query_move_zones() * @see set_move_after() */ void remove_move_zone(mixed zone) { int i; if (pointerp(zone)) for (i=0;i<sizeof(zone);i++) remove_move_zone(zone[i]); else { if ((i=member_array(zone, move_zones)) == -1) return ; move_zones = delete(move_zones, i, 1); } } /* 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 npcs will wander into, a move zone is set on the * room and the npcs 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() */ 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() */ mixed query_move_after() { return copy( _move_after ); } /* query_move_after() */ /** * 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() */ int add_enter_commands(mixed str) { if (stringp(str)) { enter_commands += ({ str }); } else if (pointerp(str)) { enter_commands += str; } else if (functionp(str)) { enter_commands += ({ str }); } return 1; } /* 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 add_enter_commands() */ void reset_enter_commands() { enter_commands = ({ }); } /** @ignore yes */ varargs int move( mixed dest, string messin, string messout ) { int result; object before; before = environment(); result = living::move( dest, messin, messout ); if ( result == MOVE_OK ) { me_moveing( before ); if ( virtual_move ) true_location = file_name( environment() ); } return result; } /* move() */ /** @ignore yes */ void room_look() { int i; ::room_look(); for ( i = 0; i < sizeof( enter_commands ); i++ ) { if ( functionp( enter_commands[ i ] ) ) { call_out( enter_commands[ i ], 2 * i + 1, this_object() ); continue; } if ( enter_commands[ i ][ 0 .. 0 ] == "#" ) call_out( enter_commands[ i ][ 1 .. 99 ], 2 * i + 1 ); else init_command( enter_commands[ i ], 2 * i + 1 ); } } /* room_look() */ /** * 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() */ void do_move_after (int running_away ) { if (sizeof(following_route)) { do_route_move(); } else { WANDER_HANDLER->move_after( running_away ); } } /* do_move_after() */ /** * 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) { mixed action; int i; // Run the combat actions. if(sizeof(this_object()->query_attacker_list())) { // Do combat actions. for(i=0; i<sizeof(_combat_actions); i+= 3) { if(_combat_actions[i] > random(100)) { action = _combat_actions[i+2]; if(stringp(action)) this_object()->do_command(action); else if(functionp(action)) evaluate(action, me, him); else if(pointerp(action) && sizeof(action) == 1 && stringp(action[0])) call_other(this_object(), action[0], me, him); else if(pointerp(action) && sizeof(action) == 2) call_other(action[0], action[1], this_object(), me, him); } } } if(him == this_object() && this_object()->query_property("see_caster") && !userp(me) && (random(him->query_property("see_caster"))) < (him->query_int())) this_object()->attack_ob(me->query_owner()); if(!join_fight_mess || !me || !him) return; if(him == this_object()) return; /* only attack npcs if fight_type is not 0. */ if(!join_fight_type && !interactive(him)) return; if(member_array(him, (object *)this_object()->query_attacker_list()) == -1) { if(!him->query_property("no attack")) { if(join_fight_mess) expand_mon_string(join_fight_mess); this_object()->attack_ob(him); } } } /* event_fight_in_progress() */ /** * 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; } /** @ignore yes */ void event_exit( object me, string mess, object to ) { mixed *bing; int i, j, k; string *zones, fname; object *attacker_list, ob; events::event_exit( me, mess, to ); living::event_exit( me, mess, to ); if (!_move_after || sizeof(following_route)) /* we dont move :( */ return ; /* If we're not going anywhere... */ if ( !to || !me->query_visible(this_object())) return; /* follow the suckers. */ attacker_list = this_object()->query_attacker_list(); if(attacker_list && sizeof(attacker_list)) { // If we're not fighting them then don't follow them. if (member_array(me, attacker_list) == -1) return; // Remove us. attacker_list -= ({ me }); // If there are people still here who we're fighting then don't // follow the person who left. foreach(ob in attacker_list) { if(ob && environment(ob) == environment()) return; } } else { /* Not being attacked, let's not follow. */ return; } bing = (mixed *)environment()->query_dest_dir( this_object() ); if( !bing ) { return ; /* lost cause */ } fname = file_name( to ); /* Check to see they're going a valid direction for this room. */ if ((i = member_array(fname, bing)) == -1) { return ; /* lost cause */ } if (!this_object()->query_property("unrestricted follow")) { zones = (string *)to->query_zones(); if (move_zones && (j = sizeof(move_zones))) { while (j--) { if (member_array(move_zones[j], zones) != -1) { k = 1; /* If we are suppose to wander in move_zones.. dont go */ break; /* where we are not supposed to... get stuck */ } } if (!k) return; } else return; } remove_call_out("do_follow_move"); call_out("do_follow_move", 4 + random(follow_speed), bing[i-1]); } /** * This method is used to make the npcs follow after attackers when they * leave the room. * @param dir the dirction to follow them in */ void do_follow_move(string dir) { if (sizeof(following_route)) return ; // NPCs are following & attacking instantly since they don't have // time_left. So, use up actions instead. this_object()->adjust_action_defecit((ACTIONS_PER_HB / (COMBAT_SPEED + 1))); do_command(dir); } /** * 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 arguements * 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; i = member_array( name, _combat_actions ); if ( i == -1 ) { _combat_actions += ({ chance, name, action }); } else { _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; i = member_array( name, _combat_actions ); if ( i == -1 ) return 0; _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> * ({ * 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 ); } /** * This method does a combat action. * @param target the target of the action * @param thing */ void do_combat_action( object player, object target, mixed action ) { object place; if ( !target ) { return; } place = environment( target ); if ( place != environment( player ) ) { return; } if ( stringp( action ) ) { do_command( action ); return; } if (functionp(action)) { evaluate(action, player, target); } if ( pointerp(action) && sizeof(action) == 1 && stringp( action[ 0 ] ) ) { call_other( this_object(), action[ 0 ], player, target ); return; } if (pointerp(action) && sizeof(action) == 2) { call_other( action[ 0 ], action[ 1 ], this_object(), player, target ); } } /* do_combat_action() */ /** * This is the call back from the combat effect to do something * wonderful and wild. * @param player the player * @param target the target */ void combat_actions_call_back( object player, object target ) { int i; object thing; object *things; mixed *actions; if ( !player || !target ) { return; } thing = environment( player ); if ( thing != environment( target ) ) { return; } things = filter_array( all_inventory( thing ), (: living( $1 ) == !userp( $1 ) :) ); foreach ( thing in things ) { actions = (mixed *)thing->query_combat_actions(); for(i=0; i<sizeof(actions); i += 3) { if(actions[i] > random(100)) { call_out( "do_combat_action", 1, player, target, actions[ i + 2 ] ); } } } } /* combat_actions_call_back() */ /** * This method adds an action to the npc that will happen if a specified * spell is cast. This is an action which * has a chance of occuring when a spell is being cast. * 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 caster, the second being the * target(s) array and the third being the magic arguments class. * <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 arguements * as above. If the size of the array is two then the function will * be called on the specified object with an extra first argument being * the npc which the effect is being called from. * @param spell_object the spell to respond to * @param chance the chance of it working * @param name the name of the thing * @param action the action to preform */ void add_spell_action(string spell_object, int chance, string name, mixed action) { if (!_spell_actions[spell_object]) { _spell_actions[spell_object] = ([ ]); } _spell_actions[spell_object][name] = ({ chance, action }); } /* add_spell_action() */ /** * This method removes the specified spell action. * @param name the name of the spell to remove * @return 1 if successful, 0 if not */ int remove_spell_action(string name) { string spell; mapping bits; int ret; foreach (spell, bits in _spell_actions) { if (bits[name]) { map_delete(bits, name); ret = 1; } } return ret; } /* remove_spell_action() */ /** * This method returns the list of spell actions present on the * npc. * @return the list of spell actions */ mapping query_spell_actions() { return _spell_actions; } /* query_spell_actions() */ /** * This method does a combat action. * @param caster the caster of the spell * @param targets the targets of the spell * @param action the action to do * @param args the spell arguements */ void do_spell_action( object caster, object* targets, mixed action, mixed args ) { if ( stringp( action ) ) { do_command( action ); return; } if (functionp(action)) { evaluate(action, caster, targets); } if ( pointerp(action) && sizeof(action) == 1 && stringp( action[ 0 ] ) ) { call_other( this_object(), action[ 0 ], caster, targets, args ); return; } if (pointerp(action) && sizeof(action) == 2) { call_other( action[ 0 ], action[ 1 ], this_object(), caster, targets, args ); } } /* do_spell_action() */ /** * This returns the race object associated with the npc. * @return the race object associated with the npc * @see set_race() * @see set_race_ob() * @see set_level() */ mixed query_race_ob() { return race_ob; } /** * This method sets the race object associated with the npc. * This will probably not do what you expect and cannot be used * in conjucton with set_level to set the race. It is called * by /std/race.c when set_level() is called. * @see set_level() * @see set_race() * @see /std/race.c * @param r the new race object */ void set_race_ob(mixed r) { race_ob = r; } /** * This returns the guild object associated with the npc. * @return the guild object associated with the npc * @see set_guild() * @see set_guild_ob() * @see set_level() */ mixed query_guild_ob() { return guild_ob; } /** * This method sets the guild object associated with the npc. * This will probably not do what you expect and cannot be used * in conjucton with set_level to set the guild. It is called * by /std/race.c when set_level() is called. * @see set_level() * @see set_guild() * @see /std/race.c * @param g the new guild object */ void set_guild_ob(mixed g) { guild_ob = g; } /** * 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. * @return the current follow speed * @see set_follow_speed() */ void set_follow_speed(int f) { follow_speed = f; } /** * 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 aggressive npcs 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 aggressive npcs attacking things. * @see query_aggressive() * @see start_attack() * @see set_join_fights() * @see set_throw_out() * @param a the new aggressive level */ void set_aggressive(int a) { aggressive = a; // this added to make aggressive npcs join in fights if they arent // 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 (a && !join_fight_mess) { join_fight_mess = this_object()->one_short()+" joins in the fight.\n"; } } /** * This method returns the current guild level of the npc. This is * a pass through call to a function on the guild object associated * with this npc. * @return the current guild level of the object * @see set_guild() */ int query_level() { if (!guild_ob) return 1; return (int)guild_ob->query_level(this_object()); } /* query_level() */ /** * 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 ( query_property( "dead" ) || query_property( "unique" ) ) { return 0; } amount = (int)TOP_TEN_HANDLER->calculate_rating( this_object() ); //amount += (int)this_object()->query_max_hp(); //amount *= 120; return amount / 2; } /* query_death_xp() */ /** @ignore yes */ mixed *stats() { mixed *bing; int i; bing = ({ }); for (i=0;i<sizeof(move_zones);i++) bing += ({ ({ "move zone "+i, move_zones[i] }) }); if (!query_move_after()) return ::stats() + ({ ({ "guild", query_guild() }), ({ "guild ob", query_guild_ob() }), ({ "race", query_race() }), ({ "race ob", query_race_ob() }), ({ "join_fights", query_join_fights() }), ({ "follow speed", query_follow_speed() }), ({ "level", query_level() }), ({ "chat chance", query_chat_chance() }), ({ "achat chance", query_achat_chance() }), ({ "aggressive", query_aggressive() }), ({ "route", (sizeof(following_route)?implode(following_route, ", "): "not going anywhere") }), }) + bing; return ::stats() + ({ ({ "race", query_race() }), ({ "race ob", query_race_ob() }), ({ "guild", query_guild() }), ({ "guild ob", query_guild_ob() }), ({ "join fights", query_join_fights() }), ({ "follow_speed", query_follow_speed() }), ({ "level", query_level() }), ({ "chat chance", query_chat_chance() }), ({ "achat chance", query_achat_chance() }), ({ "aggressive", query_aggressive() }), ({ "move after-fix", query_move_after()[0] }), ({ "move after-rand", query_move_after()[1] }), ({ "route", (sizeof(following_route)?implode(following_route, ", "): "not following anyone") }), }) + bing; } /* stats() */ /** @ignore yes */ string expand_nickname(string str) { return str; } /** @ignore yes */ void event_enter( object dest, string mess, object from ) { /* stop massive spam in /room/rubbish and /room/void */ if( environment( this_object() ) && file_name( environment( this_object() ) )[ 1 .. 4 ] == "room" ) return; if(check_anyone_here() && (moves > 2)){ moves = 0; do_move_after(0); } living::event_enter( dest, mess, from ); events::event_enter( dest, mess, from ); } /* event_enter() */ /** @ignore yes */ void event_person_say( object thing, string start, string mess, string lang, string accent) { response_mon::event_person_say(thing, start, mess, lang); } /* event_person_say() */ /** @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() */ /** @ignore yes */ varargs void event_soul( object thing, string mess, mixed avoid, string verb, string last, mixed at ) { response_mon::event_soul( thing, mess, avoid, verb, last, at ); events::event_soul( thing, mess, avoid ); } /* event_soul() */ /** * This method moves the npc to room it is really supposed to be in. * This is used with the virtual moving * @param check_room the place where it thinks we are * @see query_virtual_move() */ void real_room( string check_room ) { if ( check_room == true_location ) { move_object( true_location ); } } /* real_room() */ /** * This method returns true if we are currently using the * virtual move system. * @return 1 if we are virtual moving, 0 if not * @see set_virtual_move() */ #if !efun_defined(add_action) int query_virtual_move() { return virtual_move; } #else int query_virtual_move() { return 0; } #endif /** * This method sets the current virual move ability of the npc. * NB: This is currently disabled virtual moving is not possible * at all. * @param number 1 for virtual moving, 0 for not * @see query_virtual_move() */ void set_virtual_move( int number ) { if(virtual_move && !number && file_name(environment()) == "/room/virtual"){ object ob = load_object(true_location); if(ob) move(ob); } virtual_move = number; if ( virtual_move && environment() ) true_location = file_name( environment() ); } /* set_virtual_move() */ /** * This method returns the true location of the npc. This is * the real room it is in, not the room it is currently in. * @return the true location of the npc * @see set_true_location() * @see set_virtual_move() */ string query_true_location() { return true_location; } /** * This method sets the true location of the npc. This is * the real room it is in, not the room it is currently in. * @param word the new true location of the npc * @see set_true_location() * @see set_virtual_move() */ void set_true_location( string word ) { if(word == "/room/virtual"){ if(true_location) return; //we have to be somewhere! word = file_name(environment()); if(word == "/room/virtual"){ //something is totally wrong give up on the npc move("/room/rubbish"); true_location = "/room/rubbish"; } } true_location = word; } int cleaning_room() { if ( virtual_move ) { true_location = file_name( environment() ); "/room/virtual"->force_load(); move_object( "/room/virtual" ); return 1; } return 0; } /* cleaning_room() */ /** * This method causes the npc to move in the given direction. * @param move the direction to move */ void do_move( string move ) { if (1 || check_anyone_here()) { moves = 0; } if (moves++ < 3) { string tmp; command(move); tmp = file_name(environment()); if(tmp == "/room/virtual") tmp = true_location; //room didn't let us in. true_location = tmp; } else { WANDER_HANDLER->delete_move_after(this_object()); } } /* do_move() */ /* Move along a nice route thingy... */ /** * This method gets the next direction to go in the route which is * currently being followed. It will remove this direction off the * array. * @return the next direction to go down * @see query_last_route_direction() * @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 tells us if the npc is currently following a route. * @return 1 if there are route directions to follow still * @see get_next_route_direction() * @see query_following_route() * @see do_route_move() */ int query_last_route_direction() { return ( sizeof(following_route) ? 1 : 0 ); } /** * This method returns the current array of directions we are following * as a route. * @see get_next_route_direction() * @see query_last_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_last_route_direction() * @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_HANDLER->move_me_please(delay, dest); do_route_move(); } else { this_object()->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 arguement 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 "/obj/monster"; * * 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_last_route_direction() * @see query_following_route() * @see do_route_move() */ varargs void move_me_to(string dest, int delay) { string *dest_dir, *start_dir; if (!environment() || !file_name(environment())) return ; if(!delay) delay = 10; if (!MAP->static_query_short( dest ) || !MAP->static_query_short( file_name(environment()) ) ) { //very likely to have no route if (!sizeof( dest_dir = dest->query_dest_dir( this_object() ) ) || !sizeof( start_dir = environment()->query_dest_dir( this_object() ) ) ) { //one of the rooms have no exits move( dest, "$N wanders in.\n", "$N wanders off.\n" ); } else { //both rooms have exits move( dest, replace_string( query_msgin(), "$F", dest_dir[random(sizeof(dest_dir)/2)*2]), replace_string( query_msgout(), "$T", start_dir[random(sizeof(start_dir)/2)*2])); } call_out( "stopped_route", 1 ); return; } ROUTE_HANDLER->get_route(dest, file_name(environment()), (: got_the_route($1, $(delay), $(dest)) :)); } /* move_me_to() */ /** @ignore yes */ string identify( object thing, object *places ) { do_command( "'Please bug me! I'm using identify()!" ); return file_name( thing ); } /* identify() */ /** @ignore yes */ int query_time_left() { return 1; } /** @ignore yes */ int ignore_identifier() { return 1; } /** * This method adds a language to the npc. * <p> * After the sun has died away<br> * The stars come out and glow<br> * Lighting the embers of good intentions<br> * Ghostly white, unhappily bright<br> * Time lost, the day done * @param str the language to add */ void add_language(string str) { ::add_language(str); added_language = 1; } /* add_language() */ /** * 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) { if (new_pos != query_position() && this_player() != this_object()) { // Please, do NOT use function pointers unless it's necessary. // Took me ages to find this "function" given that all // call_stack could tell me was that it was "<function>" call_out("return_to_default_position", always_return_to_default_position, 0); } } ::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 * specified 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 * specified 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() */ /** @ignore yes */ mapping int_query_static_auto_load() { return ([ "::" : ::int_query_static_auto_load(), "cap name" : cap_name, "race" : race, "guild" : guild, ]); } /** @ignore yes */ mixed query_static_auto_load() { if ( base_name(this_object()) + ".c" == __FILE__ ) return int_query_static_auto_load(); return ([ ]); } mapping query_dynamic_auto_load() { return ([ "::" : ::query_dynamic_auto_load(), "chat string" : chat_string, "achat string" : achat_string, "combat actions" : _combat_actions, "move after" : _move_after, "throw out" : throw_out, "chat chance" : chat_chance, "achat chance" : achat_chance, "aggressive" : aggressive, "join fight type" : join_fight_type, "join fight mess" : join_fight_mess, "follow speed" : follow_speed, "virtual move" : virtual_move, "moves" : moves, "true location" : true_location, "enter commands" : enter_commands, "move zones" : move_zones, "doing story" : doing_story, "last attacked" : last_attacked, "following route" : following_route, "added language" : added_language, "cannot change position" : cannot_change_position, "always return to default position" : always_return_to_default_position, "level" : query_level(), ]); } void init_static_arg(mapping args) { if (args["::"]) ::init_static_arg(args["::"]); if (!undefinedp(args["cap name"])) cap_name = args["cap name"]; if (!undefinedp(args["race"])) race = args["race"]; if (!undefinedp(args["guild"])) guild = args["guild"]; } void init_dynamic_arg( mapping args, object ob ) { if (args["::"]) ::init_static_arg(args["::"]); if (!undefinedp(args["chat string"])) chat_string = args["chat string"]; if (!undefinedp(args["achat string"])) achat_string = args["achat string"]; if (!undefinedp(args["combat actions"])) _combat_actions = args["combat actions"]; if (!undefinedp(args["move after"])) _move_after = args["move after"]; if (!undefinedp(args["throw out"])) throw_out = args["throw out"]; if (!undefinedp(args["chat chance"])) chat_chance = args["chat chance"]; if (!undefinedp(args["achat chance"])) achat_chance = args["achat chance"]; if (!undefinedp(args["aggressive"])) aggressive = args["aggressive"]; if (!undefinedp(args["join fight type"])) join_fight_type = args["join fight type"]; if (!undefinedp(args["join fight mess"])) join_fight_mess = args["join fight mess"]; if (!undefinedp(args["follow speed"])) follow_speed = args["follow speed"]; if (!undefinedp(args["virtual move"])) virtual_move = args["virtual move"]; if (!undefinedp(args["moves"])) moves = args["moves"]; if (!undefinedp(args["true location"])) true_location = args["true location"]; if (!undefinedp(args["enter commands"])) enter_commands = args["enter commands"]; if (!undefinedp(args["move zones"])) move_zones = args["move zones"]; if (!undefinedp(args["doing story"])) doing_story = args["doing story"]; if (!undefinedp(args["last attacked"])) last_attacked = args["last attacked"]; if (!undefinedp(args["following route"])) following_route = args["following route"]; if (!undefinedp(args["added language"])) added_language = args["added language"]; if (!undefinedp(args["cannot change position"])) cannot_change_position = args["cannot change position"]; if (!undefinedp(args["always return to default position"])) always_return_to_default_position = args["always return to default position"]; if (!undefinedp(args["level"])) set_level(args["level"]); } /** * * attack_permission function, added for use by the allow_attack simul. * @param object The person performing the action * @param object The object being acted on, this object usually. * @param string Attack type, this will be one of "combat", "theft", or "magic", * this lets you give your NPCs different responses for different attacks. As well * as make them immune to theft and magic (as an example) * @return int 1 if the action is denied, 0 is it can go through. * @see efun::allow_attack() */ int attack_permission( object ob1, object ob2, string stringy ) { return 0; } #if !efun_defined(add_action) /** @ignore yes */ protected mixed _process_input(string str) { object ob = this_player(); if(str == "") return 0; _notify_fail(0); efun::set_this_player(this_object()); if(!this_object()->drunk_check(str)) if(!this_object()->exit_command(str)) if(!this_object()->cmdAll(str)) if(!this_object()->new_parser(str)) if(!this_object()->lower_check(str)){ efun::set_this_player(ob); return 0; } efun::set_this_player(ob); return "bing"; } /* _process_input() */ int drunk_check(string str) { if(cmd_func){ object owner = function_owner(cmd_func); if(owner && owner == environment(this_player())){ int res = evaluate(cmd_func, str); if(res) return res; } else cmd_func = 0; } return 0; } /* drunk_check() */ /** * Use this function to set a function that is called with the NPCs input * before command() gets 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 = func; } /** @ignore yes */ protected mixed command(string cmd){ int time = eval_cost(); if(_process_input(cmd)) return eval_cost() - time + 1; // on v22.2 eval_cost runs up, reverse for v22.1 return 0; } #endif /** @ignore yes */ int _living(){return 1;} /** * This event is triggered when hide_invis is added or removed from an * object. In this case it's used to make the NPC attack if someone * sneaks into the room and comes out of hiding. * @param hider The person who's hiding/unhiding. * @param adding 1 if the person is hiding, 0 if they are coming out. * @param type The type of hide invis. * @param quiet The quiet flag that's passed into remove_hide_invis. */ void event_hide_invis( object hider, int adding, string type, int quiet ) { if ( aggressive && !adding && environment() && file_name( environment() )[1..4] != "room" ) { start_attack(hider); } } /* event_hide_invis() */