/**************************************************************************** * [S]imulated [M]edieval [A]dventure multi[U]ser [G]ame | \\._.// * * -----------------------------------------------------------| (0...0) * * SMAUG 1.4 (C) 1994, 1995, 1996, 1998 by Derek Snider | ).:.( * * -----------------------------------------------------------| {o o} * * SMAUG code team: Thoric, Altrag, Blodkai, Narn, Haus, | / ' ' \ * * Scryn, Rennard, Swordbearer, Gorog, Grishnakh, Nivek, |~'~.VxvxV.~'~* * Tricops and Fireblade | * * ------------------------------------------------------------------------ * * Tracking/hunting module * ****************************************************************************/ #include <sys/types.h> #include <stdio.h> #include <string.h> #include <time.h> #include "mud.h" #define BFS_ERROR -1 #define BFS_ALREADY_THERE -2 #define BFS_NO_PATH -3 #define TRACK_THROUGH_DOORS extern sh_int top_room; /* You can define or not define TRACK_THOUGH_DOORS, above, depending on whether or not you want track to find paths which lead through closed or hidden doors. */ typedef struct bfs_queue_struct BFS_DATA; struct bfs_queue_struct { ROOM_INDEX_DATA *room; char dir; BFS_DATA *next; }; static BFS_DATA *queue_head = NULL, *queue_tail = NULL, *room_queue = NULL; /* Utility macros */ #define MARK(room) (xSET_BIT( (room)->room_flags, ROOM_MARK) ) #define UNMARK(room) (xREMOVE_BIT( (room)->room_flags, ROOM_MARK) ) #define IS_MARKED(room) (xIS_SET( (room)->room_flags, ROOM_MARK) ) bool valid_edge(EXIT_DATA * pexit) { if (pexit->to_room #ifndef TRACK_THROUGH_DOORS && !IS_SET(pexit->exit_info, EX_CLOSED) #endif && !IS_MARKED(pexit->to_room)) return TRUE; else return FALSE; } void bfs_enqueue(ROOM_INDEX_DATA * room, char dir) { BFS_DATA *curr; curr = malloc(sizeof(BFS_DATA)); curr->room = room; curr->dir = dir; curr->next = NULL; if (queue_tail) { queue_tail->next = curr; queue_tail = curr; } else queue_head = queue_tail = curr; } void bfs_dequeue(void) { BFS_DATA *curr; curr = queue_head; if (!(queue_head = queue_head->next)) queue_tail = NULL; free(curr); } void bfs_clear_queue(void) { while (queue_head) bfs_dequeue(); } void room_enqueue(ROOM_INDEX_DATA * room) { BFS_DATA *curr; curr = malloc(sizeof(BFS_DATA)); curr->room = room; curr->next = room_queue; room_queue = curr; } void clean_room_queue(void) { BFS_DATA *curr, *curr_next; for (curr = room_queue; curr; curr = curr_next) { UNMARK(curr->room); curr_next = curr->next; free(curr); } room_queue = NULL; } int find_first_step(ROOM_INDEX_DATA * src, ROOM_INDEX_DATA * target, int maxdist, int *steps) { int curr_dir, count; EXIT_DATA *pexit; if (!src || !target) { bug("Illegal value passed to find_first_step (track.c)", 0); return BFS_ERROR; } if (src == target) return BFS_ALREADY_THERE; if (src->area != target->area) return BFS_NO_PATH; room_enqueue(src); MARK(src); /* first, enqueue the first steps, saving which direction we're going. */ for (pexit = src->first_exit; pexit; pexit = pexit->next) if (valid_edge(pexit)) { curr_dir = pexit->vdir; MARK(pexit->to_room); room_enqueue(pexit->to_room); bfs_enqueue(pexit->to_room, curr_dir); } count = 0; while (queue_head) { if (++count > maxdist) { bfs_clear_queue(); clean_room_queue(); return BFS_NO_PATH; } if (queue_head->room == target) { curr_dir = queue_head->dir; bfs_clear_queue(); clean_room_queue(); if (steps) *steps = count; return curr_dir; } else { for (pexit = queue_head->room->first_exit; pexit; pexit = pexit->next) if (valid_edge(pexit)) { curr_dir = pexit->vdir; MARK(pexit->to_room); room_enqueue(pexit->to_room); bfs_enqueue(pexit->to_room, queue_head->dir); } bfs_dequeue(); } } clean_room_queue(); return BFS_NO_PATH; } char *get_wtrack_dir(CHAR_DATA *ch, CHAR_DATA *victim) { int diffx, diffy; int distx, disty, total; diffx = victim->coord->x - ch->coord->x; diffy = victim->coord->y - ch->coord->y; distx = abs(ch->coord->x - victim->coord->x); disty = abs(ch->coord->y - victim->coord->y); total = distx+disty; if (total == 1) { if (diffy > 0) return "south"; else if (diffy < 0) return "north"; else if (diffx > 0) return "east"; else return "west"; } if (diffy >= total/2) //S { if (diffx >= total/2) //se return "southeast"; else if (diffx <= total/-2) //sw return "southwest"; else return "south"; } if (diffy <= total/-2) //N { if (diffx >= total/2) //ne return "northeast"; else if (diffx <= total/-2) //nw return "northwest"; else return "north"; } if (diffx >= total/2) //E { if (diffy >= total/2) //se return "southeast"; else if (diffy <= total/-2) //ne return "northeast"; else return "east"; } if (diffx <= total/-2) //W { if (diffy >= total/2) //we return "southwest"; else if (diffy <= total/-2) //we return "northwest"; else return "west"; } return "unknown"; } void do_track(CHAR_DATA * ch, char *argument) { CHAR_DATA *vict; char arg[MIL]; char buf[MSL]; char name[MSL]; int dir, maxdist; sh_int level; sh_int percent; int steps; int fnd = 0; int cnt = 0; level = POINT_LEVEL(LEARNED(ch, gsn_track), MASTERED(ch, gsn_track)); if (!IS_NPC(ch) && (ch->pcdata->learned[gsn_track] <= 0 || ch->pcdata->ranking[gsn_track] <= 0)) { send_to_char("You do not know of this skill yet.\n\r", ch); return; } one_argument(argument, arg); if (argument[0] == '\0') { send_to_char("Syntax: track <victim>\n\r", ch); send_to_char("Syntax: track area [color] [color..]\n\r", ch); send_to_char("Color being: red pink yellow cyan white green\n\r", ch); return; } WAIT_STATE(ch, skill_table[gsn_track]->beats); if (!str_cmp(arg, "area")) { int red, pink, yellow, white, green, cyan; red=pink=yellow=white=green=cyan=1; argument = one_argument(argument, arg); if (argument[0] != '\0') { red=pink=yellow=white=green=cyan=0; for (;;) { argument = one_argument(argument, arg); if (arg[0] == '\0') break; else { if (!str_cmp(arg, "red")) red = 1; if (!str_cmp(arg, "pink")) pink = 1; if (!str_cmp(arg, "yellow")) yellow = 1; if (!str_cmp(arg, "cyan")) cyan = 1; if (!str_cmp(arg, "white")) white = 1; if (!str_cmp(arg, "green")) green = 1; } } } for (vict = first_char; vict; vict = vict->next) { if (vict->in_room && vict->in_room->area == ch->in_room->area) { if (xIS_SET(vict->act, ACT_NOTRACK)) continue; if (!can_see_map(ch, vict)) continue; if (IN_WILDERNESS(ch)) { percent = UMIN(5+level/2, 45); if (abs(ch->coord->x - vict->coord->x) > percent || abs(ch->coord->y - vict->coord->y) > percent) continue; steps = UMAX(abs(ch->coord->x - vict->coord->x), abs(ch->coord->y - vict->coord->y)); steps = steps * 5; } else { maxdist = 250; if (!IS_NPC(ch)) maxdist = (maxdist * level / 60); steps = 0; dir = find_first_step(ch->in_room, vict->in_room, maxdist, &steps); if (dir == BFS_NO_PATH || dir == BFS_ERROR) continue; } percent = 50 + level; if (number_range(1, 100) > percent) continue; fnd++; sprintf(name, "%.20s", PERS_MAP_NAME(vict, ch)); sprintf(buf, MXPFTAG("Command 'track \"%s\"' desc='Click here to track the target'", "%s", "/Command") "%s", PERS_MAP_NAME(vict, ch), name, add_space(strlen(name), 20)); if (steps <= 15 && red) { ch_printf(ch, " &w&R%s", buf); cnt++; } else if (steps >= 16 && steps <= 35 && pink) { ch_printf(ch, " &w&P%s", buf); cnt++; } else if (steps >= 36 && steps <= 60 && yellow) { ch_printf(ch, " &w&Y%s", buf); cnt++; } else if (steps >= 61 && steps <= 100 && cyan) { ch_printf(ch, " &w&c%s", buf); cnt++; } else if (steps >= 101 && steps <= 150 && white) { ch_printf(ch, " &w&W%s", buf); cnt++; } else if (steps > 150 && green) { ch_printf(ch, " &w&G%s", buf); cnt++; } else continue; if (cnt % 4 == 0) send_to_char("\n\r", ch); } } if (fnd > 0) learn_from_success(ch, gsn_track, vict); if (cnt > 0) send_to_char("\n\r", ch); return; } if (!(vict = get_char_world(ch, arg))) { send_to_char("You can't find a trail of anyone like that.\n\r", ch); return; } if (xIS_SET(vict->act, ACT_NOTRACK)) { send_to_char("You can't find a trail of anyone like that.\n\r", ch); return; } if (IN_WILDERNESS(ch) || IN_WILDERNESS(vict)) { if (IN_WILDERNESS(ch) && IN_WILDERNESS(vict)) { percent = UMIN(10+level, 90); if (abs(ch->coord->x - vict->coord->x) > percent || abs(ch->coord->y - vict->coord->y) > percent) { send_to_char("You cannot pickup a trail from here.\n\r", ch); learn_from_failure(ch, gsn_track, vict); return; } else { percent = 10 + level*3/2; if (number_range(1, 100) <= percent) { ch_printf(ch, "You sense %s is located to the %s\n\r", PERS_MAP_NAME(vict, ch), get_wtrack_dir(ch, vict)); learn_from_success(ch, gsn_track, vict); return; } else { send_to_char("You cannot sense a trail from here.\n\r", ch); learn_from_failure(ch, gsn_track, vict); return; } } } else { send_to_char("You can't find a trail of anyone like that.\n\r", ch); return; } } maxdist = 500; if (!IS_NPC(ch)) maxdist = (maxdist * level / 60); percent = 10 + level*3/2; if (number_range(1, 100) > percent) { send_to_char("You can't sense a trail from here.\n\r", ch); learn_from_failure(ch, gsn_track, vict); return; } dir = find_first_step(ch->in_room, vict->in_room, maxdist, NULL); switch (dir) { case BFS_ERROR: send_to_char("Hmm... something seems to be wrong.\n\r", ch); break; case BFS_ALREADY_THERE: send_to_char("You're already in the same room!\n\r", ch); break; case BFS_NO_PATH: send_to_char("You can't sense a trail from here.\n\r", ch); learn_from_failure(ch, gsn_track, vict); break; default: ch_printf(ch, "You sense a trail %s from here...\n\r", dir_name[dir]); learn_from_success(ch, gsn_track, vict); break; } } void found_prey(CHAR_DATA * ch, CHAR_DATA * victim) { char buf[MSL]; char victname[MSL]; if (victim == NULL) { bug("Found_prey: null victim", 0); return; } if (victim->in_room == NULL) { bug("Found_prey: null victim->in_room", 0); return; } if (!IS_AWAKE(ch) || IS_AFFECTED(ch, AFF_WEB)) return; sprintf(victname, IS_NPC(victim) ? victim->short_descr : victim->name); if (!can_see(ch, victim)) { if (number_percent() < 90) return; switch (number_bits(3)) { case 0: sprintf(buf, "Don't make me find you, %s!", victname); do_say(ch, buf); break; case 1: case 2: act(AT_ACTION, "$n sniffs around the room for $N.", ch, NULL, victim, TO_NOTVICT); act(AT_ACTION, "You sniff around the room for $N.", ch, NULL, victim, TO_CHAR); act(AT_ACTION, "$n sniffs around the room for you.", ch, NULL, victim, TO_VICT); sprintf(buf, "I can smell your blood!"); do_say(ch, buf); break; case 3: sprintf(buf, "I'm going to tear %s apart!", victname); do_yell(ch, buf); break; case 4: case 5: do_say(ch, "Just wait until I find you..."); break; case 6: case 7: do_say(ch, "To hell with you..."); stop_hunting(ch); stop_hating(ch); if (IN_WILDERNESS(ch)) find_next_hunt(ch, 0); break; } return; } if (is_room_safe(ch)) { if (number_percent() < 90) return; switch (number_bits(2)) { case 0: do_say(ch, "C'mon out, you coward!"); sprintf(buf, "%s is a bloody coward!", victname); do_yell(ch, buf); break; case 1: sprintf(buf, "Let's take this outside, %s", victname); do_say(ch, buf); break; case 2: sprintf(buf, "%s is a yellow-bellied wimp!", victname); do_yell(ch, buf); break; case 3: act(AT_ACTION, "$n takes a few swipes at $N.", ch, NULL, victim, TO_NOTVICT); act(AT_ACTION, "You try to take a few swipes $N.", ch, NULL, victim, TO_CHAR); act(AT_ACTION, "$n takes a few swipes at you.", ch, NULL, victim, TO_VICT); break; } return; } switch (number_bits(2)) { case 0: sprintf(buf, "%s Your blood is mine!", victname); do_tell(ch, buf); break; case 1: sprintf(buf, "%s Alas, we meet again!", victname); do_tell(ch, buf); break; case 2: sprintf(buf, "%s What do you want on your tombstone?", victname); do_tell(ch, buf); break; case 3: act(AT_ACTION, "$n lunges at $N from out of nowhere!", ch, NULL, victim, TO_NOTVICT); act(AT_ACTION, "You lunge at $N catching $M off guard!", ch, NULL, victim, TO_CHAR); act(AT_ACTION, "$n lunges at you from out of nowhere!", ch, NULL, victim, TO_VICT); } //stop_hunting(ch); set_fighting(ch, victim); one_hit(ch, victim, TYPE_UNDEFINED, LM_BODY); return; } void hunt_victim(CHAR_DATA * ch) { bool found; CHAR_DATA *tmp; EXIT_DATA *pexit; sh_int ret; int chance; if (!ch || !ch->hunting) return; if (ch->fight_timer > 0) return; /* make sure the char still exists */ for (found = FALSE, tmp = first_char; tmp && !found; tmp = tmp->next) if (ch->hunting->who == tmp) found = TRUE; if (!found) { do_say(ch, "Damn! My prey is gone!!"); stop_hunting(ch); return; } if (ch->in_room == ch->hunting->who->in_room) { if (ch->fighting) return; found_prey(ch, ch->hunting->who); return; } ret = find_first_step(ch->in_room, ch->hunting->who->in_room, 500, NULL); if (ret < 0) { do_say(ch, "Damn! Lost my prey!"); stop_hunting(ch); return; } else { if ((pexit = get_exit(ch->in_room, ret)) == NULL) { bug("Hunt_victim: lost exit?", 0); return; } chance = get_hunt_cost(pexit->to_room); if (chance <= 1 || number_range(1, chance) == 1) { if (!xIS_SET(ch->act, ACT_MILITARY) || (xIS_SET(ch->act, ACT_MILITARY) && !xIS_SET(ch->miflags, KM_SENTINEL))) move_char(ch, pexit, FALSE); } if (!ch->hunting) { if (!ch->in_room) { bug("Hunt_victim: no ch->in_room! Mob #%d, name: %s. Placing mob in limbo.", ch->pIndexData->vnum, ch->name); char_to_room(ch, get_room_index(ROOM_VNUM_LIMBO)); return; } do_say(ch, "Damn! Lost my prey!"); return; } if (ch->in_room == ch->hunting->who->in_room) found_prey(ch, ch->hunting->who); else { CHAR_DATA *vch; /* perform a ranged attack if possible */ /* Changed who to name as scan_for_victim expects the name and * Not the char struct. --Shaddai */ if ((vch = scan_for_victim(ch, pexit, ch->hunting->name)) != NULL) { if (!mob_fire(ch, ch->hunting->who->name, pexit->vdir)) { /* ranged spell attacks go here */ } } } return; } } /* Below is used to hunt/track on Wilderness Map */ int find_dir(int cx, int cy, int vx, int vy) { if (cx > vx && cy > vy) return 7; if (cx < vx && cy > vy) return 6; if (cx > vx && cy < vy) return 9; if (cx < vx && cy < vy) return 8; if (cx == vx && cy > vy) return 0; if (cx == vx && cy < vy) return 2; if (cx > vx && cy == vy) return 3; if (cx < vx && cy == vy) return 1; return -1; } int get_x(int currx, int dir) { if (dir == 6 || dir == 1 || dir == 8) return currx + 1; if (dir == 7 || dir == 3 || dir == 9) return currx - 1; return currx; } int get_y(int curry, int dir) { if (dir == 7 || dir == 0 || dir == 6) return curry - 1; if (dir == 9 || dir == 2 || dir == 8) return curry + 1; return curry; } int find_slot(dir) { if (dir == 0) return 1; if (dir == 6) return 2; if (dir == 1) return 3; if (dir == 8) return 4; if (dir == 2) return 5; if (dir == 9) return 6; if (dir == 3) return 7; if (dir == 7) return 8; return 0; } int alt_dir(CHAR_DATA * ch, int x, int y, int dir) { int gox; int goy; int godir; int newdir; int addto; int tries; const int spin[25] = { 0, 0, 6, 1, 8, 2, 9, 3, 7, 0, 6, 1, 8, 2, 9, 3, 7, 0, 6, 1, 8, 2, 9, 3, 7 }; for (tries = 1; tries < 9; tries++) { addto = find_slot(dir); if (tries % 2 == 1) godir = ((tries + 1) / 2); else godir = (tries / 2); if (tries != 9) if (tries % 2 == 1) newdir = spin[8 + addto + godir]; else newdir = spin[8 + addto - godir]; else newdir = spin[8 + addto - 4]; gox = get_x(x, newdir); goy = get_y(y, newdir); if (sect_show[(int)map_sector[ch->map][gox][goy]].canpass == FALSE || is_set_wilderness(ch, ROOM_NO_MOB, gox, goy, ch->map)) continue; else return newdir; } return -1; } // 0 - Wilderness 1 -Battle int go_find_victim(CHAR_DATA * ch, CHAR_DATA * victim, int type) { sh_int dir; sh_int count; sh_int numcount; sh_int times; sh_int locount[10]; sh_int lo = 1000; sh_int lonum = -1; sh_int initdir[10]; sh_int currx, curry, gox, goy; if (ch->coord->x == victim->coord->x && ch->coord->y == victim->coord->y && ch->map == victim->map) return -2; if (ch->in_room->area != victim->in_room->area) { return -1; } numcount = 15; if (xIS_SET(ch->act, ACT_MILITARY)) numcount = ch->m6 + 2; for (times = 0; times < 5; times++) { currx = ch->coord->x; curry = ch->coord->y; for (count = 1; count <= numcount; count++) { dir = find_dir(currx, curry, victim->coord->x, victim->coord->y); gox = get_x(currx, dir); goy = get_y(curry, dir); if (sect_show[(int)map_sector[ch->map][gox][goy]].canpass == FALSE || map_sector[ch->map][gox][goy] == SECT_EXIT || is_set_wilderness(ch, ROOM_NO_MOB, gox, goy, ch->map)) { dir = alt_dir(ch, currx, curry, dir); gox = get_x(currx, dir); goy = get_y(curry, dir); } if (count == 1) initdir[times] = dir; currx = gox; curry = goy; if (currx == victim->coord->x && curry == victim->coord->y) break; if (count == numcount) count = 1000; } locount[times] = count; } for (times = 0; times < 5; times++) { if (locount[times] < lo) { lo = locount[times]; lonum = times; } } if (lo >= 1000 || lonum == -1) return -1; else return initdir[lonum]; } int get_distform args((int x, int y, int vx, int vy)); void hunt_victim_map(CHAR_DATA * ch) { bool found; CHAR_DATA *tmp; CHAR_DATA *vch; OBJ_DATA *bow; sh_int ret, chance; sh_int maxx, maxy; sh_int x, y; if (!ch || !ch->hunting) return; x = ch->coord->x; y = ch->coord->y; maxx = MAX_X; maxy = MAX_Y; if (ch->fight_timer > 0) return; /* make sure the char still exists */ for (found = FALSE, tmp = first_char; tmp && !found; tmp = tmp->next) if (ch->hunting->who == tmp) found = TRUE; if (!found) { do_say(ch, "Damn! My prey is gone!!"); stop_hunting(ch); find_next_hunt(ch, 0); return; } if (ch->coord->x == ch->hunting->who->coord->x && ch->coord->y == ch->hunting->who->coord->y && ch->map == ch->hunting->who->map) { if (ch->fighting) return; found_prey(ch, ch->hunting->who); return; } ret = go_find_victim(ch, ch->hunting->who, 0); if (ret < 0) { if (ret == -1) { do_say(ch, "Damn! Lost my prey!"); stop_hunting(ch); find_next_hunt(ch, 0); return; } } else { switch (ret) { case DIR_NORTH: y -= 1; break; case DIR_EAST: x += 1; break; case DIR_SOUTH: y += 1; break; case DIR_WEST: x -= 1; break; case DIR_NORTHEAST: x += 1; y -= 1; break; case DIR_NORTHWEST: x -= 1; y -= 1; break; case DIR_SOUTHEAST: x += 1; y += 1; break; case DIR_SOUTHWEST: x -= 1; y += 1; break; } switch (ret) { case DIR_NORTH: if (y == 0) return; break; case DIR_EAST: if (x == maxx + 1) return; break; case DIR_SOUTH: if (y == maxy + 1) return; break; case DIR_WEST: if (x == 0) return; break; case DIR_NORTHEAST: if (x == maxx + 1 || y == 0) return; break; case DIR_NORTHWEST: if (x == 0 || y == 0) return; break; case DIR_SOUTHEAST: if (x == maxx + 1 || y == maxy + 1) return; break; case DIR_SOUTHWEST: if (x == 0 || y == maxy + 1) return; break; } vch = ch->hunting->who; //Why would an archer chase down a target when it can fire at it? if (xIS_SET(ch->act, ACT_MILITARY) && (bow = get_eq_char(ch, WEAR_MISSILE_WIELD)) != NULL && get_distform(ch->coord->x, ch->coord->y, vch->coord->x, vch->coord->y) <= URANGE(1, bow->value[3], 10) && mob_fire(ch, ch->hunting->who->name, 1)) { ; } else { chance = get_wilderness_hunt_cost(ch, x, y); if (chance <= 1 || number_range(1, chance) == 1) { if (!xIS_SET(ch->act, ACT_MILITARY) || (xIS_SET(ch->act, ACT_MILITARY) && !xIS_SET(ch->miflags, KM_SENTINEL))) { if (IS_AWAKE(ch) && !IS_AFFECTED(ch, AFF_WEB)) { ch->coord->x = x; ch->coord->y = y; update_objects(ch, 0, 0, 0); } } } } if (!ch->hunting) { if (!ch->in_room) { bug("Hunt_victim: no ch->in_room! Mob #%d, name: %s. Placing mob in limbo.", ch->pIndexData->vnum, ch->name); char_to_room(ch, get_room_index(ROOM_VNUM_LIMBO)); return; } do_say(ch, "Damn! Lost my prey!"); return; } if (ch->coord->x == ch->hunting->who->coord->x && ch->coord->y == ch->hunting->who->coord->y && ch->map == ch->hunting->who->map) found_prey(ch, ch->hunting->who); return; } }