version 1.63 | | version 1.64 |
---|
| | |
/* | | /* |
* static char *rcsid_player_c = | | * static char *rcsid_player_c = |
* "$Id: player.c,v 1.63 2001/07/14 04:11:18 mwedel Exp $"; | | * "$Id: player.c,v 1.64 2001/08/21 05:39:30 mwedel Exp $"; |
*/ | | */ |
| | |
/* | | /* |
| | |
object *op = NULL; | | object *op = NULL; |
player *pl = NULL; | | player *pl = NULL; |
objectlink *ol; | | objectlink *ol; |
int lastdist,tmp; | | int lastdist; |
| | rv_vector rv; |
| | |
for(ol=first_friendly_object,lastdist=1000;ol!=NULL;ol=ol->next) { | | for(ol=first_friendly_object,lastdist=1000;ol!=NULL;ol=ol->next) { |
/* We shoult not find free objects on this friendly list, but it | | /* We should not find free objects on this friendly list, but it |
* does periodically happen. Given that, lets deal with it. | | * does periodically happen. Given that, lets deal with it. |
* While unlikely, it is possible the next object on the friendly | | * While unlikely, it is possible the next object on the friendly |
* list is also free, so encapsulate this in a while loop. | | * list is also free, so encapsulate this in a while loop. |
| | |
* complicated method of state checking would be needed in any case - | | * complicated method of state checking would be needed in any case - |
* as it was, a clever player could type quit, and the function would | | * as it was, a clever player could type quit, and the function would |
* skip them over while waiting for confirmation. | | * skip them over while waiting for confirmation. |
| | * put checks in for |
*/ | | */ |
if(!can_detect_enemy(mon,ol->ob)||ol->ob->map!=mon->map) | | if (!on_same_map(mon,ol->ob) || !can_detect_enemy(mon,ol->ob,&rv)) |
continue; | | continue; |
tmp=distance(ol->ob,mon); | | |
if(lastdist>tmp) { | | if(lastdist>rv.distance) { |
op=ol->ob; | | op=ol->ob; |
lastdist=tmp; | | lastdist=rv.distance; |
} | | } |
} | | } |
for (pl=first_player; pl != NULL; pl=pl->next) { | | for (pl=first_player; pl != NULL; pl=pl->next) { |
if (pl->ob->map == mon->map && can_detect_enemy(mon, pl->ob)) { | | if (on_same_map(mon, pl->ob)&& can_detect_enemy(mon, pl->ob,&rv)) { |
tmp=distance(pl->ob,mon); | | |
if(lastdist>tmp) { | | if(lastdist>rv.distance) { |
op=pl->ob; | | op=pl->ob; |
lastdist=tmp; | | lastdist=rv.distance; |
} | | } |
} | | } |
} | | } |
| | |
return op; | | return op; |
} | | } |
| | |
| | /* I believe this can safely go to 2, 3 is questionable, 4 will likely |
| | * result in a monster paths backtracking. It basically determines how large a |
| | * detour a monster will take from the direction path when looking |
| | * for a path to the player. The values are in the amount of direction |
| | * the deviation is |
| | */ |
| | #define DETOUR_AMOUNT 2 |
| | |
| | /* This is used to prevent infinite loops. Consider a case where the |
| | * player is in a chamber (with gate closed), and monsters are outside. |
| | * with DETOUR_AMOUNT==2, the function will turn each corner, trying to |
| | * find a path into the chamber. This is a good thing, but since there |
| | * is no real path, it will just keep circling the chamber for |
| | * ever (this could be a nice effect for monsters, but not for the function |
| | * to get stuck in. I think for the monsters, if max is reached and |
| | * we return the first direction the creature could move would result in the |
| | * circling behaviour. Unfortunately, this function is also used to determined |
| | * if the creature should cast a spell, so returning a direction in that case |
| | * is probably not a good thing. |
| | */ |
| | #define MAX_SPACES 50 |
| | |
| | |
/* | | /* |
* Returns the direction to the player, if valid. Returns 0 otherwise. | | * Returns the direction to the player, if valid. Returns 0 otherwise. |
* modified to verify there is a path to the player. Does this by stepping towards | | * modified to verify there is a path to the player. Does this by stepping towards |
| | |
* reach player or path is blocked. Thus, will only return true if there is a free | | * reach player or path is blocked. Thus, will only return true if there is a free |
* path to player. Though path may not be a straight line. Note that it will find | | * path to player. Though path may not be a straight line. Note that it will find |
* player hiding along a corridor at right angles to the corridor with the monster. | | * player hiding along a corridor at right angles to the corridor with the monster. |
| | * |
| | * Modified by MSW 2001-08-06 to handle tiled maps. Various notes: |
| | * 1) With DETOUR_AMOUNT being 2, it should still go and find players hiding |
| | * down corriders. |
| | * 2) I think the old code was broken if the first direction the monster |
| | * should move was blocked - the code would store the first direction without |
| | * verifying that the player can actually move in that direction. The new |
| | * code does not store anything in firstdir until we have verified that the |
| | * monster can in fact move one space in that direction. |
| | * 3) I'm not sure how good this code will be for moving multipart monsters, |
| | * since only simple checks to blocked are being called, which could mean the monster |
| | * is blocking itself. |
*/ | | */ |
int path_to_player(object *mon, object *pl,int mindiff) { | | int path_to_player(object *mon, object *pl,int mindiff) { |
int dir,x,y,diff; | | rv_vector rv; |
int oldx,oldy,olddir,newdir; | | int x,y,lastx,lasty,dir,i,diff, firstdir=0,lastdir, max=MAX_SPACES; |
| | mapstruct *m ,*lastmap; |
x=mon->x, y=mon->y; | | |
dir=find_dir_2(x-pl->x,y-pl->y); | | get_rangevector(mon, pl, &rv, 0); |
| | if (rv.distance<mindiff) return 0; |
/* diff is how many steps from monster to player */ | | |
diff= abs(x-pl->x) > abs(y-pl->y) ? abs(x-pl->x) : abs(y-pl->y); | | x=mon->x; |
if(diff<mindiff) return 0; | | y=mon->y; |
| | m=mon->map; |
newdir = olddir = dir; | | dir = rv.direction; |
oldx = x, oldy=y; | | lastdir = rv.direction; |
| | diff = FABS(rv.distance_x)>FABS(rv.distance_y)?FABS(rv.distance_x):FABS(rv.distance_y); |
while( diff>1 ) { | | /* If we can't solve it within the search distance, return now. */ |
for( ; diff>1; diff--) { | | if (diff>max) return 0; |
oldx=x, oldy=y; | | while (diff >1 && max>0) { |
x+=freearr_x[newdir],y+=freearr_y[newdir]; | | lastx = x; |
if(blocked(mon->map,x,y)){ | | lasty = y; |
if (olddir == (newdir=find_dir_2(oldx-pl->x, oldy-pl->y))){ | | lastmap = m; |
if ( (newdir%2) == 0) | | x = lastx + freearr_x[dir]; |
return 0; | | y = lasty + freearr_y[dir]; |
/* if heading straight then try diag step towards player. needed to find | | |
* player in a L position compared to monster along corridors. the 2* and | | m = get_map_from_coord(m, &x, &y); |
* RANDOM are to go around a block and find the player behind. */ | | /* Space is blocked - try changing direction a little */ |
if ((newdir==1) || (newdir==5)) { | | if (blocked(m, x, y) && (m == mon->map && blocked_link(mon, x, y))) { |
( (2*x) > (2*(pl->x+RANDOM()%2)-1) ) ? x-- : x++; | | /* recalculate direction from last good location. Possible |
} else if((newdir==3) || (newdir==7)) { | | * we were not traversing ideal location before. |
( (2*y) > (2*(pl->y+RANDOM()%2)-1) ) ? y-- : y++; | | */ |
} | | get_rangevector_from_mapcoord(lastmap, lastx, lasty, pl, &rv, 0); |
if(blocked(mon->map,x,y)) | | if (rv.direction != dir) { |
return 0; /* path blocked to player */ | | /* OK - says direction should be different - lets reset the |
diff = abs(x-pl->x) > abs(y-pl->y) ? abs(x-pl->x) : abs(y-pl->y); | | * the values so it will try again. |
} else { /*have a new direction to try */ | | */ |
olddir = newdir, x=oldx, y=oldy; | | x = lastx; |
diff = abs(oldx-pl->x) > abs(oldy-pl->y) ? abs(oldx-pl->x) : abs(oldy-pl->y); | | y = lasty; |
} | | m = lastmap; |
} | | dir = rv.direction; |
} | | } else { |
/* diff may have headed us all the way down that direction, but not next to the player | | /* direct path is blocked - try taking a side step to |
* so head towards player. If next to player then will exit while loop */ | | * either the left or right. |
newdir=find_dir_2(x-pl->x, y-pl->y); | | * Note increase the values in the loop below to be |
diff = abs(x-pl->x) > abs(y-pl->y) ? abs(x-pl->x) : abs(y-pl->y); | | * more than -1/1 respectively will mean the monster takes |
} | | * bigger detour. Have to be careful about these values getting |
return dir; | | * too big (3 or maybe 4 or higher) as the monster may just try |
| | * stepping back and forth |
| | */ |
| | for (i=-DETOUR_AMOUNT; i<=DETOUR_AMOUNT; i++) { |
| | if (i==0) continue; /* already did this, so skip it */ |
| | /* Use lastdir here - otherwise, |
| | * since the direction that the creature should move in |
| | * may change, you could get infinite loops. |
| | * ie, player is northwest, but monster can only |
| | * move west, so it does that. It goes some distance, |
| | * gets blocked, finds that it should move north, |
| | * can't do that, but now finds it can move east, and |
| | * gets back to its original point. lastdir contains |
| | * the last direction the creature has successfully |
| | * moved. |
| | */ |
| | |
| | x = lastx + freearr_x[absdir(lastdir+i)]; |
| | y = lasty + freearr_y[absdir(lastdir+i)]; |
| | m = lastmap; |
| | if (!out_of_map(m, x, y)) { |
| | m = get_map_from_coord(m, &x, &y); |
| | if (!blocked(m, x, y) && (m == mon->map && blocked_link(mon, x, y))) break; |
| | } |
| | } |
| | /* go through entire loop without finding a valid |
| | * sidestep to take - thus, no valid path. |
| | */ |
| | if (i==(DETOUR_AMOUNT+1)) |
| | return 0; |
| | diff--; |
| | lastdir=dir; |
| | max--; |
| | if (!firstdir) firstdir = dir+i; |
| | } /* else check alternate directions */ |
| | } /* if blocked */ |
| | else { |
| | /* we moved towards creature, so diff is less */ |
| | diff--; |
| | max--; |
| | lastdir=dir; |
| | if (!firstdir) firstdir = dir; |
| | } |
| | if (diff<=1) { |
| | /* Recalculate diff (distance) because we may not have actually |
| | * headed toward player for entire distance. |
| | */ |
| | get_rangevector_from_mapcoord(m, x, y, pl, &rv, 0); |
| | diff = FABS(rv.distance_x)>FABS(rv.distance_y)?FABS(rv.distance_x):FABS(rv.distance_y); |
| | } |
| | if (diff>max) return 0; |
| | } |
| | /* If we reached the max, didn't find a direction in time */ |
| | if (!max) return 0; |
| | return firstdir; |
} | | } |
| | |
void give_initial_items(object *pl,treasurelist *items) { | | void give_initial_items(object *pl,treasurelist *items) { |
| | |
return tmp; | | return tmp; |
} | | } |
| | |
| | /* moved door processing out of move_player_attack. |
| | * returns 1 if player has opened the door with a key |
| | * such that the caller should not do anything more, |
| | * 0 otherwise |
| | */ |
| | static int player_attack_door(object *op, object *door) |
| | { |
| | |
| | /* If its a door, try to find a use a key. If we do destroy the door, |
| | * might as well return immediately as there is nothing more to do - |
| | * otherwise, we fall through to the rest of the code. |
| | */ |
| | object *key=find_key(op, op, door); |
| | |
| | /* IF we found a key, do some extra work */ |
| | if (key) { |
| | object *container=key->env; |
| | |
| | play_sound_map(op->map, op->x, op->y, SOUND_OPEN_DOOR); |
| | if(action_makes_visible(op)) make_visible(op); |
| | if(door->inv && door->inv->type ==RUNE) spring_trap(door->inv,op); |
| | if (door->type == DOOR) { |
| | hit_player(door,9999,op,AT_PHYSICAL); /* Break through the door */ |
| | } |
| | else if(door->type==LOCKED_DOOR) { |
| | new_draw_info_format(NDI_UNIQUE, NDI_BROWN, op, |
| | "You open the door with the %s", query_short_name(key)); |
| | remove_door2(door); /* remove door without violence ;-) */ |
| | } |
| | /* Do this after we print the message */ |
| | decrease_ob(key); /* Use up one of the keys */ |
| | /* Need to update the weight the container the key was in */ |
| | if (container != op) |
| | esrv_update_item(UPD_WEIGHT, op, container); |
| | return 1; /* Nothing more to do below */ |
| | } else if (door->type==LOCKED_DOOR) { |
| | /* Might as well return now - no other way to open this */ |
| | new_draw_info(NDI_UNIQUE | NDI_NAVY, 0, op, door->msg); |
| | return 1; |
| | } |
| | return 0; |
| | } |
| | |
/* This function is just part of a breakup from move_player. | | /* This function is just part of a breakup from move_player. |
* It should keep the code cleaner. | | * It should keep the code cleaner. |
* When this is called, the players direction has been updated | | * When this is called, the players direction has been updated |
| | |
{ | | { |
object *tmp; | | object *tmp; |
int nx=freearr_x[dir]+op->x,ny=freearr_y[dir]+op->y; | | int nx=freearr_x[dir]+op->x,ny=freearr_y[dir]+op->y; |
| | mapstruct *m; |
| | |
/* If braced, or can't move to the square, and it is not out of the | | /* If braced, or can't move to the square, and it is not out of the |
* map, attack it. Note order of if statement is important - don't | | * map, attack it. Note order of if statement is important - don't |
| | |
* quite a bit of processing. However, it probably is less than what | | * quite a bit of processing. However, it probably is less than what |
* move_ob uses. | | * move_ob uses. |
*/ | | */ |
if ((op->contr->braced || !move_ob(op,dir,op)) && | | if ((op->contr->braced || !move_ob(op,dir,op)) && !out_of_map(op->map,nx,ny)) { |
!out_of_map(op->map,nx,ny)) { | | if (OUT_OF_REAL_MAP(op->map, nx, ny)) |
| | m = get_map_from_coord(op->map, &nx, &ny); |
| | else m =op->map; |
| | |
| | if ((tmp=get_map_ob(m,nx,ny))==NULL) { |
if ((tmp=get_map_ob(op->map,nx,ny))==NULL) { | | |
/* LOG(llevError,"player_move_attack: get_map_ob returns NULL, but player can not more there.\n");*/ | | /* LOG(llevError,"player_move_attack: get_map_ob returns NULL, but player can not more there.\n");*/ |
return; | | return; |
} | | } |
| | |
if(tmp->head != NULL) | | if(tmp->head != NULL) |
tmp = tmp->head; | | tmp = tmp->head; |
| | |
/* If its a door, try to find a use a key. If we do destroy the door, | | if ((tmp->type==DOOR && tmp->stats.hp>=0) || (tmp->type==LOCKED_DOOR)) |
* might as well return immediately as there is nothing more to do - | | if (player_attack_door(op, tmp)) return; |
* otherwise, we fall through to the rest of the code. | | |
*/ | | |
if ((tmp->type==DOOR && tmp->stats.hp>=0) || (tmp->type==LOCKED_DOOR)) { | | |
object *key=find_key(op, op, tmp); | | |
| | |
/* IF we found a key, do some extra work */ | | |
if (key) { | | |
object *container=key->env; | | |
| | |
play_sound_map(op->map, op->x, op->y, SOUND_OPEN_DOOR); | | |
if(action_makes_visible(op)) make_visible(op); | | |
if(tmp->inv && tmp->inv->type ==RUNE) spring_trap(tmp->inv,op); | | |
if (tmp->type == DOOR) { | | |
hit_player(tmp,9999,op,AT_PHYSICAL); /* Break through the door */ | | |
} | | |
else if(tmp->type==LOCKED_DOOR) { | | |
new_draw_info_format(NDI_UNIQUE, NDI_BROWN, op, | | |
"You open the door with the %s", query_short_name(key)); | | |
remove_door2(tmp); /* remove door without violence ;-) */ | | |
} | | |
/* Do this after we print the message */ | | |
decrease_ob(key); /* Use up one of the keys */ | | |
/* Need to update the weight the container the key was in */ | | |
if (container != op) | | |
esrv_update_item(UPD_WEIGHT, op, container); | | |
return; /* Nothing more to do below */ | | |
} else if (tmp->type==LOCKED_DOOR) { | | |
/* Might as well return now - no other way to open this */ | | |
new_draw_info(NDI_UNIQUE | NDI_NAVY, 0, op, tmp->msg); | | |
return; | | |
} | | |
} | | |
| | |
/* The following deals with possibly attacking peaceful | | /* The following deals with possibly attacking peaceful |
* or frienddly creatures. Basically, all players are considered | | * or frienddly creatures. Basically, all players are considered |
| | |
else if(QUERY_FLAG(tmp,FLAG_CAN_ROLL)&&(!op->contr->braced)) { | | else if(QUERY_FLAG(tmp,FLAG_CAN_ROLL)&&(!op->contr->braced)) { |
recursive_roll(tmp,dir,op); | | recursive_roll(tmp,dir,op); |
if(action_makes_visible(op)) make_visible(op); | | if(action_makes_visible(op)) make_visible(op); |
| | |
} | | } |
| | |
/* Any generic living creature. Including things like doors. | | /* Any generic living creature. Including things like doors. |
| | |
* that party_number -1 is no party, so attacks can still happen. | | * that party_number -1 is no party, so attacks can still happen. |
*/ | | */ |
| | |
else | | else if ((tmp->stats.hp>=0) && QUERY_FLAG(tmp, FLAG_ALIVE) && |
if ((tmp->stats.hp>=0) && QUERY_FLAG(tmp, FLAG_ALIVE) && | | |
((tmp->type!=PLAYER || op->contr->party_number==-1 || | | ((tmp->type!=PLAYER || op->contr->party_number==-1 || |
op->contr->party_number!=tmp->contr->party_number))) { | | op->contr->party_number!=tmp->contr->party_number))) { |
| | |
| | |
/* If attacking another player, that player gets automatic | | /* If attacking another player, that player gets automatic |
* hitback, and doesn't loose luck either. | | * hitback, and doesn't loose luck either. |
*/ | | */ |
if (tmp->type == PLAYER && tmp->stats.hp >= 0 && | | if (tmp->type == PLAYER && tmp->stats.hp >= 0 && !tmp->contr->has_hit) { |
!tmp->contr->has_hit) | | |
{ | | |
short luck = tmp->stats.luck; | | short luck = tmp->stats.luck; |
tmp->contr->has_hit = 1; | | tmp->contr->has_hit = 1; |
skill_attack(op, tmp, 0, NULL); | | skill_attack(op, tmp, 0, NULL); |
| | |
} | | } |
if(action_makes_visible(op)) make_visible(op); | | if(action_makes_visible(op)) make_visible(op); |
} | | } |
} | | } /* if player should attack something */ |
} | | } |
| | |
int move_player(object *op,int dir) { | | int move_player(object *op,int dir) { |
| | |
* our backs...on the other hand, does the "facing" direction | | * our backs...on the other hand, does the "facing" direction |
* imply the way your head, or body is facing? Its possible | | * imply the way your head, or body is facing? Its possible |
* for them to differ. Sigh, this fctn could get a bit more complex. | | * for them to differ. Sigh, this fctn could get a bit more complex. |
* -b.t. */ | | * -b.t. |
| | * This function is now map tiling safe. |
| | */ |
| | |
int player_can_view (object *pl,object *op) { | | int player_can_view (object *pl,object *op) { |
| | rv_vector rv; |
| | int dx,dy; |
| | |
if(pl->type!=PLAYER) { | | if(pl->type!=PLAYER) { |
LOG(llevError,"player_can_view() called for non-player object\n"); | | LOG(llevError,"player_can_view() called for non-player object\n"); |
return -1; | | return -1; |
} | | } |
| | if (!pl || !op) return 0; |
| | |
| | if(op->head) { op = op->head; } |
| | get_rangevector(pl, op, &rv, 0x1); |
| | |
if(pl&&op&&pl->map==op->map) { | | |
/* starting with the 'head' part, lets loop | | /* starting with the 'head' part, lets loop |
* through the object and find if it has any | | * through the object and find if it has any |
* part that is in the los array but isnt on | | * part that is in the los array but isnt on |
* a blocked los square. */ | | * a blocked los square. |
if(op->head) { op = op->head; } | | * we use the archetype to figure out offsets. |
| | */ |
while(op) { | | while(op) { |
if(pl->y - MAP_CLIENT_Y/2 <= op->y && | | dx = rv.distance_x + op->arch->clone.x; |
pl->y + MAP_CLIENT_Y/2 >= op->y && | | dy = rv.distance_y + op->arch->clone.y; |
pl->x - MAP_CLIENT_X/2 <= op->x && | | |
pl->x + MAP_CLIENT_X/2 >= op->x && | | /* only the viewable area the player sees is updated by LOS |
!pl->contr->blocked_los[op->x-pl->x+MAP_CLIENT_X/2][op->y-pl->y+MAP_CLIENT_Y/2] ) | | * code, so we need to restrict ourselves to that range of values |
| | * for any meaningful values. |
| | */ |
| | if (FABS(dx) <= (pl->contr->socket.mapx/2) && |
| | FABS(dy) <= (pl->contr->socket.mapy/2) && |
| | !pl->contr->blocked_los[dx + (pl->contr->socket.mapx/2)][dy+(pl->contr->socket.mapy/2)] ) |
return 1; | | return 1; |
op = op->more; | | op = op->more; |
} | | } |
} | | |
return 0; | | return 0; |
} | | } |
| | |