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

Annotation of brogue-ce/src/brogue/Time.c, Revision 1.1

1.1     ! rubenllo    1: /*
        !             2:  *  Time.c
        !             3:  *  Brogue
        !             4:  *
        !             5:  *  Created by Brian Walker on 6/21/13.
        !             6:  *  Copyright 2013. 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 exposeCreatureToFire(creature *monst) {
        !            28:     char buf[COLS], buf2[COLS];
        !            29:     if ((monst->bookkeepingFlags & MB_IS_DYING)
        !            30:         || monst->status[STATUS_IMMUNE_TO_FIRE]
        !            31:         || (monst->info.flags & MONST_INVULNERABLE)
        !            32:         || (monst->bookkeepingFlags & MB_SUBMERGED)
        !            33:         || ((!monst->status[STATUS_LEVITATING]) && cellHasTMFlag(monst->xLoc, monst->yLoc, TM_EXTINGUISHES_FIRE))) {
        !            34:         return;
        !            35:     }
        !            36:     if (monst->status[STATUS_BURNING] == 0) {
        !            37:         if (monst == &player) {
        !            38:             rogue.minersLight.lightColor = &fireForeColor;
        !            39:             player.info.foreColor = &torchLightColor;
        !            40:             refreshDungeonCell(player.xLoc, player.yLoc);
        !            41:             //updateVision(); // this screws up the firebolt visual effect by erasing it while a message is displayed
        !            42:             combatMessage("you catch fire", &badMessageColor);
        !            43:         } else if (canDirectlySeeMonster(monst)) {
        !            44:             monsterName(buf, monst, true);
        !            45:             sprintf(buf2, "%s catches fire", buf);
        !            46:             combatMessage(buf2, messageColorFromVictim(monst));
        !            47:         }
        !            48:     }
        !            49:     monst->status[STATUS_BURNING] = monst->maxStatus[STATUS_BURNING] = max(monst->status[STATUS_BURNING], 7);
        !            50: }
        !            51:
        !            52: void updateFlavorText() {
        !            53:     char buf[DCOLS * 3];
        !            54:     if (rogue.disturbed && !rogue.gameHasEnded) {
        !            55:         if (rogue.armor
        !            56:             && (rogue.armor->flags & ITEM_RUNIC)
        !            57:             && rogue.armor->enchant2 == A_RESPIRATION
        !            58:             && tileCatalog[pmap[player.xLoc][player.yLoc].layers[highestPriorityLayer(player.xLoc, player.yLoc, false)]].flags & T_RESPIRATION_IMMUNITIES) {
        !            59:
        !            60:             flavorMessage("A pocket of cool, clean air swirls around you.");
        !            61:         } else if (player.status[STATUS_LEVITATING]) {
        !            62:             describeLocation(buf, player.xLoc, player.yLoc);
        !            63:             flavorMessage(buf);
        !            64:         } else {
        !            65:             flavorMessage(tileFlavor(player.xLoc, player.yLoc));
        !            66:         }
        !            67:     }
        !            68: }
        !            69:
        !            70: void updatePlayerUnderwaterness() {
        !            71:     if (rogue.inWater) {
        !            72:         if (!cellHasTerrainFlag(player.xLoc, player.yLoc, T_IS_DEEP_WATER) || player.status[STATUS_LEVITATING]
        !            73:             || cellHasTerrainFlag(player.xLoc, player.yLoc, (T_ENTANGLES | T_OBSTRUCTS_PASSABILITY))) {
        !            74:
        !            75:             rogue.inWater = false;
        !            76:             updateMinersLightRadius();
        !            77:             updateVision(true);
        !            78:             displayLevel();
        !            79:         }
        !            80:     } else {
        !            81:         if (cellHasTerrainFlag(player.xLoc, player.yLoc, T_IS_DEEP_WATER) && !player.status[STATUS_LEVITATING]
        !            82:             && !cellHasTerrainFlag(player.xLoc, player.yLoc, (T_ENTANGLES | T_OBSTRUCTS_PASSABILITY))) {
        !            83:
        !            84:             rogue.inWater = true;
        !            85:             updateMinersLightRadius();
        !            86:             updateVision(true);
        !            87:             displayLevel();
        !            88:         }
        !            89:     }
        !            90: }
        !            91:
        !            92: boolean monsterShouldFall(creature *monst) {
        !            93:     return (!(monst->status[STATUS_LEVITATING])
        !            94:             && cellHasTerrainFlag(monst->xLoc, monst->yLoc, T_AUTO_DESCENT)
        !            95:             && !cellHasTerrainFlag(monst->xLoc, monst->yLoc, T_ENTANGLES | T_OBSTRUCTS_PASSABILITY)
        !            96:             && !(monst->bookkeepingFlags & MB_PREPLACED));
        !            97: }
        !            98:
        !            99: // Called at least every 100 ticks; may be called more frequently.
        !           100: void applyInstantTileEffectsToCreature(creature *monst) {
        !           101:     char buf[COLS], buf2[COLS], buf3[COLS];
        !           102:     short *x = &(monst->xLoc), *y = &(monst->yLoc), damage;
        !           103:     enum dungeonLayers layer;
        !           104:     item *theItem;
        !           105:
        !           106:     if (monst->bookkeepingFlags & MB_IS_DYING) {
        !           107:         return; // the monster is already dead.
        !           108:     }
        !           109:
        !           110:     if (monst == &player) {
        !           111:         if (!player.status[STATUS_LEVITATING]) {
        !           112:             pmap[*x][*y].flags |= KNOWN_TO_BE_TRAP_FREE;
        !           113:         }
        !           114:     } else if (!player.status[STATUS_HALLUCINATING]
        !           115:                && !monst->status[STATUS_LEVITATING]
        !           116:                && canSeeMonster(monst)
        !           117:                && !(cellHasTerrainFlag(*x, *y, T_IS_DF_TRAP))) {
        !           118:         pmap[*x][*y].flags |= KNOWN_TO_BE_TRAP_FREE;
        !           119:     }
        !           120:
        !           121:     // You will discover the secrets of any tile you stand on.
        !           122:     if (monst == &player
        !           123:         && !(monst->status[STATUS_LEVITATING])
        !           124:         && cellHasTMFlag(*x, *y, TM_IS_SECRET)
        !           125:         && playerCanSee(*x, *y)) {
        !           126:
        !           127:         discover(*x, *y);
        !           128:     }
        !           129:
        !           130:     // Submerged monsters in terrain that doesn't permit submersion should immediately surface.
        !           131:     if ((monst->bookkeepingFlags & MB_SUBMERGED) && !cellHasTMFlag(*x, *y, TM_ALLOWS_SUBMERGING)) {
        !           132:         monst->bookkeepingFlags &= ~MB_SUBMERGED;
        !           133:     }
        !           134:
        !           135:     // Visual effect for submersion in water.
        !           136:     if (monst == &player) {
        !           137:         updatePlayerUnderwaterness();
        !           138:     }
        !           139:
        !           140:     // Obstructed krakens can't seize their prey.
        !           141:     if ((monst->bookkeepingFlags & MB_SEIZING)
        !           142:         && (cellHasTerrainFlag(*x, *y, T_OBSTRUCTS_PASSABILITY))
        !           143:         && !(monst->info.flags & MONST_ATTACKABLE_THRU_WALLS)) {
        !           144:
        !           145:         monst->bookkeepingFlags &= ~MB_SEIZING;
        !           146:     }
        !           147:
        !           148:     // Creatures plunge into chasms and through trap doors.
        !           149:     if (monsterShouldFall(monst)) {
        !           150:         if (monst == &player) {
        !           151:             // player falling takes place at the end of the turn
        !           152:             if (!(monst->bookkeepingFlags & MB_IS_FALLING)) {
        !           153:                 monst->bookkeepingFlags |= MB_IS_FALLING;
        !           154:             }
        !           155:             return;
        !           156:         } else { // it's a monster
        !           157:             monst->bookkeepingFlags |= MB_IS_FALLING; // handled at end of turn
        !           158:         }
        !           159:     }
        !           160:
        !           161:     // lava
        !           162:     if (!(monst->status[STATUS_LEVITATING])
        !           163:         && !(monst->status[STATUS_IMMUNE_TO_FIRE])
        !           164:         && !(monst->info.flags & MONST_INVULNERABLE)
        !           165:         && !cellHasTerrainFlag(*x, *y, (T_ENTANGLES | T_OBSTRUCTS_PASSABILITY))
        !           166:         && !cellHasTMFlag(*x, *y, TM_EXTINGUISHES_FIRE)
        !           167:         && cellHasTerrainFlag(*x, *y, T_LAVA_INSTA_DEATH)) {
        !           168:
        !           169:         if (monst == &player) {
        !           170:             sprintf(buf, "you plunge into %s!",
        !           171:                     tileCatalog[pmap[*x][*y].layers[layerWithFlag(*x, *y, T_LAVA_INSTA_DEATH)]].description);
        !           172:             message(buf, true);
        !           173:             sprintf(buf, "Killed by %s",
        !           174:                     tileCatalog[pmap[*x][*y].layers[layerWithFlag(*x, *y, T_LAVA_INSTA_DEATH)]].description);
        !           175:             gameOver(buf, true);
        !           176:             return;
        !           177:         } else { // it's a monster
        !           178:             if (canSeeMonster(monst)) {
        !           179:                 monsterName(buf, monst, true);
        !           180:                 sprintf(buf2, "%s is consumed by the %s instantly!", buf,
        !           181:                         tileCatalog[pmap[*x][*y].layers[layerWithFlag(*x, *y, T_LAVA_INSTA_DEATH)]].description);
        !           182:                 messageWithColor(buf2, messageColorFromVictim(monst), false);
        !           183:             }
        !           184:             killCreature(monst, false);
        !           185:             spawnDungeonFeature(*x, *y, &(dungeonFeatureCatalog[DF_CREATURE_FIRE]), true, false);
        !           186:             refreshDungeonCell(*x, *y);
        !           187:             return;
        !           188:         }
        !           189:     }
        !           190:
        !           191:     // Water puts out fire.
        !           192:     if (cellHasTMFlag(*x, *y, TM_EXTINGUISHES_FIRE)
        !           193:         && monst->status[STATUS_BURNING]
        !           194:         && !monst->status[STATUS_LEVITATING]
        !           195:         && !(monst->info.flags & MONST_ATTACKABLE_THRU_WALLS)
        !           196:         && !(monst->info.flags & MONST_FIERY)) {
        !           197:         extinguishFireOnCreature(monst);
        !           198:     }
        !           199:
        !           200:     // If you see a monster use a secret door, you discover it.
        !           201:     if (playerCanSee(*x, *y)
        !           202:         && cellHasTMFlag(*x, *y, TM_IS_SECRET)
        !           203:         && (cellHasTerrainFlag(*x, *y, T_OBSTRUCTS_PASSABILITY))) {
        !           204:         discover(*x, *y);
        !           205:     }
        !           206:
        !           207:     // Pressure plates.
        !           208:     if (!(monst->status[STATUS_LEVITATING])
        !           209:         && !(monst->bookkeepingFlags & MB_SUBMERGED)
        !           210:         && (!cellHasTMFlag(*x, *y, TM_ALLOWS_SUBMERGING) || !(monst->info.flags & MONST_SUBMERGES))
        !           211:         && cellHasTerrainFlag(*x, *y, T_IS_DF_TRAP)
        !           212:         && !(pmap[*x][*y].flags & PRESSURE_PLATE_DEPRESSED)) {
        !           213:
        !           214:         pmap[*x][*y].flags |= PRESSURE_PLATE_DEPRESSED;
        !           215:         if (playerCanSee(*x, *y) && cellHasTMFlag(*x, *y, TM_IS_SECRET)) {
        !           216:             discover(*x, *y);
        !           217:             refreshDungeonCell(*x, *y);
        !           218:         }
        !           219:         if (canSeeMonster(monst)) {
        !           220:             monsterName(buf, monst, true);
        !           221:             sprintf(buf2, "a pressure plate clicks underneath %s!", buf);
        !           222:             message(buf2, true);
        !           223:         } else if (playerCanSee(*x, *y)) {
        !           224:             // usually means an invisible monster
        !           225:             message("a pressure plate clicks!", false);
        !           226:         }
        !           227:         for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
        !           228:             if (tileCatalog[pmap[*x][*y].layers[layer]].flags & T_IS_DF_TRAP) {
        !           229:                 spawnDungeonFeature(*x, *y, &(dungeonFeatureCatalog[tileCatalog[pmap[*x][*y].layers[layer]].fireType]), true, false);
        !           230:                 promoteTile(*x, *y, layer, false);
        !           231:             }
        !           232:         }
        !           233:     }
        !           234:
        !           235:     if (cellHasTMFlag(*x, *y, TM_PROMOTES_ON_STEP)) { // flying creatures activate too
        !           236:         // Because this uses no pressure plate to keep track of whether it's already depressed,
        !           237:         // it will trigger every time this function is called while the monster or player is on the tile.
        !           238:         // Because this function can be called several times per turn, multiple promotions can
        !           239:         // happen unpredictably if the tile does not promote to a tile without the T_PROMOTES_ON_STEP
        !           240:         // attribute. That's acceptable for some effects, e.g. doors opening,
        !           241:         // but not for others, e.g. magical glyphs activating.
        !           242:         for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
        !           243:             if (tileCatalog[pmap[*x][*y].layers[layer]].mechFlags & TM_PROMOTES_ON_STEP) {
        !           244:                 promoteTile(*x, *y, layer, false);
        !           245:             }
        !           246:         }
        !           247:     }
        !           248:
        !           249:     if (cellHasTMFlag(*x, *y, TM_PROMOTES_ON_PLAYER_ENTRY) && monst == &player) {
        !           250:         // Subject to same caveats as T_PROMOTES_ON_STEP above.
        !           251:         for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
        !           252:             if (tileCatalog[pmap[*x][*y].layers[layer]].mechFlags & TM_PROMOTES_ON_PLAYER_ENTRY) {
        !           253:                 promoteTile(*x, *y, layer, false);
        !           254:             }
        !           255:         }
        !           256:     }
        !           257:
        !           258:     if (cellHasTMFlag(*x, *y, TM_PROMOTES_ON_SACRIFICE_ENTRY)
        !           259:         && monst->machineHome == pmap[*x][*y].machineNumber
        !           260:         && (monst->bookkeepingFlags & MB_MARKED_FOR_SACRIFICE)) {
        !           261:         // Subject to same caveats as T_PROMOTES_ON_STEP above.
        !           262:         for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
        !           263:             if (tileCatalog[pmap[*x][*y].layers[layer]].mechFlags & TM_PROMOTES_ON_SACRIFICE_ENTRY) {
        !           264:                 promoteTile(*x, *y, layer, false);
        !           265:             }
        !           266:         }
        !           267:     }
        !           268:
        !           269:     // spiderwebs
        !           270:     if (cellHasTerrainFlag(*x, *y, T_ENTANGLES) && !monst->status[STATUS_STUCK]
        !           271:         && !(monst->info.flags & (MONST_IMMUNE_TO_WEBS | MONST_INVULNERABLE))
        !           272:         && !(monst->bookkeepingFlags & MB_SUBMERGED)) {
        !           273:
        !           274:         monst->status[STATUS_STUCK] = monst->maxStatus[STATUS_STUCK] = rand_range(3, 7);
        !           275:         if (monst == &player) {
        !           276:             if (!rogue.automationActive) {
        !           277:                 // Don't interrupt exploration with this message.
        !           278:                 sprintf(buf2, "you are stuck fast in %s!",
        !           279:                         tileCatalog[pmap[*x][*y].layers[layerWithFlag(*x, *y, T_ENTANGLES)]].description);
        !           280:                 message(buf2, false);
        !           281:             }
        !           282:         } else if (canDirectlySeeMonster(monst)) { // it's a monster
        !           283:             if (!rogue.automationActive) {
        !           284:                 monsterName(buf, monst, true);
        !           285:                 sprintf(buf2, "%s is stuck fast in %s!", buf,
        !           286:                         tileCatalog[pmap[*x][*y].layers[layerWithFlag(*x, *y, T_ENTANGLES)]].description);
        !           287:                 message(buf2, false);
        !           288:             }
        !           289:         }
        !           290:     }
        !           291:
        !           292:     // explosions
        !           293:     if (cellHasTerrainFlag(*x, *y, T_CAUSES_EXPLOSIVE_DAMAGE) && !monst->status[STATUS_EXPLOSION_IMMUNITY]
        !           294:         && !(monst->bookkeepingFlags & MB_SUBMERGED)) {
        !           295:         damage = rand_range(15, 20);
        !           296:         damage = max(damage, monst->info.maxHP / 2);
        !           297:         monst->status[STATUS_EXPLOSION_IMMUNITY] = 5;
        !           298:         if (monst == &player) {
        !           299:             rogue.disturbed = true;
        !           300:             for (layer = 0; layer < NUMBER_TERRAIN_LAYERS && !(tileCatalog[pmap[*x][*y].layers[layer]].flags & T_CAUSES_EXPLOSIVE_DAMAGE); layer++);
        !           301:             message(tileCatalog[pmap[*x][*y].layers[layer]].flavorText, false);
        !           302:             if (rogue.armor && (rogue.armor->flags & ITEM_RUNIC) && rogue.armor->enchant2 == A_DAMPENING) {
        !           303:                 itemName(rogue.armor, buf2, false, false, NULL);
        !           304:                 sprintf(buf, "Your %s pulses and absorbs the damage.", buf2);
        !           305:                 messageWithColor(buf, &goodMessageColor, false);
        !           306:                 autoIdentify(rogue.armor);
        !           307:             } else if (inflictDamage(NULL, &player, damage, &yellow, false)) {
        !           308:                 strcpy(buf2, tileCatalog[pmap[*x][*y].layers[layerWithFlag(*x, *y, T_CAUSES_EXPLOSIVE_DAMAGE)]].description);
        !           309:                 sprintf(buf, "Killed by %s", buf2);
        !           310:                 gameOver(buf, true);
        !           311:                 return;
        !           312:             }
        !           313:         } else { // it's a monster
        !           314:             if (monst->creatureState == MONSTER_SLEEPING) {
        !           315:                 monst->creatureState = MONSTER_TRACKING_SCENT;
        !           316:             }
        !           317:             monsterName(buf, monst, true);
        !           318:
        !           319:             // Get explosive layer before damage in case a death DF replaces the explosion
        !           320:             strcpy(buf3, tileCatalog[pmap[*x][*y].layers[layerWithFlag(*x, *y, T_CAUSES_EXPLOSIVE_DAMAGE)]].description);
        !           321:             if (inflictDamage(NULL, monst, damage, &yellow, false)) {
        !           322:                 // if killed
        !           323:                 sprintf(buf2, "%s %s %s.", buf,
        !           324:                         (monst->info.flags & MONST_INANIMATE) ? "is destroyed by" : "dies in",
        !           325:                         buf3);
        !           326:                 messageWithColor(buf2, messageColorFromVictim(monst), false);
        !           327:                 refreshDungeonCell(*x, *y);
        !           328:                 return;
        !           329:             } else {
        !           330:                 // if survived
        !           331:                 sprintf(buf2, "%s engulfs %s.",
        !           332:                         tileCatalog[pmap[*x][*y].layers[layerWithFlag(*x, *y, T_CAUSES_EXPLOSIVE_DAMAGE)]].description, buf);
        !           333:                 messageWithColor(buf2, messageColorFromVictim(monst), false);
        !           334:             }
        !           335:         }
        !           336:     }
        !           337:
        !           338:     // Toxic gases!
        !           339:     // If it's the player, and he's wearing armor of respiration, then no effect from toxic gases.
        !           340:     if (monst == &player
        !           341:         && cellHasTerrainFlag(*x, *y, T_RESPIRATION_IMMUNITIES)
        !           342:         && rogue.armor
        !           343:         && (rogue.armor->flags & ITEM_RUNIC)
        !           344:         && rogue.armor->enchant2 == A_RESPIRATION) {
        !           345:         if (!(rogue.armor->flags & ITEM_RUNIC_IDENTIFIED)) {
        !           346:             message("Your armor trembles and a pocket of clean air swirls around you.", false);
        !           347:             autoIdentify(rogue.armor);
        !           348:         }
        !           349:     } else {
        !           350:
        !           351:         // zombie gas
        !           352:         if (cellHasTerrainFlag(*x, *y, T_CAUSES_NAUSEA)
        !           353:             && !(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))
        !           354:             && !(monst->bookkeepingFlags & MB_SUBMERGED)) {
        !           355:             if (monst == &player) {
        !           356:                 rogue.disturbed = true;
        !           357:             }
        !           358:             if (canDirectlySeeMonster(monst) && !(monst->status[STATUS_NAUSEOUS])) {
        !           359:                 if (monst->creatureState == MONSTER_SLEEPING) {
        !           360:                     monst->creatureState = MONSTER_TRACKING_SCENT;
        !           361:                 }
        !           362:                 flashMonster(monst, &brown, 100);
        !           363:                 monsterName(buf, monst, true);
        !           364:                 sprintf(buf2, "%s choke%s and gag%s on the overpowering stench of decay.", buf,
        !           365:                         (monst == &player ? "": "s"), (monst == &player ? "": "s"));
        !           366:                 message(buf2, false);
        !           367:             }
        !           368:             monst->status[STATUS_NAUSEOUS] = monst->maxStatus[STATUS_NAUSEOUS] = max(monst->status[STATUS_NAUSEOUS], 20);
        !           369:         }
        !           370:
        !           371:         // confusion gas
        !           372:         if (cellHasTerrainFlag(*x, *y, T_CAUSES_CONFUSION) && !(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) {
        !           373:             if (monst == &player) {
        !           374:                 rogue.disturbed = true;
        !           375:             }
        !           376:             if (canDirectlySeeMonster(monst) && !(monst->status[STATUS_CONFUSED])) {
        !           377:                 if (monst->creatureState == MONSTER_SLEEPING) {
        !           378:                     monst->creatureState = MONSTER_TRACKING_SCENT;
        !           379:                 }
        !           380:                 flashMonster(monst, &confusionGasColor, 100);
        !           381:                 monsterName(buf, monst, true);
        !           382:                 sprintf(buf2, "%s %s very confused!", buf, (monst == &player ? "feel": "looks"));
        !           383:                 message(buf2, false);
        !           384:             }
        !           385:             monst->status[STATUS_CONFUSED] = monst->maxStatus[STATUS_CONFUSED] = max(monst->status[STATUS_CONFUSED], 25);
        !           386:         }
        !           387:
        !           388:         // paralysis gas
        !           389:         if (cellHasTerrainFlag(*x, *y, T_CAUSES_PARALYSIS)
        !           390:             && !(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))
        !           391:             && !(monst->bookkeepingFlags & MB_SUBMERGED)) {
        !           392:
        !           393:             if (canDirectlySeeMonster(monst) && !monst->status[STATUS_PARALYZED]) {
        !           394:                 flashMonster(monst, &pink, 100);
        !           395:                 monsterName(buf, monst, true);
        !           396:                 sprintf(buf2, "%s %s paralyzed!", buf, (monst == &player ? "are": "is"));
        !           397:                 message(buf2, (monst == &player));
        !           398:             }
        !           399:             monst->status[STATUS_PARALYZED] = monst->maxStatus[STATUS_PARALYZED] = max(monst->status[STATUS_PARALYZED], 20);
        !           400:             if (monst == &player) {
        !           401:                 rogue.disturbed = true;
        !           402:             }
        !           403:         }
        !           404:     }
        !           405:
        !           406:     // poisonous lichen
        !           407:     if (cellHasTerrainFlag(*x, *y, T_CAUSES_POISON)
        !           408:         && !(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))
        !           409:         && !monst->status[STATUS_LEVITATING]) {
        !           410:
        !           411:         if (monst == &player && !player.status[STATUS_POISONED]) {
        !           412:             rogue.disturbed = true;
        !           413:         }
        !           414:         if (canDirectlySeeMonster(monst) && !(monst->status[STATUS_POISONED])) {
        !           415:             if (monst->creatureState == MONSTER_SLEEPING) {
        !           416:                 monst->creatureState = MONSTER_TRACKING_SCENT;
        !           417:             }
        !           418:             flashMonster(monst, &green, 100);
        !           419:             monsterName(buf, monst, true);
        !           420:             sprintf(buf2, "the lichen's grasping tendrils poison %s.", buf);
        !           421:             messageWithColor(buf2, messageColorFromVictim(monst), false);
        !           422:         }
        !           423:         damage = max(0, 5 - monst->status[STATUS_POISONED]);
        !           424:         addPoison(monst, damage, 0); // Lichen doesn't increase poison concentration above 1.
        !           425:     }
        !           426:
        !           427:     // fire
        !           428:     if (cellHasTerrainFlag(*x, *y, T_IS_FIRE)) {
        !           429:         exposeCreatureToFire(monst);
        !           430:     } else if (cellHasTerrainFlag(*x, *y, T_IS_FLAMMABLE)
        !           431:             // We should only expose to fire if it is flammable and not on fire. However, when
        !           432:             // gas burns, it only sets the volume to 0 and doesn't clear the layer (for visual
        !           433:             // reasons). This can cause crashes if the fire tile fails to spawn, so we also exclude it.
        !           434:                && !(pmap[*x][*y].layers[GAS] != NOTHING && pmap[*x][*y].volume == 0)
        !           435:                && !cellHasTerrainFlag(*x, *y, T_IS_FIRE)
        !           436:                && monst->status[STATUS_BURNING]
        !           437:                && !(monst->bookkeepingFlags & (MB_SUBMERGED | MB_IS_FALLING))) {
        !           438:         exposeTileToFire(*x, *y, true);
        !           439:     }
        !           440:
        !           441:     // keys
        !           442:     if (cellHasTMFlag(*x, *y, TM_PROMOTES_WITH_KEY) && (theItem = keyOnTileAt(*x, *y))) {
        !           443:         useKeyAt(theItem, *x, *y);
        !           444:     }
        !           445: }
        !           446:
        !           447: void applyGradualTileEffectsToCreature(creature *monst, short ticks) {
        !           448:     short itemCandidates, randItemIndex;
        !           449:     short x = monst->xLoc, y = monst->yLoc, damage;
        !           450:     char buf[COLS * 5], buf2[COLS * 3];
        !           451:     item *theItem;
        !           452:     enum dungeonLayers layer;
        !           453:
        !           454:     if (!(monst->status[STATUS_LEVITATING])
        !           455:         && cellHasTerrainFlag(x, y, T_IS_DEEP_WATER)
        !           456:         && !cellHasTerrainFlag(x, y, (T_ENTANGLES | T_OBSTRUCTS_PASSABILITY))
        !           457:         && !(monst->info.flags & MONST_IMMUNE_TO_WATER)) {
        !           458:         if (monst == &player) {
        !           459:             if (!(pmap[x][y].flags & HAS_ITEM) && rand_percent(ticks * 50 / 100)) {
        !           460:                 itemCandidates = numberOfMatchingPackItems(ALL_ITEMS, 0, (ITEM_EQUIPPED), false);
        !           461:                 if (itemCandidates) {
        !           462:                     randItemIndex = rand_range(1, itemCandidates);
        !           463:                     for (theItem = packItems->nextItem; theItem != NULL; theItem = theItem->nextItem) {
        !           464:                         if (!(theItem->flags & (ITEM_EQUIPPED))) {
        !           465:                             if (randItemIndex == 1) {
        !           466:                                 break;
        !           467:                             } else {
        !           468:                                 randItemIndex--;
        !           469:                             }
        !           470:                         }
        !           471:                     }
        !           472:                     theItem = dropItem(theItem);
        !           473:                     if (theItem) {
        !           474:                         itemName(theItem, buf2, false, true, NULL);
        !           475:                         sprintf(buf, "%s float%s away in the current!",
        !           476:                                 buf2,
        !           477:                                 (theItem->quantity == 1 ? "s" : ""));
        !           478:                         messageWithColor(buf, &itemMessageColor, false);
        !           479:                     }
        !           480:                 }
        !           481:             }
        !           482:         } else if (monst->carriedItem && !(pmap[x][y].flags & HAS_ITEM) && rand_percent(ticks * 50 / 100)) { // it's a monster with an item
        !           483:             makeMonsterDropItem(monst);
        !           484:         }
        !           485:     }
        !           486:
        !           487:     if (cellHasTerrainFlag(x, y, T_CAUSES_DAMAGE)
        !           488:         && !(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))
        !           489:         && !(monst->bookkeepingFlags & MB_SUBMERGED)) {
        !           490:
        !           491:         damage = (monst->info.maxHP / 15) * ticks / 100;
        !           492:         damage = max(1, damage);
        !           493:         for (layer = 0; layer < NUMBER_TERRAIN_LAYERS && !(tileCatalog[pmap[x][y].layers[layer]].flags & T_CAUSES_DAMAGE); layer++);
        !           494:         if (monst == &player) {
        !           495:             if (rogue.armor && (rogue.armor->flags & ITEM_RUNIC) && rogue.armor->enchant2 == A_RESPIRATION) {
        !           496:                 if (!(rogue.armor->flags & ITEM_RUNIC_IDENTIFIED)) {
        !           497:                     message("Your armor trembles and a pocket of clean air swirls around you.", false);
        !           498:                     autoIdentify(rogue.armor);
        !           499:                 }
        !           500:             } else {
        !           501:                 rogue.disturbed = true;
        !           502:                 messageWithColor(tileCatalog[pmap[x][y].layers[layer]].flavorText, &badMessageColor, false);
        !           503:                 if (inflictDamage(NULL, &player, damage, tileCatalog[pmap[x][y].layers[layer]].backColor, true)) {
        !           504:                     sprintf(buf, "Killed by %s", tileCatalog[pmap[x][y].layers[layer]].description);
        !           505:                     gameOver(buf, true);
        !           506:                     return;
        !           507:                 }
        !           508:             }
        !           509:         } else { // it's a monster
        !           510:             if (monst->creatureState == MONSTER_SLEEPING) {
        !           511:                 monst->creatureState = MONSTER_TRACKING_SCENT;
        !           512:             }
        !           513:             if (inflictDamage(NULL, monst, damage, tileCatalog[pmap[x][y].layers[layer]].backColor, true)) {
        !           514:                 if (canSeeMonster(monst)) {
        !           515:                     monsterName(buf, monst, true);
        !           516:                     sprintf(buf2, "%s dies.", buf);
        !           517:                     messageWithColor(buf2, messageColorFromVictim(monst), false);
        !           518:                 }
        !           519:                 refreshDungeonCell(x, y);
        !           520:                 return;
        !           521:             }
        !           522:         }
        !           523:     }
        !           524:
        !           525:     if (cellHasTerrainFlag(x, y, T_CAUSES_HEALING)
        !           526:         && !(monst->info.flags & MONST_INANIMATE)
        !           527:         && !(monst->bookkeepingFlags & MB_SUBMERGED)) {
        !           528:
        !           529:         damage = (monst->info.maxHP / 15) * ticks / 100;
        !           530:         damage = max(1, damage);
        !           531:         if (monst->currentHP < monst->info.maxHP) {
        !           532:             monst->currentHP = min(monst->currentHP + damage, monst->info.maxHP);
        !           533:             if (monst == &player) {
        !           534:                 messageWithColor("you feel much better.", &goodMessageColor, false);
        !           535:             }
        !           536:         }
        !           537:     }
        !           538: }
        !           539:
        !           540: void updateClairvoyance() {
        !           541:     short i, j, clairvoyanceRadius, dx, dy;
        !           542:     boolean cursed;
        !           543:     unsigned long cFlags;
        !           544:
        !           545:     for (i=0; i<DCOLS; i++) {
        !           546:         for (j=0; j<DROWS; j++) {
        !           547:
        !           548:             pmap[i][j].flags &= ~WAS_CLAIRVOYANT_VISIBLE;
        !           549:
        !           550:             if (pmap[i][j].flags & CLAIRVOYANT_VISIBLE) {
        !           551:                 pmap[i][j].flags |= WAS_CLAIRVOYANT_VISIBLE;
        !           552:             }
        !           553:
        !           554:             pmap[i][j].flags &= ~(CLAIRVOYANT_VISIBLE | CLAIRVOYANT_DARKENED);
        !           555:         }
        !           556:     }
        !           557:
        !           558:     cursed = (rogue.clairvoyance < 0);
        !           559:     if (cursed) {
        !           560:         clairvoyanceRadius = (rogue.clairvoyance - 1) * -1;
        !           561:         cFlags = CLAIRVOYANT_DARKENED;
        !           562:     } else {
        !           563:         clairvoyanceRadius = (rogue.clairvoyance > 0) ? rogue.clairvoyance + 1 : 0;
        !           564:         cFlags = CLAIRVOYANT_VISIBLE | DISCOVERED;
        !           565:     }
        !           566:
        !           567:     for (i = max(0, player.xLoc - clairvoyanceRadius); i < min(DCOLS, player.xLoc + clairvoyanceRadius + 1); i++) {
        !           568:         for (j = max(0, player.yLoc - clairvoyanceRadius); j < min(DROWS, player.yLoc + clairvoyanceRadius + 1); j++) {
        !           569:
        !           570:             dx = (player.xLoc - i);
        !           571:             dy = (player.yLoc - j);
        !           572:
        !           573:             if (dx*dx + dy*dy < clairvoyanceRadius*clairvoyanceRadius + clairvoyanceRadius
        !           574:                 && (pmap[i][j].layers[DUNGEON] != GRANITE || pmap[i][j].flags & DISCOVERED)) {
        !           575:
        !           576:                 if (cFlags & DISCOVERED) {
        !           577:                     discoverCell(i, j);
        !           578:                 }
        !           579:                 pmap[i][j].flags |= cFlags;
        !           580:                 if (!(pmap[i][j].flags & HAS_PLAYER) && !cursed) {
        !           581:                     pmap[i][j].flags &= ~STABLE_MEMORY;
        !           582:                 }
        !           583:             }
        !           584:         }
        !           585:     }
        !           586: }
        !           587:
        !           588: void updateTelepathy() {
        !           589:     short i, j;
        !           590:     creature *monst;
        !           591:     boolean grid[DCOLS][DROWS];
        !           592:
        !           593:     for (i=0; i<DCOLS; i++) {
        !           594:         for (j=0; j<DROWS; j++) {
        !           595:             pmap[i][j].flags &= ~WAS_TELEPATHIC_VISIBLE;
        !           596:             if (pmap[i][j].flags & TELEPATHIC_VISIBLE) {
        !           597:                 pmap[i][j].flags |= WAS_TELEPATHIC_VISIBLE;
        !           598:             }
        !           599:             pmap[i][j].flags &= ~(TELEPATHIC_VISIBLE);
        !           600:         }
        !           601:     }
        !           602:
        !           603:     zeroOutGrid(grid);
        !           604:     for (monst = monsters->nextCreature; monst; monst = monst->nextCreature) {
        !           605:         if (monsterRevealed(monst)) {
        !           606:             getFOVMask(grid, monst->xLoc, monst->yLoc, 2 * FP_FACTOR, T_OBSTRUCTS_VISION, 0, false);
        !           607:             pmap[monst->xLoc][monst->yLoc].flags |= TELEPATHIC_VISIBLE;
        !           608:             discoverCell(monst->xLoc, monst->yLoc);
        !           609:         }
        !           610:     }
        !           611:     for (monst = dormantMonsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
        !           612:         if (monsterRevealed(monst)) {
        !           613:             getFOVMask(grid, monst->xLoc, monst->yLoc, 2 * FP_FACTOR, T_OBSTRUCTS_VISION, 0, false);
        !           614:             pmap[monst->xLoc][monst->yLoc].flags |= TELEPATHIC_VISIBLE;
        !           615:             discoverCell(monst->xLoc, monst->yLoc);
        !           616:         }
        !           617:     }
        !           618:     for (i = 0; i < DCOLS; i++) {
        !           619:         for (j = 0; j < DROWS; j++) {
        !           620:             if (grid[i][j]) {
        !           621:                 pmap[i][j].flags |= TELEPATHIC_VISIBLE;
        !           622:                 discoverCell(i, j);
        !           623:             }
        !           624:         }
        !           625:     }
        !           626: }
        !           627:
        !           628: short scentDistance(short x1, short y1, short x2, short y2) {
        !           629:     if (abs(x1 - x2) > abs(y1 - y2)) {
        !           630:         return 2 * abs(x1 - x2) + abs(y1 - y2);
        !           631:     } else {
        !           632:         return abs(x1 - x2) + 2 * abs(y1 - y2);
        !           633:     }
        !           634: }
        !           635:
        !           636: void updateScent() {
        !           637:     short i, j;
        !           638:     char grid[DCOLS][DROWS];
        !           639:
        !           640:     zeroOutGrid(grid);
        !           641:
        !           642:     getFOVMask(grid, player.xLoc, player.yLoc, DCOLS * FP_FACTOR, T_OBSTRUCTS_SCENT, 0, false);
        !           643:
        !           644:     for (i=0; i<DCOLS; i++) {
        !           645:         for (j=0; j<DROWS; j++) {
        !           646:             if (grid[i][j]) {
        !           647:                 addScentToCell(i, j, scentDistance(player.xLoc, player.yLoc, i, j));
        !           648:             }
        !           649:         }
        !           650:     }
        !           651:     addScentToCell(player.xLoc, player.yLoc, 0);
        !           652: }
        !           653:
        !           654: short armorAggroAdjustment(item *theArmor) {
        !           655:     if (!theArmor
        !           656:         || !(theArmor->category & ARMOR)) {
        !           657:
        !           658:         return 0;
        !           659:     }
        !           660:     return max(0, armorTable[theArmor->kind].strengthRequired - 12);
        !           661: }
        !           662:
        !           663: short currentAggroValue() {
        !           664:     // Default value of 14 in the light.
        !           665:     short stealthVal = 14;
        !           666:
        !           667:     if (player.status[STATUS_INVISIBLE]) {
        !           668:         stealthVal = 1; // Invisibility means stealth range of 1, no matter what.
        !           669:     } else {
        !           670:         if (playerInDarkness()) {
        !           671:             // In darkness, halve, rounded down.
        !           672:             stealthVal = stealthVal / 2;
        !           673:         }
        !           674:         if (pmap[player.xLoc][player.yLoc].flags & IS_IN_SHADOW) {
        !           675:             // When not standing in a lit area, halve, rounded down (stacks with darkness halving).
        !           676:             stealthVal = stealthVal / 2;
        !           677:         }
        !           678:
        !           679:         // Add 1 for each point of your armor's natural (unenchanted) strength requirement above 12.
        !           680:         stealthVal += armorAggroAdjustment(rogue.armor);
        !           681:
        !           682:         // Halve (rounded up) if you just rested.
        !           683:         if (rogue.justRested) {
        !           684:             stealthVal = (stealthVal + 1) / 2;
        !           685:         }
        !           686:
        !           687:         if (player.status[STATUS_AGGRAVATING] > 0) {
        !           688:             stealthVal += player.status[STATUS_AGGRAVATING];
        !           689:         }
        !           690:
        !           691:         // Subtract your bonuses from rings of stealth.
        !           692:         // (Cursed rings of stealth will end up adding here.)
        !           693:         stealthVal -= rogue.stealthBonus;
        !           694:
        !           695:         // Can't go below 2 unless you just rested.
        !           696:         if (stealthVal < 2 && !rogue.justRested) {
        !           697:             stealthVal = 2;
        !           698:         } else if (stealthVal < 1) { // Can't go below 1, ever.
        !           699:             stealthVal = 1;
        !           700:         }
        !           701:     }
        !           702:     return stealthVal;
        !           703: }
        !           704:
        !           705: void demoteVisibility() {
        !           706:     short i, j;
        !           707:
        !           708:     for (i=0; i<DCOLS; i++) {
        !           709:         for (j=0; j<DROWS; j++) {
        !           710:             pmap[i][j].flags &= ~WAS_VISIBLE;
        !           711:             if (pmap[i][j].flags & VISIBLE) {
        !           712:                 pmap[i][j].flags &= ~VISIBLE;
        !           713:                 pmap[i][j].flags |= WAS_VISIBLE;
        !           714:             }
        !           715:         }
        !           716:     }
        !           717: }
        !           718:
        !           719: void discoverCell(const short x, const short y) {
        !           720:     pmap[x][y].flags &= ~STABLE_MEMORY;
        !           721:     if (!(pmap[x][y].flags & DISCOVERED)) {
        !           722:         pmap[x][y].flags |= DISCOVERED;
        !           723:         if (!cellHasTerrainFlag(x, y, T_PATHING_BLOCKER)) {
        !           724:             rogue.xpxpThisTurn++;
        !           725:         }
        !           726:     }
        !           727: }
        !           728:
        !           729: void updateVision(boolean refreshDisplay) {
        !           730:     short i, j;
        !           731:     char grid[DCOLS][DROWS];
        !           732:     item *theItem;
        !           733:     creature *monst;
        !           734:
        !           735:     demoteVisibility();
        !           736:     for (i=0; i<DCOLS; i++) {
        !           737:         for (j=0; j<DROWS; j++) {
        !           738:             pmap[i][j].flags &= ~IN_FIELD_OF_VIEW;
        !           739:         }
        !           740:     }
        !           741:
        !           742:     // Calculate player's field of view (distinct from what is visible, as lighting hasn't been done yet).
        !           743:     zeroOutGrid(grid);
        !           744:     getFOVMask(grid, player.xLoc, player.yLoc, (DCOLS + DROWS) * FP_FACTOR, (T_OBSTRUCTS_VISION), 0, false);
        !           745:     for (i=0; i<DCOLS; i++) {
        !           746:         for (j=0; j<DROWS; j++) {
        !           747:             if (grid[i][j]) {
        !           748:                 pmap[i][j].flags |= IN_FIELD_OF_VIEW;
        !           749:             }
        !           750:         }
        !           751:     }
        !           752:     pmap[player.xLoc][player.yLoc].flags |= IN_FIELD_OF_VIEW | VISIBLE;
        !           753:
        !           754:     if (rogue.clairvoyance < 0) {
        !           755:         discoverCell(player.xLoc, player.yLoc);
        !           756:     }
        !           757:
        !           758:     if (rogue.clairvoyance != 0) {
        !           759:         updateClairvoyance();
        !           760:     }
        !           761:
        !           762:     updateTelepathy();
        !           763:     updateLighting();
        !           764:     updateFieldOfViewDisplay(true, refreshDisplay);
        !           765:
        !           766:     //  for (i=0; i<DCOLS; i++) {
        !           767:     //      for (j=0; j<DROWS; j++) {
        !           768:     //          if (pmap[i][j].flags & VISIBLE) {
        !           769:     //              plotCharWithColor(' ', mapToWindowX(i), mapToWindowY(j), &yellow, &yellow);
        !           770:     //          } else if (pmap[i][j].flags & IN_FIELD_OF_VIEW) {
        !           771:     //              plotCharWithColor(' ', mapToWindowX(i), mapToWindowY(j), &blue, &blue);
        !           772:     //          }
        !           773:     //      }
        !           774:     //  }
        !           775:     //  displayMoreSign();
        !           776:
        !           777:     if (player.status[STATUS_HALLUCINATING] > 0) {
        !           778:         for (theItem = floorItems->nextItem; theItem != NULL; theItem = theItem->nextItem) {
        !           779:             if ((pmap[theItem->xLoc][theItem->yLoc].flags & DISCOVERED) && refreshDisplay) {
        !           780:                 refreshDungeonCell(theItem->xLoc, theItem->yLoc);
        !           781:             }
        !           782:         }
        !           783:         for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
        !           784:             if ((pmap[monst->xLoc][monst->yLoc].flags & DISCOVERED) && refreshDisplay) {
        !           785:                 refreshDungeonCell(monst->xLoc, monst->yLoc);
        !           786:             }
        !           787:         }
        !           788:     }
        !           789: }
        !           790:
        !           791: void checkNutrition() {
        !           792:     item *theItem;
        !           793:     char buf[DCOLS*3], foodWarning[DCOLS*3];
        !           794:
        !           795:     if (numberOfMatchingPackItems(FOOD, 0, 0, false) == 0) {
        !           796:         sprintf(foodWarning, " and have no food");
        !           797:     } else {
        !           798:         foodWarning[0] = '\0';
        !           799:     }
        !           800:
        !           801:     if (player.status[STATUS_NUTRITION] == HUNGER_THRESHOLD) {
        !           802:         player.status[STATUS_NUTRITION]--;
        !           803:         sprintf(buf, "you are hungry%s.", foodWarning);
        !           804:         message(buf, foodWarning[0]);
        !           805:     } else if (player.status[STATUS_NUTRITION] == WEAK_THRESHOLD) {
        !           806:         player.status[STATUS_NUTRITION]--;
        !           807:         sprintf(buf, "you feel weak with hunger%s.", foodWarning);
        !           808:         message(buf, true);
        !           809:     } else if (player.status[STATUS_NUTRITION] == FAINT_THRESHOLD) {
        !           810:         player.status[STATUS_NUTRITION]--;
        !           811:         sprintf(buf, "you feel faint with hunger%s.", foodWarning);
        !           812:         message(buf, true);
        !           813:     } else if (player.status[STATUS_NUTRITION] <= 1) {
        !           814:         // Force the player to eat something if he has it
        !           815:         for (theItem = packItems->nextItem; theItem != NULL; theItem = theItem->nextItem) {
        !           816:             if (theItem->category == FOOD) {
        !           817:                 sprintf(buf, "unable to control your hunger, you eat a %s.", (theItem->kind == FRUIT ? "mango" : "ration of food"));
        !           818:                 messageWithColor(buf, &itemMessageColor, true);
        !           819:                 apply(theItem, false);
        !           820:                 break;
        !           821:             }
        !           822:         }
        !           823:     }
        !           824:
        !           825:     if (player.status[STATUS_NUTRITION] == 1) { // Didn't manage to eat any food above.
        !           826:         player.status[STATUS_NUTRITION] = 0;    // So the status bar changes in time for the message:
        !           827:         message("you are starving to death!", true);
        !           828:     }
        !           829: }
        !           830:
        !           831: void burnItem(item *theItem) {
        !           832:     short x, y;
        !           833:     char buf1[COLS * 3], buf2[COLS * 3];
        !           834:     itemName(theItem, buf1, false, true, NULL);
        !           835:     sprintf(buf2, "%s burn%s up!",
        !           836:             buf1,
        !           837:             theItem->quantity == 1 ? "s" : "");
        !           838:     x = theItem->xLoc;
        !           839:     y = theItem->yLoc;
        !           840:     removeItemFromChain(theItem, floorItems);
        !           841:     deleteItem(theItem);
        !           842:     pmap[x][y].flags &= ~(HAS_ITEM | ITEM_DETECTED);
        !           843:     if (pmap[x][y].flags & (ANY_KIND_OF_VISIBLE | DISCOVERED | ITEM_DETECTED)) {
        !           844:         refreshDungeonCell(x, y);
        !           845:     }
        !           846:     if (playerCanSee(x, y)) {
        !           847:         messageWithColor(buf2, &itemMessageColor, false);
        !           848:     }
        !           849:     spawnDungeonFeature(x, y, &(dungeonFeatureCatalog[DF_ITEM_FIRE]), true, false);
        !           850: }
        !           851:
        !           852: void flashCreatureAlert(creature *monst, char msg[200], color *foreColor, color *backColor) {
        !           853:     short x, y;
        !           854:     if (monst->yLoc > DROWS / 2) {
        !           855:         y = mapToWindowY(monst->yLoc - 2);
        !           856:     } else {
        !           857:         y = mapToWindowY(monst->yLoc + 2);
        !           858:     }
        !           859:     x = mapToWindowX(monst->xLoc - strLenWithoutEscapes(msg) / 2);
        !           860:     if (x > COLS - strLenWithoutEscapes(msg)) {
        !           861:         x = COLS - strLenWithoutEscapes(msg);
        !           862:     }
        !           863:     flashMessage(msg, x, y, (rogue.playbackMode ? 100 : 1000), foreColor, backColor);
        !           864:     rogue.disturbed = true;
        !           865: }
        !           866:
        !           867: void handleHealthAlerts() {
        !           868:     short i, currentPercent, previousPercent,
        !           869:     thresholds[] = {5, 10, 25, 40},
        !           870:     pThresholds[] = {100, 90, 50};
        !           871:     char buf[DCOLS];
        !           872:
        !           873:     const short healthThresholdsCount = 4,
        !           874:     poisonThresholdsCount = 3;
        !           875:
        !           876:     assureCosmeticRNG;
        !           877:
        !           878:     currentPercent = player.currentHP * 100 / player.info.maxHP;
        !           879:     previousPercent = player.previousHealthPoints * 100 / player.info.maxHP;
        !           880:
        !           881:     if (currentPercent < previousPercent && !rogue.gameHasEnded) {
        !           882:         for (i=0; i < healthThresholdsCount; i++) {
        !           883:             if (currentPercent < thresholds[i] && previousPercent >= thresholds[i]) {
        !           884:                 sprintf(buf, " <%i%% health ", thresholds[i]);
        !           885:                 flashCreatureAlert(&player, buf, &badMessageColor, &darkRed);
        !           886:                 break;
        !           887:             }
        !           888:         }
        !           889:     }
        !           890:
        !           891:     if (!rogue.gameHasEnded) {
        !           892:         currentPercent = player.status[STATUS_POISONED] * player.poisonAmount * 100 / player.currentHP;
        !           893:
        !           894:         if (currentPercent > rogue.previousPoisonPercent && !rogue.gameHasEnded) {
        !           895:             for (i=0; i < poisonThresholdsCount; i++) {
        !           896:                 if (currentPercent > pThresholds[i] && rogue.previousPoisonPercent <= pThresholds[i]) {
        !           897:                     if (currentPercent < 100) {
        !           898:                         sprintf(buf, " >%i%% poisoned ", pThresholds[i]);
        !           899:                     } else {
        !           900:                         strcpy(buf, " Fatally poisoned ");
        !           901:                     }
        !           902:                     flashCreatureAlert(&player, buf, &yellow, &darkGreen);
        !           903:                     break;
        !           904:                 }
        !           905:             }
        !           906:         }
        !           907:         rogue.previousPoisonPercent = currentPercent;
        !           908:     }
        !           909:
        !           910:     restoreRNG;
        !           911: }
        !           912:
        !           913: void addXPXPToAlly(short XPXP, creature *monst) {
        !           914:     char theMonsterName[100], buf[200];
        !           915:     if (!(monst->info.flags & (MONST_INANIMATE | MONST_IMMOBILE))
        !           916:         && !(monst->bookkeepingFlags & MB_TELEPATHICALLY_REVEALED)
        !           917:         && monst->creatureState == MONSTER_ALLY
        !           918:         && monst->spawnDepth <= rogue.depthLevel
        !           919:         && rogue.depthLevel <= AMULET_LEVEL) {
        !           920:
        !           921:         monst->xpxp += XPXP;
        !           922:         //printf("\n%i xpxp added to your %s this turn.", rogue.xpxpThisTurn, monst->info.monsterName);
        !           923:         if (monst->xpxp >= XPXP_NEEDED_FOR_TELEPATHIC_BOND
        !           924:             && !(monst->bookkeepingFlags & MB_TELEPATHICALLY_REVEALED)) {
        !           925:
        !           926:             monst->bookkeepingFlags |= MB_TELEPATHICALLY_REVEALED;
        !           927:             updateVision(true);
        !           928:             monsterName(theMonsterName, monst, false);
        !           929:             sprintf(buf, "you have developed a telepathic bond with your %s.", theMonsterName);
        !           930:             messageWithColor(buf, &advancementMessageColor, false);
        !           931:         }
        !           932:         if (monst->xpxp > 1500 * 20) {
        !           933:             rogue.featRecord[FEAT_COMPANION] = true;
        !           934:         }
        !           935:     }
        !           936: }
        !           937:
        !           938: void handleXPXP() {
        !           939:     creature *monst;
        !           940:     //char buf[DCOLS*2], theMonsterName[50];
        !           941:
        !           942:     for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
        !           943:         addXPXPToAlly(rogue.xpxpThisTurn, monst);
        !           944:     }
        !           945:     if (rogue.depthLevel > 1) {
        !           946:         for (monst = levels[rogue.depthLevel - 2].monsters; monst != NULL; monst = monst->nextCreature) {
        !           947:             addXPXPToAlly(rogue.xpxpThisTurn, monst);
        !           948:         }
        !           949:     }
        !           950:     if (rogue.depthLevel < DEEPEST_LEVEL) {
        !           951:         for (monst = levels[rogue.depthLevel].monsters; monst != NULL; monst = monst->nextCreature) {
        !           952:             addXPXPToAlly(rogue.xpxpThisTurn, monst);
        !           953:         }
        !           954:     }
        !           955:     rogue.xpxpThisTurn = 0;
        !           956: }
        !           957:
        !           958: void playerFalls() {
        !           959:     short damage;
        !           960:     short layer;
        !           961:
        !           962:     if (cellHasTMFlag(player.xLoc, player.yLoc, TM_IS_SECRET)
        !           963:         && playerCanSee(player.xLoc, player.yLoc)) {
        !           964:
        !           965:         discover(player.xLoc, player.yLoc);
        !           966:     }
        !           967:
        !           968:     monstersFall(); // Monsters must fall with the player rather than getting suspended on the previous level.
        !           969:     updateFloorItems(); // Likewise, items should fall with the player rather than getting suspended above.
        !           970:
        !           971:     layer = layerWithFlag(player.xLoc, player.yLoc, T_AUTO_DESCENT);
        !           972:     if (layer >= 0) {
        !           973:         message(tileCatalog[pmap[player.xLoc][player.yLoc].layers[layer]].flavorText, true);
        !           974:     } else if (layer == -1) {
        !           975:         message("You plunge downward!", true);
        !           976:     }
        !           977:
        !           978:     player.bookkeepingFlags &= ~(MB_IS_FALLING | MB_SEIZED | MB_SEIZING);
        !           979:     rogue.disturbed = true;
        !           980:
        !           981:     if (rogue.depthLevel < DEEPEST_LEVEL) {
        !           982:         rogue.depthLevel++;
        !           983:         startLevel(rogue.depthLevel - 1, 0);
        !           984:         damage = randClumpedRange(FALL_DAMAGE_MIN, FALL_DAMAGE_MAX, 2);
        !           985:         messageWithColor("You are damaged by the fall.", &badMessageColor, false);
        !           986:         if (inflictDamage(NULL, &player, damage, &red, false)) {
        !           987:             gameOver("Killed by a fall", true);
        !           988:         } else if (rogue.depthLevel > rogue.deepestLevel) {
        !           989:             rogue.deepestLevel = rogue.depthLevel;
        !           990:         }
        !           991:     } else {
        !           992:         message("A strange force seizes you as you fall.", false);
        !           993:         teleport(&player, -1, -1, true);
        !           994:     }
        !           995:     createFlare(player.xLoc, player.yLoc, GENERIC_FLASH_LIGHT);
        !           996:     animateFlares(rogue.flares, rogue.flareCount);
        !           997:     rogue.flareCount = 0;
        !           998: }
        !           999:
        !          1000:
        !          1001:
        !          1002: void activateMachine(short machineNumber) {
        !          1003:     short i, j, x, y, layer, sRows[DROWS], sCols[DCOLS], monsterCount, maxMonsters;
        !          1004:     creature **activatedMonsterList, *monst;
        !          1005:
        !          1006:     fillSequentialList(sCols, DCOLS);
        !          1007:     shuffleList(sCols, DCOLS);
        !          1008:     fillSequentialList(sRows, DROWS);
        !          1009:     shuffleList(sRows, DROWS);
        !          1010:
        !          1011:     for (i=0; i<DCOLS; i++) {
        !          1012:         for (j=0; j<DROWS; j++) {
        !          1013:             x = sCols[i];
        !          1014:             y = sRows[j];
        !          1015:             if ((pmap[x][y].flags & IS_IN_MACHINE)
        !          1016:                 && pmap[x][y].machineNumber == machineNumber
        !          1017:                 && !(pmap[x][y].flags & IS_POWERED)
        !          1018:                 && cellHasTMFlag(x, y, TM_IS_WIRED)) {
        !          1019:
        !          1020:                 pmap[x][y].flags |= IS_POWERED;
        !          1021:                 for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
        !          1022:                     if (tileCatalog[pmap[x][y].layers[layer]].mechFlags & TM_IS_WIRED) {
        !          1023:                         promoteTile(x, y, layer, false);
        !          1024:                     }
        !          1025:                 }
        !          1026:             }
        !          1027:         }
        !          1028:     }
        !          1029:
        !          1030:     monsterCount = maxMonsters = 0;
        !          1031:     activatedMonsterList = NULL;
        !          1032:     for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
        !          1033:         if (monst->machineHome == machineNumber
        !          1034:             && monst->spawnDepth == rogue.depthLevel
        !          1035:             && (monst->info.flags & MONST_GETS_TURN_ON_ACTIVATION)) {
        !          1036:
        !          1037:             monsterCount++;
        !          1038:
        !          1039:             if (monsterCount > maxMonsters) {
        !          1040:                 maxMonsters += 10;
        !          1041:                 activatedMonsterList = realloc(activatedMonsterList, sizeof(creature *) * maxMonsters);
        !          1042:             }
        !          1043:             activatedMonsterList[monsterCount - 1] = monst;
        !          1044:         }
        !          1045:     }
        !          1046:     for (i=0; i<monsterCount; i++) {
        !          1047:         if (!(activatedMonsterList[i]->bookkeepingFlags & MB_IS_DYING)) {
        !          1048:             monstersTurn(activatedMonsterList[i]);
        !          1049:         }
        !          1050:     }
        !          1051:
        !          1052:     if (activatedMonsterList) {
        !          1053:         free(activatedMonsterList);
        !          1054:     }
        !          1055: }
        !          1056:
        !          1057: boolean circuitBreakersPreventActivation(short machineNumber) {
        !          1058:     short i, j;
        !          1059:     for (i=0; i<DCOLS; i++) {
        !          1060:         for (j=0; j<DROWS; j++) {
        !          1061:             if (pmap[i][j].machineNumber == machineNumber
        !          1062:                 && cellHasTMFlag(i, j, TM_IS_CIRCUIT_BREAKER)) {
        !          1063:
        !          1064:                 return true;
        !          1065:             }
        !          1066:         }
        !          1067:     }
        !          1068:     return false;
        !          1069: }
        !          1070:
        !          1071: void promoteTile(short x, short y, enum dungeonLayers layer, boolean useFireDF) {
        !          1072:     short i, j;
        !          1073:     enum dungeonFeatureTypes DFType;
        !          1074:     floorTileType *tile;
        !          1075:
        !          1076:     tile = &(tileCatalog[pmap[x][y].layers[layer]]);
        !          1077:
        !          1078:     DFType = (useFireDF ? tile->fireType : tile->promoteType);
        !          1079:
        !          1080:     if ((tile->mechFlags & TM_VANISHES_UPON_PROMOTION)) {
        !          1081:         if (tileCatalog[pmap[x][y].layers[layer]].flags & T_PATHING_BLOCKER) {
        !          1082:             rogue.staleLoopMap = true;
        !          1083:         }
        !          1084:         pmap[x][y].layers[layer] = (layer == DUNGEON ? FLOOR : NOTHING); // even the dungeon layer implicitly has floor underneath it
        !          1085:         if (layer == GAS) {
        !          1086:             pmap[x][y].volume = 0;
        !          1087:         }
        !          1088:         refreshDungeonCell(x, y);
        !          1089:     }
        !          1090:     if (DFType) {
        !          1091:         spawnDungeonFeature(x, y, &dungeonFeatureCatalog[DFType], true, false);
        !          1092:     }
        !          1093:
        !          1094:     if (!useFireDF && (tile->mechFlags & TM_IS_WIRED)
        !          1095:         && !(pmap[x][y].flags & IS_POWERED)
        !          1096:         && !circuitBreakersPreventActivation(pmap[x][y].machineNumber)) {
        !          1097:         // Send power through all cells in the same machine that are not IS_POWERED,
        !          1098:         // and on any such cell, promote each terrain layer that is T_IS_WIRED.
        !          1099:         // Note that machines need not be contiguous.
        !          1100:         pmap[x][y].flags |= IS_POWERED;
        !          1101:         activateMachine(pmap[x][y].machineNumber); // It lives!!!
        !          1102:
        !          1103:         // Power fades from the map immediately after we finish.
        !          1104:         for (i=0; i<DCOLS; i++) {
        !          1105:             for (j=0; j<DROWS; j++) {
        !          1106:                 pmap[i][j].flags &= ~IS_POWERED;
        !          1107:             }
        !          1108:         }
        !          1109:     }
        !          1110: }
        !          1111:
        !          1112: boolean exposeTileToElectricity(short x, short y) {
        !          1113:     enum dungeonLayers layer;
        !          1114:     boolean promotedSomething = false;
        !          1115:
        !          1116:     if (!cellHasTMFlag(x, y, TM_PROMOTES_ON_ELECTRICITY)) {
        !          1117:         return false;
        !          1118:     }
        !          1119:     for (layer=0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
        !          1120:         if (tileCatalog[pmap[x][y].layers[layer]].mechFlags & TM_PROMOTES_ON_ELECTRICITY) {
        !          1121:             promoteTile(x, y, layer, false);
        !          1122:             promotedSomething = true;
        !          1123:         }
        !          1124:     }
        !          1125:     return promotedSomething;
        !          1126: }
        !          1127:
        !          1128: boolean exposeTileToFire(short x, short y, boolean alwaysIgnite) {
        !          1129:     enum dungeonLayers layer;
        !          1130:     short ignitionChance = 0, bestExtinguishingPriority = 1000, explosiveNeighborCount = 0;
        !          1131:     short newX, newY;
        !          1132:     enum directions dir;
        !          1133:     boolean fireIgnited = false, explosivePromotion = false;
        !          1134:
        !          1135:     if (!cellHasTerrainFlag(x, y, T_IS_FLAMMABLE)) {
        !          1136:         return false;
        !          1137:     }
        !          1138:
        !          1139:     // Pick the extinguishing layer with the best priority.
        !          1140:     for (layer=0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
        !          1141:         if ((tileCatalog[pmap[x][y].layers[layer]].mechFlags & TM_EXTINGUISHES_FIRE)
        !          1142:             && tileCatalog[pmap[x][y].layers[layer]].drawPriority < bestExtinguishingPriority) {
        !          1143:             bestExtinguishingPriority = tileCatalog[pmap[x][y].layers[layer]].drawPriority;
        !          1144:         }
        !          1145:     }
        !          1146:
        !          1147:     // Pick the fire type of the most flammable layer that is either gas or equal-or-better priority than the best extinguishing layer.
        !          1148:     for (layer=0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
        !          1149:         if ((tileCatalog[pmap[x][y].layers[layer]].flags & T_IS_FLAMMABLE)
        !          1150:             && (layer == GAS || tileCatalog[pmap[x][y].layers[layer]].drawPriority <= bestExtinguishingPriority)
        !          1151:             && tileCatalog[pmap[x][y].layers[layer]].chanceToIgnite > ignitionChance) {
        !          1152:             ignitionChance = tileCatalog[pmap[x][y].layers[layer]].chanceToIgnite;
        !          1153:         }
        !          1154:     }
        !          1155:
        !          1156:     if (alwaysIgnite || (ignitionChance && rand_percent(ignitionChance))) { // If it ignites...
        !          1157:         fireIgnited = true;
        !          1158:
        !          1159:         // Count explosive neighbors.
        !          1160:         if (cellHasTMFlag(x, y, TM_EXPLOSIVE_PROMOTE)) {
        !          1161:             for (dir = 0, explosiveNeighborCount = 0; dir < DIRECTION_COUNT; dir++) {
        !          1162:                 newX = x + nbDirs[dir][0];
        !          1163:                 newY = y + nbDirs[dir][1];
        !          1164:                 if (coordinatesAreInMap(newX, newY)
        !          1165:                     && (cellHasTerrainFlag(newX, newY, T_IS_FIRE | T_OBSTRUCTS_GAS) || cellHasTMFlag(newX, newY, TM_EXPLOSIVE_PROMOTE))) {
        !          1166:
        !          1167:                     explosiveNeighborCount++;
        !          1168:                 }
        !          1169:             }
        !          1170:             if (explosiveNeighborCount >= 8) {
        !          1171:                 explosivePromotion = true;
        !          1172:             }
        !          1173:         }
        !          1174:
        !          1175:         // Flammable layers are consumed.
        !          1176:         for (layer=0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
        !          1177:             if (tileCatalog[pmap[x][y].layers[layer]].flags & T_IS_FLAMMABLE) {
        !          1178:                 // pmap[x][y].layers[GAS] is not cleared here, which is a bug.
        !          1179:                 // We preserve the layer anyways because this results in nicer gas burning behavior.
        !          1180:                 if (layer == GAS) {
        !          1181:                     pmap[x][y].volume = 0; // Flammable gas burns its volume away.
        !          1182:                 }
        !          1183:                 promoteTile(x, y, layer, !explosivePromotion);
        !          1184:             }
        !          1185:         }
        !          1186:         refreshDungeonCell(x, y);
        !          1187:     }
        !          1188:     return fireIgnited;
        !          1189: }
        !          1190:
        !          1191: // Only the gas layer can be volumetric.
        !          1192: void updateVolumetricMedia() {
        !          1193:     short i, j, newX, newY, numSpaces;
        !          1194:     unsigned long highestNeighborVolume;
        !          1195:     unsigned long sum;
        !          1196:     enum tileType gasType;
        !          1197:     enum directions dir;
        !          1198:     unsigned short newGasVolume[DCOLS][DROWS];
        !          1199:
        !          1200:     for (i=0; i<DCOLS; i++) {
        !          1201:         for (j=0; j<DROWS; j++) {
        !          1202:             newGasVolume[i][j] = 0;
        !          1203:         }
        !          1204:     }
        !          1205:
        !          1206:     for (i=0; i<DCOLS; i++) {
        !          1207:         for (j=0; j<DROWS; j++) {
        !          1208:             if (!cellHasTerrainFlag(i, j, T_OBSTRUCTS_GAS)) {
        !          1209:                 sum = pmap[i][j].volume;
        !          1210:                 numSpaces = 1;
        !          1211:                 highestNeighborVolume = pmap[i][j].volume;
        !          1212:                 gasType = pmap[i][j].layers[GAS];
        !          1213:                 for (dir=0; dir< DIRECTION_COUNT; dir++) {
        !          1214:                     newX = i + nbDirs[dir][0];
        !          1215:                     newY = j + nbDirs[dir][1];
        !          1216:                     if (coordinatesAreInMap(newX, newY)
        !          1217:                         && !cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_GAS)) {
        !          1218:
        !          1219:                         sum += pmap[newX][newY].volume;
        !          1220:                         numSpaces++;
        !          1221:                         if (pmap[newX][newY].volume > highestNeighborVolume) {
        !          1222:                             highestNeighborVolume = pmap[newX][newY].volume;
        !          1223:                             gasType = pmap[newX][newY].layers[GAS];
        !          1224:                         }
        !          1225:                     }
        !          1226:                 }
        !          1227:                 if (cellHasTerrainFlag(i, j, T_AUTO_DESCENT)) { // if it's a chasm tile or trap door,
        !          1228:                     numSpaces++; // this will allow gas to escape from the level entirely
        !          1229:                 }
        !          1230:                 newGasVolume[i][j] += sum / max(1, numSpaces);
        !          1231:                 if ((unsigned) rand_range(0, numSpaces - 1) < (sum % numSpaces)) {
        !          1232:                     newGasVolume[i][j]++; // stochastic rounding
        !          1233:                 }
        !          1234:                 if (pmap[i][j].layers[GAS] != gasType && newGasVolume[i][j] > 3) {
        !          1235:                     if (pmap[i][j].layers[GAS] != NOTHING) {
        !          1236:                         newGasVolume[i][j] = min(3, newGasVolume[i][j]); // otherwise interactions between gases are crazy
        !          1237:                     }
        !          1238:                     pmap[i][j].layers[GAS] = gasType;
        !          1239:                 } else if (pmap[i][j].layers[GAS] && newGasVolume[i][j] < 1) {
        !          1240:                     pmap[i][j].layers[GAS] = NOTHING;
        !          1241:                     refreshDungeonCell(i, j);
        !          1242:                 }
        !          1243:                 if (pmap[i][j].volume > 0) {
        !          1244:                     if (tileCatalog[pmap[i][j].layers[GAS]].mechFlags & TM_GAS_DISSIPATES_QUICKLY) {
        !          1245:                         newGasVolume[i][j] -= (rand_percent(50) ? 1 : 0);
        !          1246:                     } else if (tileCatalog[pmap[i][j].layers[GAS]].mechFlags & TM_GAS_DISSIPATES) {
        !          1247:                         newGasVolume[i][j] -= (rand_percent(20) ? 1 : 0);
        !          1248:                     }
        !          1249:                 }
        !          1250:             } else if (pmap[i][j].volume > 0) { // if has gas but can't hold gas,
        !          1251:                 // disperse gas instantly into neighboring tiles that can hold gas
        !          1252:                 numSpaces = 0;
        !          1253:                 for (dir = 0; dir < DIRECTION_COUNT; dir++) {
        !          1254:                     newX = i + nbDirs[dir][0];
        !          1255:                     newY = j + nbDirs[dir][1];
        !          1256:                     if (coordinatesAreInMap(newX, newY)
        !          1257:                         && !cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_GAS)) {
        !          1258:
        !          1259:                         numSpaces++;
        !          1260:                     }
        !          1261:                 }
        !          1262:                 if (numSpaces > 0) {
        !          1263:                     for (dir = 0; dir < DIRECTION_COUNT; dir++) {
        !          1264:                         newX = i + nbDirs[dir][0];
        !          1265:                         newY = j + nbDirs[dir][1];
        !          1266:                         if (coordinatesAreInMap(newX, newY)
        !          1267:                             && !cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_GAS)) {
        !          1268:
        !          1269:                             newGasVolume[newX][newY] += (pmap[i][j].volume / numSpaces);
        !          1270:                             if (pmap[i][j].volume / numSpaces) {
        !          1271:                                 pmap[newX][newY].layers[GAS] = pmap[i][j].layers[GAS];
        !          1272:                             }
        !          1273:                         }
        !          1274:                     }
        !          1275:                 }
        !          1276:                 newGasVolume[i][j] = 0;
        !          1277:                 pmap[i][j].layers[GAS] = NOTHING;
        !          1278:             }
        !          1279:         }
        !          1280:     }
        !          1281:
        !          1282:     for (i=0; i<DCOLS; i++) {
        !          1283:         for (j=0; j<DROWS; j++) {
        !          1284:             if (pmap[i][j].volume != newGasVolume[i][j]) {
        !          1285:                 pmap[i][j].volume = newGasVolume[i][j];
        !          1286:                 refreshDungeonCell(i, j);
        !          1287:             }
        !          1288:         }
        !          1289:     }
        !          1290: }
        !          1291:
        !          1292: void updateYendorWardenTracking() {
        !          1293:     creature *prevMonst;
        !          1294:     short n;
        !          1295:
        !          1296:     if (!rogue.yendorWarden) {
        !          1297:         return;
        !          1298:     }
        !          1299:     if (rogue.yendorWarden->depth == rogue.depthLevel) {
        !          1300:         return;
        !          1301:     }
        !          1302:     if (!(rogue.yendorWarden->bookkeepingFlags & MB_PREPLACED)) {
        !          1303:         levels[rogue.yendorWarden->depth - 1].mapStorage[rogue.yendorWarden->xLoc][rogue.yendorWarden->yLoc].flags &= ~HAS_MONSTER;
        !          1304:     }
        !          1305:     n = rogue.yendorWarden->depth - 1;
        !          1306:
        !          1307:     // remove traversing monster from other level monster chain
        !          1308:     if (rogue.yendorWarden == levels[n].monsters) {
        !          1309:         levels[n].monsters = rogue.yendorWarden->nextCreature;
        !          1310:     } else {
        !          1311:         for (prevMonst = levels[n].monsters; prevMonst->nextCreature != rogue.yendorWarden; prevMonst = prevMonst->nextCreature);
        !          1312:         prevMonst->nextCreature = rogue.yendorWarden->nextCreature;
        !          1313:     }
        !          1314:
        !          1315:     if (rogue.yendorWarden->depth > rogue.depthLevel) {
        !          1316:         rogue.yendorWarden->depth = rogue.depthLevel + 1;
        !          1317:         n = rogue.yendorWarden->depth - 1;
        !          1318:         rogue.yendorWarden->bookkeepingFlags |= MB_APPROACHING_UPSTAIRS;
        !          1319:         rogue.yendorWarden->xLoc = levels[n].downStairsLoc[0];
        !          1320:         rogue.yendorWarden->yLoc = levels[n].downStairsLoc[1];
        !          1321:     } else {
        !          1322:         rogue.yendorWarden->depth = rogue.depthLevel - 1;
        !          1323:         n = rogue.yendorWarden->depth - 1;
        !          1324:         rogue.yendorWarden->bookkeepingFlags |= MB_APPROACHING_DOWNSTAIRS;
        !          1325:         rogue.yendorWarden->xLoc = levels[n].upStairsLoc[0];
        !          1326:         rogue.yendorWarden->yLoc = levels[n].upStairsLoc[1];
        !          1327:     }
        !          1328:     rogue.yendorWarden->nextCreature = levels[rogue.yendorWarden->depth - 1].monsters;
        !          1329:     levels[rogue.yendorWarden->depth - 1].monsters = rogue.yendorWarden;
        !          1330:     rogue.yendorWarden->bookkeepingFlags |= MB_PREPLACED;
        !          1331:     rogue.yendorWarden->status[STATUS_ENTERS_LEVEL_IN] = 50;
        !          1332: }
        !          1333:
        !          1334: // Monsters who are over chasms or other descent tiles won't fall until this is called.
        !          1335: // This is to avoid having the monster chain change unpredictably in the middle of a turn.
        !          1336: void monstersFall() {
        !          1337:     creature *monst, *previousCreature, *nextCreature;
        !          1338:     short x, y;
        !          1339:     char buf[DCOLS], buf2[DCOLS];
        !          1340:
        !          1341:     // monsters plunge into chasms at the end of the turn
        !          1342:     for (monst = monsters->nextCreature; monst != NULL; monst = nextCreature) {
        !          1343:         nextCreature = monst->nextCreature;
        !          1344:         if ((monst->bookkeepingFlags & MB_IS_FALLING) || monsterShouldFall(monst)) {
        !          1345:             if (rogue.patchVersion >= 3) monst->bookkeepingFlags |= MB_IS_FALLING;
        !          1346:
        !          1347:             x = monst->xLoc;
        !          1348:             y = monst->yLoc;
        !          1349:
        !          1350:             if (canSeeMonster(monst)) {
        !          1351:                 monsterName(buf, monst, true);
        !          1352:                 sprintf(buf2, "%s plunges out of sight!", buf);
        !          1353:                 messageWithColor(buf2, messageColorFromVictim(monst), false);
        !          1354:             }
        !          1355:
        !          1356:             if (rogue.patchVersion < 3) {
        !          1357:                 monst->status[STATUS_ENTRANCED] = 0;
        !          1358:                 monst->bookkeepingFlags |= MB_PREPLACED;
        !          1359:                 monst->bookkeepingFlags &= ~(MB_IS_FALLING | MB_SEIZED | MB_SEIZING);
        !          1360:                 monst->targetCorpseLoc[0] = monst->targetCorpseLoc[1] = 0;
        !          1361:             }
        !          1362:
        !          1363:             if (monst->info.flags & MONST_GETS_TURN_ON_ACTIVATION) {
        !          1364:                 // Guardians and mirrored totems never survive the fall. If they did, they might block the level below.
        !          1365:                 killCreature(monst, false);
        !          1366:             } else if (!inflictDamage(NULL, monst, randClumpedRange(6, 12, 2), &red, false)) {
        !          1367:                 demoteMonsterFromLeadership(monst);
        !          1368:
        !          1369:                 if (rogue.patchVersion >= 3) {
        !          1370:                     monst->status[STATUS_ENTRANCED] = 0;
        !          1371:                     monst->bookkeepingFlags |= MB_PREPLACED;
        !          1372:                     monst->bookkeepingFlags &= ~(MB_IS_FALLING | MB_SEIZED | MB_SEIZING);
        !          1373:                     monst->targetCorpseLoc[0] = monst->targetCorpseLoc[1] = 0;
        !          1374:                 }
        !          1375:
        !          1376:                 // remove from monster chain
        !          1377:                 for (previousCreature = monsters;
        !          1378:                      previousCreature->nextCreature != monst;
        !          1379:                      previousCreature = previousCreature->nextCreature);
        !          1380:                 previousCreature->nextCreature = monst->nextCreature;
        !          1381:
        !          1382:                 // add to next level's chain
        !          1383:                 monst->nextCreature = levels[rogue.depthLevel-1 + 1].monsters;
        !          1384:                 levels[rogue.depthLevel-1 + 1].monsters = monst;
        !          1385:
        !          1386:                 monst->depth = rogue.depthLevel + 1;
        !          1387:
        !          1388:                 if (monst == rogue.yendorWarden) {
        !          1389:                     updateYendorWardenTracking();
        !          1390:                 }
        !          1391:             }
        !          1392:
        !          1393:             pmap[x][y].flags &= ~HAS_MONSTER;
        !          1394:             refreshDungeonCell(x, y);
        !          1395:         }
        !          1396:     }
        !          1397: }
        !          1398:
        !          1399: void updateEnvironment() {
        !          1400:     short i, j, direction, newX, newY, promotions[DCOLS][DROWS];
        !          1401:     long promoteChance;
        !          1402:     enum dungeonLayers layer;
        !          1403:     floorTileType *tile;
        !          1404:     boolean isVolumetricGas = false;
        !          1405:
        !          1406:     monstersFall();
        !          1407:
        !          1408:     // update gases twice
        !          1409:     for (i=0; i<DCOLS && !isVolumetricGas; i++) {
        !          1410:         for (j=0; j<DROWS && !isVolumetricGas; j++) {
        !          1411:             if (!isVolumetricGas && pmap[i][j].layers[GAS]) {
        !          1412:                 isVolumetricGas = true;
        !          1413:             }
        !          1414:         }
        !          1415:     }
        !          1416:     if (isVolumetricGas) {
        !          1417:         updateVolumetricMedia();
        !          1418:         updateVolumetricMedia();
        !          1419:     }
        !          1420:
        !          1421:     // Do random tile promotions in two passes to keep generations distinct.
        !          1422:     // First pass, make a note of each terrain layer at each coordinate that is going to promote:
        !          1423:     for (i=0; i<DCOLS; i++) {
        !          1424:         for (j=0; j<DROWS; j++) {
        !          1425:             promotions[i][j] = 0;
        !          1426:             for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
        !          1427:                 tile = &(tileCatalog[pmap[i][j].layers[layer]]);
        !          1428:                 if (tile->promoteChance < 0) {
        !          1429:                     promoteChance = 0;
        !          1430:                     for (direction = 0; direction < 4; direction++) {
        !          1431:                         if (coordinatesAreInMap(i + nbDirs[direction][0], j + nbDirs[direction][1])
        !          1432:                             && !cellHasTerrainFlag(i + nbDirs[direction][0], j + nbDirs[direction][1], T_OBSTRUCTS_PASSABILITY)
        !          1433:                             && pmap[i + nbDirs[direction][0]][j + nbDirs[direction][1]].layers[layer] != pmap[i][j].layers[layer]
        !          1434:                             && !(pmap[i][j].flags & CAUGHT_FIRE_THIS_TURN)) {
        !          1435:                             promoteChance += -1 * tile->promoteChance;
        !          1436:                         }
        !          1437:                     }
        !          1438:                 } else {
        !          1439:                     promoteChance = tile->promoteChance;
        !          1440:                 }
        !          1441:                 if (promoteChance
        !          1442:                     && !(pmap[i][j].flags & CAUGHT_FIRE_THIS_TURN)
        !          1443:                     && rand_range(0, 10000) < promoteChance) {
        !          1444:                     promotions[i][j] |= Fl(layer);
        !          1445:                     //promoteTile(i, j, layer, false);
        !          1446:                 }
        !          1447:             }
        !          1448:         }
        !          1449:     }
        !          1450:     // Second pass, do the promotions:
        !          1451:     for (i=0; i<DCOLS; i++) {
        !          1452:         for (j=0; j<DROWS; j++) {
        !          1453:             for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
        !          1454:                 if ((promotions[i][j] & Fl(layer))) {
        !          1455:                     //&& (tileCatalog[pmap[i][j].layers[layer]].promoteChance != 0)){
        !          1456:                     // make sure that it's still a promotable layer
        !          1457:                     promoteTile(i, j, layer, false);
        !          1458:                 }
        !          1459:             }
        !          1460:         }
        !          1461:     }
        !          1462:
        !          1463:     // Bookkeeping for fire, pressure plates and key-activated tiles.
        !          1464:     for (i=0; i<DCOLS; i++) {
        !          1465:         for (j=0; j<DROWS; j++) {
        !          1466:             pmap[i][j].flags &= ~(CAUGHT_FIRE_THIS_TURN);
        !          1467:             if (!(pmap[i][j].flags & (HAS_PLAYER | HAS_MONSTER | HAS_ITEM))
        !          1468:                 && (pmap[i][j].flags & PRESSURE_PLATE_DEPRESSED)) {
        !          1469:
        !          1470:                 pmap[i][j].flags &= ~PRESSURE_PLATE_DEPRESSED;
        !          1471:             }
        !          1472:             if (cellHasTMFlag(i, j, TM_PROMOTES_WITHOUT_KEY) && !keyOnTileAt(i, j)) {
        !          1473:                 for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
        !          1474:                     if (tileCatalog[pmap[i][j].layers[layer]].mechFlags & TM_PROMOTES_WITHOUT_KEY) {
        !          1475:                         promoteTile(i, j, layer, false);
        !          1476:                     }
        !          1477:                 }
        !          1478:             }
        !          1479:         }
        !          1480:     }
        !          1481:
        !          1482:     // Update fire.
        !          1483:     for (i=0; i<DCOLS; i++) {
        !          1484:         for (j=0; j<DROWS; j++) {
        !          1485:             if (cellHasTerrainFlag(i, j, T_IS_FIRE) && !(pmap[i][j].flags & CAUGHT_FIRE_THIS_TURN)) {
        !          1486:                 exposeTileToFire(i, j, false);
        !          1487:                 for (direction=0; direction<4; direction++) {
        !          1488:                     newX = i + nbDirs[direction][0];
        !          1489:                     newY = j + nbDirs[direction][1];
        !          1490:                     if (coordinatesAreInMap(newX, newY)) {
        !          1491:                         exposeTileToFire(newX, newY, false);
        !          1492:                     }
        !          1493:                 }
        !          1494:             }
        !          1495:         }
        !          1496:     }
        !          1497:
        !          1498:     // Terrain that affects items and vice versa
        !          1499:     updateFloorItems();
        !          1500: }
        !          1501:
        !          1502: void updateAllySafetyMap() {
        !          1503:     short i, j;
        !          1504:     short **playerCostMap, **monsterCostMap;
        !          1505:
        !          1506:     rogue.updatedAllySafetyMapThisTurn = true;
        !          1507:
        !          1508:     playerCostMap = allocGrid();
        !          1509:     monsterCostMap = allocGrid();
        !          1510:
        !          1511:     for (i=0; i<DCOLS; i++) {
        !          1512:         for (j=0; j<DROWS; j++) {
        !          1513:             allySafetyMap[i][j] = 30000;
        !          1514:
        !          1515:             playerCostMap[i][j] = monsterCostMap[i][j] = 1;
        !          1516:
        !          1517:             if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY)
        !          1518:                 && (!cellHasTMFlag(i, j, TM_IS_SECRET) || (discoveredTerrainFlagsAtLoc(i, j) & T_OBSTRUCTS_PASSABILITY))) {
        !          1519:
        !          1520:                 playerCostMap[i][j] = monsterCostMap[i][j] = cellHasTerrainFlag(i, j, T_OBSTRUCTS_DIAGONAL_MOVEMENT) ? PDS_OBSTRUCTION : PDS_FORBIDDEN;
        !          1521:             } else if (cellHasTerrainFlag(i, j, T_PATHING_BLOCKER & ~T_OBSTRUCTS_PASSABILITY)) {
        !          1522:                 playerCostMap[i][j] = monsterCostMap[i][j] = PDS_FORBIDDEN;
        !          1523:             } else if (cellHasTerrainFlag(i, j, T_SACRED)) {
        !          1524:                 playerCostMap[i][j] = 1;
        !          1525:                 monsterCostMap[i][j] = PDS_FORBIDDEN;
        !          1526:             } else if ((pmap[i][j].flags & HAS_MONSTER) && monstersAreEnemies(&player, monsterAtLoc(i, j))) {
        !          1527:                 playerCostMap[i][j] = 1;
        !          1528:                 monsterCostMap[i][j] = PDS_FORBIDDEN;
        !          1529:                 allySafetyMap[i][j] = 0;
        !          1530:             }
        !          1531:         }
        !          1532:     }
        !          1533:
        !          1534:     playerCostMap[player.xLoc][player.yLoc] = PDS_FORBIDDEN;
        !          1535:     monsterCostMap[player.xLoc][player.yLoc] = PDS_FORBIDDEN;
        !          1536:
        !          1537:     dijkstraScan(allySafetyMap, playerCostMap, false);
        !          1538:
        !          1539:     for (i=0; i<DCOLS; i++) {
        !          1540:         for (j=0; j<DROWS; j++) {
        !          1541:             if (monsterCostMap[i][j] < 0) {
        !          1542:                 continue;
        !          1543:             }
        !          1544:
        !          1545:             if (allySafetyMap[i][j] == 30000) {
        !          1546:                 allySafetyMap[i][j] = 150;
        !          1547:             }
        !          1548:
        !          1549:             allySafetyMap[i][j] = 50 * allySafetyMap[i][j] / (50 + allySafetyMap[i][j]);
        !          1550:
        !          1551:             allySafetyMap[i][j] *= -3;
        !          1552:
        !          1553:             if (pmap[i][j].flags & IN_LOOP) {
        !          1554:                 allySafetyMap[i][j] -= 10;
        !          1555:             }
        !          1556:         }
        !          1557:     }
        !          1558:     dijkstraScan(allySafetyMap, monsterCostMap, false);
        !          1559:
        !          1560:     freeGrid(playerCostMap);
        !          1561:     freeGrid(monsterCostMap);
        !          1562: }
        !          1563:
        !          1564: void resetDistanceCellInGrid(short **grid, short x, short y) {
        !          1565:     enum directions dir;
        !          1566:     short newX, newY;
        !          1567:     for (dir = 0; dir < 4; dir++) {
        !          1568:         newX = x + nbDirs[dir][0];
        !          1569:         newY = y + nbDirs[dir][1];
        !          1570:         if (coordinatesAreInMap(newX, newY)
        !          1571:             && grid[x][y] > grid[newX][newY] + 1) {
        !          1572:
        !          1573:             grid[x][y] = grid[newX][newY] + 1;
        !          1574:         }
        !          1575:     }
        !          1576: }
        !          1577:
        !          1578: void updateSafetyMap() {
        !          1579:     short i, j;
        !          1580:     short **playerCostMap, **monsterCostMap;
        !          1581:     creature *monst;
        !          1582:
        !          1583:     rogue.updatedSafetyMapThisTurn = true;
        !          1584:
        !          1585:     playerCostMap = allocGrid();
        !          1586:     monsterCostMap = allocGrid();
        !          1587:
        !          1588:     for (i=0; i<DCOLS; i++) {
        !          1589:         for (j=0; j<DROWS; j++) {
        !          1590:             safetyMap[i][j] = 30000;
        !          1591:
        !          1592:             playerCostMap[i][j] = monsterCostMap[i][j] = 1; // prophylactic
        !          1593:
        !          1594:             if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY)
        !          1595:                 && (!cellHasTMFlag(i, j, TM_IS_SECRET) || (discoveredTerrainFlagsAtLoc(i, j) & T_OBSTRUCTS_PASSABILITY))) {
        !          1596:
        !          1597:                 playerCostMap[i][j] = monsterCostMap[i][j] = cellHasTerrainFlag(i, j, T_OBSTRUCTS_DIAGONAL_MOVEMENT) ? PDS_OBSTRUCTION : PDS_FORBIDDEN;
        !          1598:             } else if (cellHasTerrainFlag(i, j, T_SACRED)) {
        !          1599:                 playerCostMap[i][j] = 1;
        !          1600:                 monsterCostMap[i][j] = PDS_FORBIDDEN;
        !          1601:             } else if (cellHasTerrainFlag(i, j, T_LAVA_INSTA_DEATH)) {
        !          1602:                 monsterCostMap[i][j] = PDS_FORBIDDEN;
        !          1603:                 if (player.status[STATUS_LEVITATING] || !player.status[STATUS_IMMUNE_TO_FIRE]) {
        !          1604:                     playerCostMap[i][j] = 1;
        !          1605:                 } else {
        !          1606:                     playerCostMap[i][j] = PDS_FORBIDDEN;
        !          1607:                 }
        !          1608:             } else {
        !          1609:                 if (pmap[i][j].flags & HAS_MONSTER) {
        !          1610:                     monst = monsterAtLoc(i, j);
        !          1611:                     if ((monst->creatureState == MONSTER_SLEEPING
        !          1612:                          || monst->turnsSpentStationary > 2
        !          1613:                          || (monst->info.flags & MONST_GETS_TURN_ON_ACTIVATION)
        !          1614:                          || monst->creatureState == MONSTER_ALLY)
        !          1615:                         && monst->creatureState != MONSTER_FLEEING) {
        !          1616:
        !          1617:                         playerCostMap[i][j] = 1;
        !          1618:                         monsterCostMap[i][j] = PDS_FORBIDDEN;
        !          1619:                         continue;
        !          1620:                     }
        !          1621:                 }
        !          1622:
        !          1623:                 if (cellHasTerrainFlag(i, j, (T_AUTO_DESCENT | T_IS_DF_TRAP))) {
        !          1624:                     monsterCostMap[i][j] = PDS_FORBIDDEN;
        !          1625:                     if (player.status[STATUS_LEVITATING]) {
        !          1626:                         playerCostMap[i][j] = 1;
        !          1627:                     } else {
        !          1628:                         playerCostMap[i][j] = PDS_FORBIDDEN;
        !          1629:                     }
        !          1630:                 } else if (cellHasTerrainFlag(i, j, T_IS_FIRE)) {
        !          1631:                     monsterCostMap[i][j] = PDS_FORBIDDEN;
        !          1632:                     if (player.status[STATUS_IMMUNE_TO_FIRE]) {
        !          1633:                         playerCostMap[i][j] = 1;
        !          1634:                     } else {
        !          1635:                         playerCostMap[i][j] = PDS_FORBIDDEN;
        !          1636:                     }
        !          1637:                 } else if (cellHasTerrainFlag(i, j, (T_IS_DEEP_WATER | T_SPONTANEOUSLY_IGNITES))) {
        !          1638:                     if (player.status[STATUS_LEVITATING]) {
        !          1639:                         playerCostMap[i][j] = 1;
        !          1640:                     } else {
        !          1641:                         playerCostMap[i][j] = 5;
        !          1642:                     }
        !          1643:                     monsterCostMap[i][j] = 5;
        !          1644:                 } else if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY)
        !          1645:                            && cellHasTMFlag(i, j, TM_IS_SECRET) && !(discoveredTerrainFlagsAtLoc(i, j) & T_OBSTRUCTS_PASSABILITY)
        !          1646:                            && !(pmap[i][j].flags & IN_FIELD_OF_VIEW)) {
        !          1647:                     // Secret door that the player can't currently see
        !          1648:                     playerCostMap[i][j] = 100;
        !          1649:                     monsterCostMap[i][j] = 1;
        !          1650:                 } else {
        !          1651:                     playerCostMap[i][j] = monsterCostMap[i][j] = 1;
        !          1652:                 }
        !          1653:             }
        !          1654:         }
        !          1655:     }
        !          1656:
        !          1657:     safetyMap[player.xLoc][player.yLoc] = 0;
        !          1658:     playerCostMap[player.xLoc][player.yLoc] = 1;
        !          1659:     monsterCostMap[player.xLoc][player.yLoc] = PDS_FORBIDDEN;
        !          1660:
        !          1661:     playerCostMap[rogue.upLoc[0]][rogue.upLoc[1]] = PDS_FORBIDDEN;
        !          1662:     monsterCostMap[rogue.upLoc[0]][rogue.upLoc[1]] = PDS_FORBIDDEN;
        !          1663:     playerCostMap[rogue.downLoc[0]][rogue.downLoc[1]] = PDS_FORBIDDEN;
        !          1664:     monsterCostMap[rogue.downLoc[0]][rogue.downLoc[1]] = PDS_FORBIDDEN;
        !          1665:
        !          1666:     dijkstraScan(safetyMap, playerCostMap, false);
        !          1667:
        !          1668:     for (i=0; i<DCOLS; i++) {
        !          1669:         for (j=0; j<DROWS; j++) {
        !          1670:             if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY)
        !          1671:                 && cellHasTMFlag(i, j, TM_IS_SECRET) && !(discoveredTerrainFlagsAtLoc(i, j) & T_OBSTRUCTS_PASSABILITY)
        !          1672:                 && !(pmap[i][j].flags & IN_FIELD_OF_VIEW)) {
        !          1673:
        !          1674:                 // Secret doors that the player can't see are not particularly safe themselves;
        !          1675:                 // the areas behind them are.
        !          1676:                 resetDistanceCellInGrid(safetyMap, i, j);
        !          1677:             }
        !          1678:         }
        !          1679:     }
        !          1680:
        !          1681:     for (i=0; i<DCOLS; i++) {
        !          1682:         for (j=0; j<DROWS; j++) {
        !          1683:             if (monsterCostMap[i][j] < 0) {
        !          1684:                 continue;
        !          1685:             }
        !          1686:
        !          1687:             if (safetyMap[i][j] == 30000) {
        !          1688:                 safetyMap[i][j] = 150;
        !          1689:             }
        !          1690:
        !          1691:             safetyMap[i][j] = 50 * safetyMap[i][j] / (50 + safetyMap[i][j]);
        !          1692:
        !          1693:             safetyMap[i][j] *= -3;
        !          1694:
        !          1695:             if (pmap[i][j].flags & IN_LOOP) {
        !          1696:                 safetyMap[i][j] -= 10;
        !          1697:             }
        !          1698:         }
        !          1699:     }
        !          1700:     dijkstraScan(safetyMap, monsterCostMap, false);
        !          1701:     for (i=0; i<DCOLS; i++) {
        !          1702:         for (j=0; j<DROWS; j++) {
        !          1703:             if (monsterCostMap[i][j] < 0) {
        !          1704:                 safetyMap[i][j] = 30000;
        !          1705:             }
        !          1706:         }
        !          1707:     }
        !          1708:     freeGrid(playerCostMap);
        !          1709:     freeGrid(monsterCostMap);
        !          1710: }
        !          1711:
        !          1712: void updateSafeTerrainMap() {
        !          1713:     short i, j;
        !          1714:     short **costMap;
        !          1715:     creature *monst;
        !          1716:
        !          1717:     rogue.updatedMapToSafeTerrainThisTurn = true;
        !          1718:     costMap = allocGrid();
        !          1719:
        !          1720:     for (i=0; i<DCOLS; i++) {
        !          1721:         for (j=0; j<DROWS; j++) {
        !          1722:             monst = monsterAtLoc(i, j);
        !          1723:             if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY)
        !          1724:                 && (!cellHasTMFlag(i, j, TM_IS_SECRET) || (discoveredTerrainFlagsAtLoc(i, j) & T_OBSTRUCTS_PASSABILITY))) {
        !          1725:
        !          1726:                 costMap[i][j] = cellHasTerrainFlag(i, j, T_OBSTRUCTS_DIAGONAL_MOVEMENT) ? PDS_OBSTRUCTION : PDS_FORBIDDEN;
        !          1727:                 rogue.mapToSafeTerrain[i][j] = 30000; // OOS prophylactic
        !          1728:             } else if ((monst && (monst->turnsSpentStationary > 1 || (monst->info.flags & MONST_GETS_TURN_ON_ACTIVATION)))
        !          1729:                        || (cellHasTerrainFlag(i, j, T_PATHING_BLOCKER & ~T_HARMFUL_TERRAIN) && !cellHasTMFlag(i, j, TM_IS_SECRET))) {
        !          1730:
        !          1731:                 costMap[i][j] = PDS_FORBIDDEN;
        !          1732:                 rogue.mapToSafeTerrain[i][j] = 30000;
        !          1733:             } else if (cellHasTerrainFlag(i, j, T_HARMFUL_TERRAIN) || pmap[i][j].layers[DUNGEON] == DOOR) {
        !          1734:                 // The door thing is an aesthetically offensive but necessary hack to make sure
        !          1735:                 // that monsters trying to find their way out of caustic gas do not sprint for
        !          1736:                 // the doors. Doors are superficially free of gas, but as soon as they are opened,
        !          1737:                 // gas will fill their tile, so they are not actually safe. Without this fix,
        !          1738:                 // allies will fidget back and forth in a doorway while they asphyxiate.
        !          1739:                 // This will have to do. It's a difficult problem to solve elegantly.
        !          1740:                 costMap[i][j] = 1;
        !          1741:                 rogue.mapToSafeTerrain[i][j] = 30000;
        !          1742:             } else {
        !          1743:                 costMap[i][j] = 1;
        !          1744:                 rogue.mapToSafeTerrain[i][j] = 0;
        !          1745:             }
        !          1746:         }
        !          1747:     }
        !          1748:     dijkstraScan(rogue.mapToSafeTerrain, costMap, false);
        !          1749:     freeGrid(costMap);
        !          1750: }
        !          1751:
        !          1752: void processIncrementalAutoID() {
        !          1753:     item *theItem, *autoIdentifyItems[3] = {rogue.armor, rogue.ringLeft, rogue.ringRight};
        !          1754:     char buf[DCOLS*3], theItemName[DCOLS*3];
        !          1755:     short i;
        !          1756:
        !          1757:     for (i=0; i<3; i++) {
        !          1758:         theItem = autoIdentifyItems[i];
        !          1759:         if (theItem
        !          1760:             && theItem->charges > 0
        !          1761:             && (!(theItem->flags & ITEM_IDENTIFIED) || ((theItem->category & RING) && !ringTable[theItem->kind].identified))) {
        !          1762:
        !          1763:             theItem->charges--;
        !          1764:             if (theItem->charges <= 0) {
        !          1765:                 itemName(theItem, theItemName, false, false, NULL);
        !          1766:                 sprintf(buf, "you are now familiar enough with your %s to identify it.", theItemName);
        !          1767:                 messageWithColor(buf, &itemMessageColor, false);
        !          1768:
        !          1769:                 if (theItem->category & ARMOR) {
        !          1770:                     // Don't necessarily reveal the armor's runic specifically, just that it has one.
        !          1771:                     theItem->flags |= ITEM_IDENTIFIED;
        !          1772:                 } else if (theItem->category & RING) {
        !          1773:                     identify(theItem);
        !          1774:                 }
        !          1775:                 updateIdentifiableItems();
        !          1776:
        !          1777:                 itemName(theItem, theItemName, true, true, NULL);
        !          1778:                 sprintf(buf, "%s %s.", (theItem->quantity > 1 ? "they are" : "it is"), theItemName);
        !          1779:                 messageWithColor(buf, &itemMessageColor, false);
        !          1780:             }
        !          1781:         }
        !          1782:     }
        !          1783: }
        !          1784:
        !          1785: short staffChargeDuration(const item *theItem) {
        !          1786:     // staffs of blinking and obstruction recharge half as fast so they're less powerful
        !          1787:     return (theItem->kind == STAFF_BLINKING || theItem->kind == STAFF_OBSTRUCTION ? 10000 : 5000) / theItem->enchant1;
        !          1788: }
        !          1789:
        !          1790: // Multiplier can be negative, in which case staffs and charms will be drained instead of recharged.
        !          1791: void rechargeItemsIncrementally(short multiplier) {
        !          1792:     item *theItem;
        !          1793:     char buf[DCOLS*3], theItemName[DCOLS*3];
        !          1794:     short rechargeIncrement, staffRechargeDuration;
        !          1795:
        !          1796:     if (rogue.wisdomBonus) {
        !          1797:         // at level 27, you recharge anything to full in one turn
        !          1798:         rechargeIncrement = 10 * ringWisdomMultiplier(rogue.wisdomBonus * FP_FACTOR) / FP_FACTOR;
        !          1799:     } else {
        !          1800:         rechargeIncrement = 10;
        !          1801:     }
        !          1802:
        !          1803:     rechargeIncrement *= multiplier;
        !          1804:
        !          1805:     for (theItem = packItems->nextItem; theItem != NULL; theItem = theItem->nextItem) {
        !          1806:         if (theItem->category & STAFF) {
        !          1807:             if (theItem->charges < theItem->enchant1 && rechargeIncrement > 0
        !          1808:                 || theItem->charges > 0 && rechargeIncrement < 0) {
        !          1809:
        !          1810:                 theItem->enchant2 -= rechargeIncrement;
        !          1811:             }
        !          1812:             staffRechargeDuration = staffChargeDuration(theItem);
        !          1813:             while (theItem->enchant2 <= 0) {
        !          1814:                 // if it's time to add a staff charge
        !          1815:                 if (theItem->charges < theItem->enchant1) {
        !          1816:                     theItem->charges++;
        !          1817:                 }
        !          1818:                 theItem->enchant2 += randClumpedRange(max(staffRechargeDuration / 3, 1), staffRechargeDuration * 5 / 3, 3);
        !          1819:             }
        !          1820:             while (theItem->enchant2 > staffRechargeDuration * 5 / 3) {
        !          1821:                 // if it's time to drain a staff charge
        !          1822:                 if (theItem->charges > 0) {
        !          1823:                     theItem->charges--;
        !          1824:                 }
        !          1825:                 theItem->enchant2 -= staffRechargeDuration;
        !          1826:             }
        !          1827:         } else if ((theItem->category & CHARM) && (theItem->charges > 0)) {
        !          1828:             theItem->charges = clamp(theItem->charges - multiplier, 0, charmRechargeDelay(theItem->kind, theItem->enchant1));
        !          1829:             if (theItem->charges == 0) {
        !          1830:                 itemName(theItem, theItemName, false, false, NULL);
        !          1831:                 sprintf(buf, "your %s has recharged.", theItemName);
        !          1832:                 message(buf, false);
        !          1833:             }
        !          1834:         }
        !          1835:     }
        !          1836: }
        !          1837:
        !          1838: void extinguishFireOnCreature(creature *monst) {
        !          1839:
        !          1840:     monst->status[STATUS_BURNING] = 0;
        !          1841:     if (monst == &player) {
        !          1842:         player.info.foreColor = &white;
        !          1843:         rogue.minersLight.lightColor = &minersLightColor;
        !          1844:         refreshDungeonCell(player.xLoc, player.yLoc);
        !          1845:         updateVision(true);
        !          1846:         message("you are no longer on fire.", false);
        !          1847:     }
        !          1848: }
        !          1849:
        !          1850: // n is the monster's depthLevel - 1.
        !          1851: void monsterEntersLevel(creature *monst, short n) {
        !          1852:     creature *prevMonst;
        !          1853:     char monstName[COLS], buf[COLS];
        !          1854:     boolean pit = false;
        !          1855:
        !          1856:     if (rogue.patchVersion >= 3) {
        !          1857:         levels[n].mapStorage[monst->xLoc][monst->yLoc].flags &= ~HAS_MONSTER;
        !          1858:     }
        !          1859:
        !          1860:     // place traversing monster near the stairs on this level
        !          1861:     if (monst->bookkeepingFlags & MB_APPROACHING_DOWNSTAIRS) {
        !          1862:         monst->xLoc = rogue.upLoc[0];
        !          1863:         monst->yLoc = rogue.upLoc[1];
        !          1864:     } else if (monst->bookkeepingFlags & MB_APPROACHING_UPSTAIRS) {
        !          1865:         monst->xLoc = rogue.downLoc[0];
        !          1866:         monst->yLoc = rogue.downLoc[1];
        !          1867:     } else if (monst->bookkeepingFlags & MB_APPROACHING_PIT) { // jumping down pit
        !          1868:         pit = true;
        !          1869:         monst->xLoc = levels[n].playerExitedVia[0];
        !          1870:         monst->yLoc = levels[n].playerExitedVia[1];
        !          1871:     } else {
        !          1872:         brogueAssert(false);
        !          1873:     }
        !          1874:     monst->depth = rogue.depthLevel;
        !          1875:     monst->targetCorpseLoc[0] = monst->targetCorpseLoc[1] = 0;
        !          1876:
        !          1877:     if (!pit) {
        !          1878:         getQualifyingPathLocNear(&(monst->xLoc), &(monst->yLoc), monst->xLoc, monst->yLoc, true,
        !          1879:                                  T_DIVIDES_LEVEL & avoidedFlagsForMonster(&(monst->info)), 0,
        !          1880:                                  avoidedFlagsForMonster(&(monst->info)), HAS_STAIRS, false);
        !          1881:     }
        !          1882:     if (!pit
        !          1883:         && (pmap[monst->xLoc][monst->yLoc].flags & (HAS_PLAYER | HAS_MONSTER))
        !          1884:         && !(terrainFlags(monst->xLoc, monst->yLoc) & avoidedFlagsForMonster(&(monst->info)))) {
        !          1885:         // Monsters using the stairs will displace any creatures already located there, to thwart stair-dancing.
        !          1886:         prevMonst = monsterAtLoc(monst->xLoc, monst->yLoc);
        !          1887:         brogueAssert(prevMonst);
        !          1888:         getQualifyingPathLocNear(&(prevMonst->xLoc), &(prevMonst->yLoc), monst->xLoc, monst->yLoc, true,
        !          1889:                                  T_DIVIDES_LEVEL & avoidedFlagsForMonster(&(prevMonst->info)), 0,
        !          1890:                                  avoidedFlagsForMonster(&(prevMonst->info)), (HAS_MONSTER | HAS_PLAYER | HAS_STAIRS), false);
        !          1891:         pmap[monst->xLoc][monst->yLoc].flags &= ~(HAS_PLAYER | HAS_MONSTER);
        !          1892:         pmap[prevMonst->xLoc][prevMonst->yLoc].flags |= (prevMonst == &player ? HAS_PLAYER : HAS_MONSTER);
        !          1893:         refreshDungeonCell(prevMonst->xLoc, prevMonst->yLoc);
        !          1894:         //DEBUG printf("\nBumped a creature (%s) from (%i, %i) to (%i, %i).", prevMonst->info.monsterName, monst->xLoc, monst->yLoc, prevMonst->xLoc, prevMonst->yLoc);
        !          1895:     }
        !          1896:
        !          1897:     // remove traversing monster from other level monster chain
        !          1898:     if (monst == levels[n].monsters) {
        !          1899:         levels[n].monsters = monst->nextCreature;
        !          1900:     } else {
        !          1901:         for (prevMonst = levels[n].monsters; prevMonst->nextCreature != monst; prevMonst = prevMonst->nextCreature);
        !          1902:         prevMonst->nextCreature = monst->nextCreature;
        !          1903:     }
        !          1904:
        !          1905:     // prepend traversing monster to current level monster chain
        !          1906:     monst->nextCreature = monsters->nextCreature;
        !          1907:     monsters->nextCreature = monst;
        !          1908:
        !          1909:     monst->status[STATUS_ENTERS_LEVEL_IN] = 0;
        !          1910:     monst->bookkeepingFlags |= MB_PREPLACED;
        !          1911:     monst->bookkeepingFlags &= ~MB_IS_FALLING;
        !          1912:     restoreMonster(monst, NULL, NULL);
        !          1913:     //DEBUG printf("\nPlaced a creature (%s) at (%i, %i).", monst->info.monsterName, monst->xLoc, monst->yLoc);
        !          1914:     monst->ticksUntilTurn = monst->movementSpeed;
        !          1915:     refreshDungeonCell(monst->xLoc, monst->yLoc);
        !          1916:
        !          1917:     if (pit) {
        !          1918:         monsterName(monstName, monst, true);
        !          1919:         if (!monst->status[STATUS_LEVITATING]) {
        !          1920:             if (inflictDamage(NULL, monst, randClumpedRange(6, 12, 2), &red, false)) {
        !          1921:                 if (canSeeMonster(monst)) {
        !          1922:                     sprintf(buf, "%s plummets from above and splatters against the ground!", monstName);
        !          1923:                     messageWithColor(buf, messageColorFromVictim(monst), false);
        !          1924:                 }
        !          1925:             } else {
        !          1926:                 if (canSeeMonster(monst)) {
        !          1927:                     sprintf(buf, "%s falls from above and crashes to the ground!", monstName);
        !          1928:                     message(buf, false);
        !          1929:                 }
        !          1930:             }
        !          1931:         } else if (canSeeMonster(monst)) {
        !          1932:             sprintf(buf, "%s swoops into the cavern from above.", monstName);
        !          1933:             message(buf, false);
        !          1934:         }
        !          1935:     }
        !          1936: }
        !          1937:
        !          1938: void monstersApproachStairs() {
        !          1939:     creature *monst, *nextMonst;
        !          1940:     short n;
        !          1941:
        !          1942:     for (n = rogue.depthLevel - 2; n <= rogue.depthLevel; n += 2) { // cycle through previous and next level
        !          1943:         if (n >= 0 && n < DEEPEST_LEVEL && levels[n].visited) {
        !          1944:             for (monst = levels[n].monsters; monst != NULL;) {
        !          1945:                 nextMonst = monst->nextCreature;
        !          1946:                 if (monst->status[STATUS_ENTERS_LEVEL_IN] > 1) {
        !          1947:                     monst->status[STATUS_ENTERS_LEVEL_IN]--;
        !          1948:                 } else if (monst->status[STATUS_ENTERS_LEVEL_IN] == 1) {
        !          1949:                     monsterEntersLevel(monst, n);
        !          1950:                 }
        !          1951:                 monst = nextMonst;
        !          1952:             }
        !          1953:         }
        !          1954:     }
        !          1955:
        !          1956:     if (rogue.yendorWarden
        !          1957:         && abs(rogue.depthLevel - rogue.yendorWarden->depth) > 1) {
        !          1958:
        !          1959:         updateYendorWardenTracking();
        !          1960:     }
        !          1961: }
        !          1962:
        !          1963: void decrementPlayerStatus() {
        !          1964:     // Handle hunger.
        !          1965:     if (!player.status[STATUS_PARALYZED]) {
        !          1966:         // No nutrition is expended while paralyzed.
        !          1967:         if (player.status[STATUS_NUTRITION] > 0) {
        !          1968:             if (!numberOfMatchingPackItems(AMULET, 0, 0, false) || rand_percent(20)) {
        !          1969:                 player.status[STATUS_NUTRITION]--;
        !          1970:             }
        !          1971:         }
        !          1972:         checkNutrition();
        !          1973:     }
        !          1974:
        !          1975:     if (player.status[STATUS_TELEPATHIC] > 0 && !--player.status[STATUS_TELEPATHIC]) {
        !          1976:         updateVision(true);
        !          1977:         message("your preternatural mental sensitivity fades.", false);
        !          1978:     }
        !          1979:
        !          1980:     if (player.status[STATUS_DARKNESS] > 0) {
        !          1981:         player.status[STATUS_DARKNESS]--;
        !          1982:         updateMinersLightRadius();
        !          1983:         //updateVision();
        !          1984:     }
        !          1985:
        !          1986:     if (player.status[STATUS_HALLUCINATING] > 0 && !--player.status[STATUS_HALLUCINATING]) {
        !          1987:         displayLevel();
        !          1988:         message("your hallucinations fade.", false);
        !          1989:     }
        !          1990:
        !          1991:     if (player.status[STATUS_LEVITATING] > 0 && !--player.status[STATUS_LEVITATING]) {
        !          1992:         message("you are no longer levitating.", false);
        !          1993:     }
        !          1994:
        !          1995:     if (player.status[STATUS_CONFUSED] > 0 && !--player.status[STATUS_CONFUSED]) {
        !          1996:         message("you no longer feel confused.", false);
        !          1997:     }
        !          1998:
        !          1999:     if (player.status[STATUS_NAUSEOUS] > 0 && !--player.status[STATUS_NAUSEOUS]) {
        !          2000:         message("you feel less nauseous.", false);
        !          2001:     }
        !          2002:
        !          2003:     if (player.status[STATUS_PARALYZED] > 0 && !--player.status[STATUS_PARALYZED]) {
        !          2004:         message("you can move again.", false);
        !          2005:     }
        !          2006:
        !          2007:     if (player.status[STATUS_HASTED] > 0 && !--player.status[STATUS_HASTED]) {
        !          2008:         player.movementSpeed = player.info.movementSpeed;
        !          2009:         player.attackSpeed = player.info.attackSpeed;
        !          2010:         synchronizePlayerTimeState();
        !          2011:         message("your supernatural speed fades.", false);
        !          2012:     }
        !          2013:
        !          2014:     if (player.status[STATUS_SLOWED] > 0 && !--player.status[STATUS_SLOWED]) {
        !          2015:         player.movementSpeed = player.info.movementSpeed;
        !          2016:         player.attackSpeed = player.info.attackSpeed;
        !          2017:         synchronizePlayerTimeState();
        !          2018:         message("your normal speed resumes.", false);
        !          2019:     }
        !          2020:
        !          2021:     if (player.status[STATUS_WEAKENED] > 0 && !--player.status[STATUS_WEAKENED]) {
        !          2022:         player.weaknessAmount = 0;
        !          2023:         message("strength returns to your muscles as the weakening toxin wears off.", false);
        !          2024:         updateEncumbrance();
        !          2025:     }
        !          2026:
        !          2027:     if (player.status[STATUS_DONNING]) {
        !          2028:         player.status[STATUS_DONNING]--;
        !          2029:         recalculateEquipmentBonuses();
        !          2030:     }
        !          2031:
        !          2032:     if (player.status[STATUS_IMMUNE_TO_FIRE] > 0 && !--player.status[STATUS_IMMUNE_TO_FIRE]) {
        !          2033:         message("you no longer feel immune to fire.", false);
        !          2034:     }
        !          2035:
        !          2036:     if (player.status[STATUS_STUCK] && !cellHasTerrainFlag(player.xLoc, player.yLoc, T_ENTANGLES)) {
        !          2037:         player.status[STATUS_STUCK] = 0;
        !          2038:     }
        !          2039:
        !          2040:     if (player.status[STATUS_EXPLOSION_IMMUNITY]) {
        !          2041:         player.status[STATUS_EXPLOSION_IMMUNITY]--;
        !          2042:     }
        !          2043:
        !          2044:     if (player.status[STATUS_DISCORDANT]) {
        !          2045:         player.status[STATUS_DISCORDANT]--;
        !          2046:     }
        !          2047:
        !          2048:     if (player.status[STATUS_AGGRAVATING]) {
        !          2049:         player.status[STATUS_AGGRAVATING]--;
        !          2050:     }
        !          2051:
        !          2052:     if (player.status[STATUS_SHIELDED]) {
        !          2053:         player.status[STATUS_SHIELDED] -= player.maxStatus[STATUS_SHIELDED] / 20;
        !          2054:         if (player.status[STATUS_SHIELDED] <= 0) {
        !          2055:             player.status[STATUS_SHIELDED] = player.maxStatus[STATUS_SHIELDED] = 0;
        !          2056:         }
        !          2057:     }
        !          2058:
        !          2059:     if (player.status[STATUS_INVISIBLE] > 0 && !--player.status[STATUS_INVISIBLE]) {
        !          2060:         message("you are no longer invisible.", false);
        !          2061:     }
        !          2062:
        !          2063:     if (rogue.monsterSpawnFuse <= 0) {
        !          2064:         spawnPeriodicHorde();
        !          2065:         rogue.monsterSpawnFuse = rand_range(125, 175);
        !          2066:     }
        !          2067: }
        !          2068:
        !          2069: boolean dangerChanged(boolean danger[4]) {
        !          2070:     enum directions dir;
        !          2071:     short newX, newY;
        !          2072:     for (dir = 0; dir < 4; dir++) {
        !          2073:         newX = player.xLoc + nbDirs[dir][0];
        !          2074:         newY = player.yLoc + nbDirs[dir][1];
        !          2075:         if (danger[dir] != monsterAvoids(&player, newX, newY)) {
        !          2076:             return true;
        !          2077:         }
        !          2078:     }
        !          2079:     return false;
        !          2080: }
        !          2081:
        !          2082: void autoRest() {
        !          2083:     short i = 0;
        !          2084:     boolean initiallyEmbedded; // Stop as soon as we're free from crystal.
        !          2085:     boolean danger[4];
        !          2086:     short newX, newY;
        !          2087:     enum directions dir;
        !          2088:
        !          2089:     for (dir = 0; dir < 4; dir++) {
        !          2090:         newX = player.xLoc + nbDirs[dir][0];
        !          2091:         newY = player.yLoc + nbDirs[dir][1];
        !          2092:         danger[dir] = monsterAvoids(&player, newX, newY);
        !          2093:     }
        !          2094:
        !          2095:     rogue.disturbed = false;
        !          2096:     rogue.automationActive = true;
        !          2097:     initiallyEmbedded = cellHasTerrainFlag(player.xLoc, player.yLoc, T_OBSTRUCTS_PASSABILITY);
        !          2098:
        !          2099:     if ((player.currentHP < player.info.maxHP
        !          2100:          || player.status[STATUS_HALLUCINATING]
        !          2101:          || player.status[STATUS_CONFUSED]
        !          2102:          || player.status[STATUS_NAUSEOUS]
        !          2103:          || player.status[STATUS_POISONED]
        !          2104:          || player.status[STATUS_DARKNESS]
        !          2105:          || initiallyEmbedded)
        !          2106:         && !rogue.disturbed) {
        !          2107:         while (i++ < TURNS_FOR_FULL_REGEN
        !          2108:                && (player.currentHP < player.info.maxHP
        !          2109:                    || player.status[STATUS_HALLUCINATING]
        !          2110:                    || player.status[STATUS_CONFUSED]
        !          2111:                    || player.status[STATUS_NAUSEOUS]
        !          2112:                    || player.status[STATUS_POISONED]
        !          2113:                    || player.status[STATUS_DARKNESS]
        !          2114:                    || cellHasTerrainFlag(player.xLoc, player.yLoc, T_OBSTRUCTS_PASSABILITY))
        !          2115:                && !rogue.disturbed
        !          2116:                && (!initiallyEmbedded || cellHasTerrainFlag(player.xLoc, player.yLoc, T_OBSTRUCTS_PASSABILITY))) {
        !          2117:
        !          2118:             recordKeystroke(REST_KEY, false, false);
        !          2119:             rogue.justRested = true;
        !          2120:             playerTurnEnded();
        !          2121:             if (dangerChanged(danger) || pauseBrogue(1)) {
        !          2122:                 rogue.disturbed = true;
        !          2123:             }
        !          2124:         }
        !          2125:     } else {
        !          2126:         for (i=0; i<100 && !rogue.disturbed; i++) {
        !          2127:             recordKeystroke(REST_KEY, false, false);
        !          2128:             rogue.justRested = true;
        !          2129:             playerTurnEnded();
        !          2130:             if (dangerChanged(danger) || pauseBrogue(1)) {
        !          2131:                 rogue.disturbed = true;
        !          2132:             }
        !          2133:         }
        !          2134:     }
        !          2135:     rogue.automationActive = false;
        !          2136: }
        !          2137:
        !          2138: void manualSearch() {
        !          2139:     recordKeystroke(SEARCH_KEY, false, false);
        !          2140:
        !          2141:     if (player.status[STATUS_SEARCHING] <= 0) {
        !          2142:         player.status[STATUS_SEARCHING] = 0;
        !          2143:         player.maxStatus[STATUS_SEARCHING] = 5;
        !          2144:     }
        !          2145:
        !          2146:     player.status[STATUS_SEARCHING] += 1;
        !          2147:
        !          2148:     /* The search strength values were chosen based on equating the expected
        !          2149:     number of cells discovered by 5x 80 searches (1.7.4) and 1x 200 search
        !          2150:     (1.7.5). 1x200 discovers an average of 932 cells; 5.65 times more cells than
        !          2151:     the 165 of 5x80. This factor is intepreted as the advantage of undelayed
        !          2152:     searching. Hence, we chose a short radius r and a long radius s such that
        !          2153:
        !          2154:         4 * 5.65 * E_r + E_s ~= 932
        !          2155:
        !          2156:     where E_x is the expected no. of cells discovered with radius x. We choose
        !          2157:     r=60, s=160, giving 852 < 932 (under to account for removal of 1.7.5 stealth
        !          2158:     range doubling).
        !          2159:     */
        !          2160:     short searchStrength = 0;
        !          2161:     if (player.status[STATUS_SEARCHING] < 5) {
        !          2162:         searchStrength = (rogue.awarenessBonus >= 0 ? 60 : 30);
        !          2163:     } else {
        !          2164:         // Do a final, larger-radius search on the fifth search in a row
        !          2165:         searchStrength = 160;
        !          2166:         message("you finish your detailed search of the area.", false);
        !          2167:         player.status[STATUS_SEARCHING] = 0;
        !          2168:     }
        !          2169:
        !          2170:     // ensure our search is no weaker than the current passive search
        !          2171:     search(max(searchStrength, rogue.awarenessBonus + 30));
        !          2172:
        !          2173:     rogue.justSearched = true;
        !          2174:     playerTurnEnded();
        !          2175: }
        !          2176:
        !          2177: // Call this periodically (when haste/slow wears off and when moving between depths)
        !          2178: // to keep environmental updates in sync with player turns.
        !          2179: void synchronizePlayerTimeState() {
        !          2180:     rogue.ticksTillUpdateEnvironment = player.ticksUntilTurn;
        !          2181: }
        !          2182:
        !          2183: void playerRecoversFromAttacking(boolean anAttackHit) {
        !          2184:     if (player.ticksUntilTurn >= 0) {
        !          2185:         // Don't do this if the player's weapon of speed just fired.
        !          2186:         if (rogue.weapon && (rogue.weapon->flags & ITEM_ATTACKS_STAGGER) && anAttackHit) {
        !          2187:             player.ticksUntilTurn += 2 * player.attackSpeed;
        !          2188:         } else if (rogue.weapon && (rogue.weapon->flags & ITEM_ATTACKS_QUICKLY)) {
        !          2189:             player.ticksUntilTurn += player.attackSpeed / 2;
        !          2190:         } else {
        !          2191:             player.ticksUntilTurn += player.attackSpeed;
        !          2192:         }
        !          2193:     }
        !          2194: }
        !          2195:
        !          2196:
        !          2197: static void recordCurrentCreatureHealths() {
        !          2198:     creature *monst;
        !          2199:     CYCLE_MONSTERS_AND_PLAYERS(monst) {
        !          2200:         monst->previousHealthPoints = monst->currentHP;
        !          2201:     }
        !          2202: }
        !          2203:
        !          2204: // This is the dungeon schedule manager, called every time the player's turn comes to an end.
        !          2205: // It hands control over to monsters until they've all expended their accumulated ticks,
        !          2206: // updating the environment (gas spreading, flames spreading and burning out, etc.) every
        !          2207: // 100 ticks.
        !          2208: void playerTurnEnded() {
        !          2209:     short soonestTurn, damage, turnsRequiredToShore, turnsToShore;
        !          2210:     char buf[COLS], buf2[COLS];
        !          2211:     creature *monst, *monst2, *nextMonst;
        !          2212:     boolean fastForward = false;
        !          2213:     short oldRNG;
        !          2214:
        !          2215:     brogueAssert(rogue.RNG == RNG_SUBSTANTIVE);
        !          2216:
        !          2217:     handleXPXP();
        !          2218:     resetDFMessageEligibility();
        !          2219:     recordCurrentCreatureHealths();
        !          2220:
        !          2221:     if (player.bookkeepingFlags & MB_IS_FALLING) {
        !          2222:         playerFalls();
        !          2223:         if (!rogue.gameHasEnded) {
        !          2224:             handleHealthAlerts();
        !          2225:         }
        !          2226:         return;
        !          2227:     }
        !          2228:
        !          2229:     // This happens in updateEnvironment, but some monsters move faster than the
        !          2230:     // environment updates in the loop below. This means they need to fall at
        !          2231:     // the start of the turn to avoid them being able to act while suspended
        !          2232:     // over a chasm
        !          2233:     monstersFall();
        !          2234:
        !          2235:     do {
        !          2236:         if (rogue.gameHasEnded) {
        !          2237:             return;
        !          2238:         }
        !          2239:
        !          2240:         if (!player.status[STATUS_PARALYZED]) {
        !          2241:             rogue.playerTurnNumber++; // So recordings don't register more turns than you actually have.
        !          2242:         }
        !          2243:         rogue.absoluteTurnNumber++;
        !          2244:
        !          2245:         if (player.status[STATUS_INVISIBLE]) {
        !          2246:             rogue.scentTurnNumber += 10; // Your scent fades very quickly while you are invisible.
        !          2247:         } else {
        !          2248:             rogue.scentTurnNumber += 3; // this must happen per subjective player time
        !          2249:         }
        !          2250:         if (rogue.scentTurnNumber > 20000) {
        !          2251:             resetScentTurnNumber();
        !          2252:         }
        !          2253:
        !          2254:         //updateFlavorText();
        !          2255:
        !          2256:         // Regeneration/starvation:
        !          2257:         if (player.status[STATUS_NUTRITION] <= 0) {
        !          2258:             player.currentHP--;
        !          2259:             if (player.currentHP <= 0) {
        !          2260:                 gameOver("Starved to death", true);
        !          2261:                 return;
        !          2262:             }
        !          2263:         } else if (player.currentHP < player.info.maxHP
        !          2264:                    && !player.status[STATUS_POISONED]) {
        !          2265:             if ((player.turnsUntilRegen -= 1000) <= 0) {
        !          2266:                 player.currentHP++;
        !          2267:                 if (player.previousHealthPoints < player.currentHP) {
        !          2268:                     player.previousHealthPoints++; // Regeneration doesn't display on the status bar.
        !          2269:                 }
        !          2270:                 player.turnsUntilRegen += player.info.turnsBetweenRegen;
        !          2271:             }
        !          2272:             if (player.regenPerTurn) {
        !          2273:                 player.currentHP += player.regenPerTurn;
        !          2274:                 if (player.previousHealthPoints < player.currentHP) {
        !          2275:                     player.previousHealthPoints = min(player.currentHP, player.previousHealthPoints + player.regenPerTurn);
        !          2276:                 }
        !          2277:             }
        !          2278:         }
        !          2279:
        !          2280:         if (rogue.awarenessBonus > -30 && !(pmap[player.xLoc][player.yLoc].flags & SEARCHED_FROM_HERE)) {
        !          2281:             // Low-grade auto-search wherever you step, but only once per tile.
        !          2282:             search(rogue.awarenessBonus + 30);
        !          2283:             pmap[player.xLoc][player.yLoc].flags |= SEARCHED_FROM_HERE;
        !          2284:         }
        !          2285:         if (!rogue.justSearched && player.status[STATUS_SEARCHING] > 0) {
        !          2286:             // Searching only "charges up" when done on consecutive turns
        !          2287:             player.status[STATUS_SEARCHING] = 0;
        !          2288:         }
        !          2289:         if (rogue.staleLoopMap) {
        !          2290:             analyzeMap(false); // Don't need to update the chokemap.
        !          2291:         }
        !          2292:
        !          2293:         for (monst = monsters->nextCreature; monst != NULL; monst = nextMonst) {
        !          2294:             nextMonst = monst->nextCreature;
        !          2295:             if ((monst->bookkeepingFlags & MB_BOUND_TO_LEADER)
        !          2296:                 && (!monst->leader || !(monst->bookkeepingFlags & MB_FOLLOWER))
        !          2297:                 && (monst->creatureState != MONSTER_ALLY)) {
        !          2298:
        !          2299:                 killCreature(monst, false);
        !          2300:                 if (canSeeMonster(monst)) {
        !          2301:                     monsterName(buf2, monst, true);
        !          2302:                     sprintf(buf, "%s dissipates into thin air", buf2);
        !          2303:                     combatMessage(buf, messageColorFromVictim(monst));
        !          2304:                 }
        !          2305:             }
        !          2306:         }
        !          2307:
        !          2308:         if (player.status[STATUS_BURNING] > 0) {
        !          2309:             damage = rand_range(1, 3);
        !          2310:             if (!(player.status[STATUS_IMMUNE_TO_FIRE]) && inflictDamage(NULL, &player, damage, &orange, true)) {
        !          2311:                 gameOver("Burned to death", true);
        !          2312:             }
        !          2313:             if (!--player.status[STATUS_BURNING]) {
        !          2314:                 extinguishFireOnCreature(&player);
        !          2315:             }
        !          2316:         }
        !          2317:
        !          2318:         if (player.status[STATUS_POISONED] > 0) {
        !          2319:             player.status[STATUS_POISONED]--;
        !          2320:             if (inflictDamage(NULL, &player, player.poisonAmount, &green, true)) {
        !          2321:                 gameOver("Died from poison", true);
        !          2322:             }
        !          2323:             if (!player.status[STATUS_POISONED]) {
        !          2324:                 player.poisonAmount = 0;
        !          2325:             }
        !          2326:         }
        !          2327:
        !          2328:         if (player.ticksUntilTurn == 0) { // attacking adds ticks elsewhere
        !          2329:             player.ticksUntilTurn += player.movementSpeed;
        !          2330:         } else if (player.ticksUntilTurn < 0) { // if he gets a free turn
        !          2331:             player.ticksUntilTurn = 0;
        !          2332:         }
        !          2333:
        !          2334:         updateScent();
        !          2335: //      updateVision(true);
        !          2336: //        rogue.aggroRange = currentAggroValue();
        !          2337: //        if (rogue.displayAggroRangeMode) {
        !          2338: //            displayLevel();
        !          2339: //        }
        !          2340:         rogue.updatedSafetyMapThisTurn          = false;
        !          2341:         rogue.updatedAllySafetyMapThisTurn      = false;
        !          2342:         rogue.updatedMapToSafeTerrainThisTurn   = false;
        !          2343:
        !          2344:         for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
        !          2345:             if (D_SAFETY_VISION || monst->creatureState == MONSTER_FLEEING && pmap[monst->xLoc][monst->yLoc].flags & IN_FIELD_OF_VIEW) {
        !          2346:                 updateSafetyMap(); // only if there is a fleeing monster who can see the player
        !          2347:                 break;
        !          2348:             }
        !          2349:         }
        !          2350:
        !          2351:         if (D_BULLET_TIME && !rogue.justRested) {
        !          2352:             player.ticksUntilTurn = 0;
        !          2353:         }
        !          2354:
        !          2355:         applyGradualTileEffectsToCreature(&player, player.ticksUntilTurn);
        !          2356:
        !          2357:         if (rogue.gameHasEnded) {
        !          2358:             return;
        !          2359:         }
        !          2360:
        !          2361:         rogue.heardCombatThisTurn = false;
        !          2362:
        !          2363:         while (player.ticksUntilTurn > 0) {
        !          2364:             soonestTurn = 10000;
        !          2365:             for(monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
        !          2366:                 soonestTurn = min(soonestTurn, monst->ticksUntilTurn);
        !          2367:             }
        !          2368:             soonestTurn = min(soonestTurn, player.ticksUntilTurn);
        !          2369:             soonestTurn = min(soonestTurn, rogue.ticksTillUpdateEnvironment);
        !          2370:             for(monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
        !          2371:                 monst->ticksUntilTurn -= soonestTurn;
        !          2372:             }
        !          2373:             rogue.ticksTillUpdateEnvironment -= soonestTurn;
        !          2374:             if (rogue.ticksTillUpdateEnvironment <= 0) {
        !          2375:                 rogue.ticksTillUpdateEnvironment += 100;
        !          2376:
        !          2377:                 // stuff that happens periodically according to an objective time measurement goes here:
        !          2378:                 rechargeItemsIncrementally(1); // staffs recharge every so often
        !          2379:                 processIncrementalAutoID();   // become more familiar with worn armor and rings
        !          2380:                 rogue.monsterSpawnFuse--; // monsters spawn in the level every so often
        !          2381:
        !          2382:                 for (monst = monsters->nextCreature; monst != NULL;) {
        !          2383:                     nextMonst = monst->nextCreature;
        !          2384:                     applyInstantTileEffectsToCreature(monst);
        !          2385:                     monst = nextMonst; // this weirdness is in case the monster dies in the previous step
        !          2386:                 }
        !          2387:
        !          2388:                 for (monst = monsters->nextCreature; monst != NULL;) {
        !          2389:                     nextMonst = monst->nextCreature;
        !          2390:                     decrementMonsterStatus(monst);
        !          2391:                     monst = nextMonst;
        !          2392:                 }
        !          2393:
        !          2394:                 // monsters with a dungeon feature spawn it every so often
        !          2395:                 for (monst = monsters->nextCreature; monst != NULL;) {
        !          2396:                     nextMonst = monst->nextCreature;
        !          2397:                     if (monst->info.DFChance
        !          2398:                         && !(monst->info.flags & MONST_GETS_TURN_ON_ACTIVATION)
        !          2399:                         && rand_percent(monst->info.DFChance)) {
        !          2400:
        !          2401:                         spawnDungeonFeature(monst->xLoc, monst->yLoc, &dungeonFeatureCatalog[monst->info.DFType], true, false);
        !          2402:                     }
        !          2403:                     monst = nextMonst;
        !          2404:                 }
        !          2405:
        !          2406:                 updateEnvironment(); // Update fire and gas, items floating around in water, monsters falling into chasms, etc.
        !          2407:                 decrementPlayerStatus();
        !          2408:                 applyInstantTileEffectsToCreature(&player);
        !          2409:                 if (rogue.gameHasEnded) { // caustic gas, lava, trapdoor, etc.
        !          2410:                     return;
        !          2411:                 }
        !          2412:                 monstersApproachStairs();
        !          2413:
        !          2414:                 if (player.ticksUntilTurn > 100 && !fastForward) {
        !          2415:                     fastForward = rogue.playbackFastForward || pauseBrogue(25);
        !          2416:                 }
        !          2417:
        !          2418:                 // Rolling waypoint refresh:
        !          2419:                 rogue.wpRefreshTicker++;
        !          2420:                 if (rogue.wpRefreshTicker >= rogue.wpCount) {
        !          2421:                     rogue.wpRefreshTicker = 0;
        !          2422:                 }
        !          2423:                 refreshWaypoint(rogue.wpRefreshTicker);
        !          2424:             }
        !          2425:
        !          2426:             for (monst = monsters->nextCreature; (monst != NULL) && (rogue.gameHasEnded == false); monst = monst->nextCreature) {
        !          2427:                 if (monst->ticksUntilTurn <= 0) {
        !          2428:                     if (monst->currentHP > monst->info.maxHP) {
        !          2429:                         monst->currentHP = monst->info.maxHP;
        !          2430:                     }
        !          2431:
        !          2432:                     if ((monst->info.flags & MONST_GETS_TURN_ON_ACTIVATION)
        !          2433:                         || monst->status[STATUS_PARALYZED]
        !          2434:                         || monst->status[STATUS_ENTRANCED]
        !          2435:                         || (monst->bookkeepingFlags & MB_CAPTIVE)) {
        !          2436:
        !          2437:                         // Do not pass go; do not collect 200 gold.
        !          2438:                         monst->ticksUntilTurn = monst->movementSpeed;
        !          2439:                     } else {
        !          2440:                         monstersTurn(monst);
        !          2441:                     }
        !          2442:
        !          2443:                     for(monst2 = monsters->nextCreature; monst2 != NULL; monst2 = monst2->nextCreature) {
        !          2444:                         if (monst2 == monst) { // monst still alive and on the level
        !          2445:                             applyGradualTileEffectsToCreature(monst, monst->ticksUntilTurn);
        !          2446:                             break;
        !          2447:                         }
        !          2448:                     }
        !          2449:                     monst = monsters; // loop through from the beginning to be safe
        !          2450:                 }
        !          2451:             }
        !          2452:
        !          2453:             player.ticksUntilTurn -= soonestTurn;
        !          2454:
        !          2455:             if (rogue.gameHasEnded) {
        !          2456:                 return;
        !          2457:             }
        !          2458:         }
        !          2459:         // DEBUG displayLevel();
        !          2460:         //checkForDungeonErrors();
        !          2461:
        !          2462:         updateVision(true);
        !          2463:         rogue.aggroRange = currentAggroValue();
        !          2464:         if (rogue.displayAggroRangeMode) {
        !          2465:             displayLevel();
        !          2466:         }
        !          2467:
        !          2468:         for(monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
        !          2469:             if (canSeeMonster(monst) && !(monst->bookkeepingFlags & (MB_WAS_VISIBLE | MB_ALREADY_SEEN))) {
        !          2470:                 monst->bookkeepingFlags |= MB_WAS_VISIBLE;
        !          2471:                 if (monst->creatureState != MONSTER_ALLY) {
        !          2472:                     rogue.disturbed = true;
        !          2473:                     if (rogue.cautiousMode || rogue.automationActive) {
        !          2474:                         oldRNG = rogue.RNG;
        !          2475:                         rogue.RNG = RNG_COSMETIC;
        !          2476:                         //assureCosmeticRNG;
        !          2477:                         monsterName(buf2, monst, false);
        !          2478:                         sprintf(buf, "you %s a%s %s",
        !          2479:                                 playerCanDirectlySee(monst->xLoc, monst->yLoc) ? "see" : "sense",
        !          2480:                                 (isVowelish(buf2) ? "n" : ""),
        !          2481:                                 buf2);
        !          2482:                         if (rogue.cautiousMode) {
        !          2483:                             strcat(buf, ".");
        !          2484:                             message(buf, true);
        !          2485:                         } else {
        !          2486:                             combatMessage(buf, 0);
        !          2487:                         }
        !          2488:                         restoreRNG;
        !          2489:                     }
        !          2490:                 }
        !          2491:                 if (cellHasTerrainFlag(monst->xLoc, monst->yLoc, T_OBSTRUCTS_PASSABILITY)
        !          2492:                     && cellHasTMFlag(monst->xLoc, monst->yLoc, TM_IS_SECRET)) {
        !          2493:
        !          2494:                     discover(monst->xLoc, monst->yLoc);
        !          2495:                 }
        !          2496:                 if (canDirectlySeeMonster(monst)) {
        !          2497:                     if (rogue.weapon && rogue.weapon->flags & ITEM_RUNIC
        !          2498:                         && rogue.weapon->enchant2 == W_SLAYING
        !          2499:                         && !(rogue.weapon->flags & ITEM_RUNIC_HINTED)
        !          2500:                         && monsterIsInClass(monst, rogue.weapon->vorpalEnemy)) {
        !          2501:
        !          2502:                         rogue.weapon->flags |= ITEM_RUNIC_HINTED;
        !          2503:                         itemName(rogue.weapon, buf2, false, false, NULL);
        !          2504:                         sprintf(buf, "the runes on your %s gleam balefully.", buf2);
        !          2505:                         messageWithColor(buf, &itemMessageColor, true);
        !          2506:                     }
        !          2507:                     if (rogue.armor && rogue.armor->flags & ITEM_RUNIC
        !          2508:                         && rogue.armor->enchant2 == A_IMMUNITY
        !          2509:                         && !(rogue.armor->flags & ITEM_RUNIC_HINTED)
        !          2510:                         && monsterIsInClass(monst, rogue.armor->vorpalEnemy)) {
        !          2511:
        !          2512:                         rogue.armor->flags |= ITEM_RUNIC_HINTED;
        !          2513:                         itemName(rogue.armor, buf2, false, false, NULL);
        !          2514:                         sprintf(buf, "the runes on your %s glow protectively.", buf2);
        !          2515:                         messageWithColor(buf, &itemMessageColor, true);
        !          2516:                     }
        !          2517:                 }
        !          2518:             } else if (!canSeeMonster(monst)
        !          2519:                        && (monst->bookkeepingFlags & MB_WAS_VISIBLE)
        !          2520:                        && !(monst->bookkeepingFlags & MB_CAPTIVE)) {
        !          2521:                 monst->bookkeepingFlags &= ~MB_WAS_VISIBLE;
        !          2522:             }
        !          2523:         }
        !          2524:
        !          2525:         displayCombatText();
        !          2526:
        !          2527:         if (player.status[STATUS_PARALYZED]) {
        !          2528:             if (!fastForward) {
        !          2529:                 fastForward = rogue.playbackFastForward || pauseBrogue(25);
        !          2530:             }
        !          2531:         }
        !          2532:
        !          2533:         //checkNutrition(); // Now handled within decrementPlayerStatus().
        !          2534:         if (!rogue.playbackFastForward) {
        !          2535:             shuffleTerrainColors(100, false);
        !          2536:         }
        !          2537:
        !          2538:         displayAnnotation();
        !          2539:
        !          2540:         refreshSideBar(-1, -1, false);
        !          2541:
        !          2542:         applyInstantTileEffectsToCreature(&player);
        !          2543:         if (rogue.gameHasEnded) { // caustic gas, lava, trapdoor, etc.
        !          2544:             return;
        !          2545:         }
        !          2546:
        !          2547:         if (player.currentHP > player.info.maxHP) {
        !          2548:             player.currentHP = player.info.maxHP;
        !          2549:         }
        !          2550:
        !          2551:         if (player.bookkeepingFlags & MB_IS_FALLING) {
        !          2552:             playerFalls();
        !          2553:             handleHealthAlerts();
        !          2554:             return;
        !          2555:         }
        !          2556:
        !          2557:     } while (player.status[STATUS_PARALYZED]);
        !          2558:
        !          2559:     rogue.justRested = false;
        !          2560:     rogue.justSearched = false;
        !          2561:     updateFlavorText();
        !          2562:
        !          2563:     if (!rogue.updatedMapToShoreThisTurn) {
        !          2564:         updateMapToShore();
        !          2565:     }
        !          2566:
        !          2567:     // "point of no return" check
        !          2568:     if ((player.status[STATUS_LEVITATING] && cellHasTerrainFlag(player.xLoc, player.yLoc, T_LAVA_INSTA_DEATH | T_IS_DEEP_WATER | T_AUTO_DESCENT))
        !          2569:         || (player.status[STATUS_IMMUNE_TO_FIRE] && cellHasTerrainFlag(player.xLoc, player.yLoc, T_LAVA_INSTA_DEATH))) {
        !          2570:         if (!rogue.receivedLevitationWarning) {
        !          2571:             turnsRequiredToShore = rogue.mapToShore[player.xLoc][player.yLoc] * player.movementSpeed / 100;
        !          2572:             if (cellHasTerrainFlag(player.xLoc, player.yLoc, T_LAVA_INSTA_DEATH)) {
        !          2573:                 turnsToShore = max(player.status[STATUS_LEVITATING], player.status[STATUS_IMMUNE_TO_FIRE]) * 100 / player.movementSpeed;
        !          2574:             } else {
        !          2575:                 turnsToShore = player.status[STATUS_LEVITATING] * 100 / player.movementSpeed;
        !          2576:             }
        !          2577:             if (turnsRequiredToShore == turnsToShore || turnsRequiredToShore + 1 == turnsToShore) {
        !          2578:                 message("better head back to solid ground!", true);
        !          2579:                 rogue.receivedLevitationWarning = true;
        !          2580:             } else if (turnsRequiredToShore > turnsToShore
        !          2581:                        && turnsRequiredToShore < 10000) {
        !          2582:                 message("you're past the point of no return!", true);
        !          2583:                 rogue.receivedLevitationWarning = true;
        !          2584:             }
        !          2585:         }
        !          2586:     } else {
        !          2587:         rogue.receivedLevitationWarning = false;
        !          2588:     }
        !          2589:
        !          2590:     emptyGraveyard();
        !          2591:     rogue.playbackBetweenTurns = true;
        !          2592:     RNGCheck();
        !          2593:     handleHealthAlerts();
        !          2594:
        !          2595:     if (rogue.flareCount > 0) {
        !          2596:         animateFlares(rogue.flares, rogue.flareCount);
        !          2597:         rogue.flareCount = 0;
        !          2598:     }
        !          2599: }
        !          2600:
        !          2601: void resetScentTurnNumber() { // don't want player.scentTurnNumber to roll over the short maxint!
        !          2602:     short i, j, d;
        !          2603:     rogue.scentTurnNumber -= 15000;
        !          2604:     for (d = 0; d < DEEPEST_LEVEL; d++) {
        !          2605:         if (levels[d].visited) {
        !          2606:             for (i=0; i<DCOLS; i++) {
        !          2607:                 for (j=0; j<DROWS; j++) {
        !          2608:                     if (levels[d].scentMap[i][j] > 15000) {
        !          2609:                         levels[d].scentMap[i][j] -= 15000;
        !          2610:                     } else {
        !          2611:                         levels[d].scentMap[i][j] = 0;
        !          2612:                     }
        !          2613:                 }
        !          2614:             }
        !          2615:         }
        !          2616:     }
        !          2617: }

CVSweb