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