[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

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