[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

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