[BACK]Return to Movement.c CVS log [TXT][DIR] Up to [contributed] / brogue-ce / src / brogue

Annotation of brogue-ce/src/brogue/Movement.c, Revision 1.1.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