16 Nov, 2011, Hades_Kane wrote in the 1st comment:
Votes: 0
I have adapted a tracking / hunting / mobile memory snippet… in a nutshell, the way it normally exists, when you fight and flee from a mob, the mob remembers you and is hostile. If the mob sees you again, it attacks. With the tracking snippet, when you flee, the mob will look for you until it gives up. I have been working on adapting it to where when you flee and when the mob begins tracking, it remembers its "origin_vnum" and when/if it loses track of you, it tracks back to that vnum. It works fine, mostly. The problem I'm having is when more than one mob is tracking back to their origin vnum, the MUD noticeably lags for everyone. I have a debug commented out in the code I'm going to post (I had several more in, just to track what is going on). When the debug is left in, it creates a buffer overflow pretty quickly because of how quickly and often the code is being checked when the "lost" condition is met. Thing is, when the lost condition isn't met (the original parts of this function), it "pulses" more than being a constant stream. It's very, very fast, but there is a slight pause in between and it is enough to where there is no lag (and no buffer overflow when I have the debugs in that area. What I can't figure out is what I've done in my 'lost' chunk of the code that's making it access so much quicker than the rest of it.

void hunt_victim( CHAR_DATA *ch )
{
int dir;
bool found;
CHAR_DATA *tmp;

if ( ch == NULL || !IS_NPC(ch) )
return;

if(ch->hunting == NULL && !lost)
return;

if(lost)
{
//bug ("Debug1", 0);
if(ch->in_room->vnum == ch->origin_vnum)
{
lost = FALSE;
ch->origin_vnum = 0;
return;
}

dir = find_path( ch->in_room->vnum, ch->origin_vnum,
ch, -40000, TRUE );

if ( dir < 0 || dir > 5 )
return;

if ( IS_SET( ch->in_room->exit[dir]->exit_info, EX_CLOSED ) )
{
do_open( ch, (char *) dir_name[dir] );
return;
}

if (ch->wait > 0)
return;

move_char( ch, dir, TRUE, FALSE );
WAIT_STATE (ch, 3*PULSE_PER_SECOND);
return;
}

/*
* Make sure the victim still exists.
*/
for ( found = 0, tmp = char_list; tmp && !found; tmp = tmp->next )
if ( ch->hunting == tmp )
found = 1;

if ( !found || !can_see( ch, ch->hunting ) )
{
do_say( ch, "Darn! My prey is gone!!" );
ch->hunting = NULL;
lost = TRUE;
return;
}

if ( ch->in_room == ch->hunting->in_room && can_see(ch,ch->hunting) )
{
act( "{w$n {whas found $N {wand attacks!{x",
ch, NULL, ch->hunting, TO_NOTVICT );
act( "{w$n {whas found you and attacks!{x",
ch, NULL, ch->hunting, TO_VICT );
act( "{wYou find $N {wand attack!{x",
ch, NULL, ch->hunting, TO_CHAR);
mob_forget( ch, ch->memory );
multi_hit( ch, ch->hunting, TYPE_UNDEFINED );
ch->hunting = NULL;
return;
}

dir = find_path( ch->in_room->vnum, ch->hunting->in_room->vnum,
ch, -40000, TRUE );

if ( dir < 0 || dir > 5 )
{
act( "$n says 'Darn! Lost $M!'", ch, NULL, ch->hunting, TO_ROOM );
ch->hunting = NULL;
lost = TRUE;
return;
}

/*
* Give a random direction if the mob misses the die roll.
*/
if ( number_percent () > 75 ) /* @ 25% */
{
do
{
dir = number_door();
}
while ( ( ch->in_room->exit[dir] == NULL )
|| ( ch->in_room->exit[dir]->u1.to_room == NULL ) );
}


if ( IS_SET( ch->in_room->exit[dir]->exit_info, EX_CLOSED ) )
{
do_open( ch, (char *) dir_name[dir] );
return;
}

if (ch->wait > 0)
return;

move_char( ch, dir, TRUE, FALSE );
WAIT_STATE (ch, 3*PULSE_PER_SECOND);
return;
}[/code=c]

The find_path function is as follows:
[code=c]
int find_path( int in_room_vnum, int out_room_vnum, CHAR_DATA *ch,
int depth, int in_zone )
{
struct room_q *tmp_q, *q_head, *q_tail;
struct hash_header x_room;
int i, tmp_room, count=0, thru_doors;
ROOM_INDEX_DATA *herep;
ROOM_INDEX_DATA *startp;
EXIT_DATA *exitp;

if ( depth <0 )
{
thru_doors = TRUE;
depth = -depth;
}
else
{
thru_doors = FALSE;
}

startp = get_room_index( in_room_vnum );

init_hash_table( &x_room, sizeof(int), 2048 );
hash_enter( &x_room, in_room_vnum, (void *) - 1 );

/* initialize queue */
q_head = (struct room_q *) malloc(sizeof(struct room_q));
q_tail = q_head;
q_tail->room_nr = in_room_vnum;
q_tail->next_q = 0;

while (q_head)
{
herep = get_room_index( q_head->room_nr );
/* for each room test all directions */
if ( herep->area == startp->area || !in_zone )
{
/* only look in this zone…
saves cpu time and makes world safer for players */
for ( i = 0; i <= 5; i++ )
{
exitp = herep->exit[i];
if ( exit_ok(exitp) && ( thru_doors ? GO_OK_SMARTER : GO_OK ) )
{
/* next room */
tmp_room = herep->exit[i]->u1.to_room->vnum;
if ( tmp_room != out_room_vnum )
{
/* shall we add room to queue ?
count determines total breadth and depth */
if ( !hash_find( &x_room, tmp_room )
&& ( count < depth ) )
/* && !IS_SET( RM_FLAGS(tmp_room), DEATH ) ) */
{
count++;
/* mark room as visted and put on queue */

tmp_q = (struct room_q *)
malloc(sizeof(struct room_q));
tmp_q->room_nr = tmp_room;
tmp_q->next_q = 0;
q_tail->next_q = tmp_q;
q_tail = tmp_q;

/* ancestor for first layer is the direction */
hash_enter( &x_room, tmp_room,
((int)hash_find(&x_room,q_head->room_nr)
== -1) ? (void*)(i+1)
: hash_find(&x_room,q_head->room_nr));
}
}
else
{
/* have reached our goal so free queue */
tmp_room = q_head->room_nr;
for (;q_head;q_head = tmp_q)
{
tmp_q = q_head->next_q;
free(q_head);
}
/* return direction if first layer */
if ((int)hash_find(&x_room,tmp_room)==-1)
{
if (x_room.buckets)
{
/* junk left over from a previous track */
destroy_hash_table(&x_room, donothing);
}
return(i);
}
else
{
/* else return the ancestor */
int i;

i = (int)hash_find(&x_room,tmp_room);
if (x_room.buckets)
{
/* junk left over from a previous track */
destroy_hash_table(&x_room, donothing);
}
return( -1+i);
}
}
}
}
}

/* free queue head and point to next entry */
tmp_q = q_head->next_q;
free(q_head);
q_head = tmp_q;
}

/* couldn't find path */
if ( x_room.buckets )
{
/* junk left over from a previous track */
destroy_hash_table( &x_room, donothing );
}
return -1;
}[/code=c]

The call for hunt_victim (aside from in the flee code) is in our round_update function in fight.c:
[code=c]
/*
* Hunting mobs.
*/
if ( IS_NPC(ch)
&& ch->fighting == NULL
&& IS_AWAKE(ch))
{
if(ch->hunting != NULL || (ch->hunting == NULL && lost))
hunt_victim(ch);
continue;
}[/code=c]

Which is called every pulse it seems:
[code=c]
/*
* Handle all kinds of updates.
* Called once per pulse from game loop.
* Random times to defeat tick-timing clients and players.
*/

void update_handler( void )
{
static int pulse_area;
static int pulse_auction;
static int pulse_mobile;
static int pulse_violence;
static int pulse_point;
static int pulse_magic;
static int pulse_hour;

round_update ();
delay_update ();
chocobo_racing ();

if ( –pulse_area <= 0 )
{
pulse_area = PULSE_AREA;
/* number_range( PULSE_AREA / 2, 3 * PULSE_AREA / 2 ); */
area_update ( );
//bury_treasure ();
olcautosave ( );
}[/code=c]

I'm just not seeing what is making it spaz out at the top when the mob has lost a target and is tracking a supplied vnum vs when they haven't lost their and is tracking the target's vnum.

Any help would be greatly appreciated. I figure it's probably something simple I'm overlooking, and if I need to supply any other functions or information, please let me know.
16 Nov, 2011, David Haley wrote in the 2nd comment:
Votes: 0
Umm, where is 'lost' defined? You didn't make it a global did you? It looks like you did. In that case of course your code will be stomping all over itself when you have multiple mobs tracking. For example, as soon as one mob loses its target and sets lost to true, then every single mob will be invoking that pathfinding function every single round. (because of (ch->hunting == NULL && lost) )

Why did you make lost a global and not a part of the character object?
16 Nov, 2011, Rarva.Riendf wrote in the 3rd comment:
Votes: 0
My mob is lost code:
if (char_upd_it->position >= POS_STUNNED) {
if (IS_NPC(char_upd_it) && !char_upd_it->fighting && !char_upd_it->master
&& !char_upd_it->desc && char_upd_it->in_room) {
/* mobs that are supposed to stay in their area*/
if (IS_SET(char_upd_it->act, ACT_STAY_AREA) && char_upd_it->zone && char_upd_it->in_room && char_upd_it->zone->area != char_upd_it->in_room->area
&& number_percent() < 5) {
act("$n shrugs and wanders on home.", char_upd_it, NULL, NULL, TO_ROOM);
extract_char(char_upd_it, TRUE, CHAR_RETURN_LIST);
continue;
}

// case where a usually sentinel mobs started wandering cause he was attacked or bamf or whaetver
// and his attacker left the area
if (char_upd_it->pIndexData && IS_SET(char_upd_it->pIndexData->act, ACT_SENTINEL)
&& !IS_SET(char_upd_it->act, ACT_SENTINEL) ) {
if ( (!char_upd_it->hunting && char_upd_it->in_room->vnum != char_upd_it->zone->vnum) || (char_upd_it->hunting && char_upd_it->hunting->in_room
&& char_upd_it->in_room->area != char_upd_it->hunting->in_room->area)) {
MP_DATA direction = find_path(char_upd_it->in_room->vnum, char_upd_it->zone->vnum, char_upd_it,
PATH_DEPTH, TRUE, FALSE);
if (direction.value == -1) {
act("$n shrugs and wanders on home.", char_upd_it, NULL, NULL, TO_ROOM);
extract_char(char_upd_it, TRUE, CHAR_RETURN_LIST);
continue;
}
else {
move_char(char_upd_it, direction.value, FALSE);
if (char_upd_it->hunting && (!char_upd_it->hunting->in_room || (char_upd_it->hunting->in_room->area
!= char_upd_it->in_room->area)))
act("$n says 'Damn! Lost $M!'", char_upd_it, NULL, char_upd_it->hunting, TO_ROOM);
if (char_upd_it->in_room == char_upd_it->zone) {
SET_BIT(char_upd_it->act, ACT_SENTINEL);
char_upd_it->hunting = NULL;
}
}
}
}
else
hunt_victim(char_upd_it);
}

if ( !char_upd_it->valid || char_upd_it->position == POS_DEAD)
continue;


find path is an enhanced track that gives not only the first direction to go but also the fullpath if I ask for it.
zone is the variable where I save where the mob repop (put that where the mob is loaded)

till yet, works fine, even when I make every single mob as a wandering one and hunting everything alive (zommmmbiiess)

note that I remove the sentinel flags to allow every mob to track the people who attacked them.
16 Nov, 2011, Hades_Kane wrote in the 4th comment:
Votes: 0
Thanks guys, when I get home from work, I'm going to change the 'lost' value to be character dependent.

I had the 'origin_vnum' as a global earlier and then when I was testing it originally, I noticed a bunch of mobs started tracking along with the one I was fighting and realized the global value was a bad idea there… I guess the distraction of kids kept me from realizing it was a bad idea on the other thing too :p
16 Nov, 2011, David Haley wrote in the 5th comment:
Votes: 0
Global variables are, basically, never a good idea for state specific to a single code entity. :smile:
16 Nov, 2011, Hades_Kane wrote in the 6th comment:
Votes: 0
I honestly have only used global variables maybe in about 2-3 other instances myself, so it's one of those things I'm still finding the proper uses for. This was, clearly, not one of them :p
16 Nov, 2011, Rarva.Riendf wrote in the 7th comment:
Votes: 0
A proper use of a global is an example a flag that says that everyone does triple damage while it is on. As you will set it in a method, bu it will be accessed everywhere.
You do not want to use a char variable for this, as it is something you hardly use so it would be a waste memory wise, and you dont want to save it either. And more than that you will have to check everywhere it is modified to set it again in case a char disconnect as an example.
Or a global counter you can access from everywhere, like the total number of room.
and I realised after reading the code that you should copy paste like 42 43 after line 27, as moving can kill a char.
17 Nov, 2011, David Haley wrote in the 8th comment:
Votes: 0
Quote
You do not want to use a char variable for this, as it is something you hardly use so it would be a waste memory wise,

Well, how would you propose storing it?

Are you so memory strapped that you can't store a single number for a mobile?

Quote
And more than that you will have to check everywhere it is modified to set it again in case a char disconnect as an example.

Mobiles don't disconnect, and if they die and are freed, you don't need to worry anymore.

Quote
Or a global counter you can access from everywhere, like the total number of room.

Didn't we just establish that a global like this is a terrible idea?

An appropriate global variable would be a map from mob to relevant pathfinding information, if you're really so worried about memory.
17 Nov, 2011, Rarva.Riendf wrote in the 9th comment:
Votes: 0
Not sure I follow you… I was talking about the 'power is in use' or 'magic is not allowed' or stuff like that variable that make sense as globals. They are realm variable that can be used in any method, that are not tied to the player or mobs or rooms or anything.

Quote
Didn't we just establish that a global like this is a terrible idea?

Storing total number of room in the realm as a global is a bad idea ? Where do you store it ?

A mob being lost is a calculated value anyway, not something you can store 'once and for good'. Hence the method I provided
17 Nov, 2011, Runter wrote in the 10th comment:
Votes: 0
Quote
Storing total number of room in the realm as a global is a bad idea ? Where do you store it ?


For arguments sake, the concept of global is that it is neither encapsulated nor restricted in scope. So any move towards encapsulation and restriction of the scope to just where it is needed would be a good one. A lot of people advocate never using global values at all. Keep in mind, Singleton != global. And you can have a many to one relationship without it being globally accessible.
17 Nov, 2011, Rarva.Riendf wrote in the 11th comment:
Votes: 0
Runter said:
Quote
Storing total number of room in the realm as a global is a bad idea ? Where do you store it ?

For arguments sake, the concept of global is that it is neither encapsulated nor restricted in scope. So any move towards encapsulation and restriction of the scope to just where it is needed would be a good one. A lot of people advocate never using global values at all. Keep in mind, Singleton != global. And you can have a many to one relationship without it being globally accessible.

I get how to do it properly in Java, but how would you do that in C ? How do you give a global read access , but not a write one ? In a 'convenient' way.
17 Nov, 2011, David Haley wrote in the 12th comment:
Votes: 0
Sorry, I thought we were talking about how to solve the problem at hand, not a general discussion of global variables… :wink:

Quote
A mob being lost is a calculated value anyway, not something you can store 'once and for good'.

That depends on whether you can be hunting somebody and lost, and then find them again, or whether it's a one-time thing where you get lost and immediately start heading home. That's a decision for HK.
17 Nov, 2011, Rarva.Riendf wrote in the 13th comment:
Votes: 0
Quote
With the tracking snippet, when you flee, the mob will look for you until it gives up. I have been working on adapting it to where when you flee and when the mob begins tracking, it remembers its "origin_vnum" and when/if it loses track of you, it tracks back to that vnum.

Considering it is pretty much what I did/wanted for myself, I think my code will work for him. (very little adptation needed for the basic track snippet)
BTW, the thread about track performance came when I was working on that as well :)

little code update after rereading myself:
plug it in char_update, char_upd_it being the loop on char iterator, direction.value the door value
if (char_upd_it->position >= POS_STUNNED) {
if (IS_NPC(char_upd_it) && !char_upd_it->fighting && !char_upd_it->master
&& !char_upd_it->desc && char_upd_it->in_room) {
/* mobs that are supposed to stay in their area*/
if (IS_SET(char_upd_it->act, ACT_STAY_AREA) && char_upd_it->zone && char_upd_it->in_room && char_upd_it->zone->area != char_upd_it->in_room->area
&& number_percent() < 5) {
act("$n shrugs and wanders on home.", char_upd_it, NULL, NULL, TO_ROOM);
extract_char(char_upd_it, TRUE, CHAR_RETURN_LIST);
continue;
}

// case where a usually sentinel mobs started wandering cause he was attacked or bamf or whaetver
// and his attacker left the area
if (char_upd_it->pIndexData && IS_SET(char_upd_it->pIndexData->act, ACT_SENTINEL)
&& !IS_SET(char_upd_it->act, ACT_SENTINEL)) {
if (( !char_upd_it->hunting && char_upd_it->in_room->vnum != char_upd_it->zone->vnum)
|| (char_upd_it->hunting && char_upd_it->hunting->in_room && char_upd_it->in_room->area != char_upd_it->hunting->in_room->area)) {
MP_DATA direction = find_path(char_upd_it->in_room->vnum, char_upd_it->zone->vnum, char_upd_it, PATH_DEPTH, TRUE, FALSE);
if (direction.value == -1) {
act("$n shrugs and wanders on home.", char_upd_it, NULL, NULL, TO_ROOM);
extract_char(char_upd_it, TRUE, CHAR_RETURN_LIST);
continue;
}
move_char(char_upd_it, direction.value, FALSE);
if ( !char_upd_it->valid || char_upd_it->position == POS_DEAD)
continue;
if (char_upd_it->hunting && ( !char_upd_it->hunting->in_room || (char_upd_it->hunting->in_room->area != char_upd_it->in_room->area)))
act("$n says 'Damn! Lost $M!'", char_upd_it, NULL, char_upd_it->hunting, TO_ROOM);
if (char_upd_it->in_room == char_upd_it->zone) {
SET_BIT(char_upd_it->act, ACT_SENTINEL);
char_upd_it->hunting = NULL;
}
}
}
else
hunt_victim(char_upd_it);
}

if ( !char_upd_it->valid || char_upd_it->position == POS_DEAD)
continue;

if (IS_PC(char_upd_it) && IS_SET(char_upd_it->act, PLR_NOQUIT))
REMOVE_BIT(char_upd_it->act, PLR_NOQUIT);

if (char_upd_it->hit < get_max_hit(char_upd_it))
char_upd_it->hit += hit_gain_per_tick(char_upd_it);

if (char_upd_it->mana < get_max_mana(char_upd_it))
char_upd_it->mana += mana_gain_per_tick(char_upd_it);

if (char_upd_it->move < get_max_moves(char_upd_it))
char_upd_it->move += move_gain_per_tick(char_upd_it);

check_max_hpmanamv(char_upd_it);
}

if (!char_upd_it->valid || char_upd_it->position == POS_DEAD)
continue;


MP_DATA find_path(int in_room_vnum, int out_room_vnum, CHAR_DATA *ch, int depth, bool_t in_zone, bool_t isPath)

in_zone means if you only look within the area (if you want to have your mobs look in the whole muds, feel free, isPath is something I added to return the fullpath as well, and not only the fist direction) And I am sending the char_data because if you ask the full path for a player it will only look in room he has visited.
0.0/13