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

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

1.1     ! rubenllo    1: /*
        !             2:  *  Monsters.c
        !             3:  *  Brogue
        !             4:  *
        !             5:  *  Created by Brian Walker on 1/13/09.
        !             6:  *  Copyright 2012. All rights reserved.
        !             7:  *
        !             8:  *  This file is part of Brogue.
        !             9:  *
        !            10:  *  This program is free software: you can redistribute it and/or modify
        !            11:  *  it under the terms of the GNU Affero General Public License as
        !            12:  *  published by the Free Software Foundation, either version 3 of the
        !            13:  *  License, or (at your option) any later version.
        !            14:  *
        !            15:  *  This program is distributed in the hope that it will be useful,
        !            16:  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
        !            17:  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        !            18:  *  GNU Affero General Public License for more details.
        !            19:  *
        !            20:  *  You should have received a copy of the GNU Affero General Public License
        !            21:  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
        !            22:  */
        !            23:
        !            24: #include "Rogue.h"
        !            25: #include "IncludeGlobals.h"
        !            26:
        !            27: void mutateMonster(creature *monst, short mutationIndex) {
        !            28:     monst->mutationIndex = mutationIndex;
        !            29:     const mutation *theMut = &(mutationCatalog[mutationIndex]);
        !            30:     monst->info.flags |= theMut->monsterFlags;
        !            31:     monst->info.abilityFlags |= theMut->monsterAbilityFlags;
        !            32:     monst->info.maxHP = monst->info.maxHP * theMut->healthFactor / 100;
        !            33:     monst->info.movementSpeed = monst->info.movementSpeed * theMut->moveSpeedFactor / 100;
        !            34:     monst->info.attackSpeed = monst->info.attackSpeed * theMut->attackSpeedFactor / 100;
        !            35:     monst->info.defense = monst->info.defense * theMut->defenseFactor / 100;
        !            36:     if (monst->info.damage.lowerBound > 0) {
        !            37:         monst->info.damage.lowerBound = monst->info.damage.lowerBound * theMut->damageFactor / 100;
        !            38:         monst->info.damage.lowerBound = max(monst->info.damage.lowerBound, 1);
        !            39:     }
        !            40:     if (monst->info.damage.upperBound > 0) {
        !            41:         monst->info.damage.upperBound = monst->info.damage.upperBound * theMut->damageFactor / 100;
        !            42:         monst->info.damage.upperBound = max(monst->info.damage.upperBound, (monst->info.abilityFlags & MA_POISONS) ? 2 : 1);
        !            43:     }
        !            44:     if (theMut->DFChance >= 0) {
        !            45:         monst->info.DFChance = theMut->DFChance;
        !            46:     }
        !            47:     if (theMut->DFType > 0) {
        !            48:         monst->info.DFType = theMut->DFType;
        !            49:     }
        !            50: }
        !            51:
        !            52: // Allocates space, generates a creature of the given type,
        !            53: // prepends it to the list of creatures, and returns a pointer to that creature. Note that the creature
        !            54: // is not given a map location here!
        !            55: creature *generateMonster(short monsterID, boolean itemPossible, boolean mutationPossible) {
        !            56:     short itemChance, mutationChance, i, mutationAttempt;
        !            57:     creature *monst;
        !            58:
        !            59:     // 1.17^x * 10, with x from 1 to 13:
        !            60:     const int POW_DEEP_MUTATION[] = {11, 13, 16, 18, 21, 25, 30, 35, 41, 48, 56, 65, 76};
        !            61:
        !            62:     monst = (creature *) malloc(sizeof(creature));
        !            63:     memset(monst, '\0', sizeof(creature));
        !            64:     clearStatus(monst);
        !            65:     monst->info = monsterCatalog[monsterID];
        !            66:
        !            67:     monst->mutationIndex = -1;
        !            68:     if (mutationPossible
        !            69:         && !(monst->info.flags & MONST_NEVER_MUTATED)
        !            70:         && !(monst->info.abilityFlags & MA_NEVER_MUTATED)
        !            71:         && rogue.depthLevel > 10) {
        !            72:
        !            73:
        !            74:         if (rogue.depthLevel <= AMULET_LEVEL) {
        !            75:             mutationChance = clamp(rogue.depthLevel - 10, 1, 10);
        !            76:         } else {
        !            77:             mutationChance = POW_DEEP_MUTATION[min(rogue.depthLevel - AMULET_LEVEL, 12)];
        !            78:             mutationChance = min(mutationChance, 75);
        !            79:         }
        !            80:
        !            81:         if (rand_percent(mutationChance)) {
        !            82:             mutationAttempt = rand_range(0, NUMBER_MUTATORS - 1);
        !            83:             if (!(monst->info.flags & mutationCatalog[mutationAttempt].forbiddenFlags)
        !            84:                 && !(monst->info.abilityFlags & mutationCatalog[mutationAttempt].forbiddenAbilityFlags)) {
        !            85:
        !            86:                 mutateMonster(monst, mutationAttempt);
        !            87:             }
        !            88:         }
        !            89:     }
        !            90:
        !            91:     monst->nextCreature = monsters->nextCreature;
        !            92:     monsters->nextCreature = monst;
        !            93:     monst->xLoc = monst->yLoc = 0;
        !            94:     monst->depth = rogue.depthLevel;
        !            95:     monst->bookkeepingFlags = 0;
        !            96:     monst->mapToMe = NULL;
        !            97:     monst->safetyMap = NULL;
        !            98:     monst->leader = NULL;
        !            99:     monst->carriedMonster = NULL;
        !           100:     monst->creatureState = (((monst->info.flags & MONST_NEVER_SLEEPS) || rand_percent(25))
        !           101:                             ? MONSTER_TRACKING_SCENT : MONSTER_SLEEPING);
        !           102:     monst->creatureMode = MODE_NORMAL;
        !           103:     monst->currentHP = monst->info.maxHP;
        !           104:     monst->spawnDepth = rogue.depthLevel;
        !           105:     monst->ticksUntilTurn = monst->info.movementSpeed;
        !           106:     monst->info.turnsBetweenRegen *= 1000; // tracked as thousandths to prevent rounding errors
        !           107:     monst->turnsUntilRegen = monst->info.turnsBetweenRegen;
        !           108:     monst->regenPerTurn = 0;
        !           109:     monst->movementSpeed = monst->info.movementSpeed;
        !           110:     monst->attackSpeed = monst->info.attackSpeed;
        !           111:     monst->turnsSpentStationary = 0;
        !           112:     monst->xpxp = 0;
        !           113:     monst->machineHome = 0;
        !           114:     monst->newPowerCount = monst->totalPowerCount = 0;
        !           115:     monst->targetCorpseLoc[0] = monst->targetCorpseLoc[1] = 0;
        !           116:     monst->lastSeenPlayerAt[0] = monst->lastSeenPlayerAt[1] = -1;
        !           117:     monst->targetWaypointIndex = -1;
        !           118:     for (i=0; i < MAX_WAYPOINT_COUNT; i++) {
        !           119:         monst->waypointAlreadyVisited[i] = rand_range(0, 1);
        !           120:     }
        !           121:
        !           122:     if (monst->info.flags & MONST_FIERY) {
        !           123:         monst->status[STATUS_BURNING] = monst->maxStatus[STATUS_BURNING] = 1000; // won't decrease
        !           124:     }
        !           125:     if (monst->info.flags & MONST_FLIES) {
        !           126:         monst->status[STATUS_LEVITATING] = monst->maxStatus[STATUS_LEVITATING] = 1000; // won't decrease
        !           127:     }
        !           128:     if (monst->info.flags & MONST_IMMUNE_TO_FIRE) {
        !           129:         monst->status[STATUS_IMMUNE_TO_FIRE] = monst->maxStatus[STATUS_IMMUNE_TO_FIRE] = 1000; // won't decrease
        !           130:     }
        !           131:     if (monst->info.flags & MONST_INVISIBLE) {
        !           132:         monst->status[STATUS_INVISIBLE] = monst->maxStatus[STATUS_INVISIBLE] = 1000; // won't decrease
        !           133:     }
        !           134:     monst->status[STATUS_NUTRITION] = monst->maxStatus[STATUS_NUTRITION] = 1000;
        !           135:
        !           136:     if (monst->info.flags & MONST_CARRY_ITEM_100) {
        !           137:         itemChance = 100;
        !           138:     } else if (monst->info.flags & MONST_CARRY_ITEM_25) {
        !           139:         itemChance = 25;
        !           140:     } else {
        !           141:         itemChance = 0;
        !           142:     }
        !           143:
        !           144:     if (ITEMS_ENABLED
        !           145:         && itemPossible
        !           146:         && (rogue.depthLevel <= AMULET_LEVEL)
        !           147:         && monsterItemsHopper->nextItem
        !           148:         && rand_percent(itemChance)) {
        !           149:
        !           150:         monst->carriedItem = monsterItemsHopper->nextItem;
        !           151:         monsterItemsHopper->nextItem = monsterItemsHopper->nextItem->nextItem;
        !           152:         monst->carriedItem->nextItem = NULL;
        !           153:         monst->carriedItem->originDepth = rogue.depthLevel;
        !           154:     } else {
        !           155:         monst->carriedItem = NULL;
        !           156:     }
        !           157:
        !           158:     initializeGender(monst);
        !           159:
        !           160:     if (!(monst->info.flags & MONST_INANIMATE) && !monst->status[STATUS_LIFESPAN_REMAINING]) {
        !           161:         monst->bookkeepingFlags |= MB_HAS_SOUL;
        !           162:     }
        !           163:
        !           164:     return monst;
        !           165: }
        !           166:
        !           167: boolean monsterRevealed(creature *monst) {
        !           168:     if (monst == &player) {
        !           169:         return false;
        !           170:     } else if (monst->bookkeepingFlags & MB_TELEPATHICALLY_REVEALED) {
        !           171:         return true;
        !           172:     } else if (monst->status[STATUS_ENTRANCED]) {
        !           173:         return true;
        !           174:     } else if (player.status[STATUS_TELEPATHIC] && !(monst->info.flags & MONST_INANIMATE)) {
        !           175:         return true;
        !           176:     }
        !           177:     return false;
        !           178: }
        !           179:
        !           180: boolean monsterHiddenBySubmersion(const creature *monst, const creature *observer) {
        !           181:     if (monst->bookkeepingFlags & MB_SUBMERGED) {
        !           182:         if (observer
        !           183:             && (terrainFlags(observer->xLoc, observer->yLoc) & T_IS_DEEP_WATER)
        !           184:             && !observer->status[STATUS_LEVITATING]) {
        !           185:             // observer is in deep water, so target is not hidden by water
        !           186:             return false;
        !           187:         } else {
        !           188:             // submerged and the observer is not in deep water.
        !           189:             return true;
        !           190:         }
        !           191:     }
        !           192:     return false;
        !           193: }
        !           194:
        !           195: boolean monsterIsHidden(const creature *monst, const creature *observer) {
        !           196:     if (monst->bookkeepingFlags & MB_IS_DORMANT) {
        !           197:         return true;
        !           198:     }
        !           199:     if (observer && monstersAreTeammates(monst, observer)) {
        !           200:         // Teammates can always see each other.
        !           201:         return false;
        !           202:     }
        !           203:     if ((monst->status[STATUS_INVISIBLE] && !pmap[monst->xLoc][monst->yLoc].layers[GAS])) {
        !           204:         // invisible and not in gas
        !           205:         return true;
        !           206:     }
        !           207:     if (monsterHiddenBySubmersion(monst, observer)) {
        !           208:         return true;
        !           209:     }
        !           210:     return false;
        !           211: }
        !           212:
        !           213: boolean canSeeMonster(creature *monst) {
        !           214:     if (monst == &player) {
        !           215:         return true;
        !           216:     }
        !           217:     if (!monsterIsHidden(monst, &player)
        !           218:         && (playerCanSee(monst->xLoc, monst->yLoc) || monsterRevealed(monst))) {
        !           219:         return true;
        !           220:     }
        !           221:     return false;
        !           222: }
        !           223:
        !           224: // This is different from canSeeMonster() in that it counts only physical sight -- not clairvoyance or telepathy.
        !           225: boolean canDirectlySeeMonster(creature *monst) {
        !           226:     if (monst == &player) {
        !           227:         return true;
        !           228:     }
        !           229:     if (playerCanDirectlySee(monst->xLoc, monst->yLoc) && !monsterIsHidden(monst, &player)) {
        !           230:         return true;
        !           231:     }
        !           232:     return false;
        !           233: }
        !           234:
        !           235: void monsterName(char *buf, creature *monst, boolean includeArticle) {
        !           236:     short oldRNG;
        !           237:
        !           238:     if (monst == &player) {
        !           239:         strcpy(buf, "you");
        !           240:         return;
        !           241:     }
        !           242:     if (canSeeMonster(monst) || rogue.playbackOmniscience) {
        !           243:         if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience) {
        !           244:
        !           245:             oldRNG = rogue.RNG;
        !           246:             rogue.RNG = RNG_COSMETIC;
        !           247:             //assureCosmeticRNG;
        !           248:             sprintf(buf, "%s%s", (includeArticle ? "the " : ""),
        !           249:                     monsterCatalog[rand_range(1, NUMBER_MONSTER_KINDS - 1)].monsterName);
        !           250:             restoreRNG;
        !           251:
        !           252:             return;
        !           253:         }
        !           254:         sprintf(buf, "%s%s", (includeArticle ? (monst->creatureState == MONSTER_ALLY ? "your " : "the ") : ""),
        !           255:                 monst->info.monsterName);
        !           256:         //monsterText[monst->info.monsterID].name);
        !           257:         return;
        !           258:     } else {
        !           259:         strcpy(buf, "something");
        !           260:         return;
        !           261:     }
        !           262: }
        !           263:
        !           264: boolean monsterIsInClass(const creature *monst, const short monsterClass) {
        !           265:     short i;
        !           266:     for (i = 0; monsterClassCatalog[monsterClass].memberList[i] != 0; i++) {
        !           267:         if (monsterClassCatalog[monsterClass].memberList[i] == monst->info.monsterID) {
        !           268:             return true;
        !           269:         }
        !           270:     }
        !           271:     return false;
        !           272: }
        !           273:
        !           274: // Don't attack a revenant if you're not magical.
        !           275: // Don't attack a monster embedded in obstruction crystal.
        !           276: // Etc.
        !           277: boolean attackWouldBeFutile(const creature *attacker, const creature *defender) {
        !           278:     if (cellHasTerrainFlag(defender->xLoc, defender->yLoc, T_OBSTRUCTS_PASSABILITY)
        !           279:         && !(defender->info.flags & MONST_ATTACKABLE_THRU_WALLS)) {
        !           280:         return true;
        !           281:     }
        !           282:     if (attacker == &player) {
        !           283:         // Let the player do what she wants, if it's possible.
        !           284:         return false;
        !           285:     }
        !           286:     if ((attacker->info.flags & MONST_RESTRICTED_TO_LIQUID)
        !           287:         && !(attacker->status[STATUS_LEVITATING])
        !           288:         && defender->status[STATUS_LEVITATING]) {
        !           289:         return true;
        !           290:     }
        !           291:     if (defender->info.flags & MONST_INVULNERABLE) {
        !           292:         return true;
        !           293:     }
        !           294:     if (defender->info.flags & MONST_IMMUNE_TO_WEAPONS
        !           295:         && !(attacker->info.abilityFlags & MA_POISONS)) {
        !           296:         return true;
        !           297:     }
        !           298:     return false;
        !           299: }
        !           300:
        !           301: // This is a specific kind of willingness, bordering on ability.
        !           302: // Intuition: if it swung an axe from that position, should it
        !           303: // hit the defender? Or silently pass through it, as it does for
        !           304: // allies?
        !           305: boolean monsterWillAttackTarget(const creature *attacker, const creature *defender) {
        !           306:     if (attacker == defender || (defender->bookkeepingFlags & MB_IS_DYING)) {
        !           307:         return false;
        !           308:     }
        !           309:     if (attacker == &player
        !           310:         && defender->creatureState == MONSTER_ALLY) {
        !           311:
        !           312:         return false;
        !           313:     }
        !           314:     if (attacker->status[STATUS_ENTRANCED]
        !           315:         && defender->creatureState != MONSTER_ALLY) {
        !           316:
        !           317:         return true;
        !           318:     }
        !           319:     if (attacker->creatureState == MONSTER_ALLY
        !           320:         && attacker != &player
        !           321:         && defender->status[STATUS_ENTRANCED]) {
        !           322:
        !           323:         return false;
        !           324:     }
        !           325:     if (defender->bookkeepingFlags & MB_CAPTIVE) {
        !           326:         return false;
        !           327:     }
        !           328:     if (attacker->status[STATUS_DISCORDANT]
        !           329:         || defender->status[STATUS_DISCORDANT]
        !           330:         || attacker->status[STATUS_CONFUSED]) {
        !           331:
        !           332:         return true;
        !           333:     }
        !           334:     if (monstersAreEnemies(attacker, defender)
        !           335:         && !monstersAreTeammates(attacker, defender)) {
        !           336:         return true;
        !           337:     }
        !           338:     return false;
        !           339: }
        !           340:
        !           341: boolean monstersAreTeammates(const creature *monst1, const creature *monst2) {
        !           342:     // if one follows the other, or the other follows the one, or they both follow the same
        !           343:     return ((((monst1->bookkeepingFlags & MB_FOLLOWER) && monst1->leader == monst2)
        !           344:              || ((monst2->bookkeepingFlags & MB_FOLLOWER) && monst2->leader == monst1)
        !           345:              || (monst1->creatureState == MONSTER_ALLY && monst2 == &player)
        !           346:              || (monst1 == &player && monst2->creatureState == MONSTER_ALLY)
        !           347:              || (monst1->creatureState == MONSTER_ALLY && monst2->creatureState == MONSTER_ALLY)
        !           348:              || ((monst1->bookkeepingFlags & MB_FOLLOWER) && (monst2->bookkeepingFlags & MB_FOLLOWER)
        !           349:                  && monst1->leader == monst2->leader)) ? true : false);
        !           350: }
        !           351:
        !           352: boolean monstersAreEnemies(const creature *monst1, const creature *monst2) {
        !           353:     if ((monst1->bookkeepingFlags | monst2->bookkeepingFlags) & MB_CAPTIVE) {
        !           354:         return false;
        !           355:     }
        !           356:     if (monst1 == monst2) {
        !           357:         return false; // Can't be enemies with yourself, even if discordant.
        !           358:     }
        !           359:     if (monst1->status[STATUS_DISCORDANT] || monst2->status[STATUS_DISCORDANT]) {
        !           360:         return true;
        !           361:     }
        !           362:     // eels and krakens attack anything in deep water
        !           363:     if (((monst1->info.flags & MONST_RESTRICTED_TO_LIQUID)
        !           364:          && !(monst2->info.flags & MONST_IMMUNE_TO_WATER)
        !           365:          && !(monst2->status[STATUS_LEVITATING])
        !           366:          && cellHasTerrainFlag(monst2->xLoc, monst2->yLoc, T_IS_DEEP_WATER))
        !           367:
        !           368:         || ((monst2->info.flags & MONST_RESTRICTED_TO_LIQUID)
        !           369:             && !(monst1->info.flags & MONST_IMMUNE_TO_WATER)
        !           370:             && !(monst1->status[STATUS_LEVITATING])
        !           371:             && cellHasTerrainFlag(monst1->xLoc, monst1->yLoc, T_IS_DEEP_WATER))) {
        !           372:
        !           373:             return true;
        !           374:         }
        !           375:     return ((monst1->creatureState == MONSTER_ALLY || monst1 == &player)
        !           376:             != (monst2->creatureState == MONSTER_ALLY || monst2 == &player));
        !           377: }
        !           378:
        !           379:
        !           380: void initializeGender(creature *monst) {
        !           381:     if ((monst->info.flags & MONST_MALE) && (monst->info.flags & MONST_FEMALE)) {
        !           382:         monst->info.flags &= ~(rand_percent(50) ? MONST_MALE : MONST_FEMALE);
        !           383:     }
        !           384: }
        !           385:
        !           386: // Returns true if either string has a null terminator before they otherwise disagree.
        !           387: boolean stringsMatch(const char *str1, const char *str2) {
        !           388:     short i;
        !           389:
        !           390:     for (i=0; str1[i] && str2[i]; i++) {
        !           391:         if (str1[i] != str2[i]) {
        !           392:             return false;
        !           393:         }
        !           394:     }
        !           395:     return true;
        !           396: }
        !           397:
        !           398: // Genders:
        !           399: //  0 = [character escape sequence]
        !           400: //  1 = you
        !           401: //  2 = male
        !           402: //  3 = female
        !           403: //  4 = neuter
        !           404: void resolvePronounEscapes(char *text, creature *monst) {
        !           405:     short pronounType, gender, i;
        !           406:     char *insert, *scan;
        !           407:     boolean capitalize;
        !           408:     // Note: Escape sequences MUST be longer than EACH of the possible replacements.
        !           409:     // That way, the string only contracts, and we don't need a buffer.
        !           410:     const char pronouns[4][5][20] = {
        !           411:         {"$HESHE", "you", "he", "she", "it"},
        !           412:         {"$HIMHER", "you", "him", "her", "it"},
        !           413:         {"$HISHER", "your", "his", "her", "its"},
        !           414:         {"$HIMSELFHERSELF", "yourself", "himself", "herself", "itself"}};
        !           415:
        !           416:     if (monst == &player) {
        !           417:         gender = 1;
        !           418:     } else if (!canSeeMonster(monst) && !rogue.playbackOmniscience) {
        !           419:         gender = 4;
        !           420:     } else if (monst->info.flags & MONST_MALE) {
        !           421:         gender = 2;
        !           422:     } else if (monst->info.flags & MONST_FEMALE) {
        !           423:         gender = 3;
        !           424:     } else {
        !           425:         gender = 4;
        !           426:     }
        !           427:
        !           428:     capitalize = false;
        !           429:
        !           430:     for (insert = scan = text; *scan;) {
        !           431:         if (scan[0] == '$') {
        !           432:             for (pronounType=0; pronounType<4; pronounType++) {
        !           433:                 if (stringsMatch(pronouns[pronounType][0], scan)) {
        !           434:                     strcpy(insert, pronouns[pronounType][gender]);
        !           435:                     if (capitalize) {
        !           436:                         upperCase(insert);
        !           437:                         capitalize = false;
        !           438:                     }
        !           439:                     scan += strlen(pronouns[pronounType][0]);
        !           440:                     insert += strlen(pronouns[pronounType][gender]);
        !           441:                     break;
        !           442:                 }
        !           443:             }
        !           444:             if (pronounType == 4) {
        !           445:                 // Started with a '$' but didn't match an escape sequence; just copy the character and move on.
        !           446:                 *(insert++) = *(scan++);
        !           447:             }
        !           448:         } else if (scan[0] == COLOR_ESCAPE) {
        !           449:             for (i=0; i<4; i++) {
        !           450:                 *(insert++) = *(scan++);
        !           451:             }
        !           452:         } else { // Didn't match any of the escape sequences; copy the character instead.
        !           453:             if (*scan == '.') {
        !           454:                 capitalize = true;
        !           455:             } else if (*scan != ' ') {
        !           456:                 capitalize = false;
        !           457:             }
        !           458:
        !           459:             *(insert++) = *(scan++);
        !           460:         }
        !           461:     }
        !           462:     *insert = '\0';
        !           463: }
        !           464:
        !           465: /*
        !           466: Returns a random horde, weighted by spawn frequency, which has all requiredFlags
        !           467: and does not have any forbiddenFlags. If summonerType is 0, all hordes valid on
        !           468: the given depth are considered. (Depth 0 means current depth.) Otherwise, all
        !           469: hordes with summonerType as a leader are considered.
        !           470: */
        !           471: short pickHordeType(short depth, enum monsterTypes summonerType, unsigned long forbiddenFlags, unsigned long requiredFlags) {
        !           472:     short i, index, possCount = 0;
        !           473:
        !           474:     if (depth <= 0) {
        !           475:         depth = rogue.depthLevel;
        !           476:     }
        !           477:
        !           478:     for (i=0; i<NUMBER_HORDES; i++) {
        !           479:         if (!(hordeCatalog[i].flags & forbiddenFlags)
        !           480:             && !(~(hordeCatalog[i].flags) & requiredFlags)
        !           481:             && ((!summonerType && hordeCatalog[i].minLevel <= depth && hordeCatalog[i].maxLevel >= depth)
        !           482:                 || (summonerType && (hordeCatalog[i].flags & HORDE_IS_SUMMONED) && hordeCatalog[i].leaderType == summonerType))) {
        !           483:                 possCount += hordeCatalog[i].frequency;
        !           484:             }
        !           485:     }
        !           486:
        !           487:     if (possCount == 0) {
        !           488:         return -1;
        !           489:     }
        !           490:
        !           491:     index = rand_range(1, possCount);
        !           492:
        !           493:     for (i=0; i<NUMBER_HORDES; i++) {
        !           494:         if (!(hordeCatalog[i].flags & forbiddenFlags)
        !           495:             && !(~(hordeCatalog[i].flags) & requiredFlags)
        !           496:             && ((!summonerType && hordeCatalog[i].minLevel <= depth && hordeCatalog[i].maxLevel >= depth)
        !           497:                 || (summonerType && (hordeCatalog[i].flags & HORDE_IS_SUMMONED) && hordeCatalog[i].leaderType == summonerType))) {
        !           498:                 if (index <= hordeCatalog[i].frequency) {
        !           499:                     return i;
        !           500:                 }
        !           501:                 index -= hordeCatalog[i].frequency;
        !           502:             }
        !           503:     }
        !           504:     return 0; // should never happen
        !           505: }
        !           506:
        !           507: void empowerMonster(creature *monst) {
        !           508:     char theMonsterName[100], buf[200];
        !           509:     monst->info.maxHP += 12;
        !           510:     monst->info.defense += 10;
        !           511:     monst->info.accuracy += 10;
        !           512:     monst->info.damage.lowerBound += max(1, monst->info.damage.lowerBound / 10);
        !           513:     monst->info.damage.upperBound += max(1, monst->info.damage.upperBound / 10);
        !           514:     monst->newPowerCount++;
        !           515:     monst->totalPowerCount++;
        !           516:     heal(monst, 100, true);
        !           517:
        !           518:     if (canSeeMonster(monst)) {
        !           519:         monsterName(theMonsterName, monst, true);
        !           520:         sprintf(buf, "%s looks stronger", theMonsterName);
        !           521:         combatMessage(buf, &advancementMessageColor);
        !           522:     }
        !           523: }
        !           524:
        !           525: // If placeClone is false, the clone won't get a location
        !           526: // and won't set any HAS_MONSTER flags or cause any refreshes;
        !           527: // it's just generated and inserted into the chains.
        !           528: creature *cloneMonster(creature *monst, boolean announce, boolean placeClone) {
        !           529:     creature *newMonst, *nextMonst, *parentMonst;
        !           530:     char buf[DCOLS], monstName[DCOLS];
        !           531:     short jellyCount;
        !           532:
        !           533:     newMonst = generateMonster(monst->info.monsterID, false, false);
        !           534:     nextMonst = newMonst->nextCreature;
        !           535:     *newMonst = *monst; // boink!
        !           536:     newMonst->nextCreature = nextMonst;
        !           537:
        !           538:     if (monst->carriedMonster) {
        !           539:         parentMonst = cloneMonster(monst->carriedMonster, false, false); // Also clone the carriedMonster
        !           540:         removeMonsterFromChain(parentMonst, monsters);
        !           541:         removeMonsterFromChain(parentMonst, dormantMonsters);
        !           542:     } else {
        !           543:         parentMonst = NULL;
        !           544:     }
        !           545:
        !           546:     initializeGender(newMonst);
        !           547:     newMonst->bookkeepingFlags &= ~(MB_LEADER | MB_CAPTIVE | MB_HAS_SOUL);
        !           548:     newMonst->bookkeepingFlags |= MB_FOLLOWER;
        !           549:     newMonst->mapToMe = NULL;
        !           550:     newMonst->safetyMap = NULL;
        !           551:     newMonst->carriedItem = NULL;
        !           552:     newMonst->carriedMonster = parentMonst;
        !           553:     newMonst->ticksUntilTurn = 101;
        !           554:     if (!(monst->creatureState == MONSTER_ALLY)) {
        !           555:         newMonst->bookkeepingFlags &= ~MB_TELEPATHICALLY_REVEALED;
        !           556:     }
        !           557:     if (monst->leader) {
        !           558:         newMonst->leader = monst->leader;
        !           559:     } else {
        !           560:         newMonst->leader = monst;
        !           561:         monst->bookkeepingFlags |= MB_LEADER;
        !           562:     }
        !           563:
        !           564:     if (monst->bookkeepingFlags & MB_CAPTIVE) {
        !           565:         // If you clone a captive, the clone will be your ally.
        !           566:         becomeAllyWith(newMonst);
        !           567:     }
        !           568:
        !           569:     if (placeClone) {
        !           570: //      getQualifyingLocNear(loc, monst->xLoc, monst->yLoc, true, 0, forbiddenFlagsForMonster(&(monst->info)), (HAS_PLAYER | HAS_MONSTER), false, false);
        !           571: //      newMonst->xLoc = loc[0];
        !           572: //      newMonst->yLoc = loc[1];
        !           573:         getQualifyingPathLocNear(&(newMonst->xLoc), &(newMonst->yLoc), monst->xLoc, monst->yLoc, true,
        !           574:                                  T_DIVIDES_LEVEL & avoidedFlagsForMonster(&(newMonst->info)), HAS_PLAYER,
        !           575:                                  avoidedFlagsForMonster(&(newMonst->info)), (HAS_PLAYER | HAS_MONSTER | HAS_STAIRS), false);
        !           576:         pmap[newMonst->xLoc][newMonst->yLoc].flags |= HAS_MONSTER;
        !           577:         refreshDungeonCell(newMonst->xLoc, newMonst->yLoc);
        !           578:         if (announce && canSeeMonster(newMonst)) {
        !           579:             monsterName(monstName, newMonst, false);
        !           580:             sprintf(buf, "another %s appears!", monstName);
        !           581:             message(buf, false);
        !           582:         }
        !           583:     }
        !           584:
        !           585:     if (monst == &player) { // Player managed to clone himself.
        !           586:         newMonst->info.foreColor = &gray;
        !           587:         newMonst->info.damage.lowerBound = 1;
        !           588:         newMonst->info.damage.upperBound = 2;
        !           589:         newMonst->info.damage.clumpFactor = 1;
        !           590:         newMonst->info.defense = 0;
        !           591:         strcpy(newMonst->info.monsterName, "clone");
        !           592:         newMonst->creatureState = MONSTER_ALLY;
        !           593:     }
        !           594:
        !           595:     if (monst->creatureState == MONSTER_ALLY
        !           596:         && (monst->info.abilityFlags & MA_CLONE_SELF_ON_DEFEND)
        !           597:         && !rogue.featRecord[FEAT_JELLYMANCER]) {
        !           598:
        !           599:         jellyCount = 0;
        !           600:         for (nextMonst = monsters->nextCreature; nextMonst != NULL; nextMonst = nextMonst->nextCreature) {
        !           601:             if (nextMonst->creatureState == MONSTER_ALLY
        !           602:                 && (nextMonst->info.abilityFlags & MA_CLONE_SELF_ON_DEFEND)) {
        !           603:
        !           604:                 jellyCount++;
        !           605:             }
        !           606:         }
        !           607:         if (jellyCount >= 90) {
        !           608:             rogue.featRecord[FEAT_JELLYMANCER] = true;
        !           609:         }
        !           610:     }
        !           611:     return newMonst;
        !           612: }
        !           613:
        !           614: unsigned long forbiddenFlagsForMonster(creatureType *monsterType) {
        !           615:     unsigned long flags;
        !           616:
        !           617:     flags = T_PATHING_BLOCKER;
        !           618:     if (monsterType->flags & MONST_INVULNERABLE) {
        !           619:         flags &= ~(T_LAVA_INSTA_DEATH | T_SPONTANEOUSLY_IGNITES | T_IS_FIRE);
        !           620:     }
        !           621:     if (monsterType->flags & (MONST_IMMUNE_TO_FIRE | MONST_FLIES)) {
        !           622:         flags &= ~T_LAVA_INSTA_DEATH;
        !           623:     }
        !           624:     if (monsterType->flags & MONST_IMMUNE_TO_FIRE) {
        !           625:         flags &= ~(T_SPONTANEOUSLY_IGNITES | T_IS_FIRE);
        !           626:     }
        !           627:     if (monsterType->flags & (MONST_IMMUNE_TO_WATER | MONST_FLIES)) {
        !           628:         flags &= ~T_IS_DEEP_WATER;
        !           629:     }
        !           630:     if (monsterType->flags & (MONST_FLIES)) {
        !           631:         flags &= ~(T_AUTO_DESCENT | T_IS_DF_TRAP);
        !           632:     }
        !           633:     return flags;
        !           634: }
        !           635:
        !           636: unsigned long avoidedFlagsForMonster(creatureType *monsterType) {
        !           637:     unsigned long flags;
        !           638:
        !           639:     flags = forbiddenFlagsForMonster(monsterType) | T_HARMFUL_TERRAIN | T_SACRED;
        !           640:
        !           641:     if (monsterType->flags & MONST_INVULNERABLE) {
        !           642:         flags &= ~(T_HARMFUL_TERRAIN | T_IS_DF_TRAP);
        !           643:     }
        !           644:     if (monsterType->flags & MONST_INANIMATE) {
        !           645:         flags &= ~(T_CAUSES_POISON | T_CAUSES_DAMAGE | T_CAUSES_PARALYSIS | T_CAUSES_CONFUSION);
        !           646:     }
        !           647:     if (monsterType->flags & MONST_IMMUNE_TO_FIRE) {
        !           648:         flags &= ~T_IS_FIRE;
        !           649:     }
        !           650:     if (monsterType->flags & MONST_FLIES) {
        !           651:         flags &= ~T_CAUSES_POISON;
        !           652:     }
        !           653:     return flags;
        !           654: }
        !           655:
        !           656: boolean monsterCanSubmergeNow(creature *monst) {
        !           657:     return ((monst->info.flags & MONST_SUBMERGES)
        !           658:             && cellHasTMFlag(monst->xLoc, monst->yLoc, TM_ALLOWS_SUBMERGING)
        !           659:             && !cellHasTerrainFlag(monst->xLoc, monst->yLoc, T_OBSTRUCTS_PASSABILITY)
        !           660:             && !(monst->bookkeepingFlags & (MB_SEIZING | MB_SEIZED | MB_CAPTIVE))
        !           661:             && ((monst->info.flags & (MONST_IMMUNE_TO_FIRE | MONST_INVULNERABLE))
        !           662:                 || monst->status[STATUS_IMMUNE_TO_FIRE]
        !           663:                 || !cellHasTerrainFlag(monst->xLoc, monst->yLoc, T_LAVA_INSTA_DEATH)));
        !           664: }
        !           665:
        !           666: // Returns true if at least one minion spawned.
        !           667: boolean spawnMinions(short hordeID, creature *leader, boolean summoned) {
        !           668:     short iSpecies, iMember, count;
        !           669:     unsigned long forbiddenTerrainFlags;
        !           670:     hordeType *theHorde;
        !           671:     creature *monst;
        !           672:     short x, y;
        !           673:     short failsafe;
        !           674:     boolean atLeastOneMinion = false;
        !           675:
        !           676:     x = leader->xLoc;
        !           677:     y = leader->yLoc;
        !           678:
        !           679:     theHorde = &hordeCatalog[hordeID];
        !           680:
        !           681:     for (iSpecies = 0; iSpecies < theHorde->numberOfMemberTypes; iSpecies++) {
        !           682:         count = randClump(theHorde->memberCount[iSpecies]);
        !           683:
        !           684:         forbiddenTerrainFlags = forbiddenFlagsForMonster(&(monsterCatalog[theHorde->memberType[iSpecies]]));
        !           685:         if (hordeCatalog[hordeID].spawnsIn) {
        !           686:             forbiddenTerrainFlags &= ~(tileCatalog[hordeCatalog[hordeID].spawnsIn].flags);
        !           687:         }
        !           688:
        !           689:         for (iMember = 0; iMember < count; iMember++) {
        !           690:             monst = generateMonster(theHorde->memberType[iSpecies], true, !summoned);
        !           691:             failsafe = 0;
        !           692:             do {
        !           693:                 getQualifyingPathLocNear(&(monst->xLoc), &(monst->yLoc), x, y, summoned,
        !           694:                                          T_DIVIDES_LEVEL & forbiddenTerrainFlags, (HAS_PLAYER | HAS_STAIRS),
        !           695:                                          forbiddenTerrainFlags, HAS_MONSTER, false);
        !           696:             } while (theHorde->spawnsIn && !cellHasTerrainType(monst->xLoc, monst->yLoc, theHorde->spawnsIn) && failsafe++ < 20);
        !           697:             if (failsafe >= 20) {
        !           698:                 // abort
        !           699:                 killCreature(monst, true);
        !           700:                 break;
        !           701:             }
        !           702:             if (monsterCanSubmergeNow(monst)) {
        !           703:                 monst->bookkeepingFlags |= MB_SUBMERGED;
        !           704:             }
        !           705:             brogueAssert(!(pmap[monst->xLoc][monst->yLoc].flags & HAS_MONSTER));
        !           706:             pmap[monst->xLoc][monst->yLoc].flags |= HAS_MONSTER;
        !           707:             monst->bookkeepingFlags |= (MB_FOLLOWER | MB_JUST_SUMMONED);
        !           708:             monst->leader = leader;
        !           709:             monst->creatureState = leader->creatureState;
        !           710:             monst->mapToMe = NULL;
        !           711:             if (theHorde->flags & HORDE_DIES_ON_LEADER_DEATH) {
        !           712:                 monst->bookkeepingFlags |= MB_BOUND_TO_LEADER;
        !           713:             }
        !           714:             if (hordeCatalog[hordeID].flags & HORDE_ALLIED_WITH_PLAYER) {
        !           715:                 becomeAllyWith(monst);
        !           716:             }
        !           717:             atLeastOneMinion = true;
        !           718:         }
        !           719:     }
        !           720:
        !           721:     if (atLeastOneMinion && !(theHorde->flags & HORDE_DIES_ON_LEADER_DEATH)) {
        !           722:         leader->bookkeepingFlags |= MB_LEADER;
        !           723:     }
        !           724:
        !           725:     return atLeastOneMinion;
        !           726: }
        !           727:
        !           728: boolean drawManacle(short x, short y, enum directions dir) {
        !           729:     enum tileType manacles[8] = {MANACLE_T, MANACLE_B, MANACLE_L, MANACLE_R, MANACLE_TL, MANACLE_BL, MANACLE_TR, MANACLE_BR};
        !           730:     short newX = x + nbDirs[dir][0];
        !           731:     short newY = y + nbDirs[dir][1];
        !           732:     if (coordinatesAreInMap(newX, newY)
        !           733:         && pmap[newX][newY].layers[DUNGEON] == FLOOR
        !           734:         && pmap[newX][newY].layers[LIQUID] == NOTHING) {
        !           735:
        !           736:         pmap[x + nbDirs[dir][0]][y + nbDirs[dir][1]].layers[SURFACE] = manacles[dir];
        !           737:         return true;
        !           738:     }
        !           739:     return false;
        !           740: }
        !           741:
        !           742: void drawManacles(short x, short y) {
        !           743:     enum directions fallback[4][3] = {{UPLEFT, UP, LEFT}, {DOWNLEFT, DOWN, LEFT}, {UPRIGHT, UP, RIGHT}, {DOWNRIGHT, DOWN, RIGHT}};
        !           744:     short i, j;
        !           745:     for (i = 0; i < 4; i++) {
        !           746:         for (j = 0; j < 3 && !drawManacle(x, y, fallback[i][j]); j++);
        !           747:     }
        !           748: }
        !           749:
        !           750: // If hordeID is 0, it's randomly assigned based on the depth, with a 10% chance of an out-of-depth spawn from 1-5 levels deeper.
        !           751: // If x is negative, location is random.
        !           752: // Returns a pointer to the leader.
        !           753: creature *spawnHorde(short hordeID, short x, short y, unsigned long forbiddenFlags, unsigned long requiredFlags) {
        !           754:     short loc[2];
        !           755:     short i, failsafe, depth;
        !           756:     hordeType *theHorde;
        !           757:     creature *leader, *preexistingMonst;
        !           758:     boolean tryAgain;
        !           759:
        !           760:     if (rogue.depthLevel > 1 && rand_percent(10)) {
        !           761:         depth = rogue.depthLevel + rand_range(1, min(5, rogue.depthLevel / 2));
        !           762:         if (depth > AMULET_LEVEL) {
        !           763:             depth = max(rogue.depthLevel, AMULET_LEVEL);
        !           764:         }
        !           765:         forbiddenFlags |= HORDE_NEVER_OOD;
        !           766:     } else {
        !           767:         depth = rogue.depthLevel;
        !           768:     }
        !           769:
        !           770:     if (hordeID <= 0) {
        !           771:         failsafe = 50;
        !           772:         do {
        !           773:             tryAgain = false;
        !           774:             hordeID = pickHordeType(depth, 0, forbiddenFlags, requiredFlags);
        !           775:             if (hordeID < 0) {
        !           776:                 return NULL;
        !           777:             }
        !           778:             if (x >= 0 && y >= 0) {
        !           779:                 if (cellHasTerrainFlag(x, y, T_PATHING_BLOCKER)
        !           780:                     && (!hordeCatalog[hordeID].spawnsIn || !cellHasTerrainType(x, y, hordeCatalog[hordeID].spawnsIn))) {
        !           781:
        !           782:                     // don't spawn a horde in special terrain unless it's meant to spawn there
        !           783:                     tryAgain = true;
        !           784:                 }
        !           785:                 if (hordeCatalog[hordeID].spawnsIn && !cellHasTerrainType(x, y, hordeCatalog[hordeID].spawnsIn)) {
        !           786:                     // don't spawn a horde on normal terrain if it's meant for special terrain
        !           787:                     tryAgain = true;
        !           788:                 }
        !           789:             }
        !           790:         } while (--failsafe && tryAgain);
        !           791:     }
        !           792:
        !           793:     failsafe = 50;
        !           794:
        !           795:     if (x < 0 || y < 0) {
        !           796:         i = 0;
        !           797:         do {
        !           798:             while (!randomMatchingLocation(&(loc[0]), &(loc[1]), FLOOR, NOTHING, (hordeCatalog[hordeID].spawnsIn ? hordeCatalog[hordeID].spawnsIn : -1))
        !           799:                    || passableArcCount(loc[0], loc[1]) > 1) {
        !           800:                 if (!--failsafe) {
        !           801:                     return NULL;
        !           802:                 }
        !           803:                 hordeID = pickHordeType(depth, 0, forbiddenFlags, 0);
        !           804:
        !           805:                 if (hordeID < 0) {
        !           806:                     return NULL;
        !           807:                 }
        !           808:             }
        !           809:             x = loc[0];
        !           810:             y = loc[1];
        !           811:             i++;
        !           812:
        !           813:             // This "while" condition should contain IN_FIELD_OF_VIEW, since that is specifically
        !           814:             // calculated from the entry stairs when the level is generated, and will prevent monsters
        !           815:             // from spawning within FOV of the entry stairs.
        !           816:         } while (i < 25 && (pmap[x][y].flags & (ANY_KIND_OF_VISIBLE | IN_FIELD_OF_VIEW)));
        !           817:     }
        !           818:
        !           819: //  if (hordeCatalog[hordeID].spawnsIn == DEEP_WATER && pmap[x][y].layers[LIQUID] != DEEP_WATER) {
        !           820: //      message("Waterborne monsters spawned on land!", true);
        !           821: //  }
        !           822:
        !           823:     theHorde = &hordeCatalog[hordeID];
        !           824:
        !           825:     if (theHorde->machine > 0) {
        !           826:         // Build the accompanying machine (e.g. a goblin encampment)
        !           827:         buildAMachine(theHorde->machine, x, y, 0, NULL, NULL, NULL);
        !           828:     }
        !           829:
        !           830:     leader = generateMonster(theHorde->leaderType, true, true);
        !           831:     leader->xLoc = x;
        !           832:     leader->yLoc = y;
        !           833:
        !           834:     if (hordeCatalog[hordeID].flags & HORDE_LEADER_CAPTIVE) {
        !           835:         leader->bookkeepingFlags |= MB_CAPTIVE;
        !           836:         leader->creatureState = MONSTER_WANDERING;
        !           837:         if (leader->info.turnsBetweenRegen > 0) {
        !           838:             leader->currentHP = leader->info.maxHP / 4 + 1;
        !           839:         }
        !           840:
        !           841:         // Draw the manacles unless the horde spawns in weird terrain (e.g. cages).
        !           842:         if (!hordeCatalog[hordeID].spawnsIn) {
        !           843:             drawManacles(x, y);
        !           844:         }
        !           845:     } else if (hordeCatalog[hordeID].flags & HORDE_ALLIED_WITH_PLAYER) {
        !           846:         becomeAllyWith(leader);
        !           847:     }
        !           848:
        !           849:     if (hordeCatalog[hordeID].flags & HORDE_SACRIFICE_TARGET) {
        !           850:         leader->bookkeepingFlags |= MB_MARKED_FOR_SACRIFICE;
        !           851:         leader->info.intrinsicLightType = SACRIFICE_MARK_LIGHT;
        !           852:     }
        !           853:
        !           854:     if (rogue.patchVersion >= 3 && (theHorde->flags & HORDE_MACHINE_THIEF)) {
        !           855:         leader->safetyMap = allocGrid(); // Keep thieves from fleeing before they see the player
        !           856:         fillGrid(leader->safetyMap, 0);
        !           857:     }
        !           858:
        !           859:     preexistingMonst = monsterAtLoc(x, y);
        !           860:     if (preexistingMonst) {
        !           861:         killCreature(preexistingMonst, true); // If there's already a monster here, quietly bury the body.
        !           862:     }
        !           863:
        !           864:     brogueAssert(!(pmap[x][y].flags & HAS_MONSTER));
        !           865:
        !           866:     pmap[x][y].flags |= HAS_MONSTER;
        !           867:     if (playerCanSeeOrSense(x, y)) {
        !           868:         refreshDungeonCell(x, y);
        !           869:     }
        !           870:     if (monsterCanSubmergeNow(leader)) {
        !           871:         leader->bookkeepingFlags |= MB_SUBMERGED;
        !           872:     }
        !           873:
        !           874:     spawnMinions(hordeID, leader, false);
        !           875:
        !           876:     return leader;
        !           877: }
        !           878:
        !           879: void fadeInMonster(creature *monst) {
        !           880:     color fColor, bColor;
        !           881:     enum displayGlyph displayChar;
        !           882:     getCellAppearance(monst->xLoc, monst->yLoc, &displayChar, &fColor, &bColor);
        !           883:     flashMonster(monst, &bColor, 100);
        !           884: }
        !           885:
        !           886: boolean removeMonsterFromChain(creature *monst, creature *theChain) {
        !           887:     creature *previousMonster;
        !           888:
        !           889:     for (previousMonster = theChain;
        !           890:          previousMonster->nextCreature;
        !           891:          previousMonster = previousMonster->nextCreature) {
        !           892:         if (previousMonster->nextCreature == monst) {
        !           893:             previousMonster->nextCreature = monst->nextCreature;
        !           894:             return true;
        !           895:         }
        !           896:     }
        !           897:     return false;
        !           898: }
        !           899:
        !           900: boolean summonMinions(creature *summoner) {
        !           901:     enum monsterTypes summonerType = summoner->info.monsterID;
        !           902:     const short hordeID = pickHordeType(0, summonerType, 0, 0);
        !           903:     short seenMinionCount = 0, x, y;
        !           904:     boolean atLeastOneMinion = false;
        !           905:     creature *monst, *host;
        !           906:     char buf[DCOLS];
        !           907:     char monstName[DCOLS];
        !           908:     short **grid;
        !           909:
        !           910:     if (hordeID < 0) {
        !           911:         return false;
        !           912:     }
        !           913:
        !           914:     host = NULL;
        !           915:
        !           916:     if (summoner->info.abilityFlags & MA_ENTER_SUMMONS) {
        !           917:         pmap[summoner->xLoc][summoner->yLoc].flags &= ~HAS_MONSTER;
        !           918:         removeMonsterFromChain(summoner, monsters);
        !           919:     }
        !           920:
        !           921:     atLeastOneMinion = spawnMinions(hordeID, summoner, true);
        !           922:
        !           923:     if (hordeCatalog[hordeID].flags & HORDE_SUMMONED_AT_DISTANCE) {
        !           924:         // Create a grid where "1" denotes a valid summoning location: within DCOLS/2 pathing distance,
        !           925:         // not in harmful terrain, and outside of the player's field of view.
        !           926:         grid = allocGrid();
        !           927:         fillGrid(grid, 0);
        !           928:         calculateDistances(grid, summoner->xLoc, summoner->yLoc, (T_PATHING_BLOCKER | T_SACRED), NULL, true, true);
        !           929:         findReplaceGrid(grid, 1, DCOLS/2, 1);
        !           930:         findReplaceGrid(grid, 2, 30000, 0);
        !           931:         getTerrainGrid(grid, 0, (T_PATHING_BLOCKER | T_HARMFUL_TERRAIN), (IN_FIELD_OF_VIEW | CLAIRVOYANT_VISIBLE | HAS_PLAYER | HAS_MONSTER));
        !           932:     } else {
        !           933:         grid = NULL;
        !           934:     }
        !           935:
        !           936:     for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
        !           937:         if (monst != summoner && monstersAreTeammates(monst, summoner)
        !           938:             && (monst->bookkeepingFlags & MB_JUST_SUMMONED)) {
        !           939:
        !           940:             if (hordeCatalog[hordeID].flags & HORDE_SUMMONED_AT_DISTANCE) {
        !           941:                 x = y = -1;
        !           942:                 randomLocationInGrid(grid, &x, &y, 1);
        !           943:                 teleport(monst, x, y, true);
        !           944:                 if (x != -1 && y != -1 && grid != NULL) {
        !           945:                     grid[x][y] = 0;
        !           946:                 }
        !           947:             }
        !           948:
        !           949:             monst->bookkeepingFlags &= ~MB_JUST_SUMMONED;
        !           950:             if (canSeeMonster(monst)) {
        !           951:                 seenMinionCount++;
        !           952:                 refreshDungeonCell(monst->xLoc, monst->yLoc);
        !           953:             }
        !           954:             monst->ticksUntilTurn = 101;
        !           955:             monst->leader = summoner;
        !           956:             if (monst->carriedItem) {
        !           957:                 deleteItem(monst->carriedItem);
        !           958:                 monst->carriedItem = NULL;
        !           959:             }
        !           960:             fadeInMonster(monst);
        !           961:             host = monst;
        !           962:         }
        !           963:     }
        !           964:
        !           965:     if (canSeeMonster(summoner)) {
        !           966:         monsterName(monstName, summoner, true);
        !           967:         if (monsterText[summoner->info.monsterID].summonMessage[0]) {
        !           968:             sprintf(buf, "%s %s", monstName, monsterText[summoner->info.monsterID].summonMessage);
        !           969:         } else {
        !           970:             sprintf(buf, "%s incants darkly!", monstName);
        !           971:         }
        !           972:         message(buf, false);
        !           973:     }
        !           974:
        !           975:     if (summoner->info.abilityFlags & MA_ENTER_SUMMONS) {
        !           976:         if (atLeastOneMinion
        !           977:             && host) {
        !           978:
        !           979:             host->carriedMonster = summoner;
        !           980:             demoteMonsterFromLeadership(summoner);
        !           981:             refreshDungeonCell(summoner->xLoc, summoner->yLoc);
        !           982:         } else {
        !           983:             pmap[summoner->xLoc][summoner->yLoc].flags |= HAS_MONSTER;
        !           984:             summoner->nextCreature = monsters->nextCreature;
        !           985:             monsters->nextCreature = summoner;
        !           986:         }
        !           987:     } else if (atLeastOneMinion) {
        !           988:         summoner->bookkeepingFlags |= MB_LEADER;
        !           989:     }
        !           990:     createFlare(summoner->xLoc, summoner->yLoc, SUMMONING_FLASH_LIGHT);
        !           991:
        !           992:     if (grid) {
        !           993:         freeGrid(grid);
        !           994:     }
        !           995:
        !           996:     return atLeastOneMinion;
        !           997: }
        !           998:
        !           999: // Generates and places monsters for the level.
        !          1000: void populateMonsters() {
        !          1001:     if (!MONSTERS_ENABLED) {
        !          1002:         return;
        !          1003:     }
        !          1004:
        !          1005:     short i, numberOfMonsters = min(20, 6 + 3 * max(0, rogue.depthLevel - AMULET_LEVEL)); // almost always 6.
        !          1006:
        !          1007:     while (rand_percent(60)) {
        !          1008:         numberOfMonsters++;
        !          1009:     }
        !          1010:     for (i=0; i<numberOfMonsters; i++) {
        !          1011:         spawnHorde(0, -1, -1, (HORDE_IS_SUMMONED | HORDE_MACHINE_ONLY), 0); // random horde type, random location
        !          1012:     }
        !          1013: }
        !          1014:
        !          1015: boolean getRandomMonsterSpawnLocation(short *x, short *y) {
        !          1016:     short **grid;
        !          1017:
        !          1018:     grid = allocGrid();
        !          1019:     fillGrid(grid, 0);
        !          1020:     calculateDistances(grid, player.xLoc, player.yLoc, T_DIVIDES_LEVEL, NULL, true, true);
        !          1021:     getTerrainGrid(grid, 0, (T_PATHING_BLOCKER | T_HARMFUL_TERRAIN), (HAS_PLAYER | HAS_MONSTER | HAS_STAIRS | IN_FIELD_OF_VIEW));
        !          1022:     findReplaceGrid(grid, -30000, DCOLS/2-1, 0);
        !          1023:     findReplaceGrid(grid, 30000, 30000, 0);
        !          1024:     findReplaceGrid(grid, DCOLS/2, 30000-1, 1);
        !          1025:     randomLocationInGrid(grid, x, y, 1);
        !          1026:     if (*x < 0 || *y < 0) {
        !          1027:         fillGrid(grid, 1);
        !          1028:         getTerrainGrid(grid, 0, (T_PATHING_BLOCKER | T_HARMFUL_TERRAIN), (HAS_PLAYER | HAS_MONSTER | HAS_STAIRS | IN_FIELD_OF_VIEW | IS_IN_MACHINE));
        !          1029:         randomLocationInGrid(grid, x, y, 1);
        !          1030:     }
        !          1031:     //    DEBUG {
        !          1032:     //        dumpLevelToScreen();
        !          1033:     //        hiliteGrid(grid, &orange, 50);
        !          1034:     //        plotCharWithColor('X', mapToWindowX(x), mapToWindowY(y), &black, &white);
        !          1035:     //        temporaryMessage("Horde spawn location possibilities:", true);
        !          1036:     //    }
        !          1037:     freeGrid(grid);
        !          1038:     if (*x < 0 || *y < 0) {
        !          1039:         return false;
        !          1040:     }
        !          1041:     return true;
        !          1042: }
        !          1043:
        !          1044: void spawnPeriodicHorde() {
        !          1045:     creature *monst, *monst2;
        !          1046:     short x, y;
        !          1047:
        !          1048:     if (!MONSTERS_ENABLED) {
        !          1049:         return;
        !          1050:     }
        !          1051:
        !          1052:     if (getRandomMonsterSpawnLocation(&x, &y)) {
        !          1053:         monst = spawnHorde(0, x, y, (HORDE_IS_SUMMONED | HORDE_LEADER_CAPTIVE | HORDE_NO_PERIODIC_SPAWN | HORDE_MACHINE_ONLY), 0);
        !          1054:         if (monst) {
        !          1055:             monst->creatureState = MONSTER_WANDERING;
        !          1056:             for (monst2 = monsters->nextCreature; monst2 != NULL; monst2 = monst2->nextCreature) {
        !          1057:                 if (monst2->leader == monst) {
        !          1058:                     monst2->creatureState = MONSTER_WANDERING;
        !          1059:                 }
        !          1060:             }
        !          1061:         }
        !          1062:     }
        !          1063: }
        !          1064:
        !          1065: // x and y are optional.
        !          1066: void teleport(creature *monst, short x, short y, boolean respectTerrainAvoidancePreferences) {
        !          1067:     short **grid, i, j;
        !          1068:     char monstFOV[DCOLS][DROWS];
        !          1069:
        !          1070:     if (!coordinatesAreInMap(x, y)) {
        !          1071:         zeroOutGrid(monstFOV);
        !          1072:         getFOVMask(monstFOV, monst->xLoc, monst->yLoc, DCOLS * FP_FACTOR, T_OBSTRUCTS_VISION, 0, false);
        !          1073:         grid = allocGrid();
        !          1074:         fillGrid(grid, 0);
        !          1075:         calculateDistances(grid, monst->xLoc, monst->yLoc, forbiddenFlagsForMonster(&(monst->info)) & T_DIVIDES_LEVEL, NULL, true, false);
        !          1076:         findReplaceGrid(grid, -30000, DCOLS/2, 0);
        !          1077:         findReplaceGrid(grid, 2, 30000, 1);
        !          1078:         if (validLocationCount(grid, 1) < 1) {
        !          1079:             fillGrid(grid, 1);
        !          1080:         }
        !          1081:         if (respectTerrainAvoidancePreferences) {
        !          1082:             if (monst->info.flags & MONST_RESTRICTED_TO_LIQUID) {
        !          1083:                 fillGrid(grid, 0);
        !          1084:                 getTMGrid(grid, 1, TM_ALLOWS_SUBMERGING);
        !          1085:             }
        !          1086:             getTerrainGrid(grid, 0, avoidedFlagsForMonster(&(monst->info)), (IS_IN_MACHINE | HAS_PLAYER | HAS_MONSTER | HAS_STAIRS));
        !          1087:         } else {
        !          1088:             getTerrainGrid(grid, 0, forbiddenFlagsForMonster(&(monst->info)), (IS_IN_MACHINE | HAS_PLAYER | HAS_MONSTER | HAS_STAIRS));
        !          1089:         }
        !          1090:         for (i=0; i<DCOLS; i++) {
        !          1091:             for (j=0; j<DROWS; j++) {
        !          1092:                 if (monstFOV[i][j]) {
        !          1093:                     grid[i][j] = 0;
        !          1094:                 }
        !          1095:             }
        !          1096:         }
        !          1097:         randomLocationInGrid(grid, &x, &y, 1);
        !          1098: //        DEBUG {
        !          1099: //            dumpLevelToScreen();
        !          1100: //            hiliteGrid(grid, &orange, 50);
        !          1101: //            plotCharWithColor('X', mapToWindowX(x), mapToWindowY(y), &white, &red);
        !          1102: //            temporaryMessage("Teleport candidate locations:", true);
        !          1103: //        }
        !          1104:         freeGrid(grid);
        !          1105:         if (x < 0 || y < 0) {
        !          1106:             return; // Failure!
        !          1107:         }
        !          1108:     }
        !          1109:     setMonsterLocation(monst, x, y);
        !          1110:     if (monst != &player) {
        !          1111:         chooseNewWanderDestination(monst);
        !          1112:     }
        !          1113: }
        !          1114:
        !          1115: boolean isValidWanderDestination(creature *monst, short wpIndex) {
        !          1116:     return (wpIndex >= 0
        !          1117:             && wpIndex < rogue.wpCount
        !          1118:             && !monst->waypointAlreadyVisited[wpIndex]
        !          1119:             && rogue.wpDistance[wpIndex][monst->xLoc][monst->yLoc] >= 0
        !          1120:             && nextStep(rogue.wpDistance[wpIndex], monst->xLoc, monst->yLoc, monst, false) != NO_DIRECTION);
        !          1121: }
        !          1122:
        !          1123: short closestWaypointIndex(creature *monst) {
        !          1124:     short i, closestDistance, closestIndex;
        !          1125:
        !          1126:     closestDistance = DCOLS/2;
        !          1127:     closestIndex = -1;
        !          1128:     for (i=0; i < rogue.wpCount; i++) {
        !          1129:         if (isValidWanderDestination(monst, i)
        !          1130:             && rogue.wpDistance[i][monst->xLoc][monst->yLoc] < closestDistance) {
        !          1131:
        !          1132:             closestDistance = rogue.wpDistance[i][monst->xLoc][monst->yLoc];
        !          1133:             closestIndex = i;
        !          1134:         }
        !          1135:     }
        !          1136:     return closestIndex;
        !          1137: }
        !          1138:
        !          1139: void chooseNewWanderDestination(creature *monst) {
        !          1140:     short i;
        !          1141:
        !          1142:     brogueAssert(monst->targetWaypointIndex < MAX_WAYPOINT_COUNT);
        !          1143:     brogueAssert(rogue.wpCount > 0 && rogue.wpCount <= MAX_WAYPOINT_COUNT);
        !          1144:
        !          1145:     // Set two checkpoints at random to false (which equilibrates to 50% of checkpoints being active).
        !          1146:     monst->waypointAlreadyVisited[rand_range(0, rogue.wpCount - 1)] = false;
        !          1147:     monst->waypointAlreadyVisited[rand_range(0, rogue.wpCount - 1)] = false;
        !          1148:     // Set the targeted checkpoint to true.
        !          1149:     if (monst->targetWaypointIndex >= 0) {
        !          1150:         monst->waypointAlreadyVisited[monst->targetWaypointIndex] = true;
        !          1151:     }
        !          1152:
        !          1153:     monst->targetWaypointIndex = closestWaypointIndex(monst); // Will be -1 if no waypoints were available.
        !          1154:     if (monst->targetWaypointIndex == -1) {
        !          1155:         for (i=0; i < rogue.wpCount; i++) {
        !          1156:             monst->waypointAlreadyVisited[i] = 0;
        !          1157:         }
        !          1158:         monst->targetWaypointIndex = closestWaypointIndex(monst);
        !          1159:     }
        !          1160: }
        !          1161:
        !          1162: enum subseqDFTypes {
        !          1163:     SUBSEQ_PROMOTE = 0,
        !          1164:     SUBSEQ_BURN,
        !          1165:     SUBSEQ_DISCOVER,
        !          1166: };
        !          1167:
        !          1168: // Returns the terrain flags of this tile after it's promoted according to the event corresponding to subseqDFTypes.
        !          1169: unsigned long successorTerrainFlags(enum tileType tile, enum subseqDFTypes promotionType) {
        !          1170:     enum dungeonFeatureTypes DF = 0;
        !          1171:
        !          1172:     switch (promotionType) {
        !          1173:         case SUBSEQ_PROMOTE:
        !          1174:             DF = tileCatalog[tile].promoteType;
        !          1175:             break;
        !          1176:         case SUBSEQ_BURN:
        !          1177:             DF = tileCatalog[tile].fireType;
        !          1178:             break;
        !          1179:         case SUBSEQ_DISCOVER:
        !          1180:             DF = tileCatalog[tile].discoverType;
        !          1181:             break;
        !          1182:         default:
        !          1183:             break;
        !          1184:     }
        !          1185:
        !          1186:     if (DF) {
        !          1187:         return tileCatalog[dungeonFeatureCatalog[DF].tile].flags;
        !          1188:     } else {
        !          1189:         return 0;
        !          1190:     }
        !          1191: }
        !          1192:
        !          1193: unsigned long burnedTerrainFlagsAtLoc(short x, short y) {
        !          1194:     short layer;
        !          1195:     unsigned long flags = 0;
        !          1196:
        !          1197:     for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
        !          1198:         if (tileCatalog[pmap[x][y].layers[layer]].flags & T_IS_FLAMMABLE) {
        !          1199:             flags |= successorTerrainFlags(pmap[x][y].layers[layer], SUBSEQ_BURN);
        !          1200:             if (tileCatalog[pmap[x][y].layers[layer]].mechFlags & TM_EXPLOSIVE_PROMOTE) {
        !          1201:                 flags |= successorTerrainFlags(pmap[x][y].layers[layer], SUBSEQ_PROMOTE);
        !          1202:             }
        !          1203:         }
        !          1204:     }
        !          1205:
        !          1206:     return flags;
        !          1207: }
        !          1208:
        !          1209: unsigned long discoveredTerrainFlagsAtLoc(short x, short y) {
        !          1210:     short layer;
        !          1211:     unsigned long flags = 0;
        !          1212:
        !          1213:     for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
        !          1214:         if (tileCatalog[pmap[x][y].layers[layer]].mechFlags & TM_IS_SECRET) {
        !          1215:             flags |= successorTerrainFlags(pmap[x][y].layers[layer], SUBSEQ_DISCOVER);
        !          1216:         }
        !          1217:     }
        !          1218:
        !          1219:     return flags;
        !          1220: }
        !          1221:
        !          1222: boolean monsterAvoids(creature *monst, short x, short y) {
        !          1223:     unsigned long terrainImmunities;
        !          1224:     creature *defender;
        !          1225:     unsigned long tFlags, cFlags;
        !          1226:
        !          1227:     getLocationFlags(x, y, &tFlags, NULL, &cFlags, monst == &player);
        !          1228:
        !          1229:     // everyone but the player avoids the stairs
        !          1230:     if ((x == rogue.downLoc[0] && y == rogue.downLoc[1])
        !          1231:         || (x == rogue.upLoc[0] && y == rogue.upLoc[1])) {
        !          1232:
        !          1233:         return monst != &player;
        !          1234:     }
        !          1235:
        !          1236:     // dry land
        !          1237:     if (monst->info.flags & MONST_RESTRICTED_TO_LIQUID
        !          1238:         && !cellHasTMFlag(x, y, TM_ALLOWS_SUBMERGING)) {
        !          1239:         return true;
        !          1240:     }
        !          1241:
        !          1242:     // non-allied monsters can always attack the player
        !          1243:     if (player.xLoc == x && player.yLoc == y && monst != &player && monst->creatureState != MONSTER_ALLY) {
        !          1244:         return false;
        !          1245:     }
        !          1246:
        !          1247:     // walls
        !          1248:     if (tFlags & T_OBSTRUCTS_PASSABILITY) {
        !          1249:         if (monst != &player
        !          1250:             && cellHasTMFlag(x, y, TM_IS_SECRET)
        !          1251:             && !(discoveredTerrainFlagsAtLoc(x, y) & avoidedFlagsForMonster(&(monst->info)))) {
        !          1252:             // This is so monsters can use secret doors but won't embed themselves in secret levers.
        !          1253:             return false;
        !          1254:         }
        !          1255:         if (distanceBetween(monst->xLoc, monst->yLoc, x, y) <= 1) {
        !          1256:             defender = monsterAtLoc(x, y);
        !          1257:             if (defender
        !          1258:                 && (defender->info.flags & MONST_ATTACKABLE_THRU_WALLS)) {
        !          1259:                 return false;
        !          1260:             }
        !          1261:         }
        !          1262:         return true;
        !          1263:     }
        !          1264:
        !          1265:     // Monsters can always attack unfriendly neighboring monsters,
        !          1266:     // unless it is immune to us for whatever reason.
        !          1267:     if (distanceBetween(monst->xLoc, monst->yLoc, x, y) <= 1) {
        !          1268:         defender = monsterAtLoc(x, y);
        !          1269:         if (defender
        !          1270:             && !(defender->bookkeepingFlags & MB_IS_DYING)
        !          1271:             && monsterWillAttackTarget(monst, defender)) {
        !          1272:
        !          1273:             if (attackWouldBeFutile(monst, defender)) {
        !          1274:                 return true;
        !          1275:             } else {
        !          1276:                 return false;
        !          1277:             }
        !          1278:         }
        !          1279:     }
        !          1280:
        !          1281:     // Monsters always avoid enemy monsters that we can't damage.
        !          1282:     defender = monsterAtLoc(x, y);
        !          1283:     if (defender
        !          1284:         && !(defender->bookkeepingFlags & MB_IS_DYING)
        !          1285:         && monstersAreEnemies(monst, defender)
        !          1286:         && attackWouldBeFutile(monst, defender)) {
        !          1287:
        !          1288:         return true;
        !          1289:     }
        !          1290:
        !          1291:     // hidden terrain
        !          1292:     if (cellHasTMFlag(x, y, TM_IS_SECRET) && monst == &player) {
        !          1293:         return false; // player won't avoid what he doesn't know about
        !          1294:     }
        !          1295:
        !          1296:     // Determine invulnerabilities based only on monster characteristics.
        !          1297:     terrainImmunities = 0;
        !          1298:     if (monst->status[STATUS_IMMUNE_TO_FIRE]) {
        !          1299:         terrainImmunities |= (T_IS_FIRE | T_SPONTANEOUSLY_IGNITES | T_LAVA_INSTA_DEATH);
        !          1300:     }
        !          1301:     if (monst->info.flags & MONST_INVULNERABLE) {
        !          1302:         terrainImmunities |= T_HARMFUL_TERRAIN | T_ENTANGLES | T_SPONTANEOUSLY_IGNITES | T_LAVA_INSTA_DEATH;
        !          1303:     }
        !          1304:     if (monst->info.flags & MONST_INANIMATE) {
        !          1305:         terrainImmunities |= (T_CAUSES_DAMAGE | T_CAUSES_PARALYSIS | T_CAUSES_CONFUSION | T_CAUSES_NAUSEA | T_CAUSES_POISON);
        !          1306:     }
        !          1307:     if (monst->status[STATUS_LEVITATING]) {
        !          1308:         terrainImmunities |= (T_AUTO_DESCENT | T_CAUSES_POISON | T_IS_DEEP_WATER | T_IS_DF_TRAP | T_LAVA_INSTA_DEATH);
        !          1309:     }
        !          1310:     if (monst->info.flags & MONST_IMMUNE_TO_WEBS) {
        !          1311:         terrainImmunities |= T_ENTANGLES;
        !          1312:     }
        !          1313:     if (monst->info.flags & MONST_IMMUNE_TO_WATER) {
        !          1314:         terrainImmunities |= T_IS_DEEP_WATER;
        !          1315:     }
        !          1316:     if (monst == &player) {
        !          1317:         terrainImmunities |= T_SACRED;
        !          1318:     }
        !          1319:     if (monst == &player
        !          1320:         && rogue.armor
        !          1321:         && (rogue.armor->flags & ITEM_RUNIC)
        !          1322:         && rogue.armor->enchant2 == A_RESPIRATION) {
        !          1323:
        !          1324:         terrainImmunities |= T_RESPIRATION_IMMUNITIES;
        !          1325:     }
        !          1326:
        !          1327:     // sacred ground
        !          1328:     if ((tFlags & T_SACRED & ~terrainImmunities)) {
        !          1329:         return true;
        !          1330:     }
        !          1331:
        !          1332:     // brimstone
        !          1333:     if (!(monst->status[STATUS_IMMUNE_TO_FIRE])
        !          1334:         && !(monst->info.flags & MONST_INVULNERABLE)
        !          1335:         && (tFlags & T_SPONTANEOUSLY_IGNITES)
        !          1336:         && !(cFlags & (HAS_MONSTER | HAS_PLAYER))
        !          1337:         && !cellHasTerrainFlag(monst->xLoc, monst->yLoc, T_IS_FIRE | T_SPONTANEOUSLY_IGNITES)
        !          1338:         && (monst == &player || (monst->creatureState != MONSTER_TRACKING_SCENT && monst->creatureState != MONSTER_FLEEING))) {
        !          1339:         return true;
        !          1340:     }
        !          1341:
        !          1342:     // burning wandering monsters avoid flammable terrain out of common courtesy
        !          1343:     if (monst != &player
        !          1344:         && monst->creatureState == MONSTER_WANDERING
        !          1345:         && (monst->info.flags & MONST_FIERY)
        !          1346:         && (tFlags & T_IS_FLAMMABLE)) {
        !          1347:
        !          1348:         return true;
        !          1349:     }
        !          1350:
        !          1351:     // burning monsters avoid explosive terrain and steam-emitting terrain
        !          1352:     if (monst != &player
        !          1353:         && monst->status[STATUS_BURNING]
        !          1354:         && (burnedTerrainFlagsAtLoc(x, y) & (T_CAUSES_EXPLOSIVE_DAMAGE | T_CAUSES_DAMAGE | T_AUTO_DESCENT) & ~terrainImmunities)) {
        !          1355:
        !          1356:         return true;
        !          1357:     }
        !          1358:
        !          1359:     // fire
        !          1360:     if ((tFlags & T_IS_FIRE & ~terrainImmunities)
        !          1361:         && !cellHasTerrainFlag(monst->xLoc, monst->yLoc, T_IS_FIRE)
        !          1362:         && !(cFlags & (HAS_MONSTER | HAS_PLAYER))
        !          1363:         && (monst != &player || rogue.mapToShore[x][y] >= player.status[STATUS_IMMUNE_TO_FIRE])) {
        !          1364:         return true;
        !          1365:     }
        !          1366:
        !          1367:     // non-fire harmful terrain
        !          1368:     if ((tFlags & T_HARMFUL_TERRAIN & ~T_IS_FIRE & ~terrainImmunities)
        !          1369:         && !cellHasTerrainFlag(monst->xLoc, monst->yLoc, (T_HARMFUL_TERRAIN & ~T_IS_FIRE))) {
        !          1370:         return true;
        !          1371:     }
        !          1372:
        !          1373:     // chasms or trap doors
        !          1374:     if ((tFlags & T_AUTO_DESCENT & ~terrainImmunities)
        !          1375:         && (!(tFlags & T_ENTANGLES) || !(monst->info.flags & MONST_IMMUNE_TO_WEBS))) {
        !          1376:         return true;
        !          1377:     }
        !          1378:
        !          1379:     // gas or other environmental traps
        !          1380:     if ((tFlags & T_IS_DF_TRAP & ~terrainImmunities)
        !          1381:         && !(cFlags & PRESSURE_PLATE_DEPRESSED)
        !          1382:         && (monst == &player || monst->creatureState == MONSTER_WANDERING
        !          1383:             || (monst->creatureState == MONSTER_ALLY && !(cellHasTMFlag(x, y, TM_IS_SECRET))))
        !          1384:         && !(monst->status[STATUS_ENTRANCED])
        !          1385:         && (!(tFlags & T_ENTANGLES) || !(monst->info.flags & MONST_IMMUNE_TO_WEBS))) {
        !          1386:         return true;
        !          1387:     }
        !          1388:
        !          1389:     // lava
        !          1390:     if ((tFlags & T_LAVA_INSTA_DEATH & ~terrainImmunities)
        !          1391:         && (!(tFlags & T_ENTANGLES) || !(monst->info.flags & MONST_IMMUNE_TO_WEBS))
        !          1392:         && (monst != &player || rogue.mapToShore[x][y] >= max(player.status[STATUS_IMMUNE_TO_FIRE], player.status[STATUS_LEVITATING]))) {
        !          1393:         return true;
        !          1394:     }
        !          1395:
        !          1396:     // deep water
        !          1397:     if ((tFlags & T_IS_DEEP_WATER & ~terrainImmunities)
        !          1398:         && (!(tFlags & T_ENTANGLES) || !(monst->info.flags & MONST_IMMUNE_TO_WEBS))
        !          1399:         && !cellHasTerrainFlag(monst->xLoc, monst->yLoc, T_IS_DEEP_WATER)) {
        !          1400:         return true; // avoid only if not already in it
        !          1401:     }
        !          1402:
        !          1403:     // poisonous lichen
        !          1404:     if ((tFlags & T_CAUSES_POISON & ~terrainImmunities)
        !          1405:         && !cellHasTerrainFlag(monst->xLoc, monst->yLoc, T_CAUSES_POISON)
        !          1406:         && (monst == &player || monst->creatureState != MONSTER_TRACKING_SCENT || monst->currentHP < 10)) {
        !          1407:         return true;
        !          1408:     }
        !          1409:
        !          1410:     // Smart monsters don't attack in corridors if they belong to a group and they can help it.
        !          1411:     if ((monst->info.abilityFlags & MA_AVOID_CORRIDORS)
        !          1412:         && monst->creatureState == MONSTER_TRACKING_SCENT
        !          1413:         && (monst->bookkeepingFlags & (MB_FOLLOWER | MB_LEADER))
        !          1414:         && passableArcCount(x, y) >= 2
        !          1415:         && passableArcCount(monst->xLoc, monst->yLoc) < 2
        !          1416:         && !cellHasTerrainFlag(monst->xLoc, monst->yLoc, (T_HARMFUL_TERRAIN & ~terrainImmunities))) {
        !          1417:         return true;
        !          1418:     }
        !          1419:
        !          1420:     return false;
        !          1421: }
        !          1422:
        !          1423: boolean moveMonsterPassivelyTowards(creature *monst, short targetLoc[2], boolean willingToAttackPlayer) {
        !          1424:     short x, y, dx, dy, newX, newY;
        !          1425:
        !          1426:     x = monst->xLoc;
        !          1427:     y = monst->yLoc;
        !          1428:
        !          1429:     if (targetLoc[0] == x) {
        !          1430:         dx = 0;
        !          1431:     } else {
        !          1432:         dx = (targetLoc[0] < x ? -1 : 1);
        !          1433:     }
        !          1434:     if (targetLoc[1] == y) {
        !          1435:         dy = 0;
        !          1436:     } else {
        !          1437:         dy = (targetLoc[1] < y ? -1 : 1);
        !          1438:     }
        !          1439:
        !          1440:     if (dx == 0 && dy == 0) { // already at the destination
        !          1441:         return false;
        !          1442:     }
        !          1443:
        !          1444:     newX = x + dx;
        !          1445:     newY = y + dy;
        !          1446:
        !          1447:     if (!coordinatesAreInMap(newX, newY)) {
        !          1448:         return false;
        !          1449:     }
        !          1450:
        !          1451:     if (monst->creatureState != MONSTER_TRACKING_SCENT && dx && dy) {
        !          1452:         if (abs(targetLoc[0] - x) > abs(targetLoc[1] - y) && rand_range(0, abs(targetLoc[0] - x)) > abs(targetLoc[1] - y)) {
        !          1453:             if (!(monsterAvoids(monst, newX, y) || (!willingToAttackPlayer && (pmap[newX][y].flags & HAS_PLAYER)) || !moveMonster(monst, dx, 0))) {
        !          1454:                 return true;
        !          1455:             }
        !          1456:         } else if (abs(targetLoc[0] - x) < abs(targetLoc[1] - y) && rand_range(0, abs(targetLoc[1] - y)) > abs(targetLoc[0] - x)) {
        !          1457:             if (!(monsterAvoids(monst, x, newY) || (!willingToAttackPlayer && (pmap[x][newY].flags & HAS_PLAYER)) || !moveMonster(monst, 0, dy))) {
        !          1458:                 return true;
        !          1459:             }
        !          1460:         }
        !          1461:     }
        !          1462:
        !          1463:     // Try to move toward the goal diagonally if possible or else straight.
        !          1464:     // If that fails, try both directions for the shorter coordinate.
        !          1465:     // If they all fail, return false.
        !          1466:     if (monsterAvoids(monst, newX, newY) || (!willingToAttackPlayer && (pmap[newX][newY].flags & HAS_PLAYER)) || !moveMonster(monst, dx, dy)) {
        !          1467:         if (distanceBetween(x, y, targetLoc[0], targetLoc[1]) <= 1 && (dx == 0 || dy == 0)) { // cardinally adjacent
        !          1468:             return false; // destination is blocked
        !          1469:         }
        !          1470:         //abs(targetLoc[0] - x) < abs(targetLoc[1] - y)
        !          1471:         if ((max(targetLoc[0], x) - min(targetLoc[0], x)) < (max(targetLoc[1], y) - min(targetLoc[1], y))) {
        !          1472:             if (monsterAvoids(monst, x, newY) || (!willingToAttackPlayer && pmap[x][newY].flags & HAS_PLAYER) || !moveMonster(monst, 0, dy)) {
        !          1473:                 if (monsterAvoids(monst, newX, y) || (!willingToAttackPlayer &&  pmap[newX][y].flags & HAS_PLAYER) || !moveMonster(monst, dx, 0)) {
        !          1474:                     if (monsterAvoids(monst, x-1, newY) || (!willingToAttackPlayer && pmap[x-1][newY].flags & HAS_PLAYER) || !moveMonster(monst, -1, dy)) {
        !          1475:                         if (monsterAvoids(monst, x+1, newY) || (!willingToAttackPlayer && pmap[x+1][newY].flags & HAS_PLAYER) || !moveMonster(monst, 1, dy)) {
        !          1476:                             return false;
        !          1477:                         }
        !          1478:                     }
        !          1479:                 }
        !          1480:             }
        !          1481:         } else {
        !          1482:             if (monsterAvoids(monst, newX, y) || (!willingToAttackPlayer && pmap[newX][y].flags & HAS_PLAYER) || !moveMonster(monst, dx, 0)) {
        !          1483:                 if (monsterAvoids(monst, x, newY) || (!willingToAttackPlayer && pmap[x][newY].flags & HAS_PLAYER) || !moveMonster(monst, 0, dy)) {
        !          1484:                     if (monsterAvoids(monst, newX, y-1) || (!willingToAttackPlayer && pmap[newX][y-1].flags & HAS_PLAYER) || !moveMonster(monst, dx, -1)) {
        !          1485:                         if (monsterAvoids(monst, newX, y+1) || (!willingToAttackPlayer && pmap[newX][y+1].flags & HAS_PLAYER) || !moveMonster(monst, dx, 1)) {
        !          1486:                             return false;
        !          1487:                         }
        !          1488:                     }
        !          1489:                 }
        !          1490:             }
        !          1491:         }
        !          1492:     }
        !          1493:     return true;
        !          1494: }
        !          1495:
        !          1496: short distanceBetween(short x1, short y1, short x2, short y2) {
        !          1497:     return max(abs(x1 - x2), abs(y1 - y2));
        !          1498: }
        !          1499:
        !          1500: void alertMonster(creature *monst) {
        !          1501:     monst->creatureState = (monst->creatureMode == MODE_PERM_FLEEING ? MONSTER_FLEEING : MONSTER_TRACKING_SCENT);
        !          1502:     monst->lastSeenPlayerAt[0] = player.xLoc;
        !          1503:     monst->lastSeenPlayerAt[1] = player.yLoc;
        !          1504: }
        !          1505:
        !          1506: void wakeUp(creature *monst) {
        !          1507:     creature *teammate;
        !          1508:
        !          1509:     if (monst->creatureState != MONSTER_ALLY) {
        !          1510:         alertMonster(monst);
        !          1511:     }
        !          1512:     monst->ticksUntilTurn = 100;
        !          1513:     for (teammate = monsters->nextCreature; teammate != NULL; teammate = teammate->nextCreature) {
        !          1514:         if (monst != teammate && monstersAreTeammates(monst, teammate) && teammate->creatureMode == MODE_NORMAL) {
        !          1515:             if (teammate->creatureState == MONSTER_SLEEPING
        !          1516:                 || teammate->creatureState == MONSTER_WANDERING) {
        !          1517:                 teammate->ticksUntilTurn = max(100, teammate->ticksUntilTurn);
        !          1518:             }
        !          1519:             if (monst->creatureState != MONSTER_ALLY) {
        !          1520:                 teammate->creatureState =
        !          1521:                 (teammate->creatureMode == MODE_PERM_FLEEING ? MONSTER_FLEEING : MONSTER_TRACKING_SCENT);
        !          1522:                 updateMonsterState(teammate);
        !          1523:             }
        !          1524:         }
        !          1525:     }
        !          1526: }
        !          1527:
        !          1528: boolean monsterCanShootWebs(creature *monst) {
        !          1529:     short i;
        !          1530:     for (i=0; monst->info.bolts[i] != 0; i++) {
        !          1531:         const bolt *theBolt = &boltCatalog[monst->info.bolts[i]];
        !          1532:         if (theBolt->pathDF && (tileCatalog[dungeonFeatureCatalog[theBolt->pathDF].tile].flags & T_ENTANGLES)) {
        !          1533:             return true;
        !          1534:         }
        !          1535:     }
        !          1536:     return false;
        !          1537: }
        !          1538:
        !          1539: // Assumes that observer is not the player.
        !          1540: // Returns approximately double the actual (quasi-euclidian) distance.
        !          1541: short awarenessDistance(creature *observer, creature *target) {
        !          1542:     long perceivedDistance;
        !          1543:
        !          1544:     // start with base distance
        !          1545:     if ((observer->status[STATUS_LEVITATING]
        !          1546:          || (observer->info.flags & MONST_RESTRICTED_TO_LIQUID)
        !          1547:          || (observer->info.flags & MONST_IMMOBILE)
        !          1548:          || (observer->bookkeepingFlags & MB_SUBMERGED)
        !          1549:          || ((observer->info.flags & MONST_IMMUNE_TO_WEBS) && monsterCanShootWebs(observer)))
        !          1550:         && ((target == &player && (pmap[observer->xLoc][observer->yLoc].flags & IN_FIELD_OF_VIEW))
        !          1551:             || (target != &player && openPathBetween(observer->xLoc, observer->yLoc, target->xLoc, target->yLoc)))) {
        !          1552:             // if monster flies or is immobile or waterbound or underwater or can cross pits with webs,
        !          1553:             // use absolute distance.
        !          1554:             perceivedDistance = scentDistance(observer->xLoc, observer->yLoc, target->xLoc, target->yLoc);
        !          1555:         } else {
        !          1556:             perceivedDistance = (rogue.scentTurnNumber - scentMap[observer->xLoc][observer->yLoc]); // this value is double the apparent distance
        !          1557:         }
        !          1558:
        !          1559:     perceivedDistance = min(perceivedDistance, 1000);
        !          1560:
        !          1561:     if (perceivedDistance < 0) {
        !          1562:         perceivedDistance = 1000;
        !          1563:     }
        !          1564:     return ((short) perceivedDistance);
        !          1565: }
        !          1566:
        !          1567: // yes or no -- observer is aware of the target as of this new turn.
        !          1568: // takes into account whether it is ALREADY aware of the target.
        !          1569: boolean awareOfTarget(creature *observer, creature *target) {
        !          1570:     short perceivedDistance = awarenessDistance(observer, target);
        !          1571:     short awareness = rogue.aggroRange * 2;
        !          1572:     boolean retval;
        !          1573:
        !          1574:     brogueAssert(perceivedDistance >= 0 && awareness >= 0);
        !          1575:
        !          1576:     if (observer->info.flags & MONST_ALWAYS_HUNTING) {
        !          1577:         retval = true;
        !          1578:     } else if (observer->info.flags & MONST_IMMOBILE) {
        !          1579:         // Turrets and totems are aware of you iff they are within stealth range.
        !          1580:         // The only exception is mirror totems; they're always ready to shoot because they have "always hunting" set.
        !          1581:         retval = perceivedDistance <= awareness;
        !          1582:     } else if (perceivedDistance > awareness * 3) {
        !          1583:         // out of awareness range, even if hunting
        !          1584:         retval = false;
        !          1585:     } else if (observer->creatureState == MONSTER_TRACKING_SCENT) {
        !          1586:         // already aware of the target, lose track 3% of the time if outside of stealth range.
        !          1587:          if (perceivedDistance > awareness) {
        !          1588:              retval = rand_percent(97);
        !          1589:          } else {
        !          1590:             retval = true;
        !          1591:          }
        !          1592:     } else if (target == &player
        !          1593:         && !(pmap[observer->xLoc][observer->yLoc].flags & IN_FIELD_OF_VIEW)) {
        !          1594:         // observer not hunting and player-target not in field of view
        !          1595:         retval = false;
        !          1596:     } else if (perceivedDistance <= awareness) {
        !          1597:         // within range but currently unaware
        !          1598:         retval = rand_percent(25);
        !          1599:     } else {
        !          1600:         retval = false;
        !          1601:     }
        !          1602:     return retval;
        !          1603: }
        !          1604:
        !          1605: short closestWaypointIndexTo(const short x, const short y) {
        !          1606:     short i, closestDistance, closestIndex;
        !          1607:
        !          1608:     closestDistance = 1000;
        !          1609:     closestIndex = -1;
        !          1610:     for (i=0; i < rogue.wpCount; i++) {
        !          1611:         if (rogue.wpDistance[i][x][y] < closestDistance) {
        !          1612:             closestDistance = rogue.wpDistance[i][x][y];
        !          1613:             closestIndex = i;
        !          1614:         }
        !          1615:     }
        !          1616:     return closestIndex;
        !          1617: }
        !          1618:
        !          1619: void wanderToward(creature *monst, const short x, const short y) {
        !          1620:     if (coordinatesAreInMap(x, y)) {
        !          1621:         const short theWaypointIndex = closestWaypointIndexTo(x, y);
        !          1622:         if (theWaypointIndex != -1) {
        !          1623:             monst->waypointAlreadyVisited[theWaypointIndex] = false;
        !          1624:             monst->targetWaypointIndex = theWaypointIndex;
        !          1625:         }
        !          1626:     }
        !          1627: }
        !          1628:
        !          1629: void updateMonsterState(creature *monst) {
        !          1630:     short x, y, closestFearedEnemy;
        !          1631:     boolean awareOfPlayer;
        !          1632:     creature *monst2;
        !          1633:
        !          1634:     x = monst->xLoc;
        !          1635:     y = monst->yLoc;
        !          1636:
        !          1637:     if ((monst->info.flags & MONST_ALWAYS_HUNTING)
        !          1638:         && monst->creatureState != MONSTER_ALLY) {
        !          1639:
        !          1640:         monst->creatureState = MONSTER_TRACKING_SCENT;
        !          1641:         return;
        !          1642:     }
        !          1643:
        !          1644:     awareOfPlayer = awareOfTarget(monst, &player);
        !          1645:
        !          1646:     if ((monst->info.flags & MONST_IMMOBILE)
        !          1647:         && monst->creatureState != MONSTER_ALLY) {
        !          1648:
        !          1649:         if (awareOfPlayer) {
        !          1650:             monst->creatureState = MONSTER_TRACKING_SCENT;
        !          1651:         } else {
        !          1652:             monst->creatureState = MONSTER_SLEEPING;
        !          1653:         }
        !          1654:         return;
        !          1655:     }
        !          1656:
        !          1657:     if (monst->creatureMode == MODE_PERM_FLEEING
        !          1658:         && (monst->creatureState == MONSTER_WANDERING || monst->creatureState == MONSTER_TRACKING_SCENT)) {
        !          1659:
        !          1660:         monst->creatureState = MONSTER_FLEEING;
        !          1661:     }
        !          1662:
        !          1663:     closestFearedEnemy = DCOLS+DROWS;
        !          1664:     CYCLE_MONSTERS_AND_PLAYERS(monst2) {
        !          1665:         if (monsterFleesFrom(monst, monst2)
        !          1666:             && distanceBetween(x, y, monst2->xLoc, monst2->yLoc) < closestFearedEnemy
        !          1667:             && traversiblePathBetween(monst2, x, y)
        !          1668:             && openPathBetween(x, y, monst2->xLoc, monst2->yLoc)) {
        !          1669:
        !          1670:             closestFearedEnemy = distanceBetween(x, y, monst2->xLoc, monst2->yLoc);
        !          1671:         }
        !          1672:     }
        !          1673:
        !          1674:     if ((monst->creatureState == MONSTER_WANDERING)
        !          1675:         && awareOfPlayer
        !          1676:         && (pmap[player.xLoc][player.yLoc].flags & IN_FIELD_OF_VIEW)) {
        !          1677:         // If wandering and you notice the player, start tracking the scent.
        !          1678:         alertMonster(monst);
        !          1679:     } else if (monst->creatureState == MONSTER_SLEEPING) {
        !          1680:         // if sleeping, the monster has a chance to awaken
        !          1681:         if (awareOfPlayer) {
        !          1682:             wakeUp(monst); // wakes up the whole horde if necessary
        !          1683:         }
        !          1684:     } else if (monst->creatureState == MONSTER_TRACKING_SCENT && !awareOfPlayer) {
        !          1685:         // if tracking scent, but the scent is weaker than the scent detection threshold, begin wandering.
        !          1686:         monst->creatureState = MONSTER_WANDERING;
        !          1687:         wanderToward(monst, monst->lastSeenPlayerAt[0], monst->lastSeenPlayerAt[1]);
        !          1688:     } else if (monst->creatureState == MONSTER_TRACKING_SCENT
        !          1689:                && closestFearedEnemy < 3) {
        !          1690:         monst->creatureState = MONSTER_FLEEING;
        !          1691:     } else if (monst->creatureState != MONSTER_ALLY
        !          1692:                && (monst->info.flags & MONST_FLEES_NEAR_DEATH)
        !          1693:                && monst->currentHP <= 3 * monst->info.maxHP / 4) {
        !          1694:
        !          1695:         if (monst->creatureState == MONSTER_FLEEING
        !          1696:             || monst->currentHP <= monst->info.maxHP / 4) {
        !          1697:
        !          1698:             monst->creatureState = MONSTER_FLEEING;
        !          1699:         }
        !          1700:     } else if (monst->creatureMode == MODE_NORMAL
        !          1701:                && monst->creatureState == MONSTER_FLEEING
        !          1702:                && !(monst->status[STATUS_MAGICAL_FEAR])
        !          1703:                && closestFearedEnemy >= 3) {
        !          1704:
        !          1705:         monst->creatureState = MONSTER_TRACKING_SCENT;
        !          1706:     } else if (monst->creatureMode == MODE_PERM_FLEEING
        !          1707:                && monst->creatureState == MONSTER_FLEEING
        !          1708:                && (monst->info.abilityFlags & MA_HIT_STEAL_FLEE)
        !          1709:                && !(monst->status[STATUS_MAGICAL_FEAR])
        !          1710:                && !(monst->carriedItem)) {
        !          1711:
        !          1712:         monst->creatureMode = MODE_NORMAL;
        !          1713:         alertMonster(monst);
        !          1714:     } else if (monst->creatureMode == MODE_NORMAL
        !          1715:                && monst->creatureState == MONSTER_FLEEING
        !          1716:                && (monst->info.flags & MONST_FLEES_NEAR_DEATH)
        !          1717:                && !(monst->status[STATUS_MAGICAL_FEAR])
        !          1718:                && monst->currentHP >= monst->info.maxHP * 3 / 4) {
        !          1719:
        !          1720:         if ((monst->bookkeepingFlags & MB_FOLLOWER) && monst->leader == &player) {
        !          1721:             monst->creatureState = MONSTER_ALLY;
        !          1722:         } else {
        !          1723:             alertMonster(monst);
        !          1724:         }
        !          1725:     }
        !          1726:
        !          1727:     if (awareOfPlayer) {
        !          1728:         if (monst->creatureState == MONSTER_FLEEING
        !          1729:             || monst->creatureState == MONSTER_TRACKING_SCENT) {
        !          1730:
        !          1731:             monst->lastSeenPlayerAt[0] = player.xLoc;
        !          1732:             monst->lastSeenPlayerAt[1] = player.yLoc;
        !          1733:         }
        !          1734:     }
        !          1735: }
        !          1736:
        !          1737: void decrementMonsterStatus(creature *monst) {
        !          1738:     short i, damage;
        !          1739:     char buf[COLS], buf2[COLS];
        !          1740:
        !          1741:     monst->bookkeepingFlags &= ~MB_JUST_SUMMONED;
        !          1742:
        !          1743:     if (monst->currentHP < monst->info.maxHP
        !          1744:         && monst->info.turnsBetweenRegen > 0
        !          1745:         && !monst->status[STATUS_POISONED]) {
        !          1746:
        !          1747:         if ((monst->turnsUntilRegen -= 1000) <= 0) {
        !          1748:             monst->currentHP++;
        !          1749:             monst->previousHealthPoints++;
        !          1750:             monst->turnsUntilRegen += monst->info.turnsBetweenRegen;
        !          1751:         }
        !          1752:     }
        !          1753:
        !          1754:     for (i=0; i<NUMBER_OF_STATUS_EFFECTS; i++) {
        !          1755:         switch (i) {
        !          1756:             case STATUS_LEVITATING:
        !          1757:                 if (monst->status[i] && !(monst->info.flags & MONST_FLIES)) {
        !          1758:                     monst->status[i]--;
        !          1759:                 }
        !          1760:                 break;
        !          1761:             case STATUS_SLOWED:
        !          1762:                 if (monst->status[i] && !--monst->status[i]) {
        !          1763:                     monst->movementSpeed = monst->info.movementSpeed;
        !          1764:                     monst->attackSpeed = monst->info.attackSpeed;
        !          1765:                 }
        !          1766:                 break;
        !          1767:             case STATUS_WEAKENED:
        !          1768:                 if (monst->status[i] && !--monst->status[i]) {
        !          1769:                     monst->weaknessAmount = 0;
        !          1770:                 }
        !          1771:                 break;
        !          1772:             case STATUS_HASTED:
        !          1773:                 if (monst->status[i]) {
        !          1774:                     if (!--monst->status[i]) {
        !          1775:                         monst->movementSpeed = monst->info.movementSpeed;
        !          1776:                         monst->attackSpeed = monst->info.attackSpeed;
        !          1777:                     }
        !          1778:                 }
        !          1779:                 break;
        !          1780:             case STATUS_BURNING:
        !          1781:                 if (monst->status[i]) {
        !          1782:                     if (!(monst->info.flags & MONST_FIERY)) {
        !          1783:                         monst->status[i]--;
        !          1784:                     }
        !          1785:                     damage = rand_range(1, 3);
        !          1786:                     if (!(monst->status[STATUS_IMMUNE_TO_FIRE])
        !          1787:                         && !(monst->info.flags & MONST_INVULNERABLE)
        !          1788:                         && inflictDamage(NULL, monst, damage, &orange, true)) {
        !          1789:
        !          1790:                         if (canSeeMonster(monst)) {
        !          1791:                             monsterName(buf, monst, true);
        !          1792:                             sprintf(buf2, "%s burns %s.",
        !          1793:                                     buf,
        !          1794:                                     (monst->info.flags & MONST_INANIMATE) ? "up" : "to death");
        !          1795:                             messageWithColor(buf2, messageColorFromVictim(monst), false);
        !          1796:                         }
        !          1797:                         return;
        !          1798:                     }
        !          1799:                     if (monst->status[i] <= 0) {
        !          1800:                         extinguishFireOnCreature(monst);
        !          1801:                     }
        !          1802:                 }
        !          1803:                 break;
        !          1804:             case STATUS_LIFESPAN_REMAINING:
        !          1805:                 if (monst->status[i]) {
        !          1806:                     monst->status[i]--;
        !          1807:                     if (monst->status[i] <= 0) {
        !          1808:                         killCreature(monst, false);
        !          1809:                         if (canSeeMonster(monst)) {
        !          1810:                             monsterName(buf, monst, true);
        !          1811:                             sprintf(buf2, "%s dissipates into thin air.", buf);
        !          1812:                             messageWithColor(buf2, &white, false);
        !          1813:                         }
        !          1814:                         return;
        !          1815:                     }
        !          1816:                 }
        !          1817:                 break;
        !          1818:             case STATUS_POISONED:
        !          1819:                 if (monst->status[i]) {
        !          1820:                     monst->status[i]--;
        !          1821:                     if (inflictDamage(NULL, monst, monst->poisonAmount, &green, true)) {
        !          1822:                         if (canSeeMonster(monst)) {
        !          1823:                             monsterName(buf, monst, true);
        !          1824:                             sprintf(buf2, "%s dies of poison.", buf);
        !          1825:                             messageWithColor(buf2, messageColorFromVictim(monst), false);
        !          1826:                         }
        !          1827:                         return;
        !          1828:                     }
        !          1829:                     if (!monst->status[i]) {
        !          1830:                         monst->poisonAmount = 0;
        !          1831:                     }
        !          1832:                 }
        !          1833:                 break;
        !          1834:             case STATUS_STUCK:
        !          1835:                 if (monst->status[i] && !cellHasTerrainFlag(monst->xLoc, monst->yLoc, T_ENTANGLES)) {
        !          1836:                     monst->status[i] = 0;
        !          1837:                 }
        !          1838:                 break;
        !          1839:             case STATUS_DISCORDANT:
        !          1840:                 if (monst->status[i] && !--monst->status[i]) {
        !          1841:                     if (monst->creatureState == MONSTER_FLEEING
        !          1842:                         && !monst->status[STATUS_MAGICAL_FEAR]
        !          1843:                         && monst->leader == &player) {
        !          1844:
        !          1845:                         monst->creatureState = MONSTER_ALLY;
        !          1846:                         if (monst->carriedItem) {
        !          1847:                             makeMonsterDropItem(monst);
        !          1848:                         }
        !          1849:                     }
        !          1850:                 }
        !          1851:                 break;
        !          1852:             case STATUS_MAGICAL_FEAR:
        !          1853:                 if (monst->status[i]) {
        !          1854:                     if (!--monst->status[i]) {
        !          1855:                         monst->creatureState = (monst->leader == &player ? MONSTER_ALLY : MONSTER_TRACKING_SCENT);
        !          1856:                     }
        !          1857:                 }
        !          1858:                 break;
        !          1859:             case STATUS_SHIELDED:
        !          1860:                 monst->status[i] -= monst->maxStatus[i] / 20;
        !          1861:                 if (monst->status[i] <= 0) {
        !          1862:                     monst->status[i] = monst->maxStatus[i] = 0;
        !          1863:                 }
        !          1864:                 break;
        !          1865:             case STATUS_IMMUNE_TO_FIRE:
        !          1866:                 if (monst->status[i] && !(monst->info.flags & MONST_IMMUNE_TO_FIRE)) {
        !          1867:                     monst->status[i]--;
        !          1868:                 }
        !          1869:                 break;
        !          1870:             case STATUS_INVISIBLE:
        !          1871:                 if (monst->status[i]
        !          1872:                     && !(monst->info.flags & MONST_INVISIBLE)
        !          1873:                     && !--monst->status[i]
        !          1874:                     && playerCanSee(monst->xLoc, monst->yLoc)) {
        !          1875:
        !          1876:                     refreshDungeonCell(monst->xLoc, monst->yLoc);
        !          1877:                 }
        !          1878:                 break;
        !          1879:             default:
        !          1880:                 if (monst->status[i]) {
        !          1881:                     monst->status[i]--;
        !          1882:                 }
        !          1883:                 break;
        !          1884:         }
        !          1885:     }
        !          1886:
        !          1887:     if (monsterCanSubmergeNow(monst) && !(monst->bookkeepingFlags & MB_SUBMERGED)) {
        !          1888:         if (rand_percent(20)) {
        !          1889:             monst->bookkeepingFlags |= MB_SUBMERGED;
        !          1890:             if (!monst->status[STATUS_MAGICAL_FEAR]
        !          1891:                 && monst->creatureState == MONSTER_FLEEING
        !          1892:                 && (!(monst->info.flags & MONST_FLEES_NEAR_DEATH) || monst->currentHP >= monst->info.maxHP * 3 / 4)) {
        !          1893:
        !          1894:                 monst->creatureState = MONSTER_TRACKING_SCENT;
        !          1895:             }
        !          1896:             refreshDungeonCell(monst->xLoc, monst->yLoc);
        !          1897:         } else if (monst->info.flags & (MONST_RESTRICTED_TO_LIQUID)
        !          1898:                    && monst->creatureState != MONSTER_ALLY) {
        !          1899:             monst->creatureState = MONSTER_FLEEING;
        !          1900:         }
        !          1901:     }
        !          1902: }
        !          1903:
        !          1904: boolean traversiblePathBetween(creature *monst, short x2, short y2) {
        !          1905:     short coords[DCOLS][2], i, x, y, n;
        !          1906:     short originLoc[2] = {monst->xLoc, monst->yLoc};
        !          1907:     short targetLoc[2] = {x2, y2};
        !          1908:
        !          1909:     n = getLineCoordinates(coords, originLoc, targetLoc);
        !          1910:
        !          1911:     for (i=0; i<n; i++) {
        !          1912:         x = coords[i][0];
        !          1913:         y = coords[i][1];
        !          1914:         if (x == x2 && y == y2) {
        !          1915:             return true;
        !          1916:         }
        !          1917:         if (monsterAvoids(monst, x, y)) {
        !          1918:             return false;
        !          1919:         }
        !          1920:     }
        !          1921:     brogueAssert(false);
        !          1922:     return true; // should never get here
        !          1923: }
        !          1924:
        !          1925: boolean specifiedPathBetween(short x1, short y1, short x2, short y2,
        !          1926:                              unsigned long blockingTerrain, unsigned long blockingFlags) {
        !          1927:     short coords[DCOLS][2], i, x, y, n;
        !          1928:     short originLoc[2] = {x1, y1};
        !          1929:     short targetLoc[2] = {x2, y2};
        !          1930:     n = getLineCoordinates(coords, originLoc, targetLoc);
        !          1931:
        !          1932:     for (i=0; i<n; i++) {
        !          1933:         x = coords[i][0];
        !          1934:         y = coords[i][1];
        !          1935:         if (cellHasTerrainFlag(x, y, blockingTerrain) || (pmap[x][y].flags & blockingFlags)) {
        !          1936:             return false;
        !          1937:         }
        !          1938:         if (x == x2 && y == y2) {
        !          1939:             return true;
        !          1940:         }
        !          1941:     }
        !          1942:     brogueAssert(false);
        !          1943:     return true; // should never get here
        !          1944: }
        !          1945:
        !          1946: boolean openPathBetween(short x1, short y1, short x2, short y2) {
        !          1947:     short returnLoc[2], startLoc[2] = {x1, y1}, targetLoc[2] = {x2, y2};
        !          1948:
        !          1949:     getImpactLoc(returnLoc, startLoc, targetLoc, DCOLS, false);
        !          1950:     if (returnLoc[0] == targetLoc[0] && returnLoc[1] == targetLoc[1]) {
        !          1951:         return true;
        !          1952:     }
        !          1953:     return false;
        !          1954: }
        !          1955:
        !          1956: // will return the player if the player is at (x, y).
        !          1957: creature *monsterAtLoc(short x, short y) {
        !          1958:     creature *monst;
        !          1959:     if (!(pmap[x][y].flags & (HAS_MONSTER | HAS_PLAYER))) {
        !          1960:         return NULL;
        !          1961:     }
        !          1962:     if (player.xLoc == x && player.yLoc == y) {
        !          1963:         return &player;
        !          1964:     }
        !          1965:     for (monst = monsters->nextCreature; monst != NULL && (monst->xLoc != x || monst->yLoc != y); monst = monst->nextCreature);
        !          1966:     return monst;
        !          1967: }
        !          1968:
        !          1969: creature *dormantMonsterAtLoc(short x, short y) {
        !          1970:     creature *monst;
        !          1971:     if (!(pmap[x][y].flags & HAS_DORMANT_MONSTER)) {
        !          1972:         return NULL;
        !          1973:     }
        !          1974:     for (monst = dormantMonsters->nextCreature; monst != NULL && (monst->xLoc != x || monst->yLoc != y); monst = monst->nextCreature);
        !          1975:     return monst;
        !          1976: }
        !          1977:
        !          1978: enum boltType monsterHasBoltEffect(creature *monst, enum boltEffects boltEffectIndex) {
        !          1979:     short i;
        !          1980:     for (i=0; monst->info.bolts[i] != 0; i++) {
        !          1981:         if (boltCatalog[monst->info.bolts[i]].boltEffect == boltEffectIndex) {
        !          1982:             return monst->info.bolts[i];
        !          1983:         }
        !          1984:     }
        !          1985:     return BOLT_NONE;
        !          1986: }
        !          1987:
        !          1988: void pathTowardCreature(creature *monst, creature *target) {
        !          1989:     short targetLoc[2], dir;
        !          1990:
        !          1991:     if (traversiblePathBetween(monst, target->xLoc, target->yLoc)) {
        !          1992:         if (distanceBetween(monst->xLoc, monst->yLoc, target->xLoc, target->yLoc) <= 2) {
        !          1993:             monst->bookkeepingFlags &= ~MB_GIVEN_UP_ON_SCENT;
        !          1994:         }
        !          1995:         targetLoc[0] = target->xLoc;
        !          1996:         targetLoc[1] = target->yLoc;
        !          1997:         moveMonsterPassivelyTowards(monst, targetLoc, (monst->creatureState != MONSTER_ALLY));
        !          1998:         return;
        !          1999:     }
        !          2000:
        !          2001:     // is the target missing his map altogether?
        !          2002:     if (!target->mapToMe) {
        !          2003:         target->mapToMe = allocGrid();
        !          2004:         fillGrid(target->mapToMe, 0);
        !          2005:         calculateDistances(target->mapToMe, target->xLoc, target->yLoc, 0, monst, true, false);
        !          2006:     }
        !          2007:
        !          2008:     // is the target map out of date?
        !          2009:     if (target->mapToMe[target->xLoc][target->yLoc] > 3) {
        !          2010:         // it is. recalculate the map.
        !          2011:         calculateDistances(target->mapToMe, target->xLoc, target->yLoc, 0, monst, true, false);
        !          2012:     }
        !          2013:
        !          2014:     // blink to the target?
        !          2015:     if (distanceBetween(monst->xLoc, monst->yLoc, target->xLoc, target->yLoc) > 10
        !          2016:         || monstersAreEnemies(monst, target)) {
        !          2017:
        !          2018:         if (monsterBlinkToPreferenceMap(monst, target->mapToMe, false)) { // if it blinked
        !          2019:             monst->ticksUntilTurn = monst->attackSpeed * (monst->info.flags & MONST_CAST_SPELLS_SLOWLY ? 2 : 1);
        !          2020:             return;
        !          2021:         }
        !          2022:     }
        !          2023:
        !          2024:     // follow the map.
        !          2025:     dir = nextStep(target->mapToMe, monst->xLoc, monst->yLoc, monst, true);
        !          2026:     if (dir == NO_DIRECTION) {
        !          2027:         dir = randValidDirectionFrom(monst, monst->xLoc, monst->yLoc, true);
        !          2028:     }
        !          2029:     if (dir == NO_DIRECTION) {
        !          2030:         return; // monster is blocked
        !          2031:     }
        !          2032:     targetLoc[0] = monst->xLoc + nbDirs[dir][0];
        !          2033:     targetLoc[1] = monst->yLoc + nbDirs[dir][1];
        !          2034:
        !          2035:     moveMonsterPassivelyTowards(monst, targetLoc, (monst->creatureState != MONSTER_ALLY));
        !          2036: }
        !          2037:
        !          2038: boolean creatureEligibleForSwarming(creature *monst) {
        !          2039:     if ((monst->info.flags & (MONST_IMMOBILE | MONST_GETS_TURN_ON_ACTIVATION | MONST_MAINTAINS_DISTANCE))
        !          2040:         || monst->status[STATUS_ENTRANCED]
        !          2041:         || monst->status[STATUS_CONFUSED]
        !          2042:         || monst->status[STATUS_STUCK]
        !          2043:         || monst->status[STATUS_PARALYZED]
        !          2044:         || monst->status[STATUS_MAGICAL_FEAR]
        !          2045:         || monst->status[STATUS_LIFESPAN_REMAINING] == 1
        !          2046:         || (monst->bookkeepingFlags & (MB_SEIZED | MB_SEIZING))) {
        !          2047:
        !          2048:         return false;
        !          2049:     }
        !          2050:     if (monst != &player
        !          2051:         && monst->creatureState != MONSTER_ALLY
        !          2052:         && monst->creatureState != MONSTER_TRACKING_SCENT) {
        !          2053:
        !          2054:         return false;
        !          2055:     }
        !          2056:     return true;
        !          2057: }
        !          2058:
        !          2059: // Swarming behavior.
        !          2060: // If you’re adjacent to an enemy and about to strike it, and you’re adjacent to a hunting-mode tribemate
        !          2061: // who is not adjacent to another enemy, and there is no empty space adjacent to the tribemate AND the enemy,
        !          2062: // and there is an empty space adjacent to you AND the enemy, then move into that last space.
        !          2063: // (In each case, "adjacent" excludes diagonal tiles obstructed by corner walls.)
        !          2064: enum directions monsterSwarmDirection(creature *monst, creature *enemy) {
        !          2065:     short newX, newY, i;
        !          2066:     enum directions dir, targetDir;
        !          2067:     short dirList[8] = {0, 1, 2, 3, 4, 5, 6, 7};
        !          2068:     boolean alternateDirectionExists;
        !          2069:     creature *ally, *otherEnemy;
        !          2070:
        !          2071:     if (monst == &player || !creatureEligibleForSwarming(monst)) {
        !          2072:         return NO_DIRECTION;
        !          2073:     }
        !          2074:
        !          2075:     if (distanceBetween(monst->xLoc, monst->yLoc, enemy->xLoc, enemy->yLoc) != 1
        !          2076:         || (diagonalBlocked(monst->xLoc, monst->yLoc, enemy->xLoc, enemy->yLoc, false) || (enemy->info.flags & MONST_ATTACKABLE_THRU_WALLS))
        !          2077:         || !monstersAreEnemies(monst, enemy)) {
        !          2078:
        !          2079:         return NO_DIRECTION; // Too far from the enemy, diagonally blocked, or not enemies with it.
        !          2080:     }
        !          2081:
        !          2082:     // Find a location that is adjacent to you and to the enemy.
        !          2083:     targetDir = NO_DIRECTION;
        !          2084:     shuffleList(dirList, 4);
        !          2085:     shuffleList(&(dirList[4]), 4);
        !          2086:     for (i=0; i<8 && targetDir == NO_DIRECTION; i++) {
        !          2087:         dir = dirList[i];
        !          2088:         newX = monst->xLoc + nbDirs[dir][0];
        !          2089:         newY = monst->yLoc + nbDirs[dir][1];
        !          2090:         if (coordinatesAreInMap(newX, newY)
        !          2091:             && distanceBetween(enemy->xLoc, enemy->yLoc, newX, newY) == 1
        !          2092:             && !(pmap[newX][newY].flags & (HAS_PLAYER | HAS_MONSTER))
        !          2093:             && !diagonalBlocked(monst->xLoc, monst->yLoc, newX, newY, false)
        !          2094:             && (!diagonalBlocked(enemy->xLoc, enemy->yLoc, newX, newY, false) || (enemy->info.flags & MONST_ATTACKABLE_THRU_WALLS))
        !          2095:             && !monsterAvoids(monst, newX, newY)) {
        !          2096:
        !          2097:             targetDir = dir;
        !          2098:         }
        !          2099:     }
        !          2100:     if (targetDir == NO_DIRECTION) {
        !          2101:         return NO_DIRECTION; // No open location next to both you and the enemy.
        !          2102:     }
        !          2103:
        !          2104:     // OK, now we have a place to move toward. Let's analyze the teammates around us to make sure that
        !          2105:     // one of them could take advantage of the space we open.
        !          2106:     CYCLE_MONSTERS_AND_PLAYERS(ally) {
        !          2107:         if (ally != monst
        !          2108:             && ally != enemy
        !          2109:             && monstersAreTeammates(monst, ally)
        !          2110:             && monstersAreEnemies(ally, enemy)
        !          2111:             && creatureEligibleForSwarming(ally)
        !          2112:             && distanceBetween(monst->xLoc, monst->yLoc, ally->xLoc, ally->yLoc) == 1
        !          2113:             && !diagonalBlocked(monst->xLoc, monst->yLoc, ally->xLoc, ally->yLoc, false)
        !          2114:             && !monsterAvoids(ally, monst->xLoc, monst->yLoc)
        !          2115:             && (distanceBetween(enemy->xLoc, enemy->yLoc, ally->xLoc, ally->yLoc) > 1 || diagonalBlocked(enemy->xLoc, enemy->yLoc, ally->xLoc, ally->yLoc, false))) {
        !          2116:
        !          2117:             // Found a prospective ally.
        !          2118:             // Check that there isn't already an open space from which to attack the enemy that is accessible to the ally.
        !          2119:             alternateDirectionExists = false;
        !          2120:             for (dir=0; dir< DIRECTION_COUNT && !alternateDirectionExists; dir++) {
        !          2121:                 newX = ally->xLoc + nbDirs[dir][0];
        !          2122:                 newY = ally->yLoc + nbDirs[dir][1];
        !          2123:                 if (coordinatesAreInMap(newX, newY)
        !          2124:                     && !(pmap[newX][newY].flags & (HAS_PLAYER | HAS_MONSTER))
        !          2125:                     && distanceBetween(enemy->xLoc, enemy->yLoc, newX, newY) == 1
        !          2126:                     && !diagonalBlocked(enemy->xLoc, enemy->yLoc, newX, newY, false)
        !          2127:                     && !diagonalBlocked(ally->xLoc, ally->yLoc, newX, newY, false)
        !          2128:                     && !monsterAvoids(ally, newX, newY)) {
        !          2129:
        !          2130:                     alternateDirectionExists = true;
        !          2131:                 }
        !          2132:             }
        !          2133:             if (!alternateDirectionExists) {
        !          2134:                 // OK, no alternative open spaces exist.
        !          2135:                 // Check that the ally isn't already occupied with an enemy of its own.
        !          2136:                 CYCLE_MONSTERS_AND_PLAYERS(otherEnemy) {
        !          2137:                     if (ally != otherEnemy
        !          2138:                         && monst != otherEnemy
        !          2139:                         && enemy != otherEnemy
        !          2140:                         && monstersAreEnemies(ally, otherEnemy)
        !          2141:                         && distanceBetween(ally->xLoc, ally->yLoc, otherEnemy->xLoc, otherEnemy->yLoc) == 1
        !          2142:                         && (!diagonalBlocked(ally->xLoc, ally->yLoc, otherEnemy->xLoc, otherEnemy->yLoc, false) || (otherEnemy->info.flags & MONST_ATTACKABLE_THRU_WALLS))) {
        !          2143:
        !          2144:                         break; // Ally is already occupied.
        !          2145:                     }
        !          2146:                 }
        !          2147:                 if (otherEnemy == NULL) {
        !          2148:                     // Success!
        !          2149:                     return targetDir;
        !          2150:                 }
        !          2151:             }
        !          2152:         }
        !          2153:     }
        !          2154:     return NO_DIRECTION; // Failure!
        !          2155: }
        !          2156:
        !          2157: // Isomorphs a number in [0, 39] to coordinates along the square of radius 5 surrounding (0,0).
        !          2158: // This is used as the sample space for bolt target coordinates, e.g. when reflecting or when
        !          2159: // monsters are deciding where to blink.
        !          2160: void perimeterCoords(short returnCoords[2], short n) {
        !          2161:     if (n <= 10) {          // top edge, left to right
        !          2162:         returnCoords[0] = n - 5;
        !          2163:         returnCoords[1] = -5;
        !          2164:     } else if (n <= 21) {   // bottom edge, left to right
        !          2165:         returnCoords[0] = (n - 11) - 5;
        !          2166:         returnCoords[1] = 5;
        !          2167:     } else if (n <= 30) {   // left edge, top to bottom
        !          2168:         returnCoords[0] = -5;
        !          2169:         returnCoords[1] = (n - 22) - 4;
        !          2170:     } else if (n <= 39) {   // right edge, top to bottom
        !          2171:         returnCoords[0] = 5;
        !          2172:         returnCoords[1] = (n - 31) - 4;
        !          2173:     } else {
        !          2174:         message("ERROR! Bad perimeter coordinate request!", true);
        !          2175:         returnCoords[0] = returnCoords[1] = 0; // garbage in, garbage out
        !          2176:     }
        !          2177: }
        !          2178:
        !          2179: // Tries to make the monster blink to the most desirable square it can aim at, according to the
        !          2180: // preferenceMap argument. "blinkUphill" determines whether it's aiming for higher or lower numbers on
        !          2181: // the preference map -- true means higher. Returns true if the monster blinked; false if it didn't.
        !          2182: boolean monsterBlinkToPreferenceMap(creature *monst, short **preferenceMap, boolean blinkUphill) {
        !          2183:     short i, bestTarget[2], bestPreference, nowPreference, maxDistance, target[2], impact[2], origin[2];
        !          2184:     boolean gotOne;
        !          2185:     char monstName[DCOLS];
        !          2186:     char buf[DCOLS];
        !          2187:     enum boltType theBoltType;
        !          2188:     bolt theBolt;
        !          2189:
        !          2190:     theBoltType = monsterHasBoltEffect(monst, BE_BLINKING);
        !          2191:     if (!theBoltType) {
        !          2192:         return false;
        !          2193:     }
        !          2194:
        !          2195:     maxDistance = staffBlinkDistance(5 * FP_FACTOR);
        !          2196:     gotOne = false;
        !          2197:
        !          2198:     origin[0] = monst->xLoc;
        !          2199:     origin[1] = monst->yLoc;
        !          2200:
        !          2201:     bestTarget[0]   = 0;
        !          2202:     bestTarget[1]   = 0;
        !          2203:     bestPreference  = preferenceMap[monst->xLoc][monst->yLoc];
        !          2204:
        !          2205:     // make sure that we beat the four cardinal neighbors
        !          2206:     for (i = 0; i < 4; i++) {
        !          2207:         nowPreference = preferenceMap[monst->xLoc + nbDirs[i][0]][monst->yLoc + nbDirs[i][1]];
        !          2208:
        !          2209:         if (((blinkUphill && nowPreference > bestPreference) || (!blinkUphill && nowPreference < bestPreference))
        !          2210:             && !monsterAvoids(monst, monst->xLoc + nbDirs[i][0], monst->yLoc + nbDirs[i][1])) {
        !          2211:
        !          2212:             bestPreference = nowPreference;
        !          2213:         }
        !          2214:     }
        !          2215:
        !          2216:     for (i=0; i<40; i++) {
        !          2217:         perimeterCoords(target, i);
        !          2218:         target[0] += monst->xLoc;
        !          2219:         target[1] += monst->yLoc;
        !          2220:
        !          2221:         getImpactLoc(impact, origin, target, maxDistance, true);
        !          2222:         nowPreference = preferenceMap[impact[0]][impact[1]];
        !          2223:
        !          2224:         if (((blinkUphill && (nowPreference > bestPreference))
        !          2225:              || (!blinkUphill && (nowPreference < bestPreference)))
        !          2226:             && !monsterAvoids(monst, impact[0], impact[1])) {
        !          2227:
        !          2228:             bestTarget[0]   = target[0];
        !          2229:             bestTarget[1]   = target[1];
        !          2230:             bestPreference  = nowPreference;
        !          2231:
        !          2232:             if ((abs(impact[0] - origin[0]) > 1 || abs(impact[1] - origin[1]) > 1)
        !          2233:                 || (cellHasTerrainFlag(impact[0], origin[1], T_OBSTRUCTS_PASSABILITY))
        !          2234:                 || (cellHasTerrainFlag(origin[0], impact[1], T_OBSTRUCTS_PASSABILITY))) {
        !          2235:                 gotOne = true;
        !          2236:             } else {
        !          2237:                 gotOne = false;
        !          2238:             }
        !          2239:         }
        !          2240:     }
        !          2241:
        !          2242:     if (gotOne) {
        !          2243:         if (canDirectlySeeMonster(monst)) {
        !          2244:             monsterName(monstName, monst, true);
        !          2245:             sprintf(buf, "%s blinks", monstName);
        !          2246:             combatMessage(buf, 0);
        !          2247:         }
        !          2248:         monst->ticksUntilTurn = monst->attackSpeed * (monst->info.flags & MONST_CAST_SPELLS_SLOWLY ? 2 : 1);
        !          2249:         theBolt = boltCatalog[theBoltType];
        !          2250:         zap(origin, bestTarget, &theBolt, false);
        !          2251:         return true;
        !          2252:     }
        !          2253:     return false;
        !          2254: }
        !          2255:
        !          2256: boolean fleeingMonsterAwareOfPlayer(creature *monst) {
        !          2257:     if (player.status[STATUS_INVISIBLE]) {
        !          2258:         return (distanceBetween(monst->xLoc, monst->yLoc, player.xLoc, player.yLoc) <= 1);
        !          2259:     } else {
        !          2260:         return (pmap[monst->xLoc][monst->yLoc].flags & IN_FIELD_OF_VIEW) ? true : false;
        !          2261:     }
        !          2262: }
        !          2263:
        !          2264: // returns whether the monster did something (and therefore ended its turn)
        !          2265: boolean monsterBlinkToSafety(creature *monst) {
        !          2266:     short **blinkSafetyMap;
        !          2267:
        !          2268:     if (monst->creatureState == MONSTER_ALLY) {
        !          2269:         if (!rogue.updatedAllySafetyMapThisTurn) {
        !          2270:             updateAllySafetyMap();
        !          2271:         }
        !          2272:         blinkSafetyMap = allySafetyMap;
        !          2273:     } else if (fleeingMonsterAwareOfPlayer(monst)) {
        !          2274:         if (monst->safetyMap) {
        !          2275:             freeGrid(monst->safetyMap);
        !          2276:             monst->safetyMap = NULL;
        !          2277:         }
        !          2278:         if (!rogue.updatedSafetyMapThisTurn) {
        !          2279:             updateSafetyMap();
        !          2280:         }
        !          2281:         blinkSafetyMap = safetyMap;
        !          2282:     } else {
        !          2283:         if (!monst->safetyMap) {
        !          2284:             if (!rogue.updatedSafetyMapThisTurn) {
        !          2285:                 updateSafetyMap();
        !          2286:             }
        !          2287:             monst->safetyMap = allocGrid();
        !          2288:             copyGrid(monst->safetyMap, safetyMap);
        !          2289:         }
        !          2290:         blinkSafetyMap = monst->safetyMap;
        !          2291:     }
        !          2292:
        !          2293:     return monsterBlinkToPreferenceMap(monst, blinkSafetyMap, false);
        !          2294: }
        !          2295:
        !          2296: boolean monsterSummons(creature *monst, boolean alwaysUse) {
        !          2297:     creature *target;
        !          2298:     short minionCount = 0;
        !          2299:
        !          2300:     if (monst->info.abilityFlags & (MA_CAST_SUMMON)) {
        !          2301:         // Count existing minions.
        !          2302:         for (target = monsters->nextCreature; target != NULL; target = target->nextCreature) {
        !          2303:             if (monst->creatureState == MONSTER_ALLY) {
        !          2304:                 if (target->creatureState == MONSTER_ALLY) {
        !          2305:                     minionCount++; // Allied summoners count all allies.
        !          2306:                 }
        !          2307:             } else if ((target->bookkeepingFlags & MB_FOLLOWER) && target->leader == monst) {
        !          2308:                 minionCount++; // Enemy summoners count only direct followers, not teammates.
        !          2309:             }
        !          2310:         }
        !          2311:         if (monst->creatureState == MONSTER_ALLY) { // Allied summoners also count monsters on the previous and next depths.
        !          2312:             if (rogue.depthLevel > 1) {
        !          2313:                 for (target = levels[rogue.depthLevel - 2].monsters; target != NULL; target = target->nextCreature) {
        !          2314:                     if (target->creatureState == MONSTER_ALLY && !(target->info.flags & MONST_WILL_NOT_USE_STAIRS)) {
        !          2315:                         minionCount++;
        !          2316:                     }
        !          2317:                 }
        !          2318:             }
        !          2319:             if (rogue.depthLevel < DEEPEST_LEVEL) {
        !          2320:                 for (target = levels[rogue.depthLevel].monsters; target != NULL; target = target->nextCreature) {
        !          2321:                     if (target->creatureState == MONSTER_ALLY && !(target->info.flags & MONST_WILL_NOT_USE_STAIRS)) {
        !          2322:                         minionCount++;
        !          2323:                     }
        !          2324:                 }
        !          2325:             }
        !          2326:         }
        !          2327:         if (alwaysUse && minionCount < 50) {
        !          2328:             summonMinions(monst);
        !          2329:             return true;
        !          2330:         } else if (monst->info.abilityFlags & MA_ENTER_SUMMONS) {
        !          2331:             if (!rand_range(0, 7)) {
        !          2332:                 summonMinions(monst);
        !          2333:                 return true;
        !          2334:             }
        !          2335:         } else if ((monst->creatureState != MONSTER_ALLY || minionCount < 5)
        !          2336:                    && !rand_range(0, minionCount * minionCount * 3 + 1)) {
        !          2337:
        !          2338:             summonMinions(monst);
        !          2339:             return true;
        !          2340:         }
        !          2341:     }
        !          2342:     return false;
        !          2343: }
        !          2344:
        !          2345: // Some monsters never make good targets irrespective of what bolt we're contemplating.
        !          2346: // Return false for those. Otherwise, return true.
        !          2347: boolean generallyValidBoltTarget(creature *caster, creature *target) {
        !          2348:     if (caster == target) {
        !          2349:         // Can't target yourself; that's the fundamental theorem of Brogue bolts.
        !          2350:         return false;
        !          2351:     }
        !          2352:     if (rogue.patchVersion >= 3
        !          2353:         && caster->status[STATUS_DISCORDANT]
        !          2354:         && caster->creatureState == MONSTER_WANDERING
        !          2355:         && target == &player) {
        !          2356:         // Discordant monsters always try to cast spells regardless of whether
        !          2357:         // they're hunting the player, so that they cast at other monsters. This
        !          2358:         // by bypasses the usual awareness checks, so the player and any allies
        !          2359:         // can be hit when far away. Hence, we don't target the player with
        !          2360:         // bolts if we're discordant and wandering.
        !          2361:         return false;
        !          2362:     }
        !          2363:     if (monsterIsHidden(target, caster)
        !          2364:         || (target->bookkeepingFlags & MB_SUBMERGED)) {
        !          2365:         // No bolt will affect a submerged creature. Can't shoot at invisible creatures unless it's in gas.
        !          2366:         return false;
        !          2367:     }
        !          2368:     return openPathBetween(caster->xLoc, caster->yLoc, target->xLoc, target->yLoc);
        !          2369: }
        !          2370:
        !          2371: boolean targetEligibleForCombatBuff(creature *caster, creature *target) {
        !          2372:     creature *enemy;
        !          2373:
        !          2374:     if (caster->creatureState == MONSTER_ALLY) {
        !          2375:         if (canDirectlySeeMonster(caster)) {
        !          2376:             CYCLE_MONSTERS_AND_PLAYERS(enemy) {
        !          2377:                 if (monstersAreEnemies(&player, enemy)
        !          2378:                     && canSeeMonster(enemy)
        !          2379:                     && (pmap[enemy->xLoc][enemy->yLoc].flags & IN_FIELD_OF_VIEW)) {
        !          2380:
        !          2381:                     return true;
        !          2382:                 }
        !          2383:             }
        !          2384:         }
        !          2385:         return false;
        !          2386:     } else {
        !          2387:         return (target->creatureState == MONSTER_TRACKING_SCENT);
        !          2388:     }
        !          2389: }
        !          2390:
        !          2391: // Make a decision as to whether the given caster should fire the given bolt at the given target.
        !          2392: // Assumes that the conditions in generallyValidBoltTarget have already been satisfied.
        !          2393: boolean specificallyValidBoltTarget(creature *caster, creature *target, enum boltType theBoltType) {
        !          2394:
        !          2395:     if ((boltCatalog[theBoltType].flags & BF_TARGET_ALLIES)
        !          2396:         && (!monstersAreTeammates(caster, target) || monstersAreEnemies(caster, target))) {
        !          2397:
        !          2398:         return false;
        !          2399:     }
        !          2400:     if ((boltCatalog[theBoltType].flags & BF_TARGET_ENEMIES)
        !          2401:         && (!monstersAreEnemies(caster, target))) {
        !          2402:
        !          2403:         return false;
        !          2404:     }
        !          2405:     if ((boltCatalog[theBoltType].flags & BF_TARGET_ENEMIES)
        !          2406:         && (target->info.flags & MONST_INVULNERABLE)) {
        !          2407:
        !          2408:         return false;
        !          2409:     }
        !          2410:     if ((target->info.flags & MONST_REFLECT_4)
        !          2411:         && target->creatureState != MONSTER_ALLY
        !          2412:         && !(boltCatalog[theBoltType].flags & (BF_NEVER_REFLECTS | BF_HALTS_BEFORE_OBSTRUCTION))) {
        !          2413:         // Don't fire a reflectable bolt at a reflective target unless it's your ally.
        !          2414:         return false;
        !          2415:     }
        !          2416:     if (boltCatalog[theBoltType].forbiddenMonsterFlags & target->info.flags) {
        !          2417:         // Don't fire a bolt at a creature type that it won't affect.
        !          2418:         return false;
        !          2419:     }
        !          2420:     if ((boltCatalog[theBoltType].flags & BF_FIERY)
        !          2421:         && target->status[STATUS_IMMUNE_TO_FIRE]) {
        !          2422:         // Don't shoot fireballs at fire-immune creatures.
        !          2423:         return false;
        !          2424:     }
        !          2425:     if ((boltCatalog[theBoltType].flags & BF_FIERY)
        !          2426:         && burnedTerrainFlagsAtLoc(caster->xLoc, caster->yLoc) & avoidedFlagsForMonster(&(caster->info))) {
        !          2427:         // Don't shoot fireballs if you're standing on a tile that could combust into something that harms you.
        !          2428:         return false;
        !          2429:     }
        !          2430:
        !          2431:     // Rules specific to bolt effects:
        !          2432:     switch (boltCatalog[theBoltType].boltEffect) {
        !          2433:         case BE_BECKONING:
        !          2434:             if (distanceBetween(caster->xLoc, caster->yLoc, target->xLoc, target->yLoc) <= 1) {
        !          2435:                 return false;
        !          2436:             }
        !          2437:             break;
        !          2438:         case BE_ATTACK:
        !          2439:             if (cellHasTerrainFlag(target->xLoc, target->yLoc, T_OBSTRUCTS_PASSABILITY)
        !          2440:                 && !(target->info.flags & MONST_ATTACKABLE_THRU_WALLS)) {
        !          2441:                 // Don't shoot an arrow at an embedded creature.
        !          2442:                 return false;
        !          2443:             }
        !          2444:             // continue to BE_DAMAGE below
        !          2445:         case BE_DAMAGE:
        !          2446:             if (target->status[STATUS_ENTRANCED]
        !          2447:                 && monstersAreEnemies(caster, target)) {
        !          2448:                 // Don't break your enemies' entrancement.
        !          2449:                 return false;
        !          2450:             }
        !          2451:             break;
        !          2452:         case BE_NONE:
        !          2453:             // BE_NONE bolts are always going to be all about the terrain effects,
        !          2454:             // so our logic has to follow from the terrain parameters of the bolt's target DF.
        !          2455:             if (boltCatalog[theBoltType].targetDF) {
        !          2456:                 const unsigned long terrainFlags = tileCatalog[dungeonFeatureCatalog[boltCatalog[theBoltType].targetDF].tile].flags;
        !          2457:                 if ((terrainFlags & T_ENTANGLES)
        !          2458:                     && target->status[STATUS_STUCK]) {
        !          2459:                     // Don't try to entangle a creature that is already entangled.
        !          2460:                     return false;
        !          2461:                 }
        !          2462:                 if ((boltCatalog[theBoltType].flags & BF_TARGET_ENEMIES)
        !          2463:                     && !(terrainFlags & avoidedFlagsForMonster(&(target->info)))
        !          2464:                     && (!(terrainFlags & T_ENTANGLES) || (target->info.flags & MONST_IMMUNE_TO_WEBS))) {
        !          2465:
        !          2466:                     return false;
        !          2467:                 }
        !          2468:             }
        !          2469:             break;
        !          2470:         case BE_DISCORD:
        !          2471:             if (target->status[STATUS_DISCORDANT]
        !          2472:                 || target == &player) {
        !          2473:                 // Don't cast discord if the target is already discordant, or if it is the player.
        !          2474:                 // (Players should never be intentionally targeted by discord. It's just a fact of monster psychology.)
        !          2475:                 return false;
        !          2476:             }
        !          2477:             break;
        !          2478:         case BE_NEGATION:
        !          2479:             if (monstersAreEnemies(caster, target)) {
        !          2480:                 if (target->status[STATUS_HASTED] || target->status[STATUS_TELEPATHIC] || target->status[STATUS_SHIELDED]) {
        !          2481:                     // Dispel haste, telepathy, protection.
        !          2482:                     return true;
        !          2483:                 }
        !          2484:                 if (target->info.flags & (MONST_DIES_IF_NEGATED | MONST_IMMUNE_TO_WEAPONS)) {
        !          2485:                     // Dispel magic creatures; strip weapon invulnerability from revenants.
        !          2486:                     return true;
        !          2487:                 }
        !          2488:                 if ((target->status[STATUS_IMMUNE_TO_FIRE] || target->status[STATUS_LEVITATING])
        !          2489:                     && cellHasTerrainFlag(target->xLoc, target->yLoc, (T_LAVA_INSTA_DEATH | T_IS_DEEP_WATER | T_AUTO_DESCENT))) {
        !          2490:                     // Drop the target into lava or a chasm if opportunity knocks.
        !          2491:                     return true;
        !          2492:                 }
        !          2493:                 if (monstersAreTeammates(caster, target)
        !          2494:                     && target->status[STATUS_DISCORDANT]
        !          2495:                     && !(target->info.flags & MONST_DIES_IF_NEGATED)) {
        !          2496:                     // Dispel discord from allies unless it would destroy them.
        !          2497:                     return true;
        !          2498:                 }
        !          2499:             } else if (monstersAreTeammates(caster, target)) {
        !          2500:                 if (target == &player && rogue.armor && (rogue.armor->flags & ITEM_RUNIC) && (rogue.armor->flags & ITEM_RUNIC_IDENTIFIED)
        !          2501:                     && rogue.armor->enchant2 == A_REFLECTION && netEnchant(rogue.armor) > 0) {
        !          2502:                     // Allies shouldn't cast negation on the player if she's knowingly wearing armor of reflection.
        !          2503:                     // Too much risk of negating themselves in the process.
        !          2504:                     return false;
        !          2505:                 }
        !          2506:                 if (target->info.flags & MONST_DIES_IF_NEGATED) {
        !          2507:                     // Never cast negation if it would destroy an allied creature.
        !          2508:                     return false;
        !          2509:                 }
        !          2510:                 if (target->status[STATUS_ENTRANCED]
        !          2511:                     && caster->creatureState != MONSTER_ALLY) {
        !          2512:                     // Non-allied monsters will dispel entrancement on their own kind.
        !          2513:                     return true;
        !          2514:                 }
        !          2515:                 if (target->status[STATUS_MAGICAL_FEAR]) {
        !          2516:                     // Dispel magical fear.
        !          2517:                     return true;
        !          2518:                 }
        !          2519:             }
        !          2520:             return false; // Don't cast negation unless there's a good reason.
        !          2521:             break;
        !          2522:         case BE_SLOW:
        !          2523:             if (target->status[STATUS_SLOWED]) {
        !          2524:                 return false;
        !          2525:             }
        !          2526:             break;
        !          2527:         case BE_HASTE:
        !          2528:             if (target->status[STATUS_HASTED]) {
        !          2529:                 return false;
        !          2530:             }
        !          2531:             if (!targetEligibleForCombatBuff(caster, target)) {
        !          2532:                 return false;
        !          2533:             }
        !          2534:             break;
        !          2535:         case BE_SHIELDING:
        !          2536:             if (target->status[STATUS_SHIELDED]) {
        !          2537:                 return false;
        !          2538:             }
        !          2539:             if (!targetEligibleForCombatBuff(caster, target)) {
        !          2540:                 return false;
        !          2541:             }
        !          2542:             break;
        !          2543:         case BE_HEALING:
        !          2544:             if (target->currentHP >= target->info.maxHP) {
        !          2545:                 // Don't heal a creature already at full health.
        !          2546:                 return false;
        !          2547:             }
        !          2548:             break;
        !          2549:         case BE_TUNNELING:
        !          2550:         case BE_OBSTRUCTION:
        !          2551:             // Monsters will never cast these.
        !          2552:             return false;
        !          2553:             break;
        !          2554:         default:
        !          2555:             break;
        !          2556:     }
        !          2557:     return true;
        !          2558: }
        !          2559:
        !          2560: void monsterCastSpell(creature *caster, creature *target, enum boltType boltIndex) {
        !          2561:     bolt theBolt;
        !          2562:     short originLoc[2], targetLoc[2];
        !          2563:     char buf[200], monstName[100];
        !          2564:
        !          2565:     if (canDirectlySeeMonster(caster)) {
        !          2566:         monsterName(monstName, caster, true);
        !          2567:         sprintf(buf, "%s %s", monstName, boltCatalog[boltIndex].description);
        !          2568:         resolvePronounEscapes(buf, caster);
        !          2569:         combatMessage(buf, 0);
        !          2570:     }
        !          2571:
        !          2572:     theBolt = boltCatalog[boltIndex];
        !          2573:     originLoc[0] = caster->xLoc;
        !          2574:     originLoc[1] = caster->yLoc;
        !          2575:     targetLoc[0] = target->xLoc;
        !          2576:     targetLoc[1] = target->yLoc;
        !          2577:     zap(originLoc, targetLoc, &theBolt, false);
        !          2578:
        !          2579:     if (player.currentHP <= 0) {
        !          2580:         gameOver(monsterCatalog[caster->info.monsterID].monsterName, false);
        !          2581:     }
        !          2582: }
        !          2583:
        !          2584: // returns whether the monster cast a bolt.
        !          2585: boolean monstUseBolt(creature *monst) {
        !          2586:     creature *target;
        !          2587:     short i;
        !          2588:
        !          2589:     if (!monst->info.bolts[0]) {
        !          2590:         return false; // Don't waste time with monsters that can't cast anything.
        !          2591:     }
        !          2592:
        !          2593:     CYCLE_MONSTERS_AND_PLAYERS(target) {
        !          2594:         if (generallyValidBoltTarget(monst, target)) {
        !          2595:             for (i = 0; monst->info.bolts[i]; i++) {
        !          2596:                 if (boltCatalog[monst->info.bolts[i]].boltEffect == BE_BLINKING) {
        !          2597:                     continue; // Blinking is handled elsewhere.
        !          2598:                 }
        !          2599:                 if (specificallyValidBoltTarget(monst, target, monst->info.bolts[i])) {
        !          2600:                     if ((monst->info.flags & MONST_ALWAYS_USE_ABILITY)
        !          2601:                         || rand_percent(30)) {
        !          2602:
        !          2603:                         monsterCastSpell(monst, target, monst->info.bolts[i]);
        !          2604:                         return true;
        !          2605:                     }
        !          2606:                 }
        !          2607:             }
        !          2608:         }
        !          2609:     }
        !          2610:     return false;
        !          2611: }
        !          2612:
        !          2613: // returns whether the monster did something (and therefore ended its turn)
        !          2614: boolean monstUseMagic(creature *monst) {
        !          2615:     if (monsterSummons(monst, (monst->info.flags & MONST_ALWAYS_USE_ABILITY))) {
        !          2616:         return true;
        !          2617:     } else if (monstUseBolt(monst)) {
        !          2618:         return true;
        !          2619:     }
        !          2620:     return false;
        !          2621: }
        !          2622:
        !          2623: boolean isLocalScentMaximum(short x, short y) {
        !          2624:     enum directions dir;
        !          2625:     short newX, newY;
        !          2626:
        !          2627:     const short baselineScent = scentMap[x][y];
        !          2628:
        !          2629:     for (dir=0; dir< DIRECTION_COUNT; dir++) {
        !          2630:         newX = x + nbDirs[dir][0];
        !          2631:         newY = y + nbDirs[dir][1];
        !          2632:         if (coordinatesAreInMap(newX, newY)
        !          2633:             && (scentMap[newX][newY] > baselineScent)
        !          2634:             && !cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)
        !          2635:             && !diagonalBlocked(x, y, newX, newY, false)) {
        !          2636:
        !          2637:             return false;
        !          2638:         }
        !          2639:     }
        !          2640:     return true;
        !          2641: }
        !          2642:
        !          2643: // Returns the direction the player's scent points to from a given cell. Returns -1 if the nose comes up blank.
        !          2644: enum directions scentDirection(creature *monst) {
        !          2645:     short newX, newY, x, y, newestX, newestY;
        !          2646:     enum directions bestDirection = NO_DIRECTION, dir, dir2;
        !          2647:     unsigned short bestNearbyScent = 0;
        !          2648:     boolean canTryAgain = true;
        !          2649:     creature *otherMonst;
        !          2650:
        !          2651:     x = monst->xLoc;
        !          2652:     y = monst->yLoc;
        !          2653:
        !          2654:     for (;;) {
        !          2655:
        !          2656:         for (dir=0; dir< DIRECTION_COUNT; dir++) {
        !          2657:             newX = x + nbDirs[dir][0];
        !          2658:             newY = y + nbDirs[dir][1];
        !          2659:             otherMonst = monsterAtLoc(newX, newY);
        !          2660:             if (coordinatesAreInMap(newX, newY)
        !          2661:                 && (scentMap[newX][newY] > bestNearbyScent)
        !          2662:                 && (!(pmap[newX][newY].flags & HAS_MONSTER) || (otherMonst && canPass(monst, otherMonst)))
        !          2663:                 && !cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)
        !          2664:                 && !diagonalBlocked(x, y, newX, newY, false)
        !          2665:                 && !monsterAvoids(monst, newX, newY)) {
        !          2666:
        !          2667:                 bestNearbyScent = scentMap[newX][newY];
        !          2668:                 bestDirection = dir;
        !          2669:             }
        !          2670:         }
        !          2671:
        !          2672:         if (bestDirection >= 0 && bestNearbyScent > scentMap[x][y]) {
        !          2673:             return bestDirection;
        !          2674:         }
        !          2675:
        !          2676:         if (canTryAgain) {
        !          2677:             // Okay, the monster may be stuck in some irritating diagonal.
        !          2678:             // If so, we can diffuse the scent into the offending kink and solve the problem.
        !          2679:             // There's a possibility he's stuck for some other reason, though, so we'll only
        !          2680:             // try once per his move -- hence the failsafe.
        !          2681:             canTryAgain = false;
        !          2682:             for (dir=0; dir<4; dir++) {
        !          2683:                 newX = x + nbDirs[dir][0];
        !          2684:                 newY = y + nbDirs[dir][1];
        !          2685:                 for (dir2=0; dir2<4; dir2++) {
        !          2686:                     newestX = newX + nbDirs[dir2][0];
        !          2687:                     newestY = newY + nbDirs[dir2][1];
        !          2688:                     if (coordinatesAreInMap(newX, newY) && coordinatesAreInMap(newestX, newestY)) {
        !          2689:                         scentMap[newX][newY] = max(scentMap[newX][newY], scentMap[newestX][newestY] - 1);
        !          2690:                     }
        !          2691:                 }
        !          2692:             }
        !          2693:         } else {
        !          2694:             return NO_DIRECTION; // failure!
        !          2695:         }
        !          2696:     }
        !          2697: }
        !          2698:
        !          2699: // returns true if the resurrection was successful.
        !          2700: boolean resurrectAlly(const short x, const short y) {
        !          2701:     boolean success;
        !          2702:     creature *monst;
        !          2703:     monst = purgatory->nextCreature;
        !          2704:     if (monst) {
        !          2705:         // Remove from purgatory and insert into the mortal plane.
        !          2706:         purgatory->nextCreature = purgatory->nextCreature->nextCreature;
        !          2707:         monst->nextCreature = monsters->nextCreature;
        !          2708:         monsters->nextCreature = monst;
        !          2709:         getQualifyingPathLocNear(&monst->xLoc, &monst->yLoc, x, y, true,
        !          2710:                                  (T_PATHING_BLOCKER | T_HARMFUL_TERRAIN), 0,
        !          2711:                                  0, (HAS_PLAYER | HAS_MONSTER), false);
        !          2712:         pmap[monst->xLoc][monst->yLoc].flags |= HAS_MONSTER;
        !          2713:
        !          2714:         // Restore health etc.
        !          2715:         monst->bookkeepingFlags &= ~(MB_IS_DYING | MB_IS_FALLING);
        !          2716:         if (!(monst->info.flags & MONST_FIERY)
        !          2717:             && monst->status[STATUS_BURNING]) {
        !          2718:
        !          2719:             monst->status[STATUS_BURNING] = 0;
        !          2720:         }
        !          2721:         monst->status[STATUS_DISCORDANT] = 0;
        !          2722:         heal(monst, 100, true);
        !          2723:
        !          2724:         success = true;
        !          2725:     } else {
        !          2726:         success = false;
        !          2727:     }
        !          2728:     return success;
        !          2729: }
        !          2730:
        !          2731: void unAlly(creature *monst) {
        !          2732:     if (monst->creatureState == MONSTER_ALLY) {
        !          2733:         monst->creatureState = MONSTER_TRACKING_SCENT;
        !          2734:         monst->bookkeepingFlags &= ~(MB_FOLLOWER | MB_TELEPATHICALLY_REVEALED);
        !          2735:         monst->leader = NULL;
        !          2736:     }
        !          2737: }
        !          2738:
        !          2739: boolean monsterFleesFrom(creature *monst, creature *defender) {
        !          2740:     const short x = monst->xLoc;
        !          2741:     const short y = monst->yLoc;
        !          2742:
        !          2743:     if (!monsterWillAttackTarget(defender, monst)) {
        !          2744:         return false;
        !          2745:     }
        !          2746:
        !          2747:     if (distanceBetween(x, y, defender->xLoc, defender->yLoc) >= 4) {
        !          2748:         return false;
        !          2749:     }
        !          2750:
        !          2751:     if ((defender->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE))
        !          2752:         && !(defender->info.flags & MONST_IMMOBILE)) {
        !          2753:         // Don't charge if the monster is damage-immune and is NOT immobile;
        !          2754:         // i.e., keep distance from revenants and stone guardians but not mirror totems.
        !          2755:         return true;
        !          2756:     }
        !          2757:
        !          2758:     if ((monst->info.flags & MONST_MAINTAINS_DISTANCE)
        !          2759:         || (defender->info.abilityFlags & MA_KAMIKAZE)) {
        !          2760:
        !          2761:         // Don't charge if you maintain distance or if it's a kamikaze monster.
        !          2762:         return true;
        !          2763:     }
        !          2764:
        !          2765:     if (monst->info.abilityFlags & MA_POISONS
        !          2766:         && defender->status[STATUS_POISONED] * defender->poisonAmount > defender->currentHP) {
        !          2767:
        !          2768:         return true;
        !          2769:     }
        !          2770:
        !          2771:     return false;
        !          2772: }
        !          2773:
        !          2774: boolean allyFlees(creature *ally, creature *closestEnemy) {
        !          2775:     const short x = ally->xLoc;
        !          2776:     const short y = ally->yLoc;
        !          2777:
        !          2778:     if (!closestEnemy) {
        !          2779:         return false; // No one to flee from.
        !          2780:     }
        !          2781:
        !          2782:     if (ally->info.maxHP <= 1 || (ally->status[STATUS_LIFESPAN_REMAINING]) > 0) { // Spectral blades and timed allies should never flee.
        !          2783:         return false;
        !          2784:     }
        !          2785:
        !          2786:     if (distanceBetween(x, y, closestEnemy->xLoc, closestEnemy->yLoc) < 10
        !          2787:         && (100 * ally->currentHP / ally->info.maxHP <= 33)
        !          2788:         && ally->info.turnsBetweenRegen > 0
        !          2789:         && !ally->carriedMonster
        !          2790:         && ((ally->info.flags & MONST_FLEES_NEAR_DEATH) || (100 * ally->currentHP / ally->info.maxHP * 2 < 100 * player.currentHP / player.info.maxHP))) {
        !          2791:         // Flee if you're within 10 spaces, your HP is under 1/3, you're not a phoenix or lich or vampire in bat form,
        !          2792:         // and you either flee near death or your health fraction is less than half of the player's.
        !          2793:         return true;
        !          2794:     }
        !          2795:
        !          2796:     // so do allies that keep their distance or while in the presence of damage-immune or kamikaze enemies
        !          2797:     if (monsterFleesFrom(ally, closestEnemy)) {
        !          2798:         // Flee if you're within 3 spaces and you either flee near death or the closest enemy is a bloat, revenant or guardian.
        !          2799:         return true;
        !          2800:     }
        !          2801:
        !          2802:     return false;
        !          2803: }
        !          2804:
        !          2805: void monsterMillAbout(creature *monst, short movementChance) {
        !          2806:     enum directions dir;
        !          2807:     short targetLoc[2];
        !          2808:
        !          2809:     const short x = monst->xLoc;
        !          2810:     const short y = monst->yLoc;
        !          2811:
        !          2812:     if (rand_percent(movementChance)) {
        !          2813:         dir = randValidDirectionFrom(monst, x, y, true);
        !          2814:         if (dir != -1) {
        !          2815:             targetLoc[0] = x + nbDirs[dir][0];
        !          2816:             targetLoc[1] = y + nbDirs[dir][1];
        !          2817:             moveMonsterPassivelyTowards(monst, targetLoc, false);
        !          2818:         }
        !          2819:     }
        !          2820: }
        !          2821:
        !          2822: void moveAlly(creature *monst) {
        !          2823:     creature *target, *closestMonster = NULL;
        !          2824:     short i, j, x, y, dir, shortestDistance, targetLoc[2], leashLength;
        !          2825:     short **enemyMap, **costMap;
        !          2826:     char buf[DCOLS], monstName[DCOLS];
        !          2827:
        !          2828:     x = monst->xLoc;
        !          2829:     y = monst->yLoc;
        !          2830:
        !          2831:     targetLoc[0] = targetLoc[1] = 0;
        !          2832:
        !          2833:     if (!(monst->leader)) {
        !          2834:         monst->leader = &player;
        !          2835:         monst->bookkeepingFlags |= MB_FOLLOWER;
        !          2836:     }
        !          2837:
        !          2838:     // If we're standing in harmful terrain and there is a way to escape it, spend this turn escaping it.
        !          2839:     if (cellHasTerrainFlag(x, y, (T_HARMFUL_TERRAIN & ~(T_IS_FIRE | T_CAUSES_DAMAGE | T_CAUSES_PARALYSIS | T_CAUSES_CONFUSION)))
        !          2840:         || (cellHasTerrainFlag(x, y, T_IS_FIRE) && !monst->status[STATUS_IMMUNE_TO_FIRE])
        !          2841:         || (cellHasTerrainFlag(x, y, T_CAUSES_DAMAGE | T_CAUSES_PARALYSIS | T_CAUSES_CONFUSION) && !(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE)))) {
        !          2842:
        !          2843:         if (!rogue.updatedMapToSafeTerrainThisTurn) {
        !          2844:             updateSafeTerrainMap();
        !          2845:         }
        !          2846:
        !          2847:         if (monsterBlinkToPreferenceMap(monst, rogue.mapToSafeTerrain, false)) {
        !          2848:             monst->ticksUntilTurn = monst->attackSpeed * (monst->info.flags & MONST_CAST_SPELLS_SLOWLY ? 2 : 1);
        !          2849:             return;
        !          2850:         }
        !          2851:
        !          2852:         dir = nextStep(rogue.mapToSafeTerrain, x, y, monst, true);
        !          2853:         if (dir != -1) {
        !          2854:             targetLoc[0] = x + nbDirs[dir][0];
        !          2855:             targetLoc[1] = y + nbDirs[dir][1];
        !          2856:             if (moveMonsterPassivelyTowards(monst, targetLoc, false)) {
        !          2857:                 return;
        !          2858:             }
        !          2859:         }
        !          2860:     }
        !          2861:
        !          2862:     // Look around for enemies; shortestDistance will be the distance to the nearest.
        !          2863:     shortestDistance = max(DROWS, DCOLS);
        !          2864:     for (target = monsters->nextCreature; target != NULL; target = target->nextCreature) {
        !          2865:         if (target != monst
        !          2866:             && (!(target->bookkeepingFlags & MB_SUBMERGED) || (monst->bookkeepingFlags & MB_SUBMERGED))
        !          2867:             && monsterWillAttackTarget(monst, target)
        !          2868:             && distanceBetween(x, y, target->xLoc, target->yLoc) < shortestDistance
        !          2869:             && traversiblePathBetween(monst, target->xLoc, target->yLoc)
        !          2870:             && (!cellHasTerrainFlag(target->xLoc, target->yLoc, T_OBSTRUCTS_PASSABILITY) || (target->info.flags & MONST_ATTACKABLE_THRU_WALLS))
        !          2871:             && (!target->status[STATUS_INVISIBLE] || rand_percent(33))) {
        !          2872:
        !          2873:             shortestDistance = distanceBetween(x, y, target->xLoc, target->yLoc);
        !          2874:             closestMonster = target;
        !          2875:         }
        !          2876:     }
        !          2877:
        !          2878:     // Weak allies in the presence of enemies seek safety;
        !          2879:     if (allyFlees(monst, closestMonster)) {
        !          2880:         if (monsterHasBoltEffect(monst, BE_BLINKING)
        !          2881:             && ((monst->info.flags & MONST_ALWAYS_USE_ABILITY) || rand_percent(30))
        !          2882:             && monsterBlinkToSafety(monst)) {
        !          2883:
        !          2884:             return;
        !          2885:         }
        !          2886:         if (monsterSummons(monst, (monst->info.flags & MONST_ALWAYS_USE_ABILITY))) {
        !          2887:             return;
        !          2888:         }
        !          2889:         if (!rogue.updatedAllySafetyMapThisTurn) {
        !          2890:             updateAllySafetyMap();
        !          2891:         }
        !          2892:         dir = nextStep(allySafetyMap, monst->xLoc, monst->yLoc, monst, true);
        !          2893:         if (dir != -1) {
        !          2894:             targetLoc[0] = x + nbDirs[dir][0];
        !          2895:             targetLoc[1] = y + nbDirs[dir][1];
        !          2896:         }
        !          2897:         if (dir == -1
        !          2898:             || (allySafetyMap[targetLoc[0]][targetLoc[1]] >= allySafetyMap[x][y])
        !          2899:             || (!moveMonster(monst, nbDirs[dir][0], nbDirs[dir][1]) && !moveMonsterPassivelyTowards(monst, targetLoc, true))) {
        !          2900:             // ally can't flee; continue below
        !          2901:         } else {
        !          2902:             return;
        !          2903:         }
        !          2904:     }
        !          2905:
        !          2906:     // Magic users sometimes cast spells.
        !          2907:     if (monstUseMagic(monst)) { // if he actually cast a spell
        !          2908:         monst->ticksUntilTurn = monst->attackSpeed * (monst->info.flags & MONST_CAST_SPELLS_SLOWLY ? 2 : 1);
        !          2909:         return;
        !          2910:     }
        !          2911:
        !          2912:     if (monst->bookkeepingFlags & MB_SEIZED) {
        !          2913:         leashLength = max(DCOLS, DROWS); // Ally will never be prevented from attacking while seized.
        !          2914:     } else if (rogue.justRested || rogue.justSearched) {
        !          2915:         leashLength = 10;
        !          2916:     } else {
        !          2917:         leashLength = 4;
        !          2918:     }
        !          2919:     if (shortestDistance == 1) {
        !          2920:         if (closestMonster->movementSpeed < monst->movementSpeed
        !          2921:             && !(closestMonster->info.flags & (MONST_FLITS | MONST_IMMOBILE))
        !          2922:             && closestMonster->creatureState == MONSTER_TRACKING_SCENT) {
        !          2923:             // Never try to flee from combat with a faster enemy.
        !          2924:             leashLength = max(DCOLS, DROWS);
        !          2925:         } else {
        !          2926:             leashLength++; // If the ally is adjacent to a monster at the end of its leash, it shouldn't be prevented from attacking.
        !          2927:         }
        !          2928:     }
        !          2929:
        !          2930:     if (closestMonster
        !          2931:         && (distanceBetween(x, y, player.xLoc, player.yLoc) < leashLength || (monst->bookkeepingFlags & MB_DOES_NOT_TRACK_LEADER))
        !          2932:         && !(monst->info.flags & MONST_MAINTAINS_DISTANCE)
        !          2933:         && !attackWouldBeFutile(monst, closestMonster)) {
        !          2934:
        !          2935:         // Blink toward an enemy?
        !          2936:         if (monsterHasBoltEffect(monst, BE_BLINKING)
        !          2937:             && ((monst->info.flags & MONST_ALWAYS_USE_ABILITY) || rand_percent(30))) {
        !          2938:
        !          2939:             enemyMap = allocGrid();
        !          2940:             costMap = allocGrid();
        !          2941:
        !          2942:             for (i=0; i<DCOLS; i++) {
        !          2943:                 for (j=0; j<DROWS; j++) {
        !          2944:                     if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY)) {
        !          2945:                         costMap[i][j] = cellHasTerrainFlag(i, j, T_OBSTRUCTS_DIAGONAL_MOVEMENT) ? PDS_OBSTRUCTION : PDS_FORBIDDEN;
        !          2946:                         enemyMap[i][j] = 0; // safeguard against OOS
        !          2947:                     } else if (monsterAvoids(monst, i, j)) {
        !          2948:                         costMap[i][j] = PDS_FORBIDDEN;
        !          2949:                         enemyMap[i][j] = 0; // safeguard against OOS
        !          2950:                     } else {
        !          2951:                         costMap[i][j] = 1;
        !          2952:                         enemyMap[i][j] = 10000;
        !          2953:                     }
        !          2954:                 }
        !          2955:             }
        !          2956:
        !          2957:             for (target = monsters->nextCreature; target != NULL; target = target->nextCreature) {
        !          2958:                 if (target != monst
        !          2959:                     && (!(target->bookkeepingFlags & MB_SUBMERGED) || (monst->bookkeepingFlags & MB_SUBMERGED))
        !          2960:                     && monsterWillAttackTarget(monst, target)
        !          2961:                     && distanceBetween(x, y, target->xLoc, target->yLoc) < shortestDistance
        !          2962:                     && traversiblePathBetween(monst, target->xLoc, target->yLoc)
        !          2963:                     && (!monsterAvoids(monst, target->xLoc, target->yLoc) || (target->info.flags & MONST_ATTACKABLE_THRU_WALLS))
        !          2964:                     && (!target->status[STATUS_INVISIBLE] || ((monst->info.flags & MONST_ALWAYS_USE_ABILITY) || rand_percent(33)))) {
        !          2965:
        !          2966:                     enemyMap[target->xLoc][target->yLoc] = 0;
        !          2967:                     costMap[target->xLoc][target->yLoc] = 1;
        !          2968:                 }
        !          2969:             }
        !          2970:
        !          2971:             dijkstraScan(enemyMap, costMap, true);
        !          2972:             freeGrid(costMap);
        !          2973:
        !          2974:             if (monsterBlinkToPreferenceMap(monst, enemyMap, false)) {
        !          2975:                 monst->ticksUntilTurn = monst->attackSpeed * (monst->info.flags & MONST_CAST_SPELLS_SLOWLY ? 2 : 1);
        !          2976:                 freeGrid(enemyMap);
        !          2977:                 return;
        !          2978:             }
        !          2979:             freeGrid(enemyMap);
        !          2980:         }
        !          2981:
        !          2982:         targetLoc[0] = closestMonster->xLoc;
        !          2983:         targetLoc[1] = closestMonster->yLoc;
        !          2984:         moveMonsterPassivelyTowards(monst, targetLoc, false);
        !          2985:     } else if (monst->targetCorpseLoc[0]
        !          2986:                && !monst->status[STATUS_POISONED]
        !          2987:                && (!monst->status[STATUS_BURNING] || monst->status[STATUS_IMMUNE_TO_FIRE])) { // Going to start eating a corpse.
        !          2988:         moveMonsterPassivelyTowards(monst, monst->targetCorpseLoc, false);
        !          2989:         if (monst->xLoc == monst->targetCorpseLoc[0]
        !          2990:             && monst->yLoc == monst->targetCorpseLoc[1]
        !          2991:             && !(monst->bookkeepingFlags & MB_ABSORBING)) {
        !          2992:             if (canSeeMonster(monst)) {
        !          2993:                 monsterName(monstName, monst, true);
        !          2994:                 sprintf(buf, "%s begins %s the fallen %s.", monstName, monsterText[monst->info.monsterID].absorbing, monst->targetCorpseName);
        !          2995:                 messageWithColor(buf, &goodMessageColor, false);
        !          2996:             }
        !          2997:             monst->corpseAbsorptionCounter = 20;
        !          2998:             monst->bookkeepingFlags |= MB_ABSORBING;
        !          2999:         }
        !          3000:     } else if ((monst->bookkeepingFlags & MB_DOES_NOT_TRACK_LEADER)
        !          3001:                || (distanceBetween(x, y, player.xLoc, player.yLoc) < 3 && (pmap[x][y].flags & IN_FIELD_OF_VIEW))) {
        !          3002:
        !          3003:         monst->bookkeepingFlags &= ~MB_GIVEN_UP_ON_SCENT;
        !          3004:         monsterMillAbout(monst, 30);
        !          3005:     } else {
        !          3006:         if (!(monst->bookkeepingFlags & MB_GIVEN_UP_ON_SCENT)
        !          3007:             && distanceBetween(x, y, player.xLoc, player.yLoc) > 10
        !          3008:             && monsterBlinkToPreferenceMap(monst, scentMap, true)) {
        !          3009:
        !          3010:             monst->ticksUntilTurn = monst->attackSpeed * (monst->info.flags & MONST_CAST_SPELLS_SLOWLY ? 2 : 1);
        !          3011:             return;
        !          3012:         }
        !          3013:         dir = scentDirection(monst);
        !          3014:         if (dir == -1 || (monst->bookkeepingFlags & MB_GIVEN_UP_ON_SCENT)) {
        !          3015:             monst->bookkeepingFlags |= MB_GIVEN_UP_ON_SCENT;
        !          3016:             pathTowardCreature(monst, monst->leader);
        !          3017:         } else {
        !          3018:             targetLoc[0] = x + nbDirs[dir][0];
        !          3019:             targetLoc[1] = y + nbDirs[dir][1];
        !          3020:             moveMonsterPassivelyTowards(monst, targetLoc, false);
        !          3021:         }
        !          3022:     }
        !          3023: }
        !          3024:
        !          3025: // Returns whether to abort the turn.
        !          3026: boolean updateMonsterCorpseAbsorption(creature *monst) {
        !          3027:     short i;
        !          3028:     char buf[COLS], buf2[COLS];
        !          3029:
        !          3030:     if (monst->xLoc == monst->targetCorpseLoc[0]
        !          3031:         && monst->yLoc == monst->targetCorpseLoc[1]
        !          3032:         && (monst->bookkeepingFlags & MB_ABSORBING)) {
        !          3033:
        !          3034:         if (--monst->corpseAbsorptionCounter <= 0) {
        !          3035:             monst->targetCorpseLoc[0] = monst->targetCorpseLoc[1] = 0;
        !          3036:             if (monst->absorptionBolt != BOLT_NONE) {
        !          3037:                 for (i=0; monst->info.bolts[i] != BOLT_NONE; i++);
        !          3038:                 monst->info.bolts[i] = monst->absorptionBolt;
        !          3039:             } else if (monst->absorbBehavior) {
        !          3040:                 monst->info.flags |= monst->absorptionFlags;
        !          3041:             } else {
        !          3042:                 monst->info.abilityFlags |= monst->absorptionFlags;
        !          3043:             }
        !          3044:             monst->newPowerCount--;
        !          3045:             monst->bookkeepingFlags &= ~MB_ABSORBING;
        !          3046:
        !          3047:             if (monst->info.flags & MONST_FIERY) {
        !          3048:                 monst->status[STATUS_BURNING] = monst->maxStatus[STATUS_BURNING] = 1000; // won't decrease
        !          3049:             }
        !          3050:             if (monst->info.flags & MONST_FLIES) {
        !          3051:                 monst->status[STATUS_LEVITATING] = monst->maxStatus[STATUS_LEVITATING] = 1000; // won't decrease
        !          3052:                 monst->info.flags &= ~(MONST_RESTRICTED_TO_LIQUID | MONST_SUBMERGES);
        !          3053:                 monst->bookkeepingFlags &= ~(MB_SUBMERGED);
        !          3054:             }
        !          3055:             if (monst->info.flags & MONST_IMMUNE_TO_FIRE) {
        !          3056:                 monst->status[STATUS_IMMUNE_TO_FIRE] = monst->maxStatus[STATUS_IMMUNE_TO_FIRE] = 1000; // won't decrease
        !          3057:             }
        !          3058:             if (monst->info.flags & MONST_INVISIBLE) {
        !          3059:                 monst->status[STATUS_INVISIBLE] = monst->maxStatus[STATUS_INVISIBLE] = 1000; // won't decrease
        !          3060:             }
        !          3061:             if (canSeeMonster(monst)) {
        !          3062:                 monsterName(buf2, monst, true);
        !          3063:                 sprintf(buf, "%s finished %s the %s.", buf2, monsterText[monst->info.monsterID].absorbing, monst->targetCorpseName);
        !          3064:                 messageWithColor(buf, &goodMessageColor, false);
        !          3065:                 if (monst->absorptionBolt != BOLT_NONE) {
        !          3066:                     sprintf(buf, "%s %s!", buf2, boltCatalog[monst->absorptionBolt].abilityDescription);
        !          3067:                 } else if (monst->absorbBehavior) {
        !          3068:                     sprintf(buf, "%s now %s!", buf2, monsterBehaviorFlagDescriptions[unflag(monst->absorptionFlags)]);
        !          3069:                 } else {
        !          3070:                     sprintf(buf, "%s now %s!", buf2, monsterAbilityFlagDescriptions[unflag(monst->absorptionFlags)]);
        !          3071:                 }
        !          3072:                 resolvePronounEscapes(buf, monst);
        !          3073:                 messageWithColor(buf, &advancementMessageColor, false);
        !          3074:             }
        !          3075:             monst->absorptionFlags = 0;
        !          3076:             monst->absorptionBolt = BOLT_NONE;
        !          3077:         }
        !          3078:         monst->ticksUntilTurn = 100;
        !          3079:         return true;
        !          3080:     } else if (--monst->corpseAbsorptionCounter <= 0) {
        !          3081:         monst->targetCorpseLoc[0] = monst->targetCorpseLoc[1] = 0; // lost its chance
        !          3082:         monst->bookkeepingFlags &= ~MB_ABSORBING;
        !          3083:         monst->absorptionFlags = 0;
        !          3084:         monst->absorptionBolt = BOLT_NONE;
        !          3085:     } else if (monst->bookkeepingFlags & MB_ABSORBING) {
        !          3086:         monst->bookkeepingFlags &= ~MB_ABSORBING; // absorbing but not on the corpse
        !          3087:         if (monst->corpseAbsorptionCounter <= 15) {
        !          3088:             monst->targetCorpseLoc[0] = monst->targetCorpseLoc[1] = 0; // lost its chance
        !          3089:             monst->absorptionFlags = 0;
        !          3090:             monst->absorptionBolt = BOLT_NONE;
        !          3091:         }
        !          3092:     }
        !          3093:     return false;
        !          3094: }
        !          3095:
        !          3096: void monstersTurn(creature *monst) {
        !          3097:     short x, y, playerLoc[2], targetLoc[2], dir, shortestDistance;
        !          3098:     boolean alreadyAtBestScent;
        !          3099:     creature *ally, *target, *closestMonster;
        !          3100:
        !          3101:     monst->turnsSpentStationary++;
        !          3102:
        !          3103:     if (monst->corpseAbsorptionCounter >= 0 && updateMonsterCorpseAbsorption(monst)) {
        !          3104:         return;
        !          3105:     }
        !          3106:
        !          3107:     if (monst->info.DFChance
        !          3108:         && (monst->info.flags & MONST_GETS_TURN_ON_ACTIVATION)
        !          3109:         && rand_percent(monst->info.DFChance)) {
        !          3110:
        !          3111:         spawnDungeonFeature(monst->xLoc, monst->yLoc, &dungeonFeatureCatalog[monst->info.DFType], true, false);
        !          3112:     }
        !          3113:
        !          3114:     applyInstantTileEffectsToCreature(monst); // Paralysis, confusion etc. take effect before the monster can move.
        !          3115:
        !          3116:     // if the monster is paralyzed, entranced or chained, this is where its turn ends.
        !          3117:     if (monst->status[STATUS_PARALYZED] || monst->status[STATUS_ENTRANCED] || (monst->bookkeepingFlags & MB_CAPTIVE)) {
        !          3118:         monst->ticksUntilTurn = monst->movementSpeed;
        !          3119:         if ((monst->bookkeepingFlags & MB_CAPTIVE) && monst->carriedItem) {
        !          3120:             makeMonsterDropItem(monst);
        !          3121:         }
        !          3122:         return;
        !          3123:     }
        !          3124:
        !          3125:     if (monst->bookkeepingFlags & MB_IS_DYING) {
        !          3126:         return;
        !          3127:     }
        !          3128:
        !          3129:     monst->ticksUntilTurn = monst->movementSpeed / 3; // will be later overwritten by movement or attack
        !          3130:
        !          3131:     x = monst->xLoc;
        !          3132:     y = monst->yLoc;
        !          3133:
        !          3134:     // Sleepers can awaken, but it takes a whole turn.
        !          3135:     if (monst->creatureState == MONSTER_SLEEPING) {
        !          3136:         monst->ticksUntilTurn = monst->movementSpeed;
        !          3137:         updateMonsterState(monst);
        !          3138:         return;
        !          3139:     }
        !          3140:
        !          3141:     // Update creature state if appropriate.
        !          3142:     updateMonsterState(monst);
        !          3143:
        !          3144:     if (monst->creatureState == MONSTER_SLEEPING) {
        !          3145:         monst->ticksUntilTurn = monst->movementSpeed;
        !          3146:         return;
        !          3147:     }
        !          3148:
        !          3149:     // and move the monster.
        !          3150:
        !          3151:     // immobile monsters can only use special abilities:
        !          3152:     if (monst->info.flags & MONST_IMMOBILE) {
        !          3153:         if (monstUseMagic(monst)) { // if he actually cast a spell
        !          3154:             monst->ticksUntilTurn = monst->attackSpeed * (monst->info.flags & MONST_CAST_SPELLS_SLOWLY ? 2 : 1);
        !          3155:             return;
        !          3156:         }
        !          3157:         monst->ticksUntilTurn = monst->attackSpeed;
        !          3158:         return;
        !          3159:     }
        !          3160:
        !          3161:     // discordant monsters
        !          3162:     if (monst->status[STATUS_DISCORDANT] && monst->creatureState != MONSTER_FLEEING) {
        !          3163:         shortestDistance = max(DROWS, DCOLS);
        !          3164:         closestMonster = NULL;
        !          3165:         CYCLE_MONSTERS_AND_PLAYERS(target) {
        !          3166:             if (target != monst
        !          3167:                 && (!(target->bookkeepingFlags & MB_SUBMERGED) || (monst->bookkeepingFlags & MB_SUBMERGED))
        !          3168:                 && monsterWillAttackTarget(monst, target)
        !          3169:                 && distanceBetween(x, y, target->xLoc, target->yLoc) < shortestDistance
        !          3170:                 && traversiblePathBetween(monst, target->xLoc, target->yLoc)
        !          3171:                 && (!monsterAvoids(monst, target->xLoc, target->yLoc) || (target->info.flags & MONST_ATTACKABLE_THRU_WALLS))
        !          3172:                 && (!target->status[STATUS_INVISIBLE] || rand_percent(33))) {
        !          3173:
        !          3174:                 shortestDistance = distanceBetween(x, y, target->xLoc, target->yLoc);
        !          3175:                 closestMonster = target;
        !          3176:             }
        !          3177:         }
        !          3178:         if (closestMonster && monstUseMagic(monst)) {
        !          3179:             monst->ticksUntilTurn = monst->attackSpeed * (monst->info.flags & MONST_CAST_SPELLS_SLOWLY ? 2 : 1);
        !          3180:             return;
        !          3181:         }
        !          3182:         if (closestMonster && !(monst->info.flags & MONST_MAINTAINS_DISTANCE)) {
        !          3183:             targetLoc[0] = closestMonster->xLoc;
        !          3184:             targetLoc[1] = closestMonster->yLoc;
        !          3185:             if (moveMonsterPassivelyTowards(monst, targetLoc, monst->creatureState == MONSTER_ALLY)) {
        !          3186:                 return;
        !          3187:             }
        !          3188:         }
        !          3189:     }
        !          3190:
        !          3191:     // hunting
        !          3192:     if ((monst->creatureState == MONSTER_TRACKING_SCENT
        !          3193:         || (monst->creatureState == MONSTER_ALLY && monst->status[STATUS_DISCORDANT]))
        !          3194:         // eels don't charge if you're not in the water
        !          3195:         && (!(monst->info.flags & MONST_RESTRICTED_TO_LIQUID) || cellHasTMFlag(player.xLoc, player.yLoc, TM_ALLOWS_SUBMERGING))) {
        !          3196:
        !          3197:         // magic users sometimes cast spells
        !          3198:         if (monstUseMagic(monst)
        !          3199:             || (monsterHasBoltEffect(monst, BE_BLINKING)
        !          3200:                 && ((monst->info.flags & MONST_ALWAYS_USE_ABILITY) || rand_percent(30))
        !          3201:                 && monsterBlinkToPreferenceMap(monst, scentMap, true))) { // if he actually cast a spell
        !          3202:
        !          3203:                 monst->ticksUntilTurn = monst->attackSpeed * (monst->info.flags & MONST_CAST_SPELLS_SLOWLY ? 2 : 1);
        !          3204:                 return;
        !          3205:             }
        !          3206:
        !          3207:         // if the monster is adjacent to an ally and not adjacent to the player, attack the ally
        !          3208:         if (distanceBetween(x, y, player.xLoc, player.yLoc) > 1
        !          3209:             || diagonalBlocked(x, y, player.xLoc, player.yLoc, false)) {
        !          3210:             for (ally = monsters->nextCreature; ally != NULL; ally = ally->nextCreature) {
        !          3211:                 if (monsterWillAttackTarget(monst, ally)
        !          3212:                     && distanceBetween(x, y, ally->xLoc, ally->yLoc) == 1
        !          3213:                     && (!ally->status[STATUS_INVISIBLE] || rand_percent(33))) {
        !          3214:
        !          3215:                     targetLoc[0] = ally->xLoc;
        !          3216:                     targetLoc[1] = ally->yLoc;
        !          3217:                     if (moveMonsterPassivelyTowards(monst, targetLoc, true)) { // attack
        !          3218:                         return;
        !          3219:                     }
        !          3220:                 }
        !          3221:             }
        !          3222:         }
        !          3223:
        !          3224:         if ((monst->status[STATUS_LEVITATING] || (monst->info.flags & MONST_RESTRICTED_TO_LIQUID) || (monst->bookkeepingFlags & MB_SUBMERGED)
        !          3225:              || ((monst->info.flags & (MONST_IMMUNE_TO_WEBS | MONST_INVULNERABLE) && monsterCanShootWebs(monst))))
        !          3226:             && pmap[x][y].flags & IN_FIELD_OF_VIEW) {
        !          3227:
        !          3228:             playerLoc[0] = player.xLoc;
        !          3229:             playerLoc[1] = player.yLoc;
        !          3230:             moveMonsterPassivelyTowards(monst, playerLoc, true); // attack
        !          3231:             return;
        !          3232:         }
        !          3233:         if ((monst->info.flags & MONST_ALWAYS_HUNTING)
        !          3234:             && (monst->bookkeepingFlags & MB_GIVEN_UP_ON_SCENT)) {
        !          3235:
        !          3236:             pathTowardCreature(monst, &player);
        !          3237:             return;
        !          3238:         }
        !          3239:
        !          3240:         dir = scentDirection(monst);
        !          3241:         if (dir == NO_DIRECTION) {
        !          3242:             alreadyAtBestScent = isLocalScentMaximum(monst->xLoc, monst->yLoc);
        !          3243:             if (alreadyAtBestScent && monst->creatureState != MONSTER_ALLY) {
        !          3244:                 if (monst->info.flags & MONST_ALWAYS_HUNTING) {
        !          3245:                     pathTowardCreature(monst, &player);
        !          3246:                     monst->bookkeepingFlags |= MB_GIVEN_UP_ON_SCENT;
        !          3247:                     return;
        !          3248:                 }
        !          3249:                 monst->creatureState = MONSTER_WANDERING;
        !          3250:                 chooseNewWanderDestination(monst);
        !          3251:             }
        !          3252:         } else {
        !          3253:             moveMonster(monst, nbDirs[dir][0], nbDirs[dir][1]);
        !          3254:         }
        !          3255:     } else if (monst->creatureState == MONSTER_FLEEING) {
        !          3256:         // fleeing
        !          3257:         if (monsterHasBoltEffect(monst, BE_BLINKING)
        !          3258:             && ((monst->info.flags & MONST_ALWAYS_USE_ABILITY) || rand_percent(30))
        !          3259:             && monsterBlinkToSafety(monst)) {
        !          3260:
        !          3261:             return;
        !          3262:         }
        !          3263:
        !          3264:         if (monsterSummons(monst, (monst->info.flags & MONST_ALWAYS_USE_ABILITY))) {
        !          3265:             return;
        !          3266:         }
        !          3267:
        !          3268:         if (fleeingMonsterAwareOfPlayer(monst)) {
        !          3269:             if (monst->safetyMap) {
        !          3270:                 freeGrid(monst->safetyMap);
        !          3271:                 monst->safetyMap = NULL;
        !          3272:             }
        !          3273:             if (!rogue.updatedSafetyMapThisTurn) {
        !          3274:                 updateSafetyMap();
        !          3275:             }
        !          3276:             dir = nextStep(safetyMap, monst->xLoc, monst->yLoc, NULL, true);
        !          3277:         } else {
        !          3278:             if (!monst->safetyMap) {
        !          3279:                 if (rogue.patchVersion >= 3 && !rogue.updatedSafetyMapThisTurn) {
        !          3280:                     updateSafetyMap();
        !          3281:                 }
        !          3282:                 monst->safetyMap = allocGrid();
        !          3283:                 copyGrid(monst->safetyMap, safetyMap);
        !          3284:             }
        !          3285:             dir = nextStep(monst->safetyMap, monst->xLoc, monst->yLoc, NULL, true);
        !          3286:         }
        !          3287:         if (dir != -1) {
        !          3288:             targetLoc[0] = x + nbDirs[dir][0];
        !          3289:             targetLoc[1] = y + nbDirs[dir][1];
        !          3290:         }
        !          3291:         if (dir == -1 || (!moveMonster(monst, nbDirs[dir][0], nbDirs[dir][1]) && !moveMonsterPassivelyTowards(monst, targetLoc, true))) {
        !          3292:             CYCLE_MONSTERS_AND_PLAYERS(ally) {
        !          3293:                 if (!monst->status[STATUS_MAGICAL_FEAR] // Fearful monsters will never attack.
        !          3294:                     && monsterWillAttackTarget(monst, ally)
        !          3295:                     && distanceBetween(x, y, ally->xLoc, ally->yLoc) <= 1) {
        !          3296:
        !          3297:                     moveMonster(monst, ally->xLoc - x, ally->yLoc - y); // attack the player if cornered
        !          3298:                     return;
        !          3299:                 }
        !          3300:             }
        !          3301:         }
        !          3302:         return;
        !          3303:     } else if (monst->creatureState == MONSTER_WANDERING
        !          3304:                // eels wander if you're not in water
        !          3305:                || ((monst->info.flags & MONST_RESTRICTED_TO_LIQUID) && !cellHasTMFlag(player.xLoc, player.yLoc, TM_ALLOWS_SUBMERGING))) {
        !          3306:
        !          3307:         // if we're standing in harmful terrain and there is a way to escape it, spend this turn escaping it.
        !          3308:         if (cellHasTerrainFlag(x, y, (T_HARMFUL_TERRAIN & ~T_IS_FIRE))
        !          3309:             || (cellHasTerrainFlag(x, y, T_IS_FIRE) && !monst->status[STATUS_IMMUNE_TO_FIRE] && !(monst->info.flags & MONST_INVULNERABLE))) {
        !          3310:             if (!rogue.updatedMapToSafeTerrainThisTurn) {
        !          3311:                 updateSafeTerrainMap();
        !          3312:             }
        !          3313:
        !          3314:             if (monsterBlinkToPreferenceMap(monst, rogue.mapToSafeTerrain, false)) {
        !          3315:                 monst->ticksUntilTurn = monst->attackSpeed * (monst->info.flags & MONST_CAST_SPELLS_SLOWLY ? 2 : 1);
        !          3316:                 return;
        !          3317:             }
        !          3318:
        !          3319:             dir = nextStep(rogue.mapToSafeTerrain, x, y, monst, true);
        !          3320:             if (dir != -1) {
        !          3321:                 targetLoc[0] = x + nbDirs[dir][0];
        !          3322:                 targetLoc[1] = y + nbDirs[dir][1];
        !          3323:                 if (moveMonsterPassivelyTowards(monst, targetLoc, true)) {
        !          3324:                     return;
        !          3325:                 }
        !          3326:             }
        !          3327:         }
        !          3328:
        !          3329:         // if a captive leader is captive, regenerative and healthy enough to withstand an attack,
        !          3330:         // and we're not poisonous, then approach or attack him.
        !          3331:         if ((monst->bookkeepingFlags & MB_FOLLOWER)
        !          3332:             && (monst->leader->bookkeepingFlags & MB_CAPTIVE)
        !          3333:             && monst->leader->currentHP > (int) (monst->info.damage.upperBound * monsterDamageAdjustmentAmount(monst) / FP_FACTOR)
        !          3334:             && monst->leader->info.turnsBetweenRegen > 0
        !          3335:             && !(monst->info.abilityFlags & MA_POISONS)
        !          3336:             && !diagonalBlocked(monst->xLoc, monst->yLoc, monst->leader->xLoc, monst->leader->yLoc, false)) {
        !          3337:
        !          3338:             if (distanceBetween(monst->xLoc, monst->yLoc, monst->leader->xLoc, monst->leader->yLoc) == 1) {
        !          3339:                 // Attack if adjacent.
        !          3340:                 monst->ticksUntilTurn = monst->attackSpeed;
        !          3341:                 attack(monst, monst->leader, false);
        !          3342:                 return;
        !          3343:             } else {
        !          3344:                 // Otherwise, approach.
        !          3345:                 pathTowardCreature(monst, monst->leader);
        !          3346:                 return;
        !          3347:             }
        !          3348:         }
        !          3349:
        !          3350:         // if the monster is adjacent to an ally and not fleeing, attack the ally
        !          3351:         if (monst->creatureState == MONSTER_WANDERING) {
        !          3352:             for (ally = monsters->nextCreature; ally != NULL; ally = ally->nextCreature) {
        !          3353:                 if (monsterWillAttackTarget(monst, ally)
        !          3354:                     && distanceBetween(x, y, ally->xLoc, ally->yLoc) == 1
        !          3355:                     && (!ally->status[STATUS_INVISIBLE] || rand_percent(33))) {
        !          3356:
        !          3357:                     targetLoc[0] = ally->xLoc;
        !          3358:                     targetLoc[1] = ally->yLoc;
        !          3359:                     if (moveMonsterPassivelyTowards(monst, targetLoc, true)) {
        !          3360:                         return;
        !          3361:                     }
        !          3362:                 }
        !          3363:             }
        !          3364:         }
        !          3365:
        !          3366:         // if you're a follower, don't get separated from the pack
        !          3367:         if (monst->bookkeepingFlags & MB_FOLLOWER) {
        !          3368:             if (distanceBetween(x, y, monst->leader->xLoc, monst->leader->yLoc) > 2) {
        !          3369:                 pathTowardCreature(monst, monst->leader);
        !          3370:             } else if (monst->leader->info.flags & MONST_IMMOBILE) {
        !          3371:                 monsterMillAbout(monst, 100); // Worshipers will pace frenetically.
        !          3372:             } else if (monst->leader->bookkeepingFlags & MB_CAPTIVE) {
        !          3373:                 monsterMillAbout(monst, 10); // Captors are languid.
        !          3374:             } else {
        !          3375:                 monsterMillAbout(monst, 30); // Other followers mill about like your allies do.
        !          3376:             }
        !          3377:         } else {
        !          3378:             // Step toward the chosen waypoint.
        !          3379:             dir = NO_DIRECTION;
        !          3380:             if (isValidWanderDestination(monst, monst->targetWaypointIndex)) {
        !          3381:                 dir = nextStep(rogue.wpDistance[monst->targetWaypointIndex], monst->xLoc, monst->yLoc, monst, false);
        !          3382:             }
        !          3383:             // If there's no path forward, call that waypoint finished and pick a new one.
        !          3384:             if (!isValidWanderDestination(monst, monst->targetWaypointIndex)
        !          3385:                 || dir == NO_DIRECTION) {
        !          3386:
        !          3387:                 chooseNewWanderDestination(monst);
        !          3388:                 if (isValidWanderDestination(monst, monst->targetWaypointIndex)) {
        !          3389:                     dir = nextStep(rogue.wpDistance[monst->targetWaypointIndex], monst->xLoc, monst->yLoc, monst, false);
        !          3390:                 }
        !          3391:             }
        !          3392:             // If there's still no path forward, step randomly as though flitting.
        !          3393:             // (This is how eels wander in deep water.)
        !          3394:             if (dir == NO_DIRECTION) {
        !          3395:                 dir = randValidDirectionFrom(monst, x, y, true);
        !          3396:             }
        !          3397:             if (dir != NO_DIRECTION) {
        !          3398:                 targetLoc[0] = x + nbDirs[dir][0];
        !          3399:                 targetLoc[1] = y + nbDirs[dir][1];
        !          3400:                 if (moveMonsterPassivelyTowards(monst, targetLoc, true)) {
        !          3401:                     return;
        !          3402:                 }
        !          3403:             }
        !          3404:         }
        !          3405:     } else if (monst->creatureState == MONSTER_ALLY) {
        !          3406:         moveAlly(monst);
        !          3407:     }
        !          3408: }
        !          3409:
        !          3410: boolean canPass(creature *mover, creature *blocker) {
        !          3411:
        !          3412:     if (blocker == &player) {
        !          3413:         return false;
        !          3414:     }
        !          3415:
        !          3416:     if (blocker->status[STATUS_CONFUSED]
        !          3417:         || blocker->status[STATUS_STUCK]
        !          3418:         || blocker->status[STATUS_PARALYZED]
        !          3419:         || blocker->status[STATUS_ENTRANCED]
        !          3420:         || mover->status[STATUS_ENTRANCED]) {
        !          3421:
        !          3422:         return false;
        !          3423:     }
        !          3424:
        !          3425:     if ((blocker->bookkeepingFlags & (MB_CAPTIVE | MB_ABSORBING))
        !          3426:         || (blocker->info.flags & MONST_IMMOBILE)) {
        !          3427:         return false;
        !          3428:     }
        !          3429:
        !          3430:     if (monstersAreEnemies(mover, blocker)) {
        !          3431:         return false;
        !          3432:     }
        !          3433:
        !          3434:     if (blocker->leader == mover) {
        !          3435:         return true;
        !          3436:     }
        !          3437:
        !          3438:     if (mover->leader == blocker) {
        !          3439:         return false;
        !          3440:     }
        !          3441:
        !          3442:     return (monstersAreTeammates(mover, blocker)
        !          3443:             && blocker->currentHP < mover->currentHP);
        !          3444: }
        !          3445:
        !          3446: boolean isPassableOrSecretDoor(short x, short y) {
        !          3447:     return (!cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)
        !          3448:             || (cellHasTMFlag(x, y, TM_IS_SECRET) && !(discoveredTerrainFlagsAtLoc(x, y) & T_OBSTRUCTS_PASSABILITY)));
        !          3449: }
        !          3450:
        !          3451: boolean knownToPlayerAsPassableOrSecretDoor(short x, short y) {
        !          3452:     unsigned long tFlags, TMFlags;
        !          3453:     getLocationFlags(x, y, &tFlags, &TMFlags, NULL, true);
        !          3454:     return (!(tFlags & T_OBSTRUCTS_PASSABILITY)
        !          3455:             || ((TMFlags & TM_IS_SECRET) && !(discoveredTerrainFlagsAtLoc(x, y) & T_OBSTRUCTS_PASSABILITY)));
        !          3456: }
        !          3457:
        !          3458: void setMonsterLocation(creature *monst, short newX, short newY) {
        !          3459:     unsigned long creatureFlag = (monst == &player ? HAS_PLAYER : HAS_MONSTER);
        !          3460:     pmap[monst->xLoc][monst->yLoc].flags &= ~creatureFlag;
        !          3461:     refreshDungeonCell(monst->xLoc, monst->yLoc);
        !          3462:     monst->turnsSpentStationary = 0;
        !          3463:     monst->xLoc = newX;
        !          3464:     monst->yLoc = newY;
        !          3465:     pmap[newX][newY].flags |= creatureFlag;
        !          3466:     if ((monst->bookkeepingFlags & MB_SUBMERGED) && !cellHasTMFlag(newX, newY, TM_ALLOWS_SUBMERGING)) {
        !          3467:         monst->bookkeepingFlags &= ~MB_SUBMERGED;
        !          3468:     }
        !          3469:     if (playerCanSee(newX, newY)
        !          3470:         && cellHasTMFlag(newX, newY, TM_IS_SECRET)
        !          3471:         && cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)) {
        !          3472:
        !          3473:         discover(newX, newY); // if you see a monster use a secret door, you discover it
        !          3474:     }
        !          3475:     refreshDungeonCell(newX, newY);
        !          3476:     applyInstantTileEffectsToCreature(monst);
        !          3477:     if (monst == &player) {
        !          3478:         updateVision(true);
        !          3479:         // get any items at the destination location
        !          3480:         if (pmap[player.xLoc][player.yLoc].flags & HAS_ITEM) {
        !          3481:             pickUpItemAt(player.xLoc, player.yLoc);
        !          3482:         }
        !          3483:     }
        !          3484: }
        !          3485:
        !          3486: // Tries to move the given monster in the given vector; returns true if the move was legal
        !          3487: // (including attacking player, vomiting or struggling in vain)
        !          3488: // Be sure that dx, dy are both in the range [-1, 1] or the move will sometimes fail due to the diagonal check.
        !          3489: boolean moveMonster(creature *monst, short dx, short dy) {
        !          3490:     short x = monst->xLoc, y = monst->yLoc;
        !          3491:     short newX, newY;
        !          3492:     short i;
        !          3493:     short confusedDirection, swarmDirection;
        !          3494:     creature *defender = NULL;
        !          3495:     creature *hitList[16] = {NULL};
        !          3496:     enum directions dir;
        !          3497:
        !          3498:     if (dx == 0 && dy == 0) {
        !          3499:         return false;
        !          3500:     }
        !          3501:
        !          3502:     newX = x + dx;
        !          3503:     newY = y + dy;
        !          3504:
        !          3505:     if (!coordinatesAreInMap(newX, newY)) {
        !          3506:         //DEBUG printf("\nProblem! Monster trying to move more than one space at a time.");
        !          3507:         return false;
        !          3508:     }
        !          3509:
        !          3510:     // vomiting
        !          3511:     if (monst->status[STATUS_NAUSEOUS] && rand_percent(25)) {
        !          3512:         vomit(monst);
        !          3513:         monst->ticksUntilTurn = monst->movementSpeed;
        !          3514:         return true;
        !          3515:     }
        !          3516:
        !          3517:     // move randomly?
        !          3518:     if (!monst->status[STATUS_ENTRANCED]) {
        !          3519:         if (monst->status[STATUS_CONFUSED]) {
        !          3520:             confusedDirection = randValidDirectionFrom(monst, x, y, false);
        !          3521:             if (confusedDirection != -1) {
        !          3522:                 dx = nbDirs[confusedDirection][0];
        !          3523:                 dy = nbDirs[confusedDirection][1];
        !          3524:             }
        !          3525:         } else if ((monst->info.flags & MONST_FLITS) && !(monst->bookkeepingFlags & MB_SEIZING) && rand_percent(33)) {
        !          3526:             confusedDirection = randValidDirectionFrom(monst, x, y, true);
        !          3527:             if (confusedDirection != -1) {
        !          3528:                 dx = nbDirs[confusedDirection][0];
        !          3529:                 dy = nbDirs[confusedDirection][1];
        !          3530:             }
        !          3531:         }
        !          3532:     }
        !          3533:
        !          3534:     newX = x + dx;
        !          3535:     newY = y + dy;
        !          3536:
        !          3537:     // Liquid-based monsters should never move or attack outside of liquid.
        !          3538:     if ((monst->info.flags & MONST_RESTRICTED_TO_LIQUID) && !cellHasTMFlag(newX, newY, TM_ALLOWS_SUBMERGING)) {
        !          3539:         return false;
        !          3540:     }
        !          3541:
        !          3542:     // Caught in spiderweb?
        !          3543:     if (monst->status[STATUS_STUCK] && !(pmap[newX][newY].flags & (HAS_PLAYER | HAS_MONSTER))
        !          3544:         && cellHasTerrainFlag(x, y, T_ENTANGLES) && !(monst->info.flags & MONST_IMMUNE_TO_WEBS)) {
        !          3545:         if (!(monst->info.flags & MONST_INVULNERABLE)
        !          3546:             && --monst->status[STATUS_STUCK]) {
        !          3547:
        !          3548:             monst->ticksUntilTurn = monst->movementSpeed;
        !          3549:             return true;
        !          3550:         } else if (tileCatalog[pmap[x][y].layers[SURFACE]].flags & T_ENTANGLES) {
        !          3551:             pmap[x][y].layers[SURFACE] = NOTHING;
        !          3552:         }
        !          3553:     }
        !          3554:
        !          3555:     if (pmap[newX][newY].flags & (HAS_MONSTER | HAS_PLAYER)) {
        !          3556:         defender = monsterAtLoc(newX, newY);
        !          3557:     } else {
        !          3558:         if (monst->bookkeepingFlags & MB_SEIZED) {
        !          3559:             for (defender = monsters->nextCreature; defender != NULL; defender = defender->nextCreature) {
        !          3560:                 if ((defender->bookkeepingFlags & MB_SEIZING)
        !          3561:                     && monstersAreEnemies(monst, defender)
        !          3562:                     && distanceBetween(monst->xLoc, monst->yLoc, defender->xLoc, defender->yLoc) == 1
        !          3563:                     && !diagonalBlocked(monst->xLoc, monst->yLoc, defender->xLoc, defender->yLoc, false)) {
        !          3564:
        !          3565:                     monst->ticksUntilTurn = monst->movementSpeed;
        !          3566:                     return true;
        !          3567:                 }
        !          3568:             }
        !          3569:             monst->bookkeepingFlags &= ~MB_SEIZED; // failsafe
        !          3570:         }
        !          3571:         if (monst->bookkeepingFlags & MB_SEIZING) {
        !          3572:             monst->bookkeepingFlags &= ~MB_SEIZING;
        !          3573:         }
        !          3574:     }
        !          3575:
        !          3576:
        !          3577:     for (dir = 0; dir < DIRECTION_COUNT; dir++) {
        !          3578:         if (dx == nbDirs[dir][0]
        !          3579:             && dy == nbDirs[dir][1]) {
        !          3580:
        !          3581:             break;
        !          3582:         }
        !          3583:     }
        !          3584:     brogueAssert(dir != NO_DIRECTION);
        !          3585:     if (handleWhipAttacks(monst, dir, NULL)
        !          3586:         || handleSpearAttacks(monst, dir, NULL)) {
        !          3587:
        !          3588:         monst->ticksUntilTurn = monst->attackSpeed;
        !          3589:         return true;
        !          3590:     }
        !          3591:
        !          3592:     if (((defender && (defender->info.flags & MONST_ATTACKABLE_THRU_WALLS))
        !          3593:          || (isPassableOrSecretDoor(newX, newY)
        !          3594:              && !diagonalBlocked(x, y, newX, newY, false)
        !          3595:              && isPassableOrSecretDoor(x, y)))
        !          3596:         && (!defender || canPass(monst, defender) || monsterWillAttackTarget(monst, defender))) {
        !          3597:             // if it's a legal move
        !          3598:
        !          3599:             if (defender) {
        !          3600:                 if (canPass(monst, defender)) {
        !          3601:
        !          3602:                     // swap places
        !          3603:                     pmap[defender->xLoc][defender->yLoc].flags &= ~HAS_MONSTER;
        !          3604:                     refreshDungeonCell(defender->xLoc, defender->yLoc);
        !          3605:
        !          3606:                     pmap[monst->xLoc][monst->yLoc].flags &= ~HAS_MONSTER;
        !          3607:                     refreshDungeonCell(monst->xLoc, monst->yLoc);
        !          3608:
        !          3609:                     monst->xLoc = newX;
        !          3610:                     monst->yLoc = newY;
        !          3611:                     pmap[monst->xLoc][monst->yLoc].flags |= HAS_MONSTER;
        !          3612:
        !          3613:                     if (monsterAvoids(defender, x, y)) { // don't want a flying monster to swap a non-flying monster into lava!
        !          3614:                         getQualifyingPathLocNear(&(defender->xLoc), &(defender->yLoc), x, y, true,
        !          3615:                                                  forbiddenFlagsForMonster(&(defender->info)), HAS_PLAYER,
        !          3616:                                                  forbiddenFlagsForMonster(&(defender->info)), (HAS_PLAYER | HAS_MONSTER | HAS_STAIRS), false);
        !          3617:                     } else {
        !          3618:                         defender->xLoc = x;
        !          3619:                         defender->yLoc = y;
        !          3620:                     }
        !          3621:                     pmap[defender->xLoc][defender->yLoc].flags |= HAS_MONSTER;
        !          3622:
        !          3623:                     refreshDungeonCell(monst->xLoc, monst->yLoc);
        !          3624:                     refreshDungeonCell(defender->xLoc, defender->yLoc);
        !          3625:
        !          3626:                     monst->ticksUntilTurn = monst->movementSpeed;
        !          3627:                     return true;
        !          3628:                 }
        !          3629:
        !          3630:                 // Sights are set on an enemy monster. Would we rather swarm than attack?
        !          3631:                 swarmDirection = monsterSwarmDirection(monst, defender);
        !          3632:                 if (swarmDirection != NO_DIRECTION) {
        !          3633:                     newX = monst->xLoc + nbDirs[swarmDirection][0];
        !          3634:                     newY = monst->yLoc + nbDirs[swarmDirection][1];
        !          3635:                     setMonsterLocation(monst, newX, newY);
        !          3636:                     monst->ticksUntilTurn = monst->movementSpeed;
        !          3637:                     return true;
        !          3638:                 } else {
        !          3639:                     // attacking another monster!
        !          3640:                     monst->ticksUntilTurn = monst->attackSpeed;
        !          3641:                     if (!((monst->info.abilityFlags & MA_SEIZES) && !(monst->bookkeepingFlags & MB_SEIZING))) {
        !          3642:                         // Bog monsters and krakens won't surface on the turn that they seize their target.
        !          3643:                         monst->bookkeepingFlags &= ~MB_SUBMERGED;
        !          3644:                     }
        !          3645:                     refreshDungeonCell(x, y);
        !          3646:
        !          3647:                     buildHitList(hitList, monst, defender,
        !          3648:                                  (monst->info.abilityFlags & MA_ATTACKS_ALL_ADJACENT) ? true : false);
        !          3649:                     // Attack!
        !          3650:                     for (i=0; i<16; i++) {
        !          3651:                         if (hitList[i]
        !          3652:                             && monsterWillAttackTarget(monst, hitList[i])
        !          3653:                             && !(hitList[i]->bookkeepingFlags & MB_IS_DYING)
        !          3654:                             && !rogue.gameHasEnded) {
        !          3655:
        !          3656:                             attack(monst, hitList[i], false);
        !          3657:                         }
        !          3658:                     }
        !          3659:                 }
        !          3660:                 return true;
        !          3661:             } else {
        !          3662:                 // okay we're moving!
        !          3663:                 setMonsterLocation(monst, newX, newY);
        !          3664:                 monst->ticksUntilTurn = monst->movementSpeed;
        !          3665:                 return true;
        !          3666:             }
        !          3667:         }
        !          3668:     return false;
        !          3669: }
        !          3670:
        !          3671: void clearStatus(creature *monst) {
        !          3672:     short i;
        !          3673:
        !          3674:     for (i=0; i<NUMBER_OF_STATUS_EFFECTS; i++) {
        !          3675:         monst->status[i] = monst->maxStatus[i] = 0;
        !          3676:     }
        !          3677: }
        !          3678:
        !          3679: // Bumps a creature to a random nearby hospitable cell.
        !          3680: void findAlternativeHomeFor(creature *monst, short *x, short *y, boolean chooseRandomly) {
        !          3681:     short sCols[DCOLS], sRows[DROWS], i, j, maxPermissibleDifference, dist;
        !          3682:
        !          3683:     fillSequentialList(sCols, DCOLS);
        !          3684:     fillSequentialList(sRows, DROWS);
        !          3685:     if (chooseRandomly) {
        !          3686:         shuffleList(sCols, DCOLS);
        !          3687:         shuffleList(sRows, DROWS);
        !          3688:     }
        !          3689:
        !          3690:     for (maxPermissibleDifference = 1; maxPermissibleDifference < max(DCOLS, DROWS); maxPermissibleDifference++) {
        !          3691:         for (i=0; i < DCOLS; i++) {
        !          3692:             for (j=0; j<DROWS; j++) {
        !          3693:                 dist = abs(sCols[i] - monst->xLoc) + abs(sRows[j] - monst->yLoc);
        !          3694:                 if (dist <= maxPermissibleDifference
        !          3695:                     && dist > 0
        !          3696:                     && !(pmap[sCols[i]][sRows[j]].flags & (HAS_PLAYER | HAS_MONSTER))
        !          3697:                     && !monsterAvoids(monst, sCols[i], sRows[j])
        !          3698:                     && !(monst == &player && cellHasTerrainFlag(sCols[i], sRows[j], T_PATHING_BLOCKER))) {
        !          3699:
        !          3700:                     // Success!
        !          3701:                     *x = sCols[i];
        !          3702:                     *y = sRows[j];
        !          3703:                     return;
        !          3704:                 }
        !          3705:             }
        !          3706:         }
        !          3707:     }
        !          3708:     // Failure!
        !          3709:     *x = *y = -1;
        !          3710: }
        !          3711:
        !          3712: // blockingMap is optional
        !          3713: boolean getQualifyingLocNear(short loc[2],
        !          3714:                              short x, short y,
        !          3715:                              boolean hallwaysAllowed,
        !          3716:                              char blockingMap[DCOLS][DROWS],
        !          3717:                              unsigned long forbiddenTerrainFlags,
        !          3718:                              unsigned long forbiddenMapFlags,
        !          3719:                              boolean forbidLiquid,
        !          3720:                              boolean deterministic) {
        !          3721:     short i, j, k, candidateLocs, randIndex;
        !          3722:
        !          3723:     candidateLocs = 0;
        !          3724:
        !          3725:     // count up the number of candidate locations
        !          3726:     for (k=0; k<max(DROWS, DCOLS) && !candidateLocs; k++) {
        !          3727:         for (i = x-k; i <= x+k; i++) {
        !          3728:             for (j = y-k; j <= y+k; j++) {
        !          3729:                 if (coordinatesAreInMap(i, j)
        !          3730:                     && (i == x-k || i == x+k || j == y-k || j == y+k)
        !          3731:                     && (!blockingMap || !blockingMap[i][j])
        !          3732:                     && !cellHasTerrainFlag(i, j, forbiddenTerrainFlags)
        !          3733:                     && !(pmap[i][j].flags & forbiddenMapFlags)
        !          3734:                     && (!forbidLiquid || pmap[i][j].layers[LIQUID] == NOTHING)
        !          3735:                     && (hallwaysAllowed || passableArcCount(i, j) < 2)) {
        !          3736:                     candidateLocs++;
        !          3737:                 }
        !          3738:             }
        !          3739:         }
        !          3740:     }
        !          3741:
        !          3742:     if (candidateLocs == 0) {
        !          3743:         return false;
        !          3744:     }
        !          3745:
        !          3746:     // and pick one
        !          3747:     if (deterministic) {
        !          3748:         randIndex = 1 + candidateLocs / 2;
        !          3749:     } else {
        !          3750:         randIndex = rand_range(1, candidateLocs);
        !          3751:     }
        !          3752:
        !          3753:     for (k=0; k<max(DROWS, DCOLS); k++) {
        !          3754:         for (i = x-k; i <= x+k; i++) {
        !          3755:             for (j = y-k; j <= y+k; j++) {
        !          3756:                 if (coordinatesAreInMap(i, j)
        !          3757:                     && (i == x-k || i == x+k || j == y-k || j == y+k)
        !          3758:                     && (!blockingMap || !blockingMap[i][j])
        !          3759:                     && !cellHasTerrainFlag(i, j, forbiddenTerrainFlags)
        !          3760:                     && !(pmap[i][j].flags & forbiddenMapFlags)
        !          3761:                     && (!forbidLiquid || pmap[i][j].layers[LIQUID] == NOTHING)
        !          3762:                     && (hallwaysAllowed || passableArcCount(i, j) < 2)) {
        !          3763:                     if (--randIndex == 0) {
        !          3764:                         loc[0] = i;
        !          3765:                         loc[1] = j;
        !          3766:                         return true;
        !          3767:                     }
        !          3768:                 }
        !          3769:             }
        !          3770:         }
        !          3771:     }
        !          3772:
        !          3773:     brogueAssert(false);
        !          3774:     return false; // should never reach this point
        !          3775: }
        !          3776:
        !          3777: boolean getQualifyingGridLocNear(short loc[2],
        !          3778:                                  short x, short y,
        !          3779:                                  boolean grid[DCOLS][DROWS],
        !          3780:                                  boolean deterministic) {
        !          3781:     short i, j, k, candidateLocs, randIndex;
        !          3782:
        !          3783:     candidateLocs = 0;
        !          3784:
        !          3785:     // count up the number of candidate locations
        !          3786:     for (k=0; k<max(DROWS, DCOLS) && !candidateLocs; k++) {
        !          3787:         for (i = x-k; i <= x+k; i++) {
        !          3788:             for (j = y-k; j <= y+k; j++) {
        !          3789:                 if (coordinatesAreInMap(i, j)
        !          3790:                     && (i == x-k || i == x+k || j == y-k || j == y+k)
        !          3791:                     && grid[i][j]) {
        !          3792:
        !          3793:                     candidateLocs++;
        !          3794:                 }
        !          3795:             }
        !          3796:         }
        !          3797:     }
        !          3798:
        !          3799:     if (candidateLocs == 0) {
        !          3800:         return false;
        !          3801:     }
        !          3802:
        !          3803:     // and pick one
        !          3804:     if (deterministic) {
        !          3805:         randIndex = 1 + candidateLocs / 2;
        !          3806:     } else {
        !          3807:         randIndex = rand_range(1, candidateLocs);
        !          3808:     }
        !          3809:
        !          3810:     for (k=0; k<max(DROWS, DCOLS); k++) {
        !          3811:         for (i = x-k; i <= x+k; i++) {
        !          3812:             for (j = y-k; j <= y+k; j++) {
        !          3813:                 if (coordinatesAreInMap(i, j)
        !          3814:                     && (i == x-k || i == x+k || j == y-k || j == y+k)
        !          3815:                     && grid[i][j]) {
        !          3816:
        !          3817:                     if (--randIndex == 0) {
        !          3818:                         loc[0] = i;
        !          3819:                         loc[1] = j;
        !          3820:                         return true;
        !          3821:                     }
        !          3822:                 }
        !          3823:             }
        !          3824:         }
        !          3825:     }
        !          3826:
        !          3827:     brogueAssert(false);
        !          3828:     return false; // should never reach this point
        !          3829: }
        !          3830:
        !          3831: void makeMonsterDropItem(creature *monst) {
        !          3832:     short x, y;
        !          3833:     getQualifyingPathLocNear(&x, &y, monst->xLoc, monst->yLoc, true,
        !          3834:                              (T_DIVIDES_LEVEL), 0,
        !          3835:                              T_OBSTRUCTS_ITEMS, (HAS_PLAYER | HAS_STAIRS | HAS_ITEM), false);
        !          3836:     placeItem(monst->carriedItem, x, y);
        !          3837:     monst->carriedItem = NULL;
        !          3838:     refreshDungeonCell(x, y);
        !          3839: }
        !          3840:
        !          3841: void checkForContinuedLeadership(creature *monst) {
        !          3842:     creature *follower;
        !          3843:     boolean maintainLeadership = false;
        !          3844:
        !          3845:     if (monst->bookkeepingFlags & MB_LEADER) {
        !          3846:         for (follower = monsters->nextCreature; follower != NULL; follower = follower->nextCreature) {
        !          3847:             if (follower->leader == monst && monst != follower) {
        !          3848:                 maintainLeadership = true;
        !          3849:                 break;
        !          3850:             }
        !          3851:         }
        !          3852:     }
        !          3853:     if (!maintainLeadership) {
        !          3854:         monst->bookkeepingFlags &= ~MB_LEADER;
        !          3855:     }
        !          3856: }
        !          3857:
        !          3858: void demoteMonsterFromLeadership(creature *monst) {
        !          3859:     creature *follower, *newLeader = NULL;
        !          3860:     boolean atLeastOneNewFollower = false;
        !          3861:
        !          3862:     monst->bookkeepingFlags &= ~MB_LEADER;
        !          3863:     if (monst->mapToMe) {
        !          3864:         freeGrid(monst->mapToMe);
        !          3865:         monst->mapToMe = NULL;
        !          3866:     }
        !          3867:
        !          3868:     for (int level = 0; level <= DEEPEST_LEVEL; level++) {
        !          3869:         if (rogue.patchVersion < 1 && level > 0) break; // to play back 1.9.0 recordings, skip other levels
        !          3870:         // we'll work on this level's monsters first, so that the new leader is preferably on the same level
        !          3871:         creature *firstMonster = (level == 0 ? monsters->nextCreature : levels[level-1].monsters);
        !          3872:         for (follower = firstMonster; follower != NULL; follower = follower->nextCreature) {
        !          3873:             if (follower == monst || follower->leader != monst) continue;
        !          3874:             if (follower->bookkeepingFlags & MB_BOUND_TO_LEADER) {
        !          3875:                 // gonna die in playerTurnEnded().
        !          3876:                 follower->leader = NULL;
        !          3877:                 follower->bookkeepingFlags &= ~MB_FOLLOWER;
        !          3878:             } else if (newLeader) {
        !          3879:                 follower->leader = newLeader;
        !          3880:                 atLeastOneNewFollower = true;
        !          3881:                 follower->targetWaypointIndex = monst->targetWaypointIndex;
        !          3882:                 if (follower->targetWaypointIndex >= 0) {
        !          3883:                     follower->waypointAlreadyVisited[follower->targetWaypointIndex] = false;
        !          3884:                 }
        !          3885:             } else {
        !          3886:                 newLeader = follower;
        !          3887:                 follower->bookkeepingFlags |= MB_LEADER;
        !          3888:                 follower->bookkeepingFlags &= ~MB_FOLLOWER;
        !          3889:                 follower->leader = NULL;
        !          3890:             }
        !          3891:         }
        !          3892:     }
        !          3893:
        !          3894:     if (newLeader
        !          3895:         && !atLeastOneNewFollower) {
        !          3896:         newLeader->bookkeepingFlags &= ~MB_LEADER;
        !          3897:     }
        !          3898:
        !          3899:     for (int level = 0; level <= DEEPEST_LEVEL; level++) {
        !          3900:         if (rogue.patchVersion < 1 && level > 0) break;
        !          3901:         creature *firstMonster = (level == 0 ? dormantMonsters->nextCreature : levels[level-1].dormantMonsters);
        !          3902:         for (follower = firstMonster; follower != NULL; follower = follower->nextCreature) {
        !          3903:             if (follower == monst || follower->leader != monst) continue;
        !          3904:             follower->leader = NULL;
        !          3905:             follower->bookkeepingFlags &= ~MB_FOLLOWER;
        !          3906:         }
        !          3907:     }
        !          3908: }
        !          3909:
        !          3910: // Makes a monster dormant, or awakens it from that state
        !          3911: void toggleMonsterDormancy(creature *monst) {
        !          3912:     creature *prevMonst;
        !          3913:     //short loc[2] = {0, 0};
        !          3914:
        !          3915:     for (prevMonst = dormantMonsters; prevMonst != NULL; prevMonst = prevMonst->nextCreature) {
        !          3916:         if (prevMonst->nextCreature == monst) {
        !          3917:             // Found it! It's dormant. Wake it up.
        !          3918:
        !          3919:             // Remove it from the dormant chain.
        !          3920:             prevMonst->nextCreature = monst->nextCreature;
        !          3921:
        !          3922:             // Add it to the normal chain.
        !          3923:             monst->nextCreature = monsters->nextCreature;
        !          3924:             monsters->nextCreature = monst;
        !          3925:
        !          3926:             pmap[monst->xLoc][monst->yLoc].flags &= ~HAS_DORMANT_MONSTER;
        !          3927:
        !          3928:             // Does it need a new location?
        !          3929:             if (pmap[monst->xLoc][monst->yLoc].flags & (HAS_MONSTER | HAS_PLAYER)) { // Occupied!
        !          3930:                 getQualifyingPathLocNear(&(monst->xLoc), &(monst->yLoc), monst->xLoc, monst->yLoc, true,
        !          3931:                                          T_DIVIDES_LEVEL & avoidedFlagsForMonster(&(monst->info)), HAS_PLAYER,
        !          3932:                                          avoidedFlagsForMonster(&(monst->info)), (HAS_PLAYER | HAS_MONSTER | HAS_STAIRS), false);
        !          3933: //              getQualifyingLocNear(loc, monst->xLoc, monst->yLoc, true, 0, T_PATHING_BLOCKER, (HAS_PLAYER | HAS_MONSTER), false, false);
        !          3934: //              monst->xLoc = loc[0];
        !          3935: //              monst->yLoc = loc[1];
        !          3936:             }
        !          3937:
        !          3938:             if (monst->bookkeepingFlags & MB_MARKED_FOR_SACRIFICE) {
        !          3939:                 monst->bookkeepingFlags |= MB_TELEPATHICALLY_REVEALED;
        !          3940:                 if (monst->carriedItem) {
        !          3941:                     makeMonsterDropItem(monst);
        !          3942:                 }
        !          3943:             }
        !          3944:
        !          3945:             // Miscellaneous transitional tasks.
        !          3946:             // Don't want it to move before the player has a chance to react.
        !          3947:             monst->ticksUntilTurn = 200;
        !          3948:
        !          3949:             pmap[monst->xLoc][monst->yLoc].flags |= HAS_MONSTER;
        !          3950:             monst->bookkeepingFlags &= ~MB_IS_DORMANT;
        !          3951:             fadeInMonster(monst);
        !          3952:             return;
        !          3953:         }
        !          3954:     }
        !          3955:
        !          3956:     for (prevMonst = monsters; prevMonst != NULL; prevMonst = prevMonst->nextCreature) {
        !          3957:         if (prevMonst->nextCreature == monst) {
        !          3958:             // Found it! It's alive. Put it into dormancy.
        !          3959:             // Remove it from the monsters chain.
        !          3960:             prevMonst->nextCreature = monst->nextCreature;
        !          3961:             // Add it to the dormant chain.
        !          3962:             monst->nextCreature = dormantMonsters->nextCreature;
        !          3963:             dormantMonsters->nextCreature = monst;
        !          3964:             // Miscellaneous transitional tasks.
        !          3965:             pmap[monst->xLoc][monst->yLoc].flags &= ~HAS_MONSTER;
        !          3966:             pmap[monst->xLoc][monst->yLoc].flags |= HAS_DORMANT_MONSTER;
        !          3967:             monst->bookkeepingFlags |= MB_IS_DORMANT;
        !          3968:             return;
        !          3969:         }
        !          3970:     }
        !          3971: }
        !          3972:
        !          3973: boolean staffOrWandEffectOnMonsterDescription(char *newText, item *theItem, creature *monst) {
        !          3974:     char theItemName[COLS], monstName[COLS];
        !          3975:     boolean successfulDescription = false;
        !          3976:     fixpt enchant = netEnchant(theItem);
        !          3977:
        !          3978:     if ((theItem->category & (STAFF | WAND))
        !          3979:         && tableForItemCategory(theItem->category, NULL)[theItem->kind].identified) {
        !          3980:
        !          3981:         monsterName(monstName, monst, true);
        !          3982:         itemName(theItem, theItemName, false, false, NULL);
        !          3983:
        !          3984:         switch (boltEffectForItem(theItem)) {
        !          3985:             case BE_DAMAGE:
        !          3986:                 if ((boltCatalog[boltForItem(theItem)].flags & BF_FIERY) && (monst->status[STATUS_IMMUNE_TO_FIRE])
        !          3987:                     || (monst->info.flags & MONST_INVULNERABLE)) {
        !          3988:
        !          3989:                     sprintf(newText, "\n     Your %s (%c) will not harm %s.",
        !          3990:                             theItemName,
        !          3991:                             theItem->inventoryLetter,
        !          3992:                             monstName);
        !          3993:                     successfulDescription = true;
        !          3994:                 } else if (theItem->flags & (ITEM_MAX_CHARGES_KNOWN | ITEM_IDENTIFIED)) {
        !          3995:                     if (staffDamageLow(enchant) >= monst->currentHP) {
        !          3996:                         sprintf(newText, "\n     Your %s (%c) will %s the %s in one hit.",
        !          3997:                                 theItemName,
        !          3998:                                 theItem->inventoryLetter,
        !          3999:                                 (monst->info.flags & MONST_INANIMATE) ? "destroy" : "kill",
        !          4000:                                 monstName);
        !          4001:                     } else {
        !          4002:                         sprintf(newText, "\n     Your %s (%c) will hit %s for between %i%% and %i%% of $HISHER current health.",
        !          4003:                                 theItemName,
        !          4004:                                 theItem->inventoryLetter,
        !          4005:                                 monstName,
        !          4006:                                 100 * staffDamageLow(enchant) / monst->currentHP,
        !          4007:                                 100 * staffDamageHigh(enchant) / monst->currentHP);
        !          4008:                     }
        !          4009:                     successfulDescription = true;
        !          4010:                 }
        !          4011:                 break;
        !          4012:             case BE_POISON:
        !          4013:                 if (monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE)) {
        !          4014:                     sprintf(newText, "\n     Your %s (%c) will not affect %s.",
        !          4015:                             theItemName,
        !          4016:                             theItem->inventoryLetter,
        !          4017:                             monstName);
        !          4018:                 } else {
        !          4019:                     sprintf(newText, "\n     Your %s (%c) will poison %s for %i%% of $HISHER current health.",
        !          4020:                             theItemName,
        !          4021:                             theItem->inventoryLetter,
        !          4022:                             monstName,
        !          4023:                             100 * staffPoison(enchant) / monst->currentHP);
        !          4024:                 }
        !          4025:                 successfulDescription = true;
        !          4026:                 break;
        !          4027:             case BE_DOMINATION:
        !          4028:                 if (monst->creatureState != MONSTER_ALLY) {
        !          4029:                     if (monst->info.flags & MONST_INANIMATE) {
        !          4030:                         sprintf(newText, "\n     A wand of domination will have no effect on objects like %s.",
        !          4031:                                 monstName);
        !          4032:                     } else if (monst->info.flags & MONST_INVULNERABLE) {
        !          4033:                             sprintf(newText, "\n     A wand of domination will not affect %s.",
        !          4034:                                     monstName);
        !          4035:                     } else if (wandDominate(monst) <= 0) {
        !          4036:                         sprintf(newText, "\n     A wand of domination will fail at %s's current health level.",
        !          4037:                                 monstName);
        !          4038:                     } else if (wandDominate(monst) >= 100) {
        !          4039:                         sprintf(newText, "\n     A wand of domination will always succeed at %s's current health level.",
        !          4040:                                 monstName);
        !          4041:                     } else {
        !          4042:                         sprintf(newText, "\n     A wand of domination will have a %i%% chance of success at %s's current health level.",
        !          4043:                                 wandDominate(monst),
        !          4044:                                 monstName);
        !          4045:                     }
        !          4046:                     successfulDescription = true;
        !          4047:                 }
        !          4048:                 break;
        !          4049:             default:
        !          4050:                 strcpy(newText, "");
        !          4051:                 break;
        !          4052:         }
        !          4053:     }
        !          4054:     return successfulDescription;
        !          4055: }
        !          4056:
        !          4057: void monsterDetails(char buf[], creature *monst) {
        !          4058:     char monstName[COLS], capMonstName[COLS], theItemName[COLS * 3], newText[20*COLS];
        !          4059:     short i, j, combatMath, combatMath2, playerKnownAverageDamage, playerKnownMaxDamage, commaCount, realArmorValue;
        !          4060:     boolean anyFlags, alreadyDisplayedDominationText = false;
        !          4061:     item *theItem;
        !          4062:
        !          4063:     buf[0] = '\0';
        !          4064:     commaCount = 0;
        !          4065:
        !          4066:     monsterName(monstName, monst, true);
        !          4067:     strcpy(capMonstName, monstName);
        !          4068:     upperCase(capMonstName);
        !          4069:
        !          4070:     if (!(monst->info.flags & MONST_RESTRICTED_TO_LIQUID)
        !          4071:          || cellHasTMFlag(monst->xLoc, monst->yLoc, TM_ALLOWS_SUBMERGING)) {
        !          4072:         // If the monster is not a beached whale, print the ordinary flavor text.
        !          4073:         sprintf(newText, "     %s\n     ", monsterText[monst->info.monsterID].flavorText);
        !          4074:         strcat(buf, newText);
        !          4075:     }
        !          4076:
        !          4077:     if (monst->mutationIndex >= 0) {
        !          4078:         i = strlen(buf);
        !          4079:         i = encodeMessageColor(buf, i, mutationCatalog[monst->mutationIndex].textColor);
        !          4080:         strcpy(newText, mutationCatalog[monst->mutationIndex].description);
        !          4081:         resolvePronounEscapes(newText, monst);
        !          4082:         upperCase(newText);
        !          4083:         strcat(newText, "\n     ");
        !          4084:         strcat(buf, newText);
        !          4085:         i = strlen(buf);
        !          4086:         i = encodeMessageColor(buf, i, &white);
        !          4087:     }
        !          4088:
        !          4089:     if (!(monst->info.flags & MONST_ATTACKABLE_THRU_WALLS)
        !          4090:         && cellHasTerrainFlag(monst->xLoc, monst->yLoc, T_OBSTRUCTS_PASSABILITY)) {
        !          4091:         // If the monster is trapped in impassible terrain, explain as much.
        !          4092:         sprintf(newText, "%s is trapped %s %s.\n     ",
        !          4093:                 capMonstName,
        !          4094:                 (tileCatalog[pmap[monst->xLoc][monst->yLoc].layers[layerWithFlag(monst->xLoc, monst->yLoc, T_OBSTRUCTS_PASSABILITY)]].mechFlags & TM_STAND_IN_TILE) ? "in" : "on",
        !          4095:                 tileCatalog[pmap[monst->xLoc][monst->yLoc].layers[layerWithFlag(monst->xLoc, monst->yLoc, T_OBSTRUCTS_PASSABILITY)]].description);
        !          4096:         strcat(buf, newText);
        !          4097:     }
        !          4098:
        !          4099:     // Allegiance and ability slots
        !          4100:     newText[0] = '\0';
        !          4101:     if (monst->creatureState == MONSTER_ALLY) {
        !          4102:         i = strlen(buf);
        !          4103:         i = encodeMessageColor(buf, i, &goodMessageColor);
        !          4104:
        !          4105:         sprintf(newText, "%s is your ally.\n     ", capMonstName);
        !          4106:         strcat(buf, newText);
        !          4107:         if (monst->newPowerCount > 0) {
        !          4108:             i = strlen(buf);
        !          4109:             i = encodeMessageColor(buf, i, &advancementMessageColor);
        !          4110:
        !          4111:             if (monst->newPowerCount == 1) {
        !          4112:                 sprintf(newText, "$HESHE seems ready to learn something new.\n     ");
        !          4113:             } else {
        !          4114:                 sprintf(newText, "$HESHE seems ready to learn %i new talents.\n     ", monst->newPowerCount);
        !          4115:             }
        !          4116:             resolvePronounEscapes(newText, monst); // So that it gets capitalized appropriately.
        !          4117:             upperCase(newText);
        !          4118:             strcat(buf, newText);
        !          4119:         }
        !          4120:     }
        !          4121:
        !          4122:     if (!rogue.armor || (rogue.armor->flags & ITEM_IDENTIFIED)) {
        !          4123:         combatMath2 = hitProbability(monst, &player);
        !          4124:     } else {
        !          4125:         realArmorValue = player.info.defense;
        !          4126:         player.info.defense = (armorTable[rogue.armor->kind].range.upperBound + armorTable[rogue.armor->kind].range.lowerBound) / 2;
        !          4127:         player.info.defense += 10 * strengthModifier(rogue.armor) / FP_FACTOR;
        !          4128:         combatMath2 = hitProbability(monst, &player);
        !          4129:         player.info.defense = realArmorValue;
        !          4130:     }
        !          4131:
        !          4132:     // Combat info for the monster attacking the player
        !          4133:     if ((monst->info.flags & MONST_RESTRICTED_TO_LIQUID) && !cellHasTMFlag(monst->xLoc, monst->yLoc, TM_ALLOWS_SUBMERGING)) {
        !          4134:         sprintf(newText, "     %s writhes helplessly on dry land.\n     ", capMonstName);
        !          4135:     } else if (rogue.armor
        !          4136:                && (rogue.armor->flags & ITEM_RUNIC)
        !          4137:                && (rogue.armor->flags & ITEM_RUNIC_IDENTIFIED)
        !          4138:                && rogue.armor->enchant2 == A_IMMUNITY
        !          4139:                && monsterIsInClass(monst, rogue.armor->vorpalEnemy)) {
        !          4140:
        !          4141:         itemName(rogue.armor, theItemName, false, false, NULL);
        !          4142:         sprintf(newText, "Your %s renders you immune to %s.\n     ", theItemName, monstName);
        !          4143:     } else if (monst->info.damage.upperBound * monsterDamageAdjustmentAmount(monst) / FP_FACTOR == 0) {
        !          4144:         sprintf(newText, "%s deals no direct damage.\n     ", capMonstName);
        !          4145:     } else {
        !          4146:         i = strlen(buf);
        !          4147:         i = encodeMessageColor(buf, i, &badMessageColor);
        !          4148:         if (monst->info.abilityFlags & MA_POISONS) {
        !          4149:             combatMath = player.status[STATUS_POISONED]; // combatMath is poison duration
        !          4150:             for (i = 0; combatMath * (player.poisonAmount + i) < player.currentHP; i++) {
        !          4151:                 combatMath += monst->info.damage.upperBound * monsterDamageAdjustmentAmount(monst) / FP_FACTOR;
        !          4152:             }
        !          4153:             if (i == 0) {
        !          4154:                 // Already fatally poisoned.
        !          4155:                 sprintf(newText, "%s has a %i%% chance to poison you and typically poisons for %i turns.\n     ",
        !          4156:                         capMonstName,
        !          4157:                         combatMath2,
        !          4158:                         (int) ((monst->info.damage.lowerBound + monst->info.damage.upperBound) * monsterDamageAdjustmentAmount(monst) / 2 / FP_FACTOR));
        !          4159:             } else {
        !          4160:             sprintf(newText, "%s has a %i%% chance to poison you, typically poisons for %i turns, and at worst, could fatally poison you in %i hit%s.\n     ",
        !          4161:                     capMonstName,
        !          4162:                     combatMath2,
        !          4163:                     (int) ((monst->info.damage.lowerBound + monst->info.damage.upperBound) * monsterDamageAdjustmentAmount(monst) / 2 / FP_FACTOR),
        !          4164:                     i,
        !          4165:                     (i > 1 ? "s" : ""));
        !          4166:             }
        !          4167:         } else {
        !          4168:             combatMath = ((player.currentHP + (monst->info.damage.upperBound * monsterDamageAdjustmentAmount(monst) / FP_FACTOR) - 1) * FP_FACTOR)
        !          4169:                     / (monst->info.damage.upperBound * monsterDamageAdjustmentAmount(monst));
        !          4170:             if (combatMath < 1) {
        !          4171:                 combatMath = 1;
        !          4172:             }
        !          4173:             sprintf(newText, "%s has a %i%% chance to hit you, typically hits for %i%% of your current health, and at worst, could defeat you in %i hit%s.\n     ",
        !          4174:                     capMonstName,
        !          4175:                     combatMath2,
        !          4176:                     (int) (100 * (monst->info.damage.lowerBound + monst->info.damage.upperBound) * monsterDamageAdjustmentAmount(monst) / 2 / player.currentHP / FP_FACTOR),
        !          4177:                     combatMath,
        !          4178:                     (combatMath > 1 ? "s" : ""));
        !          4179:         }
        !          4180:     }
        !          4181:     upperCase(newText);
        !          4182:     strcat(buf, newText);
        !          4183:
        !          4184:     if (!rogue.weapon || (rogue.weapon->flags & ITEM_IDENTIFIED)) {
        !          4185:         playerKnownAverageDamage = (player.info.damage.upperBound + player.info.damage.lowerBound) / 2;
        !          4186:         playerKnownMaxDamage = player.info.damage.upperBound;
        !          4187:     } else {
        !          4188:         fixpt strengthFactor = damageFraction(strengthModifier(rogue.weapon));
        !          4189:         short tempLow = rogue.weapon->damage.lowerBound * strengthFactor / FP_FACTOR;
        !          4190:         short tempHigh = rogue.weapon->damage.upperBound * strengthFactor / FP_FACTOR;
        !          4191:
        !          4192:         playerKnownAverageDamage = max(1, (tempLow + tempHigh) / 2);
        !          4193:         playerKnownMaxDamage = max(1, tempHigh);
        !          4194:     }
        !          4195:
        !          4196:     // Combat info for the player attacking the monster (or whether it's captive)
        !          4197:     if (playerKnownMaxDamage == 0) {
        !          4198:         i = strlen(buf);
        !          4199:         i = encodeMessageColor(buf, i, &white);
        !          4200:
        !          4201:         sprintf(newText, "You deal no direct damage.");
        !          4202:     } else if (rogue.weapon
        !          4203:                && (rogue.weapon->flags & ITEM_RUNIC)
        !          4204:                && (rogue.weapon->flags & ITEM_RUNIC_IDENTIFIED)
        !          4205:                && rogue.weapon->enchant2 == W_SLAYING
        !          4206:                && monsterIsInClass(monst, rogue.weapon->vorpalEnemy)) {
        !          4207:
        !          4208:         i = strlen(buf);
        !          4209:         i = encodeMessageColor(buf, i, &goodMessageColor);
        !          4210:         itemName(rogue.weapon, theItemName, false, false, NULL);
        !          4211:         sprintf(newText, "Your %s will slay %s in one stroke.", theItemName, monstName);
        !          4212:     } else if (monst->info.flags & (MONST_INVULNERABLE | MONST_IMMUNE_TO_WEAPONS)) {
        !          4213:         i = strlen(buf);
        !          4214:         i = encodeMessageColor(buf, i, &white);
        !          4215:         sprintf(newText, "%s is immune to your attacks.", monstName);
        !          4216:     } else if (monst->bookkeepingFlags & MB_CAPTIVE) {
        !          4217:         i = strlen(buf);
        !          4218:         i = encodeMessageColor(buf, i, &goodMessageColor);
        !          4219:
        !          4220:         sprintf(newText, "%s is being held captive.", capMonstName);
        !          4221:     } else {
        !          4222:         i = strlen(buf);
        !          4223:         i = encodeMessageColor(buf, i, &goodMessageColor);
        !          4224:
        !          4225:         combatMath = (monst->currentHP + playerKnownMaxDamage - 1) / playerKnownMaxDamage;
        !          4226:         if (combatMath < 1) {
        !          4227:             combatMath = 1;
        !          4228:         }
        !          4229:         if (rogue.weapon && !(rogue.weapon->flags & ITEM_IDENTIFIED)) {
        !          4230:             realArmorValue = rogue.weapon->enchant1;
        !          4231:             rogue.weapon->enchant1 = 0;
        !          4232:             combatMath2 = hitProbability(&player, monst);
        !          4233:             rogue.weapon->enchant1 = realArmorValue;
        !          4234:         } else {
        !          4235:             combatMath2 = hitProbability(&player, monst);
        !          4236:         }
        !          4237:         sprintf(newText, "You have a %i%% chance to hit %s, typically hit for %i%% of $HISHER current health, and at best, could defeat $HIMHER in %i hit%s.",
        !          4238:                 combatMath2,
        !          4239:                 monstName,
        !          4240:                 100 * playerKnownAverageDamage / monst->currentHP,
        !          4241:                 combatMath,
        !          4242:                 (combatMath > 1 ? "s" : ""));
        !          4243:     }
        !          4244:     upperCase(newText);
        !          4245:     strcat(buf, newText);
        !          4246:
        !          4247:     for (theItem = packItems->nextItem; theItem != NULL; theItem = theItem->nextItem) {
        !          4248:         if (staffOrWandEffectOnMonsterDescription(newText, theItem, monst)) {
        !          4249:             if (boltEffectForItem(theItem) == BE_DOMINATION) {
        !          4250:                 if (alreadyDisplayedDominationText) {
        !          4251:                     continue;
        !          4252:                 } else {
        !          4253:                     alreadyDisplayedDominationText = true;
        !          4254:                 }
        !          4255:             }
        !          4256:             i = strlen(buf);
        !          4257:             i = encodeMessageColor(buf, i, &itemMessageColor);
        !          4258:             strcat(buf, newText);
        !          4259:         }
        !          4260:     }
        !          4261:
        !          4262:     if (monst->carriedItem) {
        !          4263:         i = strlen(buf);
        !          4264:         i = encodeMessageColor(buf, i, &itemMessageColor);
        !          4265:         itemName(monst->carriedItem, theItemName, true, true, NULL);
        !          4266:         sprintf(newText, "%s has %s.", capMonstName, theItemName);
        !          4267:         upperCase(newText);
        !          4268:         strcat(buf, "\n     ");
        !          4269:         strcat(buf, newText);
        !          4270:     }
        !          4271:
        !          4272:     strcat(buf, "\n     ");
        !          4273:
        !          4274:     i = strlen(buf);
        !          4275:     i = encodeMessageColor(buf, i, &white);
        !          4276:
        !          4277:     anyFlags = false;
        !          4278:     sprintf(newText, "%s ", capMonstName);
        !          4279:
        !          4280:     if (monst->attackSpeed < 100) {
        !          4281:         strcat(newText, "attacks quickly");
        !          4282:         anyFlags = true;
        !          4283:     } else if (monst->attackSpeed > 100) {
        !          4284:         strcat(newText, "attacks slowly");
        !          4285:         anyFlags = true;
        !          4286:     }
        !          4287:
        !          4288:     if (monst->movementSpeed < 100) {
        !          4289:         if (anyFlags) {
        !          4290:             strcat(newText, "& ");
        !          4291:             commaCount++;
        !          4292:         }
        !          4293:         strcat(newText, "moves quickly");
        !          4294:         anyFlags = true;
        !          4295:     } else if (monst->movementSpeed > 100) {
        !          4296:         if (anyFlags) {
        !          4297:             strcat(newText, "& ");
        !          4298:             commaCount++;
        !          4299:         }
        !          4300:         strcat(newText, "moves slowly");
        !          4301:         anyFlags = true;
        !          4302:     }
        !          4303:
        !          4304:     if (monst->info.turnsBetweenRegen == 0) {
        !          4305:         if (anyFlags) {
        !          4306:             strcat(newText, "& ");
        !          4307:             commaCount++;
        !          4308:         }
        !          4309:         strcat(newText, "does not regenerate");
        !          4310:         anyFlags = true;
        !          4311:     } else if (monst->info.turnsBetweenRegen < 5000) {
        !          4312:         if (anyFlags) {
        !          4313:             strcat(newText, "& ");
        !          4314:             commaCount++;
        !          4315:         }
        !          4316:         strcat(newText, "regenerates quickly");
        !          4317:         anyFlags = true;
        !          4318:     }
        !          4319:
        !          4320:     // bolt flags
        !          4321:     for (i = 0; monst->info.bolts[i] != BOLT_NONE; i++) {
        !          4322:         if (boltCatalog[monst->info.bolts[i]].abilityDescription[0]) {
        !          4323:             if (anyFlags) {
        !          4324:                 strcat(newText, "& ");
        !          4325:                 commaCount++;
        !          4326:             }
        !          4327:             strcat(newText, boltCatalog[monst->info.bolts[i]].abilityDescription);
        !          4328:             anyFlags = true;
        !          4329:         }
        !          4330:     }
        !          4331:
        !          4332:     // ability flags
        !          4333:     for (i=0; i<32; i++) {
        !          4334:         if ((monst->info.abilityFlags & (Fl(i)))
        !          4335:             && monsterAbilityFlagDescriptions[i][0]) {
        !          4336:             if (anyFlags) {
        !          4337:                 strcat(newText, "& ");
        !          4338:                 commaCount++;
        !          4339:             }
        !          4340:             strcat(newText, monsterAbilityFlagDescriptions[i]);
        !          4341:             anyFlags = true;
        !          4342:         }
        !          4343:     }
        !          4344:
        !          4345:     // behavior flags
        !          4346:     for (i=0; i<32; i++) {
        !          4347:         if ((monst->info.flags & (Fl(i)))
        !          4348:             && monsterBehaviorFlagDescriptions[i][0]) {
        !          4349:             if (anyFlags) {
        !          4350:                 strcat(newText, "& ");
        !          4351:                 commaCount++;
        !          4352:             }
        !          4353:             strcat(newText, monsterBehaviorFlagDescriptions[i]);
        !          4354:             anyFlags = true;
        !          4355:         }
        !          4356:     }
        !          4357:
        !          4358:     // bookkeeping flags
        !          4359:     for (i=0; i<32; i++) {
        !          4360:         if ((monst->bookkeepingFlags & (Fl(i)))
        !          4361:             && monsterBookkeepingFlagDescriptions[i][0]) {
        !          4362:             if (anyFlags) {
        !          4363:                 strcat(newText, "& ");
        !          4364:                 commaCount++;
        !          4365:             }
        !          4366:             strcat(newText, monsterBookkeepingFlagDescriptions[i]);
        !          4367:             anyFlags = true;
        !          4368:         }
        !          4369:     }
        !          4370:
        !          4371:     if (anyFlags) {
        !          4372:         strcat(newText, ". ");
        !          4373:         //strcat(buf, "\n\n");
        !          4374:         j = strlen(buf);
        !          4375:         for (i=0; newText[i] != '\0'; i++) {
        !          4376:             if (newText[i] == '&') {
        !          4377:                 if (!--commaCount) {
        !          4378:                     buf[j] = '\0';
        !          4379:                     strcat(buf, " and");
        !          4380:                     j += 4;
        !          4381:                 } else {
        !          4382:                     buf[j++] = ',';
        !          4383:                 }
        !          4384:             } else {
        !          4385:                 buf[j++] = newText[i];
        !          4386:             }
        !          4387:         }
        !          4388:         buf[j] = '\0';
        !          4389:     }
        !          4390:     resolvePronounEscapes(buf, monst);
        !          4391: }

CVSweb