| 1 | /* |
| 2 | * movem.c (move monster) Larn is copyrighted 1986 by Noah Morgan. |
| 3 | * $FreeBSD: src/games/larn/movem.c,v 1.4 1999/11/16 02:57:23 billf Exp $ |
| 4 | * |
| 5 | * Here are the functions in this file: |
| 6 | * |
| 7 | * movemonst() Routine to move the monsters toward the player |
| 8 | * movemt(x,y) Function to move a monster at (x,y) -- must determine where |
| 9 | * mmove(x,y,xd,yd) Function to actually perform the monster movement |
| 10 | * movsphere() Function to look for and move spheres of annihilation |
| 11 | */ |
| 12 | #include "header.h" |
| 13 | |
| 14 | static void movemt(int, int); |
| 15 | static void mmove(int, int, int, int); |
| 16 | static void movsphere(void); |
| 17 | |
| 18 | /* |
| 19 | * movemonst() Routine to move the monsters toward the player |
| 20 | * |
| 21 | * This routine has the responsibility to determine which monsters are to |
| 22 | * move, and call movemt() to do the move. |
| 23 | * Returns no value. |
| 24 | */ |
| 25 | static short w1[9], w1x[9], w1y[9]; |
| 26 | static int tmp1, tmp2, tmp3, tmp4, distance; |
| 27 | |
| 28 | void |
| 29 | movemonst(void) |
| 30 | { |
| 31 | int i, j; |
| 32 | if (c[TIMESTOP]) /* no action if time is stopped */ |
| 33 | return; |
| 34 | if (c[HASTESELF]) |
| 35 | if ((c[HASTESELF] & 1) == 0) |
| 36 | return; |
| 37 | if (spheres) /* move the spheres of annihilation if any */ |
| 38 | movsphere(); |
| 39 | if (c[HOLDMONST]) /* no action if monsters are held */ |
| 40 | return; |
| 41 | |
| 42 | if (c[AGGRAVATE]) { /* determine window of monsters to move */ |
| 43 | tmp1 = playery - 5; |
| 44 | tmp2 = playery + 6; |
| 45 | tmp3 = playerx - 10; |
| 46 | tmp4 = playerx + 11; |
| 47 | distance = 40; /* depth of intelligent monster movement */ |
| 48 | } else { |
| 49 | tmp1 = playery - 3; |
| 50 | tmp2 = playery + 4; |
| 51 | tmp3 = playerx - 5; |
| 52 | tmp4 = playerx + 6; |
| 53 | distance = 17; /* depth of intelligent monster movement */ |
| 54 | } |
| 55 | |
| 56 | /* if on outside level monsters can move in perimeter */ |
| 57 | if (level == 0) { |
| 58 | if (tmp1 < 0) |
| 59 | tmp1 = 0; |
| 60 | if (tmp2 > MAXY) |
| 61 | tmp2 = MAXY; |
| 62 | if (tmp3 < 0) |
| 63 | tmp3 = 0; |
| 64 | if (tmp4 > MAXX) |
| 65 | tmp4 = MAXX; |
| 66 | } else { /* if in a dungeon monsters can't be on the perimeter (wall there) */ |
| 67 | if (tmp1 < 1) |
| 68 | tmp1 = 1; |
| 69 | if (tmp2 > MAXY - 1) |
| 70 | tmp2 = MAXY - 1; |
| 71 | if (tmp3 < 1) |
| 72 | tmp3 = 1; |
| 73 | if (tmp4 > MAXX - 1) |
| 74 | tmp4 = MAXX - 1; |
| 75 | } |
| 76 | |
| 77 | for (j = tmp1; j < tmp2; j++) /* now reset monster moved flags */ |
| 78 | for (i = tmp3; i < tmp4; i++) |
| 79 | moved[i][j] = 0; |
| 80 | moved[lasthx][lasthy] = 0; |
| 81 | |
| 82 | /* who gets moved? split for efficiency */ |
| 83 | if (c[AGGRAVATE] || !c[STEALTH]) { |
| 84 | /* look thru all locations in window */ |
| 85 | for (j = tmp1; j < tmp2; j++) |
| 86 | for (i = tmp3; i < tmp4; i++) |
| 87 | /* if there is a monster to move */ |
| 88 | if (mitem[i][j]) |
| 89 | /* if it has not already been moved */ |
| 90 | if (moved[i][j] == 0) |
| 91 | /* go and move the monster */ |
| 92 | movemt(i, j); |
| 93 | } else { /* not aggravated and not stealth */ |
| 94 | /* look thru all locations in window */ |
| 95 | for (j = tmp1; j < tmp2; j++) |
| 96 | for (i = tmp3; i < tmp4; i++) |
| 97 | /* if there is a monster to move */ |
| 98 | if (mitem[i][j]) |
| 99 | /* if it has not already been moved */ |
| 100 | if (moved[i][j] == 0) |
| 101 | /* if it is asleep due to stealth */ |
| 102 | if (stealth[i][j]) |
| 103 | /* go and move the monster */ |
| 104 | movemt(i, j); |
| 105 | } |
| 106 | |
| 107 | /* now move monster last hit by player if not already moved */ |
| 108 | if (mitem[lasthx][lasthy]) { |
| 109 | /* if it has not already been moved */ |
| 110 | if (moved[lasthx][lasthy] == 0) { |
| 111 | movemt(lasthx, lasthy); |
| 112 | lasthx = w1x[0]; |
| 113 | lasthy = w1y[0]; |
| 114 | } |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | /* |
| 119 | * movemt(x,y) Function to move a monster at (x,y) -- must determine where |
| 120 | * int x,y; |
| 121 | * |
| 122 | * This routine is responsible for determining where one monster at (x,y) will |
| 123 | * move to. Enter with the monsters coordinates in (x,y). |
| 124 | * Returns no value. |
| 125 | */ |
| 126 | static int tmpitem, xl, xh, yl, yh; |
| 127 | |
| 128 | static void |
| 129 | movemt(int i, int j) |
| 130 | { |
| 131 | int k, m, z, tmp, xtmp, ytmp, monst; |
| 132 | switch (monst = mitem[i][j]) { /* for half speed monsters */ |
| 133 | case TROGLODYTE: |
| 134 | case HOBGOBLIN: |
| 135 | case METAMORPH: |
| 136 | case XVART: |
| 137 | case INVISIBLESTALKER: |
| 138 | case ICELIZARD: |
| 139 | if ((gtime & 1) == 1) |
| 140 | return; |
| 141 | } |
| 142 | |
| 143 | if (c[SCAREMONST]) { /* choose destination randomly if scared */ |
| 144 | if ((xl = i + rnd(3) - 2) < 0) |
| 145 | xl = 0; |
| 146 | if (xl >= MAXX) |
| 147 | xl = MAXX - 1; |
| 148 | if ((yl = j + rnd(3) - 2) < 0) |
| 149 | yl = 0; |
| 150 | if (yl >= MAXY) |
| 151 | yl = MAXY - 1; |
| 152 | if ((tmp = item[xl][yl]) != OWALL) |
| 153 | if (mitem[xl][yl] == 0) |
| 154 | if ((mitem[i][j] != VAMPIRE) || (tmpitem != OMIRROR)) |
| 155 | if (tmp != OCLOSEDDOOR) |
| 156 | mmove(i, j, xl, yl); |
| 157 | return; |
| 158 | } |
| 159 | |
| 160 | if (monster[monst].intelligence > 10 - c[HARDGAME]) { /* if smart monster */ |
| 161 | /* intelligent movement here -- first setup screen array */ |
| 162 | xl = tmp3 - 2; |
| 163 | yl = tmp1 - 2; |
| 164 | xh = tmp4 + 2; |
| 165 | yh = tmp2 + 2; |
| 166 | vxy(&xl, &yl); |
| 167 | vxy(&xh, &yh); |
| 168 | for (k = yl; k < yh; k++) |
| 169 | for (m = xl; m < xh; m++) { |
| 170 | switch (item[m][k]) { |
| 171 | case OWALL: |
| 172 | case OPIT: |
| 173 | case OTRAPARROW: |
| 174 | case ODARTRAP: |
| 175 | case OCLOSEDDOOR: |
| 176 | case OTRAPDOOR: |
| 177 | case OTELEPORTER: |
| 178 | smm: screen[m][k] = 127; |
| 179 | break; |
| 180 | case OMIRROR: |
| 181 | if (mitem[m][k] == VAMPIRE) |
| 182 | goto smm; |
| 183 | /* FALLTHROUGH */ |
| 184 | default: |
| 185 | screen[m][k] = 0; |
| 186 | break; |
| 187 | } |
| 188 | } |
| 189 | screen[playerx][playery] = 1; |
| 190 | |
| 191 | /* now perform proximity ripple from playerx,playery to monster */ |
| 192 | xl = tmp3 - 1; |
| 193 | yl = tmp1 - 1; |
| 194 | xh = tmp4 + 1; |
| 195 | yh = tmp2 + 1; |
| 196 | vxy(&xl, &yl); |
| 197 | vxy(&xh, &yh); |
| 198 | for (tmp = 1; tmp < distance; tmp++) /* only up to 20 squares away */ |
| 199 | for (k = yl; k < yh; k++) |
| 200 | for (m = xl; m < xh; m++) |
| 201 | /* if find proximity n advance it */ |
| 202 | if (screen[m][k] == tmp) |
| 203 | /* go around in a circle */ |
| 204 | for (z = 1; z < 9; z++) { |
| 205 | if (screen[xtmp = m + diroffx[z]][ytmp = k + diroffy[z]] == 0) |
| 206 | screen[xtmp][ytmp] = tmp + 1; |
| 207 | if (xtmp == i && ytmp == j) |
| 208 | goto out; |
| 209 | } |
| 210 | |
| 211 | out: if (tmp < distance) /* did find connectivity */ |
| 212 | /* now select lowest value around playerx,playery */ |
| 213 | for (z = 1; z < 9; z++) /* go around in a circle */ |
| 214 | if (screen[xl = i + diroffx[z]][yl = j + diroffy[z]] == tmp) |
| 215 | if (!mitem[xl][yl]) { |
| 216 | mmove(i, j, w1x[0] = xl, w1y[0] = yl); |
| 217 | return; |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | /* dumb monsters move here */ |
| 222 | xl = i - 1; |
| 223 | yl = j - 1; |
| 224 | xh = i + 2; |
| 225 | yh = j + 2; |
| 226 | if (i < playerx) |
| 227 | xl++; |
| 228 | else if (i > playerx) |
| 229 | --xh; |
| 230 | if (j < playery) |
| 231 | yl++; |
| 232 | else if (j > playery) |
| 233 | --yh; |
| 234 | for (k = 0; k < 9; k++) |
| 235 | w1[k] = 10000; |
| 236 | |
| 237 | for (k = xl; k < xh; k++) |
| 238 | /* for each square compute distance to player */ |
| 239 | for (m = yl; m < yh; m++) { |
| 240 | tmp = k - i + 4 + 3 * (m - j); |
| 241 | tmpitem = item[k][m]; |
| 242 | if (tmpitem != OWALL || (k == playerx && m == playery)) |
| 243 | if (mitem[k][m] == 0) |
| 244 | if ((mitem[i][j] != VAMPIRE) || (tmpitem != OMIRROR)) |
| 245 | if (tmpitem != OCLOSEDDOOR) { |
| 246 | w1[tmp] = (playerx - k) * (playerx - k) + (playery - m) * (playery - m); |
| 247 | w1x[tmp] = k; |
| 248 | w1y[tmp] = m; |
| 249 | } |
| 250 | } |
| 251 | |
| 252 | tmp = 0; |
| 253 | for (k = 1; k < 9; k++) |
| 254 | if (w1[tmp] > w1[k]) |
| 255 | tmp = k; |
| 256 | |
| 257 | if (w1[tmp] < 10000) |
| 258 | if ((i != w1x[tmp]) || (j != w1y[tmp])) |
| 259 | mmove(i, j, w1x[tmp], w1y[tmp]); |
| 260 | } |
| 261 | |
| 262 | /* |
| 263 | * mmove(x,y,xd,yd) Function to actually perform the monster movement |
| 264 | * int x,y,xd,yd; |
| 265 | * |
| 266 | * Enter with the from coordinates in (x,y) and the destination coordinates |
| 267 | * in (xd,yd). |
| 268 | */ |
| 269 | static void |
| 270 | mmove(int aa, int bb, int cc, int dd) |
| 271 | { |
| 272 | int tmp, i, flag; |
| 273 | const char *who = NULL, *p; |
| 274 | flag = 0; /* set to 1 if monster hit by arrow trap */ |
| 275 | if ((cc == playerx) && (dd == playery)) { |
| 276 | hitplayer(aa, bb); |
| 277 | moved[aa][bb] = 1; |
| 278 | return; |
| 279 | } |
| 280 | i = item[cc][dd]; |
| 281 | if ((i == OPIT) || (i == OTRAPDOOR)) |
| 282 | switch (mitem[aa][bb]) { |
| 283 | case SPIRITNAGA: |
| 284 | case PLATINUMDRAGON: |
| 285 | case WRAITH: |
| 286 | case VAMPIRE: |
| 287 | case SILVERDRAGON: |
| 288 | case POLTERGEIST: |
| 289 | case DEMONLORD: |
| 290 | case DEMONLORD + 1: |
| 291 | case DEMONLORD + 2: |
| 292 | case DEMONLORD + 3: |
| 293 | case DEMONLORD + 4: |
| 294 | case DEMONLORD + 5: |
| 295 | case DEMONLORD + 6: |
| 296 | case DEMONPRINCE: |
| 297 | break; |
| 298 | |
| 299 | default: |
| 300 | mitem[aa][bb] = 0; /* fell in a pit or trapdoor */ |
| 301 | } |
| 302 | tmp = mitem[cc][dd] = mitem[aa][bb]; |
| 303 | if (i == OANNIHILATION) { |
| 304 | if (tmp >= DEMONLORD + 3) { /* demons dispel spheres */ |
| 305 | cursors(); |
| 306 | lprintf("\nThe %s dispels the sphere!", monster[tmp].name); |
| 307 | rmsphere(cc, dd); /* delete the sphere */ |
| 308 | } else |
| 309 | i = tmp = mitem[cc][dd] = 0; |
| 310 | } |
| 311 | stealth[cc][dd] = 1; |
| 312 | if ((hitp[cc][dd] = hitp[aa][bb]) < 0) |
| 313 | hitp[cc][dd] = 1; |
| 314 | mitem[aa][bb] = 0; |
| 315 | moved[cc][dd] = 1; |
| 316 | if (tmp == LEPRECHAUN) |
| 317 | switch (i) { |
| 318 | case OGOLDPILE: |
| 319 | case OMAXGOLD: |
| 320 | case OKGOLD: |
| 321 | case ODGOLD: |
| 322 | case ODIAMOND: |
| 323 | case ORUBY: |
| 324 | case OEMERALD: |
| 325 | case OSAPPHIRE: |
| 326 | item[cc][dd] = 0; /* leprechaun takes gold */ |
| 327 | } |
| 328 | |
| 329 | if (tmp == TROLL) /* if a troll regenerate him */ |
| 330 | if ((gtime & 1) == 0) |
| 331 | if (monster[tmp].hitpoints > hitp[cc][dd]) |
| 332 | hitp[cc][dd]++; |
| 333 | |
| 334 | if (i == OTRAPARROW) { /* arrow hits monster */ |
| 335 | who = "An arrow"; |
| 336 | if ((hitp[cc][dd] -= rnd(10) + level) <= 0) { |
| 337 | mitem[cc][dd] = 0; |
| 338 | flag = 2; |
| 339 | } else |
| 340 | flag = 1; |
| 341 | } |
| 342 | if (i == ODARTRAP) { /* dart hits monster */ |
| 343 | who = "A dart"; |
| 344 | if ((hitp[cc][dd] -= rnd(6)) <= 0) { |
| 345 | mitem[cc][dd] = 0; |
| 346 | flag = 2; |
| 347 | } else |
| 348 | flag = 1; |
| 349 | } |
| 350 | if (i == OTELEPORTER) { /* monster hits teleport trap */ |
| 351 | flag = 3; |
| 352 | fillmonst(mitem[cc][dd]); |
| 353 | mitem[cc][dd] = 0; |
| 354 | } |
| 355 | if (c[BLINDCOUNT]) /* if blind don't show where monsters are */ |
| 356 | return; |
| 357 | if (know[cc][dd] & 1) { |
| 358 | p = NULL; |
| 359 | if (flag) |
| 360 | cursors(); |
| 361 | switch (flag) { |
| 362 | case 1: |
| 363 | p = "\n%s hits the %s"; |
| 364 | break; |
| 365 | case 2: |
| 366 | p = "\n%s hits and kills the %s"; |
| 367 | break; |
| 368 | case 3: |
| 369 | p = "\nThe %s%s gets teleported"; |
| 370 | who = ""; |
| 371 | break; |
| 372 | } |
| 373 | if (p) { |
| 374 | lprintf(p, who, monster[tmp].name); |
| 375 | beep(); |
| 376 | } |
| 377 | } |
| 378 | if (know[aa][bb] & 1) |
| 379 | show1cell(aa, bb); |
| 380 | if (know[cc][dd] & 1) |
| 381 | show1cell(cc, dd); |
| 382 | } |
| 383 | |
| 384 | /* |
| 385 | * movsphere() Function to look for and move spheres of annihilation |
| 386 | * |
| 387 | * This function works on the sphere linked list, first duplicating the list |
| 388 | * (the act of moving changes the list), then processing each sphere in order |
| 389 | * to move it. They eat anything in their way, including stairs, volcanic |
| 390 | * shafts, potions, etc, except for upper level demons, who can dispel |
| 391 | * spheres. |
| 392 | * No value is returned. |
| 393 | */ |
| 394 | #define SPHMAX 20 /* maximum number of spheres movsphere can handle */ |
| 395 | static void |
| 396 | movsphere(void) |
| 397 | { |
| 398 | int x, y, dir, len; |
| 399 | struct sphere *sp, *sp2; |
| 400 | struct sphere sph[SPHMAX]; |
| 401 | |
| 402 | /* first duplicate sphere list */ |
| 403 | /* look through sphere list */ |
| 404 | for (sp = NULL, x = 0, sp2 = spheres; sp2; sp2 = sp2->p) |
| 405 | if (sp2->lev == level) { /* only if this level */ |
| 406 | sph[x] = *sp2; |
| 407 | sph[x++].p = 0; /* copy the struct */ |
| 408 | if (x > 1) |
| 409 | sph[x - 2].p = &sph[x - 1]; /* link pointers */ |
| 410 | } |
| 411 | if (x) /* if any spheres, point to them */ |
| 412 | sp = sph; |
| 413 | else /* no spheres */ |
| 414 | return; |
| 415 | |
| 416 | for (sp = sph; sp; sp = sp->p) { /* look through sphere list */ |
| 417 | x = sp->x; |
| 418 | y = sp->y; |
| 419 | if (item[x][y] != OANNIHILATION) /* not really there */ |
| 420 | continue; |
| 421 | if (--(sp->lifetime) < 0) { /* has sphere run out of gas? */ |
| 422 | rmsphere(x, y); /* delete sphere */ |
| 423 | continue; |
| 424 | } |
| 425 | /* time to move the sphere */ |
| 426 | switch (rnd((int)max(7, c[INTELLIGENCE] >> 1))) { |
| 427 | case 1: |
| 428 | case 2: /* change direction to a random one */ |
| 429 | sp->dir = rnd(8); |
| 430 | /* FALLTHROUGH */ |
| 431 | default: /* move in normal direction */ |
| 432 | dir = sp->dir; |
| 433 | len = sp->lifetime; |
| 434 | rmsphere(x, y); |
| 435 | newsphere(x + diroffx[dir], y + diroffy[dir], dir, len); |
| 436 | } |
| 437 | } |
| 438 | } |