/** * Basic sight and sound broadcaster. */ #define DIR_ARRAY ({"east", "northeast", "north", "northwest", \ "west", "southwest", "south", "southeast", "east"}) #define SAVE_FILE SAVE_DIR "/broadcaster" #define BROADCAST_HISTORY_LENGTH 10 void broadcast_event( object *things, /* things that get told about it */ int *centre, /* coords where event occurs */ string message, /* what the things get told */ int range, /* range of telling in room units */ int inside, /* tell things if indoors */ int underwater ); /* tell things if underwater */ void npc_hear_shouts( object newlistener ); int npc_unhear_shouts( object oldlistener ); void npc_shout_event( object shouter, string start, string message, string lang, int *coord, int range ); /* arguments for event_person_shout */ private void save_me(); private nosave object *_listeners; private nosave mapping _channels; private nosave mapping _channel_history; private mapping _channel_ids; void create() { _listeners = ({ }); _channels = ([ ]); _channel_ids = ([ ]); _channel_history = ([ ]); if( file_exists(SAVE_FILE+".o") ) unguarded( (: restore_object(SAVE_FILE, 1 ) :) ); if( !mapp(_channel_ids) ) _channel_ids = ([ ]); } /* create() */ private void save_me() { unguarded( (: save_object(SAVE_FILE) :) ); } /** * This method returns the square distance, the name of the direction * the sound comes from and the up down offset. It is used by the shout * code and by various other things that require this inofmraiont. * The return array is formated as:<br> * ({ square_distance, name_of_direction, z_offset }) * @param co_ord1 the first co-ordinate * @param co_ord2 the co-ordinate to compare against * @return the array as described above */ mixed get_direc_dist( int *co_ord1, int *co_ord2 ) { int dx, dy, dz, sector; if( !pointerp(co_ord1) || !pointerp(co_ord2) ) { // Make it right here... return ({ 0, DIR_ARRAY[0], 0 }); } dx = co_ord1[ 0 ] - co_ord2[ 0 ]; dy = co_ord1[ 1 ] - co_ord2[ 1 ]; dz = co_ord1[ 2 ] - co_ord2[ 2 ]; if ( dx > 0 ) { if ( ( 1000 * dy ) > ( 2414 * dx ) ) { sector = 0; } else { if ( ( 1000 * dy ) > ( 414 * dx ) ) { sector = 1; } else { if ( ( 1000 * dy ) > ( -414 * dx ) ) { sector = 2; } else { if ( ( 1000 * dy ) > ( -2414 * dx ) ) { sector = 3; } else { sector = 4; } } } } } else { if ( ( 1000 * dy ) < ( 2414 * dx ) ) { sector = 4; } else { if ( ( 1000 * dy ) < ( 414 * dx ) ) { sector = 5; } else { if ( ( 1000 * dy ) < ( -414 * dx ) ) { sector = 6; } else { if ( ( 1000 * dy ) < ( -2414 * dx ) ) { sector = 7; } else { sector = 8; } } } } } return ({ dx * dx + dy * dy, DIR_ARRAY[ sector ], dz }); } /* get_direc_dist() */ /** * Sends an event out to all the things. It checks to make sure they * can hear the event and all that stuff. * * @param things things that get told about it * @param centre coords where event occurs * @param msg the message to broadcast, if * the message is an array, then the first element * of the array will be the message told to people * outdoors and the second for people indoors, * if it's a string, it will be used for all areas. * @param range range of telling in room units * @param inside tell things if indoors * @param underwater tell things if underwater */ void broadcast_event( object *things, int *centre, mixed msg, int range, int inside, int underwater ) { int *to; string text, out_msg, in_msg; object thing, place; mixed *dir_direc; if( !pointerp( things ) || !pointerp( centre ) || sizeof( centre ) != 3 ) { return; } if( stringp(msg) ) { out_msg = msg; in_msg = msg; } else if( pointerp(msg) && sizeof(msg) == 2 ) { out_msg = msg[0]; in_msg = msg[1]; } else { return; } foreach( thing in things ) { string loc, message; if( !living( thing ) ) { continue; } /* Maybe something to inform non-living things later */ place = ENV( thing ); if( !place ) { continue; } to = (int *)place->query_co_ord(); if( !pointerp( to ) || sizeof( to ) != 3 ) { continue; } loc = (string)place->query_property( "location" ); switch( loc ) { case "inside" : if( !inside ) { continue; } break; case "underwater" : if( !underwater ) { continue; } break; default : } dir_direc = get_direc_dist( centre, to ); if( dir_direc[ 0 ] < 0 ) { /* So far away it's hit two's complement. */ continue; } /* Lo, no need for square roots. */ if( dir_direc[ 0 ] > range * range ) { continue; } message = ( loc == "inside" ? in_msg : out_msg ); // This code is huge and fluffy, need to reset the cost in here a // little. reset_eval_cost(); switch( dir_direc[ 0 ] ) { case 0 : /* They're in the same room - they should get told elsewhere. */ // but only if they're at the same altitude with us, // or too far up/down - Sandoz if( dir_direc[ 2 ] == 0 || dir_direc[ 2 ] > range ) continue; case 1 .. 2500 : /* Very close: 1 to 50 units */ if ( ( dir_direc[ 2 ] * dir_direc[ 2 ] ) > ( dir_direc[ 0 ] / 2 ) ) { /* added some nicer messages to up/down dirs */ switch( dir_direc[ 2 ] ) { case -10240000 .. -40001 : text = "In the distance below you, "+ message +"\n"; break; case -40000 .. -2501 : text = "Somewhere below you, "+ message +"\n"; break; case -2500 .. -1 : text = "Right below you, "+ message +"\n"; break; case 0 .. 2500 : text = "Right above you, "+ message +"\n"; break; case 2501 .. 40000 : text = "Somewhere above you, "+ message +"\n"; break; case 40001 .. 10240000 : text = "In the distance above you, "+ message +"\n"; break; default: } } else { text = "Very close to the "+ dir_direc[ 1 ] + ", "+ message +"\n"; } break; case 2501 .. 40000 : /* Nearby: 50 to 200 units */ text = "Nearby to the "+ dir_direc[ 1 ] + ", "+ message +"\n"; break; case 40001 .. 640000 : /* 200 to 800 units */ text = "To the "+ dir_direc[ 1 ] + ", "+ message +"\n"; break; case 640001 .. 10240000 : /* In the distance: 800 to 1600 units */ text = "In the distance to the "+ dir_direc[ 1 ] + ", "+ message +"\n"; break; default: /* Too far away */ continue; } if( interactive(thing) && query_verb() == "shout" ) { text = thing->colour_event("shout", "") + text + "%^RESET%^"; } thing->add_message( "$I$5="+ text, ({ }) ); } } /* broadcast_event() */ /** * Adds an NPC to the list of NPC's who are to receive shout events. * The NPC need not be removed from this list when he dies -- desting * the NPC object has the same effect as calling npc_unhear_shouts(). * event_person_shout() is activated on the NPC for all shouts. * NPC's do not normally detect shouts. * event_person_shout() on the NPC should be replaced with similar code * to that in the broadcaster to determine if the shouter is within * range and to generate the desired response. * * @param newlistener NPC object to be added to the list * * @see /global/events->event_person_shout() * @see npc_unhear_shouts() */ void npc_hear_shouts(object newlistener) { int i; if (member_array(newlistener,_listeners)!=-1) { return; } i = member_array(0,_listeners); if (i != -1) { _listeners[i]=newlistener; } else { _listeners=_listeners+({newlistener}); } } /* npc_hear_shouts() */ /** * Removes an NPC from the list of NPC's who are to receive shout events. * The NPC need not be removed from this list when he dies -- desting * the NPC object has the same effect as calling npc_unhear_shouts(). * * @param oldlistener NPC object to be removed from the list * @return 1 if successfully removed * * @see /global/events->event_person_shout() * @see npc_hear_shouts() */ int npc_unhear_shouts(object oldlistener) { int i; if (sizeof(_listeners)==0) { return 0; } i = member_array(oldlistener,_listeners); if (i == -1) { return 0; } // Remove the listener _listeners = _listeners[0..i] + _listeners[i+1..]; return 1; } /* npc_unhear_shouts() */ /** * This method is called by the shout command to filter the shouts onto * the npcs that are listening. * @param shouter the person who shouted * @param start the start of the message * @param message the message shouted * @param lang the language the message is in * @param coord the co-ordinate it was shouted from * @param range the range of the shout */ void npc_shout_event( object shouter, string start, string message, string lang, int *coord, int range ) { if (_listeners) { _listeners -= ({ 0 }); _listeners->event_person_shout(shouter, start, message, lang, coord, range ); } } /* npc_shout_event() */ /** * This method adds an object to the list of objects to be told about the * specfied channel. The method called on the object for the channel * will be event_channel_message. The method will be called with * three arguements, the first is the object generating the event * the second is the channel the event is generated on and the * third is the message being sent to the channel. * @param channel_name the name of the channel * @param ob the object to add to the list */ void add_object_to_channel(string channel_name, object ob) { if (!_channels[channel_name]) { _channels[channel_name] = ({ }); } _channels[channel_name] += ({ ob }); } /* add_object_to_channel() */ /** * This method removes an object from the list of objects to be told about the * specified channel. * @param channel_name the name of the channel * @param ob the object to add to the channel */ int remove_object_from_channel(string channel_name, object ob) { if (_channels[channel_name]) { if (member_array(ob, _channels[channel_name]) != -1) { _channels[channel_name] -= ({ ob }); if (!sizeof(_channels[channel_name])) { map_delete(_channels, channel_name); } return 1; } } return 0; } /* remove_object_from_channel() */ /** * Inform channel of message. The message will get sent to all the objects * added to the list to the method event_channel_message. The method * will be called with three arguements, first is the object that started * the event, the second the channel it is being send to and the last is the * message being sent to the channel. * @param ob the object creating the channel event * @param channel the channel to inform people about * @param message the message to tell the channel about */ void broadcast_to_channel(mixed ob, string channel, mixed message) { string str; if (!ob) { return ; } if (objectp(ob)) { str = ob->query_cap_name(); } if (_channels[channel]) { _channels[channel] -= ({ 0 }); _channels[channel]->event_channel_message(ob, channel, message); // Put the message into the history list. if (!_channel_history[channel]) { _channel_history[channel] = ({ }); } _channel_history[channel] += ({ ({ str, message }) }); if (sizeof(_channel_history[channel]) > BROADCAST_HISTORY_LENGTH) { _channel_history[channel] = _channel_history[channel][1..]; } } } /* broadcast_to_channel() */ /** * This message returns the current history list for the channel. The * array is:<br> * ({ ({ person, message }), ... }) * @param channel the channel to get the history of * @return the history of the channel * @see broadcast_to_channel() */ mixed query_channel_history( string channel ) { return _channel_history[channel]; } /* query_channel_history() */ /** * This method must only be used for debugging purposes. * @param channel the channel which has all these things on it * @return the array of objects in the channel * @see is_channel() */ object *query_channel_members( string channel ) { return _channels[channel]; } /** * This method checks to see if the channel exists. * @param channel the channel name to check for * @return 1 if the channel eixsts, 0 if not * @see query_channel_members() */ int is_channel( string channel ) { return !undefinedp(_channels[channel]); } /** @ignore yes. THis is definately definately not allowed to be used * in code, it is only allow to be used specifically for debugging * purposes. * @return the nice mapping */ mapping query_all_channels() { return _channels; } /** * This method keeps track of an ever increasing number for a specified * channel type. THis is used in the wizards spell (for instance) to * keep track of unique ids for wizard channels. * @param channel the name of the channel * @return the next number in the series */ int query_next_channel_number( string channel ) { int next_num; next_num = ++_channel_ids[channel]; save_me(); return next_num; } /* query_next_channel_number() */ /** @ignore yes */ mapping query_dynamic_auto_load() { return ([ "channels" : _channels, "history" : _channel_history ]); } /* query_dynamic_auto_load() */ void init_dynamic_arg( mapping arg ) { _channels = arg["channels"]; if( !_channels ) _channels = ([ ]); _channel_history = arg["history"]; if( !_channel_history ) _channel_history = ([ ]); } /* init_dynamic_arg() */