/**
* Handles NPCs wandering randomly around the place and NPCs
* following a route to a destination.
* @see /str/npc->move_me_to()
* @author Wodan
* @author Pinkfish
* @changed Rewritten by Wodan 19-6-1997.
* @changed Rewritten by Sandoz 23-6-2002.
*/
#define LAST_LOC_PROPERTY "last locations"
/**
* This class stores the data for a single NPC that is following a route.
*/
class route_traveller {
int movetime;
int delay;
string dest;
}
private mapping wanderers, travellers, moving;
private nosave int tick;
/** @ignore yes */
void create() {
wanderers = ([ ]);
travellers = ([ ]);
moving = ([ ]);
set_heart_beat(1);
} /* create() */
/**
* This method returns the mapping of move times and NPCs
* that are going to move at a particular move time.
* @return the moving NPCs mapping
*/
mapping query_moving() { return moving; }
/**
* This method returns the mapping of all wandering NPCs
* and their related data.
* @return a mapping of all wandering NPCs and their data
*/
mapping query_wanderers() { return wanderers; }
/**
* This method returns the mapping of all travelling NPCs
* and their related data.
* @return a mapping of all travelling NPCs and their data
*/
mapping query_travellers() { return travellers; }
/** @ignore yes */
private void add_moving( object monster, int when ) {
if( !pointerp( moving[when] ) )
moving[when] = ({ monster });
else if( member_array( monster, moving[when] ) == -1 )
moving[when] += ({ monster });
} /* add_moving() */
/**
* Called from the NPC to start them moving. This should not
* need to be called inside your code. It is called from the
* function move_me_to in the NPC object.
* @see /std/npc->move_me_to()
* @param delay the delay between each move
* @param dest the destination room for the npc
* @return 0 if it failed and 1 on success
*/
int move_me_please( int delay, string dest ) {
object monster;
int when;
if( !intp(delay) )
return 0;
monster = PO;
if( delay < 5 )
delay = 5;
when = time() + delay;
travellers[monster] = new( class route_traveller,
delay : delay,
movetime : when,
dest : dest );
add_moving( monster, when );
return 1;
} /* move_me_please() */
/** @ignore yes */
private void do_wander_move( int running_away, object monster ) {
mapping dest_dir;
string tmp, direction;
string *dirs, *room_zones, *move_zones, *last_locs, *last_used;
object env;
if( !monster || monster->do_not_wander() || monster->query_hp() <= 0 ||
!( env = ENV(monster) ) || file_name(env)[1..4] == "room" ||
monster->query_property(PASSED_OUT) )
return;
if( !running_away && sizeof( (object *)monster->query_attacker_list() ) )
return;
if( !mapp( dest_dir = (mapping)env->query_dest_dir_mapping() ) ||
!sizeof(dest_dir) )
return;
last_locs = monster->query_propery(LAST_LOC_PROPERTY);
if( !pointerp(last_locs) )
last_locs = ({ });
move_zones = (string *)monster->query_move_zones();
dirs = shuffle( keys(dest_dir) );
last_used = ({ });
foreach( tmp in dirs ) {
if( sizeof(move_zones) ) {
room_zones = (string *)MAP_H->query_zones( dest_dir[tmp] );
if( !room_zones || !sizeof( move_zones & room_zones ) )
continue;
}
if( member_array( dest_dir[tmp], last_locs ) == -1 ) {
direction = tmp;
break;
} else
last_used += ({ tmp });
}
if( !direction ) {
if( sizeof(last_used) ) {
direction = choice(last_used);
} else
return;
}
tmp = dest_dir[direction];
last_locs += ({ tmp });
if( sizeof(last_locs) > 4 )
last_locs = last_locs[sizeof(last_locs)-4..];
monster->add_property( LAST_LOC_PROPERTY, last_locs );
monster->do_move(direction);
} /* do_wander_move() */
/** @ignore yes */
private void do_route_move( object monster ) {
string direc;
if( direc = monster->get_next_route_direction() ) {
// Catch so that we could use runtiming, but loading rooms.
if( catch( monster->do_command(direc) ) )
catch( monster->do_command(direc) );
}
if( direc && !monster->query_stop_moving() ) {
travellers[monster]->movetime = time() + travellers[monster]->delay;
add_moving( monster, travellers[monster]->movetime );
} else {
direc = file_name( ENV(monster) );
monster->stopped_route( direc == travellers[monster]->dest );
map_delete( travellers, monster );
}
} /* do_route_move() */
/**
* This method makes all NPCs that should either wander or
* follow their route move.
*/
private void heart_beat() {
int time, cur_time, *times;
object monster, *monsters;
cur_time = time();
monsters = ({ });
// Separate the monsters we are going to move, and remove them
// from the main mapping.
foreach( time in sort_array( keys(moving) - ({ 0 }), 1 ) ) {
if( time <= cur_time ) {
monsters += moving[time] - ({ 0 });
map_delete( moving, time );
}
}
foreach( monster in monsters ) {
if( travellers[monster] ) {
do_route_move( monster );
} else {
times = monster->query_move_after();
time = time() + times[ 0 ] + random( times[ 1 ] );
if( time < time() + 2 )
time = time() + 2;
add_moving( monster, time );
do_wander_move( 0, monster );
}
}
if( tick++ == 100 ) {
tick = 1;
wanderers = filter( wanderers, (: $1 :) );
travellers = filter( travellers, (: $1 :) );
}
} /* heart_beat() */
/**
* This method makes an NPC to stop wandering.
* @param ob the NPC to stop wandering
*/
void delete_move_after( object ob ) {
if( !undefinedp( wanderers[ob] ) ) {
int time = wanderers[ob];
if( !undefinedp( moving[time] ) ) {
// Only delete if they are not going to do a route move
// at the same moment.
if( undefinedp( travellers[ob] ) ||
time != travellers[ob]->movetime ) {
moving[time] -= ({ ob, 0 });
if( !sizeof( moving[time] ) )
map_delete( moving, time );
}
}
map_delete( wanderers, ob );
}
} /* delete_move_after() */
/**
* This method puts an NPC into the random movement group.
* This is called from the set_move_after code in the NPC object.
* You should not need to call this function directly.
* @param runaway whether or not this is a wimpy attempt
* @see /std/npc->set_move_after()
*/
void move_after( int runaway ) {
int *after;
object monster;
if( runaway )
return do_wander_move( runaway, PO );
monster = PO;
if( pointerp( after = monster->query_move_after() ) ) {
delete_move_after(monster);
runaway = time() + after[ 0 ] + random( after[ 1 ] );
wanderers[monster] = runaway;
add_moving( monster, runaway );
}
} /* move_after() */
/** @ignore yes */
mapping *query_dynamic_auto_load() {
return ({ wanderers, travellers, moving });
} /* query_dynamic_auto_load() */
/** @ignore yes */
void init_dynamic_arg( mapping *maps ) {
wanderers = maps[0];
travellers = maps[1];
moving = maps[2];
} /* init_dynamic_arg() */
/** @ignore yes */
mixed stats() {
return ({
({"wanderers", sizeof(wanderers) }),
({"travellers", sizeof(travellers) }),
});
} /* stats() */