Annotation of brogue-ce/src/brogue/Movement.c, Revision 1.1
1.1 ! rubenllo 1: /*
! 2: * Movement.c
! 3: * Brogue
! 4: *
! 5: * Created by Brian Walker on 1/10/09.
! 6: * Copyright 2012. All rights reserved.
! 7: *
! 8: * This file is part of Brogue.
! 9: *
! 10: * This program is free software: you can redistribute it and/or modify
! 11: * it under the terms of the GNU Affero General Public License as
! 12: * published by the Free Software Foundation, either version 3 of the
! 13: * License, or (at your option) any later version.
! 14: *
! 15: * This program is distributed in the hope that it will be useful,
! 16: * but WITHOUT ANY WARRANTY; without even the implied warranty of
! 17: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
! 18: * GNU Affero General Public License for more details.
! 19: *
! 20: * You should have received a copy of the GNU Affero General Public License
! 21: * along with this program. If not, see <http://www.gnu.org/licenses/>.
! 22: */
! 23:
! 24: #include "Rogue.h"
! 25: #include "IncludeGlobals.h"
! 26:
! 27: void playerRuns(short direction) {
! 28: short newX, newY, dir;
! 29: boolean cardinalPassability[4];
! 30:
! 31: rogue.disturbed = (player.status[STATUS_CONFUSED] ? true : false);
! 32:
! 33: for (dir = 0; dir < 4; dir++) {
! 34: newX = player.xLoc + nbDirs[dir][0];
! 35: newY = player.yLoc + nbDirs[dir][1];
! 36: cardinalPassability[dir] = monsterAvoids(&player, newX, newY);
! 37: }
! 38:
! 39: while (!rogue.disturbed) {
! 40: if (!playerMoves(direction)) {
! 41: rogue.disturbed = true;
! 42: break;
! 43: }
! 44:
! 45: newX = player.xLoc + nbDirs[direction][0];
! 46: newY = player.yLoc + nbDirs[direction][1];
! 47: if (!coordinatesAreInMap(newX, newY)
! 48: || monsterAvoids(&player, newX, newY)) {
! 49:
! 50: rogue.disturbed = true;
! 51: }
! 52: if (isDisturbed(player.xLoc, player.yLoc)) {
! 53: rogue.disturbed = true;
! 54: } else if (direction < 4) {
! 55: for (dir = 0; dir < 4; dir++) {
! 56: newX = player.xLoc + nbDirs[dir][0];
! 57: newY = player.yLoc + nbDirs[dir][1];
! 58: if (cardinalPassability[dir] != monsterAvoids(&player, newX, newY)
! 59: && !(nbDirs[dir][0] + nbDirs[direction][0] == 0 &&
! 60: nbDirs[dir][1] + nbDirs[direction][1] == 0)) {
! 61: // dir is not the x-opposite or y-opposite of direction
! 62: rogue.disturbed = true;
! 63: }
! 64: }
! 65: }
! 66: }
! 67: updateFlavorText();
! 68: }
! 69:
! 70: enum dungeonLayers highestPriorityLayer(short x, short y, boolean skipGas) {
! 71: short bestPriority = 10000;
! 72: enum dungeonLayers tt, best = 0;
! 73:
! 74: for (tt = 0; tt < NUMBER_TERRAIN_LAYERS; tt++) {
! 75: if (tt == GAS && skipGas) {
! 76: continue;
! 77: }
! 78: if (pmap[x][y].layers[tt] && tileCatalog[pmap[x][y].layers[tt]].drawPriority < bestPriority) {
! 79: bestPriority = tileCatalog[pmap[x][y].layers[tt]].drawPriority;
! 80: best = tt;
! 81: }
! 82: }
! 83: return best;
! 84: }
! 85:
! 86: enum dungeonLayers layerWithTMFlag(short x, short y, unsigned long flag) {
! 87: enum dungeonLayers layer;
! 88:
! 89: for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
! 90: if (tileCatalog[pmap[x][y].layers[layer]].mechFlags & flag) {
! 91: return layer;
! 92: }
! 93: }
! 94: return NO_LAYER;
! 95: }
! 96:
! 97: enum dungeonLayers layerWithFlag(short x, short y, unsigned long flag) {
! 98: enum dungeonLayers layer;
! 99:
! 100: for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
! 101: if (tileCatalog[pmap[x][y].layers[layer]].flags & flag) {
! 102: return layer;
! 103: }
! 104: }
! 105: return NO_LAYER;
! 106: }
! 107:
! 108: // Retrieves a pointer to the flavor text of the highest-priority terrain at the given location
! 109: char *tileFlavor(short x, short y) {
! 110: return tileCatalog[pmap[x][y].layers[highestPriorityLayer(x, y, false)]].flavorText;
! 111: }
! 112:
! 113: // Retrieves a pointer to the description text of the highest-priority terrain at the given location
! 114: char *tileText(short x, short y) {
! 115: return tileCatalog[pmap[x][y].layers[highestPriorityLayer(x, y, false)]].description;
! 116: }
! 117:
! 118: void describedItemBasedOnParameters(short theCategory, short theKind, short theQuantity, short theOriginDepth, char *buf) {
! 119: item *tempItem = initializeItem();
! 120: tempItem->category = theCategory;
! 121: tempItem->kind = theKind;
! 122: tempItem->quantity = theQuantity;
! 123: tempItem->originDepth = theOriginDepth;
! 124: itemName(tempItem, buf, false, true, NULL);
! 125: free(tempItem);
! 126: return;
! 127: }
! 128:
! 129: // Describes the item in question either by naming it if the player has already seen its name,
! 130: // or by tersely identifying its category otherwise.
! 131: void describedItemName(item *theItem, char *buf) {
! 132: if (rogue.playbackOmniscience || (!player.status[STATUS_HALLUCINATING])) {
! 133: itemName(theItem, buf, (theItem->category & (WEAPON | ARMOR) ? false : true), true, NULL);
! 134: } else {
! 135: describeHallucinatedItem(buf);
! 136: }
! 137: }
! 138:
! 139: void describeLocation(char *buf, short x, short y) {
! 140: creature *monst;
! 141: item *theItem, *magicItem;
! 142: boolean standsInTerrain;
! 143: boolean subjectMoving;
! 144: boolean prepositionLocked = false;
! 145: boolean monsterDormant;
! 146:
! 147: char subject[COLS * 3];
! 148: char verb[COLS * 3];
! 149: char preposition[COLS * 3];
! 150: char object[COLS * 3];
! 151: char adjective[COLS * 3];
! 152:
! 153: assureCosmeticRNG;
! 154:
! 155: if (x == player.xLoc && y == player.yLoc) {
! 156: if (player.status[STATUS_LEVITATING]) {
! 157: sprintf(buf, "you are hovering above %s.", tileText(x, y));
! 158: } else {
! 159: strcpy(buf, tileFlavor(x, y));
! 160: }
! 161: restoreRNG;
! 162: return;
! 163: }
! 164:
! 165: monst = NULL;
! 166: standsInTerrain = ((tileCatalog[pmap[x][y].layers[highestPriorityLayer(x, y, false)]].mechFlags & TM_STAND_IN_TILE) ? true : false);
! 167: theItem = itemAtLoc(x, y);
! 168: monsterDormant = false;
! 169: if (pmap[x][y].flags & HAS_MONSTER) {
! 170: monst = monsterAtLoc(x, y);
! 171: } else if (pmap[x][y].flags & HAS_DORMANT_MONSTER) {
! 172: monst = dormantMonsterAtLoc(x, y);
! 173: monsterDormant = true;
! 174: }
! 175:
! 176: // detecting magical items
! 177: magicItem = NULL;
! 178: if (theItem && !playerCanSeeOrSense(x, y)
! 179: && (theItem->flags & ITEM_MAGIC_DETECTED)
! 180: && itemMagicPolarity(theItem)) {
! 181: magicItem = theItem;
! 182: } else if (monst && !canSeeMonster(monst)
! 183: && monst->carriedItem
! 184: && (monst->carriedItem->flags & ITEM_MAGIC_DETECTED)
! 185: && itemMagicPolarity(monst->carriedItem)) {
! 186: magicItem = monst->carriedItem;
! 187: }
! 188: if (magicItem && !(pmap[x][y].flags & DISCOVERED)) {
! 189: switch (itemMagicPolarity(magicItem)) {
! 190: case 1:
! 191: strcpy(object, magicItem->category == AMULET ? "the Amulet of Yendor" : "benevolent magic");
! 192: break;
! 193: case -1:
! 194: strcpy(object, "malevolent magic");
! 195: break;
! 196: default:
! 197: strcpy(object, "mysterious magic");
! 198: break;
! 199: }
! 200: sprintf(buf, "you can detect the aura of %s here.", object);
! 201: restoreRNG;
! 202: return;
! 203: }
! 204:
! 205: // telepathy
! 206: if (monst
! 207: && !canSeeMonster(monst)
! 208: && monsterRevealed(monst)) {
! 209:
! 210: strcpy(adjective, (((!player.status[STATUS_HALLUCINATING] || rogue.playbackOmniscience) && !monst->info.isLarge)
! 211: || (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience && rand_range(0, 1)) ? "small" : "large"));
! 212: if (pmap[x][y].flags & DISCOVERED) {
! 213: strcpy(object, tileText(x, y));
! 214: if (monst->bookkeepingFlags & MB_SUBMERGED) {
! 215: strcpy(preposition, "under ");
! 216: } else if (monsterDormant) {
! 217: strcpy(preposition, "coming from within ");
! 218: } else if (standsInTerrain) {
! 219: strcpy(preposition, "in ");
! 220: } else {
! 221: strcpy(preposition, "over ");
! 222: }
! 223: } else {
! 224: strcpy(object, "here");
! 225: strcpy(preposition, "");
! 226: }
! 227:
! 228: sprintf(buf, "you can sense a %s psychic emanation %s%s.", adjective, preposition, object);
! 229: restoreRNG;
! 230: return;
! 231: }
! 232:
! 233: if (monst && !canSeeMonster(monst) && !rogue.playbackOmniscience) {
! 234: // Monster is not visible.
! 235: monst = NULL;
! 236: }
! 237:
! 238: if (!playerCanSeeOrSense(x, y)) {
! 239: if (pmap[x][y].flags & DISCOVERED) { // memory
! 240: if (pmap[x][y].rememberedItemCategory) {
! 241: if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience) {
! 242: describeHallucinatedItem(object);
! 243: } else {
! 244: describedItemBasedOnParameters(pmap[x][y].rememberedItemCategory, pmap[x][y].rememberedItemKind,
! 245: pmap[x][y].rememberedItemQuantity, pmap[x][y].rememberedItemOriginDepth, object);
! 246: }
! 247: } else {
! 248: strcpy(object, tileCatalog[pmap[x][y].rememberedTerrain].description);
! 249: }
! 250: sprintf(buf, "you remember seeing %s here.", object);
! 251: restoreRNG;
! 252: return;
! 253: } else if (pmap[x][y].flags & MAGIC_MAPPED) { // magic mapped
! 254: sprintf(buf, "you expect %s to be here.", tileCatalog[pmap[x][y].rememberedTerrain].description);
! 255: restoreRNG;
! 256: return;
! 257: }
! 258: strcpy(buf, "");
! 259: restoreRNG;
! 260: return;
! 261: }
! 262:
! 263: if (monst) {
! 264:
! 265: monsterName(subject, monst, true);
! 266:
! 267: if (pmap[x][y].layers[GAS] && monst->status[STATUS_INVISIBLE]) { // phantoms in gas
! 268: sprintf(buf, "you can perceive the faint outline of %s in %s.", subject, tileCatalog[pmap[x][y].layers[GAS]].description);
! 269: restoreRNG;
! 270: return;
! 271: }
! 272:
! 273: subjectMoving = (monst->turnsSpentStationary == 0
! 274: && !(monst->info.flags & (MONST_GETS_TURN_ON_ACTIVATION | MONST_IMMOBILE))
! 275: && monst->creatureState != MONSTER_SLEEPING
! 276: && !(monst->bookkeepingFlags & (MB_SEIZED | MB_CAPTIVE)));
! 277: if ((monst->info.flags & MONST_ATTACKABLE_THRU_WALLS)
! 278: && cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)) {
! 279: strcpy(verb, "is embedded");
! 280: } else if (cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)) {
! 281: strcpy(verb, "is trapped");
! 282: subjectMoving = false;
! 283: } else if (monst->bookkeepingFlags & MB_CAPTIVE) {
! 284: strcpy(verb, "is shackled in place");
! 285: subjectMoving = false;
! 286: } else if (monst->status[STATUS_PARALYZED]) {
! 287: strcpy(verb, "is frozen in place");
! 288: subjectMoving = false;
! 289: } else if (monst->status[STATUS_STUCK]) {
! 290: strcpy(verb, "is entangled");
! 291: subjectMoving = false;
! 292: } else if (monst->status[STATUS_LEVITATING]) {
! 293: strcpy(verb, (subjectMoving ? "is flying" : "is hovering"));
! 294: strcpy(preposition, "over");
! 295: prepositionLocked = true;
! 296: } else if (monsterCanSubmergeNow(monst)) {
! 297: strcpy(verb, (subjectMoving ? "is gliding" : "is drifting"));
! 298: } else if (cellHasTerrainFlag(x, y, T_MOVES_ITEMS) && !(monst->info.flags & MONST_SUBMERGES)) {
! 299: strcpy(verb, (subjectMoving ? "is swimming" : "is struggling"));
! 300: } else if (cellHasTerrainFlag(x, y, T_AUTO_DESCENT)) {
! 301: strcpy(verb, "is suspended in mid-air");
! 302: strcpy(preposition, "over");
! 303: prepositionLocked = true;
! 304: subjectMoving = false;
! 305: } else if (monst->status[STATUS_CONFUSED]) {
! 306: strcpy(verb, "is staggering");
! 307: } else if ((monst->info.flags & MONST_RESTRICTED_TO_LIQUID)
! 308: && !cellHasTMFlag(monst->xLoc, monst->yLoc, TM_ALLOWS_SUBMERGING)) {
! 309: strcpy(verb, "is lying");
! 310: subjectMoving = false;
! 311: } else if (monst->info.flags & MONST_IMMOBILE) {
! 312: strcpy(verb, "is resting");
! 313: } else {
! 314: switch (monst->creatureState) {
! 315: case MONSTER_SLEEPING:
! 316: strcpy(verb, "is sleeping");
! 317: subjectMoving = false;
! 318: break;
! 319: case MONSTER_WANDERING:
! 320: strcpy(verb, subjectMoving ? "is wandering" : "is standing");
! 321: break;
! 322: case MONSTER_FLEEING:
! 323: strcpy(verb, subjectMoving ? "is fleeing" : "is standing");
! 324: break;
! 325: case MONSTER_TRACKING_SCENT:
! 326: strcpy(verb, subjectMoving ? "is charging" : "is standing");
! 327: break;
! 328: case MONSTER_ALLY:
! 329: strcpy(verb, subjectMoving ? "is following you" : "is standing");
! 330: break;
! 331: default:
! 332: strcpy(verb, "is standing");
! 333: break;
! 334: }
! 335: }
! 336: if (monst->status[STATUS_BURNING] && !(monst->info.flags & MONST_FIERY)) {
! 337: strcat(verb, ", burning,");
! 338: }
! 339:
! 340: if (theItem) {
! 341: strcpy(preposition, "over");
! 342: describedItemName(theItem, object);
! 343: } else {
! 344: if (!prepositionLocked) {
! 345: strcpy(preposition, subjectMoving ? (standsInTerrain ? "through" : "across")
! 346: : (standsInTerrain ? "in" : "on"));
! 347: }
! 348:
! 349: strcpy(object, tileText(x, y));
! 350:
! 351: }
! 352: } else { // no monster
! 353: strcpy(object, tileText(x, y));
! 354: if (theItem) {
! 355: describedItemName(theItem, subject);
! 356: subjectMoving = cellHasTerrainFlag(x, y, T_MOVES_ITEMS);
! 357: if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience) {
! 358: strcpy(verb, "is");
! 359: } else {
! 360: strcpy(verb, (theItem->quantity > 1 || (theItem->category & GOLD)) ? "are" : "is");
! 361: }
! 362: if (cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)) {
! 363: strcat(verb, " enclosed");
! 364: } else {
! 365: strcat(verb, subjectMoving ? " drifting" : " lying");
! 366: }
! 367: strcpy(preposition, standsInTerrain ? (subjectMoving ? "through" : "in")
! 368: : (subjectMoving ? "across" : "on"));
! 369:
! 370:
! 371: } else { // no item
! 372: sprintf(buf, "you %s %s.", (playerCanDirectlySee(x, y) ? "see" : "sense"), object);
! 373: restoreRNG;
! 374: return;
! 375: }
! 376: }
! 377:
! 378: sprintf(buf, "%s %s %s %s.", subject, verb, preposition, object);
! 379: restoreRNG;
! 380: }
! 381:
! 382: void printLocationDescription(short x, short y) {
! 383: char buf[DCOLS*3];
! 384: describeLocation(buf, x, y);
! 385: flavorMessage(buf);
! 386: }
! 387:
! 388: void useKeyAt(item *theItem, short x, short y) {
! 389: short layer, i;
! 390: creature *monst;
! 391: char buf[COLS], buf2[COLS], terrainName[COLS], preposition[10];
! 392: boolean disposable;
! 393:
! 394: strcpy(terrainName, "unknown terrain"); // redundant failsafe
! 395: for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
! 396: if (tileCatalog[pmap[x][y].layers[layer]].mechFlags & TM_PROMOTES_WITH_KEY) {
! 397: if (tileCatalog[pmap[x][y].layers[layer]].description[0] == 'a'
! 398: && tileCatalog[pmap[x][y].layers[layer]].description[1] == ' ') {
! 399: sprintf(terrainName, "the %s", &(tileCatalog[pmap[x][y].layers[layer]].description[2]));
! 400: } else {
! 401: strcpy(terrainName, tileCatalog[pmap[x][y].layers[layer]].description);
! 402: }
! 403: if (tileCatalog[pmap[x][y].layers[layer]].mechFlags & TM_STAND_IN_TILE) {
! 404: strcpy(preposition, "in");
! 405: } else {
! 406: strcpy(preposition, "on");
! 407: }
! 408: promoteTile(x, y, layer, false);
! 409: }
! 410: }
! 411:
! 412: disposable = false;
! 413: for (i=0; i < KEY_ID_MAXIMUM && (theItem->keyLoc[i].x || theItem->keyLoc[i].machine); i++) {
! 414: if (theItem->keyLoc[i].x == x && theItem->keyLoc[i].y == y && theItem->keyLoc[i].disposableHere) {
! 415: disposable = true;
! 416: } else if (theItem->keyLoc[i].machine == pmap[x][y].machineNumber && theItem->keyLoc[i].disposableHere) {
! 417: disposable = true;
! 418: }
! 419: }
! 420:
! 421: if (disposable) {
! 422: if (removeItemFromChain(theItem, packItems)) {
! 423: itemName(theItem, buf2, true, false, NULL);
! 424: sprintf(buf, "you use your %s %s %s.",
! 425: buf2,
! 426: preposition,
! 427: terrainName);
! 428: messageWithColor(buf, &itemMessageColor, false);
! 429: deleteItem(theItem);
! 430: } else if (removeItemFromChain(theItem, floorItems)) {
! 431: deleteItem(theItem);
! 432: pmap[x][y].flags &= ~HAS_ITEM;
! 433: } else if (pmap[x][y].flags & HAS_MONSTER) {
! 434: monst = monsterAtLoc(x, y);
! 435: if (monst->carriedItem && monst->carriedItem == theItem) {
! 436: monst->carriedItem = NULL;
! 437: deleteItem(theItem);
! 438: }
! 439: }
! 440: }
! 441: }
! 442:
! 443: short randValidDirectionFrom(creature *monst, short x, short y, boolean respectAvoidancePreferences) {
! 444: short i, newX, newY, validDirections[8], count = 0;
! 445:
! 446: brogueAssert(rogue.RNG == RNG_SUBSTANTIVE);
! 447: for (i=0; i<8; i++) {
! 448: newX = x + nbDirs[i][0];
! 449: newY = y + nbDirs[i][1];
! 450: if (coordinatesAreInMap(newX, newY)
! 451: && !cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)
! 452: && !diagonalBlocked(x, y, newX, newY, false)
! 453: && (!respectAvoidancePreferences
! 454: || (!monsterAvoids(monst, newX, newY))
! 455: || ((pmap[newX][newY].flags & HAS_PLAYER) && monst->creatureState != MONSTER_ALLY))) {
! 456: validDirections[count++] = i;
! 457: }
! 458: }
! 459: if (count == 0) {
! 460: // Rare, and important in this case that the function returns BEFORE a random roll is made to avoid OOS.
! 461: return NO_DIRECTION;
! 462: }
! 463: return validDirections[rand_range(0, count - 1)];
! 464: }
! 465:
! 466: void vomit(creature *monst) {
! 467: char buf[COLS], monstName[COLS];
! 468: spawnDungeonFeature(monst->xLoc, monst->yLoc, &dungeonFeatureCatalog[DF_VOMIT], true, false);
! 469:
! 470: if (canDirectlySeeMonster(monst)
! 471: && !rogue.automationActive) {
! 472:
! 473: monsterName(monstName, monst, true);
! 474: sprintf(buf, "%s vomit%s profusely", monstName, (monst == &player ? "" : "s"));
! 475: combatMessage(buf, NULL);
! 476: }
! 477: }
! 478:
! 479: void moveEntrancedMonsters(enum directions dir) {
! 480: creature *monst, *nextMonst;
! 481:
! 482: dir = oppositeDirection(dir);
! 483:
! 484: if (rogue.patchVersion >= 3) {
! 485: for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
! 486: monst->bookkeepingFlags &= ~MB_HAS_ENTRANCED_MOVED;
! 487: }
! 488:
! 489: for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
! 490: if (!(monst->bookkeepingFlags & MB_HAS_ENTRANCED_MOVED)
! 491: && monst->status[STATUS_ENTRANCED]
! 492: && !monst->status[STATUS_STUCK]
! 493: && !monst->status[STATUS_PARALYZED]
! 494: && !(monst->bookkeepingFlags & MB_CAPTIVE)) {
! 495:
! 496: moveMonster(monst, nbDirs[dir][0], nbDirs[dir][1]);
! 497: monst->bookkeepingFlags |= MB_HAS_ENTRANCED_MOVED;
! 498: monst = monsters; // loop through from the beginning to be safe
! 499: }
! 500: }
! 501:
! 502: } else {
! 503: for (monst = monsters->nextCreature; monst != NULL; monst = nextMonst) {
! 504: nextMonst = monst->nextCreature;
! 505: if (monst->status[STATUS_ENTRANCED]
! 506: && !monst->status[STATUS_STUCK]
! 507: && !monst->status[STATUS_PARALYZED]
! 508: && !(monst->bookkeepingFlags & MB_CAPTIVE)) {
! 509:
! 510: moveMonster(monst, nbDirs[dir][0], nbDirs[dir][1]);
! 511: }
! 512: }
! 513: }
! 514:
! 515: }
! 516:
! 517: void becomeAllyWith(creature *monst) {
! 518: demoteMonsterFromLeadership(monst);
! 519: // Drop your item.
! 520: if (monst->carriedItem) {
! 521: makeMonsterDropItem(monst);
! 522: }
! 523: // If you're going to change into something, it should be friendly.
! 524: if (monst->carriedMonster) {
! 525: becomeAllyWith(monst->carriedMonster);
! 526: }
! 527: monst->creatureState = MONSTER_ALLY;
! 528: monst->bookkeepingFlags |= MB_FOLLOWER;
! 529: monst->leader = &player;
! 530: monst->bookkeepingFlags &= ~(MB_CAPTIVE | MB_SEIZED);
! 531: refreshDungeonCell(monst->xLoc, monst->yLoc);
! 532: }
! 533:
! 534: void freeCaptive(creature *monst) {
! 535: char buf[COLS * 3], monstName[COLS];
! 536:
! 537: becomeAllyWith(monst);
! 538: monsterName(monstName, monst, false);
! 539: sprintf(buf, "you free the grateful %s and gain a faithful ally.", monstName);
! 540: message(buf, false);
! 541: }
! 542:
! 543: boolean freeCaptivesEmbeddedAt(short x, short y) {
! 544: creature *monst;
! 545:
! 546: if (pmap[x][y].flags & HAS_MONSTER) {
! 547: // Free any captives trapped in the tunnelized terrain.
! 548: monst = monsterAtLoc(x, y);
! 549: if ((monst->bookkeepingFlags & MB_CAPTIVE)
! 550: && !(monst->info.flags & MONST_ATTACKABLE_THRU_WALLS)
! 551: && (cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY))) {
! 552: freeCaptive(monst);
! 553: return true;
! 554: }
! 555: }
! 556: return false;
! 557: }
! 558:
! 559: // Do we need confirmation so we don't accidently hit an acid mound?
! 560: boolean abortAttackAgainstAcidicTarget(creature *hitList[8]) {
! 561: short i;
! 562: char monstName[COLS], weaponName[COLS];
! 563: char buf[COLS*3];
! 564:
! 565: if (rogue.weapon
! 566: && !(rogue.weapon->flags & ITEM_PROTECTED)
! 567: && !player.status[STATUS_HALLUCINATING]
! 568: && !player.status[STATUS_CONFUSED]) {
! 569:
! 570: for (i=0; i<8; i++) {
! 571: if (hitList[i]
! 572: && (hitList[i]->info.flags & MONST_DEFEND_DEGRADE_WEAPON)
! 573: && canSeeMonster(hitList[i])
! 574: && (!(rogue.weapon->flags & ITEM_RUNIC)
! 575: || !(rogue.weapon->flags & ITEM_RUNIC_IDENTIFIED)
! 576: || rogue.weapon->enchant2 != W_SLAYING
! 577: || !monsterIsInClass(hitList[i], rogue.weapon->vorpalEnemy))) {
! 578:
! 579: monsterName(monstName, hitList[i], true);
! 580: itemName(rogue.weapon, weaponName, false, false, NULL);
! 581: sprintf(buf, "Degrade your %s by attacking %s?", weaponName, monstName);
! 582: if (confirm(buf, false)) {
! 583: return false; // Fire when ready!
! 584: } else {
! 585: return true; // Abort!
! 586: }
! 587: }
! 588: }
! 589: }
! 590: return false;
! 591: }
! 592:
! 593: // Returns true if a whip attack was launched.
! 594: // If "aborted" pointer is provided, sets it to true if it was aborted because
! 595: // the player opted not to attack an acid mound (in which case the whole turn
! 596: // should be aborted), as opposed to there being no valid whip attack available
! 597: // (in which case the player/monster should move instead).
! 598: boolean handleWhipAttacks(creature *attacker, enum directions dir, boolean *aborted) {
! 599: bolt theBolt;
! 600: creature *defender, *hitList[8] = {0};
! 601: short strikeLoc[2], originLoc[2], targetLoc[2];
! 602:
! 603: const char boltChar[DIRECTION_COUNT] = "||~~\\//\\";
! 604:
! 605: brogueAssert(dir > NO_DIRECTION && dir < DIRECTION_COUNT);
! 606:
! 607: if (attacker == &player) {
! 608: if (!rogue.weapon || !(rogue.weapon->flags & ITEM_ATTACKS_EXTEND)) {
! 609: return false;
! 610: }
! 611: } else if (!(attacker->info.abilityFlags & MA_ATTACKS_EXTEND)) {
! 612: return false;
! 613: }
! 614: originLoc[0] = attacker->xLoc;
! 615: originLoc[1] = attacker->yLoc;
! 616: targetLoc[0] = attacker->xLoc + nbDirs[dir][0];
! 617: targetLoc[1] = attacker->yLoc + nbDirs[dir][1];
! 618: getImpactLoc(strikeLoc, originLoc, targetLoc, 5, false);
! 619:
! 620: defender = monsterAtLoc(strikeLoc[0], strikeLoc[1]);
! 621: if (defender
! 622: && (attacker != &player || canSeeMonster(defender))
! 623: && !monsterIsHidden(defender, attacker)
! 624: && monsterWillAttackTarget(attacker, defender)) {
! 625:
! 626: if (attacker == &player) {
! 627: hitList[0] = defender;
! 628: if (abortAttackAgainstAcidicTarget(hitList)) {
! 629: if (aborted) {
! 630: *aborted = true;
! 631: }
! 632: return false;
! 633: }
! 634: }
! 635: attacker->bookkeepingFlags &= ~MB_SUBMERGED;
! 636: theBolt = boltCatalog[BOLT_WHIP];
! 637: theBolt.theChar = boltChar[dir];
! 638: zap(originLoc, targetLoc, &theBolt, false);
! 639: return true;
! 640: }
! 641: return false;
! 642: }
! 643:
! 644: // Returns true if a spear attack was launched.
! 645: // If "aborted" pointer is provided, sets it to true if it was aborted because
! 646: // the player opted not to attack an acid mound (in which case the whole turn
! 647: // should be aborted), as opposed to there being no valid spear attack available
! 648: // (in which case the player/monster should move instead).
! 649: boolean handleSpearAttacks(creature *attacker, enum directions dir, boolean *aborted) {
! 650: creature *defender, *hitList[8] = {0};
! 651: short targetLoc[2], range = 2, i = 0, h = 0;
! 652: boolean proceed = false, visualEffect = false;
! 653:
! 654: const char boltChar[DIRECTION_COUNT] = "||--\\//\\";
! 655:
! 656: brogueAssert(dir > NO_DIRECTION && dir < DIRECTION_COUNT);
! 657:
! 658: if (attacker == &player) {
! 659: if (!rogue.weapon || !(rogue.weapon->flags & ITEM_ATTACKS_PENETRATE)) {
! 660: return false;
! 661: }
! 662: } else if (!(attacker->info.abilityFlags & MA_ATTACKS_PENETRATE)) {
! 663: return false;
! 664: }
! 665:
! 666: for (i = 0; i < range; i++) {
! 667: targetLoc[0] = attacker->xLoc + (1 + i) * nbDirs[dir][0];
! 668: targetLoc[1] = attacker->yLoc + (1 + i) * nbDirs[dir][1];
! 669: if (!coordinatesAreInMap(targetLoc[0], targetLoc[1])) {
! 670: break;
! 671: }
! 672:
! 673: /* Add creatures that we are willing to attack to the potential
! 674: hitlist. Any of those that are either right by us or visible will
! 675: trigger the attack. */
! 676: defender = monsterAtLoc(targetLoc[0], targetLoc[1]);
! 677: if (defender
! 678: && (!cellHasTerrainFlag(targetLoc[0], targetLoc[1], T_OBSTRUCTS_PASSABILITY)
! 679: || (defender->info.flags & MONST_ATTACKABLE_THRU_WALLS))
! 680: && monsterWillAttackTarget(attacker, defender)) {
! 681:
! 682: hitList[h++] = defender;
! 683:
! 684: /* We check if i=0, i.e. the defender is right next to us, because
! 685: we have to do "normal" attacking here. We can't just return
! 686: false and leave to playerMoves/moveMonster due to the collateral hitlist. */
! 687: if (i == 0 || !monsterIsHidden(defender, attacker)
! 688: && (attacker != &player || canSeeMonster(defender))) {
! 689: // We'll attack.
! 690: proceed = true;
! 691: }
! 692: }
! 693:
! 694: if (cellHasTerrainFlag(targetLoc[0], targetLoc[1], (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_VISION))) {
! 695: break;
! 696: }
! 697: }
! 698: range = i;
! 699: if (proceed) {
! 700: if (attacker == &player) {
! 701: if (abortAttackAgainstAcidicTarget(hitList)) {
! 702: if (aborted) {
! 703: *aborted = true;
! 704: }
! 705: return false;
! 706: }
! 707: }
! 708: if (!rogue.playbackFastForward) {
! 709: for (i = 0; i < range; i++) {
! 710: targetLoc[0] = attacker->xLoc + (1 + i) * nbDirs[dir][0];
! 711: targetLoc[1] = attacker->yLoc + (1 + i) * nbDirs[dir][1];
! 712: if (coordinatesAreInMap(targetLoc[0], targetLoc[1])
! 713: && playerCanSeeOrSense(targetLoc[0], targetLoc[1])) {
! 714:
! 715: visualEffect = true;
! 716: plotForegroundChar(boltChar[dir], targetLoc[0], targetLoc[1], &lightBlue, true);
! 717: }
! 718: }
! 719: }
! 720: attacker->bookkeepingFlags &= ~MB_SUBMERGED;
! 721: // Artificially reverse the order of the attacks,
! 722: // so that spears of force can send both monsters flying.
! 723: for (i = h - 1; i >= 0; i--) {
! 724: attack(attacker, hitList[i], false);
! 725: }
! 726: if (visualEffect) {
! 727: pauseBrogue(16);
! 728: for (i = 0; i < range; i++) {
! 729: targetLoc[0] = attacker->xLoc + (1 + i) * nbDirs[dir][0];
! 730: targetLoc[1] = attacker->yLoc + (1 + i) * nbDirs[dir][1];
! 731: if (coordinatesAreInMap(targetLoc[0], targetLoc[1])) {
! 732: refreshDungeonCell(targetLoc[0], targetLoc[1]);
! 733: }
! 734: }
! 735: }
! 736: return true;
! 737: }
! 738: return false;
! 739: }
! 740:
! 741: void buildFlailHitList(const short x, const short y, const short newX, const short newY, creature *hitList[16]) {
! 742: creature *monst;
! 743: short mx, my;
! 744: short i = 0;
! 745:
! 746: for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
! 747: mx = monst->xLoc;
! 748: my = monst->yLoc;
! 749: if (distanceBetween(x, y, mx, my) == 1
! 750: && distanceBetween(newX, newY, mx, my) == 1
! 751: && canSeeMonster(monst)
! 752: && monstersAreEnemies(&player, monst)
! 753: && monst->creatureState != MONSTER_ALLY
! 754: && !(monst->bookkeepingFlags & MB_IS_DYING)
! 755: && (!cellHasTerrainFlag(monst->xLoc, monst->yLoc, T_OBSTRUCTS_PASSABILITY) || (monst->info.flags & MONST_ATTACKABLE_THRU_WALLS))) {
! 756:
! 757: while (hitList[i]) {
! 758: i++;
! 759: }
! 760: hitList[i] = monst;
! 761: }
! 762: }
! 763: }
! 764:
! 765: boolean diagonalBlocked(const short x1, const short y1, const short x2, const short y2, const boolean limitToPlayerKnowledge) {
! 766: unsigned long tFlags;
! 767: if (x1 == x2 || y1 == y2) {
! 768: return false; // If it's not a diagonal, it's not diagonally blocked.
! 769: }
! 770: getLocationFlags(x1, y2, &tFlags, NULL, NULL, limitToPlayerKnowledge);
! 771: if (tFlags & T_OBSTRUCTS_DIAGONAL_MOVEMENT) {
! 772: return true;
! 773: }
! 774: getLocationFlags(x2, y1, &tFlags, NULL, NULL, limitToPlayerKnowledge);
! 775: if (tFlags & T_OBSTRUCTS_DIAGONAL_MOVEMENT) {
! 776: return true;
! 777: }
! 778: return false;
! 779: }
! 780:
! 781: // Called whenever the player voluntarily tries to move in a given direction.
! 782: // Can be called from movement keys, exploration, or auto-travel.
! 783: boolean playerMoves(short direction) {
! 784: short initialDirection = direction, i, layer;
! 785: short x = player.xLoc, y = player.yLoc;
! 786: short newX, newY, newestX, newestY;
! 787: boolean playerMoved = false, alreadyRecorded = false, specialAttackAborted = false, anyAttackHit = false;
! 788: creature *defender = NULL, *tempMonst = NULL, *hitList[16] = {NULL};
! 789: char monstName[COLS];
! 790: char buf[COLS*3];
! 791: const int directionKeys[8] = {UP_KEY, DOWN_KEY, LEFT_KEY, RIGHT_KEY, UPLEFT_KEY, DOWNLEFT_KEY, UPRIGHT_KEY, DOWNRIGHT_KEY};
! 792:
! 793: brogueAssert(direction >= 0 && direction < DIRECTION_COUNT);
! 794:
! 795: newX = x + nbDirs[direction][0];
! 796: newY = y + nbDirs[direction][1];
! 797:
! 798: if (!coordinatesAreInMap(newX, newY)) {
! 799: return false;
! 800: }
! 801:
! 802: if (player.status[STATUS_CONFUSED]) {
! 803: // Confirmation dialog if you're moving while confused and you're next to lava and not levitating or immune to fire.
! 804: if (player.status[STATUS_LEVITATING] <= 1
! 805: && player.status[STATUS_IMMUNE_TO_FIRE] <= 1) {
! 806:
! 807: for (i=0; i<8; i++) {
! 808: newestX = x + nbDirs[i][0];
! 809: newestY = y + nbDirs[i][1];
! 810: if (coordinatesAreInMap(newestX, newestY)
! 811: && (pmap[newestX][newestY].flags & (DISCOVERED | MAGIC_MAPPED))
! 812: && !diagonalBlocked(x, y, newestX, newestY, false)
! 813: && cellHasTerrainFlag(newestX, newestY, T_LAVA_INSTA_DEATH)
! 814: && !cellHasTerrainFlag(newestX, newestY, T_OBSTRUCTS_PASSABILITY | T_ENTANGLES)
! 815: && !((pmap[newestX][newestY].flags & HAS_MONSTER)
! 816: && canSeeMonster(monsterAtLoc(newestX, newestY))
! 817: && monsterAtLoc(newestX, newestY)->creatureState != MONSTER_ALLY)) {
! 818:
! 819: if (!confirm("Risk stumbling into lava?", false)) {
! 820: return false;
! 821: } else {
! 822: break;
! 823: }
! 824: }
! 825: }
! 826: }
! 827:
! 828: direction = randValidDirectionFrom(&player, x, y, false);
! 829: if (direction == -1) {
! 830: return false;
! 831: } else {
! 832: newX = x + nbDirs[direction][0];
! 833: newY = y + nbDirs[direction][1];
! 834: if (!coordinatesAreInMap(newX, newY)) {
! 835: return false;
! 836: }
! 837: if (!alreadyRecorded) {
! 838: recordKeystroke(directionKeys[initialDirection], false, false);
! 839: alreadyRecorded = true;
! 840: }
! 841: }
! 842: }
! 843:
! 844: if (pmap[newX][newY].flags & HAS_MONSTER) {
! 845: defender = monsterAtLoc(newX, newY);
! 846: }
! 847:
! 848: // If there's no enemy at the movement location that the player is aware of, consider terrain promotions.
! 849: if (!defender
! 850: || (!canSeeMonster(defender) && !monsterRevealed(defender))
! 851: || !monstersAreEnemies(&player, defender)) {
! 852:
! 853: if (cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY) && cellHasTMFlag(newX, newY, TM_PROMOTES_ON_PLAYER_ENTRY)) {
! 854: layer = layerWithTMFlag(newX, newY, TM_PROMOTES_ON_PLAYER_ENTRY);
! 855: if (tileCatalog[pmap[newX][newY].layers[layer]].flags & T_OBSTRUCTS_PASSABILITY) {
! 856: if (!alreadyRecorded) {
! 857: recordKeystroke(directionKeys[initialDirection], false, false);
! 858: alreadyRecorded = true;
! 859: }
! 860: message(tileCatalog[pmap[newX][newY].layers[layer]].flavorText, false);
! 861: promoteTile(newX, newY, layer, false);
! 862: playerTurnEnded();
! 863: return true;
! 864: }
! 865: }
! 866:
! 867: if (rogue.patchVersion < 1 && player.status[STATUS_STUCK] && cellHasTerrainFlag(x, y, T_ENTANGLES)) {
! 868: // Don't interrupt exploration with this message.
! 869: if (--player.status[STATUS_STUCK]) {
! 870: if (!rogue.automationActive) {
! 871: message("you struggle but cannot free yourself.", false);
! 872: }
! 873: } else {
! 874: if (!rogue.automationActive) {
! 875: message("you break free!", false);
! 876: }
! 877: if (tileCatalog[pmap[x][y].layers[SURFACE]].flags & T_ENTANGLES) {
! 878: pmap[x][y].layers[SURFACE] = NOTHING;
! 879: }
! 880: }
! 881: moveEntrancedMonsters(direction);
! 882: if (!alreadyRecorded) {
! 883: recordKeystroke(directionKeys[initialDirection], false, false);
! 884: alreadyRecorded = true;
! 885: }
! 886: if (player.status[STATUS_STUCK]) {
! 887: playerTurnEnded();
! 888: return true;
! 889: }
! 890: }
! 891: }
! 892:
! 893: if (((!cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY) || (cellHasTMFlag(newX, newY, TM_PROMOTES_WITH_KEY) && keyInPackFor(newX, newY)))
! 894: && !diagonalBlocked(x, y, newX, newY, false)
! 895: && (!cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY) || (cellHasTMFlag(x, y, TM_PROMOTES_WITH_KEY) && keyInPackFor(x, y))))
! 896: || (defender && defender->info.flags & MONST_ATTACKABLE_THRU_WALLS)) {
! 897: // if the move is not blocked
! 898:
! 899: if (handleWhipAttacks(&player, direction, &specialAttackAborted)
! 900: || handleSpearAttacks(&player, direction, &specialAttackAborted)) {
! 901:
! 902: if (!alreadyRecorded) {
! 903: recordKeystroke(directionKeys[initialDirection], false, false);
! 904: alreadyRecorded = true;
! 905: }
! 906: playerRecoversFromAttacking(true);
! 907: moveEntrancedMonsters(direction);
! 908: playerTurnEnded();
! 909: return true;
! 910: } else if (specialAttackAborted) { // Canceled an attack against an acid mound.
! 911: brogueAssert(!alreadyRecorded);
! 912: rogue.disturbed = true;
! 913: return false;
! 914: }
! 915:
! 916: if (defender) {
! 917: // if there is a monster there
! 918:
! 919: if (defender->bookkeepingFlags & MB_CAPTIVE) {
! 920: monsterName(monstName, defender, false);
! 921: sprintf(buf, "Free the captive %s?", monstName);
! 922: if (alreadyRecorded || confirm(buf, false)) {
! 923: if (!alreadyRecorded) {
! 924: recordKeystroke(directionKeys[initialDirection], false, false);
! 925: alreadyRecorded = true;
! 926: }
! 927: if (cellHasTMFlag(newX, newY, TM_PROMOTES_WITH_KEY) && keyInPackFor(newX, newY)) {
! 928: useKeyAt(keyInPackFor(newX, newY), newX, newY);
! 929: }
! 930: freeCaptive(defender);
! 931: player.ticksUntilTurn += player.attackSpeed;
! 932: playerTurnEnded();
! 933: return true;
! 934: } else {
! 935: return false;
! 936: }
! 937: }
! 938:
! 939: if (defender->creatureState != MONSTER_ALLY) {
! 940: // Make a hit list of monsters the player is attacking this turn.
! 941: // We separate this tallying phase from the actual attacking phase because sometimes the attacks themselves
! 942: // create more monsters, and those shouldn't be attacked in the same turn.
! 943:
! 944: buildHitList(hitList, &player, defender,
! 945: rogue.weapon && (rogue.weapon->flags & ITEM_ATTACKS_ALL_ADJACENT));
! 946:
! 947: if (abortAttackAgainstAcidicTarget(hitList)) { // Acid mound attack confirmation.
! 948: brogueAssert(!alreadyRecorded);
! 949: rogue.disturbed = true;
! 950: return false;
! 951: }
! 952:
! 953: if (player.status[STATUS_NAUSEOUS]) {
! 954: if (!alreadyRecorded) {
! 955: recordKeystroke(directionKeys[initialDirection], false, false);
! 956: alreadyRecorded = true;
! 957: }
! 958: if (rand_percent(25)) {
! 959: vomit(&player);
! 960: playerTurnEnded();
! 961: return false;
! 962: }
! 963: }
! 964:
! 965: // Proceeding with the attack.
! 966:
! 967: if (!alreadyRecorded) {
! 968: recordKeystroke(directionKeys[initialDirection], false, false);
! 969: alreadyRecorded = true;
! 970: }
! 971:
! 972: // Attack!
! 973: for (i=0; i<16; i++) {
! 974: if (hitList[i]
! 975: && monsterWillAttackTarget(&player, hitList[i])
! 976: && !(hitList[i]->bookkeepingFlags & MB_IS_DYING)
! 977: && !rogue.gameHasEnded) {
! 978:
! 979: if (attack(&player, hitList[i], false)) {
! 980: anyAttackHit = true;
! 981: }
! 982: }
! 983: }
! 984:
! 985: playerRecoversFromAttacking(anyAttackHit);
! 986: moveEntrancedMonsters(direction);
! 987: playerTurnEnded();
! 988: return true;
! 989: }
! 990: }
! 991:
! 992: if (player.bookkeepingFlags & MB_SEIZED) {
! 993: for (tempMonst = monsters->nextCreature; tempMonst != NULL; tempMonst = tempMonst->nextCreature) {
! 994: if ((tempMonst->bookkeepingFlags & MB_SEIZING)
! 995: && monstersAreEnemies(&player, tempMonst)
! 996: && distanceBetween(player.xLoc, player.yLoc, tempMonst->xLoc, tempMonst->yLoc) == 1
! 997: && !diagonalBlocked(player.xLoc, player.yLoc, tempMonst->xLoc, tempMonst->yLoc, false)
! 998: && !tempMonst->status[STATUS_ENTRANCED]) {
! 999:
! 1000: monsterName(monstName, tempMonst, true);
! 1001: if (alreadyRecorded || !canSeeMonster(tempMonst)) {
! 1002: if (!alreadyRecorded) {
! 1003: recordKeystroke(directionKeys[initialDirection], false, false);
! 1004: alreadyRecorded = true;
! 1005: }
! 1006: sprintf(buf, "you struggle but %s is holding your legs!", monstName);
! 1007: moveEntrancedMonsters(direction);
! 1008: message(buf, false);
! 1009: playerTurnEnded();
! 1010: return true;
! 1011: } else {
! 1012: sprintf(buf, "you cannot move; %s is holding your legs!", monstName);
! 1013: message(buf, false);
! 1014: return false;
! 1015: }
! 1016: }
! 1017: }
! 1018: player.bookkeepingFlags &= ~MB_SEIZED; // failsafe
! 1019: }
! 1020:
! 1021: if (pmap[newX][newY].flags & (DISCOVERED | MAGIC_MAPPED)
! 1022: && player.status[STATUS_LEVITATING] <= 1
! 1023: && !player.status[STATUS_CONFUSED]
! 1024: && cellHasTerrainFlag(newX, newY, T_LAVA_INSTA_DEATH)
! 1025: && player.status[STATUS_IMMUNE_TO_FIRE] <= 1
! 1026: && !cellHasTerrainFlag(newX, newY, T_ENTANGLES)
! 1027: && !cellHasTMFlag(newX, newY, TM_IS_SECRET)) {
! 1028: message("that would be certain death!", false);
! 1029: return false; // player won't willingly step into lava
! 1030: } else if (pmap[newX][newY].flags & (DISCOVERED | MAGIC_MAPPED)
! 1031: && player.status[STATUS_LEVITATING] <= 1
! 1032: && !player.status[STATUS_CONFUSED]
! 1033: && cellHasTerrainFlag(newX, newY, T_AUTO_DESCENT)
! 1034: && !cellHasTerrainFlag(newX, newY, T_ENTANGLES)
! 1035: && !cellHasTMFlag(newX, newY, TM_IS_SECRET)
! 1036: && !confirm("Dive into the depths?", false)) {
! 1037: return false;
! 1038: } else if (playerCanSee(newX, newY)
! 1039: && !player.status[STATUS_CONFUSED]
! 1040: && !player.status[STATUS_BURNING]
! 1041: && player.status[STATUS_IMMUNE_TO_FIRE] <= 1
! 1042: && cellHasTerrainFlag(newX, newY, T_IS_FIRE)
! 1043: && !cellHasTMFlag(newX, newY, TM_EXTINGUISHES_FIRE)
! 1044: && !confirm("Venture into flame?", false)) {
! 1045: return false;
! 1046: } else if (playerCanSee(newX, newY)
! 1047: && !player.status[STATUS_CONFUSED]
! 1048: && !player.status[STATUS_BURNING]
! 1049: && cellHasTerrainFlag(newX, newY, T_CAUSES_CONFUSION | T_CAUSES_PARALYSIS)
! 1050: && (!rogue.armor || !(rogue.armor->flags & ITEM_RUNIC) || !(rogue.armor->flags & ITEM_RUNIC_IDENTIFIED) || rogue.armor->enchant2 != A_RESPIRATION)
! 1051: && !confirm("Venture into dangerous gas?", false)) {
! 1052: return false;
! 1053: } else if (pmap[newX][newY].flags & (ANY_KIND_OF_VISIBLE | MAGIC_MAPPED)
! 1054: && player.status[STATUS_LEVITATING] <= 1
! 1055: && !player.status[STATUS_CONFUSED]
! 1056: && cellHasTerrainFlag(newX, newY, T_IS_DF_TRAP)
! 1057: && !(pmap[newX][newY].flags & PRESSURE_PLATE_DEPRESSED)
! 1058: && !cellHasTMFlag(newX, newY, TM_IS_SECRET)
! 1059: && !confirm("Step onto the pressure plate?", false)) {
! 1060: return false;
! 1061: }
! 1062:
! 1063: if (rogue.weapon && (rogue.weapon->flags & ITEM_LUNGE_ATTACKS)) {
! 1064: newestX = player.xLoc + 2*nbDirs[direction][0];
! 1065: newestY = player.yLoc + 2*nbDirs[direction][1];
! 1066: if (coordinatesAreInMap(newestX, newestY) && (pmap[newestX][newestY].flags & HAS_MONSTER)) {
! 1067: tempMonst = monsterAtLoc(newestX, newestY);
! 1068: if (tempMonst
! 1069: && canSeeMonster(tempMonst)
! 1070: && monstersAreEnemies(&player, tempMonst)
! 1071: && tempMonst->creatureState != MONSTER_ALLY
! 1072: && !(tempMonst->bookkeepingFlags & MB_IS_DYING)
! 1073: && (!cellHasTerrainFlag(tempMonst->xLoc, tempMonst->yLoc, T_OBSTRUCTS_PASSABILITY) || (tempMonst->info.flags & MONST_ATTACKABLE_THRU_WALLS))) {
! 1074:
! 1075: hitList[0] = tempMonst;
! 1076: if (abortAttackAgainstAcidicTarget(hitList)) { // Acid mound attack confirmation.
! 1077: brogueAssert(!alreadyRecorded);
! 1078: rogue.disturbed = true;
! 1079: return false;
! 1080: }
! 1081: }
! 1082: }
! 1083: }
! 1084: if (rogue.weapon && (rogue.weapon->flags & ITEM_PASS_ATTACKS)) {
! 1085: buildFlailHitList(x, y, newX, newY, hitList);
! 1086: if (abortAttackAgainstAcidicTarget(hitList)) { // Acid mound attack confirmation.
! 1087: brogueAssert(!alreadyRecorded);
! 1088: rogue.disturbed = true;
! 1089: return false;
! 1090: }
! 1091: }
! 1092:
! 1093: if (rogue.patchVersion >= 1 && player.status[STATUS_STUCK] && cellHasTerrainFlag(x, y, T_ENTANGLES)) {
! 1094: // Don't interrupt exploration with this message.
! 1095: if (--player.status[STATUS_STUCK]) {
! 1096: if (!rogue.automationActive) {
! 1097: message("you struggle but cannot free yourself.", false);
! 1098: }
! 1099: moveEntrancedMonsters(direction);
! 1100: if (!alreadyRecorded) {
! 1101: recordKeystroke(directionKeys[initialDirection], false, false);
! 1102: alreadyRecorded = true;
! 1103: }
! 1104: playerTurnEnded();
! 1105: return true;
! 1106: } else {
! 1107: if (!rogue.automationActive) {
! 1108: message("you break free!", false);
! 1109: }
! 1110: if (tileCatalog[pmap[x][y].layers[SURFACE]].flags & T_ENTANGLES) {
! 1111: pmap[x][y].layers[SURFACE] = NOTHING;
! 1112: }
! 1113: }
! 1114: }
! 1115:
! 1116: if (player.status[STATUS_NAUSEOUS]) {
! 1117: if (!alreadyRecorded) {
! 1118: recordKeystroke(directionKeys[initialDirection], false, false);
! 1119: alreadyRecorded = true;
! 1120: }
! 1121: if (rand_percent(25)) {
! 1122: vomit(&player);
! 1123: playerTurnEnded();
! 1124: return true;
! 1125: }
! 1126: }
! 1127:
! 1128: // Are we taking the stairs?
! 1129: if (rogue.downLoc[0] == newX && rogue.downLoc[1] == newY) {
! 1130: if (!alreadyRecorded) {
! 1131: recordKeystroke(directionKeys[initialDirection], false, false);
! 1132: alreadyRecorded = true;
! 1133: }
! 1134: useStairs(1);
! 1135: } else if (rogue.upLoc[0] == newX && rogue.upLoc[1] == newY) {
! 1136: if (!alreadyRecorded) {
! 1137: recordKeystroke(directionKeys[initialDirection], false, false);
! 1138: alreadyRecorded = true;
! 1139: }
! 1140: useStairs(-1);
! 1141: } else {
! 1142: // Okay, we're finally moving!
! 1143: if (!alreadyRecorded) {
! 1144: recordKeystroke(directionKeys[initialDirection], false, false);
! 1145: alreadyRecorded = true;
! 1146: }
! 1147:
! 1148: player.xLoc += nbDirs[direction][0];
! 1149: player.yLoc += nbDirs[direction][1];
! 1150: pmap[x][y].flags &= ~HAS_PLAYER;
! 1151: pmap[player.xLoc][player.yLoc].flags |= HAS_PLAYER;
! 1152: pmap[player.xLoc][player.yLoc].flags &= ~IS_IN_PATH;
! 1153: if (defender && defender->creatureState == MONSTER_ALLY) { // Swap places with ally.
! 1154: pmap[defender->xLoc][defender->yLoc].flags &= ~HAS_MONSTER;
! 1155: defender->xLoc = x;
! 1156: defender->yLoc = y;
! 1157: if (monsterAvoids(defender, x, y)) {
! 1158: getQualifyingPathLocNear(&(defender->xLoc), &(defender->yLoc), player.xLoc, player.yLoc, true, forbiddenFlagsForMonster(&(defender->info)), 0, 0, (HAS_PLAYER | HAS_MONSTER | HAS_STAIRS), false);
! 1159: }
! 1160: //getQualifyingLocNear(loc, player.xLoc, player.yLoc, true, NULL, forbiddenFlagsForMonster(&(defender->info)) & ~(T_IS_DF_TRAP | T_IS_DEEP_WATER | T_SPONTANEOUSLY_IGNITES), HAS_MONSTER, false, false);
! 1161: //defender->xLoc = loc[0];
! 1162: //defender->yLoc = loc[1];
! 1163: pmap[defender->xLoc][defender->yLoc].flags |= HAS_MONSTER;
! 1164: }
! 1165:
! 1166: if (pmap[player.xLoc][player.yLoc].flags & HAS_ITEM) {
! 1167: pickUpItemAt(player.xLoc, player.yLoc);
! 1168: rogue.disturbed = true;
! 1169: }
! 1170: refreshDungeonCell(x, y);
! 1171: refreshDungeonCell(player.xLoc, player.yLoc);
! 1172: playerMoved = true;
! 1173:
! 1174: checkForMissingKeys(x, y);
! 1175: if (monsterShouldFall(&player)) {
! 1176: player.bookkeepingFlags |= MB_IS_FALLING;
! 1177: }
! 1178: moveEntrancedMonsters(direction);
! 1179:
! 1180: // Perform a lunge or flail attack if appropriate.
! 1181: for (i=0; i<16; i++) {
! 1182: if (hitList[i]) {
! 1183: if (attack(&player, hitList[i], (rogue.weapon && (rogue.weapon->flags & ITEM_LUNGE_ATTACKS)))) {
! 1184: anyAttackHit = true;
! 1185: }
! 1186: }
! 1187: }
! 1188: if (hitList[0]) {
! 1189: playerRecoversFromAttacking(anyAttackHit);
! 1190: }
! 1191:
! 1192: playerTurnEnded();
! 1193: }
! 1194: } else if (cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)) {
! 1195: i = pmap[newX][newY].layers[layerWithFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)];
! 1196: if ((tileCatalog[i].flags & T_OBSTRUCTS_PASSABILITY)
! 1197: && (!diagonalBlocked(x, y, newX, newY, false) || !cellHasTMFlag(newX, newY, TM_PROMOTES_WITH_KEY))) {
! 1198:
! 1199: if (!(pmap[newX][newY].flags & DISCOVERED)) {
! 1200: if (!alreadyRecorded) {
! 1201: recordKeystroke(directionKeys[initialDirection], false, false);
! 1202: alreadyRecorded = true;
! 1203: }
! 1204: discoverCell(newX, newY);
! 1205: refreshDungeonCell(newX, newY);
! 1206: }
! 1207:
! 1208: messageWithColor(tileCatalog[i].flavorText, &backgroundMessageColor, false);
! 1209: }
! 1210: }
! 1211: return playerMoved;
! 1212: }
! 1213:
! 1214: // replaced in Dijkstra.c:
! 1215: /*
! 1216: // returns true if the cell value changed
! 1217: boolean updateDistanceCell(short **distanceMap, short x, short y) {
! 1218: short dir, newX, newY;
! 1219: boolean somethingChanged = false;
! 1220:
! 1221: if (distanceMap[x][y] >= 0 && distanceMap[x][y] < 30000) {
! 1222: for (dir=0; dir< DIRECTION_COUNT; dir++) {
! 1223: newX = x + nbDirs[dir][0];
! 1224: newY = y + nbDirs[dir][1];
! 1225: if (coordinatesAreInMap(newX, newY)
! 1226: && distanceMap[newX][newY] >= distanceMap[x][y] + 2
! 1227: && !diagonalBlocked(x, y, newX, newY)) {
! 1228: distanceMap[newX][newY] = distanceMap[x][y] + 1;
! 1229: somethingChanged = true;
! 1230: }
! 1231: }
! 1232: }
! 1233: return somethingChanged;
! 1234: }
! 1235:
! 1236: void dijkstraScan(short **distanceMap, char passMap[DCOLS][DROWS], boolean allowDiagonals) {
! 1237: short i, j, maxDir;
! 1238: enum directions dir;
! 1239: boolean somethingChanged;
! 1240:
! 1241: maxDir = (allowDiagonals ? 8 : 4);
! 1242:
! 1243: do {
! 1244: somethingChanged = false;
! 1245: for (i=1; i<DCOLS-1; i++) {
! 1246: for (j=1; j<DROWS-1; j++) {
! 1247: if (!passMap || passMap[i][j]) {
! 1248: for (dir = 0; dir < maxDir; dir++) {
! 1249: if (coordinatesAreInMap(i + nbDirs[dir][0], j + nbDirs[dir][1])
! 1250: && (!passMap || passMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]])
! 1251: && distanceMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]] >= distanceMap[i][j] + 2) {
! 1252: distanceMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]] = distanceMap[i][j] + 1;
! 1253: somethingChanged = true;
! 1254: }
! 1255: }
! 1256: }
! 1257: }
! 1258: }
! 1259:
! 1260:
! 1261: for (i = DCOLS - 1; i >= 0; i--) {
! 1262: for (j = DROWS - 1; j >= 0; j--) {
! 1263: if (!passMap || passMap[i][j]) {
! 1264: for (dir = 0; dir < maxDir; dir++) {
! 1265: if (coordinatesAreInMap(i + nbDirs[dir][0], j + nbDirs[dir][1])
! 1266: && (!passMap || passMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]])
! 1267: && distanceMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]] >= distanceMap[i][j] + 2) {
! 1268: distanceMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]] = distanceMap[i][j] + 1;
! 1269: somethingChanged = true;
! 1270: }
! 1271: }
! 1272: }
! 1273: }
! 1274: }
! 1275: } while (somethingChanged);
! 1276: }*/
! 1277:
! 1278: /*void enqueue(short x, short y, short val, distanceQueue *dQ) {
! 1279: short *qX2, *qY2, *qVal2;
! 1280:
! 1281: // if we need to allocate more memory:
! 1282: if (dQ->qLen + 1 > dQ->qMaxLen) {
! 1283: dQ->qMaxLen *= 2;
! 1284: qX2 = realloc(dQ->qX, dQ->qMaxLen);
! 1285: if (qX2) {
! 1286: free(dQ->qX);
! 1287: dQ->qX = qX2;
! 1288: } else {
! 1289: // out of memory
! 1290: }
! 1291: qY2 = realloc(dQ->qY, dQ->qMaxLen);
! 1292: if (qY2) {
! 1293: free(dQ->qY);
! 1294: dQ->qY = qY2;
! 1295: } else {
! 1296: // out of memory
! 1297: }
! 1298: qVal2 = realloc(dQ->qVal, dQ->qMaxLen);
! 1299: if (qVal2) {
! 1300: free(dQ->qVal);
! 1301: dQ->qVal = qVal2;
! 1302: } else {
! 1303: // out of memory
! 1304: }
! 1305: }
! 1306:
! 1307: dQ->qX[dQ->qLen] = x;
! 1308: dQ->qY[dQ->qLen] = y;
! 1309: (dQ->qVal)[dQ->qLen] = val;
! 1310:
! 1311: dQ->qLen++;
! 1312:
! 1313: if (val < dQ->qMinVal) {
! 1314: dQ->qMinVal = val;
! 1315: dQ->qMinCount = 1;
! 1316: } else if (val == dQ->qMinVal) {
! 1317: dQ->qMinCount++;
! 1318: }
! 1319: }
! 1320:
! 1321: void updateQueueMinCache(distanceQueue *dQ) {
! 1322: short i;
! 1323: dQ->qMinCount = 0;
! 1324: dQ->qMinVal = 30001;
! 1325: for (i = 0; i < dQ->qLen; i++) {
! 1326: if (dQ->qVal[i] < dQ->qMinVal) {
! 1327: dQ->qMinVal = dQ->qVal[i];
! 1328: dQ->qMinCount = 1;
! 1329: } else if (dQ->qVal[i] == dQ->qMinVal) {
! 1330: dQ->qMinCount++;
! 1331: }
! 1332: }
! 1333: }
! 1334:
! 1335: // removes the lowest value from the queue, populates x/y/value variables and updates min caching
! 1336: void dequeue(short *x, short *y, short *val, distanceQueue *dQ) {
! 1337: short i, minIndex;
! 1338:
! 1339: if (dQ->qMinCount <= 0) {
! 1340: updateQueueMinCache(dQ);
! 1341: }
! 1342:
! 1343: *val = dQ->qMinVal;
! 1344:
! 1345: // find the last instance of the minVal
! 1346: for (minIndex = dQ->qLen - 1; minIndex >= 0 && dQ->qVal[minIndex] != *val; minIndex--);
! 1347:
! 1348: // populate the return variables
! 1349: *x = dQ->qX[minIndex];
! 1350: *y = dQ->qY[minIndex];
! 1351:
! 1352: dQ->qLen--;
! 1353:
! 1354: // delete the minValue queue entry
! 1355: for (i = minIndex; i < dQ->qLen; i++) {
! 1356: dQ->qX[i] = dQ->qX[i+1];
! 1357: dQ->qY[i] = dQ->qY[i+1];
! 1358: dQ->qVal[i] = dQ->qVal[i+1];
! 1359: }
! 1360:
! 1361: // update min values
! 1362: dQ->qMinCount--;
! 1363: if (!dQ->qMinCount && dQ->qLen) {
! 1364: updateQueueMinCache(dQ);
! 1365: }
! 1366:
! 1367: }
! 1368:
! 1369: void dijkstraScan(short **distanceMap, char passMap[DCOLS][DROWS], boolean allowDiagonals) {
! 1370: short i, j, maxDir, val;
! 1371: enum directions dir;
! 1372: distanceQueue dQ;
! 1373:
! 1374: dQ.qMaxLen = DCOLS * DROWS * 1.5;
! 1375: dQ.qX = (short *) malloc(dQ.qMaxLen * sizeof(short));
! 1376: dQ.qY = (short *) malloc(dQ.qMaxLen * sizeof(short));
! 1377: dQ.qVal = (short *) malloc(dQ.qMaxLen * sizeof(short));
! 1378: dQ.qLen = 0;
! 1379: dQ.qMinVal = 30000;
! 1380: dQ.qMinCount = 0;
! 1381:
! 1382: maxDir = (allowDiagonals ? 8 : 4);
! 1383:
! 1384: // seed the queue with the entire map
! 1385: for (i=0; i<DCOLS; i++) {
! 1386: for (j=0; j<DROWS; j++) {
! 1387: if (!passMap || passMap[i][j]) {
! 1388: enqueue(i, j, distanceMap[i][j], &dQ);
! 1389: }
! 1390: }
! 1391: }
! 1392:
! 1393: // iterate through queue updating lowest entries until the queue is empty
! 1394: while (dQ.qLen) {
! 1395: dequeue(&i, &j, &val, &dQ);
! 1396: if (distanceMap[i][j] == val) { // if it hasn't been improved since joining the queue
! 1397: for (dir = 0; dir < maxDir; dir++) {
! 1398: if (coordinatesAreInMap(i + nbDirs[dir][0], j + nbDirs[dir][1])
! 1399: && (!passMap || passMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]])
! 1400: && distanceMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]] >= distanceMap[i][j] + 2) {
! 1401:
! 1402: distanceMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]] = distanceMap[i][j] + 1;
! 1403:
! 1404: enqueue(i + nbDirs[dir][0], j + nbDirs[dir][1], distanceMap[i][j] + 1, &dQ);
! 1405: }
! 1406: }
! 1407: }
! 1408: }
! 1409:
! 1410: free(dQ.qX);
! 1411: free(dQ.qY);
! 1412: free(dQ.qVal);
! 1413: }*/
! 1414:
! 1415: /*
! 1416: void calculateDistances(short **distanceMap, short destinationX, short destinationY, unsigned long blockingTerrainFlags, creature *traveler) {
! 1417: short i, j;
! 1418: boolean somethingChanged;
! 1419:
! 1420: for (i=0; i<DCOLS; i++) {
! 1421: for (j=0; j<DROWS; j++) {
! 1422: distanceMap[i][j] = ((traveler && traveler == &player && !(pmap[i][j].flags & (DISCOVERED | MAGIC_MAPPED)))
! 1423: || ((traveler && monsterAvoids(traveler, i, j))
! 1424: || cellHasTerrainFlag(i, j, blockingTerrainFlags))) ? -1 : 30000;
! 1425: }
! 1426: }
! 1427:
! 1428: distanceMap[destinationX][destinationY] = 0;
! 1429:
! 1430: // dijkstraScan(distanceMap);
! 1431: do {
! 1432: somethingChanged = false;
! 1433: for (i=0; i<DCOLS; i++) {
! 1434: for (j=0; j<DROWS; j++) {
! 1435: if (updateDistanceCell(distanceMap, i, j)) {
! 1436: somethingChanged = true;
! 1437: }
! 1438: }
! 1439: }
! 1440:
! 1441:
! 1442: for (i = DCOLS - 1; i >= 0; i--) {
! 1443: for (j = DROWS - 1; j >= 0; j--) {
! 1444: if (updateDistanceCell(distanceMap, i, j)) {
! 1445: somethingChanged = true;
! 1446: }
! 1447: }
! 1448: }
! 1449: } while (somethingChanged);
! 1450: }*/
! 1451:
! 1452: // Returns -1 if there are no beneficial moves.
! 1453: // If preferDiagonals is true, we will prefer diagonal moves.
! 1454: // Always rolls downhill on the distance map.
! 1455: // If monst is provided, do not return a direction pointing to
! 1456: // a cell that the monster avoids.
! 1457: short nextStep(short **distanceMap, short x, short y, creature *monst, boolean preferDiagonals) {
! 1458: short newX, newY, bestScore;
! 1459: enum directions dir, bestDir;
! 1460: creature *blocker;
! 1461: boolean blocked;
! 1462:
! 1463: brogueAssert(coordinatesAreInMap(x, y));
! 1464:
! 1465: bestScore = 0;
! 1466: bestDir = NO_DIRECTION;
! 1467:
! 1468: for (dir = (preferDiagonals ? 7 : 0);
! 1469: (preferDiagonals ? dir >= 0 : dir < DIRECTION_COUNT);
! 1470: (preferDiagonals ? dir-- : dir++)) {
! 1471:
! 1472: newX = x + nbDirs[dir][0];
! 1473: newY = y + nbDirs[dir][1];
! 1474:
! 1475: brogueAssert(coordinatesAreInMap(newX, newY));
! 1476: if (coordinatesAreInMap(newX, newY)) {
! 1477: blocked = false;
! 1478: blocker = monsterAtLoc(newX, newY);
! 1479: if (monst
! 1480: && monsterAvoids(monst, newX, newY)) {
! 1481:
! 1482: blocked = true;
! 1483: } else if (monst
! 1484: && blocker
! 1485: && !canPass(monst, blocker)
! 1486: && !monstersAreTeammates(monst, blocker)
! 1487: && !monstersAreEnemies(monst, blocker)) {
! 1488: blocked = true;
! 1489: }
! 1490: if ((distanceMap[x][y] - distanceMap[newX][newY]) > bestScore
! 1491: && !diagonalBlocked(x, y, newX, newY, monst == &player)
! 1492: && knownToPlayerAsPassableOrSecretDoor(newX, newY)
! 1493: && !blocked) {
! 1494:
! 1495: bestDir = dir;
! 1496: bestScore = distanceMap[x][y] - distanceMap[newX][newY];
! 1497: }
! 1498: }
! 1499: }
! 1500: return bestDir;
! 1501: }
! 1502:
! 1503: void displayRoute(short **distanceMap, boolean removeRoute) {
! 1504: short currentX = player.xLoc, currentY = player.yLoc, dir, newX, newY;
! 1505: boolean advanced;
! 1506:
! 1507: if (distanceMap[player.xLoc][player.yLoc] < 0 || distanceMap[player.xLoc][player.yLoc] == 30000) {
! 1508: return;
! 1509: }
! 1510: do {
! 1511: if (removeRoute) {
! 1512: refreshDungeonCell(currentX, currentY);
! 1513: } else {
! 1514: hiliteCell(currentX, currentY, &hiliteColor, 50, true);
! 1515: }
! 1516: advanced = false;
! 1517: for (dir = 7; dir >= 0; dir--) {
! 1518: newX = currentX + nbDirs[dir][0];
! 1519: newY = currentY + nbDirs[dir][1];
! 1520: if (coordinatesAreInMap(newX, newY)
! 1521: && distanceMap[newX][newY] >= 0 && distanceMap[newX][newY] < distanceMap[currentX][currentY]
! 1522: && !diagonalBlocked(currentX, currentY, newX, newY, true)) {
! 1523:
! 1524: currentX = newX;
! 1525: currentY = newY;
! 1526: advanced = true;
! 1527: break;
! 1528: }
! 1529: }
! 1530: } while (advanced);
! 1531: }
! 1532:
! 1533: void travelRoute(short path[1000][2], short steps) {
! 1534: short i, j;
! 1535: short dir;
! 1536: creature *monst;
! 1537:
! 1538: brogueAssert(!rogue.playbackMode);
! 1539:
! 1540: rogue.disturbed = false;
! 1541: rogue.automationActive = true;
! 1542:
! 1543: for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
! 1544: if (canSeeMonster(monst)) {
! 1545: monst->bookkeepingFlags |= MB_ALREADY_SEEN;
! 1546: } else {
! 1547: monst->bookkeepingFlags &= ~MB_ALREADY_SEEN;
! 1548: }
! 1549: }
! 1550:
! 1551: for (i=0; i < steps && !rogue.disturbed; i++) {
! 1552: for (j = i + 1; j < steps - 1; j++) {
! 1553: // Check to see if the path has become obstructed or avoided since the last time we saw it.
! 1554: if (diagonalBlocked(path[j-1][0], path[j-1][1], path[j][0], path[j][1], true)
! 1555: || monsterAvoids(&player, path[j][0], path[j][1])) {
! 1556:
! 1557: rogue.disturbed = true;
! 1558: break;
! 1559: }
! 1560: }
! 1561: for (dir = 0; dir < DIRECTION_COUNT && !rogue.disturbed; dir++) {
! 1562: if (player.xLoc + nbDirs[dir][0] == path[i][0]
! 1563: && player.yLoc + nbDirs[dir][1] == path[i][1]) {
! 1564:
! 1565: if (!playerMoves(dir)) {
! 1566: rogue.disturbed = true;
! 1567: }
! 1568: if (pauseBrogue(25)) {
! 1569: rogue.disturbed = true;
! 1570: }
! 1571: break;
! 1572: }
! 1573: }
! 1574: }
! 1575: rogue.disturbed = true;
! 1576: rogue.automationActive = false;
! 1577: updateFlavorText();
! 1578: }
! 1579:
! 1580: void travelMap(short **distanceMap) {
! 1581: short currentX = player.xLoc, currentY = player.yLoc, dir, newX, newY;
! 1582: boolean advanced;
! 1583:
! 1584: rogue.disturbed = false;
! 1585: rogue.automationActive = true;
! 1586:
! 1587: if (distanceMap[player.xLoc][player.yLoc] < 0 || distanceMap[player.xLoc][player.yLoc] == 30000) {
! 1588: return;
! 1589: }
! 1590: do {
! 1591: advanced = false;
! 1592: for (dir = 7; dir >= 0; dir--) {
! 1593: newX = currentX + nbDirs[dir][0];
! 1594: newY = currentY + nbDirs[dir][1];
! 1595: if (coordinatesAreInMap(newX, newY)
! 1596: && distanceMap[newX][newY] >= 0
! 1597: && distanceMap[newX][newY] < distanceMap[currentX][currentY]
! 1598: && !diagonalBlocked(currentX, currentY, newX, newY, true)) {
! 1599:
! 1600: if (!playerMoves(dir)) {
! 1601: rogue.disturbed = true;
! 1602: }
! 1603: if (pauseBrogue(500)) {
! 1604: rogue.disturbed = true;
! 1605: }
! 1606: currentX = newX;
! 1607: currentY = newY;
! 1608: advanced = true;
! 1609: break;
! 1610: }
! 1611: }
! 1612: } while (advanced && !rogue.disturbed);
! 1613: rogue.disturbed = true;
! 1614: rogue.automationActive = false;
! 1615: updateFlavorText();
! 1616: }
! 1617:
! 1618: void travel(short x, short y, boolean autoConfirm) {
! 1619: short **distanceMap, i;
! 1620: rogueEvent theEvent;
! 1621: unsigned short staircaseConfirmKey;
! 1622:
! 1623: confirmMessages();
! 1624:
! 1625: if (D_WORMHOLING) {
! 1626: recordMouseClick(mapToWindowX(x), mapToWindowY(y), true, false);
! 1627: pmap[player.xLoc][player.yLoc].flags &= ~HAS_PLAYER;
! 1628: refreshDungeonCell(player.xLoc, player.yLoc);
! 1629: player.xLoc = x;
! 1630: player.yLoc = y;
! 1631: pmap[x][y].flags |= HAS_PLAYER;
! 1632: updatePlayerUnderwaterness();
! 1633: refreshDungeonCell(x, y);
! 1634: updateVision(true);
! 1635: return;
! 1636: }
! 1637:
! 1638: if (abs(player.xLoc - x) + abs(player.yLoc - y) == 1) {
! 1639: // targeting a cardinal neighbor
! 1640: for (i=0; i<4; i++) {
! 1641: if (nbDirs[i][0] == (x - player.xLoc) && nbDirs[i][1] == (y - player.yLoc)) {
! 1642: playerMoves(i);
! 1643: break;
! 1644: }
! 1645: }
! 1646: return;
! 1647: }
! 1648:
! 1649: if (!(pmap[x][y].flags & (DISCOVERED | MAGIC_MAPPED))) {
! 1650: message("You have not explored that location.", false);
! 1651: return;
! 1652: }
! 1653:
! 1654: distanceMap = allocGrid();
! 1655:
! 1656: calculateDistances(distanceMap, x, y, 0, &player, false, false);
! 1657: if (distanceMap[player.xLoc][player.yLoc] < 30000) {
! 1658: if (autoConfirm) {
! 1659: travelMap(distanceMap);
! 1660: //refreshSideBar(-1, -1, false);
! 1661: } else {
! 1662: if (rogue.upLoc[0] == x && rogue.upLoc[1] == y) {
! 1663: staircaseConfirmKey = ASCEND_KEY;
! 1664: } else if (rogue.downLoc[0] == x && rogue.downLoc[1] == y) {
! 1665: staircaseConfirmKey = DESCEND_KEY;
! 1666: } else {
! 1667: staircaseConfirmKey = 0;
! 1668: }
! 1669: displayRoute(distanceMap, false);
! 1670: message("Travel this route? (y/n)", false);
! 1671:
! 1672: do {
! 1673: nextBrogueEvent(&theEvent, true, false, false);
! 1674: } while (theEvent.eventType != MOUSE_UP && theEvent.eventType != KEYSTROKE);
! 1675:
! 1676: displayRoute(distanceMap, true); // clear route display
! 1677: confirmMessages();
! 1678:
! 1679: if ((theEvent.eventType == MOUSE_UP && windowToMapX(theEvent.param1) == x && windowToMapY(theEvent.param2) == y)
! 1680: || (theEvent.eventType == KEYSTROKE && (theEvent.param1 == 'Y' || theEvent.param1 == 'y'
! 1681: || theEvent.param1 == RETURN_KEY
! 1682: || (theEvent.param1 == staircaseConfirmKey
! 1683: && theEvent.param1 != 0)))) {
! 1684: travelMap(distanceMap);
! 1685: //refreshSideBar(-1, -1, false);
! 1686: commitDraws();
! 1687: } else if (theEvent.eventType == MOUSE_UP) {
! 1688: executeMouseClick(&theEvent);
! 1689: }
! 1690: }
! 1691: // if (player.xLoc == x && player.yLoc == y) {
! 1692: // rogue.cursorLoc[0] = rogue.cursorLoc[1] = 0;
! 1693: // } else {
! 1694: // rogue.cursorLoc[0] = x;
! 1695: // rogue.cursorLoc[1] = y;
! 1696: // }
! 1697: } else {
! 1698: rogue.cursorLoc[0] = rogue.cursorLoc[1] = -1;
! 1699: message("No path is available.", false);
! 1700: }
! 1701: freeGrid(distanceMap);
! 1702: }
! 1703:
! 1704: void populateGenericCostMap(short **costMap) {
! 1705: short i, j;
! 1706:
! 1707: for (i=0; i<DCOLS; i++) {
! 1708: for (j=0; j<DROWS; j++) {
! 1709: if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY)
! 1710: && (!cellHasTMFlag(i, j, TM_IS_SECRET) || (discoveredTerrainFlagsAtLoc(i, j) & T_OBSTRUCTS_PASSABILITY))) {
! 1711:
! 1712: costMap[i][j] = cellHasTerrainFlag(i, j, T_OBSTRUCTS_DIAGONAL_MOVEMENT) ? PDS_OBSTRUCTION : PDS_FORBIDDEN;
! 1713: } else if (cellHasTerrainFlag(i, j, T_PATHING_BLOCKER & ~T_OBSTRUCTS_PASSABILITY)) {
! 1714: costMap[i][j] = PDS_FORBIDDEN;
! 1715: } else {
! 1716: costMap[i][j] = 1;
! 1717: }
! 1718: }
! 1719: }
! 1720: }
! 1721:
! 1722: void getLocationFlags(const short x, const short y,
! 1723: unsigned long *tFlags, unsigned long *TMFlags, unsigned long *cellFlags,
! 1724: const boolean limitToPlayerKnowledge) {
! 1725: if (limitToPlayerKnowledge
! 1726: && (pmap[x][y].flags & (DISCOVERED | MAGIC_MAPPED))
! 1727: && !playerCanSee(x, y)) {
! 1728:
! 1729: if (tFlags) {
! 1730: *tFlags = pmap[x][y].rememberedTerrainFlags;
! 1731: }
! 1732: if (TMFlags) {
! 1733: *TMFlags = pmap[x][y].rememberedTMFlags;
! 1734: }
! 1735: if (cellFlags) {
! 1736: *cellFlags = pmap[x][y].rememberedCellFlags;
! 1737: }
! 1738: } else {
! 1739: if (tFlags) {
! 1740: *tFlags = terrainFlags(x, y);
! 1741: }
! 1742: if (TMFlags) {
! 1743: *TMFlags = terrainMechFlags(x, y);
! 1744: }
! 1745: if (cellFlags) {
! 1746: *cellFlags = pmap[x][y].flags;
! 1747: }
! 1748: }
! 1749: }
! 1750:
! 1751: void populateCreatureCostMap(short **costMap, creature *monst) {
! 1752: short i, j, unexploredCellCost;
! 1753: creature *currentTenant;
! 1754: item *theItem;
! 1755: unsigned long tFlags, cFlags;
! 1756:
! 1757: unexploredCellCost = 10 + (clamp(rogue.depthLevel, 5, 15) - 5) * 2;
! 1758:
! 1759: for (i=0; i<DCOLS; i++) {
! 1760: for (j=0; j<DROWS; j++) {
! 1761: if (monst == &player && !(pmap[i][j].flags & (DISCOVERED | MAGIC_MAPPED))) {
! 1762: costMap[i][j] = PDS_OBSTRUCTION;
! 1763: continue;
! 1764: }
! 1765:
! 1766: getLocationFlags(i, j, &tFlags, NULL, &cFlags, monst == &player);
! 1767:
! 1768: if ((tFlags & T_OBSTRUCTS_PASSABILITY)
! 1769: && (!cellHasTMFlag(i, j, TM_IS_SECRET) || (discoveredTerrainFlagsAtLoc(i, j) & T_OBSTRUCTS_PASSABILITY) || monst == &player)) {
! 1770:
! 1771: costMap[i][j] = (tFlags & T_OBSTRUCTS_DIAGONAL_MOVEMENT) ? PDS_OBSTRUCTION : PDS_FORBIDDEN;
! 1772: continue;
! 1773: }
! 1774:
! 1775: if ((tFlags & T_LAVA_INSTA_DEATH)
! 1776: && !(monst->info.flags & (MONST_IMMUNE_TO_FIRE | MONST_FLIES | MONST_INVULNERABLE))
! 1777: && (monst->status[STATUS_LEVITATING] || monst->status[STATUS_IMMUNE_TO_FIRE])
! 1778: && max(monst->status[STATUS_LEVITATING], monst->status[STATUS_IMMUNE_TO_FIRE]) < (rogue.mapToShore[i][j] + distanceBetween(i, j, monst->xLoc, monst->yLoc) * monst->movementSpeed / 100)) {
! 1779: // Only a temporary effect will permit the monster to survive the lava, and the remaining duration either isn't
! 1780: // enough to get it to the spot, or it won't suffice to let it return to shore if it does get there.
! 1781: // Treat these locations as obstacles.
! 1782: costMap[i][j] = PDS_FORBIDDEN;
! 1783: continue;
! 1784: }
! 1785:
! 1786: if (((tFlags & T_AUTO_DESCENT) || (tFlags & T_IS_DEEP_WATER) && !(monst->info.flags & MONST_IMMUNE_TO_WATER))
! 1787: && !(monst->info.flags & MONST_FLIES)
! 1788: && (monst->status[STATUS_LEVITATING])
! 1789: && monst->status[STATUS_LEVITATING] < (rogue.mapToShore[i][j] + distanceBetween(i, j, monst->xLoc, monst->yLoc) * monst->movementSpeed / 100)) {
! 1790: // Only a temporary effect will permit the monster to levitate over the chasm/water, and the remaining duration either isn't
! 1791: // enough to get it to the spot, or it won't suffice to let it return to shore if it does get there.
! 1792: // Treat these locations as obstacles.
! 1793: costMap[i][j] = PDS_FORBIDDEN;
! 1794: continue;
! 1795: }
! 1796:
! 1797: if (monsterAvoids(monst, i, j)) {
! 1798: costMap[i][j] = PDS_FORBIDDEN;
! 1799: continue;
! 1800: }
! 1801:
! 1802: if (cFlags & HAS_MONSTER) {
! 1803: currentTenant = monsterAtLoc(i, j);
! 1804: if (currentTenant
! 1805: && (currentTenant->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE))
! 1806: && !canPass(monst, currentTenant)) {
! 1807:
! 1808: costMap[i][j] = PDS_FORBIDDEN;
! 1809: continue;
! 1810: }
! 1811: }
! 1812:
! 1813: if ((cFlags & KNOWN_TO_BE_TRAP_FREE)
! 1814: || (monst != &player && monst->creatureState != MONSTER_ALLY)) {
! 1815:
! 1816: costMap[i][j] = 10;
! 1817: } else {
! 1818: // Player and allies give locations that are known to be free of traps
! 1819: // an advantage that increases with depth level, based on the depths
! 1820: // at which traps are generated.
! 1821: costMap[i][j] = unexploredCellCost;
! 1822: }
! 1823:
! 1824: if (!(monst->info.flags & MONST_INVULNERABLE)) {
! 1825: if ((tFlags & T_CAUSES_NAUSEA)
! 1826: || cellHasTMFlag(i, j, TM_PROMOTES_ON_ITEM_PICKUP)
! 1827: || (tFlags & T_ENTANGLES) && !(monst->info.flags & MONST_IMMUNE_TO_WEBS)) {
! 1828:
! 1829: costMap[i][j] += 20;
! 1830: }
! 1831: }
! 1832:
! 1833: if (monst == &player) {
! 1834: theItem = itemAtLoc(i, j);
! 1835: if (theItem && (theItem->flags & ITEM_PLAYER_AVOIDS)) {
! 1836: costMap[i][j] += 10;
! 1837: }
! 1838: }
! 1839: }
! 1840: }
! 1841: }
! 1842:
! 1843: enum directions adjacentFightingDir() {
! 1844: short newX, newY;
! 1845: enum directions dir;
! 1846: creature *monst;
! 1847:
! 1848: if (cellHasTerrainFlag(player.xLoc, player.yLoc, T_OBSTRUCTS_PASSABILITY)) {
! 1849: return NO_DIRECTION;
! 1850: }
! 1851: for (dir = 0; dir < DIRECTION_COUNT; dir++) {
! 1852: newX = player.xLoc + nbDirs[dir][0];
! 1853: newY = player.yLoc + nbDirs[dir][1];
! 1854: monst = monsterAtLoc(newX, newY);
! 1855: if (monst
! 1856: && canSeeMonster(monst)
! 1857: && (!diagonalBlocked(player.xLoc, player.yLoc, newX, newY, false) || (monst->info.flags & MONST_ATTACKABLE_THRU_WALLS))
! 1858: && monstersAreEnemies(&player, monst)
! 1859: && !(monst->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE))) {
! 1860:
! 1861: return dir;
! 1862: }
! 1863: }
! 1864: return NO_DIRECTION;
! 1865: }
! 1866:
! 1867: #define exploreGoalValue(x, y) (0 - abs((x) - DCOLS / 2) / 3 - abs((x) - DCOLS / 2) / 4)
! 1868:
! 1869: void getExploreMap(short **map, boolean headingToStairs) {// calculate explore map
! 1870: short i, j;
! 1871: short **costMap;
! 1872: item *theItem;
! 1873:
! 1874: costMap = allocGrid();
! 1875: populateCreatureCostMap(costMap, &player);
! 1876:
! 1877: for (i=0; i<DCOLS; i++) {
! 1878: for (j=0; j<DROWS; j++) {
! 1879: map[i][j] = 30000; // Can be overridden later.
! 1880: theItem = itemAtLoc(i, j);
! 1881: if (!(pmap[i][j].flags & DISCOVERED)) {
! 1882: if ((pmap[i][j].flags & MAGIC_MAPPED)
! 1883: && (tileCatalog[pmap[i][j].layers[DUNGEON]].flags | tileCatalog[pmap[i][j].layers[LIQUID]].flags) & T_PATHING_BLOCKER) {
! 1884: // Magic-mapped cells revealed as obstructions should be treated as such even though they're not discovered.
! 1885: costMap[i][j] = cellHasTerrainFlag(i, j, T_OBSTRUCTS_DIAGONAL_MOVEMENT) ? PDS_OBSTRUCTION : PDS_FORBIDDEN;
! 1886: } else {
! 1887: costMap[i][j] = 1;
! 1888: map[i][j] = exploreGoalValue(i, j);
! 1889: }
! 1890: } else if (theItem
! 1891: && !monsterAvoids(&player, i, j)) {
! 1892: if (theItem->flags & ITEM_PLAYER_AVOIDS) {
! 1893: costMap[i][j] = 20;
! 1894: } else {
! 1895: costMap[i][j] = 1;
! 1896: map[i][j] = exploreGoalValue(i, j) - 10;
! 1897: }
! 1898: }
! 1899: }
! 1900: }
! 1901:
! 1902: costMap[rogue.downLoc[0]][rogue.downLoc[1]] = 100;
! 1903: costMap[rogue.upLoc[0]][rogue.upLoc[1]] = 100;
! 1904:
! 1905: if (headingToStairs) {
! 1906: map[rogue.downLoc[0]][rogue.downLoc[1]] = 0; // head to the stairs
! 1907: }
! 1908:
! 1909: dijkstraScan(map, costMap, true);
! 1910:
! 1911: //displayGrid(costMap);
! 1912: freeGrid(costMap);
! 1913: }
! 1914:
! 1915: boolean explore(short frameDelay) {
! 1916: short **distanceMap;
! 1917: short path[1000][2], steps;
! 1918: boolean madeProgress, headingToStairs;
! 1919: enum directions dir;
! 1920: creature *monst;
! 1921:
! 1922: // Explore commands should never be written to a recording.
! 1923: // Instead, the elemental movement commands that compose it
! 1924: // should be written individually.
! 1925: brogueAssert(!rogue.playbackMode);
! 1926:
! 1927: clearCursorPath();
! 1928:
! 1929: madeProgress = false;
! 1930: headingToStairs = false;
! 1931:
! 1932: if (player.status[STATUS_CONFUSED]) {
! 1933: message("Not while you're confused.", false);
! 1934: return false;
! 1935: }
! 1936: if (cellHasTerrainFlag(player.xLoc, player.yLoc, T_OBSTRUCTS_PASSABILITY)) {
! 1937: message("Not while you're trapped.", false);
! 1938: return false;
! 1939: }
! 1940:
! 1941: for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
! 1942: if (canSeeMonster(monst)) {
! 1943: monst->bookkeepingFlags |= MB_ALREADY_SEEN;
! 1944: } else {
! 1945: monst->bookkeepingFlags &= ~MB_ALREADY_SEEN;
! 1946: }
! 1947: }
! 1948:
! 1949: // fight any adjacent enemies
! 1950: dir = adjacentFightingDir();
! 1951: if (dir != NO_DIRECTION
! 1952: && startFighting(dir, (player.status[STATUS_HALLUCINATING] ? true : false))) {
! 1953:
! 1954: return true;
! 1955: }
! 1956:
! 1957: if (!rogue.autoPlayingLevel) {
! 1958: message(KEYBOARD_LABELS ? "Exploring... press any key to stop." : "Exploring... touch anywhere to stop.",
! 1959: false);
! 1960: // A little hack so the exploring message remains bright while exploring and then auto-dims when
! 1961: // another message is displayed:
! 1962: confirmMessages();
! 1963: printString(KEYBOARD_LABELS ? "Exploring... press any key to stop." : "Exploring... touch anywhere to stop.",
! 1964: mapToWindowX(0), mapToWindowY(-1), &white, &black, NULL);
! 1965: }
! 1966: rogue.disturbed = false;
! 1967: rogue.automationActive = true;
! 1968:
! 1969: distanceMap = allocGrid();
! 1970: do {
! 1971: // fight any adjacent enemies
! 1972: dir = adjacentFightingDir();
! 1973: if (dir != NO_DIRECTION) {
! 1974: startFighting(dir, (player.status[STATUS_HALLUCINATING] ? true : false));
! 1975: if (rogue.disturbed) {
! 1976: madeProgress = true;
! 1977: continue;
! 1978: }
! 1979: }
! 1980: if (rogue.disturbed) {
! 1981: continue;
! 1982: }
! 1983:
! 1984: getExploreMap(distanceMap, headingToStairs);
! 1985:
! 1986: // hilite path
! 1987: steps = getPlayerPathOnMap(path, distanceMap, player.xLoc, player.yLoc);
! 1988: hilitePath(path, steps, false);
! 1989:
! 1990: // take a step
! 1991: dir = nextStep(distanceMap, player.xLoc, player.yLoc, NULL, false);
! 1992:
! 1993: if (!headingToStairs && rogue.autoPlayingLevel && dir == NO_DIRECTION) {
! 1994: headingToStairs = true;
! 1995: continue;
! 1996: }
! 1997:
! 1998: refreshSideBar(-1, -1, false);
! 1999:
! 2000: if (dir == NO_DIRECTION) {
! 2001: rogue.disturbed = true;
! 2002: } else if (!playerMoves(dir)) {
! 2003: rogue.disturbed = true;
! 2004: } else {
! 2005: madeProgress = true;
! 2006: if (pauseBrogue(frameDelay)) {
! 2007: rogue.disturbed = true;
! 2008: rogue.autoPlayingLevel = false;
! 2009: }
! 2010: }
! 2011: hilitePath(path, steps, true);
! 2012: } while (!rogue.disturbed);
! 2013: //clearCursorPath();
! 2014: rogue.automationActive = false;
! 2015: refreshSideBar(-1, -1, false);
! 2016: freeGrid(distanceMap);
! 2017: return madeProgress;
! 2018: }
! 2019:
! 2020: void autoPlayLevel(boolean fastForward) {
! 2021: boolean madeProgress;
! 2022:
! 2023: rogue.autoPlayingLevel = true;
! 2024:
! 2025: confirmMessages();
! 2026: message(KEYBOARD_LABELS ? "Playing... press any key to stop." : "Playing... touch anywhere to stop.", false);
! 2027:
! 2028: // explore until we are not making progress
! 2029: do {
! 2030: madeProgress = explore(fastForward ? 1 : 50);
! 2031: //refreshSideBar(-1, -1, false);
! 2032:
! 2033: if (!madeProgress && rogue.downLoc[0] == player.xLoc && rogue.downLoc[1] == player.yLoc) {
! 2034: useStairs(1);
! 2035: madeProgress = true;
! 2036: }
! 2037: } while (madeProgress && rogue.autoPlayingLevel);
! 2038:
! 2039: confirmMessages();
! 2040:
! 2041: rogue.autoPlayingLevel = false;
! 2042: }
! 2043:
! 2044: short directionOfKeypress(unsigned short ch) {
! 2045: switch (ch) {
! 2046: case LEFT_KEY:
! 2047: case LEFT_ARROW:
! 2048: case NUMPAD_4:
! 2049: return LEFT;
! 2050: case RIGHT_KEY:
! 2051: case RIGHT_ARROW:
! 2052: case NUMPAD_6:
! 2053: return RIGHT;
! 2054: case UP_KEY:
! 2055: case UP_ARROW:
! 2056: case NUMPAD_8:
! 2057: return UP;
! 2058: case DOWN_KEY:
! 2059: case DOWN_ARROW:
! 2060: case NUMPAD_2:
! 2061: return DOWN;
! 2062: case UPLEFT_KEY:
! 2063: case NUMPAD_7:
! 2064: return UPLEFT;
! 2065: case UPRIGHT_KEY:
! 2066: case NUMPAD_9:
! 2067: return UPRIGHT;
! 2068: case DOWNLEFT_KEY:
! 2069: case NUMPAD_1:
! 2070: return DOWNLEFT;
! 2071: case DOWNRIGHT_KEY:
! 2072: case NUMPAD_3:
! 2073: return DOWNRIGHT;
! 2074: default:
! 2075: return -1;
! 2076: }
! 2077: }
! 2078:
! 2079: boolean startFighting(enum directions dir, boolean tillDeath) {
! 2080: short x, y, expectedDamage;
! 2081: creature *monst;
! 2082:
! 2083: x = player.xLoc + nbDirs[dir][0];
! 2084: y = player.yLoc + nbDirs[dir][1];
! 2085: monst = monsterAtLoc(x, y);
! 2086: if (monst->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE)) {
! 2087: return false;
! 2088: }
! 2089: expectedDamage = monst->info.damage.upperBound * monsterDamageAdjustmentAmount(monst) / FP_FACTOR;
! 2090: if (rogue.easyMode) {
! 2091: expectedDamage /= 5;
! 2092: }
! 2093: rogue.blockCombatText = true;
! 2094: rogue.disturbed = false;
! 2095: do {
! 2096: if (!playerMoves(dir)) {
! 2097: break;
! 2098: }
! 2099: if (pauseBrogue(1)) {
! 2100: break;
! 2101: }
! 2102: } while (!rogue.disturbed && !rogue.gameHasEnded && (tillDeath || player.currentHP > expectedDamage)
! 2103: && (pmap[x][y].flags & HAS_MONSTER) && monsterAtLoc(x, y) == monst);
! 2104:
! 2105: rogue.blockCombatText = false;
! 2106: return rogue.disturbed;
! 2107: }
! 2108:
! 2109: boolean isDisturbed(short x, short y) {
! 2110: short i;
! 2111: creature *monst;
! 2112: for (i=0; i< DIRECTION_COUNT; i++) {
! 2113: monst = monsterAtLoc(x + nbDirs[i][0], y + nbDirs[i][1]);
! 2114: if (pmap[x + nbDirs[i][0]][y + nbDirs[i][1]].flags & (HAS_ITEM)) {
! 2115: // Do not trigger for submerged or invisible or unseen monsters.
! 2116: return true;
! 2117: }
! 2118: if (monst
! 2119: && !(monst->creatureState == MONSTER_ALLY)
! 2120: && (canSeeMonster(monst) || monsterRevealed(monst))) {
! 2121: // Do not trigger for submerged or invisible or unseen monsters.
! 2122: return true;
! 2123: }
! 2124: }
! 2125: return false;
! 2126: }
! 2127:
! 2128: void discover(short x, short y) {
! 2129: enum dungeonLayers layer;
! 2130: dungeonFeature *feat;
! 2131: if (cellHasTMFlag(x, y, TM_IS_SECRET)) {
! 2132:
! 2133: for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
! 2134: if (tileCatalog[pmap[x][y].layers[layer]].mechFlags & TM_IS_SECRET) {
! 2135: feat = &dungeonFeatureCatalog[tileCatalog[pmap[x][y].layers[layer]].discoverType];
! 2136: pmap[x][y].layers[layer] = (layer == DUNGEON ? FLOOR : NOTHING);
! 2137: spawnDungeonFeature(x, y, feat, true, false);
! 2138: }
! 2139: }
! 2140: refreshDungeonCell(x, y);
! 2141:
! 2142: if (playerCanSee(x, y)) {
! 2143: rogue.disturbed = true;
! 2144: }
! 2145: }
! 2146: }
! 2147:
! 2148: // returns true if found anything
! 2149: boolean search(short searchStrength) {
! 2150: short i, j, radius, x, y, percent;
! 2151: boolean foundSomething = false;
! 2152:
! 2153: radius = searchStrength / 10;
! 2154: x = player.xLoc;
! 2155: y = player.yLoc;
! 2156:
! 2157: for (i = x - radius; i <= x + radius; i++) {
! 2158: for (j = y - radius; j <= y + radius; j++) {
! 2159: if (coordinatesAreInMap(i, j)
! 2160: && playerCanDirectlySee(i, j)) {
! 2161:
! 2162: percent = searchStrength - distanceBetween(x, y, i, j) * 10;
! 2163: if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY)) {
! 2164: percent = percent * 2/3;
! 2165: }
! 2166: if (percent >= 100) {
! 2167: pmap[i][j].flags |= KNOWN_TO_BE_TRAP_FREE;
! 2168: }
! 2169: percent = min(percent, 100);
! 2170: if (cellHasTMFlag(i, j, TM_IS_SECRET)) {
! 2171: if (rand_percent(percent)) {
! 2172: discover(i, j);
! 2173: foundSomething = true;
! 2174: }
! 2175: }
! 2176: }
! 2177: }
! 2178: }
! 2179: return foundSomething;
! 2180: }
! 2181:
! 2182: boolean proposeOrConfirmLocation(short x, short y, char *failureMessage) {
! 2183: boolean retval = false;
! 2184: if (player.xLoc == x && player.yLoc == y) {
! 2185: message("you are already there.", false);
! 2186: } else if (pmap[x][y].flags & (DISCOVERED | MAGIC_MAPPED)) {
! 2187: if (rogue.cursorLoc[0] == x && rogue.cursorLoc[1] == y) {
! 2188: retval = true;
! 2189: } else {
! 2190: rogue.cursorLoc[0] = x;
! 2191: rogue.cursorLoc[1] = y;
! 2192: }
! 2193: } else {
! 2194: message(failureMessage, false);
! 2195: }
! 2196: return retval;
! 2197: }
! 2198:
! 2199: boolean useStairs(short stairDirection) {
! 2200: boolean succeeded = false;
! 2201: //cellDisplayBuffer fromBuf[COLS][ROWS], toBuf[COLS][ROWS];
! 2202:
! 2203: if (stairDirection == 1) {
! 2204: if (rogue.depthLevel < DEEPEST_LEVEL) {
! 2205: //copyDisplayBuffer(fromBuf, displayBuffer);
! 2206: rogue.cursorLoc[0] = rogue.cursorLoc[1] = -1;
! 2207: rogue.depthLevel++;
! 2208: message("You descend.", false);
! 2209: startLevel(rogue.depthLevel - 1, stairDirection);
! 2210: if (rogue.depthLevel > rogue.deepestLevel) {
! 2211: rogue.deepestLevel = rogue.depthLevel;
! 2212: }
! 2213: //copyDisplayBuffer(toBuf, displayBuffer);
! 2214: //irisFadeBetweenBuffers(fromBuf, toBuf, mapToWindowX(player.xLoc), mapToWindowY(player.yLoc), 20, false);
! 2215: } else if (numberOfMatchingPackItems(AMULET, 0, 0, false)) {
! 2216: victory(true);
! 2217: } else {
! 2218: confirmMessages();
! 2219: messageWithColor("the crystal archway repels you with a mysterious force!", &lightBlue, false);
! 2220: messageWithColor("(Only the bearer of the Amulet of Yendor may pass.)", &backgroundMessageColor, false);
! 2221: }
! 2222: succeeded = true;
! 2223: } else {
! 2224: if (rogue.depthLevel > 1 || numberOfMatchingPackItems(AMULET, 0, 0, false)) {
! 2225: rogue.cursorLoc[0] = rogue.cursorLoc[1] = -1;
! 2226: rogue.depthLevel--;
! 2227: if (rogue.depthLevel == 0) {
! 2228: victory(false);
! 2229: } else {
! 2230: //copyDisplayBuffer(fromBuf, displayBuffer);
! 2231: message("You ascend.", false);
! 2232: startLevel(rogue.depthLevel + 1, stairDirection);
! 2233: //copyDisplayBuffer(toBuf, displayBuffer);
! 2234: //irisFadeBetweenBuffers(fromBuf, toBuf, mapToWindowX(player.xLoc), mapToWindowY(player.yLoc), 20, true);
! 2235: }
! 2236: succeeded = true;
! 2237: } else {
! 2238: confirmMessages();
! 2239: messageWithColor("The dungeon exit is magically sealed!", &lightBlue, false);
! 2240: messageWithColor("(Only the bearer of the Amulet of Yendor may pass.)", &backgroundMessageColor, false);
! 2241: }
! 2242: }
! 2243:
! 2244: if (succeeded) {
! 2245: updatePlayerUnderwaterness();
! 2246: }
! 2247:
! 2248: return succeeded;
! 2249: }
! 2250:
! 2251: void storeMemories(const short x, const short y) {
! 2252: pmap[x][y].rememberedTerrainFlags = terrainFlags(x, y);
! 2253: pmap[x][y].rememberedTMFlags = terrainMechFlags(x, y);
! 2254: pmap[x][y].rememberedCellFlags = pmap[x][y].flags;
! 2255: pmap[x][y].rememberedTerrain = pmap[x][y].layers[highestPriorityLayer(x, y, false)];
! 2256: }
! 2257:
! 2258: void updateFieldOfViewDisplay(boolean updateDancingTerrain, boolean refreshDisplay) {
! 2259: short i, j;
! 2260: item *theItem;
! 2261: char buf[COLS*3], name[COLS*3];
! 2262:
! 2263: assureCosmeticRNG;
! 2264:
! 2265: for (i=0; i<DCOLS; i++) {
! 2266: for (j = DROWS-1; j >= 0; j--) {
! 2267: if (pmap[i][j].flags & IN_FIELD_OF_VIEW
! 2268: && (max(0, tmap[i][j].light[0])
! 2269: + max(0, tmap[i][j].light[1])
! 2270: + max(0, tmap[i][j].light[2]) > VISIBILITY_THRESHOLD)
! 2271: && !(pmap[i][j].flags & CLAIRVOYANT_DARKENED)) {
! 2272:
! 2273: pmap[i][j].flags |= VISIBLE;
! 2274: }
! 2275:
! 2276: if ((pmap[i][j].flags & VISIBLE) && !(pmap[i][j].flags & WAS_VISIBLE)) { // if the cell became visible this move
! 2277: if (!(pmap[i][j].flags & DISCOVERED) && rogue.automationActive) {
! 2278: if (pmap[i][j].flags & HAS_ITEM) {
! 2279: theItem = itemAtLoc(i, j);
! 2280: if (theItem && (theItem->category & KEY)) {
! 2281: itemName(theItem, name, false, true, NULL);
! 2282: sprintf(buf, "you see %s.", name);
! 2283: messageWithColor(buf, &itemMessageColor, false);
! 2284: }
! 2285: }
! 2286: if (!(pmap[i][j].flags & MAGIC_MAPPED)
! 2287: && cellHasTMFlag(i, j, TM_INTERRUPT_EXPLORATION_WHEN_SEEN)) {
! 2288:
! 2289: strcpy(name, tileCatalog[pmap[i][j].layers[layerWithTMFlag(i, j, TM_INTERRUPT_EXPLORATION_WHEN_SEEN)]].description);
! 2290: sprintf(buf, "you see %s.", name);
! 2291: messageWithColor(buf, &backgroundMessageColor, false);
! 2292: }
! 2293: }
! 2294: discoverCell(i, j);
! 2295: if (refreshDisplay) {
! 2296: refreshDungeonCell(i, j);
! 2297: }
! 2298: } else if (!(pmap[i][j].flags & VISIBLE) && (pmap[i][j].flags & WAS_VISIBLE)) { // if the cell ceased being visible this move
! 2299: storeMemories(i, j);
! 2300: if (refreshDisplay) {
! 2301: refreshDungeonCell(i, j);
! 2302: }
! 2303: } else if (!(pmap[i][j].flags & CLAIRVOYANT_VISIBLE) && (pmap[i][j].flags & WAS_CLAIRVOYANT_VISIBLE)) { // ceased being clairvoyantly visible
! 2304: storeMemories(i, j);
! 2305: if (refreshDisplay) {
! 2306: refreshDungeonCell(i, j);
! 2307: }
! 2308: } else if (!(pmap[i][j].flags & WAS_CLAIRVOYANT_VISIBLE) && (pmap[i][j].flags & CLAIRVOYANT_VISIBLE)) { // became clairvoyantly visible
! 2309: pmap[i][j].flags &= ~STABLE_MEMORY;
! 2310: if (refreshDisplay) {
! 2311: refreshDungeonCell(i, j);
! 2312: }
! 2313: } else if (!(pmap[i][j].flags & TELEPATHIC_VISIBLE) && (pmap[i][j].flags & WAS_TELEPATHIC_VISIBLE)) { // ceased being telepathically visible
! 2314: storeMemories(i, j);
! 2315: if (refreshDisplay) {
! 2316: refreshDungeonCell(i, j);
! 2317: }
! 2318: } else if (!(pmap[i][j].flags & WAS_TELEPATHIC_VISIBLE) && (pmap[i][j].flags & TELEPATHIC_VISIBLE)) { // became telepathically visible
! 2319: if (!(pmap[i][j].flags & DISCOVERED)
! 2320: && !cellHasTerrainFlag(i, j, T_PATHING_BLOCKER)) {
! 2321: rogue.xpxpThisTurn++;
! 2322: }
! 2323:
! 2324: pmap[i][j].flags &= ~STABLE_MEMORY;
! 2325: if (refreshDisplay) {
! 2326: refreshDungeonCell(i, j);
! 2327: }
! 2328: } else if (playerCanSeeOrSense(i, j)
! 2329: && (tmap[i][j].light[0] != tmap[i][j].oldLight[0] ||
! 2330: tmap[i][j].light[1] != tmap[i][j].oldLight[1] ||
! 2331: tmap[i][j].light[2] != tmap[i][j].oldLight[2])) { // if the cell's light color changed this move
! 2332:
! 2333: if (refreshDisplay) {
! 2334: refreshDungeonCell(i, j);
! 2335: }
! 2336: } else if (updateDancingTerrain
! 2337: && playerCanSee(i, j)
! 2338: && (!rogue.automationActive || !(rogue.playerTurnNumber % 5))
! 2339: && ((tileCatalog[pmap[i][j].layers[DUNGEON]].backColor) && tileCatalog[pmap[i][j].layers[DUNGEON]].backColor->colorDances
! 2340: || (tileCatalog[pmap[i][j].layers[DUNGEON]].foreColor) && tileCatalog[pmap[i][j].layers[DUNGEON]].foreColor->colorDances
! 2341: || (tileCatalog[pmap[i][j].layers[LIQUID]].backColor) && tileCatalog[pmap[i][j].layers[LIQUID]].backColor->colorDances
! 2342: || (tileCatalog[pmap[i][j].layers[LIQUID]].foreColor) && tileCatalog[pmap[i][j].layers[LIQUID]].foreColor->colorDances
! 2343: || (tileCatalog[pmap[i][j].layers[SURFACE]].backColor) && tileCatalog[pmap[i][j].layers[SURFACE]].backColor->colorDances
! 2344: || (tileCatalog[pmap[i][j].layers[SURFACE]].foreColor) && tileCatalog[pmap[i][j].layers[SURFACE]].foreColor->colorDances
! 2345: || (tileCatalog[pmap[i][j].layers[GAS]].backColor) && tileCatalog[pmap[i][j].layers[GAS]].backColor->colorDances
! 2346: || (tileCatalog[pmap[i][j].layers[GAS]].foreColor) && tileCatalog[pmap[i][j].layers[GAS]].foreColor->colorDances
! 2347: || player.status[STATUS_HALLUCINATING])) {
! 2348:
! 2349: pmap[i][j].flags &= ~STABLE_MEMORY;
! 2350: if (refreshDisplay) {
! 2351: refreshDungeonCell(i, j);
! 2352: }
! 2353: }
! 2354: }
! 2355: }
! 2356: restoreRNG;
! 2357: }
! 2358:
! 2359: // Octants: //
! 2360: // \7|8/ //
! 2361: // 6\|/1 //
! 2362: // --@-- //
! 2363: // 5/|\2 //
! 2364: // /4|3\ //
! 2365:
! 2366: void betweenOctant1andN(short *x, short *y, short x0, short y0, short n) {
! 2367: short x1 = *x, y1 = *y;
! 2368: short dx = x1 - x0, dy = y1 - y0;
! 2369: switch (n) {
! 2370: case 1:
! 2371: return;
! 2372: case 2:
! 2373: *y = y0 - dy;
! 2374: return;
! 2375: case 5:
! 2376: *x = x0 - dx;
! 2377: *y = y0 - dy;
! 2378: return;
! 2379: case 6:
! 2380: *x = x0 - dx;
! 2381: return;
! 2382: case 8:
! 2383: *x = x0 - dy;
! 2384: *y = y0 - dx;
! 2385: return;
! 2386: case 3:
! 2387: *x = x0 - dy;
! 2388: *y = y0 + dx;
! 2389: return;
! 2390: case 7:
! 2391: *x = x0 + dy;
! 2392: *y = y0 - dx;
! 2393: return;
! 2394: case 4:
! 2395: *x = x0 + dy;
! 2396: *y = y0 + dx;
! 2397: return;
! 2398: }
! 2399: }
! 2400:
! 2401: // Returns a boolean grid indicating whether each square is in the field of view of (xLoc, yLoc).
! 2402: // forbiddenTerrain is the set of terrain flags that will block vision (but the blocking cell itself is
! 2403: // illuminated); forbiddenFlags is the set of map flags that will block vision.
! 2404: // If cautiousOnWalls is set, we will not illuminate blocking tiles unless the tile one space closer to the origin
! 2405: // is visible to the player; this is to prevent lights from illuminating a wall when the player is on the other
! 2406: // side of the wall.
! 2407: void getFOVMask(char grid[DCOLS][DROWS], short xLoc, short yLoc, fixpt maxRadius,
! 2408: unsigned long forbiddenTerrain, unsigned long forbiddenFlags, boolean cautiousOnWalls) {
! 2409: short i;
! 2410:
! 2411: for (i=1; i<=8; i++) {
! 2412: scanOctantFOV(grid, xLoc, yLoc, i, maxRadius, 1, LOS_SLOPE_GRANULARITY * -1, 0,
! 2413: forbiddenTerrain, forbiddenFlags, cautiousOnWalls);
! 2414: }
! 2415: }
! 2416:
! 2417: // This is a custom implementation of recursive shadowcasting.
! 2418: void scanOctantFOV(char grid[DCOLS][DROWS], short xLoc, short yLoc, short octant, fixpt maxRadius,
! 2419: short columnsRightFromOrigin, long startSlope, long endSlope, unsigned long forbiddenTerrain,
! 2420: unsigned long forbiddenFlags, boolean cautiousOnWalls) {
! 2421:
! 2422: if (columnsRightFromOrigin * FP_FACTOR >= maxRadius) return;
! 2423:
! 2424: short i, a, b, iStart, iEnd, x, y, x2, y2; // x and y are temporary variables on which we do the octant transform
! 2425: long newStartSlope, newEndSlope;
! 2426: boolean cellObstructed;
! 2427:
! 2428: newStartSlope = startSlope;
! 2429:
! 2430: a = ((LOS_SLOPE_GRANULARITY / -2 + 1) + startSlope * columnsRightFromOrigin) / LOS_SLOPE_GRANULARITY;
! 2431: b = ((LOS_SLOPE_GRANULARITY / -2 + 1) + endSlope * columnsRightFromOrigin) / LOS_SLOPE_GRANULARITY;
! 2432:
! 2433: iStart = min(a, b);
! 2434: iEnd = max(a, b);
! 2435:
! 2436: // restrict vision to a circle of radius maxRadius
! 2437: if ((columnsRightFromOrigin*columnsRightFromOrigin + iEnd*iEnd) >= maxRadius*maxRadius / FP_FACTOR / FP_FACTOR) {
! 2438: return;
! 2439: }
! 2440: if ((columnsRightFromOrigin*columnsRightFromOrigin + iStart*iStart) >= maxRadius*maxRadius / FP_FACTOR / FP_FACTOR) {
! 2441: iStart = (int) (-1 * fp_sqrt((maxRadius*maxRadius / FP_FACTOR) - (columnsRightFromOrigin*columnsRightFromOrigin * FP_FACTOR)) / FP_FACTOR);
! 2442: }
! 2443:
! 2444: x = xLoc + columnsRightFromOrigin;
! 2445: y = yLoc + iStart;
! 2446: betweenOctant1andN(&x, &y, xLoc, yLoc, octant);
! 2447: boolean currentlyLit = coordinatesAreInMap(x, y) && !(cellHasTerrainFlag(x, y, forbiddenTerrain) ||
! 2448: (pmap[x][y].flags & forbiddenFlags));
! 2449: for (i = iStart; i <= iEnd; i++) {
! 2450: x = xLoc + columnsRightFromOrigin;
! 2451: y = yLoc + i;
! 2452: betweenOctant1andN(&x, &y, xLoc, yLoc, octant);
! 2453: if (!coordinatesAreInMap(x, y)) {
! 2454: // We're off the map -- here there be memory corruption.
! 2455: continue;
! 2456: }
! 2457: cellObstructed = (cellHasTerrainFlag(x, y, forbiddenTerrain) || (pmap[x][y].flags & forbiddenFlags));
! 2458: // if we're cautious on walls and this is a wall:
! 2459: if (cautiousOnWalls && cellObstructed) {
! 2460: // (x2, y2) is the tile one space closer to the origin from the tile we're on:
! 2461: x2 = xLoc + columnsRightFromOrigin - 1;
! 2462: y2 = yLoc + i;
! 2463: if (i < 0) {
! 2464: y2++;
! 2465: } else if (i > 0) {
! 2466: y2--;
! 2467: }
! 2468: betweenOctant1andN(&x2, &y2, xLoc, yLoc, octant);
! 2469:
! 2470: if (pmap[x2][y2].flags & IN_FIELD_OF_VIEW) {
! 2471: // previous tile is visible, so illuminate
! 2472: grid[x][y] = 1;
! 2473: }
! 2474: } else {
! 2475: // illuminate
! 2476: grid[x][y] = 1;
! 2477: }
! 2478: if (!cellObstructed && !currentlyLit) { // next column slope starts here
! 2479: newStartSlope = (long int) ((LOS_SLOPE_GRANULARITY * (i) - LOS_SLOPE_GRANULARITY / 2) / (columnsRightFromOrigin * 2 + 1) * 2);
! 2480: currentlyLit = true;
! 2481: } else if (cellObstructed && currentlyLit) { // next column slope ends here
! 2482: newEndSlope = (long int) ((LOS_SLOPE_GRANULARITY * (i) - LOS_SLOPE_GRANULARITY / 2)
! 2483: / (columnsRightFromOrigin * 2 - 1) * 2);
! 2484: if (newStartSlope <= newEndSlope) {
! 2485: // run next column
! 2486: scanOctantFOV(grid, xLoc, yLoc, octant, maxRadius, columnsRightFromOrigin + 1, newStartSlope, newEndSlope,
! 2487: forbiddenTerrain, forbiddenFlags, cautiousOnWalls);
! 2488: }
! 2489: currentlyLit = false;
! 2490: }
! 2491: }
! 2492: if (currentlyLit) { // got to the bottom of the scan while lit
! 2493: newEndSlope = endSlope;
! 2494: if (newStartSlope <= newEndSlope) {
! 2495: // run next column
! 2496: scanOctantFOV(grid, xLoc, yLoc, octant, maxRadius, columnsRightFromOrigin + 1, newStartSlope, newEndSlope,
! 2497: forbiddenTerrain, forbiddenFlags, cautiousOnWalls);
! 2498: }
! 2499: }
! 2500: }
! 2501:
! 2502: void addScentToCell(short x, short y, short distance) {
! 2503: unsigned short value;
! 2504: if (!cellHasTerrainFlag(x, y, T_OBSTRUCTS_SCENT) || !cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)) {
! 2505: value = rogue.scentTurnNumber - distance;
! 2506: scentMap[x][y] = max(value, (unsigned short) scentMap[x][y]);
! 2507: }
! 2508: }
CVSweb